Polkadot bridge SDK for multi-chain cross-chain token transfer.
You can integrate the amazing multi-chain bridge into your DApp with this SDK.
And you're welcome to add your parachain-adapter into the SDK.
| channel | tokens |
|---|---|
| acala | INTR IBTC |
| astar | INTR IBTC |
| bifrost | BNC VDOT |
| parallel | INTR IBTC |
| polkadot | DOT |
| polkadot asset hub | USDT |
| hydradx | HDX IBTC |
| channel | tokens |
|---|---|
| bifrost | VKSM |
| heiko | KBTC KINT |
| karura | KBTC KINT LKSM |
| kusama asset hub | USDT, KSM* |
- KSM can only be transferred from Kintsugi to Kusama Asset Hub. Transfers are not available in the other direction.
| channel | tokens |
|---|---|
| polkadot asset hub | DOT |
Example: src/bridge.spec.ts
/// import any parachain-adapters you want in your bridge.constavailableAdapters: Record<string,BaseCrossChainAdapter>={polkadot: newPolkadotAdapter(),// kusama: new KusamaAdapter(),acala: newAcalaAdapter(),// karura: new KaruraAdapter(),// statemine: new StatemineAdapter(),// bifrost: new BifrostAdapter(),// ...};/// create your bridge instance and pass the adapters to it.constbridge=newBridge({adapters: Object.values(availableAdapters),});Then you can get the bridge routers:
constallRouters=bridge.router.getRouters();/// or the available routers (some may temporarily unavailable)constavailableRouters=bridge.router.getAvailableRouters();/// and get filtered routersconstdestChains=bridge.router.getDestinationChains({from: "interlay"});consttokens=bridge.router.getAvailableTokens({from: "interlay",to: "polkadot",});You can use the ApiProvider of the SDK which can connect to all the parachains https://polkadot.js.org/apps supported,
or you can use your own apiProvider.
import{ApiProvider}from"./api-provider";constprovider=newApiProvider();Connect network and pass the ApiPromise | ApiRx into the adapters.
// list all available from-chainsconstchains=Object.keys(availableAdapters)asChainName[];// connect all adaptersconstconnected=awaitfirstValueFrom(provider.connectFromChain(chains,undefined));// and set `ApiPromise | ApiRx` for each adapterawaitPromise.all(chains.map((chain)=>availableAdapters[chain].setApi(provider.getApi(chain))));/// balance queryconstbalance=awaitfirstValueFrom(adapter.subscribeTokenBalance(token,testAccount));/// and you may want to use the inputConfig provided by the SDK/// to limit user's transfer amount inputconstinputConfig=awaitfirstValueFrom(adapter.subscribeInputConfigs({to: toChain,
token,address: toAddress,
signer,}));console.log(inputConfig.minInput,inputConfig.maxInput,inputConfig.destFee,inputConfig.estimateFee,inputConfig.ss58Prefix);/// create tx & sendconsttx=adapter.createTx({amount: FixedPointNumber.fromInner("10000000000",10),to: "polkadot",token: "DOT",address: toAddress,signer: testAccount,});tx.signAndSend(keyPair,{tip: "0"},onStatusChangecCallback);Add a new item in src/configs/chains/polkadot-chains.ts or src/configs/chains/kusama-chains.ts.
/// karura for example{karura: {id: 'karura',display: 'Karura',icon: 'https://resources.acala.network/networks/karura.png',paraChainId: 2000,ss58Prefix: 8}/// ...other parachains}Add a new adapter file in src/adapters/, and create your ParachainAdapter class extends BaseCrossChainAdapter.
Example: src/adapters/bifrost.ts
/// bifrost for exampleexportconstbifrostTokensConfig: Record<string,MultiChainToken>={BNC: {name: "BNC",symbol: "BNC",decimals: 12,ed: "10000000000"},VSKSM: {name: "VSKSM",symbol: "VSKSM",decimals: 12,ed: "100000000"},/// ...other tokens};exportconstbifrostRoutersConfig: Omit<CrossChainRouterConfigs,"from">[]=[/// router for token `BNC` from `bifrost` to `karura`,/// `xcm.fee` defines the XCM-Fee on karura,/// `xcm.weightLimit` defines the weightLimit value used creating Extrinsic.{to: "karura",token: "BNC",xcm: {fee: {token: "BNC",amount: "932400000"},weightLimit: "Unlimited",},},/// router for token `KUSD` from `bifrost` to `karura`{to: "karura",token: "KUSD",xcm: {fee: {token: "KUSD",amount: "3826597686"},weightLimit: "Unlimited",},},];Implement the subscribeTokenBalance method so the bridge can query token balances.
/// 1. create `BifrostBalanceAdapter` extends `BalanceAdapter`.classBifrostBalanceAdapterextendsBalanceAdapter{privatestorages: ReturnType<typeofcreateBalanceStorages>;constructor({ api, chain, tokens }: BalanceAdapterConfigs){super({ api, chain, tokens });this.storages=createBalanceStorages(api);}publicsubscribeBalance(token: string,address: string): Observable<BalanceData>{/// ...balance queries}}/// 2. we use a `createBalanceStorages` function with acala `Storage` utils/// for token balance queries here.functioncreateBalanceStorages(api: AnyApi)=>{return{/// balances for native-token (BNC for bifrost)balances: (address: string)=>Storage.create<any>({
api,path: 'query.system.account',params: [address]}),/// assets for non-native-token (KUSD for bifrost)assets: (tokenId: string,address: string)=>Storage.create<any>({
api,path: 'query.tokens.accounts',params: [tokenId,address]})};};/// 3. implement the `subscribeTokenBalance` methodclassBaseBifrostAdapterextendsBaseCrossChainAdapter{privatebalanceAdapter?: BifrostBalanceAdapter;publicsubscribeTokenBalance(token: string,address: string): Observable<BalanceData>{returnthis.balanceAdapter.subscribeBalance(token,address);}}Implement the subscribeMaxInput method so the bridge can set transferable token amount limit.
/// maxInput = availableBalance - estimatedFee - existentialDepositclassBaseBifrostAdapterextendsBaseCrossChainAdapter{publicsubscribeMaxInput(token: string,address: string,to: ChainName): Observable<FN>{returncombineLatest({txFee:
token===this.balanceAdapter?.nativeToken ? this.estimateTxFee() : "0",balance: this.balanceAdapter.subscribeBalance(token,address).pipe(map((i)=>i.available)),}).pipe(map(({ balance, txFee })=>{consttokenMeta=this.balanceAdapter?.getToken(token);constfeeFactor=1.2;constfee=FN.fromInner(txFee,tokenMeta?.decimals).mul(newFN(feeFactor));returnbalance.minus(fee).minus(FN.fromInner(tokenMeta?.ed||"0",tokenMeta?.decimals));}));}}Implement the createTx method so the bridge can create the cross-chain transfer Extrinsic.
/// maxInput = availableBalance - estimatedFee - existentialDepositclassBaseBifrostAdapterextendsBaseCrossChainAdapter{publiccreateTx(params: CrossChainTransferParams):
|SubmittableExtrinsic<"promise",ISubmittableResult>|SubmittableExtrinsic<"rxjs",ISubmittableResult>{const{ address, amount, to, token }=params;consttoChain=chains[to];constaccountId=this.api?.createType("AccountId32",address).toHex();consttokenId=SUPPORTED_TOKENS[token];if(!tokenId){thrownewCurrencyNotFound(token);}returnthis.api.tx.xTokens.transfer(tokenId,amount.toChainData(),{V1: {parents: 1,interior: {X2: [{Parachain: toChain.paraChainId},{AccountId32: {id: accountId,network: "Any"}},],},},},this.getDestWeight(token,to)?.toString());}}/// `chains.bifrost` is the config you added in step 1./// `bifrostRoutersConfig` & `bifrostTokensConfig` is the config you defined in step 2.1.exportclassBifrostAdapterextendsBaseBifrostAdapter{constructor(){super(chains.bifrost,bifrostRoutersConfig,bifrostTokensConfig);}}And you are all set now!
You can import your ParachainAdapter into src/bridge.spec.ts to test your adapter.
run testing with
yarn test.
And remember to run yarn lint before commit your code.
TODO