├── .eslintignore ├── .eslintrc.cjs ├── .github ├── dependabot.yml └── workflows │ ├── pr.yml │ └── semantic.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierrc.cjs ├── .yarn └── releases │ └── yarn-4.5.0.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASE.md ├── babel.config.cjs ├── bin └── README.MD ├── design └── general-architecture.jpg ├── docs ├── .nojekyll ├── assets │ ├── hierarchy.js │ ├── highlight.css │ ├── icons.js │ ├── icons.svg │ ├── main.js │ ├── navigation.js │ ├── search.js │ └── style.css ├── classes │ └── AssetTransferApi.AssetTransferApi.html ├── enums │ ├── errors_BaseError.BaseErrorsEnum.html │ └── types.Direction.html ├── functions │ └── constructApiPromise.constructApiPromise.html ├── hierarchy.html ├── index.html ├── interfaces │ ├── constructApiPromise.ApiInfo.html │ ├── types.TransferArgsOpts.html │ └── types.TxResult.html ├── modules.html ├── modules │ ├── AssetTransferApi.html │ ├── constructApiPromise.html │ ├── errors_BaseError.html │ └── types.html └── types │ ├── types.AssetTransferApiOpts.html │ ├── types.ConstructedFormat.html │ ├── types.Format.html │ ├── types.LocalTransferTypes.html │ ├── types.Methods.html │ ├── types.RegistryTypes.html │ └── types.XcmDirection.html ├── e2e-tests ├── README.md ├── __snapshots__ │ ├── hydration.ethereum.spec.ts.snap │ ├── moonbeam.hydration.spec.ts.snap │ ├── polkadotAssetHub.bridgeHub.spec.ts.snap │ └── polkadotAssetHub.hydration.spec.ts.snap ├── hydration.ethereum.spec.ts ├── moonbeam.hydration.spec.ts ├── polkadotAssetHub.bridgeHub.spec.ts ├── polkadotAssetHub.hydration.spec.ts └── tsconfig.json ├── examples ├── WestendAssetHubToWestendCollectivesXcmV4.ts ├── WestendAssetHubToWestendXcmV4.ts ├── WestendToAssetHubXcmV4.ts ├── claimAssets.ts ├── colors.ts ├── dryRunCall.ts ├── fetchFeeInfo.ts ├── kusama │ ├── assetHub │ │ ├── bridgeTransfers │ │ │ ├── assetHubDOTToPolkadot.ts │ │ │ └── assetHubKSMToPolkadot.ts │ │ ├── foreignAssetTransfers │ │ │ ├── local │ │ │ │ └── assetHubTNKRTransfer.ts │ │ │ ├── reserve │ │ │ │ ├── assetHubKSMDOTUSDTToBasilisk.ts │ │ │ │ └── assetHubMOVRToBifrostKusama.ts │ │ │ └── teleport │ │ │ │ └── assetHubMOVRToMoonriver.ts │ │ └── paysWithFeeOriginTransfers │ │ │ └── usdtToMoonriverPaysWithUSDT.ts │ └── relayChain │ │ └── kusamaAssetHubReserveKSMToKrievo.ts ├── localLpToken.ts ├── localParachainTx.ts ├── paraToParaParachainPrimaryNativeReserveTransfer.ts ├── paraToParaTransferMultiAsset.ts ├── paraToParaTransferMultiAssetWithFee.ts ├── paraToParaTransferMultiAssets.ts ├── paraToRelayTransferMultiAsset.ts ├── paraToSystemParachainPrimaryNative.ts ├── paraToSystemTransferMultiAsset.ts ├── paraToSystemTransferMultiAssetWithFee.ts ├── paraToSystemTransferMultiAssets.ts ├── paseo │ ├── assetHub │ │ ├── bridgeTransfers │ │ │ ├── assetHubPASToWestend.ts │ │ │ ├── assetHubWETHToEthereumSepolia.ts │ │ │ └── assetHubWNDToWestend.ts │ │ ├── foreignAssetTransfers │ │ │ ├── local │ │ │ │ └── assetHubWETHTransfer.ts │ │ │ └── reserve │ │ │ │ ├── assetHubMUSEToBifrostRemoteReserve.ts │ │ │ │ ├── assetHubMUSEToHydration.ts │ │ │ │ └── assetHubWETHToHydration.ts │ │ └── paysWithFeeOriginTransfers │ │ │ └── pasToHydrationPaysWithMUSE.ts │ └── relayChain │ │ └── bridgeTransfers │ │ └── paseoPASToWestendAssetHub.ts ├── paseoAssetHubToRelay.ts ├── polkadot │ ├── assetHub │ │ ├── bridgeTransfers │ │ │ ├── assetHubDOTToKusama.ts │ │ │ └── assetHubKSMToKusama.ts │ │ ├── foreignAssetTransfers │ │ │ ├── local │ │ │ │ ├── assetHubEQDTransfer.ts │ │ │ │ └── assetHubKSMTransfer.ts │ │ │ ├── reserve │ │ │ │ └── assetHubKSMToMoonbeam.ts │ │ │ └── teleport │ │ │ │ └── assetHubEQToEquilibrium.ts │ │ └── paysWithFeeOriginTransfers │ │ │ └── dotToHydrationPaysWithGLMR.ts │ └── parachain │ │ ├── hydrationToEthereumMainnetWETH.ts │ │ └── paysWithFeeDest │ │ └── bifrostToAssetHubPaysWithDOT.ts ├── relayToPara.ts ├── relayToSystem.ts ├── run_examples.sh ├── submittable.ts ├── systemToPara.ts ├── systemToParaLpToken.ts ├── systemToParaTeleportForeignAssets.ts ├── systemToRelay.ts ├── systemToSystem.ts └── tsconfig.json ├── jest.config.cjs ├── package.json ├── src ├── AssetTransferApi.spec.ts ├── AssetTransferApi.ts ├── config │ └── disabledOpts.ts ├── constructApiPromise.ts ├── consts.ts ├── createCalls │ ├── assets │ │ ├── index.ts │ │ ├── transfer.spec.ts │ │ ├── transfer.ts │ │ ├── transferAll.spec.ts │ │ ├── transferAll.ts │ │ ├── transferKeepAlive.spec.ts │ │ └── transferKeepAlive.ts │ ├── balances │ │ ├── index.ts │ │ ├── transfer.spec.ts │ │ ├── transfer.ts │ │ ├── transferAll.spec.ts │ │ ├── transferAll.ts │ │ ├── transferKeepAlive.spec.ts │ │ └── transferKeepAlive.ts │ ├── foreignAssets │ │ ├── index.ts │ │ ├── transfer.spec.ts │ │ ├── transfer.ts │ │ ├── transferAll.spec.ts │ │ ├── transferAll.ts │ │ ├── transferKeepAlive.spec.ts │ │ └── transferKeepAlive.ts │ ├── poolAssets │ │ ├── index.ts │ │ ├── transfer.spec.ts │ │ ├── transfer.ts │ │ ├── transferAll.spec.ts │ │ ├── transferAll.ts │ │ ├── transferKeepAlive.spec.ts │ │ └── transferKeepAlive.ts │ └── tokens │ │ ├── index.ts │ │ ├── transfer.spec.ts │ │ ├── transfer.ts │ │ ├── transferAll.spec.ts │ │ ├── transferAll.ts │ │ ├── transferKeepAlive.spec.ts │ │ └── transferKeepAlive.ts ├── createXcmCalls │ ├── index.ts │ ├── polkadotXcm │ │ ├── claimAssets.spec.ts │ │ ├── claimAssets.ts │ │ ├── limitedReserveTransferAssets.spec.ts │ │ ├── limitedReserveTransferAssets.ts │ │ ├── limitedTeleportAssets.spec.ts │ │ ├── limitedTeleportAssets.ts │ │ ├── transferAssets.spec.ts │ │ ├── transferAssets.ts │ │ ├── transferAssetsUsingTypeAndThen.spec.ts │ │ ├── transferAssetsUsingTypeAndThen.ts │ │ └── types.ts │ ├── types.ts │ ├── util │ │ ├── establishXcmPallet.spec.ts │ │ ├── establishXcmPallet.ts │ │ ├── fetchSafeXcmVersion.spec.ts │ │ └── fetchSafeXcmVersion.ts │ └── xTokens │ │ ├── transferMultiAsset.spec.ts │ │ ├── transferMultiAssetWithFee.spec.ts │ │ ├── transferMultiAssets.spec.ts │ │ ├── transferMultiasset.ts │ │ ├── transferMultiassetWithFee.ts │ │ ├── transferMultiassets.ts │ │ └── types.ts ├── createXcmTypes │ ├── ParaToEthereum.spec.ts │ ├── ParaToEthereum.ts │ ├── ParaToPara.spec.ts │ ├── ParaToPara.ts │ ├── ParaToRelay.spec.ts │ ├── ParaToRelay.ts │ ├── ParaToSystem.spec.ts │ ├── ParaToSystem.ts │ ├── RelayToBridge.spec.ts │ ├── RelayToBridge.ts │ ├── RelayToPara.spec.ts │ ├── RelayToPara.ts │ ├── RelayToSystem.spec.ts │ ├── RelayToSystem.ts │ ├── SystemToBridge.spec.ts │ ├── SystemToBridge.ts │ ├── SystemToPara.spec.ts │ ├── SystemToPara.ts │ ├── SystemToRelay.spec.ts │ ├── SystemToRelay.ts │ ├── SystemToSystem.spec.ts │ ├── SystemToSystem.ts │ ├── index.ts │ ├── types.ts │ └── util │ │ ├── assetIdIsLocation.spec.ts │ │ ├── assetIdIsLocation.ts │ │ ├── assetIdsContainsRelayAsset.spec.ts │ │ ├── assetIdsContainsRelayAsset.ts │ │ ├── chainDestIsBridge.spec.ts │ │ ├── chainDestIsBridge.ts │ │ ├── chainDestIsEthereum.spec.ts │ │ ├── chainDestIsEthereum.ts │ │ ├── createAssetLocations.spec.ts │ │ ├── createAssetLocations.ts │ │ ├── createBeneficiary.spec.ts │ │ ├── createBeneficiary.ts │ │ ├── createXcmOnDestBeneficiary.spec.ts │ │ ├── createXcmOnDestBeneficiary.ts │ │ ├── createXcmOnDestination.spec.ts │ │ ├── createXcmOnDestination.ts │ │ ├── createXcmVersionedAssetId.spec.ts │ │ ├── createXcmVersionedAssetId.ts │ │ ├── dedupeAssets.spec.ts │ │ ├── dedupeAssets.ts │ │ ├── fetchPalletInstanceId.spec.ts │ │ ├── fetchPalletInstanceId.ts │ │ ├── foreignAssetMultiLocationIsInCacheOrRegistry.spec.ts │ │ ├── foreignAssetMultiLocationIsInCacheOrRegistry.ts │ │ ├── foreignAssetsMultiLocationExists.spec.ts │ │ ├── foreignAssetsMultiLocationExists.ts │ │ ├── getAssetId.spec.ts │ │ ├── getAssetId.ts │ │ ├── getGlobalConsensusSystemName.spec.ts │ │ ├── getGlobalConsensusSystemName.ts │ │ ├── getParachainNativeAssetLocation.spec.ts │ │ ├── getParachainNativeAssetLocation.ts │ │ ├── getPaysWithFeeOriginAssetLocationFromRegistry.spec.ts │ │ ├── getPaysWithFeeOriginAssetLocationFromRegistry.ts │ │ ├── getXcAssetMultiLocationByAssetId.spec.ts │ │ ├── getXcAssetMultiLocationByAssetId.ts │ │ ├── isParachain.spec.ts │ │ ├── isParachain.ts │ │ ├── isParachainPrimaryNativeAsset.spec.ts │ │ ├── isParachainPrimaryNativeAsset.ts │ │ ├── isRelayNativeAsset.spec.ts │ │ ├── isRelayNativeAsset.ts │ │ ├── isSystemChain.spec.ts │ │ ├── isSystemChain.ts │ │ ├── multiLocationAssetIsParachainsNativeAsset.spec.ts │ │ ├── multiLocationAssetIsParachainsNativeAsset.ts │ │ ├── parseLocationStrToLocation.spec.ts │ │ ├── parseLocationStrToLocation.ts │ │ ├── resolveAssetTransferType.spec.ts │ │ ├── resolveAssetTransferType.ts │ │ ├── sortAssetsAscending.spec.ts │ │ └── sortAssetsAscending.ts ├── errors │ ├── BaseError.ts │ ├── checkBaseInputOptions.spec.ts │ ├── checkBaseInputOptions.ts │ ├── checkBaseInputTypes.spec.ts │ ├── checkBaseInputTypes.ts │ ├── checkLocalTxInput │ │ ├── checkLocalParachainInput.spec.ts │ │ ├── checkLocalParachainInput.ts │ │ ├── checkLocalRelayInput.spec.ts │ │ ├── checkLocalRelayInput.ts │ │ ├── checkLocalSystemParachainInput.ts │ │ ├── checkLocalTxInput.spec.ts │ │ ├── checkLocalTxInput.ts │ │ ├── index.ts │ │ └── types.ts │ ├── checkXcmTxInputs.spec.ts │ ├── checkXcmTxInputs.ts │ ├── checkXcmVersion.spec.ts │ ├── checkXcmVersion.ts │ ├── disableOpts.ts │ ├── index.ts │ └── types.ts ├── index.ts ├── integrationTests │ ├── AssetsTransferApi.spec.ts │ ├── parachains │ │ ├── bifrost.spec.ts │ │ ├── hydration.spec.ts │ │ └── moonriver.spec.ts │ └── util.ts ├── registry │ ├── Registry.spec.ts │ ├── Registry.ts │ ├── findRelayChain.spec.ts │ ├── findRelayChain.ts │ ├── index.ts │ ├── parseRegistry.spec.ts │ ├── parseRegistry.ts │ ├── registry-json-cjs.cts │ ├── registry-json.ts │ └── types.ts ├── sanitize │ ├── sanitizeAddress.spec.ts │ └── sanitizeAddress.ts ├── testHelpers │ ├── adjustedMockBifrostParachainApi.ts │ ├── adjustedMockHydrationParachainApi.ts │ ├── adjustedMockMoonriverParachainApi.ts │ ├── adjustedMockMoonriverParachainNoXTokens.ts │ ├── adjustedMockRelayApiNoLimitedReserveTransferAssets.ts │ ├── adjustedMockRelayApiV1016000.ts │ ├── adjustedMockRelayApiV9420.ts │ ├── adjustedMockSystemApiV1004000.ts │ ├── adjustedMockSystemApiV1016000.ts │ ├── adjustedMockWestendRelayApiV1007001.ts │ ├── createApiWithAugmentations.ts │ ├── metadata │ │ ├── assetHubWestendV1004000.ts │ │ ├── assetHubWestendV1016000.ts │ │ ├── bifrostKusamaV13000.ts │ │ ├── hydrationV3100.ts │ │ ├── kusamaV9420.ts │ │ ├── moonriverV2302.ts │ │ ├── westendV1007001.ts │ │ └── westendV1016000.ts │ ├── mockBifrostParachainApi.ts │ ├── mockDryRunCallResult.ts │ ├── mockHydrationParachainApi.ts │ ├── mockMoonriverParachainApi.ts │ ├── mockQueryWeightToAssetFeeResult.ts │ ├── mockQueryXcmWeightResult.ts │ ├── mockRelayApiV1007001.ts │ ├── mockRelayApiV1016000.ts │ ├── mockRelayApiV9420.ts │ ├── mockSystemApi.ts │ └── mockWeightInfo.ts ├── types.ts ├── util │ ├── callExistsInRuntime.spec.ts │ ├── callExistsInRuntime.ts │ ├── deepEqual.spec.ts │ ├── deepEqual.ts │ ├── detectJsEvn.ts │ ├── getFeeAssetItemIndex.spec.ts │ ├── getFeeAssetItemIndex.ts │ ├── normalizeArrToStr.spec.ts │ ├── normalizeArrToStr.ts │ ├── resolveMultiLocation.spec.ts │ ├── resolveMultiLocation.ts │ ├── sanitizeKeys.spec.ts │ └── sanitizeKeys.ts └── validate │ ├── index.ts │ ├── validateAddress.spec.ts │ ├── validateAddress.ts │ ├── validateNumber.spec.ts │ └── validateNumber.ts ├── tsconfig.json ├── typedoc.config.cjs ├── vitest-setup-file.ts ├── vitest.config.ts ├── vitest.workspace.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | **/lib/* 4 | **/build/* 5 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const config = require('@substrate/dev/config/eslint'); 2 | 3 | module.exports = { 4 | ...config, 5 | parser: "@typescript-eslint/parser", 6 | parserOptions: { 7 | project: '**/tsconfig.json', 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | day: "monday" 12 | time: "11:00" 13 | # EST timezone 14 | timezone: "America/New_York" 15 | rebase-strategy: "auto" 16 | groups: 17 | pjs: 18 | patterns: 19 | - "@polkadot/*" 20 | commit-message: 21 | # Prefix all commit messages with "chore" 22 | # include a list of updated dependencies 23 | prefix: "chore" 24 | include: "scope" 25 | labels: 26 | - "dependencies" 27 | -------------------------------------------------------------------------------- /.github/workflows/semantic.yml: -------------------------------------------------------------------------------- 1 | name: pr-title 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | types: 8 | - opened 9 | - edited 10 | - synchronize 11 | pull_request_target: 12 | types: 13 | - opened 14 | - edited 15 | - synchronize 16 | 17 | jobs: 18 | validate-title: 19 | permissions: 20 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs 21 | name: Validate PR Title 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: semantic-pull-request 25 | uses: amannn/action-semantic-pull-request@v5 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node Modules 2 | node_modules 3 | 4 | # MacOS 5 | .DS_Store 6 | 7 | # Yarn berry 8 | .yarn/* 9 | !.yarn/releases 10 | !.yarn/plugins 11 | 12 | # Tsc Build 13 | **/lib 14 | **/build 15 | **/dist 16 | 17 | # Zombienet 18 | zombienet-macos 19 | zombienet-linux-arm64 20 | zombienet-linux-x64 21 | zombienet.log 22 | zombienet/bin/* 23 | 24 | # Binaries 25 | /bin/* 26 | !bin/README.md 27 | 28 | # Chopsticks 29 | db.sqlite 30 | db.sqlite-* 31 | chopsticks-db 32 | 33 | # VS Code 34 | .vscode/ 35 | 36 | # Package Lock file 37 | package-lock.json 38 | 39 | .tshy 40 | .tshy-build 41 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # NOTE: We use .npmignore instead of the `files` field in the package.json so we 2 | # can ignore specific test helper directories 3 | 4 | # Ignore all directories 5 | /* 6 | 7 | # Unignore the build directory 8 | !/lib 9 | 10 | # Ignore specific paths within the build directory 11 | /lib/testHelpers 12 | 13 | # Ignore all test files. 14 | /lib/**/*.spec.* 15 | 16 | # Ignore all integration tests 17 | /lib/integrationTests 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.20.8 2 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | const config = require('@substrate/dev/config/prettier'); 2 | 3 | module.exports = { 4 | ...config, 5 | printWidth: 120, 6 | }; 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | nodeLinker: node-modules 6 | 7 | yarnPath: .yarn/releases/yarn-4.5.0.cjs 8 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('@substrate/dev/config/babel'); 2 | -------------------------------------------------------------------------------- /bin/README.MD: -------------------------------------------------------------------------------- 1 | This directory should contain all the binaries for running zombienet. -------------------------------------------------------------------------------- /design/general-architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/asset-transfer-api/bfeecf298892af894308fffe265aad4d508b553f/design/general-architecture.jpg -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/hierarchy.js: -------------------------------------------------------------------------------- 1 | window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg==" -------------------------------------------------------------------------------- /docs/assets/navigation.js: -------------------------------------------------------------------------------- 1 | window.navigationData = "eJyNk1FLwzAQx79Lnotzw03t29QJgqKMPQgyJKTXNdgkJXeFDdl3F7eua9Ks7VtJfvzun7vr1y8j2BKL2RwRaGW5xhTsvJAsYgWnjMVMmaTMAUc+cZWRylnEfqROWDyJmMhknljQLP4aoBU5Rwxpu+uMJ3f79T6qCwijkWwpaF7ID2uURGhHD0DD0hfyRafmLJSawKZcXHBWvOeezobmTUstSBodtve+YnbjtAasNRZHDxxh8f/Z7suR+K6JQU2paVzoUp2loEsVULq4W8EdJe0KwHbIw/GgZE/SwqF/fqijor72Q5wj1GtnN/heEAYnf7T5aNfQV9slYJlTl65CujT+n+EmPHgqW4j0zNf3t+PppGF/PK0XJM/GKk5hdQvr83bJhhlejeD56TUrd0uatjbXZ34DykxyQVdd9jmWsJFIdtcRzEH6fJ9CBRa5qWsSYdt6v/4D6boJRQ==" -------------------------------------------------------------------------------- /e2e-tests/README.md: -------------------------------------------------------------------------------- 1 | ## E2E Tests 2 | 3 | End-to-End tests that run using Chopsticks. 4 | 5 | To run the end-to-end tests run the command: 6 | 7 | ```bash 8 | $ yarn build && yarn build:e2e 9 | ``` 10 | 11 | Then run: 12 | 13 | ```bash 14 | $ yarn test:e2e 15 | ``` 16 | -------------------------------------------------------------------------------- /e2e-tests/__snapshots__/moonbeam.hydration.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Moonbeam <> Hydration > XCM V3 > Transfer GLMR from Moonbeam to Hydration > hydration recipients initial glmr balance 1`] = ` 4 | { 5 | "free": 0, 6 | "frozen": 0, 7 | "reserved": 0, 8 | } 9 | `; 10 | 11 | exports[`Moonbeam <> Hydration > XCM V3 > Transfer GLMR from Moonbeam to Hydration > hydration recipients updated glmr balance 1`] = ` 12 | { 13 | "free": "(rounded 10000000000000000000)", 14 | "frozen": 0, 15 | "reserved": 0, 16 | } 17 | `; 18 | 19 | exports[`Moonbeam <> Hydration > XCM V4 > Transfer GLMR from Moonbeam to Hydration > hydration recipients initial glmr balance 1`] = ` 20 | { 21 | "free": 0, 22 | "frozen": 0, 23 | "reserved": 0, 24 | } 25 | `; 26 | 27 | exports[`Moonbeam <> Hydration > XCM V4 > Transfer GLMR from Moonbeam to Hydration > hydration recipients updated glmr balance 1`] = ` 28 | { 29 | "free": "(rounded 10000000000000000000)", 30 | "frozen": 0, 31 | "reserved": 0, 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /e2e-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../node_modules/@substrate/dev/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "build", 6 | "ignoreDeprecations": "5.0", 7 | "resolveJsonModule": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/WestendAssetHubToWestendCollectivesXcmV4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a `limitedTeleportAssets` call to send 1 WND from a Westend AssetHub (System Parachain) account 13 | * to a Westend Collectives (System Parachain) account, where the `xcmVersion` is set to safeXcmVersion and and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westend-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1001', 27 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 28 | ['WND'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/WestendAssetHubToWestendXcmV4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a `limitedTeleportAssets` call to send 1 WND from a Westend AssetHub (System Parachain) account 13 | * to a Westend (Relay Chain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westend-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '0', 27 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 28 | ['WND'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/WestendToAssetHubXcmV4.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a `limitedTeleportAssets` call to send 1 WND from a Westend (Relay Chain) account 13 | * to a Westend AssetHub (System Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westend-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', 27 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 28 | ['WND'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/claimAssets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a `claimAssets` call to claim `WND` tokens trapped in the AssetTrap of Westend AssetHub where the `xcmVersion` is set to 3. 13 | * 14 | */ 15 | const main = async () => { 16 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westend-asset-hub-rpc.polkadot.io', { 17 | throwOnConnect: true, 18 | }); 19 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 20 | let callInfo: TxResult<'call'>; 21 | try { 22 | callInfo = await assetApi.claimAssets( 23 | ['WND'], 24 | ['10000000000'], 25 | '5HBuLJz9LdkUNseUEL6DLeVkx2bqEi6pQr8Ea7fS4bzx7i7E', 26 | { 27 | format: 'call', 28 | xcmVersion: safeXcmVersion, 29 | }, 30 | ); 31 | 32 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 33 | } catch (e) { 34 | console.error(e); 35 | throw Error(e as string); 36 | } 37 | 38 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 39 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 40 | }; 41 | 42 | void (async () => { 43 | try { 44 | await main(); 45 | } catch (err) { 46 | console.error(err); 47 | process.exit(1); 48 | } finally { 49 | process.exit(); 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /examples/colors.ts: -------------------------------------------------------------------------------- 1 | export const GREEN = '\u001b[32m'; 2 | export const CYAN = '\u001b[36m'; 3 | export const PURPLE = '\u001b[35m'; 4 | export const RESET = '\u001b[0m'; 5 | -------------------------------------------------------------------------------- /examples/fetchFeeInfo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send WETH from a Bifrost Polkadot (Parachain) account 13 | * to a Polkadot AssetHub (System Parachain) account, where the `xcmVersion` is set to 3. 14 | * 15 | * `fetchFeeInfo` returns the associated weight of the transaction, the transaction's class and the estimated fee denoted in the native asset of the origin chain. 16 | * 17 | */ 18 | const main = async () => { 19 | const safeXcmVersion = 3; 20 | const { api, specName } = await constructApiPromise('wss://bifrost-polkadot-rpc.dwellir.com', { 21 | throwOnConnect: true, 22 | }); 23 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | '1000', 28 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 29 | ['WETH'], 30 | ['1000000000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | const feeInfo = await assetApi.fetchFeeInfo(callInfo.tx, 'call'); 38 | 39 | console.log( 40 | `${PURPLE}The following feeInfo data that is returned:\n${GREEN}${JSON.stringify(feeInfo?.toHuman(), null, 4)}`, 41 | ); 42 | } catch (e) { 43 | console.error(e); 44 | throw Error(e as string); 45 | } 46 | 47 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 48 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 49 | }; 50 | 51 | void (async () => { 52 | try { 53 | await main(); 54 | } catch (err) { 55 | console.error(err); 56 | process.exit(1); 57 | } finally { 58 | process.exit(); 59 | } 60 | })(); 61 | -------------------------------------------------------------------------------- /examples/kusama/assetHub/bridgeTransfers/assetHubKSMToPolkadot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../src'; 7 | import { TxResult } from '../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `polkadotXcm` pallet `transferAssets` call to send 1 KSM (asset with location `{"parents":"1","interior":{"Here":""}}`) 12 | * from a Kusama Asset Hub (System Parachain) account 13 | * to a Polkadot Asset Hub account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://kusama-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Polkadot"},{"Parachain":"1000"}]}}`, 28 | '13EoPU88424tufnjevEYbbvZ7sGV3q1uhuN4ZbUaoTsnLHYt', 29 | [`{"parents":"1","interior":{"Here":""}}`], 30 | ['1000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/kusama/assetHub/foreignAssetTransfers/local/assetHubTNKRTransfer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../../src'; 7 | import { TxResult } from '../../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `foreignAssets` pallet `transfer` call to send TNKR (foreign asset with location `{"parents":"1","interior":{"X2":[{"Parachain":"2125"},{"GeneralIndex":"0"}]}}`) 12 | * from a Kusama Asset Hub (System Parachain) account 13 | * to a Kusama Asset Hub (System Parachain) account. 14 | * 15 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 16 | */ 17 | const main = async () => { 18 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://kusama-asset-hub-rpc.polkadot.io', { 19 | throwOnConnect: true, 20 | }); 21 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 22 | 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', // NOTE: The destination id is `1000` and matches the origin chain making this a local transfer 27 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 28 | ['{"parents":"1","interior":{"X2":[{"Parachain":"2125"},{"GeneralIndex":"0"}]}}'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | }, 33 | ); 34 | 35 | console.log(callInfo); 36 | } catch (e) { 37 | console.error(e); 38 | throw Error(e as string); 39 | } 40 | 41 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 42 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 43 | }; 44 | 45 | void (async () => { 46 | try { 47 | await main(); 48 | } catch (err) { 49 | console.error(err); 50 | process.exit(1); 51 | } finally { 52 | process.exit(); 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /examples/localLpToken.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send LiquidPool Asset '0' locally on westmint, using the `keepAlive` option. 13 | */ 14 | const main = async () => { 15 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westmint-rpc.polkadot.io', { 16 | throwOnConnect: true, 17 | }); 18 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 19 | 20 | let callInfo: TxResult<'call'>; 21 | try { 22 | callInfo = await assetApi.createTransferTransaction( 23 | '1000', 24 | '0xF977814e90dA44bFA03b6295A0616a897441aceC', 25 | ['0'], 26 | ['100000'], 27 | { 28 | format: 'call', 29 | keepAlive: true, 30 | transferLiquidToken: true, 31 | }, 32 | ); 33 | 34 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 35 | } catch (e) { 36 | console.error(e); 37 | throw Error(e as string); 38 | } 39 | 40 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 41 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 42 | }; 43 | 44 | void (async () => { 45 | try { 46 | await main(); 47 | } catch (err) { 48 | console.error(err); 49 | process.exit(1); 50 | } finally { 51 | process.exit(); 52 | } 53 | })(); 54 | -------------------------------------------------------------------------------- /examples/localParachainTx.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * The following example demonstrates a local transaction on a parachain. It is important to note that 13 | * if any token and or asset is passed in, the api will resolve to using the tokens pallet. When there 14 | * is no asset passed in it will resort to using the balances pallet. 15 | */ 16 | const main = async () => { 17 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://wss.api.moonbeam.network', { 18 | throwOnConnect: true, 19 | }); 20 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 21 | 22 | let callInfo: TxResult<'call'>; 23 | try { 24 | callInfo = await assetApi.createTransferTransaction( 25 | '2004', 26 | '0xF977814e90dA44bFA03b6295A0616a897441aceC', 27 | [], 28 | ['100000'], 29 | { 30 | format: 'call', 31 | keepAlive: true, 32 | }, 33 | ); 34 | 35 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 36 | } catch (e) { 37 | console.error(e); 38 | throw Error(e as string); 39 | } 40 | 41 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 42 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 43 | }; 44 | 45 | void (async () => { 46 | try { 47 | await main(); 48 | } catch (err) { 49 | console.error(err); 50 | process.exit(1); 51 | } finally { 52 | process.exit(); 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /examples/paraToParaParachainPrimaryNativeReserveTransfer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send PHA from a Phala Network (Parachain) account 13 | * to a Moonbeam (Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow `unlimited` weight to be used. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://phala.api.onfinality.io/public-ws', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '2004', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['PHA'], // Note: since it is the primary asset of Phala that is being sent to a Parachain, it will be a `limitedReserveTransfer` call 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/paraToParaTransferMultiAsset.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send `BNC` from a Moonriver (Parachain) account 13 | * to a Bifrost Kusama (Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * it will be `unlimited` since there is no `weightLimit` option as well. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver-rpc.dwellir.com', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '2001', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['319623561105283008236062145480775032445'], 29 | ['1000000'], 30 | { 31 | format: 'call', 32 | xcmPalletOverride: 'xTokens', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paraToParaTransferMultiAssets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send vMOVR and vBNC from a Moonriver (Parachain) account 13 | * to a Bifrost Kusama (Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * the tx will be allowed to use unlimited weight for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver-rpc.dwellir.com', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '2001', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['vMOVR', '72145018963825376852137222787619937732'], 29 | ['1000000', '10000000000'], 30 | { 31 | format: 'call', 32 | xcmPalletOverride: 'xTokens', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paraToRelayTransferMultiAsset.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send KSM from a Moonriver (Parachain) account 13 | * to a Kusama Relay chain account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * it will allow `unlimited` weight for the tx. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver-rpc.dwellir.com', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '0', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['KSM'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmPalletOverride: 'xTokens', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paraToSystemParachainPrimaryNative.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send MOVR from a Moonriver (Parachain) account 13 | * to a Kusama Asset Hub (System Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * it will allow `unlimited` weight for the tx. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver-rpc.dwellir.com', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['movr'], // Note: since it is the primary asset of Moonriver that is being sent to AssetHub, it will be a `limitedTeleportAssets` call 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/paraToSystemTransferMultiAsset.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 xcUSDT from a Moonriver (Parachain) account 13 | * to a Kusama Asset Hub (System Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * it will allow `unlimited` weight for the tx. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver-rpc.dwellir.com', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['xcUSDT'], 29 | ['1000000'], 30 | { 31 | format: 'call', 32 | xcmPalletOverride: 'xTokens', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paraToSystemTransferMultiAssets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 xcUSDT and 1 xcRMRK from a Moonriver (Parachain) account 13 | * to a Kusama Asset Hub (System Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * it will allow `unlimited` weight for the tx. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://moonriver-rpc.dwellir.com', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', 27 | '0xc4db7bcb733e117c0b34ac96354b10d47e84a006b9e7e66a229d174e8ff2a063', 28 | ['xcUSDT', 'xcRMRK'], 29 | ['1000000', '10000000000'], 30 | { 31 | format: 'call', 32 | xcmPalletOverride: 'xTokens', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paseo/assetHub/bridgeTransfers/assetHubPASToWestend.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../src'; 7 | import { TxResult } from '../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `polkadotXcm` pallet `transferAssets` call to send 1 PAS (asset with location `{"parents":"1","interior":{"Here":""}}`) 12 | * from a Paseo Asset Hub (System Parachain) account 13 | * to a Westend Asset Hub account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://paseo-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Westend"},{"Parachain":"1000"}]}}`, 28 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 29 | [`{"parents":"1","interior":{"Here":""}}`], 30 | ['1000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paseo/assetHub/bridgeTransfers/assetHubWNDToWestend.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../src'; 7 | import { TxResult } from '../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `polkadotXcm` pallet `transferAssets` call to send 1 WND (foreign asset with location `{"parents":"2","interior":{"X1":{"GlobalConsensus":"Westend"}}}`) 12 | * from a Paseo Asset Hub (System Parachain) account 13 | * to a Westend Asset Hub account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://paseo-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Westend"},{"Parachain":"1000"}]}}`, 28 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 29 | [`{"parents":"2","interior":{"X1":{"GlobalConsensus":"Westend"}}}`], 30 | ['1000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/paseoAssetHubToRelay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 PAS from a asset-hub-paseo (System Parachain) account 13 | * to a Paseo (Relay Chain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | * 18 | */ 19 | const main = async () => { 20 | const { api, safeXcmVersion } = await constructApiPromise('wss://paseo-asset-hub-rpc.polkadot.io', { 21 | throwOnConnect: true, 22 | }); 23 | const assetApi = new AssetTransferApi(api, 'asset-hub-paseo', safeXcmVersion); 24 | 25 | let callInfo: TxResult<'call'>; 26 | try { 27 | callInfo = await assetApi.createTransferTransaction( 28 | '0', // NOTE: The destination id is `0` noting that we are sending to the relay chain. 29 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 30 | ['PAS'], 31 | ['1000000000000'], 32 | { 33 | format: 'call', 34 | xcmVersion: safeXcmVersion, 35 | }, 36 | ); 37 | 38 | console.log(callInfo); 39 | } catch (e) { 40 | console.error(e); 41 | throw Error(e as string); 42 | } 43 | 44 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 45 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 46 | }; 47 | 48 | void (async () => { 49 | try { 50 | await main(); 51 | } catch (err) { 52 | console.error(err); 53 | process.exit(1); 54 | } finally { 55 | process.exit(); 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/polkadot/assetHub/bridgeTransfers/assetHubDOTToKusama.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../src'; 7 | import { TxResult } from '../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `polkadotXcm` pallet `transferAssets` call to send 1 DOT (asset with location `{"parents":"1","interior":{"Here":""}}`) 12 | * from a Polkadot Asset Hub (System Parachain) account 13 | * to a Kusama Asset Hub account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://polkadot-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Kusama"},{"Parachain":"1000"}]}}`, 28 | 'Ep7uTCvpbpMDnbfTyzbMQTQQqZ5ACGx5nUKnxmBjB4ktmSa', 29 | [`DOT`], 30 | ['10000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/polkadot/assetHub/bridgeTransfers/assetHubKSMToKusama.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../src'; 7 | import { TxResult } from '../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `polkadotXcm` pallet `transferAssets` call to send 1 KSM (foreign asset with location `{"parents":"2","interior":{"X1":{"GlobalConsensus":"Kusama"}}}`) 12 | * from a Polkadot Asset Hub (System Parachain) account 13 | * to a Kusama Asset Hub account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://polkadot-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Kusama"},{"Parachain":"1000"}]}}`, 28 | '13EoPU88424tufnjevEYbbvZ7sGV3q1uhuN4ZbUaoTsnLHYt', 29 | [`{"parents":"2","interior":{"X1":{"GlobalConsensus":"Kusama"}}}`], 30 | ['1000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/polkadot/assetHub/foreignAssetTransfers/local/assetHubKSMTransfer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../../src'; 7 | import { TxResult } from '../../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `foreignAssets` pallet `transfer` call to send 1 KSM (foreign asset with location {"parents":"2","interior":{"X1":{"GlobalConsensus":"Kusama"}}}`) 12 | * from a Polkadot Asset Hub (System Parachain) account 13 | * to a Polkadot Asset Hub (System Parachain) account. 14 | * 15 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 16 | */ 17 | const main = async () => { 18 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://polkadot-asset-hub-rpc.polkadot.io', { 19 | throwOnConnect: true, 20 | }); 21 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 22 | 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', // NOTE: The destination id is `1000` and matches the origin chain making this a local transfer 27 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 28 | ['{"parents":"2","interior":{"X1":{"GlobalConsensus":"Kusama"}}}'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, // Note: GlobalConsensus junctions require XCM V3 or higher 33 | }, 34 | ); 35 | 36 | console.log(callInfo); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/polkadot/parachain/paysWithFeeDest/bifrostToAssetHubPaysWithDOT.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi, constructApiPromise } from '../../../../src'; 7 | import { TxResult } from '../../../../src/types'; 8 | import { GREEN, PURPLE, RESET } from '../../../colors'; 9 | 10 | /** 11 | * In this example we are creating a `transferAssets` call to send WETH 12 | * from a Bifrost Polkadot (Parachain) account 13 | * to a Polkadot AssetHub (System Parachain) account, where the `xcmVersion` is set to 3 and no `weightLimit` is provided declaring that 14 | * the allowable weight will be `unlimited` and `paysWithFeeDest` is asset ID `DOT` (Polkadot) 15 | * declaring that `DOT` `should be used to pay for tx fees on the destination chain. 16 | * 17 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 18 | */ 19 | const main = async () => { 20 | const xcmVersion = 3; 21 | const { api, specName } = await constructApiPromise('wss://bifrost-polkadot-rpc.dwellir.com/ws', { 22 | throwOnConnect: true, 23 | }); 24 | const assetApi = new AssetTransferApi(api, specName, xcmVersion); 25 | 26 | let callInfo: TxResult<'call'>; 27 | try { 28 | callInfo = await assetApi.createTransferTransaction( 29 | '1000', 30 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 31 | ['WETH', 'DOT'], 32 | ['1000000000000', '10000000000'], 33 | { 34 | format: 'call', 35 | xcmVersion, 36 | paysWithFeeDest: 'DOT', // Asset to be used to pay for fees on destination chain 37 | }, 38 | ); 39 | 40 | console.log(callInfo); 41 | } catch (e) { 42 | console.error(e); 43 | throw Error(e as string); 44 | } 45 | 46 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 47 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 48 | }; 49 | 50 | void (async () => { 51 | try { 52 | await main(); 53 | } catch (err) { 54 | console.error(err); 55 | process.exit(1); 56 | } finally { 57 | process.exit(); 58 | } 59 | })(); 60 | -------------------------------------------------------------------------------- /examples/relayToPara.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 KSM from a Kusama (Relay Chain) account 13 | * to a Moonriver (Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` is provided declaring that 14 | * the tx will allow unlimited weight for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://kusama-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '2023', 27 | '0xF977814e90dA44bFA03b6295A0616a897441aceC', 28 | ['KSM'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/relayToSystem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 WND from a Westend (Relay Chain) account 13 | * to a Westmint (System Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westend-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | let callInfo: TxResult<'call'>; 24 | try { 25 | callInfo = await assetApi.createTransferTransaction( 26 | '1000', 27 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 28 | ['WND'], 29 | ['1000000000000'], 30 | { 31 | format: 'call', 32 | xcmVersion: safeXcmVersion, 33 | }, 34 | ); 35 | 36 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 37 | } catch (e) { 38 | console.error(e); 39 | throw Error(e as string); 40 | } 41 | 42 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 43 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 44 | }; 45 | 46 | void (async () => { 47 | try { 48 | await main(); 49 | } catch (err) { 50 | console.error(err); 51 | process.exit(1); 52 | } finally { 53 | process.exit(); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/submittable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { Keyring } from '@polkadot/keyring'; 7 | import { cryptoWaitReady } from '@polkadot/util-crypto'; 8 | 9 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 10 | import { constructApiPromise } from '../src/constructApiPromise.js'; 11 | import { TxResult } from '../src/types.js'; 12 | import { GREEN, PURPLE, RESET } from './colors.js'; 13 | 14 | /** 15 | * In this example, we are creating a `SubmittableExtrinsic` and showing how one may sign and send it over 16 | * a network. Since it is the `SubmittableExtrinsic`, there are a plethora of attached methods to use such as: 17 | * 18 | * `sign`, `signAsync`, `dryRun`, `addSignature`, `paymentInfo`, etc. 19 | */ 20 | const main = async () => { 21 | const { api, specName, safeXcmVersion } = await constructApiPromise('ws://127.0.0.1:9944', { throwOnConnect: true }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | // When declaring this type it will ensure that the inputted `format` matches it or the type checker will error. 25 | let callInfo: TxResult<'submittable'>; 26 | try { 27 | callInfo = await assetApi.createTransferTransaction( 28 | '1000', 29 | 'EGP7XztdTosm1EmaATZVMjSWujGEj9nNidhjqA2zZtttkFg', 30 | ['KSM'], 31 | ['1000000000000'], 32 | { 33 | format: 'submittable', 34 | xcmVersion: safeXcmVersion, 35 | }, 36 | ); 37 | 38 | console.log( 39 | `${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}${RESET}`, 40 | ); 41 | } catch (e) { 42 | console.error(e); 43 | throw Error(e as string); 44 | } 45 | 46 | await cryptoWaitReady(); 47 | // Create a new keyring, and add an "Alice" account 48 | const keyring = new Keyring(); 49 | const alice = keyring.addFromUri('//Alice', { name: 'Alice' }, 'sr25519'); 50 | 51 | await callInfo.tx.signAndSend(alice); 52 | }; 53 | 54 | void (async () => { 55 | try { 56 | await main(); 57 | } catch (err) { 58 | console.error(err); 59 | process.exit(1); 60 | } finally { 61 | process.exit(); 62 | } 63 | })(); 64 | -------------------------------------------------------------------------------- /examples/systemToPara.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 0.1 USDt from a Kusama AssetHub (System Parachain) account 13 | * to a Moonriver (Parachain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://kusama-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | '2023', 28 | '0xF977814e90dA44bFA03b6295A0616a897441aceC', 29 | ['1984'], 30 | ['100000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(`${PURPLE}The following call data that is returned:\n${GREEN}${JSON.stringify(callInfo, null, 4)}`); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/systemToParaTeleportForeignAssets.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a teleport call to send foreign asset '{"parents":"1","interior":{"X2":[{"Parachain":"2125"},{"GeneralIndex":"0"}]}}' 13 | * from a Kusama Asset Hub (System Parachain) account 14 | * to a Tinkernet (ParaChain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 15 | * the tx will allow unlimited weight to be used for fees. 16 | * 17 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 18 | */ 19 | const main = async () => { 20 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://kusama-asset-hub-rpc.polkadot.io', { 21 | throwOnConnect: true, 22 | }); 23 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 24 | 25 | let callInfo: TxResult<'call'>; 26 | try { 27 | callInfo = await assetApi.createTransferTransaction( 28 | '2125', // Note: the Parchain ID matches the MultiLocations 'Parachain' ID, making this a limitedTeleportAssets call 29 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 30 | ['{"parents":"1","interior":{"X2":[{"Parachain":"2125"},{"GeneralIndex":"0"}]}}'], 31 | ['1000000000000'], 32 | { 33 | format: 'call', 34 | xcmVersion: safeXcmVersion, 35 | }, 36 | ); 37 | 38 | console.log(callInfo); 39 | } catch (e) { 40 | console.error(e); 41 | throw Error(e as string); 42 | } 43 | 44 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 45 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 46 | }; 47 | 48 | void (async () => { 49 | try { 50 | await main(); 51 | } catch (err) { 52 | console.error(err); 53 | process.exit(1); 54 | } finally { 55 | process.exit(); 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /examples/systemToRelay.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 WND from a Westmint (System Parachain) account 13 | * to a Westend (Relay Chain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westmint-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | '0', // NOTE: The destination id is `0` noting that we are sending to the relay chain. 28 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 29 | ['WND'], 30 | ['1000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/systemToSystem.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * When importing from @substrate/asset-transfer-api it would look like the following 3 | * 4 | * import { AssetTransferApi, constructApiPromise } from '@substrate/asset-transfer-api' 5 | */ 6 | import { AssetTransferApi } from '../src/AssetTransferApi.js'; 7 | import { constructApiPromise } from '../src/constructApiPromise.js'; 8 | import { TxResult } from '../src/types.js'; 9 | import { GREEN, PURPLE, RESET } from './colors.js'; 10 | 11 | /** 12 | * In this example we are creating a call to send 1 WND from a Westmint (System Parachain) account 13 | * to a Westend Collectives (System Chain) account, where the `xcmVersion` is set to safeXcmVersion and no `weightLimit` option is provided declaring that 14 | * the tx will allow unlimited weight to be used for fees. 15 | * 16 | * NOTE: To specify the amount of weight for the tx to use provide a `weightLimit` option containing desired values for `refTime` and `proofSize`. 17 | */ 18 | const main = async () => { 19 | const { api, specName, safeXcmVersion } = await constructApiPromise('wss://westend-asset-hub-rpc.polkadot.io', { 20 | throwOnConnect: true, 21 | }); 22 | const assetApi = new AssetTransferApi(api, specName, safeXcmVersion); 23 | 24 | let callInfo: TxResult<'call'>; 25 | try { 26 | callInfo = await assetApi.createTransferTransaction( 27 | '1001', // NOTE: The destination id is `1001` noting that we are sending to the collectives/encointer System parachain 28 | '5EWNeodpcQ6iYibJ3jmWVe85nsok1EDG8Kk3aFg8ZzpfY1qX', 29 | ['WND'], 30 | ['1000000000000'], 31 | { 32 | format: 'call', 33 | xcmVersion: safeXcmVersion, 34 | }, 35 | ); 36 | 37 | console.log(callInfo); 38 | } catch (e) { 39 | console.error(e); 40 | throw Error(e as string); 41 | } 42 | 43 | const decoded = assetApi.decodeExtrinsic(callInfo.tx, 'call'); 44 | console.log(`\n${PURPLE}The following decoded tx:\n${GREEN} ${JSON.stringify(JSON.parse(decoded), null, 4)}${RESET}`); 45 | }; 46 | 47 | void (async () => { 48 | try { 49 | await main(); 50 | } catch (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } finally { 54 | process.exit(); 55 | } 56 | })(); 57 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../node_modules/@substrate/dev/config/tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "build", 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "ignoreDeprecations": "5.0" 9 | }, 10 | "typeRoots": ["../node_modules/@types"], 11 | "exclude": ["node_modules"], 12 | "include": ["./*", "./paseo/**/*.ts", "./kusama/**/*.ts", "./polkadot/**/*.ts", "../src/**/*.ts", "../src/**/*.json"] 13 | } -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | const base = require('@substrate/dev/config/jest'); 2 | 3 | module.exports = { 4 | ...base, 5 | verbose: true, 6 | coverageThreshold: { 7 | global: { 8 | branches: 50, 9 | functions: 70, 10 | lines: 70, 11 | statements: 85, 12 | }, 13 | }, 14 | testEnvironment: 'node', 15 | maxConcurrency: 3, 16 | maxWorkers: '50%', 17 | testPathIgnorePatterns: ['/lib/', '/node_modules/', '/build/'], 18 | // The below resolves `jest-haste-map:...` 19 | modulePathIgnorePatterns: ['/lib', '/build'], 20 | }; 21 | -------------------------------------------------------------------------------- /src/constructApiPromise.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { ApiPromise } from '@polkadot/api'; 4 | import type { ApiOptions } from '@polkadot/api/types'; 5 | import { WsProvider } from '@polkadot/rpc-provider'; 6 | 7 | import { fetchSafeXcmVersion } from './createXcmCalls/util/fetchSafeXcmVersion.js'; 8 | 9 | /** 10 | * Return value for `constructApiPromise` 11 | */ 12 | export interface ApiInfo { 13 | /** 14 | * Polkadot-js ApiPromise 15 | */ 16 | api: ApiPromise; 17 | /** 18 | * SpecName of the chain which the api is connected to. 19 | */ 20 | specName: string; 21 | /** 22 | * Chain name of the chain which the api is connected to. 23 | */ 24 | chainName: string; 25 | /** 26 | * SafeXcmVersion for the chain which the api is connected too. 27 | */ 28 | safeXcmVersion: number; 29 | } 30 | 31 | /** 32 | * Construct a Polkadot-js ApiPromise, and retrieve the specName of the chain. 33 | * 34 | * ```ts 35 | * import { constructApiPromise } from '@substrate/asset-transfer-api'; 36 | * 37 | * const { api, specName, safeXcmVersion } = constructApiPromise('wss://some_ws_url'); 38 | * ``` 39 | * 40 | * @param wsUrl WebSocket Url to connect to. 41 | * @param opts ApiOptions 42 | */ 43 | export const constructApiPromise = async (wsUrl: string, opts: ApiOptions = {}): Promise => { 44 | const api = await ApiPromise.create({ 45 | provider: new WsProvider(wsUrl), 46 | noInitWarn: true, 47 | ...opts, 48 | }); 49 | await api.isReady; 50 | 51 | const { specName } = await api.rpc.state.getRuntimeVersion(); 52 | const chainName = await api.rpc.system.chain(); 53 | const safeXcmVersion = await fetchSafeXcmVersion(api); 54 | 55 | return { 56 | api, 57 | specName: specName.toString(), 58 | chainName: chainName.toString(), 59 | safeXcmVersion: safeXcmVersion, 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /src/createCalls/assets/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { transfer } from './transfer.js'; 4 | export { transferAll } from './transferAll.js'; 5 | export { transferKeepAlive } from './transferKeepAlive.js'; 6 | -------------------------------------------------------------------------------- /src/createCalls/assets/transfer.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transfer } from './transfer'; 5 | 6 | describe('assets::transfer', () => { 7 | it('Should construct a valid transfer extrinsic', () => { 8 | const res = transfer( 9 | mockSystemApi, 10 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 11 | '1', 12 | '10000', 13 | ); 14 | expect(res.toHex()).toEqual('0x9c0432080400f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/createCalls/assets/transfer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transfer = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | assetId: string, 11 | amount: string, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | return api.tx.assets.transfer(assetId, destAddr, amount); 14 | }; 15 | -------------------------------------------------------------------------------- /src/createCalls/assets/transferAll.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transferAll } from './transferAll'; 5 | 6 | describe('assets::transferAll', () => { 7 | it('Should construct a valid assets pallet transferAll extrinsic', () => { 8 | const res = transferAll( 9 | mockSystemApi, 10 | '1', 11 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 12 | true, 13 | ); 14 | expect(res.toHex()).toEqual('0x980432200400f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/createCalls/assets/transferAll.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferAll = ( 8 | api: ApiPromise, 9 | assetId: string, 10 | destAddr: string, 11 | keepAlive: boolean, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | return api.tx.assets.transferAll(assetId, destAddr, keepAlive); 14 | }; 15 | -------------------------------------------------------------------------------- /src/createCalls/assets/transferKeepAlive.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transferKeepAlive } from './transferKeepAlive'; 5 | 6 | describe('assets::transfer', () => { 7 | it('Should construct a valid transfer extrinsic', () => { 8 | const res = transferKeepAlive( 9 | mockSystemApi, 10 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 11 | '1', 12 | '10000', 13 | ); 14 | expect(res.toHex()).toEqual('0x9c0432090400f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/createCalls/assets/transferKeepAlive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferKeepAlive = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | assetId: string, 11 | amount: string, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | return api.tx.assets.transferKeepAlive(assetId, destAddr, amount); 14 | }; 15 | -------------------------------------------------------------------------------- /src/createCalls/balances/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { transfer } from './transfer.js'; 4 | export { transferAll } from './transferAll.js'; 5 | export { transferKeepAlive } from './transferKeepAlive.js'; 6 | -------------------------------------------------------------------------------- /src/createCalls/balances/transfer.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transfer } from './transfer'; 5 | 6 | describe('balances::transfer', () => { 7 | it('Should construct a valid transfer extrinsic', () => { 8 | const res = transfer(mockSystemApi, '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', '10000'); 9 | expect(res.toHex()).toEqual('0x98040a0000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/createCalls/balances/transfer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transfer = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | amount: string, 11 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 12 | if (api.tx.balances.transferAllowDeath) { 13 | return api.tx.balances.transferAllowDeath(destAddr, amount); 14 | } else { 15 | return api.tx.balances.transfer(destAddr, amount); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /src/createCalls/balances/transferAll.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transferAll } from './transferAll'; 5 | 6 | describe('balances::transferAll', () => { 7 | it('Should construct a valid balances pallet transferAll extrinsic', () => { 8 | const res = transferAll(mockSystemApi, '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', true); 9 | expect(res.toHex()).toEqual('0x94040a0400f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/createCalls/balances/transferAll.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferAll = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | keepAlive: boolean, 11 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 12 | return api.tx.balances.transferAll(destAddr, keepAlive); 13 | }; 14 | -------------------------------------------------------------------------------- /src/createCalls/balances/transferKeepAlive.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transferKeepAlive } from './transferKeepAlive'; 5 | 6 | describe('balances::transfer', () => { 7 | it('Should construct a valid transfer extrinsic', () => { 8 | const res = transferKeepAlive( 9 | mockSystemApi, 10 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 11 | '10000', 12 | ); 13 | expect(res.toHex()).toEqual('0x98040a0300f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/createCalls/balances/transferKeepAlive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferKeepAlive = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | amount: string, 11 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 12 | return api.tx.balances.transferKeepAlive(destAddr, amount); 13 | }; 14 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { transfer } from './transfer.js'; 4 | export { transferAll } from './transferAll.js'; 5 | export { transferKeepAlive } from './transferKeepAlive.js'; 6 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/transfer.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { UnionXcmMultiLocation } from '../../createXcmTypes/types'; 4 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 5 | import { transfer } from './transfer'; 6 | 7 | describe('foreignAssets::transfer', () => { 8 | it('Should construct a valid foreignAsset transfer extrinsic', () => { 9 | const foreignAsset = { 10 | parents: 1, 11 | interior: { 12 | X2: [ 13 | { 14 | Parachain: '2125', 15 | }, 16 | { 17 | GeneralIndex: '0', 18 | }, 19 | ], 20 | }, 21 | } as unknown as UnionXcmMultiLocation; 22 | 23 | const res = transfer( 24 | mockSystemApi, 25 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 26 | foreignAsset, 27 | '10000', 28 | ); 29 | expect(res.toHex()).toEqual( 30 | '0xb40435080102003521050000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c', 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/transfer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | import type { UnionXcmMultiLocation } from '../../createXcmTypes/types.js'; 8 | 9 | export const transfer = ( 10 | api: ApiPromise, 11 | destAddr: string, 12 | assetId: UnionXcmMultiLocation, 13 | amount: string, 14 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 15 | return api.tx.foreignAssets.transfer(assetId, destAddr, amount); 16 | }; 17 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/transferAll.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { UnionXcmMultiLocation } from '../../createXcmTypes/types'; 4 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 5 | import { transferAll } from './transferAll'; 6 | 7 | describe('foreignAssets::transferAll', () => { 8 | it('Should construct a valid foreignAssets pallet transferAll extrinsic', () => { 9 | const foreignAsset = { 10 | parents: 1, 11 | interior: { 12 | X2: [ 13 | { 14 | Parachain: '2125', 15 | }, 16 | { 17 | GeneralIndex: '0', 18 | }, 19 | ], 20 | }, 21 | } as UnionXcmMultiLocation; 22 | 23 | const res = transferAll( 24 | mockSystemApi, 25 | foreignAsset, 26 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 27 | true, 28 | ); 29 | expect(res.toHex()).toEqual( 30 | '0xb00435200102003521050000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01', 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/transferAll.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | import { UnionXcmMultiLocation } from '../../createXcmTypes/types.js'; 8 | 9 | export const transferAll = ( 10 | api: ApiPromise, 11 | assetId: UnionXcmMultiLocation, 12 | destAddr: string, 13 | keepAlive: boolean, 14 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 15 | return api.tx.foreignAssets.transferAll(assetId, destAddr, keepAlive); 16 | }; 17 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/transferKeepAlive.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { UnionXcmMultiLocation } from '../../createXcmTypes/types'; 4 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 5 | import { transferKeepAlive } from './transferKeepAlive'; 6 | 7 | describe('foreignAssets::transfer', () => { 8 | it('Should construct a valid foreignAssets transferKeepAlive extrinsic', () => { 9 | const foreignAssetMultiLocation = { 10 | parents: 1, 11 | interior: { 12 | X2: [ 13 | { 14 | Parachain: '2125', 15 | }, 16 | { 17 | GeneralIndex: '0', 18 | }, 19 | ], 20 | }, 21 | } as unknown as UnionXcmMultiLocation; 22 | 23 | const res = transferKeepAlive( 24 | mockSystemApi, 25 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 26 | foreignAssetMultiLocation, 27 | '10000', 28 | ); 29 | expect(res.toHex()).toEqual( 30 | '0xb40435090102003521050000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b419c', 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/createCalls/foreignAssets/transferKeepAlive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | import type { UnionXcmMultiLocation } from '../../createXcmTypes/types.js'; 8 | 9 | export const transferKeepAlive = ( 10 | api: ApiPromise, 11 | destAddr: string, 12 | assetId: UnionXcmMultiLocation, 13 | amount: string, 14 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 15 | return api.tx.foreignAssets.transferKeepAlive(assetId, destAddr, amount); 16 | }; 17 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { transfer } from './transfer.js'; 4 | export { transferAll } from './transferAll.js'; 5 | export { transferKeepAlive } from './transferKeepAlive.js'; 6 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/transfer.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transfer } from './transfer'; 5 | 6 | describe('poolAssets::transfer', () => { 7 | it('Should correctly build a poolAssets transfer', () => { 8 | const res = transfer( 9 | mockSystemApi, 10 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 11 | '0', 12 | '100000', 13 | ); 14 | 15 | expect(res.toHex()).toEqual( 16 | '0xb00437080000000000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b821a0600', 17 | ); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/transfer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transfer = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | assetId: string, 11 | amount: string, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | return api.tx.poolAssets.transfer(assetId, destAddr, amount); 14 | }; 15 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/transferAll.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transferAll } from './transferAll'; 5 | 6 | describe('transferAll', () => { 7 | it('Should construct a valid poolAssets pallet transferAll extrinsic', () => { 8 | const res = transferAll( 9 | mockSystemApi, 10 | '1', 11 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 12 | true, 13 | ); 14 | expect(res.toHex()).toEqual( 15 | '0xa40437200100000000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b01', 16 | ); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/transferAll.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferAll = ( 8 | api: ApiPromise, 9 | assetId: string, 10 | destAddr: string, 11 | keepAlive: boolean, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | return api.tx.poolAssets.transferAll(assetId, destAddr, keepAlive); 14 | }; 15 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/transferKeepAlive.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 4 | import { transferKeepAlive } from './transferKeepAlive'; 5 | 6 | describe('poolAssets::transferKeepAlive', () => { 7 | it('Should correctly build a poolAssets transferKeepAlive', () => { 8 | const res = transferKeepAlive( 9 | mockSystemApi, 10 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 11 | '0', 12 | '100000', 13 | ); 14 | 15 | expect(res.toHex()).toEqual( 16 | '0xb00437090000000000f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b821a0600', 17 | ); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/createCalls/poolAssets/transferKeepAlive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferKeepAlive = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | assetId: string, 11 | amount: string, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | return api.tx.poolAssets.transferKeepAlive(assetId, destAddr, amount); 14 | }; 15 | -------------------------------------------------------------------------------- /src/createCalls/tokens/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { transfer } from './transfer.js'; 4 | export { transferAll } from './transferAll.js'; 5 | export { transferKeepAlive } from './transferKeepAlive.js'; 6 | -------------------------------------------------------------------------------- /src/createCalls/tokens/transfer.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockBifrostParachainApi } from '../../testHelpers/mockBifrostParachainApi'; 4 | import { transfer } from './transfer'; 5 | 6 | describe('tokens::transfer', () => { 7 | it('transfer', () => { 8 | const res = transfer(mockBifrostParachainApi, 'djSk4JfeBVmDA6T4yuTZajjZGmn9h8hcSH1NE6mGE4UdFv3', 'dot', '1000000'); 9 | expect(res.toHex()).toEqual( 10 | '0xa80447000058d75e286e10ad6c94094927df2ef747b39864dcf3c7aff55a7696915b1e0785020302093d00', 11 | ); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/createCalls/tokens/transfer.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transfer = ( 8 | api: ApiPromise, 9 | dest: string, 10 | currencyId: string, 11 | amount: string, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | /** 14 | * The ORML tokens pallets `tokens::transfer` accepts a `T::CurrencyId` value which can be many different types, 15 | * in this case we are strictly applying `{ Token: }`; 16 | */ 17 | return api.tx.tokens.transfer(dest, { Token: currencyId }, amount); 18 | }; 19 | -------------------------------------------------------------------------------- /src/createCalls/tokens/transferAll.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { mockBifrostParachainApi } from '../../testHelpers/mockBifrostParachainApi'; 4 | import { transferAll } from './transferAll'; 5 | 6 | describe('tokens::transferAll', () => { 7 | it('Should construct a valid ORML tokens pallet transferAll extrinsic', () => { 8 | const res = transferAll(mockBifrostParachainApi, 'djSk4JfeBVmDA6T4yuTZajjZGmn9h8hcSH1NE6mGE4UdFv3', 'dot', true); 9 | expect(res.toHex()).toEqual('0x9c0447010058d75e286e10ad6c94094927df2ef747b39864dcf3c7aff55a7696915b1e0785020301'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/createCalls/tokens/transferAll.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferAll = ( 8 | api: ApiPromise, 9 | destAddr: string, 10 | currencyId: string, 11 | keepAlive: boolean, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | /** 14 | * The ORML tokens pallets `tokens::transferAll` accepts a `T::CurrencyId` value which can be many different types, 15 | * in this case we are strictly applying `{ Token: }`; 16 | */ 17 | return api.tx.tokens.transferAll(destAddr, { Token: currencyId }, keepAlive); 18 | }; 19 | -------------------------------------------------------------------------------- /src/createCalls/tokens/transferKeepAlive.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockBifrostParachainApi } from '../../testHelpers/mockBifrostParachainApi'; 4 | import { transferKeepAlive } from './transferKeepAlive'; 5 | 6 | describe('tokens::transferKeepAlive', () => { 7 | it('transferKeepAlive', () => { 8 | const res = transferKeepAlive( 9 | mockBifrostParachainApi, 10 | 'djSk4JfeBVmDA6T4yuTZajjZGmn9h8hcSH1NE6mGE4UdFv3', 11 | 'dot', 12 | '1000000', 13 | ); 14 | expect(res.toHex()).toEqual( 15 | '0xa80447020058d75e286e10ad6c94094927df2ef747b39864dcf3c7aff55a7696915b1e0785020302093d00', 16 | ); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/createCalls/tokens/transferKeepAlive.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | export const transferKeepAlive = ( 8 | api: ApiPromise, 9 | dest: string, 10 | currencyId: string, 11 | amount: string, 12 | ): SubmittableExtrinsic<'promise', ISubmittableResult> => { 13 | /** 14 | * The ORML tokens pallets `tokens::transfer` accepts a `T::CurrencyId` value which can be many different types, 15 | * in this case we are strictly applying `{ Token: }`; 16 | */ 17 | return api.tx.tokens.transferKeepAlive(dest, { Token: currencyId }, amount); 18 | }; 19 | -------------------------------------------------------------------------------- /src/createXcmCalls/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | export { claimAssets } from './polkadotXcm/claimAssets.js'; 4 | export { limitedReserveTransferAssets } from './polkadotXcm/limitedReserveTransferAssets.js'; 5 | export { limitedTeleportAssets } from './polkadotXcm/limitedTeleportAssets.js'; 6 | export { transferAssets } from './polkadotXcm/transferAssets.js'; 7 | export { transferAssetsUsingTypeAndThen } from './polkadotXcm/transferAssetsUsingTypeAndThen.js'; 8 | export { transferMultiasset } from './xTokens/transferMultiasset.js'; 9 | export { transferMultiassets } from './xTokens/transferMultiassets.js'; 10 | export { transferMultiassetWithFee } from './xTokens/transferMultiassetWithFee.js'; 11 | -------------------------------------------------------------------------------- /src/createXcmCalls/polkadotXcm/claimAssets.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { ApiPromise } from '@polkadot/api'; 4 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 5 | import type { ISubmittableResult } from '@polkadot/types/types'; 6 | 7 | import { createAssetLocations } from '../../createXcmTypes/util/createAssetLocations.js'; 8 | import { createBeneficiary } from '../../createXcmTypes/util/createBeneficiary.js'; 9 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 10 | import { Registry } from '../../registry/index.js'; 11 | import { CreateXcmCallOpts } from '../types.js'; 12 | import { establishXcmPallet } from '../util/establishXcmPallet.js'; 13 | 14 | /** 15 | * Allow users to claim assets trapped locally on-chain during failed XCM execution. 16 | * 17 | * @param api ApiPromise 18 | * @param assetLocations string[] 19 | * @param amounts string[] 20 | * @param xcmVersion number 21 | * @param beneficiaryAddress string 22 | */ 23 | export const claimAssets = async ( 24 | api: ApiPromise, 25 | registry: Registry, 26 | specName: string, 27 | assetIds: string[], 28 | amounts: string[], 29 | beneficiaryAddress: string, 30 | xcmVersion: number, 31 | originChainId: string, 32 | opts: CreateXcmCallOpts, 33 | ): Promise> => { 34 | const { isForeignAssetsTransfer: assetIdsContainLocations, isLiquidTokenTransfer } = opts; 35 | const beneficiary = createBeneficiary(beneficiaryAddress, xcmVersion); 36 | 37 | const assets = await createAssetLocations( 38 | api, 39 | assetIds, 40 | specName, 41 | amounts, 42 | xcmVersion, 43 | registry, 44 | originChainId, 45 | assetIdsContainLocations, 46 | isLiquidTokenTransfer, 47 | ); 48 | 49 | const pallet = establishXcmPallet(api); 50 | const ext = api.tx[pallet].claimAssets; 51 | 52 | if (!ext) { 53 | throw new BaseError( 54 | `Did not find claimAssets call from pallet ${pallet} in the current runtime.`, 55 | BaseErrorsEnum.RuntimeCallNotFound, 56 | ); 57 | } 58 | 59 | return ext(assets, beneficiary); 60 | }; 61 | -------------------------------------------------------------------------------- /src/createXcmCalls/polkadotXcm/limitedReserveTransferAssets.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 4 | import type { ISubmittableResult } from '@polkadot/types/types'; 5 | 6 | import { createXcmTypes } from '../../createXcmTypes/index.js'; 7 | import { normalizeArrToStr } from '../../util/normalizeArrToStr.js'; 8 | import type { CreateXcmCallOpts } from '../types.js'; 9 | import { establishXcmPallet } from '../util/establishXcmPallet.js'; 10 | import type { PolkadotXcmBaseArgs } from './types.js'; 11 | 12 | /** 13 | * Build a Polkadot-js SubmittableExtrinsic for a `limitedReserveTransferAssets` 14 | * call. 15 | * 16 | * @param baseArgs The base args needed to construct this call. 17 | * @param opts CreateXcmCallOpts 18 | */ 19 | export const limitedReserveTransferAssets = async ( 20 | baseArgs: PolkadotXcmBaseArgs, 21 | opts: CreateXcmCallOpts, 22 | ): Promise> => { 23 | const { api, direction, destAddr, assetIds, amounts, destChainId, xcmVersion, specName, registry } = baseArgs; 24 | const { weightLimit, paysWithFeeDest, isLiquidTokenTransfer, isForeignAssetsTransfer } = opts; 25 | const pallet = establishXcmPallet(api); 26 | const ext = api.tx[pallet].limitedReserveTransferAssets; 27 | const typeCreator = createXcmTypes[direction]; 28 | const beneficiary = typeCreator.createBeneficiary(destAddr, xcmVersion); 29 | const dest = typeCreator.createDest(destChainId, xcmVersion); 30 | const assets = await typeCreator.createAssets(normalizeArrToStr(amounts), xcmVersion, specName, assetIds, { 31 | registry, 32 | isForeignAssetsTransfer, 33 | isLiquidTokenTransfer, 34 | api, 35 | destChainId, 36 | }); 37 | const weightLimitType = typeCreator.createWeightLimit({ 38 | weightLimit, 39 | }); 40 | 41 | const feeAssetItem = paysWithFeeDest 42 | ? await typeCreator.createFeeAssetItem(api, { 43 | registry, 44 | paysWithFeeDest, 45 | specName, 46 | assetIds, 47 | amounts, 48 | xcmVersion, 49 | isForeignAssetsTransfer, 50 | isLiquidTokenTransfer, 51 | }) 52 | : 0; 53 | 54 | return ext(dest, beneficiary, assets, feeAssetItem, weightLimitType); 55 | }; 56 | -------------------------------------------------------------------------------- /src/createXcmCalls/polkadotXcm/limitedTeleportAssets.spec.ts: -------------------------------------------------------------------------------- 1 | import type { ApiPromise } from '@polkadot/api'; 2 | 3 | import { Registry } from '../../registry'; 4 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 5 | import { Direction, XcmDirection } from '../../types'; 6 | import { limitedTeleportAssets } from './limitedTeleportAssets'; 7 | 8 | describe('limitedTeleportAssets', () => { 9 | const registry = new Registry('statemine', {}); 10 | describe('SystemToPara', () => { 11 | const isLiquidTokenTransfer = false; 12 | const baseArgs = { 13 | api: mockSystemApi, 14 | direction: Direction.SystemToPara as XcmDirection, 15 | destAddr: '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 16 | assetIds: ['1'], 17 | amounts: ['100'], 18 | destChainId: '1000', 19 | xcmVersion: 2, 20 | specName: 'statemine', 21 | registry, 22 | }; 23 | it('Should correctly construct a tx for a system parachain with V2', async () => { 24 | const refTime = '1000000000'; 25 | const proofSize = '2000'; 26 | 27 | const paysWithFeeDest = undefined; 28 | const isForeignAssetsTransfer = false; 29 | 30 | const ext = await limitedTeleportAssets(baseArgs, { 31 | weightLimit: { 32 | refTime, 33 | proofSize, 34 | }, 35 | paysWithFeeDest, 36 | isLiquidTokenTransfer, 37 | isForeignAssetsTransfer, 38 | }); 39 | 40 | expect(ext.toHex()).toBe( 41 | '0x1501041f0901010100a10f0100010100f5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b010400000204320504009101000000000102286bee411f', 42 | ); 43 | }); 44 | it('Should error when a api does not support the required pallets', async () => { 45 | const mockApi = { tx: {} } as unknown as ApiPromise; 46 | const mockApiBaseArgs = { ...baseArgs, api: mockApi }; 47 | const paysWithFeeDest = undefined; 48 | const isForeignAssetsTransfer = false; 49 | 50 | await expect(async () => { 51 | await limitedTeleportAssets(mockApiBaseArgs, { 52 | paysWithFeeDest, 53 | isLiquidTokenTransfer, 54 | isForeignAssetsTransfer, 55 | }); 56 | }).rejects.toThrow( 57 | 'No supported pallet found in the current runtime. Supported pallets are xcmPallet, polkadotXcm, xTokens.', 58 | ); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/createXcmCalls/polkadotXcm/limitedTeleportAssets.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 4 | import type { ISubmittableResult } from '@polkadot/types/types'; 5 | 6 | import { createXcmTypes } from '../../createXcmTypes/index.js'; 7 | import { normalizeArrToStr } from '../../util/normalizeArrToStr.js'; 8 | import type { CreateXcmCallOpts } from '../types.js'; 9 | import { establishXcmPallet } from '../util/establishXcmPallet.js'; 10 | import type { PolkadotXcmBaseArgs } from './types.js'; 11 | 12 | /** 13 | * Build a Polkadot-js SubmittableExtrinsic for a `limitedTeleportAssets` call. 14 | * 15 | * @param baseArgs The base args needed to construct this call. 16 | * @param opts CreateXcmCallOpts 17 | */ 18 | export const limitedTeleportAssets = async ( 19 | baseArgs: PolkadotXcmBaseArgs, 20 | opts: CreateXcmCallOpts, 21 | ): Promise> => { 22 | const { api, direction, destAddr, assetIds, amounts, destChainId, xcmVersion, specName, registry } = baseArgs; 23 | const { weightLimit, paysWithFeeDest, isForeignAssetsTransfer } = opts; 24 | const pallet = establishXcmPallet(api); 25 | const ext = api.tx[pallet].limitedTeleportAssets; 26 | const typeCreator = createXcmTypes[direction]; 27 | const beneficiary = typeCreator.createBeneficiary(destAddr, xcmVersion); 28 | const dest = typeCreator.createDest(destChainId, xcmVersion); 29 | const assets = await typeCreator.createAssets(normalizeArrToStr(amounts), xcmVersion, specName, assetIds, { 30 | registry, 31 | isForeignAssetsTransfer, 32 | isLiquidTokenTransfer: false, 33 | api, 34 | destChainId, 35 | }); 36 | const weightLimitType = typeCreator.createWeightLimit({ 37 | weightLimit, 38 | }); 39 | 40 | const feeAssetItem = paysWithFeeDest 41 | ? await typeCreator.createFeeAssetItem(api, { 42 | registry, 43 | isForeignAssetsTransfer, 44 | isLiquidTokenTransfer: false, 45 | }) 46 | : 0; 47 | 48 | return ext(dest, beneficiary, assets, feeAssetItem, weightLimitType); 49 | }; 50 | -------------------------------------------------------------------------------- /src/createXcmCalls/polkadotXcm/transferAssets.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 4 | import type { ISubmittableResult } from '@polkadot/types/types'; 5 | 6 | import { createXcmTypes } from '../../createXcmTypes/index.js'; 7 | import { normalizeArrToStr } from '../../util/normalizeArrToStr.js'; 8 | import type { CreateXcmCallOpts } from '../types.js'; 9 | import { establishXcmPallet } from '../util/establishXcmPallet.js'; 10 | import type { PolkadotXcmBaseArgs } from './types.js'; 11 | 12 | /** 13 | * Build a Polkadot-js SubmittableExtrinsic for a `transferAssets` call. 14 | * 15 | * @param baseArgs The base args needed to construct this call. 16 | * @param opts CreateXcmCallOpts 17 | */ 18 | export const transferAssets = async ( 19 | baseArgs: PolkadotXcmBaseArgs, 20 | opts: CreateXcmCallOpts, 21 | ): Promise> => { 22 | const { api, direction, destAddr, assetIds, amounts, destChainId, xcmVersion, specName, registry } = baseArgs; 23 | const { weightLimit, paysWithFeeDest, isForeignAssetsTransfer, isLiquidTokenTransfer } = opts; 24 | const pallet = establishXcmPallet(api); 25 | const ext = api.tx[pallet].transferAssets; 26 | const typeCreator = createXcmTypes[direction]; 27 | const beneficiary = typeCreator.createBeneficiary(destAddr, xcmVersion); 28 | const dest = typeCreator.createDest(destChainId, xcmVersion); 29 | const assets = await typeCreator.createAssets(normalizeArrToStr(amounts), xcmVersion, specName, assetIds, { 30 | registry, 31 | isForeignAssetsTransfer, 32 | isLiquidTokenTransfer, 33 | api, 34 | destChainId, 35 | }); 36 | 37 | const weightLimitValue = typeCreator.createWeightLimit({ 38 | weightLimit, 39 | }); 40 | 41 | const feeAssetItem = paysWithFeeDest 42 | ? await typeCreator.createFeeAssetItem(api, { 43 | specName, 44 | xcmVersion, 45 | assetIds, 46 | amounts, 47 | paysWithFeeDest, 48 | registry, 49 | isForeignAssetsTransfer, 50 | isLiquidTokenTransfer, 51 | }) 52 | : 0; 53 | 54 | return ext(dest, beneficiary, assets, feeAssetItem, weightLimitValue); 55 | }; 56 | -------------------------------------------------------------------------------- /src/createXcmCalls/polkadotXcm/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { XcmBaseArgs } from '../../types.js'; 4 | 5 | export type PolkadotXcmBaseArgs = XcmBaseArgs; 6 | -------------------------------------------------------------------------------- /src/createXcmCalls/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | export interface CreateXcmCallOpts { 4 | weightLimit?: { refTime?: string; proofSize?: string }; 5 | paysWithFeeDest?: string; 6 | isLiquidTokenTransfer: boolean; 7 | isForeignAssetsTransfer: boolean; 8 | assetTransferType?: string; 9 | remoteReserveAssetTransferTypeLocation?: string; 10 | feesTransferType?: string; 11 | remoteReserveFeesTransferTypeLocation?: string; 12 | customXcmOnDest?: string; 13 | sendersAddr?: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/createXcmCalls/util/establishXcmPallet.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockRelayApiV9420 } from '../../testHelpers/mockRelayApiV9420'; 4 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 5 | import { establishXcmPallet } from './establishXcmPallet'; 6 | 7 | describe('establishXcmPallet', () => { 8 | it('Should detect xcmPallet pallet correctly', () => { 9 | const res = establishXcmPallet(mockRelayApiV9420); 10 | expect(res).toEqual('xcmPallet'); 11 | }); 12 | it('Should detect polkadotXcm pallet correctly', () => { 13 | const res = establishXcmPallet(mockSystemApi); 14 | expect(res).toEqual('polkadotXcm'); 15 | }); 16 | it('Should correctly throw an error when an overrided pallet is not found for the given runtime', () => { 17 | const xcmPalletOverride = 'xTokens'; 18 | const err = () => establishXcmPallet(mockSystemApi, undefined, xcmPalletOverride); 19 | expect(err).toThrow('Pallet xTokens not found in the current runtime.'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/createXcmCalls/util/fetchSafeXcmVersion.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { adjustedMockSystemApi } from '../../testHelpers/adjustedMockSystemApiV1004000'; 4 | import { fetchSafeXcmVersion } from './fetchSafeXcmVersion'; 5 | 6 | describe('fetchSafeXcmVersion', () => { 7 | it('Should return the correct value when the Option is true', async () => { 8 | const version = await fetchSafeXcmVersion(adjustedMockSystemApi); 9 | expect(version).toEqual(2); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/createXcmCalls/util/fetchSafeXcmVersion.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | import type { Option, u32 } from '@polkadot/types'; 5 | 6 | import { DEFAULT_XCM_VERSION } from '../../consts.js'; 7 | import { establishXcmPallet } from './establishXcmPallet.js'; 8 | 9 | /** 10 | * Fetch for a safe Xcm Version from the chain, if none exists the 11 | * in app default version will be used. 12 | * 13 | * @param api ApiPromise 14 | */ 15 | export const fetchSafeXcmVersion = async (api: ApiPromise): Promise => { 16 | const pallet = establishXcmPallet(api); 17 | const safeVersion = await api.query[pallet].safeXcmVersion>(); 18 | const version = safeVersion.isSome ? safeVersion.unwrap().toNumber() : DEFAULT_XCM_VERSION; 19 | 20 | return version; 21 | }; 22 | -------------------------------------------------------------------------------- /src/createXcmCalls/xTokens/transferMultiasset.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 4 | import type { ISubmittableResult } from '@polkadot/types/types'; 5 | 6 | import { createXcmTypes } from '../../createXcmTypes/index.js'; 7 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 8 | import type { CreateXcmCallOpts } from '../types.js'; 9 | import type { XTokensBaseArgs } from './types.js'; 10 | 11 | /** 12 | * Build a Polkadot-js `transferMultiasset` SubmittableExtrinsic 13 | * call. 14 | * 15 | * @param baseArgs The base args needed to construct this call. 16 | * @param opts CreateXcmCallOpts 17 | */ 18 | export const transferMultiasset = async ( 19 | baseArgs: XTokensBaseArgs, 20 | opts: CreateXcmCallOpts, 21 | ): Promise> => { 22 | const { api, direction, destAddr, assetIds, amounts, destChainId, xcmVersion, specName, registry } = baseArgs; 23 | const { weightLimit, isForeignAssetsTransfer, isLiquidTokenTransfer } = opts; 24 | const ext = api.tx[baseArgs.xcmPallet].transferMultiasset; 25 | const typeCreator = createXcmTypes[direction]; 26 | const destWeightLimit = typeCreator.createWeightLimit({ 27 | weightLimit, 28 | }); 29 | 30 | if (typeCreator.createXTokensAsset && typeCreator.createXTokensBeneficiary) { 31 | const amount = amounts[0]; 32 | const assetId = assetIds[0]; 33 | 34 | const asset = await typeCreator.createXTokensAsset(amount, xcmVersion, specName, assetId, { 35 | registry, 36 | isForeignAssetsTransfer, 37 | isLiquidTokenTransfer, 38 | api, 39 | }); 40 | const beneficiary = typeCreator.createXTokensBeneficiary(destChainId, destAddr, xcmVersion); 41 | 42 | return ext(asset, beneficiary, destWeightLimit); 43 | } 44 | 45 | throw new BaseError('Unable to create xTokens assets', BaseErrorsEnum.InternalError); 46 | }; 47 | -------------------------------------------------------------------------------- /src/createXcmCalls/xTokens/transferMultiassetWithFee.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 4 | import type { ISubmittableResult } from '@polkadot/types/types'; 5 | 6 | import { createXcmTypes } from '../../createXcmTypes/index.js'; 7 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 8 | import type { CreateXcmCallOpts } from '../types.js'; 9 | import type { XTokensBaseArgs } from './types.js'; 10 | 11 | /** 12 | * Build a Polkadot-js `transferMultiassetWithFee` SubmittableExtrinsic 13 | * call. 14 | * 15 | * @param baseArgs The base args needed to construct this call. 16 | * @param opts CreateXcmCallOpts 17 | */ 18 | export const transferMultiassetWithFee = async ( 19 | baseArgs: XTokensBaseArgs, 20 | opts: CreateXcmCallOpts, 21 | ): Promise> => { 22 | const { api, direction, destAddr, assetIds, amounts, destChainId, xcmVersion, specName, registry } = baseArgs; 23 | const { weightLimit, paysWithFeeDest, isForeignAssetsTransfer, isLiquidTokenTransfer } = opts; 24 | const ext = api.tx[baseArgs.xcmPallet].transferMultiassetWithFee; 25 | const typeCreator = createXcmTypes[direction]; 26 | const destWeightLimit = typeCreator.createWeightLimit({ 27 | weightLimit, 28 | }); 29 | 30 | if (typeCreator.createXTokensAsset && typeCreator.createXTokensFeeAssetItem && typeCreator.createXTokensBeneficiary) { 31 | const amount = amounts[0]; 32 | const assetId = assetIds[0]; 33 | const asset = await typeCreator.createXTokensAsset(amount, xcmVersion, specName, assetId, { 34 | registry, 35 | isForeignAssetsTransfer, 36 | isLiquidTokenTransfer, 37 | api, 38 | }); 39 | const fee = typeCreator.createXTokensFeeAssetItem({ 40 | registry, 41 | paysWithFeeDest, 42 | xcmVersion, 43 | isForeignAssetsTransfer, 44 | isLiquidTokenTransfer, 45 | }); 46 | 47 | const beneficiary = typeCreator.createXTokensBeneficiary(destChainId, destAddr, xcmVersion); 48 | 49 | return ext(asset, fee, beneficiary, destWeightLimit); 50 | } 51 | 52 | throw new BaseError('Unable to create xTokens assets', BaseErrorsEnum.InternalError); 53 | }; 54 | -------------------------------------------------------------------------------- /src/createXcmCalls/xTokens/transferMultiassets.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types'; 4 | import type { ISubmittableResult } from '@polkadot/types/types'; 5 | 6 | import { createXcmTypes } from '../../createXcmTypes/index.js'; 7 | import type { XcmDestBeneficiaryXcAssets } from '../../createXcmTypes/types.js'; 8 | import { UnionXcAssetsMultiAssets } from '../../createXcmTypes/types.js'; 9 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 10 | import type { CreateXcmCallOpts } from '../types.js'; 11 | import type { XTokensBaseArgs } from './types.js'; 12 | 13 | /** 14 | * Build a Polkadot-js `transferMultiassets` SubmittableExtrinsic 15 | * call. 16 | * 17 | * @param baseArgs The base args needed to construct this call. 18 | * @param opts CreateXcmCallOpts 19 | */ 20 | export const transferMultiassets = async ( 21 | baseArgs: XTokensBaseArgs, 22 | opts: CreateXcmCallOpts, 23 | ): Promise> => { 24 | const { api, direction, destAddr, assetIds, amounts, destChainId, xcmVersion, specName, registry } = baseArgs; 25 | const { weightLimit, paysWithFeeDest, isForeignAssetsTransfer, isLiquidTokenTransfer } = opts; 26 | const ext = api.tx[baseArgs.xcmPallet].transferMultiassets; 27 | const typeCreator = createXcmTypes[direction]; 28 | 29 | const destWeightLimit = typeCreator.createWeightLimit({ 30 | weightLimit, 31 | }); 32 | 33 | let assets: UnionXcAssetsMultiAssets; 34 | let beneficiary: XcmDestBeneficiaryXcAssets; 35 | 36 | if ( 37 | typeCreator.createXTokensAssets && 38 | typeCreator.createXTokensFeeAssetItem && 39 | typeCreator.createXTokensBeneficiary 40 | ) { 41 | assets = await typeCreator.createXTokensAssets(amounts, xcmVersion, specName, assetIds, { 42 | registry, 43 | isForeignAssetsTransfer, 44 | isLiquidTokenTransfer, 45 | api, 46 | }); 47 | 48 | beneficiary = typeCreator.createXTokensBeneficiary(destChainId, destAddr, xcmVersion); 49 | return ext(assets, paysWithFeeDest, beneficiary, destWeightLimit); 50 | } 51 | 52 | throw new BaseError('Unable to create xTokens assets', BaseErrorsEnum.InternalError); 53 | }; 54 | -------------------------------------------------------------------------------- /src/createXcmCalls/xTokens/types.ts: -------------------------------------------------------------------------------- 1 | import type { XcmBaseArgs } from '../../types.js'; 2 | import type { XcmPalletName } from '../util/establishXcmPallet.js'; 3 | 4 | export interface XTokensBaseArgs extends XcmBaseArgs { 5 | xcmPallet: XcmPalletName; 6 | } 7 | -------------------------------------------------------------------------------- /src/createXcmTypes/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { Direction } from '../types.js'; 4 | import { ParaToEthereum } from './ParaToEthereum.js'; 5 | import { ParaToPara } from './ParaToPara.js'; 6 | import { ParaToRelay } from './ParaToRelay.js'; 7 | import { ParaToSystem } from './ParaToSystem.js'; 8 | import { RelayToBridge } from './RelayToBridge.js'; 9 | import { RelayToPara } from './RelayToPara.js'; 10 | import { RelayToSystem } from './RelayToSystem.js'; 11 | import { SystemToBridge } from './SystemToBridge.js'; 12 | import { SystemToPara } from './SystemToPara.js'; 13 | import { SystemToRelay } from './SystemToRelay.js'; 14 | import { SystemToSystem } from './SystemToSystem.js'; 15 | import { ICreateXcmType } from './types.js'; 16 | 17 | type ICreateXcmTypeLookup = { 18 | [key in Exclude]: ICreateXcmType; 19 | }; 20 | 21 | export const createXcmTypes: ICreateXcmTypeLookup = { 22 | SystemToSystem, 23 | SystemToPara, 24 | SystemToRelay, 25 | SystemToBridge, 26 | RelayToPara, 27 | RelayToSystem, 28 | RelayToBridge, 29 | ParaToPara, 30 | ParaToRelay, 31 | ParaToSystem, 32 | ParaToEthereum, 33 | }; 34 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/assetIdIsLocation.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { assetIdIsLocation } from './assetIdIsLocation'; 4 | 5 | describe('assetIdIsLocation', () => { 6 | it('Should correctly return true for a valid location assetId string', () => { 7 | const assetId = `{"parents":"1","interior":{"X2":[{"Parchain":"2004"},{"PalletInstance":"10"}]}}`; 8 | 9 | expect(assetIdIsLocation(assetId)).toBe(true); 10 | }); 11 | it('Should correctly return false for an invalid location assetId string', () => { 12 | const assetId = `{"parents":"1"}`; 13 | 14 | expect(assetIdIsLocation(assetId)).toBe(false); 15 | }); 16 | it('Should correctly return false for an inter assetId string', () => { 17 | const assetId = `1984`; 18 | 19 | expect(assetIdIsLocation(assetId)).toBe(false); 20 | }); 21 | it('Should correctly return false for a symbol assetId string', () => { 22 | const assetId = `usdt`; 23 | 24 | expect(assetIdIsLocation(assetId)).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/assetIdIsLocation.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { parseLocationStrToLocation } from './parseLocationStrToLocation.js'; 4 | 5 | export const assetIdIsLocation = (assetId: string): boolean => { 6 | if (!assetId.toLowerCase().includes('parents') || !assetId.toLowerCase().includes('interior')) { 7 | return false; 8 | } 9 | 10 | const location = parseLocationStrToLocation(assetId); 11 | 12 | return Object.keys(location).length === 2; 13 | }; 14 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/assetIdsContainsRelayAsset.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { Registry } from '../../registry'; 4 | import { assetIdsContainRelayAsset } from './assetIdsContainsRelayAsset'; 5 | 6 | describe('assetIdsContainsRelayAsset', () => { 7 | it('Should return true when assetIds contains relay chain symbol', () => { 8 | const registry = new Registry('statemine', {}); 9 | const assetIds = ['ksm', 'usdt', 'usdc']; 10 | 11 | const result = assetIdsContainRelayAsset(assetIds, registry); 12 | 13 | expect(result).toEqual(true); 14 | }); 15 | it('Should return true when assetIds is an empty array', () => { 16 | const registry = new Registry('statemint', {}); 17 | const assetIds = ['dot', 'usdt', 'usdc']; 18 | 19 | const result = assetIdsContainRelayAsset(assetIds, registry); 20 | 21 | expect(result).toEqual(true); 22 | }); 23 | it('Should return true when assetIds contains the relay assets multilocation', () => { 24 | const registry = new Registry('asset-hub-polkadot', {}); 25 | const assetIds = [ 26 | `{"parents": 1, "interior": {"Here": ''}}`, 27 | `{"parents": 1, interior: {"X1": {"Parachain": "2023"}}}`, 28 | ]; 29 | 30 | const result = assetIdsContainRelayAsset(assetIds, registry); 31 | 32 | expect(result).toEqual(true); 33 | }); 34 | it('Should return false when assetIds does not contain the relay assets multilocation', () => { 35 | const registry = new Registry('statemine', {}); 36 | const assetIds = [`{"parents": 1, interior: {"X1": {"Parachain": "2023"}}}`]; 37 | 38 | const result = assetIdsContainRelayAsset(assetIds, registry); 39 | 40 | expect(result).toEqual(false); 41 | }); 42 | it('Should return false when assetIds does not contain the relay assets symbol', () => { 43 | const registry = new Registry('statemine', {}); 44 | const assetIds = ['usdc', 'usdt']; 45 | 46 | const result = assetIdsContainRelayAsset(assetIds, registry); 47 | 48 | expect(result).toEqual(false); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/assetIdsContainsRelayAsset.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { Registry } from '../../registry/index.js'; 4 | import { isRelayNativeAsset } from './isRelayNativeAsset.js'; 5 | 6 | export const assetIdsContainRelayAsset = (assetIds: string[], registry: Registry): boolean => { 7 | // if assetIds is empty it is the relay asset 8 | if (assetIds.length === 0) { 9 | return true; 10 | } 11 | 12 | const relayAssetMultiLocation = `{"parents": 1, "interior": { "Here": ''}}`; 13 | 14 | for (const asset of assetIds) { 15 | // check relay tokens and if matched it is the relay asset 16 | if (isRelayNativeAsset(registry, asset)) { 17 | return true; 18 | } 19 | 20 | // check assets against relay asset multilocation 21 | if ( 22 | asset.toLowerCase().trim().replace(/ /g, '') === relayAssetMultiLocation.toLowerCase().trim().replace(/ /g, '') 23 | ) { 24 | return true; 25 | } 26 | } 27 | 28 | return false; 29 | }; 30 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/chainDestIsBridge.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { chainDestIsBridge } from './chainDestIsBridge'; 4 | 5 | describe('chainDestIsBridge', () => { 6 | it('Should correctly return true for a GlobalConsensus destination location', () => { 7 | const destLocation = `{"parents":"2","interior":{"X1":{"GlobalConsensus":{"Ethereum":{"chainId":"11155111"}}}}}`; 8 | 9 | const result = chainDestIsBridge(destLocation); 10 | 11 | expect(result).toEqual(true); 12 | }); 13 | it('Should correctly return false for a destination location that does not contain a GlobalConsensus junction', () => { 14 | const destLocation = `{"parents":"2","interior":{"X1":{"Parachain":"2030"}}}`; 15 | 16 | const result = chainDestIsBridge(destLocation); 17 | 18 | expect(result).toEqual(false); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/chainDestIsBridge.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { parseLocationStrToLocation } from './parseLocationStrToLocation.js'; 4 | 5 | /** 6 | * Determines if the dest chain is a Global Consensus origin based on the value of its dest location 7 | * @param destLocation 8 | * @returns boolean 9 | */ 10 | export const chainDestIsBridge = (destLocation: string): boolean => { 11 | const location = parseLocationStrToLocation(destLocation); 12 | let destIsBridge = false; 13 | 14 | if (location.interior) { 15 | destIsBridge = location.interior.X1 16 | ? JSON.stringify(location.interior.X1).toLowerCase().includes('globalconsensus') 17 | : location.interior.X2 18 | ? JSON.stringify(location.interior.X2).toLowerCase().includes('globalconsensus') 19 | : false; 20 | } 21 | 22 | return destIsBridge; 23 | }; 24 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/chainDestIsEthereum.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { chainDestIsEthereum } from './chainDestIsEthereum'; 4 | 5 | describe('chainDestIsEthereum', () => { 6 | it('Should correctly return true for a GlobalConsensus destination location', () => { 7 | const destLocation = `{"parents":"2","interior":{"X1":{"GlobalConsensus":{"Ethereum":{"chainId":"11155111"}}}}}`; 8 | const result = chainDestIsEthereum(destLocation); 9 | 10 | expect(result).toEqual(true); 11 | }); 12 | it('Should correctly return false for a destination location that does not contain a GlobalConsensus junction', () => { 13 | const destLocation = `{"parents":"2","interior":{"X1":{"Parachain":"2030"}}}`; 14 | 15 | const result = chainDestIsEthereum(destLocation); 16 | 17 | expect(result).toEqual(false); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/chainDestIsEthereum.ts: -------------------------------------------------------------------------------- 1 | import { parseLocationStrToLocation } from './parseLocationStrToLocation.js'; 2 | 3 | export const chainDestIsEthereum = (destLocation: string): boolean => { 4 | if (!destLocation.toLowerCase().includes('parents') || !destLocation.toLowerCase().includes('interior')) { 5 | return false; 6 | } 7 | 8 | const location = parseLocationStrToLocation(destLocation); 9 | 10 | const destIsEthereum = location.interior.X1 11 | ? JSON.stringify(location.interior.X1).toLowerCase().includes('ethereum') 12 | : false; 13 | 14 | return destIsEthereum; 15 | }; 16 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/createBeneficiary.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { isEthereumAddress } from '@polkadot/util-crypto'; 4 | 5 | import { XcmDestBeneficiary } from '../types.js'; 6 | 7 | export const createBeneficiary = (accountId: string, xcmVersion?: number): XcmDestBeneficiary => { 8 | if (xcmVersion === 2) { 9 | const X1 = isEthereumAddress(accountId) 10 | ? { AccountKey20: { network: 'Any', key: accountId } } 11 | : { AccountId32: { network: 'Any', id: accountId } }; 12 | 13 | return { 14 | V2: { 15 | parents: 0, 16 | interior: { 17 | X1, 18 | }, 19 | }, 20 | }; 21 | } 22 | 23 | if (xcmVersion === 3) { 24 | const X1 = isEthereumAddress(accountId) ? { AccountKey20: { key: accountId } } : { AccountId32: { id: accountId } }; 25 | 26 | return { 27 | V3: { 28 | parents: 0, 29 | interior: { 30 | X1, 31 | }, 32 | }, 33 | }; 34 | } 35 | 36 | const X1 = isEthereumAddress(accountId) 37 | ? [{ AccountKey20: { key: accountId } }] 38 | : [{ AccountId32: { id: accountId } }]; 39 | 40 | return { 41 | V4: { 42 | parents: 0, 43 | interior: { 44 | X1, 45 | }, 46 | }, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/createXcmOnDestBeneficiary.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | // import { UnionXcmMultiLocation } from "../types"; 4 | import { createXcmOnDestBeneficiary } from './createXcmOnDestBeneficiary'; 5 | 6 | describe('createXcmOnDestBeneficiary hello', () => { 7 | it('Should correctly construct the beneficiary for XCM V3 using an Ethereum address', () => { 8 | const xcmVersion = 3; 9 | const accountId = '0x6E733286C3Dc52C67b8DAdFDd634eD9c3Fb05B5B'; 10 | 11 | const expected = { 12 | parents: 0, 13 | interior: { 14 | X1: { 15 | AccountKey20: { key: accountId }, 16 | }, 17 | }, 18 | }; 19 | 20 | expect(createXcmOnDestBeneficiary(accountId, xcmVersion)).toEqual(expected); 21 | }); 22 | it('Should correctly construct the beneficiary for XCM V3 using a Substrate address', () => { 23 | const xcmVersion = 3; 24 | const accountId = '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b'; 25 | 26 | const expected = { 27 | parents: 0, 28 | interior: { 29 | X1: { 30 | AccountId32: { id: accountId }, 31 | }, 32 | }, 33 | }; 34 | 35 | expect(createXcmOnDestBeneficiary(accountId, xcmVersion)).toEqual(expected); 36 | }); 37 | it('Should correctly construct the beneficiary for XCM V4 using an Ethereum address', () => { 38 | const xcmVersion = 4; 39 | const accountId = '0x6E733286C3Dc52C67b8DAdFDd634eD9c3Fb05B5B'; 40 | 41 | const expected = { 42 | parents: 0, 43 | interior: { 44 | X1: [{ AccountKey20: { key: accountId } }], 45 | }, 46 | }; 47 | 48 | expect(createXcmOnDestBeneficiary(accountId, xcmVersion)).toEqual(expected); 49 | }); 50 | it('Should correctly construct the beneficiary for XCM V4 using a Substrate address', () => { 51 | const xcmVersion = 4; 52 | const accountId = '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b'; 53 | 54 | const expected = { 55 | parents: 0, 56 | interior: { 57 | X1: [{ AccountId32: { id: accountId } }], 58 | }, 59 | }; 60 | 61 | expect(createXcmOnDestBeneficiary(accountId, xcmVersion)).toEqual(expected); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/createXcmOnDestBeneficiary.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { isEthereumAddress } from '@polkadot/util-crypto'; 4 | 5 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 6 | import { UnionXcmMultiLocation } from '../types.js'; 7 | 8 | export const createXcmOnDestBeneficiary = (accountId: string, xcmVersion: number): UnionXcmMultiLocation => { 9 | if (xcmVersion < 3) { 10 | throw new BaseError( 11 | 'createXcmOnDestBeneficiary: XcmVersion must be greater than 2', 12 | BaseErrorsEnum.InvalidXcmVersion, 13 | ); 14 | } 15 | 16 | if (xcmVersion === 3) { 17 | const X1 = isEthereumAddress(accountId) ? { AccountKey20: { key: accountId } } : { AccountId32: { id: accountId } }; 18 | 19 | return { 20 | parents: 0, 21 | interior: { 22 | X1, 23 | }, 24 | } as UnionXcmMultiLocation; 25 | } 26 | 27 | const X1 = isEthereumAddress(accountId) 28 | ? [{ AccountKey20: { key: accountId } }] 29 | : [{ AccountId32: { id: accountId } }]; 30 | 31 | return { 32 | parents: 0, 33 | interior: { 34 | X1, 35 | }, 36 | } as UnionXcmMultiLocation; 37 | }; 38 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/createXcmOnDestination.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { UnionXcmMultiLocation } from '../types'; 4 | import { createXcmOnDestination } from './createXcmOnDestination'; 5 | 6 | describe('createXcmOnDestination', () => { 7 | it('Should correctly construct the default XCM message for V3', () => { 8 | const assetIds = [ 9 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":{"Ethereum":{"chainId":"11155111"}}},{"AccountKey20":{"network":null,"key":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14"}}]}}`, 10 | ]; 11 | const accountId = '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b'; 12 | 13 | const beneficiary = { 14 | parents: 0, 15 | interior: { 16 | X1: { AccountId32: { id: accountId } }, 17 | }, 18 | } as UnionXcmMultiLocation; 19 | 20 | const xcmVersion = 3; 21 | 22 | const expected = { 23 | V3: [ 24 | { 25 | depositAsset: { 26 | assets: { 27 | Wild: { 28 | AllCounted: 1, 29 | }, 30 | }, 31 | beneficiary, 32 | }, 33 | }, 34 | ], 35 | }; 36 | 37 | expect(createXcmOnDestination(assetIds, beneficiary, xcmVersion)).toEqual(expected); 38 | }); 39 | it('Should correctly construct the default XCM message for V4', () => { 40 | const assetIds = [ 41 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":{"Ethereum":{"chainId":"11155111"}}},{"AccountKey20":{"network":null,"key":"0xfff9976782d46cc05630d1f6ebab18b2324d6b14"}}]}}`, 42 | `{"parents":"2","interior":{"X2":[{"GlobalConsensus":{"Ethereum":{"chainId":"11155111"}}},{"AccountKey20":{"network":null,"key":"0xc3d088842dcf02c13699f936bb83dfbbc6f721ab"}}]}}`, 43 | ]; 44 | const accountId = '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b'; 45 | 46 | const beneficiary = { 47 | parents: 0, 48 | interior: { 49 | X1: { AccountId32: { id: accountId } }, 50 | }, 51 | } as UnionXcmMultiLocation; 52 | 53 | const xcmVersion = 4; 54 | 55 | const expected = { 56 | V4: [ 57 | { 58 | depositAsset: { 59 | assets: { 60 | Wild: { 61 | AllCounted: 2, 62 | }, 63 | }, 64 | beneficiary, 65 | }, 66 | }, 67 | ], 68 | }; 69 | 70 | expect(createXcmOnDestination(assetIds, beneficiary, xcmVersion)).toEqual(expected); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/createXcmOnDestination.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import type { AnyJson } from '@polkadot/types/types'; 4 | 5 | import { UnionXcmMultiLocation } from '../types.js'; 6 | 7 | export const createXcmOnDestination = ( 8 | assets: string[], 9 | beneficiary: UnionXcmMultiLocation, 10 | xcmVersion: number, 11 | customXcmOnDest?: string, 12 | ): AnyJson => { 13 | const xcmMessage: AnyJson = customXcmOnDest 14 | ? (JSON.parse(customXcmOnDest) as AnyJson) 15 | : [ 16 | { 17 | depositAsset: { 18 | assets: { 19 | Wild: { 20 | AllCounted: assets.length, 21 | }, 22 | }, 23 | beneficiary, 24 | }, 25 | }, 26 | ]; 27 | 28 | const defaultDestXcm: AnyJson = 29 | xcmVersion === 3 30 | ? { 31 | V3: xcmMessage, 32 | } 33 | : { 34 | V4: xcmMessage, 35 | }; 36 | 37 | return defaultDestXcm; 38 | }; 39 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/createXcmVersionedAssetId.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 4 | import { resolveMultiLocation } from '../../util/resolveMultiLocation.js'; 5 | import { XcmVersionedAssetId } from '../types.js'; 6 | import { parseLocationStrToLocation } from './parseLocationStrToLocation.js'; 7 | 8 | export const createXcmVersionedAssetId = ( 9 | destFeesAssetId: string | undefined, 10 | xcmVersion: number, 11 | ): XcmVersionedAssetId => { 12 | if (!destFeesAssetId) { 13 | throw new BaseError('resolveAssetTransferType: destFeesAssetId not found', BaseErrorsEnum.InvalidInput); 14 | } 15 | if (xcmVersion < 3) { 16 | throw new BaseError('XcmVersion must be greater than 2', BaseErrorsEnum.InvalidXcmVersion); 17 | } 18 | 19 | let remoteFeesId: XcmVersionedAssetId; 20 | let location = parseLocationStrToLocation(destFeesAssetId); 21 | 22 | if (typeof location === 'object' && 'v1' in location) { 23 | location = parseLocationStrToLocation(JSON.stringify(location.v1)); 24 | } 25 | 26 | if (xcmVersion === 3) { 27 | remoteFeesId = { 28 | V3: { 29 | Concrete: location, 30 | }, 31 | }; 32 | } else { 33 | remoteFeesId = { 34 | V4: resolveMultiLocation(location, 4), 35 | }; 36 | } 37 | 38 | return remoteFeesId; 39 | }; 40 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/dedupeAssets.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { FungibleObjAssetType, FungibleStrAssetType } from '../types.js'; 4 | 5 | /** 6 | * This removes duplicate assets when given a sorted list 7 | * 8 | * @param assets FungibleStrAssetType[] | FungibleObjAssetType[] 9 | */ 10 | export const dedupeAssets = ( 11 | assets: FungibleStrAssetType[] | FungibleObjAssetType[], 12 | ): FungibleStrAssetType[] | FungibleObjAssetType[] => { 13 | const dedupedAssets = []; 14 | 15 | for (let i = 0; i < assets.length; i++) { 16 | const multiAsset = assets[i]; 17 | 18 | if (i === 0) { 19 | dedupedAssets.push(multiAsset); 20 | continue; 21 | } 22 | 23 | const previousAsset = dedupedAssets[dedupedAssets.length - 1]; 24 | if (JSON.stringify(multiAsset) === JSON.stringify(previousAsset)) { 25 | continue; 26 | } 27 | 28 | dedupedAssets.push(multiAsset); 29 | } 30 | 31 | if (typeof assets[0].fun.Fungible === 'string') { 32 | return dedupedAssets as FungibleStrAssetType[]; 33 | } 34 | 35 | return dedupedAssets as FungibleObjAssetType[]; 36 | }; 37 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/fetchPalletInstanceId.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockBifrostParachainApi } from '../../testHelpers/mockBifrostParachainApi'; 4 | import { mockSystemApi } from '../../testHelpers/mockSystemApi'; 5 | import { fetchPalletInstanceId } from './fetchPalletInstanceId'; 6 | 7 | describe('fetchPalletInstanceId', () => { 8 | it('Should return the correct string when the api has the assets pallet', () => { 9 | const res = fetchPalletInstanceId(mockSystemApi, '1984', false, false); 10 | 11 | expect(res).toEqual('50'); 12 | }); 13 | it('Should correctly grab the poolAssets pallet instance', () => { 14 | const res = fetchPalletInstanceId(mockSystemApi, '0', true, false); 15 | 16 | expect(res).toEqual('55'); 17 | }); 18 | it('Should correctly grab the foreignAssets pallet instance', () => { 19 | const res = fetchPalletInstanceId( 20 | mockSystemApi, 21 | `{"parents":"2","interior":{"X1":{"GlobalConsensus":"Polkadot"}}}`, 22 | false, 23 | true, 24 | ); 25 | 26 | expect(res).toEqual('53'); 27 | }); 28 | it('Should correctly error when both foreign assets and pool assets are true', () => { 29 | const err = () => fetchPalletInstanceId(mockSystemApi, '0', true, true); 30 | 31 | expect(err).toThrow("Can't find the appropriate pallet when both liquid tokens and foreign assets"); 32 | }); 33 | it('Should correctly return an empty string when the assets pallet is not found', () => { 34 | const res = fetchPalletInstanceId(mockBifrostParachainApi, '0', false, false); 35 | 36 | expect(res).toEqual(''); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/fetchPalletInstanceId.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | 5 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 6 | import { assetIdIsLocation } from './assetIdIsLocation.js'; 7 | 8 | /** 9 | * This fetches the metadata for the chain we are connected to and searches for the appropriate pallet and returns its index. 10 | * 11 | * @param api ApiPromise. 12 | * @param isLiquidToken Boolean to determine whether or not to fetch the PoolAssets id. 13 | */ 14 | export const fetchPalletInstanceId = ( 15 | api: ApiPromise, 16 | assetId: string, 17 | isLiquidToken: boolean, 18 | isForeignAsset: boolean, 19 | ): string => { 20 | if (isLiquidToken && isForeignAsset) { 21 | throw new BaseError( 22 | "Can't find the appropriate pallet when both liquid tokens and foreign assets", 23 | BaseErrorsEnum.InternalError, 24 | ); 25 | } 26 | 27 | const palletName = 28 | isLiquidToken && api.query.poolAssets 29 | ? 'PoolAssets' 30 | : isForeignAsset && api.query.foreignAssets && assetIdIsLocation(assetId) 31 | ? 'ForeignAssets' 32 | : api.query.assets 33 | ? 'Assets' 34 | : ''; 35 | 36 | // return early if assets pallet is not found and palletName is not PoolAssets or ForeignAssets 37 | if (!api.query.assets && palletName.length === 0) { 38 | return palletName; 39 | } 40 | 41 | const pallet = api.registry.metadata.pallets.filter((pallet) => pallet.name.toString() === palletName); 42 | 43 | if (pallet.length === 0) { 44 | throw new BaseError( 45 | `No ${palletName} pallet available, can't find a valid PalletInstance.`, 46 | BaseErrorsEnum.PalletNotFound, 47 | ); 48 | } 49 | 50 | return pallet[0].index.toString(); 51 | }; 52 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/foreignAssetMultiLocationIsInCacheOrRegistry.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { Registry } from '../../registry'; 4 | import { foreignAssetMultiLocationIsInCacheOrRegistry } from './foreignAssetMultiLocationIsInCacheOrRegistry'; 5 | 6 | describe('foreignAssetMultiLocationIsInCacheOrRegistry', () => { 7 | it('Should return true if a given foreign asset multilocation exists in the asset api registry', () => { 8 | const expected = true; 9 | const multiLocation = '{"parents":1,"interior":{ "X2":[{"Parachain":2125},{"GeneralIndex":0}]}}'; 10 | const registry = new Registry('statemine', {}); 11 | 12 | const foreignAssetExistsInRegistry = foreignAssetMultiLocationIsInCacheOrRegistry(multiLocation, registry, 2); 13 | 14 | expect(foreignAssetExistsInRegistry).toEqual(expected); 15 | }); 16 | 17 | it('Should return false if a given foreign asset multilocation does not exist in the asset api registry', () => { 18 | const expected = false; 19 | const multiLocation = '{"parents":"1","interior":{"X1": {"Parachain":"200100510"}}}'; 20 | const registry = new Registry('statemine', {}); 21 | 22 | const foreignAssetExistsInRegistry = foreignAssetMultiLocationIsInCacheOrRegistry(multiLocation, registry, 2); 23 | 24 | expect(foreignAssetExistsInRegistry).toEqual(expected); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/foreignAssetMultiLocationIsInCacheOrRegistry.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { ASSET_HUB_CHAIN_ID } from '../../consts.js'; 4 | import { Registry } from '../../registry/index.js'; 5 | import type { ForeignAssetsInfo } from '../../registry/types.js'; 6 | import { resolveMultiLocation } from '../../util/resolveMultiLocation.js'; 7 | import { sanitizeKeys } from '../../util/sanitizeKeys.js'; 8 | 9 | export const foreignAssetMultiLocationIsInCacheOrRegistry = ( 10 | multilocationStr: string, 11 | registry: Registry, 12 | xcmVersion: number, 13 | ): boolean => { 14 | // check if foreign asset exists in assets cache 15 | const foreignAssetsCache = registry.cache[registry.relayChain][ASSET_HUB_CHAIN_ID].foreignAssetsInfo; 16 | if (checkForeignAssetExists(foreignAssetsCache, multilocationStr, xcmVersion)) { 17 | return true; 18 | } 19 | 20 | // check if foreign asset exists in registry 21 | const foreignAssetsRegistry = registry.currentRelayRegistry[ASSET_HUB_CHAIN_ID].foreignAssetsInfo; 22 | return checkForeignAssetExists(foreignAssetsRegistry, multilocationStr, xcmVersion); 23 | }; 24 | 25 | const checkForeignAssetExists = ( 26 | foreignAssetsInfo: ForeignAssetsInfo, 27 | multiLocationStr: string, 28 | xcmVersion: number, 29 | ): boolean => { 30 | const multiLocation = resolveMultiLocation(multiLocationStr, xcmVersion); 31 | 32 | if (Object.keys(foreignAssetsInfo).length > 0) { 33 | const foreignAssets = Object.entries(foreignAssetsInfo).map((data) => { 34 | return data[1].multiLocation; 35 | }); 36 | 37 | for (const asset of foreignAssets) { 38 | // We sanitize `assets` here since multiLocation is already sanitized. 39 | if (JSON.stringify(sanitizeKeys(JSON.parse(asset))) === JSON.stringify(multiLocation)) { 40 | return true; 41 | } 42 | } 43 | } 44 | 45 | return false; 46 | }; 47 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/foreignAssetsMultiLocationExists.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { ApiPromise } from '@polkadot/api'; 4 | 5 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 6 | import { Registry } from '../../registry/index.js'; 7 | import type { ForeignAssetsData } from '../../registry/types.js'; 8 | import type { AssetMetadata } from '../../types.js'; 9 | import { UnionXcmMultiLocation } from '../types.js'; 10 | 11 | export const foreignAssetsMultiLocationExists = async ( 12 | assetHubApi: ApiPromise, 13 | registry: Registry, 14 | multilocationStr: string, 15 | ): Promise => { 16 | try { 17 | const rawMultiLocation = JSON.parse(multilocationStr) as UnionXcmMultiLocation; 18 | const foreignAsset = await assetHubApi.query.foreignAssets.asset(rawMultiLocation); 19 | 20 | // check if foreign asset exists 21 | if (foreignAsset.toString().length > 0) { 22 | const foreignAssetMetadata = (await assetHubApi.query.foreignAssets.metadata(rawMultiLocation)).toHuman(); 23 | 24 | if (foreignAssetMetadata) { 25 | const metadata = foreignAssetMetadata as AssetMetadata; 26 | const assetSymbol = metadata.symbol; 27 | const assetName = metadata.name; 28 | const asset: ForeignAssetsData = { 29 | symbol: assetSymbol, 30 | name: assetName, 31 | multiLocation: JSON.stringify(rawMultiLocation), 32 | }; 33 | 34 | // cache the foreign asset 35 | registry.setForeignAssetInCache(assetSymbol, asset); 36 | } 37 | 38 | return true; 39 | } 40 | 41 | return false; 42 | } catch (error) { 43 | if ((error as Error).message.includes('::')) { 44 | const errorInfo = (error as Error).message.split('::'); 45 | const errorDetails = errorInfo[errorInfo.length - 2].concat(errorInfo[errorInfo.length - 1]); 46 | 47 | throw new BaseError( 48 | `Error creating MultiLocation type:${errorDetails}`, 49 | BaseErrorsEnum.InvalidMultiLocationAsset, 50 | ); 51 | } else { 52 | throw new BaseError( 53 | `Error creating multilocation type: ${(error as Error).message}`, 54 | BaseErrorsEnum.InvalidMultiLocationAsset, 55 | ); 56 | } 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/getGlobalConsensusSystemName.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { KNOWN_GLOBAL_CONSENSUS_SYSTEM_NAMES } from '../../consts.js'; 4 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 5 | import { parseLocationStrToLocation } from './parseLocationStrToLocation.js'; 6 | 7 | export const getGlobalConsensusSystemName = (destLocation: string): string => { 8 | const location = parseLocationStrToLocation(destLocation); 9 | 10 | for (const systemName of KNOWN_GLOBAL_CONSENSUS_SYSTEM_NAMES) { 11 | if ( 12 | (location.interior.X1 && JSON.stringify(location.interior.X1).toLowerCase().includes(systemName)) || 13 | (location.interior.X2 && JSON.stringify(location.interior.X2).toLowerCase().includes(systemName)) 14 | ) { 15 | return systemName; 16 | } 17 | } 18 | 19 | throw new BaseError( 20 | `No known consensus system found for location ${destLocation}`, 21 | BaseErrorsEnum.UnknownConsensusSystem, 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/getParachainNativeAssetLocation.spec.ts: -------------------------------------------------------------------------------- 1 | import { Registry } from '../../registry'; 2 | import { getParachainNativeAssetLocation } from './getParachainNativeAssetLocation'; 3 | 4 | describe('getParachainNativeAssetLocation', () => { 5 | it('Correctly returns the native asset location for Moonbeam', () => { 6 | const registry = new Registry('moonbeam', {}); 7 | 8 | const res = getParachainNativeAssetLocation(registry, 'GLMR', '2034'); 9 | 10 | expect(JSON.stringify(res)).toEqual('{"parents":0,"interior":{"X1":{"PalletInstance":"10"}}}'); 11 | }); 12 | it('Correctly returns the native asset location for Moonriver', () => { 13 | const registry = new Registry('moonriver', {}); 14 | 15 | const res = getParachainNativeAssetLocation(registry, 'MOVR', '2001'); 16 | 17 | expect(JSON.stringify(res)).toEqual('{"parents":0,"interior":{"X1":{"PalletInstance":"10"}}}'); 18 | }); 19 | it('Correctly returns the native asset location for Phala', () => { 20 | const registry = new Registry('phala', {}); 21 | 22 | const res = getParachainNativeAssetLocation(registry, 'PHA', '2034'); 23 | 24 | expect(JSON.stringify(res)).toEqual('{"parents":0,"interior":{"X1":{"Parachain":"2035"}}}'); 25 | }); 26 | it('Correctly returns the native asset location for Hydration', () => { 27 | const registry = new Registry('hydradx', {}); 28 | 29 | const res = getParachainNativeAssetLocation(registry, 'HDX', '2004'); 30 | 31 | expect(JSON.stringify(res)).toEqual('{"parents":0,"interior":{"X1":{"GeneralIndex":"0"}}}'); 32 | }); 33 | it('Correctly returns the native asset location for Hydration when destination is AssetHub', () => { 34 | const registry = new Registry('hydradx', {}); 35 | 36 | const res = getParachainNativeAssetLocation(registry, 'HDX', '1000'); 37 | 38 | expect(JSON.stringify(res)).toEqual('{"parents":0,"interior":{"X1":{"GeneralIndex":"0"}}}'); 39 | }); 40 | it('correctly throws an error when no valid location is found for the given native asset', () => { 41 | const registry = new Registry('moonbeam', {}); 42 | 43 | const err = () => getParachainNativeAssetLocation(registry, 'GLMR', '3369'); 44 | 45 | expect(err).toThrow('No location found for asset GLMR'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/getXcAssetMultiLocationByAssetId.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { ApiPromise } from '@polkadot/api'; 4 | 5 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 6 | import { Registry } from '../../registry/index.js'; 7 | import type { SanitizedXcAssetsData } from '../../registry/types.js'; 8 | import { validateNumber } from '../../validate/index.js'; 9 | import { getAssetId } from './getAssetId.js'; 10 | 11 | export const getXcAssetMultiLocationByAssetId = async ( 12 | api: ApiPromise, 13 | assetId: string, 14 | specName: string, 15 | xcmVersion: number, 16 | registry: Registry, 17 | ): Promise => { 18 | // if symbol, get the integer or multilocation assetId 19 | if (!validateNumber(assetId)) { 20 | assetId = await getAssetId(api, registry, assetId, specName, xcmVersion); 21 | } 22 | 23 | const paraId = registry.lookupChainIdBySpecName(specName); 24 | const paraXcAssets = registry.getRelaysRegistry[paraId].xcAssetsData as SanitizedXcAssetsData[]; 25 | 26 | if (validateNumber(assetId)) { 27 | for (const info of paraXcAssets) { 28 | if (typeof info.asset === 'string' && info.asset === assetId) { 29 | return info.xcmV1MultiLocation; 30 | } 31 | } 32 | } else if (assetId.toLowerCase().includes('parents')) { 33 | return assetId; 34 | } 35 | 36 | throw new BaseError( 37 | `assetId ${assetId} is not a valid symbol or integer asset id for ${specName}`, 38 | BaseErrorsEnum.InvalidAsset, 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/isParachain.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { isParachain } from './isParachain'; 4 | 5 | describe('isParachain', () => { 6 | it('Should correctly return true for a chainId of 2001', () => { 7 | const result = isParachain('2001'); 8 | 9 | expect(result).toEqual(true); 10 | }); 11 | it('Should correctly return false for a chainId of 1000', () => { 12 | const result = isParachain('1000'); 13 | 14 | expect(result).toEqual(false); 15 | }); 16 | it('Should correctly return false for a chainId of 0', () => { 17 | const result = isParachain('0'); 18 | 19 | expect(result).toEqual(false); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/isParachain.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | import BN from 'bn.js'; 3 | 4 | import { validateNumber } from '../../validate/index.js'; 5 | 6 | /** 7 | * Determines if a chain is a parachain based on the value of its chainId being strictly greater than or equal to 2000 8 | * @param chainId 9 | * @returns boolean 10 | */ 11 | export const isParachain = (chainId: string): boolean => { 12 | if (validateNumber(chainId)) { 13 | const chainIdAsNumber = new BN(chainId); 14 | return chainIdAsNumber.gte(new BN(2000)); 15 | } 16 | 17 | return false; 18 | }; 19 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/isParachainPrimaryNativeAsset.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { Registry } from '../../registry/index.js'; 4 | import { Direction } from '../../types.js'; 5 | 6 | export const isParachainPrimaryNativeAsset = ( 7 | registry: Registry, 8 | specName: string, 9 | xcmDirection: Direction, 10 | assetId?: string, 11 | ) => { 12 | // check direction is origin Para 13 | if (xcmDirection != Direction.ParaToSystem && xcmDirection != Direction.ParaToPara) { 14 | return false; 15 | } 16 | 17 | // in case of empty array, undefined assetId is considered to be the relay chains primary native asset 18 | if (!assetId) { 19 | return true; 20 | } 21 | 22 | // if assetId is an empty string 23 | // treat it as the parachains primary native asset 24 | if (assetId === '') { 25 | return true; 26 | } 27 | 28 | const currentChainId = registry.lookupChainIdBySpecName(specName); 29 | const { tokens } = registry.currentRelayRegistry[currentChainId]; 30 | const primaryParachainNativeAsset = tokens[0]; 31 | if (primaryParachainNativeAsset.toLowerCase() === assetId.toLowerCase()) { 32 | return true; 33 | } 34 | 35 | return false; 36 | }; 37 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/isRelayNativeAsset.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { RELAY_CHAIN_IDS } from '../../consts.js'; 4 | import { Registry } from '../../registry/index.js'; 5 | 6 | export const isRelayNativeAsset = (registry: Registry, assetId: string): boolean => { 7 | const relayChainId = RELAY_CHAIN_IDS[0]; 8 | const { tokens } = registry.currentRelayRegistry[relayChainId]; 9 | const originChainId = registry.lookupChainIdBySpecName(registry.specName); 10 | const originIsRelayChain = originChainId === relayChainId; 11 | 12 | // if assetId is an empty string treat it as the relay asset 13 | if (assetId === '') { 14 | return true; 15 | } 16 | 17 | if (originIsRelayChain && assetId === `{"parents":"0","interior":{"Here":""}}`) { 18 | return true; 19 | } 20 | 21 | if (!originIsRelayChain && assetId === `{"parents":"1","interior":{"Here":""}}`) { 22 | return true; 23 | } 24 | 25 | for (const token of tokens) { 26 | if (token.toLowerCase() === assetId.toLowerCase()) { 27 | return true; 28 | } 29 | } 30 | 31 | return false; 32 | }; 33 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/isSystemChain.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { isSystemChain } from './isSystemChain'; 4 | 5 | describe('isSystemChain', () => { 6 | it('Should correctly return true for a chainId of 1000', () => { 7 | const result = isSystemChain('1000'); 8 | 9 | expect(result).toEqual(true); 10 | }); 11 | it('Should correctly return false for a chainId of 2000', () => { 12 | const result = isSystemChain('2000'); 13 | 14 | expect(result).toEqual(false); 15 | }); 16 | it('Should correctly return false for a chainId of 0', () => { 17 | const result = isSystemChain('0'); 18 | 19 | expect(result).toEqual(false); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/isSystemChain.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | import BN from 'bn.js'; 3 | 4 | import { validateNumber } from '../../validate/index.js'; 5 | 6 | /** 7 | * Determines if a chain is a system chain based on the value of its chainId being strictly greater than 0 and less than 2000 8 | * @param chainId 9 | * @returns boolean 10 | */ 11 | export const isSystemChain = (chainId: string): boolean => { 12 | if (validateNumber(chainId)) { 13 | const chainIdAsNumber = new BN(chainId); 14 | return chainIdAsNumber.gt(new BN(0)) && chainIdAsNumber.lt(new BN(2000)); 15 | } 16 | 17 | return false; 18 | }; 19 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/multiLocationAssetIsParachainsNativeAsset.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { multiLocationAssetIsParachainsNativeAsset } from './multiLocationAssetIsParachainsNativeAsset'; 4 | 5 | describe('multiLocationAssetIsParachainsNativeAsset', () => { 6 | type Test = [destChainId: string, multiLocationAssetId: string, expected: boolean]; 7 | 8 | it('Should correctly return true when a foreign assets multilocation matches its native chain of origin', () => { 9 | const tests: Test[] = [ 10 | ['2023', '{"parents":"1","interior":{"X1": {"Parachain":"2023"}}}', true], 11 | ['2125', '{"parents":"1","interior":{"X2":[{"Parachain":"2125"},{"GeneralIndex":"0"}]}}', true], 12 | [ 13 | '2006', 14 | '{"parents":"1","interior":{"X3":[{"Parachain":"2006"},{"PalletInstance": "50"},{"GeneralIndex":"10"}]}}', 15 | true, 16 | ], 17 | ]; 18 | 19 | for (const test of tests) { 20 | const [destChainId, multiLocationAssetId, expected] = test; 21 | const isNativeChain = multiLocationAssetIsParachainsNativeAsset(destChainId, multiLocationAssetId); 22 | 23 | expect(isNativeChain).toEqual(expected); 24 | } 25 | }); 26 | 27 | it('Should correctly return false when a foreign assets multilocation does not match the destination chain', () => { 28 | const tests: Test[] = [ 29 | ['2023', '{"parents":"1","interior":{"X1": {"Parachain":"2000"}}}', false], 30 | ['2004', '{"parents":"1","interior":{"X2":[{"Parachain":"2006"},{"GeneralIndex":"0"}]}}', false], 31 | [ 32 | '2030', 33 | '{"parents":"1","interior":{"X3":[{"Parachain":"2006"},{"PalletInstance": "50"},{"GeneralIndex":"10"}]}}', 34 | false, 35 | ], 36 | ]; 37 | 38 | for (const test of tests) { 39 | const [destChainId, multiLocationAssetId, expected] = test; 40 | const isNativeChain = multiLocationAssetIsParachainsNativeAsset(destChainId, multiLocationAssetId); 41 | 42 | expect(isNativeChain).toEqual(expected); 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/multiLocationAssetIsParachainsNativeAsset.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | /** 4 | * Returns whether a provided multilocation is native to a parachain 5 | * 6 | * @param chainID The Id of the chain to be checked 7 | * @param multiLocationAssetId multilocation asset id 8 | */ 9 | export const multiLocationAssetIsParachainsNativeAsset = (chainId: string, multiLocationAssetId: string): boolean => { 10 | const destChainMultiLocationid = `"Parachain":"${chainId}"`; 11 | 12 | if ( 13 | multiLocationAssetId 14 | .toLowerCase() 15 | .trim() 16 | .replace(/ /g, '') // remove all empty spaces 17 | .includes(destChainMultiLocationid.toLowerCase().trim().replace(/ /g, '')) 18 | ) { 19 | return true; 20 | } 21 | 22 | return false; 23 | }; 24 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/parseLocationStrToLocation.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { parseLocationStrToLocation } from './parseLocationStrToLocation'; 4 | 5 | describe('parseLocationStrToLocation', () => { 6 | it('Should correctly return a valid UnionXcmMultilocation', () => { 7 | const locationStr = '{"parents":"2","interior":{"X2":[{"GlobalConsensus":"Polkadot"},{"Parachain":"1000"}]}}'; 8 | const expected = { 9 | parents: '2', 10 | interior: { 11 | X2: [ 12 | { 13 | GlobalConsensus: 'Polkadot', 14 | }, 15 | { 16 | Parachain: '1000', 17 | }, 18 | ], 19 | }, 20 | }; 21 | const result = parseLocationStrToLocation(locationStr); 22 | 23 | expect(result).toEqual(expected); 24 | }); 25 | it('Should correctly return a valid Ethereum UnionXcmMultilocation', () => { 26 | const locationStr = `{"parents":"2","interior":{"X1":[{"GlobalConsensus":{"Ethereum":{"chainId":"1"}}}]}}`; 27 | const expected = { 28 | parents: '2', 29 | interior: { 30 | X1: [ 31 | { 32 | GlobalConsensus: { 33 | Ethereum: { 34 | chainId: '1', 35 | }, 36 | }, 37 | }, 38 | ], 39 | }, 40 | }; 41 | const result = parseLocationStrToLocation(locationStr); 42 | 43 | expect(result).toEqual(expected); 44 | }); 45 | it('Should correctly error when an unparseable location string is provided', () => { 46 | const locationStr = '{"parents":"2","interior":{"X2":[{GlobalConsensus":"Polkadot"},{"Parachain":"1000"}]}}'; 47 | 48 | const err = () => parseLocationStrToLocation(locationStr); 49 | 50 | expect(err).toThrow( 51 | 'Unable to parse {"parents":"2","interior":{"X2":[{GlobalConsensus":"Polkadot"},{"Parachain":"1000"}]}} as a valid location', 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/parseLocationStrToLocation.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 4 | import { UnionXcmMultiLocation } from '../types.js'; 5 | 6 | export const parseLocationStrToLocation = (locationStr: string, xcmVersion?: number): UnionXcmMultiLocation => { 7 | let location = ''; 8 | const isX1V4Location = locationStr.includes(`X1":[`) && locationStr.includes(`]`); 9 | 10 | if (xcmVersion && xcmVersion === 3 && isX1V4Location) { 11 | location = locationStr.replace('[', '').replace(']', ''); 12 | } else { 13 | location = locationStr; 14 | } 15 | 16 | try { 17 | return JSON.parse(location) as UnionXcmMultiLocation; 18 | } catch { 19 | throw new BaseError(`Unable to parse ${locationStr} as a valid location`, BaseErrorsEnum.InvalidInput); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/createXcmTypes/util/resolveAssetTransferType.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { BaseError, BaseErrorsEnum } from '../../errors/index.js'; 4 | import { AssetTransferType } from '../../types.js'; 5 | import { resolveMultiLocation } from '../../util/resolveMultiLocation.js'; 6 | import { UnionXcmMultiLocation } from '../types.js'; 7 | 8 | export const resolveAssetTransferType = ( 9 | assetTransferType: string | undefined, 10 | xcmVersion: number, 11 | remoteTransferLocationStr?: string, 12 | ): AssetTransferType => { 13 | if (!assetTransferType) { 14 | throw new BaseError('resolveAssetTransferType: assetTransferType not found', BaseErrorsEnum.InvalidInput); 15 | } 16 | if (xcmVersion < 3) { 17 | throw new BaseError('Bridge txs require XCM version 3 or higher', BaseErrorsEnum.InvalidXcmVersion); 18 | } 19 | 20 | let transferType: AssetTransferType; 21 | let remoteTransferLocation: UnionXcmMultiLocation; 22 | if (remoteTransferLocationStr && assetTransferType === 'RemoteReserve') { 23 | remoteTransferLocation = resolveMultiLocation(remoteTransferLocationStr, xcmVersion); 24 | transferType = 25 | xcmVersion === 3 26 | ? { RemoteReserve: { V3: remoteTransferLocation } } 27 | : { RemoteReserve: { V4: remoteTransferLocation } }; 28 | } else { 29 | transferType = 30 | assetTransferType === 'LocalReserve' 31 | ? { 32 | LocalReserve: 'null', 33 | } 34 | : assetTransferType === 'DestinationReserve' 35 | ? { 36 | DestinationReserve: 'null', 37 | } 38 | : { 39 | Teleport: 'null', 40 | }; 41 | } 42 | 43 | return transferType; 44 | }; 45 | -------------------------------------------------------------------------------- /src/errors/checkBaseInputOptions.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { Format, TransferArgsOpts } from '../types'; 4 | import { checkBaseInputOptions } from './checkBaseInputOptions'; 5 | 6 | describe('checkBaseInputOptions', () => { 7 | it('Should error when a call is used with `PaysWithFeeOrigin`', () => { 8 | const opts = { 9 | format: 'call', 10 | paysWithFeeOrigin: '1984', 11 | } as TransferArgsOpts; 12 | const err = () => checkBaseInputOptions(opts, 'statemine'); 13 | 14 | expect(err).toThrow('PaysWithFeeOrigin is only compatible with the format type payload. Received: call'); 15 | }); 16 | it('Should error when a submittable is used with `PaysWithFeeOrigin`', () => { 17 | const opts = { 18 | format: 'submittable', 19 | paysWithFeeOrigin: '1984', 20 | } as TransferArgsOpts; 21 | const err = () => checkBaseInputOptions(opts, 'statemine'); 22 | 23 | expect(err).toThrow('PaysWithFeeOrigin is only compatible with the format type payload. Received: submittable'); 24 | }); 25 | it('Should error when a payload format is inputted but there is no sendersAddr', () => { 26 | const opts = { 27 | format: 'payload', 28 | paysWithFeeOrigin: '1984', 29 | } as TransferArgsOpts; 30 | const err = () => checkBaseInputOptions(opts, 'statemine'); 31 | 32 | expect(err).toThrow("The 'sendersAddr' option must be present when constructing a 'payload' format."); 33 | }); 34 | it('Should error when a sendersAddr is an incorrect format', () => { 35 | const opts = { 36 | format: 'payload', 37 | paysWithFeeOrigin: '1984', 38 | sendersAddr: '0x000', 39 | } as TransferArgsOpts; 40 | const err = () => checkBaseInputOptions(opts, 'statemine'); 41 | 42 | expect(err).toThrow('The inputted sendersAddr is not valid. Invalid base58 character "0" (0x30) at index 0'); 43 | }); 44 | it('Should not error when a sendersAddr is a correct format', () => { 45 | const opts = { 46 | format: 'payload', 47 | paysWithFeeOrigin: '1984', 48 | sendersAddr: 'FBeL7DanUDs5SZrxZY1CizMaPgG9vZgJgvr52C2dg81SsF1', 49 | } as TransferArgsOpts; 50 | const res = () => checkBaseInputOptions(opts, 'statemine'); 51 | 52 | expect(res).not.toThrow(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/errors/checkBaseInputOptions.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { Format, TransferArgsOpts } from '../types.js'; 4 | import { validateAddress } from '../validate/index.js'; 5 | import { BaseError, BaseErrorsEnum } from './BaseError.js'; 6 | import { disableOpts } from './disableOpts.js'; 7 | 8 | /** 9 | * Ensure that options that require certain inputs are validated. 10 | * 11 | * @param opts 12 | */ 13 | export const checkBaseInputOptions = (opts: TransferArgsOpts, specName: string) => { 14 | const { paysWithFeeOrigin, format, sendersAddr } = opts; 15 | 16 | disableOpts(opts, specName); 17 | 18 | if (paysWithFeeOrigin) { 19 | if (format === 'call' || format === 'submittable') { 20 | throw new BaseError( 21 | `PaysWithFeeOrigin is only compatible with the format type payload. Received: ${format}`, 22 | BaseErrorsEnum.InvalidInput, 23 | ); 24 | } 25 | } 26 | 27 | if (format === 'payload' && !sendersAddr) { 28 | throw new BaseError( 29 | `The 'sendersAddr' option must be present when constructing a 'payload' format.`, 30 | BaseErrorsEnum.InvalidInput, 31 | ); 32 | } 33 | 34 | if (sendersAddr) { 35 | const [bool, errMsg] = validateAddress(sendersAddr); 36 | 37 | if (!bool) { 38 | throw new BaseError(`The inputted sendersAddr is not valid. ${errMsg as string}`, BaseErrorsEnum.InvalidInput); 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/errors/checkBaseInputTypes.spec.ts: -------------------------------------------------------------------------------- 1 | import { checkBaseInputTypes } from './checkBaseInputTypes'; 2 | 3 | describe('checkBaseInputTypes', () => { 4 | it('Should error when destChainId is the wrong type', () => { 5 | const err = () => 6 | checkBaseInputTypes( 7 | 1000 as unknown as string, 8 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 9 | ['TST'], 10 | ['10000000000'], 11 | ); 12 | 13 | expect(err).toThrow(`'destChainId' must be a string. Received: number`); 14 | }); 15 | it('Should error when destAddr is the wrong type', () => { 16 | const err = () => checkBaseInputTypes('1000', 10000000 as unknown as string, ['TST'], ['10000000000']); 17 | 18 | expect(err).toThrow(`'destAddr' must be a string. Received: number`); 19 | }); 20 | it('Should error when assetIds is the wrong type', () => { 21 | const err = () => 22 | checkBaseInputTypes( 23 | '1000', 24 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 25 | 'TST' as unknown as string[], 26 | ['10000000000'], 27 | ); 28 | 29 | expect(err).toThrow(`'assetIds' must be an array. Received: string`); 30 | }); 31 | it('Should error when assetIds has the wrong types in the array', () => { 32 | const err = () => 33 | checkBaseInputTypes( 34 | '1000', 35 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 36 | [1] as unknown as string[], 37 | ['10000000000'], 38 | ); 39 | 40 | expect(err).toThrow(`All inputs in the 'assetIds' array must be strings: Received: a number at index 0`); 41 | }); 42 | it('Should error when amounts is the wrong type', () => { 43 | const err = () => 44 | checkBaseInputTypes( 45 | '1000', 46 | '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', 47 | ['TST'], 48 | 10000000000 as unknown as string[], 49 | ); 50 | 51 | expect(err).toThrow(`'amounts' must be an array. Received: number`); 52 | }); 53 | it('Should error when amounts has the wrong types in the array', () => { 54 | const err = () => 55 | checkBaseInputTypes('1000', '0xf5d5714c084c112843aca74f8c498da06cc5a2d63153b825189baa51043b1f0b', ['TST'], [ 56 | 10000000000, 57 | ] as unknown as string[]); 58 | 59 | expect(err).toThrow(`All inputs in the 'amounts' array must be strings: Received: a number at index 0`); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/errors/checkBaseInputTypes.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { BaseError, BaseErrorsEnum } from './BaseError.js'; 4 | 5 | /** 6 | * Check the base types for the inputs for createTransferTransaction 7 | * 8 | * @param destChainId 9 | * @param destAddr 10 | * @param assetIds 11 | * @param amounts 12 | */ 13 | export const checkBaseInputTypes = (destChainId: string, destAddr: string, assetIds: string[], amounts: string[]) => { 14 | if (typeof destChainId !== 'string') { 15 | throw new BaseError(`'destChainId' must be a string. Received: ${typeof destChainId}`, BaseErrorsEnum.InvalidInput); 16 | } 17 | 18 | if (typeof destAddr !== 'string') { 19 | throw new BaseError(`'destAddr' must be a string. Received: ${typeof destAddr}`, BaseErrorsEnum.InvalidInput); 20 | } 21 | 22 | if (!Array.isArray(assetIds)) { 23 | throw new BaseError(`'assetIds' must be an array. Received: ${typeof assetIds}`, BaseErrorsEnum.InvalidInput); 24 | } else { 25 | for (let i = 0; i < assetIds.length; i++) { 26 | if (typeof assetIds[i] !== 'string') { 27 | throw new BaseError( 28 | `All inputs in the 'assetIds' array must be strings: Received: a ${typeof assetIds[i]} at index ${i}`, 29 | BaseErrorsEnum.InvalidInput, 30 | ); 31 | } 32 | } 33 | } 34 | 35 | if (!Array.isArray(amounts)) { 36 | throw new BaseError(`'amounts' must be an array. Received: ${typeof amounts}`, BaseErrorsEnum.InvalidInput); 37 | } else { 38 | for (let i = 0; i < amounts.length; i++) { 39 | if (typeof amounts[i] !== 'string') { 40 | throw new BaseError( 41 | `All inputs in the 'amounts' array must be strings: Received: a ${typeof amounts[i]} at index ${i}`, 42 | BaseErrorsEnum.InvalidInput, 43 | ); 44 | } 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/checkLocalParachainInput.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { mockBifrostParachainApi } from '../../testHelpers/mockBifrostParachainApi'; 4 | import { mockMoonriverParachainApi } from '../../testHelpers/mockMoonriverParachainApi'; 5 | import { checkLocalParachainInput } from './checkLocalParachainInput'; 6 | import { LocalTxType } from './types'; 7 | 8 | describe('checkLocalParachainInput', () => { 9 | it('Should return LocalTxType.Balances', () => { 10 | const res = checkLocalParachainInput(mockBifrostParachainApi, [], ['10000']); 11 | expect(res).toEqual(LocalTxType.Balances); 12 | }); 13 | it('Should return LocalTxType.Tokens', () => { 14 | const res = checkLocalParachainInput(mockBifrostParachainApi, ['dot'], ['10000']); 15 | expect(res).toEqual(LocalTxType.Tokens); 16 | }); 17 | it("Should correctly error when the tokens pallet doesn'nt exist", () => { 18 | expect(() => checkLocalParachainInput(mockMoonriverParachainApi, ['ksm'], ['10000'])).toThrow( 19 | 'Given the inputted assets, no Tokens pallet was found for a local transfer.', 20 | ); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/checkLocalParachainInput.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | 5 | import { BaseError, BaseErrorsEnum } from '../BaseError.js'; 6 | import { LocalTxType } from './types.js'; 7 | 8 | export const checkLocalParachainInput = (api: ApiPromise, assetIds: string[], amounts: string[]): LocalTxType => { 9 | if (assetIds.length > 1 || amounts.length !== 1) { 10 | throw new BaseError( 11 | 'Local transactions must have the `assetIds` input be a length of 1 or 0, and the `amounts` input be a length of 1', 12 | BaseErrorsEnum.InvalidInput, 13 | ); 14 | } 15 | 16 | if (assetIds.length === 0) { 17 | // If no asset is inputted, the chains native asset will be transferred. 18 | return LocalTxType.Balances; 19 | } else { 20 | if (!api.tx.tokens) { 21 | throw new BaseError( 22 | 'Given the inputted assets, no Tokens pallet was found for a local transfer.', 23 | BaseErrorsEnum.PalletNotFound, 24 | ); 25 | } 26 | return LocalTxType.Tokens; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/checkLocalRelayInput.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { Registry } from '../../registry'; 4 | import { checkLocalRelayInput } from './checkLocalRelayInput'; 5 | import { LocalTxType } from './types'; 6 | 7 | describe('checkLocalRelayInput', () => { 8 | const registry = new Registry('asset-hub-paseo', { chainName: 'paseo' }); 9 | 10 | it('Should return LocalTxType.Balances', () => { 11 | const res = checkLocalRelayInput([], ['10000'], registry); 12 | expect(res).toEqual(LocalTxType.Balances); 13 | }); 14 | it('Should error with an invalid input', () => { 15 | expect(() => checkLocalRelayInput(['pas', 'ksm'], ['100000'], registry)).toThrow( 16 | 'Local transactions must have the `assetIds` input be a length of 1 or 0, and the `amounts` input be a length of 1', 17 | ); 18 | }); 19 | it('Should correctly error when a non native asset is passed in', () => { 20 | expect(() => 21 | checkLocalRelayInput(['{"parents":"3","interior":{"X1":{"GlobalConsensus":"Westend"}}}'], ['100000'], registry), 22 | ).toThrow( 23 | 'AssetId {"parents":"3","interior":{"X1":{"GlobalConsensus":"Westend"}}} is not the native asset of paseo relay chain. Expected an assetId of PAS or asset location {"parents":"0","interior":{"Here":""}}', 24 | ); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/checkLocalRelayInput.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { RELAY_CHAIN_IDS, RELAY_CHAINS_NATIVE_ASSET_LOCATION } from '../../consts.js'; 4 | import { isRelayNativeAsset } from '../../createXcmTypes/util/isRelayNativeAsset.js'; 5 | import { Registry } from '../../registry/index.js'; 6 | import { BaseError, BaseErrorsEnum } from '../BaseError.js'; 7 | import { LocalTxType } from './types.js'; 8 | 9 | export const checkLocalRelayInput = (assetIds: string[], amounts: string[], registry: Registry): LocalTxType => { 10 | if (assetIds.length > 1 || amounts.length !== 1) { 11 | throw new BaseError( 12 | 'Local transactions must have the `assetIds` input be a length of 1 or 0, and the `amounts` input be a length of 1', 13 | BaseErrorsEnum.InvalidInput, 14 | ); 15 | } 16 | const relayChainId = RELAY_CHAIN_IDS[0]; 17 | const relayChain = registry.currentRelayRegistry[relayChainId]; 18 | const relayChainNativeAsset = relayChain.tokens[0]; 19 | 20 | const assetIsRelayNativeAsset = assetIds.length === 0 ? true : isRelayNativeAsset(registry, assetIds[0]); 21 | if (!assetIsRelayNativeAsset) { 22 | throw new BaseError( 23 | `AssetId ${assetIds[0]} is not the native asset of ${relayChain.specName} relay chain. Expected an assetId of ${relayChainNativeAsset} or asset location ${RELAY_CHAINS_NATIVE_ASSET_LOCATION}`, 24 | ); 25 | } 26 | 27 | return LocalTxType.Balances; 28 | }; 29 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/checkLocalSystemParachainInput.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { ApiPromise } from '@polkadot/api'; 4 | 5 | import type { Registry } from '../../registry/index.js'; 6 | import { checkLocalTxInput } from './checkLocalTxInput.js'; 7 | import { LocalTxType } from './types.js'; 8 | 9 | export const checkLocalSystemParachainInput = async ( 10 | api: ApiPromise, 11 | assetIds: string[], 12 | amounts: string[], 13 | specName: string, 14 | registry: Registry, 15 | xcmVersion: number, 16 | isForeignAssetsTransfer: boolean, 17 | isLiquidTokenTransfer: boolean, 18 | ): Promise => { 19 | return await checkLocalTxInput( 20 | api, 21 | assetIds, 22 | amounts, 23 | specName, 24 | registry, 25 | xcmVersion, 26 | isForeignAssetsTransfer, 27 | isLiquidTokenTransfer, 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/index.ts: -------------------------------------------------------------------------------- 1 | export { checkLocalParachainInput } from './checkLocalParachainInput.js'; 2 | export { checkLocalRelayInput } from './checkLocalRelayInput.js'; 3 | export { checkLocalSystemParachainInput } from './checkLocalSystemParachainInput.js'; 4 | -------------------------------------------------------------------------------- /src/errors/checkLocalTxInput/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export enum LocalTxType { 4 | Assets = 'Assets', 5 | Balances = 'Balances', 6 | ForeignAssets = 'ForeignAssets', 7 | PoolAssets = 'PoolAssets', 8 | Tokens = 'Tokens', 9 | } 10 | -------------------------------------------------------------------------------- /src/errors/checkXcmVersion.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { checkXcmVersion } from './checkXcmVersion'; 4 | 5 | describe('checkXcmVersion', () => { 6 | it('Should correctly throw an error on a unsupported version', () => { 7 | const err = () => checkXcmVersion(1); 8 | expect(err).toThrow('1 is not a supported xcm version. Supported versions are: 2, 3 and 4'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/errors/checkXcmVersion.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { SUPPORTED_XCM_VERSIONS } from '../consts.js'; 4 | import { BaseError, BaseErrorsEnum } from './BaseError.js'; 5 | 6 | /** 7 | * Check that an inputted xcm version is supported. If not throw an error. 8 | * 9 | * @param version Xcm version 10 | */ 11 | export const checkXcmVersion = (version: number) => { 12 | if (!SUPPORTED_XCM_VERSIONS.includes(version)) { 13 | throw new BaseError( 14 | `${version} is not a supported xcm version. Supported versions are: ${SUPPORTED_XCM_VERSIONS[0]}, ${SUPPORTED_XCM_VERSIONS[1]} and ${SUPPORTED_XCM_VERSIONS[2]}`, 15 | BaseErrorsEnum.InvalidXcmVersion, 16 | ); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/errors/disableOpts.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { MappedOpts } from '../config/disabledOpts.js'; 4 | import { disabledOpts } from '../config/disabledOpts.js'; 5 | import type { Format, TransferArgsOpts } from '../types.js'; 6 | 7 | /** 8 | * This checks specific options to ensure they are disabled when met in certain conditions. 9 | * 10 | * @param opts Options for `createTransferTransaction` 11 | * @param specName SpecName of the current chain 12 | */ 13 | export const disableOpts = (opts: TransferArgsOpts, specName: string) => { 14 | const optKeys = Object.keys(opts) as MappedOpts[]; 15 | const chain = specName.toLowerCase(); 16 | 17 | for (const key of optKeys) { 18 | const disabledKeyInfo = disabledOpts[key]; 19 | if (opts[key] && disabledKeyInfo.disabled) { 20 | if (disabledKeyInfo.chains.includes('*')) { 21 | disabledKeyInfo.error(key, `all chains`); 22 | } 23 | if (disabledKeyInfo.chains.includes(chain)) { 24 | disabledKeyInfo.error(key, chain); 25 | } 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { BaseError, BaseErrorsEnum } from './BaseError.js'; 4 | export { checkBaseInputOptions } from './checkBaseInputOptions.js'; 5 | export { checkBaseInputTypes } from './checkBaseInputTypes.js'; 6 | export * from './checkLocalTxInput/index.js'; 7 | export { checkXcmTxInputs } from './checkXcmTxInputs.js'; 8 | export { checkXcmVersion } from './checkXcmVersion.js'; 9 | -------------------------------------------------------------------------------- /src/errors/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | export interface CheckXcmTxInputsOpts { 4 | isForeignAssetsTransfer: boolean; 5 | isLiquidTokenTransfer: boolean; 6 | isPrimaryParachainNativeAsset: boolean; 7 | paysWithFeeDest?: string; 8 | weightLimit?: { refTime?: string; proofSize?: string }; 9 | assetTransferType?: string; 10 | remoteReserveAssetTransferTypeLocation?: string; 11 | feesTransferType?: string; 12 | remoteReserveFeesTransferTypeLocation?: string; 13 | dryRunCall?: boolean; 14 | sendersAddr?: string; 15 | xcmFeeAsset?: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | export * from './AssetTransferApi.js'; 4 | export * from './constructApiPromise.js'; 5 | export * from './types.js'; 6 | -------------------------------------------------------------------------------- /src/registry/findRelayChain.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import reg from '@substrate/asset-transfer-api-registry'; 4 | 5 | import { findRelayChain } from './findRelayChain'; 6 | import { parseRegistry } from './parseRegistry'; 7 | import { ChainInfoKeys, ChainInfoRegistry } from './types'; 8 | 9 | describe('findRelayChain', () => { 10 | const registry = parseRegistry(reg as ChainInfoRegistry, {}); 11 | it('Should correctly discover the right relay chain', () => { 12 | const findPolkadot = findRelayChain('statemint', registry); 13 | const findKusama = findRelayChain('statemine', registry); 14 | const findWestend = findRelayChain('westmint', registry); 15 | const findPaseo = findRelayChain('asset-hub-paseo', registry); 16 | 17 | expect(findPolkadot).toEqual('polkadot'); 18 | expect(findKusama).toEqual('kusama'); 19 | expect(findWestend).toEqual('westend'); 20 | expect(findPaseo).toEqual('paseo'); 21 | }); 22 | it('Should correctly discover the right relay chain when using asset-hub specNames', () => { 23 | const findPolkadot = findRelayChain('asset-hub-polkadot', registry); 24 | const findKusama = findRelayChain('asset-hub-kusama', registry); 25 | const findWestend = findRelayChain('asset-hub-westend', registry); 26 | const findPaseo = findRelayChain('asset-hub-paseo', registry); 27 | 28 | expect(findPolkadot).toEqual('polkadot'); 29 | expect(findKusama).toEqual('kusama'); 30 | expect(findWestend).toEqual('westend'); 31 | expect(findPaseo).toEqual('paseo'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/registry/findRelayChain.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { 4 | KUSAMA_ASSET_HUB_SPEC_NAMES, 5 | PASEO_ASSET_HUB_SPEC_NAME, 6 | POLKADOT_ASSET_HUB_SPEC_NAMES, 7 | WESTEND_ASSET_HUB_SPEC_NAMES, 8 | } from '../consts.js'; 9 | import { BaseError, BaseErrorsEnum } from '../errors/index.js'; 10 | import type { ChainInfoKeys, ChainInfoRegistry, RelayChains } from './types.js'; 11 | /** 12 | * Finds the name of the relay chain of a given specName. If the chain does not exist within the registry 13 | * an error will be thrown. 14 | * 15 | * @param specName SpecName of the given chain 16 | * @param registry The registry to search 17 | * @param chainName optional chain name for cases where more than one chain share a specName 18 | */ 19 | export const findRelayChain = (specName: string, registry: ChainInfoRegistry): RelayChains => { 20 | const polkadotChains = Object.keys(registry.polkadot).map((val) => registry.polkadot[val].specName); 21 | if (polkadotChains.includes(specName.toLowerCase()) || POLKADOT_ASSET_HUB_SPEC_NAMES.includes(specName.toLowerCase())) 22 | return 'polkadot'; 23 | 24 | const paseoChains = Object.keys(registry.paseo).map((val) => registry.paseo[val].specName); 25 | if (paseoChains.includes(specName.toLowerCase()) || PASEO_ASSET_HUB_SPEC_NAME.includes(specName.toLowerCase())) { 26 | return 'paseo'; 27 | } 28 | 29 | const kusamaChains = Object.keys(registry.kusama).map((val) => registry.kusama[val].specName); 30 | if (kusamaChains.includes(specName.toLowerCase()) || KUSAMA_ASSET_HUB_SPEC_NAMES.includes(specName.toLowerCase())) 31 | return 'kusama'; 32 | 33 | const westendChains = Object.keys(registry.westend).map((val) => registry.westend[val].specName); 34 | if (westendChains.includes(specName.toLowerCase()) || WESTEND_ASSET_HUB_SPEC_NAMES.includes(specName.toLowerCase())) 35 | return 'westend'; 36 | 37 | throw new BaseError(`Cannot find the relay chain for specName: ${specName}`, BaseErrorsEnum.InternalError); 38 | }; 39 | -------------------------------------------------------------------------------- /src/registry/index.ts: -------------------------------------------------------------------------------- 1 | export { findRelayChain } from './findRelayChain.js'; 2 | export { parseRegistry } from './parseRegistry.js'; 3 | export { Registry } from './Registry.js'; 4 | -------------------------------------------------------------------------------- /src/registry/registry-json-cjs.cts: -------------------------------------------------------------------------------- 1 | import registry from '@substrate/asset-transfer-api-registry'; 2 | 3 | export { registry }; 4 | -------------------------------------------------------------------------------- /src/registry/registry-json.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | // https://github.com/isaacs/tshy?tab=readme-ov-file#commonjs-dialect-polyfills 3 | // @ts-ignore suppress "Import assertions are not allowed on statements that compile to CommonJS 'require' calls." 4 | import registry from '@substrate/asset-transfer-api-registry' with { type: 'json' }; 5 | 6 | export { registry }; 7 | -------------------------------------------------------------------------------- /src/sanitize/sanitizeAddress.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { sanitizeAddress } from './sanitizeAddress'; 4 | 5 | describe('sanitizeAddress', () => { 6 | it('Should sanitize a SS58 address to the correct publicKey', () => { 7 | expect(sanitizeAddress('1ULZhwpUPLLg5VRYiq6rBHY8XaShAmBW7kqGBfvHBqrgBcN')).toEqual( 8 | '0x14d97bde56483534b553ec13c1867924b2eb559cbf9767090af5d8ecf8ee2936', 9 | ); 10 | }); 11 | it('Should return the public key if its already valid', () => { 12 | expect(sanitizeAddress('0x14d97bde56483534b553ec13c1867924b2eb559cbf9767090af5d8ecf8ee2936')).toEqual( 13 | '0x14d97bde56483534b553ec13c1867924b2eb559cbf9767090af5d8ecf8ee2936', 14 | ); 15 | }); 16 | it('Should error with an invalid address', () => { 17 | expect(() => sanitizeAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU')).toThrow( 18 | 'Invalid decoded address checksum', 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/sanitize/sanitizeAddress.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { isHex, u8aToHex } from '@polkadot/util'; 4 | import { decodeAddress } from '@polkadot/util-crypto'; 5 | 6 | import { BaseError, BaseErrorsEnum } from '../errors/index.js'; 7 | import { validateAddress } from '../validate/validateAddress.js'; 8 | 9 | /** 10 | * First validate the given address is valid, then ensure 11 | * it is converted and sanitized to it's public key. 12 | * 13 | * @param addr Either a SS58 address or the public key of an address 14 | */ 15 | export const sanitizeAddress = (addr: string): `0x${string}` => { 16 | const [isValid, msg] = validateAddress(addr); 17 | if (!isValid) { 18 | throw new BaseError(msg as string, BaseErrorsEnum.InvalidAddress); 19 | } 20 | // Verify that it is a valid hex public key 21 | if (isHex(addr)) return addr; 22 | 23 | const decoded = decodeAddress(addr); 24 | return u8aToHex(decoded); 25 | }; 26 | -------------------------------------------------------------------------------- /src/testHelpers/createApiWithAugmentations.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2023 @polkadot/api authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | // 4 | // This has been converted from the original version which can be found here: 5 | // 6 | // https://github.com/polkadot-js/api/blob/v10.0.1/packages/api-derive/src/test/helpers.ts 7 | 8 | import { ApiPromise, WsProvider } from '@polkadot/api'; 9 | import { Metadata, TypeRegistry } from '@polkadot/types'; 10 | 11 | export function createApiWithAugmentations(metadataHex: `0x${string}`): ApiPromise { 12 | const registry = new TypeRegistry(); 13 | const metadata = new Metadata(registry, metadataHex); 14 | 15 | registry.setMetadata(metadata); 16 | 17 | const api = new ApiPromise({ 18 | provider: new WsProvider('ws://', false), 19 | registry, 20 | }); 21 | 22 | api.injectMetadata(metadata, true, registry); 23 | 24 | return api; 25 | } 26 | -------------------------------------------------------------------------------- /src/testHelpers/mockBifrostParachainApi.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { bifrostKusamaV13000 } from './metadata/bifrostKusamaV13000'; 5 | 6 | export const mockBifrostParachainApi = createApiWithAugmentations(bifrostKusamaV13000); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockHydrationParachainApi.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { hydrationV3100 } from './metadata/hydrationV3100'; 5 | 6 | export const mockHydrationParachainApi = createApiWithAugmentations(hydrationV3100); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockMoonriverParachainApi.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { moonriverV2302 } from './metadata/moonriverV2302'; 5 | 6 | export const mockMoonriverParachainApi = createApiWithAugmentations(moonriverV2302); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockQueryWeightToAssetFeeResult.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | export const mockQueryWeightToAssetFeeResult = { 4 | ok: 3500000000000000, 5 | }; 6 | -------------------------------------------------------------------------------- /src/testHelpers/mockQueryXcmWeightResult.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | export const mockQueryXcmWeightResult = { 4 | Ok: { 5 | refTime: 3500000000, 6 | proofSize: 350000000, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/testHelpers/mockRelayApiV1007001.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { westendV1007001 } from './metadata/westendV1007001'; 5 | 6 | export const mockRelayApiV1007001 = createApiWithAugmentations(westendV1007001); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockRelayApiV1016000.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { westendV1016000 } from './metadata/westendV1016000'; 5 | 6 | export const mockRelayApiV1016000 = createApiWithAugmentations(westendV1016000); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockRelayApiV9420.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { kusamaV9420 } from './metadata/kusamaV9420'; 5 | 6 | export const mockRelayApiV9420 = createApiWithAugmentations(kusamaV9420); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockSystemApi.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { createApiWithAugmentations } from './createApiWithAugmentations'; 4 | import { assetHubWestendV1016000 } from './metadata/assetHubWestendV1016000'; 5 | 6 | export const mockSystemApi = createApiWithAugmentations(assetHubWestendV1016000); 7 | -------------------------------------------------------------------------------- /src/testHelpers/mockWeightInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export const mockWeightInfo = { 4 | weight: { 5 | refTime: '133179000', 6 | proofSize: '0', 7 | }, 8 | class: 'Normal', 9 | partialFee: '171607466', 10 | }; 11 | -------------------------------------------------------------------------------- /src/util/callExistsInRuntime.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { AssetTransferApi } from '../AssetTransferApi'; 4 | import { adjustedMockBifrostParachainApi } from '../testHelpers/adjustedMockBifrostParachainApi'; 5 | import { adjustedMockRelayApiNoLimitedReserveTransferAssets } from '../testHelpers/adjustedMockRelayApiNoLimitedReserveTransferAssets'; 6 | import { callExistsInRuntime } from './callExistsInRuntime'; 7 | 8 | describe('callExistsInRuntime', () => { 9 | it('Correctly returns false when a selected runtime call is not found in the provided runtime', () => { 10 | const relayAssetsApiNoLimitedReserveTransferAssets = new AssetTransferApi( 11 | adjustedMockRelayApiNoLimitedReserveTransferAssets, 12 | 'kusama', 13 | 2, 14 | { registryType: 'NPM' }, 15 | ); 16 | const runtimeCall = 'limitedReserveAssetsTransfer'; 17 | const xcmPalletName = 'xcmPallet'; 18 | 19 | expect(callExistsInRuntime(relayAssetsApiNoLimitedReserveTransferAssets.api, runtimeCall, xcmPalletName)).toEqual( 20 | false, 21 | ); 22 | }); 23 | it('Correctly returns true when a selected runtime call is found in the provided runtime', () => { 24 | const bifrostAssetsApi = new AssetTransferApi(adjustedMockBifrostParachainApi, 'bifrost', 2, { 25 | registryType: 'NPM', 26 | }); 27 | const runtimeCall = 'transferMultiasset'; 28 | const xcmPalletName = 'xTokens'; 29 | 30 | expect(callExistsInRuntime(bifrostAssetsApi.api, runtimeCall, xcmPalletName)).toEqual(true); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/util/callExistsInRuntime.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { ApiPromise } from '@polkadot/api'; 4 | 5 | export const callExistsInRuntime = (api: ApiPromise, runtimeCall: string, xcmPalletName: string): boolean => { 6 | const availableRuntimeCalls = Object.keys(api.tx[xcmPalletName]).map((call) => call); 7 | 8 | return availableRuntimeCalls.indexOf(runtimeCall) != -1; 9 | }; 10 | -------------------------------------------------------------------------------- /src/util/deepEqual.spec.ts: -------------------------------------------------------------------------------- 1 | import { deepEqual } from './deepEqual'; 2 | 3 | describe('deepEqual', () => { 4 | it('Should return true for 2 equal objects that are out of order', () => { 5 | const a = { x: { y: 'z' }, z: ['1', '2', '3'] }; 6 | const b = { z: ['1', '2', '3'], x: { y: 'z' } }; 7 | expect(deepEqual(a, b)).toEqual(true); 8 | }); 9 | it('Should return false with 2 unequal objects', () => { 10 | const a = { x: { y: 'z' }, z: ['1', '2', '3'] }; 11 | const b = { w: { y: 'z' }, z: ['1', '2', '3'] }; 12 | expect(deepEqual(a, b)).toEqual(false); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/util/deepEqual.ts: -------------------------------------------------------------------------------- 1 | import { AnyJson } from '@polkadot/types/types'; 2 | /* eslint-disable @typescript-eslint/no-unsafe-argument */ 3 | 4 | /** 5 | * Courtesy of https://stackoverflow.com/a/32922084/13609948 for this solution. 6 | * Checks the equality between 2 objects. 7 | * 8 | * @param x 9 | * @param y 10 | * @returns 11 | */ 12 | export function deepEqual(x: AnyJson, y: AnyJson): boolean { 13 | const ok = Object.keys, 14 | tx = typeof x, 15 | ty = typeof y; 16 | return x && y && tx === 'object' && tx === ty 17 | ? ok(x).length === ok(y).length && 18 | ok(x).every((key) => deepEqual((x as { [key: string]: AnyJson })[key], (y as { [key: string]: AnyJson })[key])) 19 | : x === y; 20 | } 21 | -------------------------------------------------------------------------------- /src/util/detectJsEvn.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Parity Technologies (UK) Ltd. 2 | 3 | import { BaseError, BaseErrorsEnum } from '../errors/index.js'; 4 | 5 | /** 6 | * Returns the specific environment the api is running in. 7 | * It will throw an error when the environment is not supported. 8 | */ 9 | export const detectJsEnv = (): string => { 10 | const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined'; 11 | const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null; 12 | 13 | if (isNode) return 'node'; 14 | if (isBrowser) return 'browser'; 15 | 16 | throw new BaseError( 17 | 'The following JS environment is not supported. Currently only the browser, and NodeJS have support.', 18 | BaseErrorsEnum.UnsupportedEnvironment, 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/util/normalizeArrToStr.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { normalizeArrToStr } from './normalizeArrToStr'; 4 | 5 | describe('normalizeAccounts', () => { 6 | it('Should handle an array with both strings and numbers', () => { 7 | const vals = ['100', 100]; 8 | 9 | expect(normalizeArrToStr(vals)).toEqual(['100', '100']); 10 | }); 11 | 12 | it('Should handle just string correctly', () => { 13 | const vals = ['100', '100']; 14 | 15 | expect(normalizeArrToStr(vals)).toEqual(['100', '100']); 16 | }); 17 | 18 | it('Should handle just numbers correctly', () => { 19 | const vals = [100, 100]; 20 | 21 | expect(normalizeArrToStr(vals)).toEqual(['100', '100']); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/util/normalizeArrToStr.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export const normalizeArrToStr = (arr: (string | number)[]): string[] => { 4 | return arr.map((val) => { 5 | if (typeof val === 'string') { 6 | return val; 7 | } 8 | 9 | if (typeof val === 'number') { 10 | return val.toString(); 11 | } 12 | 13 | // Should never hit this, this exists to make the typescript compiler happy. 14 | return val; 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/util/resolveMultiLocation.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { resolveMultiLocation } from './resolveMultiLocation'; 4 | 5 | describe('resolveMultiLocation', () => { 6 | it('Should correctly not throw an error when xcmVersion is 3 and the multiLocation does not contain a generalKey Junction', () => { 7 | const str = `{"parents":0,"interior":{"here": null}}`; 8 | const err = () => resolveMultiLocation(str, 3); 9 | 10 | expect(err).not.toThrow(); 11 | }); 12 | it('Should correctly return a resolved multilocation object given a correct value', () => { 13 | const str = `{"parents":1,"interior":{"x2":[{"parachain":2001},{"generalKey":"0x0001"}]}}`; 14 | const exp = { Parents: '1', Interior: { X2: [{ Parachain: '2001' }, { GeneralKey: '0x0001' }] } }; 15 | 16 | expect(resolveMultiLocation(str, 2)).toStrictEqual(exp); 17 | }); 18 | it('Should correctly return a resolved V4 X1 location object given a correct value', () => { 19 | const str = `{"parents":"2","interior":{"X1":{"GlobalConsensus":"Polkadot"}}}`; 20 | const exp = { parents: '2', interior: { X1: [{ GlobalConsensus: 'Polkadot' }] } }; 21 | 22 | expect(resolveMultiLocation(str, 4)).toStrictEqual(exp); 23 | }); 24 | it('Should correctly error when xcmVersion is less than 3 and the asset location contains a GlobalConsensus junction', () => { 25 | const str = `{"parents":1,"interior":{"x1":{"GlobalConsensus":"Kusama"}}}`; 26 | const err = () => resolveMultiLocation(str, 2); 27 | 28 | expect(err).toThrow( 29 | 'XcmVersion must be greater than 2 for MultiLocations that contain a GlobalConsensus junction.', 30 | ); 31 | }); 32 | it('Should correctly resolve an xcmV1Multilocation values location', () => { 33 | const str = `{"v1":{"parents":1,"interior":{"x2":[{"globalConsensus":{"ethereum":{"chainId":1}}},{"accountKey20":{"network":null,"key":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}]}}}`; 34 | const exp = { 35 | parents: 1, 36 | interior: { 37 | x2: [ 38 | { globalConsensus: { ethereum: { chainId: 1 } } }, 39 | { accountKey20: { network: null, key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' } }, 40 | ], 41 | }, 42 | }; 43 | 44 | expect(resolveMultiLocation(str, 3)).toStrictEqual(exp); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/util/resolveMultiLocation.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import type { AnyJson } from '@polkadot/types/types'; 4 | 5 | import { SUPPORTED_XCM_VERSIONS } from '../consts.js'; 6 | import type { UnionXcmMultiLocation, XcmV4Junction } from '../createXcmTypes/types.js'; 7 | import { parseLocationStrToLocation } from '../createXcmTypes/util/parseLocationStrToLocation.js'; 8 | import { BaseError, BaseErrorsEnum } from '../errors/BaseError.js'; 9 | import { sanitizeKeys } from './sanitizeKeys.js'; 10 | 11 | /** 12 | * This ensures that the given multiLocation does not have certain junctions depending on the xcm version. 13 | * 14 | * @param multiLocation 15 | * @param xcmVersion 16 | * @returns 17 | */ 18 | export const resolveMultiLocation = (multiLocation: AnyJson, xcmVersion: number): UnionXcmMultiLocation => { 19 | const multiLocationStr = typeof multiLocation === 'string' ? multiLocation : JSON.stringify(multiLocation); 20 | 21 | // Ensure we check this first since the main difference between v2, and later versions is the `globalConsensus` junction 22 | const hasGlobalConsensus = 23 | multiLocationStr.includes('globalConsensus') || multiLocationStr.includes('GlobalConsensus'); 24 | if (xcmVersion < 3 && hasGlobalConsensus) { 25 | throw new BaseError( 26 | 'XcmVersion must be greater than 2 for MultiLocations that contain a GlobalConsensus junction.', 27 | BaseErrorsEnum.InvalidXcmVersion, 28 | ); 29 | } 30 | 31 | if (!SUPPORTED_XCM_VERSIONS.includes(xcmVersion)) { 32 | throw new BaseError(`Invalid XcmVersion for mulitLocation construction`, BaseErrorsEnum.InternalError); 33 | } 34 | 35 | let result = parseLocationStrToLocation(multiLocationStr, xcmVersion); 36 | 37 | // handle case where result is an xcmV1Multilocation from the registry 38 | if (typeof result === 'object' && 'v1' in result) { 39 | result = result.v1 as UnionXcmMultiLocation; 40 | } 41 | 42 | const isX1V4Location = multiLocationStr.includes('"X1":['); 43 | 44 | if (xcmVersion > 3 && typeof result === 'object' && result.interior?.X1 && !isX1V4Location) { 45 | result = { 46 | parents: result.parents, 47 | interior: { 48 | X1: [result.interior.X1 as XcmV4Junction], 49 | }, 50 | }; 51 | } 52 | 53 | if (!hasGlobalConsensus) { 54 | return sanitizeKeys(result); 55 | } else { 56 | return result; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/util/sanitizeKeys.spec.ts: -------------------------------------------------------------------------------- 1 | import { sanitizeKeys } from './sanitizeKeys'; 2 | 3 | describe('sanitizeKeys', () => { 4 | it('Should correctly update all keys in plain objects', () => { 5 | const obj = { key1: { key2: { key3: '', key4: '' } }, key5: '' }; 6 | const exp = { Key1: { Key2: { Key3: '', Key4: '' } }, Key5: '' }; 7 | expect(sanitizeKeys(obj)).toStrictEqual(exp); 8 | }); 9 | it('Should correctly update all keys in objects with arrays', () => { 10 | const obj = { key1: { key2: { key3: [{ key6: null, key7: null }, { key8: null }], key4: '' } }, key5: '' }; 11 | const exp = { Key1: { Key2: { Key3: [{ Key6: null, Key7: null }, { Key8: null }], Key4: '' } }, Key5: '' }; 12 | expect(sanitizeKeys(obj)).toStrictEqual(exp); 13 | }); 14 | it('Should correctly sanitize all keys that are relevant to an xcm junction', () => { 15 | const obj = { onlyChild: { palletinstance: '' }, Globalconsensus: { key1: '' } }; 16 | const exp = { OnlyChild: { PalletInstance: '' }, GlobalConsensus: { Key1: '' } }; 17 | expect(sanitizeKeys(obj)).toStrictEqual(exp); 18 | }); 19 | it('Should correctly sanitize values that are integers', () => { 20 | const obj = { OnlyChild: { PalletInstance: 10 }, GlobalConsensus: { Key1: '' } }; 21 | const exp = { OnlyChild: { PalletInstance: '10' }, GlobalConsensus: { Key1: '' } }; 22 | expect(sanitizeKeys(obj)).toStrictEqual(exp); 23 | 24 | const obj2 = { key1: { key2: { key3: [{ key6: 111, key7: 222 }, { key8: null }], key4: 333 } }, key5: 444 }; 25 | const exp2 = { Key1: { Key2: { Key3: [{ Key6: '111', Key7: '222' }, { Key8: null }], Key4: '333' } }, Key5: '444' }; 26 | expect(sanitizeKeys(obj2)).toStrictEqual(exp2); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/util/sanitizeKeys.ts: -------------------------------------------------------------------------------- 1 | import { AnyObj } from '../types.js'; 2 | 3 | enum MultiLocationJunctionTypeKeys { 4 | 'parachain' = 'Parachain', 5 | 'accountid32' = 'AccountId32', 6 | 'accountindex64' = 'AccountIndex64', 7 | 'accountkey20' = 'AccountKey20', 8 | 'palletinstance' = 'PalletInstance', 9 | 'generalindex' = 'GeneralIndex', 10 | 'generalkey' = 'GeneralKey', 11 | 'onlychild' = 'OnlyChild', 12 | 'plurality' = 'Plurality', 13 | 'globalconsensus' = 'GlobalConsensus', 14 | } 15 | 16 | const isPlainObject = (input: unknown) => { 17 | return input && !Array.isArray(input) && typeof input === 'object'; 18 | }; 19 | 20 | /** 21 | * Set all keys in an object with the first key being capitalized. 22 | * When a keys value is an integer it will convert that integer into a string. 23 | * 24 | * @param xcmObj 25 | */ 26 | export const sanitizeKeys = (xcmObj: T): T => { 27 | const final = {} as AnyObj; 28 | 29 | // Iterate over key-value pairs of the root object 'obj' 30 | for (const [key, value] of Object.entries(xcmObj)) { 31 | if (Array.isArray(value)) { 32 | final[mapKey(key)] = value.map(sanitizeKeys); 33 | } else if (!isPlainObject(value)) { 34 | // Ensure when the value is an integer that it is sanitized to a string. 35 | const sanitizedValue = Number.isInteger(value) ? Number(value).toString() : value; 36 | final[mapKey(key)] = sanitizedValue; 37 | } else { 38 | final[mapKey(key)] = sanitizeKeys(value as AnyObj); 39 | } 40 | } 41 | 42 | return final as T; 43 | }; 44 | 45 | const mapKey = (key: string): string => { 46 | const lowerKey = key.toLowerCase() as keyof typeof MultiLocationJunctionTypeKeys; 47 | if (MultiLocationJunctionTypeKeys[lowerKey]) { 48 | return MultiLocationJunctionTypeKeys[lowerKey] as string; 49 | } 50 | return key[0].toUpperCase() + key.slice(1).toLowerCase(); 51 | }; 52 | -------------------------------------------------------------------------------- /src/validate/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | export { validateAddress } from './validateAddress.js'; 4 | export { validateNumber } from './validateNumber.js'; 5 | -------------------------------------------------------------------------------- /src/validate/validateAddress.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { validateAddress } from './validateAddress'; 4 | 5 | describe('validateAddress', () => { 6 | it('Should not error with a valid substrate address', () => { 7 | expect(validateAddress('5EnxxUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG')).toEqual([true, undefined]); 8 | }); 9 | it('Should not error with a valid hex address', () => { 10 | expect(validateAddress('0x14d97bde56483534b553ec13c1867924b2eb559cbf9767090af5d8ecf8ee2936')).toEqual([ 11 | true, 12 | undefined, 13 | ]); 14 | }); 15 | it('Should not error with a valid ethereum address', () => { 16 | expect(validateAddress('0x96Bd611EbE3Af39544104e26764F4939924F6Ece')).toEqual([true, undefined]); 17 | }); 18 | it('Should error with a nonsense address', () => { 19 | expect(validateAddress('hello')).toEqual([false, 'Invalid base58 character "l" (0x6c) at index 2']); 20 | }); 21 | it('Should error with an address with invalid decoded address checksum', () => { 22 | expect(validateAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU')).toEqual([ 23 | false, 24 | 'Invalid decoded address checksum', 25 | ]); 26 | }); 27 | it('Should error with an address missing some bytes', () => { 28 | expect(validateAddress('y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b')).toEqual([ 29 | false, 30 | 'Invalid address format', 31 | ]); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/validate/validateAddress.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Parity Technologies (UK) Ltd. 2 | 3 | import { hexToU8a, isHex } from '@polkadot/util'; 4 | import { base58Decode, checkAddressChecksum, encodeAddress, isEthereumAddress } from '@polkadot/util-crypto'; 5 | import { defaults } from '@polkadot/util-crypto/address/defaults'; 6 | 7 | /** 8 | * Verify that an address is a valid substrate address. 9 | * 10 | * Note: this is very similar '@polkadot/util-crypto/address/checkAddress, 11 | * except it does not check the ss58 prefix. 12 | * 13 | * @param address potential ss58 or raw address 14 | */ 15 | export const validateAddress = (address: string): [boolean, string | undefined] => { 16 | let u8Address; 17 | 18 | if (isEthereumAddress(address)) { 19 | return [true, undefined]; 20 | } 21 | 22 | if (isHex(address)) { 23 | u8Address = base58Decode(encodeAddress(hexToU8a(address))); 24 | } else { 25 | try { 26 | u8Address = base58Decode(address); 27 | } catch (error) { 28 | return [false, (error as Error).message]; 29 | } 30 | } 31 | 32 | if (defaults.allowedEncodedLengths.includes(u8Address.length)) { 33 | const [isValid] = checkAddressChecksum(u8Address); 34 | 35 | return [isValid, isValid ? undefined : 'Invalid decoded address checksum']; 36 | } 37 | 38 | return [false, 'Invalid address format']; 39 | }; 40 | -------------------------------------------------------------------------------- /src/validate/validateNumber.spec.ts: -------------------------------------------------------------------------------- 1 | import { validateNumber } from './validateNumber'; 2 | 3 | describe('validateNumber', () => { 4 | it('Should correctly validate a number as a string with a length less than 16', () => { 5 | const res = validateNumber('123456789000000'); 6 | 7 | expect(res).toEqual(true); 8 | }); 9 | it('Should return false on an invalid number as a string with a length less than 16', () => { 10 | const res = validateNumber('test'); 11 | 12 | expect(res).toEqual(false); 13 | }); 14 | it('Should correctly validate a number as a string with a length greater than 16', () => { 15 | const res = validateNumber('123456789000000123456789'); 16 | 17 | expect(res).toEqual(true); 18 | }); 19 | it('Should return false on an invalid number as a string with a length greater than 16', () => { 20 | const res = validateNumber('thisisatestthissisatestthisisatest'); 21 | 22 | expect(res).toEqual(false); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/validate/validateNumber.ts: -------------------------------------------------------------------------------- 1 | import { MAX_NUM_LENGTH } from '../consts.js'; 2 | 3 | /** 4 | * Check if a given string input is a valid integer. 5 | * 6 | * @param val 7 | */ 8 | export const validateNumber = (val: string): boolean => { 9 | if (!val) return false; 10 | 11 | if (val.length < MAX_NUM_LENGTH) { 12 | const isNum = Number.parseInt(val); 13 | 14 | return !Number.isNaN(isNum); 15 | } 16 | 17 | try { 18 | BigInt(val); 19 | } catch { 20 | return false; 21 | } 22 | 23 | return true; 24 | }; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@substrate/dev/config/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "module": "NodeNext", 6 | "moduleResolution": "NodeNext", 7 | "outDir": "dist", 8 | "declaration": true, 9 | "emitDeclarationOnly": false, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "moduleDetection": "force", 13 | "strict": true, 14 | "resolveJsonModule": true 15 | }, 16 | "typeRoots": ["./node_modules/@types"], 17 | "exclude": ["node_modules", "./src/**/*.spec.ts", "./src/testHelpers"], 18 | "include": ["./src/**/*.ts", "./src/**/*.json", "./vitest.config.ts", "./vitest.workspace.ts", "./vitest-setup-file.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /typedoc.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entryPoints: [ 3 | './src/AssetTransferApi.ts', 4 | './src/constructApiPromise.ts', 5 | './src/types.ts', 6 | './src/errors/BaseError.ts', 7 | ], 8 | plugin: ['typedoc-theme-hierarchy'], 9 | exclude: ['**/*spec.ts', 'node_modules/**', './src/testHelpers/**'], 10 | excludeNotDocumented: true, 11 | navigationLinks: { 12 | Repository: 'https://github.com/paritytech/asset-transfer-api', 13 | 'Parity.io': 'https://www.parity.io/', 14 | }, 15 | includeVersion: true, 16 | excludeExternals: true, 17 | excludePrivate: true, 18 | hideGenerator: true, 19 | out: 'docs', 20 | }; 21 | -------------------------------------------------------------------------------- /vitest-setup-file.ts: -------------------------------------------------------------------------------- 1 | process.on('unhandledRejection', () => { 2 | // eslint-disable-next-line no-console 3 | console.log('Caught unhandled promise rejection'); 4 | }); 5 | 6 | export default {}; 7 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | setupFiles: ['./vitest-setup-file.ts'], 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config'; 2 | 3 | export default defineWorkspace([ 4 | { 5 | extends: './vitest.config.ts', 6 | test: { 7 | name: 'unit', 8 | include: ['./src/**/*.spec.ts'], 9 | exclude: ['./src/integrationTests/**/*.spec.ts', './e2e-tests/*.spec.ts'], 10 | }, 11 | }, 12 | { 13 | extends: './vitest.config.ts', 14 | test: { 15 | name: 'integration', 16 | include: ['./src/integrationTests/**/*.spec.ts'], 17 | }, 18 | }, 19 | { 20 | extends: './vitest.config.ts', 21 | test: { 22 | name: 'e2e', 23 | include: ['./e2e-tests/*.spec.ts'], 24 | }, 25 | }, 26 | ]); 27 | --------------------------------------------------------------------------------