This example is built with:
- @swing.xyz/sdk
- @thirdweb-dev/react
- @thirdweb-dev/sdk
- TronLink Wallet Adapter
- Tron React Hooks
- Next.js App Router
- Tailwind CSS
View the live demo https://swaps-api-nextjs-tron.vercel.app
The implementation of Swing's Cross-chain API and Platform API can be found in src/components/Swap.tsx
This example demonstrates how you can perform a cross-chain transaction between the Tron and Ethereum chains using Swing's Cross-Chain and Platform APIs via Swing's SDK.
In this example, we will be using thirdweb's SDK and TronLink's Wallet Adapter connector to connect to a user's Ethereum and Tron wallets, respectively. We will also demonstrate how to utilize Swing's SDK exported API functions, namely crossChainAPI
and platformAPI
, to build out a fully functionaly cross-chain application.
The process/steps for performing a TRX to ETH transaction, and vice versa, are as follows:
- Getting a quote and selecting the best route
- Sending a token approval request for ERC20 Tokens. (Optional for TRON > EVM Route)
- Sending a transaction
Although not essential for performing a swap transaction, providing your users with real-time updates on the transaction's status by polling the status can significantly enhance the user experience.
To get started with this template, first install the required npm dependencies:
yarn install
Next, launch the development server by running the following command:
yarn dev --filter=swaps-api-nextjs-tron
Finally, open http://localhost:3000 in your browser to view the website.
Swing's SDK contains two (2) vital objects that are crucial to our API integration. Namely, the platformAPI
and the crossChainAPI
objects. These objects are a wrapper for all of Swing's Cross-Chain and Platform APIs removing the need for developers to make API requests using libraries like fetch
or axios
. The SDK handles those API requests from behind the scenes.
We've included all the necessary request and response interfaces in the src/interfaces
folder to aid development.
Navigating to our src/services/requests.ts
, let's start by initializing Swing's SDK in our SwingServiceAPI
class:
import { SwingSDK } from "@swing.xyz/sdk";
export class SwingServiceAPI implements ISwingServiceAPI {
private readonly swingSDK: SwingSDK;
constructor() {
this.swingSDK = new SwingSDK({
projectId: "replug",
debug: true,
});
}
}
The SwingSDK
constructor accepts a projectId
as a mandatory parameter and a few other optional parameters:
Property | Example | Description |
---|---|---|
projectId |
replug |
Swing Platform project identifier |
debug |
true |
Enable verbose logging |
environment |
production |
Set's SwingAPI to operate either on testnet or mainnet chains |
analytics |
false |
Enable analytics and error reporting |
You can get your
projectId
by signing up to Swing!
To perform a swap between ETH and TRX, we first have to get a quote from Swing's Cross-Chain API.
URL: https://swap.prod.swing.xyz/v0/transfer/quote
Parameters:
Property | Example | Description |
---|---|---|
tokenAmount |
1000000000000000000 | Amount of the source token being sent (in wei for ETH). |
fromChain |
ethereum |
Source Chain slug |
fromUserAddress |
0x018c15DA1239B84b08283799B89045CD476BBbBb | Sender's wallet address |
fromTokenAddress |
0x0000000000000000000000000000000000000000 | Source Token Address |
tokenSymbol |
ETH |
Source Token slug |
toTokenAddress |
0x0000000000000000000000000000000000000000 | Destination Token Address. |
toTokenSymbol |
TRX |
Destination Token slug |
toChain |
tron |
Destination Chain slug |
toUserAddress |
TV6ybRmqiUK6a7JVMRwPg2cDDkLqgR5MaZ | Receiver's wallet address |
projectId |
replug |
Your project's ID |
Navigating to our src/services/requests.ts
file, you will find our method for getting a quote from Swing's Cross-Chain API called getQuoteRequest()
.
async getQuoteRequest(
queryParams: QuoteQueryParams,
): Promise<QuoteAPIResponse | undefined> {
try {
const response = await this.swingSDK.crossChainAPI.GET(
"/v0/transfer/quote",
{
params: {
query: queryParams,
},
},
);
return response.data;
} catch (error) {
console.error("Error fetching quote:", error);
throw error;
}
}
The response received from the getQuoteRequest
endpoint provides us with the fees
a user will have to pay when performing a transaction, as well as a list of possible routes
for the user to choose from.
The definition for the getQuoteRequest
response can be found in src/interfaces/quote.interface.ts.
export interface QuoteAPIResponse {
routes: Route[];
fromToken: Token;
fromChain: Chain;
toToken: Token;
toChain: Chain;
}
Each Route
contains a gasFee
, bridgeFee
and the amount of tokens the destination wallet will receive.
Here's an example response that contains the route data:
"routes": [
{
"duration": 1,
"gas": "1260090989371730",
"quote": {
"integration": "symbiosis",
"type": "swap",
"bridgeFee": "125402496",
"bridgeFeeInNativeToken": "0",
"amount": "60720124077",
"decimals": 6,
"amountUSD": "9296.676",
"bridgeFeeUSD": "19.199",
"bridgeFeeInNativeTokenUSD": "0",
"fees": [
{
"type": "bridge",
"amount": "125402496",
"amountUSD": "19.199",
"chainSlug": "tron",
"tokenSymbol": "TRX",
"tokenAddress": "0x0000000000000000000000000000000000000000",
"decimals": 6,
"deductedFromSourceToken": true
},
{
"type": "gas",
"amount": "1260090989371730",
"amountUSD": "2.954",
"chainSlug": "ethereum",
"tokenSymbol": "ETH",
"tokenAddress": "0x0000000000000000000000000000000000000000",
"decimals": 18,
"deductedFromSourceToken": false
}
]
},
"route": [
{
"bridge": "symbiosis",
"bridgeTokenAddress": "0x0000000000000000000000000000000000000000",
"steps": [
"allowance",
"approve",
"send"
],
"name": "ETH",
"part": 100
}
],
"distribution": {
"symbiosis": 1
},
"gasUSD": "2.954"
}
]
Navigating to our src/components/Swap.tsx
file, you'll find our defaultTransferParams
object which will store the default transaction config for our example:
const defaultTransferParams: TransferParams = {
tokenAmount: "1",
fromChain: "ethereum",
tokenSymbol: "ETH",
fromUserAddress: "",
fromTokenAddress: "0x0000000000000000000000000000000000000000",
fromNativeTokenSymbol: "ETH",
fromTokenIconUrl:
"https://raw.githubusercontent.com/Pymmdrza/Cryptocurrency_Logos/mainx/PNG/eth.png",
fromChainIconUrl:
"https://raw.githubusercontent.com/polkaswitch/assets/master/blockchains/ethereum/info/logo.png",
fromChainDecimal: 18,
toTokenAddress: "11111111111111111111111111111111",
toTokenSymbol: "TRX",
toNativeTokenSymbol: "TRX",
toChain: "tron",
toTokenIconUrl:
"https://raw.githubusercontent.com/Pymmdrza/Cryptocurrency_Logos/mainx/SVG/sol.svg",
toChainIconUrl:
"https://raw.githubusercontent.com/Pymmdrza/Cryptocurrency_Logos/mainx/SVG/sol.svg",
toUserAddress: "", //tron wallet address
toChainDecimal: 9,
};
If you're attempting to bridge an ERC20 token from a user's wallet to Tron, you need to prompt the user to approve the required amount of tokens to be bridged.
Navigating to our src/components/Swap.tsx
file, inside our startTransfer()
method, you will find our implementation of the getAllowanceRequest()
and getApprovalTxDataRequest()
methods. Before approving, you have to perform two checks:
- First, we will check if we're performing a native currency swap by comparing the values of
tokenSymbol
andfromNativeTokenSymbol
on the source chain. If we're not dealing with a native currency swap, we then proceed to ask for an allowance. - Next, we will check if an allowance has already been made by Swing on a user's wallet by calling the
getAllowanceRequest()
method. If no approved allowance is found, we will then proceed to make an approval request by calling thegetApprovalTxDataRequest()
method.
Since the /approval
and /approve
endpoints are specific to EVM chains, we have to check that source chain via fromChain
is anything but tron
. Skipping this check will result in the /approval
endpoint returning an error to the user:
{
"statusCode": 400,
"message": "Non-evm is not supported for approval method: tron",
"error": "Bad Request"
}
Let's execute these steps:
if (
transferParams.tokenSymbol !== transferParams.fromNativeTokenSymbol &&
transferParams.fromChain !== "tron"
) {
const checkAllowance = await getAllowanceRequest({
bridge: transferRoute.quote.integration,
fromAddress: transferParams.fromUserAddress,
fromChain: transferParams.fromChain,
tokenAddress: transferParams.fromTokenAddress,
tokenSymbol: transferParams.tokenSymbol,
toChain: transferParams.toChain,
toTokenAddress: transferParams.toTokenAddress!,
toTokenSymbol: transferParams.toTokenSymbol!,
contractCall: false,
});
if (checkAllowance.allowance === tokenAmount) {
setTransStatus({
status: `Wallet Interaction Required: Approval Token`,
});
const getApprovalTxData = await getApprovalTxDataRequest({
tokenAmount: Number(tokenAmount),
bridge: transferRoute.quote.integration,
fromAddress: transferParams.fromUserAddress,
fromChain: transferParams.fromChain,
tokenAddress: transferParams.fromTokenAddress,
tokenSymbol: transferParams.tokenSymbol,
toChain: transferParams.toChain,
toTokenAddress: transferParams.toTokenAddress!,
toTokenSymbol: transferParams.toTokenSymbol!,
contractCall: false,
});
const txData: TransactionDetails = {
data: getApprovalTxData.tx[0].data,
from: getApprovalTxData.tx[0].from,
to: getApprovalTxData.tx[0].to,
};
const txResponse = await signer?.sendTransaction(txData);
const receipt = await txResponse?.wait();
console.log("Transaction receipt:", receipt);
setTransStatus({ status: "Token allowance approved" });
}
}
After getting a quote, you'll next have to send a transaction to Swing's Cross-Chain API.
The steps for sending a transaction are as followed:
- First, we will make a request to
https://swap.prod.swing.xyz/v0/transfer/send
- Using the
txData
returned from the/send
request, sign the transaction by using a user's wallet
Navigating to our src/services/requests.ts
, you'll find our request implemenation for the /send
endpoint:
async sendTransactionRequest(
payload: SendTransactionPayload,
): Promise<SendTransactionApiResponse | undefined> {
try {
const response = await this.swingSDK.crossChainAPI.POST(
"/v0/transfer/send",
{
body: payload,
},
);
return response.data;
} catch (error) {
console.error("Error sending transaction:", error);
throw error;
}
}
The SendTransactionPayload
body payload contains the source chain
, destination chain
, tokenAmount
, and the desired route
.
URL: https://swap.prod.swing.xyz/v0/transfer/send
Parameters:
Key | Example | Description |
---|---|---|
fromChain |
ethereum | The blockchain where the transaction originates. |
fromTokenAddress |
0x0000000000000000000000000000000000000000 | Source Token Address |
fromUserAddress |
0x018c15DA1239B84b08283799B89045CD476BBbBb | Sender's wallet address |
tokenSymbol |
ETH | Source Token slug |
toTokenAddress |
0x0000000000000000000000000000000000000000 | Destination Token Address. |
toChain |
tron | Destination Source slug |
toTokenAmount |
4000000 | Amount of the destination token being received. |
toTokenSymbol |
TRX | Destination Chain slug |
toUserAddress |
TV6ybRmqiUK6a7JVMRwPg2cDDkLqgR5MaZ | Receiver's wallet address |
tokenAmount |
1000000000000000000 | Amount of the source token being sent (in wei for ETH). |
type |
swap | Type of transaction. |
projectId |
replug |
Your project's ID |
route |
see Route insrc/interfaces/send.interface.ts |
Selected Route |
Since performing a swap will change the state of a user's wallet, the next step of this transaction must be done via a Smart Contract
Transaction and not via Swing's Cross-Chain API. The response received from the sendTransactionRequest
endpoint provides us with the necessary txData/callData
needed to be passed on to a user's wallet to sign the transaction.
The txData
from the sendTransactionRequest
will look something like this:
{
....
"tx": {
"from": "0x018c15DA1239B84b08283799B89045CD476BBbBb",
"to": "0x39E3e49C99834C9573c9FC7Ff5A4B226cD7B0E63",
"data": "0x301a3720000000000000000000000000eeeeeeeeeeee........",
"value": "0x0e35fa931a0000",
"gas": "0x06a02f"
}
....
}
To demonstrate, we first make a request by calling the sendTransactionRequest
method.
// src/components/Swaps.tsx
const transfer = await sendTransactionRequest({
fromChain: transferParams.fromChain,
fromTokenAddress: transferParams.fromTokenAddress,
fromUserAddress: transferParams.fromUserAddress,
tokenSymbol: transferParams.tokenSymbol,
toTokenAddress: transferParams.toTokenAddress!,
toChain: transferParams.toChain,
toTokenAmount: transferRoute.quote.amount,
toTokenSymbol: transferParams.toTokenSymbol!,
toUserAddress: transferParams.toUserAddress!,
tokenAmount: convertEthToWei(
transferParams.tokenAmount,
transferParams.fromChainDecimal,
),
route: transferRoute.route,
type: "swap",
});
Next, we'll extract the txData
:
// src/components/Swaps.tsx
let txData: any = {
data: transfer.tx.data,
from: transfer.tx.from,
to: transfer.tx.to,
value: transfer.tx.value,
gasLimit: transfer.tx.gas,
};
Using our wallet provider, we will send a transaction to the user's wallet.
// src/components/Swaps.tsx
const txResponse = await signer?.sendTransaction(txData); // <- `txResponse` contains the `txHash` of our transaction. You will need this later for getting a transaction's status.
const receipt = await txResponse?.wait();
console.log("Transaction receipt:", receipt);
The definition for the sendTransactionRequest
response can be found in src/interfaces/send.interface.ts.
export interface SendTransactionApiResponse {
id: number;
fromToken: Token;
toToken: Token;
fromChain: Chain;
toChain: Chain;
route: Route[];
tx: TransactionDetails;
}
The
sendTransactionRequest
will return andid
whilst thetxResponse
will contain atxHash
which we will need later for checking the status of a transaction.
If you've decided to perform a cross chain swap using Tron as the source chain, you'll have to sign the transaction using a wallet provider that supports the Tron Network like TronLink.
Remember, you'll have to call the
/send
endpoint viasendTransactionRequest
before signing the transaction.
We will sign the txData
returned from the /send
endpoint using the Tronlink Wallet React Hooks library and then broadcast the transaction to the rest of the network using the Tronlink Wallet Adapter that comes installed with TronLink in your browser.
As a reminder, the txData
from the sendTransactionRequest
will look something like this:
{
....
"tx": {
"from": "0x018c15DA1239B84b08283799B89045CD476BBbBb",
"to": "0x39E3e49C99834C9573c9FC7Ff5A4B226cD7B0E63",
"data": "0x301a3720000000000000000000000000eeeeeeeeeeee........",
"value": "0x0e35fa931a0000",
"meta": {
"raw_data_hex": {
.....
}
}
"gas": "0x06a02f"
}
....
}
Note: You're only interested in the
meta
object in ourtxData
.
To sign a callData, you have to make a request to your TronLink Wallet and pass the meta
object to the signer function. The meta
object contains the necessary smart contract callData to be executed.
// Create and sign the transaction
const { wallet } = useWallet();
const signedTx = await wallet.adapter.signTransaction(txData.meta!);
Next, we will send the transaction to the network:
const broadcast =
await window?.tronLink?.tronWeb.trx.sendRawTransaction(signedTx);
Putting it all together:
// Update the sendTronTrans function
async function sendTronTrans(
txData: TransactionData,
): Promise<string | undefined> {
try {
if (!tronConnected || !wallet) {
throw new Error("Tron wallet is not connected");
}
// Create and sign the transaction
const signedTx = await wallet.adapter.signTransaction(txData.meta!);
// Broadcasting the transaction
const broadcast =
await window?.tronLink?.tronWeb.trx.sendRawTransaction(signedTx);
console.log(`broadcast: ${broadcast?.result}`);
return signedTx.txID as string;
} catch (error) {
console.error("Error sending Tron transaction:", error);
throw new Error((error as Error).message);
}
}
After sending a transaction over to the network, for the sake of user experience, it's best to poll the transaction status endpoint by periodically checking to see if the transaction is complete. This will let the user using your dapp know in realtime, the status of the current transaction.
Navigating to our src/services/requests.ts
file, you will find our method for getting a transaction status called getTransationStatus()
.
async getTransationStatusRequest(
queryParams: TransactionStatusParams,
): Promise<TransactionStatusAPIResponse | undefined> {
try {
const response = await this.swingSDK.platformAPI.GET(
"/projects/{projectId}/transactions/{transactionId}",
{
params: {
path: {
transactionId: queryParams.id,
projectId,
},
query: {
txHash: queryParams.txHash,
},
},
},
);
return response.data;
} catch (error) {
console.error("Error fetching transaction status:", error);
throw error;
}
}
The TransactionStatusParams
params contains the three properties, namely: id
, txHash
and projectId
URL: https://platform.swing.xyz/api/v1/projects/{projectId}/transactions/{transactionId}
Parameters:
Key | Example | Description |
---|---|---|
id |
239750 | Transaction ID from /send response |
txHash |
0x3b2a04e2d16489bcbbb10960a248..... | The transaction hash identifier. |
projectId |
replug |
Your project's ID |
To poll the /status
endpoint, you will use setTimeout()
to retry getTransationStatus()
over a period of time. We will define a function, pollTransactionStatus()
, which will recursively call getTransStatus()
until the transaction is completed.
// src/components/Swaps.tsx
async function getTransStatus(transId: string, txHash: string) {
const transactionStatus = await swingServiceAPI?.getTransationStatusRequest({
id: transId,
txHash,
});
setTransStatus(transactionStatus);
return transactionStatus;
}
async function pollTransactionStatus(transId: string, txHash: string) {
const transactionStatus = await getTransStatus(transId, txHash);
if (transactionStatus?.status! === "Pending") {
setTimeout(
() => pollTransactionStatus(transId, txHash),
transactionPollingDuration,
);
} else {
if (transactionStatus?.status === "Success") {
toast({
title: "Transaction Successful",
description: `Bridge Successful`,
});
} else if (transactionStatus?.status === "Failed") {
toast({
variant: "destructive",
title: "Transaction Failed",
description: transStatus?.errorReason,
});
}
setTransferRoute(null);
setIsTransacting(false);
(sendInputRef.current as HTMLInputElement).value = "";
}
}
In our startTransfer()
method, you will run the pollTransactionStatus()
immediately after our transaction has been sent to the network
// src/components/Swaps.tsx
let txHash = "";
if (transferParams.fromChain === "tron") {
const hash = await sendSolTrans({
...txData,
from: transferParams.fromUserAddress,
});
txHash = hash!;
} else {
const txResponse = await signer?.sendTransaction({
data: txData.data,
from: txData.from,
to: txData.to,
value: txData.value,
gasLimit: txData.gasLimit,
});
// Wait for the transaction to be mined
const receipt = await txResponse?.wait();
console.log("Transaction receipt:", receipt);
txHash = txResponse?.hash!;
}
pollTransactionStatus(transfer?.id.toString()!, txHash);
You can start editing this template by modifying the files in the /src
folder. The site will auto-update as you edit these files.