Usage and Examples

Examples showing how to use the SDK to bridge transactions, get bridge quotes, etc.

Initialization & Bridge Quote

To get started, here's a basic example of initialization of the SDK and a cross-chain swap quote being loaded:
import {
Bridge,
Tokens,
ChainId,
Networks,
} from "@synapseprotocol/sdk";
import {JsonRpcProvider} from "@ethersproject/providers";
import {parseUnits, formatUnits} from "@ethersproject/units";
// Use SDK Data about different chains
const AVAX_NETWORK = Networks.AVALANCHE;
// Initialize Bridge
const SYNAPSE_BRIDGE = new Bridge.SynapseBridge({
network: AVAX_NETWORK
});
// Set up some variables to prepare a Avalanche USDC -> BSC USDT quote
const
TOKEN_IN = Tokens.USDC,
TOKEN_OUT = Tokens.USDT,
CHAIN_OUT = ChainId.BSC,
INPUT_AMOUNT = parseUnits("1000", TOKEN_IN.decimals(AVAX_NETWORK.chainId));
SYNAPSE_BRIDGE.estimateBridgeTokenOutput({
tokenFrom: TOKEN_IN, // token to send from the source chain, in this case USDT on Avalanche
chainIdTo: CHAIN_OUT, // Chain ID of the destination chain, in this case BSC
tokenTo: TOKEN_OUT, // Token to be received on the destination chain, in this case USDC
amountFrom: INPUT_AMOUNT,
}).then(({ amountToReceive, bridgeFee }) => {
let amountOutFormatted = formatUnits(
amountToReceive,
TOKEN_OUT.decimals(CHAIN_OUT)
);
console.log(`${amountOutFormatted} USDT will be received on the output chain`)
})
.catch((err) => { throw new Error(err.toString()) });

🌉
Executing a Bridge Transaction

When interacting with Synapse's bridging smart contracts, we go about sending transactions in a two-step process: create a populated transaction (a transaction with all details of what token is being bridged, how much is being bridged, what is the destination chain, etc.) and signing the populated transaction.
The creation of a transaction is agnostic to the way it is going to be signed and sent. Once the populated transaction is created, there are multiple ways to sign the transaction and get it executed on the blockchain. We demonstrate two of the main ways in this example: ethers, and Metamask.
When creating a bridging transaction with the Synapse SDK, there are three things that need to occur: create a populated transaction approving the Synapse bridge contract to have access to your tokens, create a populated transaction for executing the actual bridge, and signing and sending both of these transactions (first approving, then bridging).
Ethers Example
This first example code is how you would generate these transactions and sign them using ethers to create a Signer from a private key.
import { Bridge, Tokens, ChainId, Networks } from "@synapseprotocol/sdk";
import { ethers } from "ethers";
import { JsonRpcProvider } from "@ethersproject/providers";
import { parseUnits } from "@ethersproject/units";
import { BigNumber } from "@ethersproject/bignumber";
// Initialize dummy Ethers Provider
const AVAX_PROVIDER = new JsonRpcProvider(
"https://api.avax.network/ext/bc/C/rpc"
);
// Use SDK Data about different chains
const AVAX_NETWORK = Networks.AVALANCHE;
// Initialize Bridge
const SYNAPSE_BRIDGE = new Bridge.SynapseBridge({
network: AVAX_NETWORK,
});
// Set up some variables to prepare a Avalanche USDC -> BSC USDT quote
const TOKEN_IN = Tokens.USDC,
TOKEN_OUT = Tokens.USDT,
ON_CHAIN = ChainId.AVALANCHE,
CHAIN_OUT = ChainId.BSC,
INPUT_AMOUNT = parseUnits(
"1000",
BigNumber.from(TOKEN_IN.decimals(ON_CHAIN))
);
// Use the following flow to handle
// 1) Receiving a bridge quote
// 2) Submitting an approval TX on the specific Token
// 3) Initiating a bridge transaction
async function submitBridgeTransaction_manual() {
// Create a signer with your private key to sign the transactions
const signer = new ethers.Wallet(your_private_key, AVAX_PROVIDER);
// get estimated output
const { amountToReceive } = await SYNAPSE_BRIDGE.estimateBridgeTokenOutput({
tokenFrom: TOKEN_IN, // token to send from the source chain, in this case USDT on Avalanche
chainIdTo: CHAIN_OUT, // Chain ID of the destination chain, in this case BSC
tokenTo: TOKEN_OUT, // Token to be received on the destination chain, in this case USDC
amountFrom: INPUT_AMOUNT,
});
let populatedApproveTxn, populatedBridgeTokenTxn;
// build an ERC20 Approve() TX to have the user submit so that the Bridge contract
// can handle the cross-chain swap
// If desired, `amount` can be passed in the args object, which overrides
// the default behavior of "infinite approval" for the token.
try {
populatedApproveTxn = await SYNAPSE_BRIDGE.buildApproveTransaction({
token: TOKEN_IN,
});
} catch (e) {
// handle error if one occurs
}
// submit populatedApproveTx via your web3/ethers pkg
await signer.sendTransaction(populatedApproveTxn);
// Once confirmed, build the bridge TX
try {
populatedBridgeTokenTxn = await SYNAPSE_BRIDGE.buildBridgeTokenTransaction({
tokenFrom: TOKEN_IN, // token to send from the source chain, in this case nUSD on Avalanche
chainIdTo: CHAIN_OUT, // Chain ID of the destination chain, in this case BSC
tokenTo: TOKEN_OUT, // Token to be received on the destination chain, in this case USDC
amountFrom: INPUT_AMOUNT, // Amount of `tokenFrom` being sent
amountTo: amountToReceive, // minimum desired amount of `tokenTo` to receive on the destination chain
addressTo: await signer.getAddress(),
});
} catch (e) {
// handle error if one occurs
}
// Submit bridge TX via your web3/ethers pkg
await signer.sendTransaction(populatedBridgeTokenTxn);
}
Metamask Example
This next example is how you would generate these transactions and sign them using Metamask.
A little more overhead is needed here to switch the user's Metamask account to the appropriate chain, and use the Metamask pop-up to sign each individual transaction once they are created.
A full React app implementation is here, with the main Javascript logic in index.js. The logic is almost identical to that displayed above in the ethers signing example, just needing some native Metamask calls to switch chains and prompt the user to sign transactions.
Just like that, your application initiated a cross-chain transfer. The Synapse Network will then respond to the user-submitted bridge transaction, and complete the token transfer on BSC within a few minutes.

Automatic Transaction Submission

While in the first example the SDK handled creation of the data needed to be submitted on-chain through the connected wallet provider, in the following example, the SDK will both create the data needed, as well as initiate the transactions through the connected wallet for the user to submit on chain.
Use the following flow if having the SDK take care of Approval and Bridge "magically" is desired.
Note that in this flow, a valid ethersjs Signer instance must be passed to executeApproveTransaction and executeBridgeTokenTransaction so that they're able to send/execute their respective transactions on behalf of the user.
If your project maintains some sort of storage for contract approvals such that it's able to determine that an approval transaction isn't necessary in a given context, then there's obviously no need to send an otherwise unnecessary approval transaction.
async function doBridgeTransaction_magic() {
// This should not be null in practice, but it is such here for the purposes of example documentation.
let signer = null;
// get minimum desired output
const { amountToReceive } = await SYNAPSE_BRIDGE.estimateBridgeTokenOutput({
tokenFrom: TOKEN_IN, // token to send from the source chain, in this case nUSD on Avalanche
chainIdTo: CHAIN_OUT, // Chain ID of the destination chain, in this case BSC
tokenTo: TOKEN_OUT, // Token to be received on the destination chain, in this case USDC
amountFrom: INPUT_AMOUNT, // Amount of `tokenFrom` being sent
});
try {
// build and execute an ERC20 Approve transaction so that the Synapse Bridge contract
// can do its thing.
// If desired, `amount` can be passed in the args object, which overrides
// the default behavior of "infinite approval" for the token.
let approveTxn = await SYNAPSE_BRIDGE.executeApproveTransaction(
{
token: TOKEN_IN,
},
signer
);
// Wait for at least one confirmation on the sending chain, this is an optional
// step and can be either omitted or implemented in a custom manner.
await approveTxn.wait(1);
console.log(`ERC20 Approve transaction hash: ${approveTxn.hash}`);
console.log(
`ERC20 Approve transaction block number: ${approveTxn.blockNumber}`
);
} catch (err) {
// deal with the caught error accordingly
}
try {
// executeBridgeTokenTransaction requires an ethers Signer instance to be
// passed to it in order to actually do the bridge transaction.
// An optional field `addressTo` can be passed, which will send tokens
// on the output chain to an address other than the address of the Signer instance.
//
// NOTE: executeBridgeTokenTransaction performs the step of actually sending/broadcasting the signed
// transaction on the source chain.
let bridgeTxn = await SYNAPSE_BRIDGE.executeBridgeTokenTransaction(
{
tokenFrom: TOKEN_IN, // token to send from the source chain, in this case nUSD on Avalanche
chainIdTo: CHAIN_OUT, // Chain ID of the destination chain, in this case BSC
tokenTo: TOKEN_OUT, // Token to be received on the destination chain, in this case USDC
amountFrom: INPUT_AMOUNT, // Amount of `tokenFrom` being sent
amountTo: amountToReceive, // minimum desired amount of `tokenTo` to receive on the destination chain
},
signer
);
// Wait for at least one confirmation on the sending chain, this is an optional
// step and can be either omitted or implemented in a custom manner.
await bridgeTxn.wait(1);
console.log(`Bridge transaction hash: ${bridgeTxn.hash}`);
console.log(`Bridge transaction block number: ${bridgeTxn.blockNumber}`);
} catch (err) {
// deal with the caught error accordingly
}
// You're done!
}