Skip to content

Send XCM messages across Paraverse ๐Ÿช โ€‹

You can use our SDK in five different cross-chain scenarios: โ€‹

  • Relay chain to Parachain XCM transfer
  • Parachain to Relay chain XCM transfer
  • Parachain to Parachain XCM transfer
  • Parachain to Ethereum transfer
  • Polkadot to Kusama ecosystem transfer and vice versa (DOT & KSM)

Relay chain to Parachain โ€‹

ts
const builder = Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)
      .from(RELAY_CHAIN) //'Kusama' | 'Polkadot'
      .to(CHAIN/*,customParaId - optional*/ | Multilocation object) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .currency({symbol: 'DOT', amount: amount})
      .address(address | Multilocation object)

const tx = await builder.build()

//Make sure to disconnect API after it is no longer used (eg. after transaction)
await builder.disconnect()

Example

Following example will perform 1 DOT transfer from Polkadot to Polimec
ts
const builder = await Builder()
  .from('Polkadot')
  .to('Polimec')
  .currency({
    symbol: 'DOT',
    amount: '10000000000'
  })
  .address(address)

const tx = await builder.build()

//Disconnect API after TX
await builder.disconnect()

Advanced settings

You can add following details to the builder to further customize your call
ts
.xcmVersion(Version.V1/V2/V3/V4)  //Optional parameter for manual override of XCM Version used in call
.customPallet('Pallet','pallet_function') //Optional parameter for manual override of XCM Pallet and function used in call (If they are named differently on some CHAIN but syntax stays the same). Both pallet name and function required. Pallet name must be CamelCase, function name snake_case.

Parachain to Relay chain โ€‹

ts
const builder = Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)
      .from(CHAIN) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .to(RELAY_CHAIN) //'Kusama' | 'Polkadot'
      .currency({symbol: 'DOT', amount: amount})
      .address(address | Multilocation object)

const tx = await builder.build()

//Make sure to disconnect API after it is no longer used (eg. after transaction)
await builder.disconnect()

Example

Following example will perform 1 DOT transfer from Hydration to Polkadot
ts
const builder = await Builder()
  .from('Hydration')
  .to('Polkadot')
  .currency({
    symbol: 'DOT',
    amount: '10000000000'
  })
  .address(address)

const tx = await builder.build()

//Disconnect API after TX
await builder.disconnect()

Advanced settings

You can add following details to the builder to further customize your call
ts
.xcmVersion(Version.V1/V2/V3/V4)  //Optional parameter for manual override of XCM Version used in call
.customPallet('Pallet','pallet_function') //Optional parameter for manual override of XCM Pallet and function used in call (If they are named differently on some CHAIN but syntax stays the same). Both pallet name and function required. Pallet name must be CamelCase, function name snake_case.

Parachain to Parachain โ€‹

ts
const builder = Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)
      .from(CHAIN) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .to(CHAIN /*,customParaId - optional*/ | Multilocation object /*Only works for PolkadotXCM pallet*/) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .currency({id: currencyID, amount: amount} | {symbol: currencySymbol, amount: amount} | {symbol: Native('currencySymbol'), amount: amount} | {symbol: Foreign('currencySymbol'), amount: amount} | {symbol: ForeignAbstract('currencySymbol'), amount: amount} | {multilocation: AssetMultilocationString, amount: amount | AssetMultilocationJson, amount: amount} | {multilocation: Override('Custom Multilocation'), amount: amount} | {multiasset: {currencySelection/* for example symbol: symbol or id: id, or multilocation: multilocation*/, amount: amount}})
      .address(address | Multilocation object /*If you are sending through xTokens, you need to pass the destination and address multilocation in one object (x2)*/)
      .senderAddress(address) // - OPTIONAL but strongly recommended as it is automatically ignored when not needed - Used when origin is AssetHub/Hydration with feeAsset or when sending to AssetHub to prevent asset traps by auto-swapping to DOT to have DOT ED.

const tx = await builder.build()

//Make sure to disconnect API after it is no longer used (eg. after transaction)
await builder.disconnect()

Notes

Quick note for transfers when selecting by ID and it is long

When transferring from Parachain that uses long IDs for example Moonbeam make sure to add character 'n' at the end of currencyID. For example: .currency(42259045809535163221576417993425387648n) will mean that you have selected to transfer xcDOT.

Example

Following example will perform 1 USDT transfer from Hydration to AssetHubPolkadot
ts
const builder = Builder()
  .from('Hydration')
  .to('AssetHubPolkadot')
  .currency({
    id: 10,
    amount: '1000000'
  })
  .address(address)

const tx = await builder.build()

//Disconnect API after TX
await builder.disconnect()

Advanced settings

You can add following details to the builder to further customize your call
ts
.ahAddress(ahAddress) - OPTIONAL - used when origin is EVM CHAIN and XCM goes through AssetHub (Multihop transfer where we are unable to convert Key20 to ID32 address eg. origin: Moonbeam & destination: Ethereum (Multihop goes from Moonbeam > AssetHub > BridgeHub > Ethereum)
.feeAsset({symbol: 'symbol'} || {id: 'id'} || {multilocation: 'multilocation'}) // Optional parameter used when multiasset is provided or when origin is AssetHub/Hydration - so user can pay fees with asset different than DOT
.xcmVersion(Version.V1/V2/V3/V4)  //Optional parameter for manual override of XCM Version used in call
.customPallet('Pallet','pallet_function') //Optional parameter for manual override of XCM Pallet and function used in call (If they are named differently on some CHAIN but syntax stays the same). Both pallet name and function required. Pallet name must be CamelCase, function name snake_case.

Ecosystem Bridges โ€‹

This section sums up currently available and implemented ecosystem bridges that are offered in the XCM SDK. Implementing cross-ecosystem asset transfers was never this easy!

Polkadot <> Kusama bridge โ€‹

Latest SDK versions support Polkadot <> Kusama bridge in very native and intuitive way. You just construct the Polkadot <> Kusama transfer as standard Parachain to Parachain scenario transfer.

ts
await Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)       
      .from('AssetHubPolkadot')  //'AssetHubPolkadot' | 'AssetHubKusama'
      .to('AssetHubKusama')     //'AssetHubPolkadot' | 'AssetHubKusama'
      .currency({symbol: 'DOT', amount: amount})        // 'KSM' | 'DOT'
      .address(address)
      .build()

Polkadot <> Ethereum bridge (Snowbridge) โ€‹

Just like Polkadot <> Kusama bridge the Snowbridge is implemented in as intuitive and native form as possible. The implementations for Polkadot -> Ethereum and Ethereum -> Polkadot differ due to different architecure so we will mention both scenarios.

Polkadot -> Ethereum transfer โ€‹

ts
await Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)
          .from('AssetHubPolkadot') //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
          .to('Ethereum')           
          .currency({symbol: 'WETH', amount: amount})   //Any supported asset by bridge - WETH, WBTC, SHIB and more - {symbol: currencySymbol} | {id: currencyID}
          .address(eth_address)  //AccountKey20 recipient address
          .senderAddress(sender_address) //Injector SS58 address
          .build()

Ethereum -> Polkadot transfer โ€‹

Currently only available in PJS version of XCM SDK (Until Snowbridge migrates to PAPI and VIEM).

ts
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

await EvmBuilder(provider)   //Ethereum provider
  .from('Ethereum')   
  .to('AssetHubPolkadot') //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
  .currency({symbol: 'WETH', amount: amount})    //Any supported asset by bridge eg. WETH, WBTC, SHIB and more - {symbol: currencySymbol} | {id: currencyID}
  .address(address)   //AccountID32 recipient address
  //.ahAddress(ahAddress) ////ahAddress is optional and used in Ethereum>EVM Substrate chain (eg. Moonbeam) transfer.
  .signer(signer)     //Ethereum signer address
  .build();

Helper functions:

js
await depositToken(signer: Signer, amount: bigint, symbol: string); //Deposit token to contract
await getTokenBalance(signer: Signer, symbol: string); //Get token balance
await approveToken(signer: Signer, amount: bigint, symbol: string); //Approve token

Snowbridge status check โ€‹

Query for Snowbridge status

ts
const status = await getBridgeStatus(/*optional parameter Bridge Hub API*/)

Local transfers โ€‹

ts
const builder = Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)
      .from(CHAIN) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .to(CHAIN) //Has to be same as origin (from)
      .currency({id: currencyID, amount: amount} | {symbol: currencySymbol, amount: amount} | {symbol: Native('currencySymbol'), amount: amount} | {symbol: Foreign('currencySymbol'), amount: amount} | {symbol: ForeignAbstract('currencySymbol'), amount: amount} | {multilocation: AssetMultilocationString, amount: amount | AssetMultilocationJson, amount: amount} | {multilocation: Override('Custom Multilocation'), amount: amount} | {multiasset: {currencySelection /* for example symbol: symbol or id: id, or multilocation: multilocation*/, amount: amount}})
      .address(address)

const tx = await builder.build()

//Make sure to disconnect API after it is no longer used (eg. after transaction)
await builder.disconnect()

Example

Following example will perform 1 DOT transfer from one account to another on Hydration
ts
const builder = Builder()
  .from('Hydration')
  .to('Hydration')
  .currency({
    symbol: 'DOT',
    amount: '10000000000'
  })
  .address(address)

const tx = await builder.build()

//Disconnect API after TX
await builder.disconnect()

Batch calls โ€‹

You can batch XCM calls and execute multiple XCM calls within one call. All three scenarios (Para->Para, Para->Relay, Relay->Para) can be used and combined.

ts
await Builder(/*CHAIN api/ws_url_string - optional*/)
      .from(CHAIN) //Ensure, that origin CHAIN is the same in all batched XCM Calls.
      .to(CHAIN_2) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .currency({currencySelection, amount}) //Currency to transfer - options as in scenarios above
      .address(address | Multilocation object)
      .addToBatch()

      .from(CHAIN) //Ensure, that origin CHAIN is the same in all batched XCM Calls.
      .to(CHAIN_3) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .currency({currencySelection, amount}) //Currency to transfer - options as in scenarios above
      .address(address | Multilocation object)
      .addToBatch()
      .buildBatch({ 
          // This settings object is optional and batch all is the default option
          mode: BatchMode.BATCH_ALL //or BatchMode.BATCH
      })

Moonbeam xTokens smart-contract โ€‹

If you need to sign Moonbeam / Moonriver transactions with other than Polkadot wallets (eg. Metamask), you can interact with their smart contract to perform operations with other wallets. Both Ethers and Viem are supported.

ts
const hash = await EvmBuilder()
      .from('Moonbeam') //'Moonbeam' | 'Moonriver'
      .to(CHAIN) //'Polkadot' | 'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
      .currency(({id: currencyID, amount: amount} | {symbol: currencySymbol, amount: amount}) //Select currency by ID or Symbol
      .address(address)
      .signer(signer) // Ethers Signer or Viem Wallet Client
      .build()

Asset claim: โ€‹

Claim XCM trapped assets from the selected chain.

ts
const builder = Builder(/*client | ws_url | [ws_url, ws_url,..] - Optional*/)
      .claimFrom(CHAIN) //'AssetHubPolkadot' | 'AssetHubKusama' | 'Polkadot' | 'Kusama'
      .fungible(MultilocationArray (Only one multilocation allowed) [{Multilocation}])
      .account(address | Multilocation object)
      /*.xcmVersion(Version.V3) Optional parameter, by default V3. XCM Version ENUM if a different XCM version is needed (Supported V2 & V3). Requires importing Version enum.*/

const tx = await builder.build()

//Make sure to disconnect API after it is no longer used (eg. after transaction)
await builder.disconnect()

Dry run your XCM Calls โ€‹

Dry running let's you check whether your XCM Call will execute, giving you a chance to fix it if it is constructed wrongly or you didn't select correct account/asset or don't have enough balance. It is constructed in same way as standard XCM messages with parameter .dryRun() instead of .build()

ts
const result = await Builder(API /*optional*/)
        .from(CHAIN) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
        .to(CHAIN_2) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...
        .currency({id: currencyID, amount: amount} | {symbol: currencySymbol, amount: amount} | {symbol: Native('currencySymbol'), amount: amount} | {symbol: Foreign('currencySymbol'), amount: amount} | {symbol: ForeignAbstract('currencySymbol'), amount: amount} | {multilocation: AssetMultilocationString, amount: amount | AssetMultilocationJson, amount: amount} | {multilocation: Override('Custom Multilocation'), amount: amount} | {multiasset: {currencySelection/* for example symbol: symbol or id: id, or multilocation: multilocation*/, amount: amount}})
        /*.feeAsset(CURRENCY) - Optional parameter when origin === AssetHubPolkadot || Hydration and TX is supposed to be paid in same fee asset as selected currency.*/
        .address(ADDRESS)
        .senderAddress(SENDER_ADDRESS)
        .dryRun()

//Check Parachain for DryRun support - returns true/false
//PAPI
import { hasDryRunSupport } from "@paraspell/sdk";
//PJS
import { hasDryRunSupport } from "@paraspell/sdk-pjs";

const result = hasDryRunSupport(CHAIN) //'AssetHubPolkadot' | 'Hydration' | 'Moonbeam' | ...

Possible output objects:

The dryrun will return following objects
origin - Always present
assetHub - Present if XCM is Multihop (For example Para > Ethereum) - WILL DEPRECATE SOON - Superseded by hops array
bridgeHub - Present if XCM is Multihop (For example Para > Ethereum) - WILL DEPRECATE SOON - Superseded by hops array
destination - Present if origin doesn't fail
hops - Always present - An array of chains that the transfer hops through (Empty if none)

Example output:

Example of an output for transfer of 0.3DOT - Polkadot > Polimec (This transfer contains hop through AssetHubPolkadot)
json
    {
"origin": {
  "success": true,
  "fee": "11831489",
  "forwardedXcms": [
    {
      "type": "V3",
      "value": {
        "parents": 0,
        "interior": {
          "type": "X1",
          "value": {
            "type": "Parachain",
            "value": 1000
          }
        }
      }
    },
    [
      {
        "type": "V3",
        "value": [
          {
            "type": "ReceiveTeleportedAsset",
            "value": [
              {
                "id": {
                  "type": "Concrete",
                  "value": {
                    "parents": 1,
                    "interior": {
                      "type": "Here"
                    }
                  }
                },
                "fun": {
                  "type": "Fungible",
                  "value": "3000000000"
                }
              }
            ]
          },
          {
            "type": "ClearOrigin"
          },
          {
            "type": "BuyExecution",
            "value": {
              "fees": {
                "id": {
                  "type": "Concrete",
                  "value": {
                    "parents": 1,
                    "interior": {
                      "type": "Here"
                    }
                  }
                },
                "fun": {
                  "type": "Fungible",
                  "value": "3000000000"
                }
              },
              "weight_limit": {
                "type": "Unlimited"
              }
            }
          },
          {
            "type": "DepositReserveAsset",
            "value": {
              "assets": {
                "type": "Wild",
                "value": {
                  "type": "AllCounted",
                  "value": 1
                }
              },
              "dest": {
                "parents": 1,
                "interior": {
                  "type": "X1",
                  "value": {
                    "type": "Parachain",
                    "value": 3344
                  }
                }
              },
              "xcm": [
                {
                  "type": "BuyExecution",
                  "value": {
                    "fees": {
                      "id": {
                        "type": "Concrete",
                        "value": {
                          "parents": 1,
                          "interior": {
                            "type": "Here"
                          }
                        }
                      },
                      "fun": {
                        "type": "Fungible",
                        "value": "1000000000"
                      }
                    },
                    "weight_limit": {
                      "type": "Unlimited"
                    }
                  }
                },
                {
                  "type": "DepositAsset",
                  "value": {
                    "assets": {
                      "type": "Wild",
                      "value": {
                        "type": "AllCounted",
                        "value": 1
                      }
                    },
                    "beneficiary": {
                      "parents": 0,
                      "interior": {
                        "type": "X1",
                        "value": {
                          "type": "AccountId32",
                          "value": {
                            "id": {}
                          }
                        }
                      }
                    }
                  }
                }
              ]
            }
          },
          {
            "type": "SetTopic",
            "value": {}
          }
        ]
      }
    ]
  ],
  "destParaId": 1000,
  "currency": "DOT"
},
"assetHub": {
  "success": true,
  "fee": "30980000",
  "weight": {
    "refTime": "576458000",
    "proofSize": "6196"
  },
  "forwardedXcms": [
    {
      "type": "V3",
      "value": {
        "parents": 1,
        "interior": {
          "type": "X1",
          "value": {
            "type": "Parachain",
            "value": 3344
          }
        }
      }
    },
    [
      {
        "type": "V3",
        "value": [
          {
            "type": "ReserveAssetDeposited",
            "value": [
              {
                "id": {
                  "type": "Concrete",
                  "value": {
                    "parents": 1,
                    "interior": {
                      "type": "Here"
                    }
                  }
                },
                "fun": {
                  "type": "Fungible",
                  "value": "2664320000"
                }
              }
            ]
          },
          {
            "type": "ClearOrigin"
          },
          {
            "type": "BuyExecution",
            "value": {
              "fees": {
                "id": {
                  "type": "Concrete",
                  "value": {
                    "parents": 1,
                    "interior": {
                      "type": "Here"
                    }
                  }
                },
                "fun": {
                  "type": "Fungible",
                  "value": "1000000000"
                }
              },
              "weight_limit": {
                "type": "Unlimited"
              }
            }
          },
          {
            "type": "DepositAsset",
            "value": {
              "assets": {
                "type": "Wild",
                "value": {
                  "type": "AllCounted",
                  "value": 1
                }
              },
              "beneficiary": {
                "parents": 0,
                "interior": {
                  "type": "X1",
                  "value": {
                    "type": "AccountId32",
                    "value": {
                      "id": {}
                    }
                  }
                }
              }
            }
          },
          {
            "type": "SetTopic",
            "value": {}
          }
        ]
      }
    ]
  ],
  "destParaId": 3344,
  "currency": "DOT"
},
"destination": {
  "success": true,
  "fee": "147295375",
  "weight": {
    "refTime": "5000000000",
    "proofSize": "327680"
  },
  "forwardedXcms": [],
  "currency": "DOT"
},
"hops": [
  {
    "CHAIN": "AssetHubPolkadot",
    "result": {
      "success": true,
      "fee": "30980000",
      "weight": {
        "refTime": "576458000",
        "proofSize": "6196"
      },
      "forwardedXcms": [
        {
          "type": "V3",
          "value": {
            "parents": 1,
            "interior": {
              "type": "X1",
              "value": {
                "type": "Parachain",
                "value": 3344
              }
            }
          }
        },
        [
          {
            "type": "V3",
            "value": [
              {
                "type": "ReserveAssetDeposited",
                "value": [
                  {
                    "id": {
                      "type": "Concrete",
                      "value": {
                        "parents": 1,
                        "interior": {
                          "type": "Here"
                        }
                      }
                    },
                    "fun": {
                      "type": "Fungible",
                      "value": "2664320000"
                    }
                  }
                ]
              },
              {
                "type": "ClearOrigin"
              },
              {
                "type": "BuyExecution",
                "value": {
                  "fees": {
                    "id": {
                      "type": "Concrete",
                      "value": {
                        "parents": 1,
                        "interior": {
                          "type": "Here"
                        }
                      }
                    },
                    "fun": {
                      "type": "Fungible",
                      "value": "1000000000"
                    }
                  },
                  "weight_limit": {
                    "type": "Unlimited"
                  }
                }
              },
              {
                "type": "DepositAsset",
                "value": {
                  "assets": {
                    "type": "Wild",
                    "value": {
                      "type": "AllCounted",
                      "value": 1
                    }
                  },
                  "beneficiary": {
                    "parents": 0,
                    "interior": {
                      "type": "X1",
                      "value": {
                        "type": "AccountId32",
                        "value": {
                          "id": {}
                        }
                      }
                    }
                  }
                }
              },
              {
                "type": "SetTopic",
                "value": {}
              }
            ]
          }
        ]
      ],
      "destParaId": 3344,
      "currency": "DOT"
    }
  }
]
}

Developer experience โ€‹

Builder pattern experience โ€‹

When developing with the Builder pattern, the developer is guided by the typescript and thus knows which parameter can be added next. This increases the developer experience and makes SDK easier to use. builder