GithubHelp home page GithubHelp logo

bridge's Introduction

Polkadot bridge SDK

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.

Support Bridges

all support bridges

Usage

Example: src/bridge.spec.ts

1. initiate the bridge SDK

/// import any parachain-adapters you want in your bridge.
const acala = new AcalaAdapter();
const polkadot = new PolkadotAdapter();
const acalaApi = new ApiPromise({ provider: new WsProvider('xxx') });
const polkadotApi = new ApiPromise({ provider: new WsProvider('xxx') });

await acala.init(acalaApi);
await polkadot.init(polkadotApi);

/// create your bridge instance and pass the adapters to it.
const bridge = new Bridge({ adapters: [acala, polkadot] });

Then you can get the bridge routers:

const allRouters = bridge.router.getRouters();
/// or the available routers, we can disable some routes by config
/// const availableRouters = bridge.router.getAvailableRouters();

/// and get filtered routers
const destChains = bridge.router.getDestinationChains({ from: "acala" });
const tokens = bridge.router.getAvailableTokens({ from: "acala", to: "polkadot" });

2. network connection

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";

const provider = new ApiProvider();

Connect network and pass the ApiPromise | ApiRx into the adapters.

// list all available from-chains
const chains = Object.keys(availableAdapters) as ChainId[];

// connect all adapters
const connected = await firstValueFrom(provider.connectFromChain(chains, undefined));

// and set `ApiPromise | ApiRx` for each adapter
await Promise.all(chains.map((chain) => availableAdapters[chain].init(provider.getApi(chain))));

For ERC20 token of EVM, acala.js introduces an approach to query token balance from EVM with @acala-network/eth-providers. see: src/adapters/acala.spec.ts

import { EvmRpcProvider } from "@acala-network/eth-providers";
import { Wallet } from "@acala-network/sdk";

const provider = new ApiProvider();
const api = provider.getApiPromise("acala");
const evmProvider = new EvmRpcProvider("wss://acala.polkawallet.io");
const wallet = new Wallet(api, { evmProvider });

// by passing a wallet instance with [EvmRpcProvider],
// the [AcalaAdapter] can access the ERC20 token balance in EVM.
const acala = new AcalaAdapter();
await acala.init(api, wallet);

3. token balance query & token transfer

/// balance query
const balance = await firstValueFrom(adapter.subscribeTokenBalance(token, testAccount));

/// and you may want to use the inputConfig provided by the SDK
/// to limit user's transfer amount input
const inputConfig = await firstValueFrom(adapter.subscribeInputConfigs({ to: toChain, token, address: toAddress, signer }));
console.log(inputConfig.minInput, inputConfig.maxInput, inputConfig.destFee, inputConfig.estimateFee, inputConfig.ss58Prefix);

/// create tx & send
const tx = adapter.createTx({
  amount: FixedPointNumber.fromInner("10000000000", 10),
  to: "polkadot",
  token: "DOT",
  address: toAddress
});
tx.signAndSend(keyPair, { tip: "0" }, onStatusChangecCallback);

How to integrate your parachain into the bridge sdk

For Substrate parachains

1. Add parachain config

Add a new item in src/configs/chains/polkadot-chains.ts or src/configs/chains/kusama-chains.ts.

/// karura for example
{
  karura: {
    display: "Karura",
    type: typeSubstrate,
    icon: "https://resources.acala.network/_next/image?url=%2Fnetworks%2Fkarura.png&w=96&q=75",
    paraChainId: 2000,
    ss58Prefix: 8,
  }
  /// ...other parachains
}

2. Create adapter for your parachain

Add a new adapter file in src/adapters/, and create your ParachainAdapter class extends BaseCrossChainAdapter.

Example: src/adapters/bifrost.ts

2.1 define tokens and routers
/// bifrost for example
export const bifrostTokensConfig: Record<string, MultiChainToken> = {
  BNC: { name: "BNC", symbol: "BNC", decimals: 12, ed: "10000000000" },
  VSKSM: { name: "VSKSM", symbol: "VSKSM", decimals: 12, ed: "100000000" },
  /// ...other tokens
};
export const bifrostRouteConfigs: Omit<RouteConfigs, "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" } },
];
2.2 implement public method subscribeTokenBalance()

Implement the subscribeTokenBalance method so the bridge can query token balances.

/// 1. create `BifrostBalanceAdapter` extends `BalanceAdapter`.
class BifrostBalanceAdapter extends BalanceAdapter {
  private storages: ReturnType<typeof createBalanceStorages>;

  constructor ({ api, chain, tokens }: BalanceAdapterConfigs) {
    super({ api, chain, tokens });
    this.storages = createBalanceStorages(api);
  }

  public subscribeBalance (token: string, address: string): Observable<BalanceData> {
    /// ...balance queries
  }
}
/// 2. we use a `createBalanceStorages` function with acala `Storage` utils
///    for token balance queries here.
function createBalanceStorages(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: (address: string, token: unknown) =>
      Storage.create<any>({
        api,
        path: "query.tokens.accounts",
        params: [address, token],
      }),
  };
};
/// 3. implement the `subscribeTokenBalance` method
class BaseBifrostAdapter extends BaseCrossChainAdapter {
  private balanceAdapter?: BifrostBalanceAdapter;

  public subscribeTokenBalance (token: string, address: string): Observable<BalanceData> {
    return this.balanceAdapter.subscribeBalance(token, address);
  }
}
2.3 implement public method subscribeMaxInput()

Implement the subscribeMaxInput method so the bridge can set transferable token amount limit.

/// maxInput = availableBalance - estimatedFee - existentialDeposit
class BaseBifrostAdapter extends BaseCrossChainAdapter {
  public subscribeMaxInput(token: string, address: string, to: ChainId): Observable<FN> {
    return combineLatest({
      txFee: token === this.balanceAdapter?.nativeToken ? this.estimateTxFee() : "0",
      balance: this.balanceAdapter.subscribeBalance(token, address).pipe(map((i) => i.available)),
    }).pipe(
      map(({ balance, txFee }) => {
        const tokenMeta = this.balanceAdapter?.getToken(token);
        const feeFactor = 1.2;
        const fee = FN.fromInner(txFee, tokenMeta?.decimals).mul(new FN(feeFactor));

        return balance.minus(fee).minus(FN.fromInner(tokenMeta?.ed || "0", tokenMeta?.decimals));
      })
    );
  }
}
2.4 implement public method createTx()

Implement the createTx method so the bridge can create the cross-chain transfer Extrinsic.

class BaseBifrostAdapter extends BaseCrossChainAdapter {
  public createTx(
    params: TransferParams
  ): SubmittableExtrinsic<"promise", ISubmittableResult> | SubmittableExtrinsic<"rxjs", ISubmittableResult> {
    const { address, amount, to, token } = params;
    const toChain = chains[to];

    const accountId = this.api?.createType("AccountId32", address).toHex();

    const tokenId = SUPPORTED_TOKENS[token];
    if (!tokenId) {
      throw new TokenNotFound(token);
    }

    return this.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()
    );
  }
}
2.5 pass your routers config to your adapter
/// `chains.bifrost` is the config you added in step 1.
/// `bifrostRouteConfigs` & `bifrostTokensConfig` is the config you defined in step 2.1.
export class BifrostAdapter extends BaseBifrostAdapter {
  constructor() {
    super(chains.bifrost, bifrostRouteConfigs, bifrostTokensConfig);
  }
}

And you are all set now!

Additional steps

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.

For EVM parachains

TODO

bridge's People

Contributors

actions-user avatar bvotteler avatar github-actions[bot] avatar gxhx avatar jonathanpdunne avatar ntduan avatar qiweiii avatar qwer951123 avatar romeroyang avatar samchuk-vlad avatar sander2 avatar teodorus-nathaniel avatar tien avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

bridge's Issues

add Statemint <--> Acala

Statemint <--> Acala USDT
decimals: 6
acala 上 ForeignAssetId 为 12,minimalBalance 10,000
statemint 上 id 1984, minBalance: 700,000

statemint --> acala
enconded call data: 0x1f0801010100411f0100010100c0997c4f2b3a83eb07ef74a867cf672a25a2a30cc61abc936dcc994df77ba84a0104000002043205011f0002093d000000000000
Destination Chain Transfer Fee 为 808
需要注意的是,无法全部转走,statemint上需要保留 e.d, 即 minBalance: 700,000

acala --> statemint
enconded call data:
0x36010100010300a10f043205011f0002093d0001010200a10f0100c0997c4f2b3a83eb07ef74a867cf672a25a2a30cc61abc936dcc994df77ba84a00
Destination Chain Transfer Fee 为 700,000
需要注意的是,由于 Destination Chain Transfer Fee 跟 e.d 都为 700,000,因此在 acala 直接将 usdt 跨链到 statemint 上没有 usdt 的账户时,转账数量需要大于 1,400,000

Error when calling `subscribeInputConfig` when destination route is `moonbeam` or `moonriver`

Got the below error only with the following routes:

  • acala -> moonbeam
  • karura -> moonriver

I'm using version 0.0.6-15

Uncaught Error: createType(Call):: Call: failed decoding xTokens.transfer:: Struct: failed on args: {"currency_id":"{\"_enum\":{\"Token\":\"AcalaPrimitivesCurrencyTokenSymbol\",\"DexShare\":\"(AcalaPrimitivesCurrencyDexShare,AcalaPrimitivesCurrencyDexShare)\",\"Erc20\":\"H160\",\"StableAssetPoolToken\":\"u32\",\"LiquidCrowdloan\":\"u32\",\"ForeignAsset\":\"u16\"}}","amount":"u128","dest":"{\"_enum\":{\"V0\":\"XcmV0MultiLocation\",\"V1\":\"XcmV1MultiLocation\"}}","dest_weight_limit":"{\"_enum\":{\"Unlimited\":\"Null\",\"Limited\":\"Compact<u64>\"}}"}:: Struct: failed on dest: {"_enum":{"V0":"XcmV0MultiLocation","V1":"XcmV1MultiLocation"}}:: Enum(V1):: Struct: failed on interior: {"_enum":{"Here":"Null","X1":"XcmV1Junction","X2":"(XcmV1Junction,XcmV1Junction)","X3":"(XcmV1Junction,XcmV1Junction,XcmV1Junction)","X4":"(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)","X5":"(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)","X6":"(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)","X7":"(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)","X8":"(XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction,XcmV1Junction)"}}:: Enum(X2):: Tuple: failed on 1:: Enum(AccountKey20):: Struct: failed on key: [u8;20]:: Expected input with 20 bytes (160 bits), found 48 bytes
    createTypeUnsafe type.js:54
    createTypeUnsafe registry.js:276
    extrinsicFn createUnchecked.js:16
    decorated Decorate.js:466
    createTx acala.js:810
    estimateTxFee base-chain-adapter.js:111
    subscribeInputConfig base-chain-adapter.js:36
    subscription TeleportDialog.tsx:148
    RxJS 59
type.js:54
    createTypeUnsafe type.js:54
    createTypeUnsafe registry.js:276
    extrinsicFn createUnchecked.js:16
    decorated Decorate.js:466
    createTx acala.js:810
    estimateTxFee base-chain-adapter.js:111
    subscribeInputConfig base-chain-adapter.js:36
    subscription TeleportDialog.tsx:148
    RxJS 59

Bug: Error when `createTx` from Karura/Acala

Problem

Following the example code from README, it will result to an error if I try transferring from Acala/Karura network.
(Using version 0.0.3-2)
image

Suspect Problem

When I check in the built code in node_modules, the code used for acala adapter is old code, where it uses destWeight using number, instead of Unlimited.

Potential Solution

Publish new version using the newest commit.

How to Reproduce

Here's a simple js script that I use to confirm the error.

const { Bridge, ApiProvider } = require('@polkawallet/bridge')
const { firstValueFrom } = require('rxjs')
const { AcalaAdapter } = require('@polkawallet/bridge/build/adapters/acala')
const { PolkadotAdapter } = require('@polkawallet/bridge/build/adapters/polkadot')
const { ParallelAdapter } = require('@polkawallet/bridge/build/adapters/parallel')
const { FN } = require('@polkawallet/bridge/build/types')

const availableAdapters = {
  polkadot: new PolkadotAdapter(),
  acala: new AcalaAdapter(),
  parallel: new ParallelAdapter()
};

const bridge = new Bridge({
  adapters: Object.values(availableAdapters),
});

async function main () {
  const provider = new ApiProvider();
  const chains = Object.keys(availableAdapters);
  await firstValueFrom(provider.connectFromChain(chains, undefined));
  await Promise.all(chains.map((chain) => availableAdapters[chain].setApi(provider.getApi(chain))));

  bridge.findAdapter('acala').createTx({
    address: '[address]',
    amount: FN.fromInner('1000000000', 10),
    to: 'parallel',
    token: 'ACA'
  })
}

main()

Remove hard dependency on Acala

Is it possible to move to an implementation without dependencies on @acala-network/*?

This would be awesome for projects that doesn't use Acala sdks

Astar cross-chain fee update

Acala --> Astar
LDOT : 3,692,000
AUSD: 252,800,000
ACA: 1,108,000,000
ASTR: 4,006,410,300,000,000

Astar --> Acala
ASTR: 8,082,400,000,000,000
ACA: 8,082,400,000
LDOT: 13,400,229
AUSD: 1,815,098,681

Typo: getDestiantionsChains --> getDestinationChains

This is small and not catastrophic, but is worth mentioning:

The function available on the router getDestiantionsChains is spelt incorrectly. I believe this should be getDestinationChains or getDestinationsChains :)

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.