diff --git a/apps/playground/src/components/XcmTransfer.tsx b/apps/playground/src/components/XcmTransfer.tsx index 943c4bb1..18c40951 100644 --- a/apps/playground/src/components/XcmTransfer.tsx +++ b/apps/playground/src/components/XcmTransfer.tsx @@ -3,12 +3,13 @@ import ErrorAlert from "./ErrorAlert"; import type { FormValuesTransformed } from "./TransferForm"; import TransferForm from "./TransferForm"; import { useDisclosure, useScrollIntoView } from "@mantine/hooks"; -import type { - Extrinsic, - TCurrencyInput, - TMultiLocation, - TNode, - TNodePolkadotKusama, +import { + isForeignAsset, + type Extrinsic, + type TCurrencyInput, + type TMultiLocation, + type TNode, + type TNodePolkadotKusama, } from "@paraspell/sdk"; import type { TPapiTransaction } from "@paraspell/sdk/papi"; import { getOtherAssets, isRelayChain } from "@paraspell/sdk/papi"; @@ -63,11 +64,11 @@ const XcmTransfer = () => { }; } } else if (currency) { - if (ethers.isAddress(currency.assetId)) { + if (isForeignAsset(currency) && ethers.isAddress(currency.assetId)) { return { symbol: currency.symbol ?? "" }; } - if (!currency.assetId) { + if (!isForeignAsset(currency)) { return { symbol: currency.symbol ?? "" }; } diff --git a/apps/playground/src/hooks/useCurrencyOptions.ts b/apps/playground/src/hooks/useCurrencyOptions.ts index 3009e942..f5297727 100644 --- a/apps/playground/src/hooks/useCurrencyOptions.ts +++ b/apps/playground/src/hooks/useCurrencyOptions.ts @@ -1,5 +1,5 @@ import type { TAsset, TNodeWithRelayChains } from "@paraspell/sdk"; -import { getSupportedAssets } from "@paraspell/sdk"; +import { getSupportedAssets, isForeignAsset } from "@paraspell/sdk"; import { useMemo } from "react"; const useCurrencyOptions = ( @@ -20,7 +20,7 @@ const useCurrencyOptions = ( const currencyMap = useMemo( () => supportedAssets.reduce((map: Record, asset) => { - const key = `${asset.symbol ?? "NO_SYMBOL"}-${asset.assetId ?? "NO_ID"}`; + const key = `${asset.symbol ?? "NO_SYMBOL"}-${isForeignAsset(asset) ? asset.assetId : "NO_ID"}`; map[key] = asset; return map; }, {}), @@ -31,7 +31,7 @@ const useCurrencyOptions = ( () => Object.keys(currencyMap).map((key) => ({ value: key, - label: `${currencyMap[key].symbol} - ${currencyMap[key].assetId ?? "Native"}`, + label: `${currencyMap[key].symbol} - ${isForeignAsset(currencyMap[key]) ? currencyMap[key].assetId : "Native"}`, })), [currencyMap], ); diff --git a/apps/playground/src/hooks/useRouterCurrencyOptions.ts b/apps/playground/src/hooks/useRouterCurrencyOptions.ts index 93c9bd82..818383a6 100644 --- a/apps/playground/src/hooks/useRouterCurrencyOptions.ts +++ b/apps/playground/src/hooks/useRouterCurrencyOptions.ts @@ -1,4 +1,8 @@ -import type { TAsset, TNodeWithRelayChains } from "@paraspell/sdk"; +import { + isForeignAsset, + type TAsset, + type TNodeWithRelayChains, +} from "@paraspell/sdk"; import { useMemo } from "react"; import type { TAutoSelect, TExchangeNode } from "@paraspell/xcm-router"; import { @@ -24,7 +28,7 @@ const useRouterCurrencyOptions = ( const currencyFromMap = useMemo( () => supportedAssetsFrom.reduce((map: Record, asset) => { - const key = `${asset.symbol ?? "NO_SYMBOL"}-${asset.assetId ?? "NO_ID"}`; + const key = `${asset.symbol ?? "NO_SYMBOL"}-${isForeignAsset(asset) ? asset.assetId : "NO_ID"}`; map[key] = asset; return map; }, {}), @@ -34,7 +38,7 @@ const useRouterCurrencyOptions = ( const currencyToMap = useMemo( () => supportedAssetsTo.reduce((map: Record, asset) => { - const key = `${asset.symbol ?? "NO_SYMBOL"}-${asset.assetId ?? "NO_ID"}`; + const key = `${asset.symbol ?? "NO_SYMBOL"}-${isForeignAsset(asset) ? asset.assetId : "NO_ID"}`; map[key] = asset; return map; }, {}), @@ -45,7 +49,7 @@ const useRouterCurrencyOptions = ( () => Object.keys(currencyFromMap).map((key) => ({ value: key, - label: `${currencyFromMap[key].symbol} - ${currencyFromMap[key].assetId ?? "Native"}`, + label: `${currencyFromMap[key].symbol} - ${isForeignAsset(currencyFromMap[key]) ? currencyFromMap[key].assetId : "Native"}`, })), [currencyFromMap], ); @@ -54,7 +58,7 @@ const useRouterCurrencyOptions = ( () => Object.keys(currencyToMap).map((key) => ({ value: key, - label: `${currencyToMap[key].symbol} - ${currencyToMap[key].assetId ?? "Native"}`, + label: `${currencyToMap[key].symbol} - ${isForeignAsset(currencyToMap[key]) ? currencyToMap[key].assetId : "Native"}`, })), [currencyToMap], ); diff --git a/apps/playground/src/routes/RouterTransferPage.tsx b/apps/playground/src/routes/RouterTransferPage.tsx index 937ca282..c547346e 100644 --- a/apps/playground/src/routes/RouterTransferPage.tsx +++ b/apps/playground/src/routes/RouterTransferPage.tsx @@ -38,7 +38,7 @@ import type { MultiAddressStruct } from "@snowbridge/contract-types/dist/IGatewa import { u8aToHex } from "@polkadot/util"; import { decodeAddress } from "@polkadot/keyring"; import { ApiPromise, WsProvider } from "@polkadot/api"; -import type { TSerializedApiCall } from "@paraspell/sdk"; +import { isForeignAsset, type TSerializedApiCall } from "@paraspell/sdk"; import { Web3 } from "web3"; import type { EIP6963ProviderDetail } from "../types"; @@ -173,12 +173,12 @@ const RouterTransferPage = () => { .to(to) .exchange(exchange) .currencyFrom( - currencyFrom.assetId + isForeignAsset(currencyFrom) ? { id: currencyFrom.assetId } : { symbol: currencyFrom.symbol ?? "" }, ) .currencyTo( - currencyTo.assetId + isForeignAsset(currencyTo) ? { id: currencyTo.assetId } : { symbol: currencyTo.symbol ?? "" }, ) @@ -208,10 +208,10 @@ const RouterTransferPage = () => { `${API_URL}/router`, { ...formValues, - currencyFrom: currencyFrom.assetId + currencyFrom: isForeignAsset(currencyFrom) ? { id: currencyFrom.assetId } : { symbol: currencyFrom.symbol ?? "" }, - currencyTo: currencyTo.assetId + currencyTo: isForeignAsset(currencyTo) ? { id: currencyTo.assetId } : { symbol: currencyTo.symbol ?? "" }, type: TransactionType[transactionType], diff --git a/codecov.yml b/codecov.yml index dc4be619..55603597 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,7 +2,7 @@ flag_management: default_rules: statuses: - type: project - target: auto + target: 90% threshold: 1% - type: patch target: 90% @@ -28,5 +28,17 @@ flag_management: - apps/visualizator-be carryforward: true +coverage: + precision: 2 + round: down + range: "90...100" + status: + project: + default: + target: 90% + patch: + default: + target: 90% + comment: show_carryforward_flags: true diff --git a/packages/sdk/e2e/xcm-papi.test.ts b/packages/sdk/e2e/xcm-papi.test.ts index 6ec24b32..f83d19ca 100644 --- a/packages/sdk/e2e/xcm-papi.test.ts +++ b/packages/sdk/e2e/xcm-papi.test.ts @@ -24,6 +24,7 @@ import { secp256k1 } from '@noble/curves/secp256k1' import { keccak_256 } from '@noble/hashes/sha3' import { mnemonicToSeedSync } from '@scure/bip39' import { HDKey } from '@scure/bip32' +import { isForeignAsset } from '../src/utils/assets' const MOCK_AMOUNT = 1000 const MOCK_ADDRESS = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty' @@ -63,7 +64,11 @@ const filteredNodes = NODE_NAMES_DOT_KSM.filter( node !== 'Turing' && node !== 'Pendulum' && node !== 'Polkadex' && - node !== 'Subsocial' + node !== 'Subsocial' && + // has no assets + node !== 'Quartz' && + node !== 'InvArchTinker' && + node !== 'Unique' ) const findTransferableNodeAndAsset = ( @@ -92,7 +97,7 @@ const findTransferableNodeAndAsset = ( return { nodeTo, asset: supportedAsset.symbol, - assetId: supportedAsset.assetId ?? null + assetId: isForeignAsset(supportedAsset) ? supportedAsset.assetId : null } } @@ -149,10 +154,6 @@ describe.sequential('XCM - e2e', () => { ) } - // const deriveEvm = ecdsaCreateDerive(miniSecret) - // const aliceKeyPairEvm = deriveEvm('//Alice//0') - // const evmSigner = getEvmEcdsaSigner(aliceKeyPairEvm.) - const seed = mnemonicToSeedSync(DEV_PHRASE) const hdkey = HDKey.fromMasterSeed(seed) const keyPair = hdkey.derive(`m/44'/60'/0'/0/0`) diff --git a/packages/sdk/e2e/xcm.test.ts b/packages/sdk/e2e/xcm.test.ts index 518e2b88..875b96df 100644 --- a/packages/sdk/e2e/xcm.test.ts +++ b/packages/sdk/e2e/xcm.test.ts @@ -16,6 +16,7 @@ import { getSupportedAssets } from '../src' import { type ApiPromise } from '@polkadot/api' +import { isForeignAsset } from '../src/utils/assets' const MOCK_AMOUNT = 1000 const MOCK_ADDRESS = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty' @@ -38,11 +39,15 @@ const getAssetsForNode = (node: TNode): string[] => { const filteredNodes = NODE_NAMES_DOT_KSM.filter( node => - node !== 'Quartz' && node !== 'Bitgreen' && node !== 'Bajun' && node !== 'CoretimeKusama' && - node !== 'Polkadex' + node !== 'Polkadex' && + // Has no assets + node !== 'Quartz' && + node !== 'Pendulum' && + node !== 'InvArchTinker' && + node !== 'Unique' ) const findTransferableNodeAndAsset = ( @@ -71,7 +76,7 @@ const findTransferableNodeAndAsset = ( return { nodeTo, asset: supportedAsset.symbol, - assetId: supportedAsset.assetId ?? null + assetId: isForeignAsset(supportedAsset) ? supportedAsset.assetId : null } } diff --git a/packages/sdk/package.json b/packages/sdk/package.json index b260dbe5..afc0882f 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -36,6 +36,7 @@ "updateAssets": "node --loader ts-node/esm --experimental-specifier-resolution=node ./scripts/assets/updateAssets.ts", "updatePallets": "node --loader ts-node/esm --experimental-specifier-resolution=node ./scripts/pallets/updatePallets.ts", "updateEds": "node --loader ts-node/esm --experimental-specifier-resolution=node ./scripts/eds/updateEds.ts", + "checkDuplicateAssets": "node --loader ts-node/esm --experimental-specifier-resolution=node ./scripts/assets/checkDuplicates.ts", "runAll": "pnpm compile && pnpm format:write && pnpm lint && pnpm test", "test:e2e": "vitest run --config ./vitest.config.e2e.ts --sequence.concurrent" }, diff --git a/packages/sdk/scripts/assets/addAliases.ts b/packages/sdk/scripts/assets/addAliases.ts new file mode 100644 index 00000000..5171cb0e --- /dev/null +++ b/packages/sdk/scripts/assets/addAliases.ts @@ -0,0 +1,76 @@ +import type { TAssetJsonMap, TNode } from '../../src/types' +import { isForeignAsset } from '../../src/utils/assets' + +function collectDuplicateSymbolsInChains(assetsMap: TAssetJsonMap): { + [node: string]: { [symbol: string]: string[] } +} { + const chainDuplicates: { [node: string]: { [symbol: string]: string[] } } = {} + + for (const node in assetsMap) { + const nodeData = assetsMap[node as TNode] + const symbolToAssetIds: { [symbol: string]: Set } = {} + + const allAssets = [...(nodeData.nativeAssets || []), ...(nodeData.otherAssets || [])] + for (const asset of allAssets) { + const symbol = asset.symbol + const assetId = isForeignAsset(asset) ? asset.assetId : '' + if (symbol && assetId) { + if (!symbolToAssetIds[symbol]) { + symbolToAssetIds[symbol] = new Set() + } + symbolToAssetIds[symbol].add(assetId) + } + } + + const duplicates: { [symbol: string]: string[] } = {} + for (const symbol in symbolToAssetIds) { + const assetIds = Array.from(symbolToAssetIds[symbol]) + if (assetIds.length > 1) { + duplicates[symbol] = assetIds + } + } + + if (Object.keys(duplicates).length > 0) { + chainDuplicates[node] = duplicates + } + } + + return chainDuplicates +} + +function assignAliasNumbers(assetIds: string[]): { [assetId: string]: number } { + const aliasMapping: { [assetId: string]: number } = {} + const sortedAssetIds = [...assetIds].sort() + + sortedAssetIds.forEach((assetId, index) => { + aliasMapping[assetId] = index + 1 + }) + + return aliasMapping +} + +export function addAliasesToDuplicateSymbols(assetsMap: TAssetJsonMap): TAssetJsonMap { + const chainDuplicates = collectDuplicateSymbolsInChains(assetsMap) + + for (const node in chainDuplicates) { + const duplicates = chainDuplicates[node] + for (const symbol in duplicates) { + const assetIds = duplicates[symbol] + const aliasNumbers = assignAliasNumbers(assetIds) + + const nodeData = assetsMap[node as TNode] + const allAssets = [...(nodeData.nativeAssets || []), ...(nodeData.otherAssets || [])] + + for (const asset of allAssets) { + if (asset.symbol === symbol && isForeignAsset(asset)) { + const aliasNumber = aliasNumbers[asset.assetId] + if (aliasNumber !== undefined) { + asset.alias = `${symbol}${aliasNumber}` + } + } + } + } + } + + return assetsMap +} diff --git a/packages/sdk/scripts/assets/checkDuplicates.ts b/packages/sdk/scripts/assets/checkDuplicates.ts new file mode 100644 index 00000000..0c83dd6f --- /dev/null +++ b/packages/sdk/scripts/assets/checkDuplicates.ts @@ -0,0 +1,39 @@ +import assetsMapJson from '../../src/maps/assets.json' assert { type: 'json' } +import type { TAssetJsonMap } from '../../src/types' + +const assetsMap = assetsMapJson as TAssetJsonMap + +const findDuplicates = () => { + Object.entries(assetsMap).forEach(([networkName, network]) => { + const assetSymbols = new Map>() + + const addSymbol = (symbol: string | undefined, type: string) => { + if (!symbol) return // Skip if symbol is undefined + if (!assetSymbols.has(symbol)) { + assetSymbols.set(symbol, { nativeAssets: 0, otherAssets: 0 }) + } + assetSymbols.get(symbol)![type] += 1 + } + + network.nativeAssets.forEach(asset => addSymbol(asset.symbol, 'nativeAssets')) + network.otherAssets.forEach(asset => addSymbol(asset.symbol, 'otherAssets')) + + const duplicates = Array.from(assetSymbols.entries()) + .filter( + ([_, types]) => + types.nativeAssets > 1 || + types.otherAssets > 1 || + (types.nativeAssets > 0 && types.otherAssets > 0) + ) + .map(([symbol, counts]) => ({ + symbol, + counts: `nativeAssets: ${counts.nativeAssets}, otherAssets: ${counts.otherAssets}` + })) + + if (duplicates.length > 0) { + console.log(`Duplicates in ${networkName}:`, duplicates) + } + }) +} + +findDuplicates() diff --git a/packages/sdk/scripts/assets/fetchAssets.ts b/packages/sdk/scripts/assets/fetchAssets.ts index 038bdae3..354b46eb 100644 --- a/packages/sdk/scripts/assets/fetchAssets.ts +++ b/packages/sdk/scripts/assets/fetchAssets.ts @@ -4,10 +4,10 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type { ApiPromise } from '@polkadot/api' import type { - TAssetDetails, + TForeignAsset, TAssetJsonMap, TMultiLocation, - TNativeAssetDetails, + TNativeAsset, TNode, TNodeAssets, TNodePolkadotKusama @@ -17,8 +17,9 @@ import { fetchTryMultipleProvidersWithTimeout } from '../scriptUtils' import { nodeToQuery } from './nodeToQueryMap' import { fetchBifrostAssets } from './fetchBifrostAssets' import { fetchEthereumAssets } from './fetchEthereumAssets' +import { addAliasesToDuplicateSymbols } from './addAliases' -const fetchNativeAssets = async (api: ApiPromise): Promise => { +const fetchNativeAssets = async (api: ApiPromise): Promise => { const propertiesRes = await api.rpc.system.properties() const json = propertiesRes.toHuman() const symbols = json.tokenSymbol as string[] @@ -218,7 +219,7 @@ const fetchOtherAssetsInnerType = async (api: ApiPromise, query: string) => { ) return matchingAsset ? { ...assetWithoutDecimals, decimals: matchingAsset.decimals } : null }) - .filter(asset => asset !== null) as unknown as TAssetDetails[] + .filter(asset => asset !== null) as unknown as TForeignAsset[] } const fetchAssetsType2 = async (api: ApiPromise, query: string): Promise> => { @@ -461,5 +462,6 @@ export const fetchAllNodesAssets = async (assetsMapJson: any) => { } } } - return output + + return addAliasesToDuplicateSymbols(output) } diff --git a/packages/sdk/scripts/assets/fetchBifrostAssets.ts b/packages/sdk/scripts/assets/fetchBifrostAssets.ts index 23ddbd10..0ed62554 100644 --- a/packages/sdk/scripts/assets/fetchBifrostAssets.ts +++ b/packages/sdk/scripts/assets/fetchBifrostAssets.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type { ApiPromise } from '@polkadot/api' -import type { TAssetDetails, TNativeAssetDetails } from '../../src/types' +import type { TForeignAsset, TNativeAsset } from '../../src/types' import type { StorageKey } from '@polkadot/types' import type { AnyTuple, Codec } from '@polkadot/types/types' @@ -10,8 +10,8 @@ export const fetchBifrostAssets = async ( api: ApiPromise, query: string ): Promise<{ - nativeAssets: TNativeAssetDetails[] - otherAssets: TAssetDetails[] + nativeAssets: TNativeAsset[] + otherAssets: TForeignAsset[] }> => { const [module, section] = query.split('.') const res = await api.query[module][section].entries() @@ -43,12 +43,12 @@ export const fetchBifrostAssets = async ( const nativeAssets = mapAssets( filterAssets(['token', 'vtoken', 'native']), true - ) as TNativeAssetDetails[] + ) as TNativeAsset[] const otherAssets = mapAssets( filterAssets(['token2', 'vtoken2', 'vstoken2']), false - ) as TAssetDetails[] + ) as TForeignAsset[] return { nativeAssets, diff --git a/packages/sdk/scripts/assets/fetchEthereumAssets.ts b/packages/sdk/scripts/assets/fetchEthereumAssets.ts index cfc2bbde..b4630219 100644 --- a/packages/sdk/scripts/assets/fetchEthereumAssets.ts +++ b/packages/sdk/scripts/assets/fetchEthereumAssets.ts @@ -1,4 +1,4 @@ -import type { TAssetDetails, TNodeAssets } from '../../src/types' +import type { TForeignAsset, TNodeAssets } from '../../src/types' import axios from 'axios' import { Project, SyntaxKind } from 'ts-morph' @@ -43,7 +43,7 @@ export const fetchEthereumAssets = async (): Promise => { token.getLastChildByKindOrThrow(SyntaxKind.PropertyAssignment).remove() }) - const assets: TAssetDetails[] = [] + const assets: TForeignAsset[] = [] tokenArray.forEachChild(token => { const item = token.getFirstChildByKindOrThrow(SyntaxKind.SyntaxList) diff --git a/packages/sdk/src/errors/DuplicateAssetError.ts b/packages/sdk/src/errors/DuplicateAssetError.ts index 96c4a1e5..e2427f8e 100644 --- a/packages/sdk/src/errors/DuplicateAssetError.ts +++ b/packages/sdk/src/errors/DuplicateAssetError.ts @@ -7,10 +7,8 @@ export class DuplicateAssetError extends Error { * * @param symbol - The symbol of the asset causing the duplication error. */ - constructor(symbol: string) { - super( - `Multiple assets found with the same symbol: ${symbol}. Please specify asset ID directly by .currency({id: })` - ) + constructor(msg: string) { + super(msg) this.name = 'DuplicateAsset' } } diff --git a/packages/sdk/src/maps/assets.json b/packages/sdk/src/maps/assets.json index 28d12231..7fa87912 100644 --- a/packages/sdk/src/maps/assets.json +++ b/packages/sdk/src/maps/assets.json @@ -160,7 +160,8 @@ { "assetId": "5", "symbol": "WBTC", - "decimals": 8 + "decimals": 8, + "alias": "WBTC2" }, { "assetId": "0xd1729649ee6d5e3740ee2f9254c4226aabd0dc5b", @@ -240,7 +241,8 @@ { "assetId": "0xc80084af223c8b598536178d9361dc55bfda6818", "symbol": "WBTC", - "decimals": 8 + "decimals": 8, + "alias": "WBTC1" }, { "assetId": "15", @@ -327,7 +329,8 @@ { "assetId": "1323", "symbol": "ALGM", - "decimals": 18 + "decimals": 18, + "alias": "ALGM1" }, { "assetId": "862812", @@ -337,7 +340,8 @@ { "assetId": "1328", "symbol": "ALGM", - "decimals": 18 + "decimals": 18, + "alias": "ALGM2" }, { "assetId": "18446744073709551633", @@ -347,7 +351,8 @@ { "assetId": "1333", "symbol": "ASTR", - "decimals": 20 + "decimals": 20, + "alias": "ASTR2" }, { "assetId": "18446744073709551625", @@ -382,7 +387,8 @@ { "assetId": "1338", "symbol": "TST", - "decimals": 18 + "decimals": 18, + "alias": "TST1" }, { "assetId": "18446744073709551635", @@ -427,7 +433,8 @@ { "assetId": "1339", "symbol": "TST", - "decimals": 18 + "decimals": 18, + "alias": "TST2" }, { "assetId": "18446744073709551637", @@ -497,7 +504,8 @@ { "assetId": "1324", "symbol": "ASTR", - "decimals": 20 + "decimals": 20, + "alias": "ASTR1" }, { "assetId": "18446744073709551628", @@ -527,12 +535,14 @@ { "assetId": "1331", "symbol": "sDOT", - "decimals": 10 + "decimals": 10, + "alias": "sDOT1" }, { "assetId": "1332", "symbol": "sDOT", - "decimals": 10 + "decimals": 10, + "alias": "sDOT2" }, { "assetId": "18446744073709551639", @@ -799,7 +809,8 @@ { "assetId": "79228162514264337593543950466", "symbol": "USDT", - "decimals": 6 + "decimals": 6, + "alias": "USDT1" }, { "assetId": "79228162514264337593543950370", @@ -889,7 +900,8 @@ { "assetId": "79228162514264337593543950485", "symbol": "USDT", - "decimals": 6 + "decimals": 6, + "alias": "USDT2" }, { "assetId": "79228162514264337593543950368", @@ -980,17 +992,20 @@ { "assetId": "10", "symbol": "USDT", - "decimals": 6 + "decimals": 6, + "alias": "USDT1" }, { "assetId": "4", "symbol": "WETH", - "decimals": 18 + "decimals": 18, + "alias": "WETH3" }, { "assetId": "21", "symbol": "USDC", - "decimals": 6 + "decimals": 6, + "alias": "USDC1" }, { "assetId": "28", @@ -1000,7 +1015,8 @@ { "assetId": "20", "symbol": "WETH", - "decimals": 18 + "decimals": 18, + "alias": "WETH2" }, { "assetId": "30", @@ -1010,7 +1026,8 @@ { "assetId": "101", "symbol": "2-Pool", - "decimals": 18 + "decimals": 18, + "alias": "2-Pool1" }, { "assetId": "16", @@ -1045,7 +1062,8 @@ { "assetId": "19", "symbol": "WBTC", - "decimals": 8 + "decimals": 8, + "alias": "WBTC2" }, { "assetId": "31", @@ -1065,7 +1083,8 @@ { "assetId": "2", "symbol": "DAI", - "decimals": 18 + "decimals": 18, + "alias": "DAI2" }, { "assetId": "13", @@ -1090,7 +1109,8 @@ { "assetId": "102", "symbol": "2-Pool", - "decimals": 18 + "decimals": 18, + "alias": "2-Pool2" }, { "assetId": "5", @@ -1100,12 +1120,14 @@ { "assetId": "18", "symbol": "DAI", - "decimals": 18 + "decimals": 18, + "alias": "DAI1" }, { "assetId": "7", "symbol": "USDC", - "decimals": 6 + "decimals": 6, + "alias": "USDC3" }, { "assetId": "26", @@ -1115,12 +1137,14 @@ { "assetId": "1000190", "symbol": "WBTC", - "decimals": 8 + "decimals": 8, + "alias": "WBTC1" }, { "assetId": "22", "symbol": "USDC", - "decimals": 6 + "decimals": 6, + "alias": "USDC2" }, { "assetId": "24", @@ -1155,7 +1179,8 @@ { "assetId": "3", "symbol": "WBTC", - "decimals": 8 + "decimals": 8, + "alias": "WBTC3" }, { "assetId": "17", @@ -1170,7 +1195,8 @@ { "assetId": "23", "symbol": "USDT", - "decimals": 6 + "decimals": 6, + "alias": "USDT2" }, { "assetId": "9", @@ -1180,7 +1206,8 @@ { "assetId": "1000189", "symbol": "WETH", - "decimals": 18 + "decimals": 18, + "alias": "WETH1" } ] }, @@ -1905,7 +1932,8 @@ { "assetId": "22222028", "symbol": "COKE", - "decimals": 12 + "decimals": 12, + "alias": "COKE2" }, { "assetId": "22222079", @@ -1915,7 +1943,8 @@ { "assetId": "20090110", "symbol": "test1", - "decimals": 10 + "decimals": 10, + "alias": "test11" }, { "assetId": "22222024", @@ -1950,7 +1979,8 @@ { "assetId": "108", "symbol": "GAME", - "decimals": 10 + "decimals": 10, + "alias": "GAME1" }, { "assetId": "1111", @@ -1975,7 +2005,8 @@ { "assetId": "300", "symbol": "DOGE", - "decimals": 6 + "decimals": 6, + "alias": "DOGE2" }, { "assetId": "22222046", @@ -2065,7 +2096,8 @@ { "assetId": "30", "symbol": "DED", - "decimals": 10 + "decimals": 10, + "alias": "DED1" }, { "assetId": "50000010", @@ -2075,12 +2107,14 @@ { "assetId": "39", "symbol": "PEPE", - "decimals": 6 + "decimals": 6, + "alias": "PEPE1" }, { "assetId": "10101", "symbol": "COKE", - "decimals": 10 + "decimals": 10, + "alias": "COKE1" }, { "assetId": "101", @@ -2100,7 +2134,8 @@ { "assetId": "46", "symbol": "PEPE", - "decimals": 10 + "decimals": 10, + "alias": "PEPE3" }, { "assetId": "20090133", @@ -2180,7 +2215,8 @@ { "assetId": "30035", "symbol": "DED", - "decimals": 18 + "decimals": 18, + "alias": "DED2" }, { "assetId": "22222062", @@ -2225,12 +2261,14 @@ { "assetId": "66", "symbol": "DOGE", - "decimals": 6 + "decimals": 6, + "alias": "DOGE4" }, { "assetId": "555", "symbol": "GAME", - "decimals": 10 + "decimals": 10, + "alias": "GAME2" }, { "assetId": "2828", @@ -2345,7 +2383,8 @@ { "assetId": "35", "symbol": "DOGE", - "decimals": 10 + "decimals": 10, + "alias": "DOGE3" }, { "assetId": "22222029", @@ -2360,7 +2399,8 @@ { "assetId": "36", "symbol": "CLAY", - "decimals": 10 + "decimals": 10, + "alias": "CLAY2" }, { "assetId": "420666", @@ -2380,7 +2420,8 @@ { "assetId": "31", "symbol": "WOOD", - "decimals": 6 + "decimals": 6, + "alias": "WOOD2" }, { "assetId": "33", @@ -2390,7 +2431,8 @@ { "assetId": "20090115", "symbol": "DOTCOIN", - "decimals": 10 + "decimals": 10, + "alias": "DOTCOIN1" }, { "assetId": "301", @@ -2405,7 +2447,8 @@ { "assetId": "2020", "symbol": "HYDRA", - "decimals": 10 + "decimals": 10, + "alias": "HYDRA1" }, { "assetId": "41", @@ -2480,7 +2523,8 @@ { "assetId": "40", "symbol": "PEPE", - "decimals": 10 + "decimals": 10, + "alias": "PEPE2" }, { "assetId": "20090144", @@ -2490,7 +2534,8 @@ { "assetId": "2", "symbol": "BTC", - "decimals": 20 + "decimals": 20, + "alias": "BTC1" }, { "assetId": "789", @@ -2565,7 +2610,8 @@ { "assetId": "32", "symbol": "CLAY", - "decimals": 10 + "decimals": 10, + "alias": "CLAY1" }, { "assetId": "22222035", @@ -2670,7 +2716,8 @@ { "assetId": "42", "symbol": "GAVUN", - "decimals": 10 + "decimals": 10, + "alias": "GAVUN1" }, { "assetId": "22222059", @@ -2690,7 +2737,8 @@ { "assetId": "2001", "symbol": "DOTA", - "decimals": 6 + "decimals": 6, + "alias": "DOTA2" }, { "assetId": "22222003", @@ -2710,7 +2758,8 @@ { "assetId": "18", "symbol": "DOTA", - "decimals": 4 + "decimals": 4, + "alias": "DOTA1" }, { "assetId": "83", @@ -2730,7 +2779,8 @@ { "assetId": "4242", "symbol": "GAVUN", - "decimals": 10 + "decimals": 10, + "alias": "GAVUN2" }, { "assetId": "26", @@ -2740,7 +2790,8 @@ { "assetId": "1010", "symbol": "ETH", - "decimals": 10 + "decimals": 10, + "alias": "ETH1" }, { "assetId": "22222072", @@ -2750,7 +2801,8 @@ { "assetId": "201", "symbol": "WUD", - "decimals": 6 + "decimals": 6, + "alias": "WUD1" }, { "assetId": "1984", @@ -2770,7 +2822,8 @@ { "assetId": "20090103", "symbol": "BTC", - "decimals": 20 + "decimals": 20, + "alias": "BTC2" }, { "assetId": "20090118", @@ -2790,7 +2843,8 @@ { "assetId": "20090124", "symbol": "test1", - "decimals": 10 + "decimals": 10, + "alias": "test13" }, { "assetId": "690", @@ -2845,7 +2899,8 @@ { "assetId": "20090113", "symbol": "test1", - "decimals": 10 + "decimals": 10, + "alias": "test12" }, { "assetId": "22222076", @@ -2855,7 +2910,8 @@ { "assetId": "567", "symbol": "ANT", - "decimals": 6 + "decimals": 6, + "alias": "ANT1" }, { "assetId": "457", @@ -2930,7 +2986,8 @@ { "assetId": "20090123", "symbol": "GOVD", - "decimals": 10 + "decimals": 10, + "alias": "GOVD1" }, { "assetId": "2023", @@ -2955,7 +3012,8 @@ { "assetId": "80", "symbol": "GOVD", - "decimals": 10 + "decimals": 10, + "alias": "GOVD2" }, { "assetId": "20090139", @@ -2980,7 +3038,8 @@ { "assetId": "45", "symbol": "HYDRA", - "decimals": 10 + "decimals": 10, + "alias": "HYDRA2" }, { "assetId": "22222080", @@ -2995,7 +3054,8 @@ { "assetId": "10000", "symbol": "DOGE", - "decimals": 6 + "decimals": 6, + "alias": "DOGE1" }, { "assetId": "8277", @@ -3055,7 +3115,8 @@ { "assetId": "20090134", "symbol": "DOTCOIN", - "decimals": 10 + "decimals": 10, + "alias": "DOTCOIN2" }, { "assetId": "1000", @@ -3095,7 +3156,8 @@ { "assetId": "60", "symbol": "ANT", - "decimals": 6 + "decimals": 6, + "alias": "ANT2" }, { "assetId": "20090126", @@ -3155,7 +3217,8 @@ { "assetId": "31337", "symbol": "WUD", - "decimals": 10 + "decimals": 10, + "alias": "WUD2" }, { "assetId": "97", @@ -3305,7 +3368,8 @@ { "assetId": "20090105", "symbol": "ETH", - "decimals": 10 + "decimals": 10, + "alias": "ETH2" }, { "assetId": "37", @@ -3365,7 +3429,8 @@ { "assetId": "20090107", "symbol": "ETH", - "decimals": 10 + "decimals": 10, + "alias": "ETH3" }, { "assetId": "22222061", @@ -3430,7 +3495,8 @@ { "assetId": "2024", "symbol": "WOOD", - "decimals": 10 + "decimals": 10, + "alias": "WOOD1" }, { "assetId": "22222060", @@ -5605,7 +5671,8 @@ { "assetId": "9999", "symbol": "BTC", - "decimals": 20 + "decimals": 20, + "alias": "BTC2" }, { "assetId": "100", @@ -5650,7 +5717,8 @@ { "assetId": "69420", "symbol": "CHAOS", - "decimals": 10 + "decimals": 10, + "alias": "CHAOS1" }, { "assetId": "47", @@ -5685,7 +5753,8 @@ { "assetId": "120", "symbol": "OAT", - "decimals": 4 + "decimals": 4, + "alias": "OAT2" }, { "assetId": "1111", @@ -5825,7 +5894,8 @@ { "assetId": "11", "symbol": "USDT", - "decimals": 4 + "decimals": 4, + "alias": "USDT1" }, { "assetId": "224", @@ -6005,7 +6075,8 @@ { "assetId": "1121", "symbol": "OAT", - "decimals": 12 + "decimals": 12, + "alias": "OAT1" }, { "assetId": "117", @@ -6150,7 +6221,8 @@ { "assetId": "1984", "symbol": "USDt", - "decimals": 6 + "decimals": 6, + "alias": "USDt1" }, { "assetId": "2050", @@ -6180,7 +6252,8 @@ { "assetId": "777", "symbol": "GOD", - "decimals": 0 + "decimals": 0, + "alias": "GOD2" }, { "assetId": "567", @@ -6260,7 +6333,8 @@ { "assetId": "6967", "symbol": "CHAOS", - "decimals": 10 + "decimals": 10, + "alias": "CHAOS2" }, { "assetId": "51", @@ -6330,7 +6404,8 @@ { "assetId": "2000", "symbol": "USDT", - "decimals": 10 + "decimals": 10, + "alias": "USDT2" }, { "assetId": "3", @@ -6360,7 +6435,8 @@ { "assetId": "88", "symbol": "BTC", - "decimals": 20 + "decimals": 20, + "alias": "BTC1" }, { "assetId": "17", @@ -6400,7 +6476,8 @@ { "assetId": "19840", "symbol": "USDt", - "decimals": 3 + "decimals": 3, + "alias": "USDt2" }, { "assetId": "37", @@ -6430,7 +6507,8 @@ { "assetId": "1225", "symbol": "GOD", - "decimals": 20 + "decimals": 20, + "alias": "GOD1" }, { "assetId": "64", @@ -6976,7 +7054,8 @@ { "assetId": "1", "symbol": "USDC", - "decimals": 6 + "decimals": 6, + "alias": "USDC1" }, { "assetId": "11", @@ -6986,7 +7065,8 @@ { "assetId": "4", "symbol": "USDC", - "decimals": 6 + "decimals": 6, + "alias": "USDC2" }, { "assetId": "9", @@ -7260,7 +7340,8 @@ { "assetId": "5", "symbol": "BSX", - "decimals": 12 + "decimals": 12, + "alias": "BSX1" }, { "assetId": "7", @@ -7295,7 +7376,8 @@ { "assetId": "9", "symbol": "BSX", - "decimals": 12 + "decimals": 12, + "alias": "BSX2" } ] }, diff --git a/packages/sdk/src/nodes/ParachainNode.test.ts b/packages/sdk/src/nodes/ParachainNode.test.ts index 315b8069..7584eb87 100644 --- a/packages/sdk/src/nodes/ParachainNode.test.ts +++ b/packages/sdk/src/nodes/ParachainNode.test.ts @@ -115,8 +115,8 @@ describe('ParachainNode', () => { it('should return true for canUseXTokens when using exposeCanUseXTokens', () => { const options = { - api: {}, - currencySymbol: 'DOT', + api: {} as IPolkadotApi, + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress' } as TSendInternalOptions @@ -126,7 +126,7 @@ describe('ParachainNode', () => { it('should call transferXTokens when supportsXTokens and canUseXTokens return true', async () => { const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress' } as TSendInternalOptions @@ -143,7 +143,7 @@ describe('ParachainNode', () => { const node = new NoXTokensNode('Acala', 'TestNode', 'polkadot', Version.V3) const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress' } as TSendInternalOptions @@ -160,7 +160,7 @@ describe('ParachainNode', () => { const node = new OnlyPolkadotXCMNode('Acala', 'TestNode', 'polkadot', Version.V3) const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress' } as TSendInternalOptions @@ -177,7 +177,7 @@ describe('ParachainNode', () => { const node = new NoSupportNode('Acala', 'TestNode', 'polkadot', Version.V3) const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress' } as TSendInternalOptions @@ -197,7 +197,7 @@ describe('ParachainNode', () => { const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress' } as TSendInternalOptions @@ -215,7 +215,7 @@ describe('ParachainNode', () => { it('should throw error when destination is Polimec and node is not AssetHubPolkadot', async () => { const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress', destination: 'Polimec' @@ -232,7 +232,7 @@ describe('ParachainNode', () => { const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress', destination: 'Polimec' @@ -250,7 +250,7 @@ describe('ParachainNode', () => { const node = new OnlyPolkadotXCMNode('Acala', 'TestNode', 'polkadot', Version.V3) const options = { api: {}, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100', address: 'destinationAddress', overridedCurrencyMultiLocation: {} @@ -288,7 +288,7 @@ describe('ParachainNode', () => { }) it('should create currency spec', () => { - const result = node.createCurrencySpec('100', 'ParaToRelay', Version.V3, 'currencyId') + const result = node.createCurrencySpec('100', 'ParaToRelay', Version.V3, { symbol: 'DOT' }) expect(result).toBe('currencySpec') }) diff --git a/packages/sdk/src/nodes/ParachainNode.ts b/packages/sdk/src/nodes/ParachainNode.ts index 6ca9941c..97d0a087 100644 --- a/packages/sdk/src/nodes/ParachainNode.ts +++ b/packages/sdk/src/nodes/ParachainNode.ts @@ -18,7 +18,8 @@ import type { TMultiAsset, TMultiLocation, TMultiLocationHeader, - TSerializedApiCallV2 + TSerializedApiCallV2, + TAsset } from '../types' import { Version, Parents } from '../types' import { getAllNodeProviders, generateAddressPayload, getFees, verifyMultiLocation } from '../utils' @@ -92,8 +93,7 @@ abstract class ParachainNode { async transfer(options: TSendInternalOptions): Promise> { const { api, - currencySymbol, - currencyId, + asset, amount, address, destination, @@ -119,8 +119,7 @@ abstract class ParachainNode { if (supportsXTokens(this) && this.canUseXTokens(options)) { return this.transferXTokens({ api, - currency: currencySymbol, - currencyID: currencyId, + asset, amount, addressSelection: generateAddressPayload( api, @@ -142,8 +141,7 @@ abstract class ParachainNode { } else if (supportsXTransfer(this)) { return this.transferXTransfer({ api, - currency: currencySymbol, - currencyID: currencyId, + asset, amount, recipientAddress: address, paraId, @@ -176,12 +174,11 @@ abstract class ParachainNode { amount, scenario, versionOrDefault, - currencyId, + asset, overridedCurrencyMultiLocation ), - currencyId, + asset, scenario, - currencySymbol, feeAsset, destination, paraIdTo: paraId, @@ -216,7 +213,7 @@ abstract class ParachainNode { amount: string, scenario: TScenario, version: Version, - _currencyId?: string, + _asset?: TAsset, overridedMultiLocation?: TMultiLocation | TMultiAsset[] ): TCurrencySelectionHeaderArr { return createCurrencySpec( diff --git a/packages/sdk/src/nodes/supported/Acala.test.ts b/packages/sdk/src/nodes/supported/Acala.test.ts index e0629146..5c2e3017 100644 --- a/packages/sdk/src/nodes/supported/Acala.test.ts +++ b/packages/sdk/src/nodes/supported/Acala.test.ts @@ -21,7 +21,7 @@ vi.mock('../../utils/getAllNodeProviders', () => ({ describe('Acala', () => { let acala: Acala const mockInput = { - currency: 'ACA', + asset: { symbol: 'ACA' }, amount: '100' } as XTokensTransferInput const spyTransferXTokens = vi.spyOn(XTokensTransferImpl, 'transferXTokens') @@ -45,7 +45,13 @@ describe('Acala', () => { }) it('should call transferXTokens with ForeignAsset when currencyID is defined', () => { - const inputWithCurrencyID = { ...mockInput, currencyID: '1' } + const inputWithCurrencyID = { + ...mockInput, + asset: { + symbol: 'ACA', + assetId: 1 + } + } acala.transferXTokens(inputWithCurrencyID) diff --git a/packages/sdk/src/nodes/supported/Acala.ts b/packages/sdk/src/nodes/supported/Acala.ts index a885142f..cb1d3f48 100644 --- a/packages/sdk/src/nodes/supported/Acala.ts +++ b/packages/sdk/src/nodes/supported/Acala.ts @@ -8,6 +8,7 @@ import { type XTokensTransferInput } from '../../types' import { getAllNodeProviders } from '../../utils' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -17,9 +18,10 @@ class Acala extends ParachainNode implements IXTokensTra } transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection: TForeignOrTokenAsset = - currencyID !== undefined ? { ForeignAsset: Number(currencyID) } : { Token: currency } + const { asset } = input + const currencySelection: TForeignOrTokenAsset = isForeignAsset(asset) + ? { ForeignAsset: Number(asset.assetId) } + : { Token: asset.symbol } return XTokensTransferImpl.transferXTokens(input, currencySelection) } diff --git a/packages/sdk/src/nodes/supported/Altair.test.ts b/packages/sdk/src/nodes/supported/Altair.test.ts index 725fd438..dd0d3168 100644 --- a/packages/sdk/src/nodes/supported/Altair.test.ts +++ b/packages/sdk/src/nodes/supported/Altair.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Altair', () => { let altair: Altair const mockInput = { - currency: 'AIR', + asset: { symbol: 'AIR', assetId: '1' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Altair.ts b/packages/sdk/src/nodes/supported/Altair.ts index e3881460..1a626cc0 100644 --- a/packages/sdk/src/nodes/supported/Altair.ts +++ b/packages/sdk/src/nodes/supported/Altair.ts @@ -1,7 +1,9 @@ // Contains detailed structure of XCM call construction for Altair Parachain -import type { TForeignOrNativeAsset } from '../../types' +import { InvalidCurrencyError } from '../../errors' +import type { TAsset, TForeignOrNativeAsset } from '../../types' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,10 +12,19 @@ class Altair extends ParachainNode implements IXTokensTr super('Altair', 'altair', 'kusama', Version.V3) } + private getCurrencySelection(asset: TAsset): TForeignOrNativeAsset { + if (asset.symbol === this.getNativeAssetSymbol()) return 'Native' + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { ForeignAsset: Number(asset.assetId) } + } + transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection: TForeignOrNativeAsset = - currency === this.getNativeAssetSymbol() ? 'Native' : { ForeignAsset: Number(currencyID) } + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/Amplitude.test.ts b/packages/sdk/src/nodes/supported/Amplitude.test.ts index 7de296cd..ff1dc330 100644 --- a/packages/sdk/src/nodes/supported/Amplitude.test.ts +++ b/packages/sdk/src/nodes/supported/Amplitude.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Amplitude', () => { let amplitude: Amplitude const mockInput = { - currencyID: '123', + asset: { symbol: 'AMPE', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('Amplitude', () => { amplitude.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { XCM: '123' }) + expect(spy).toHaveBeenCalledWith(mockInput, { XCM: 123 }) }) }) diff --git a/packages/sdk/src/nodes/supported/Amplitude.ts b/packages/sdk/src/nodes/supported/Amplitude.ts index eafbf455..c2c3917b 100644 --- a/packages/sdk/src/nodes/supported/Amplitude.ts +++ b/packages/sdk/src/nodes/supported/Amplitude.ts @@ -1,11 +1,13 @@ // Contains detailed structure of XCM call construction for Amplitude Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TXcmAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,8 +17,13 @@ class Amplitude extends ParachainNode implements IXToken } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - const currencySelection: TXcmAsset = { XCM: currencyID } + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + const currencySelection: TXcmAsset = { XCM: Number(asset.assetId) } return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/AssetHubKusama.test.ts b/packages/sdk/src/nodes/supported/AssetHubKusama.test.ts index fd49cb66..b55789e8 100644 --- a/packages/sdk/src/nodes/supported/AssetHubKusama.test.ts +++ b/packages/sdk/src/nodes/supported/AssetHubKusama.test.ts @@ -9,8 +9,9 @@ describe('transferPolkadotXCM', () => { it('throws ScenarioNotSupportedError for native KSM transfers in para to para scenarios', () => { const assetHub = getNode('AssetHubKusama') const input = { - currencySymbol: 'KSM', - currencyId: undefined, + asset: { + symbol: 'KSM' + }, scenario: 'ParaToPara', destination: 'Karura' } as PolkadotXCMTransferInput @@ -21,8 +22,9 @@ describe('transferPolkadotXCM', () => { it('throws ScenarioNotSupportedError for native DOT transfers in para to para scenarios', () => { const assetHub = getNode('AssetHubKusama') const input = { - currencySymbol: 'DOT', - currencyId: undefined, + asset: { + symbol: 'DOT' + }, scenario: 'ParaToPara', destination: 'Karura' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/AssetHubKusama.ts b/packages/sdk/src/nodes/supported/AssetHubKusama.ts index 7209c2c6..45fe8cf8 100644 --- a/packages/sdk/src/nodes/supported/AssetHubKusama.ts +++ b/packages/sdk/src/nodes/supported/AssetHubKusama.ts @@ -2,7 +2,7 @@ import { ScenarioNotSupportedError } from '../../errors' import { constructRelayToParaParameters } from '../../pallets/xcmPallet/utils' -import type { TTransferReturn } from '../../types' +import type { TAsset, TTransferReturn } from '../../types' import { type IPolkadotXCMTransfer, type PolkadotXCMTransferInput, @@ -14,6 +14,7 @@ import { type TMultiLocation } from '../../types' import { getNode } from '../../utils' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import PolkadotXCMTransferImpl from '../polkadotXcm' @@ -25,7 +26,7 @@ class AssetHubKusama extends ParachainNode implements IP transferPolkadotXCM( input: PolkadotXCMTransferInput ): Promise> { - const { destination, currencySymbol, currencyId, scenario } = input + const { destination, asset, scenario } = input // TESTED https://kusama.subscan.io/xcm_message/kusama-ddc2a48f0d8e0337832d7aae26f6c3053e1f4ffd // TESTED https://kusama.subscan.io/xcm_message/kusama-8e423130a4d8b61679af95dbea18a55124f99672 @@ -33,7 +34,7 @@ class AssetHubKusama extends ParachainNode implements IP return Promise.resolve(getNode('AssetHubPolkadot').handleBridgeTransfer(input, 'Polkadot')) } - if (scenario === 'ParaToPara' && currencySymbol === 'KSM' && currencyId === undefined) { + if (scenario === 'ParaToPara' && asset.symbol === 'KSM' && !isForeignAsset(asset)) { throw new ScenarioNotSupportedError( this.node, scenario, @@ -41,7 +42,7 @@ class AssetHubKusama extends ParachainNode implements IP ) } - if (scenario === 'ParaToPara' && currencySymbol === 'DOT' && currencyId === undefined) { + if (scenario === 'ParaToPara' && asset.symbol === 'DOT' && !isForeignAsset(asset)) { throw new ScenarioNotSupportedError( this.node, scenario, @@ -67,14 +68,14 @@ class AssetHubKusama extends ParachainNode implements IP amount: string, scenario: TScenario, version: Version, - currencyId?: string, + asset?: TAsset, overridedMultiLocation?: TMultiLocation | TMultiAsset[] ) { return getNode('AssetHubPolkadot').createCurrencySpec( amount, scenario, version, - currencyId, + asset, overridedMultiLocation ) } diff --git a/packages/sdk/src/nodes/supported/AssetHubPolkadot.test.ts b/packages/sdk/src/nodes/supported/AssetHubPolkadot.test.ts index ac2a1cba..04225515 100644 --- a/packages/sdk/src/nodes/supported/AssetHubPolkadot.test.ts +++ b/packages/sdk/src/nodes/supported/AssetHubPolkadot.test.ts @@ -55,7 +55,7 @@ describe('AssetHubPolkadot', () => { const mockInput = { api: mockApi, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, currencySelection: {}, currencyId: '0', scenario: 'ParaToRelay', @@ -89,7 +89,7 @@ describe('AssetHubPolkadot', () => { .spyOn(PolkadotXCMTransferImpl, 'transferPolkadotXCM') .mockReturnValue(mockResult) - const input = { ...mockInput, currencySymbol: 'DOT' } as PolkadotXCMTransferInput< + const input = { ...mockInput, asset: { symbol: 'DOT' } } as PolkadotXCMTransferInput< ApiPromise, Extrinsic > @@ -100,7 +100,7 @@ describe('AssetHubPolkadot', () => { }) it('throws InvalidCurrencyError for unsupported currency', () => { - const input = { ...mockInput, currencySymbol: 'UNKNOWN' } + const input = { ...mockInput, asset: { symbol: 'UNKNOWN' } } expect(() => assetHub.handleBridgeTransfer(input, 'Polkadot')).toThrow(InvalidCurrencyError) }) }) @@ -133,7 +133,7 @@ describe('AssetHubPolkadot', () => { const input = { ...mockInput, - currencySymbol: 'ETH', + asset: { symbol: 'ETH' }, destination: 'Ethereum' } as PolkadotXCMTransferInput const result = assetHub.handleEthBridgeTransfer(input) @@ -167,7 +167,7 @@ describe('AssetHubPolkadot', () => { it('throws ScenarioNotSupportedError for native DOT transfers in para to para scenarios', () => { const input = { ...mockInput, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, currencyId: undefined, scenario: 'ParaToPara', destination: 'Acala' @@ -179,7 +179,7 @@ describe('AssetHubPolkadot', () => { it('throws ScenarioNotSupportedError for native KSM transfers in para to para scenarios', () => { const input = { ...mockInput, - currencySymbol: 'KSM', + asset: { symbol: 'KSM' }, currencyId: undefined, scenario: 'ParaToPara', destination: 'Acala' @@ -241,9 +241,12 @@ describe('AssetHubPolkadot', () => { it('should call transferPolkadotXCM when destination is BifrostPolkadot and currency WETH.e', async () => { mockInput.destination = 'BifrostPolkadot' - mockInput.currencySymbol = 'WETH' + mockInput.asset = { + symbol: 'WETH', + assetId: '0x123' + } - vi.mocked(getOtherAssets).mockReturnValue([{ symbol: 'WETH', assetId: '' }]) + vi.mocked(getOtherAssets).mockReturnValue([{ symbol: 'WETH', assetId: '0x123' }]) vi.mocked(generateAddressPayload).mockReturnValue({ [Version.V3]: {} } as unknown as TMultiLocationHeader) @@ -256,7 +259,9 @@ describe('AssetHubPolkadot', () => { }) it('should modify input for USDT currencySymbol', async () => { - mockInput.currencySymbol = 'USDT' + mockInput.asset = { + symbol: 'USDT' + } mockInput.scenario = 'ParaToPara' mockInput.destination = 'BifrostPolkadot' @@ -271,7 +276,10 @@ describe('AssetHubPolkadot', () => { }) it('should modify input for USDC currencyId', async () => { - mockInput.currencySymbol = 'USDC' + mockInput.asset = { + symbol: 'USDC', + assetId: '1' + } mockInput.scenario = 'ParaToPara' mockInput.destination = 'BifrostPolkadot' @@ -287,8 +295,9 @@ describe('AssetHubPolkadot', () => { it('should modify input for DOT transfer to Hydration', async () => { mockInput.destination = 'Hydration' - mockInput.currencySymbol = 'DOT' - mockInput.currencyId = undefined + mockInput.asset = { + symbol: 'DOT' + } vi.mocked(getOtherAssets).mockImplementation(node => node === 'Ethereum' ? [] : [{ symbol: 'DOT', assetId: '' }] diff --git a/packages/sdk/src/nodes/supported/AssetHubPolkadot.ts b/packages/sdk/src/nodes/supported/AssetHubPolkadot.ts index bc3e92ab..7a7c2111 100644 --- a/packages/sdk/src/nodes/supported/AssetHubPolkadot.ts +++ b/packages/sdk/src/nodes/supported/AssetHubPolkadot.ts @@ -9,7 +9,7 @@ import { createCurrencySpec, createPolkadotXcmHeader } from '../../pallets/xcmPallet/utils' -import type { Junctions, TSerializedApiCallV2, TTransferReturn } from '../../types' +import type { Junctions, TAsset, TSerializedApiCallV2, TTransferReturn } from '../../types' import { type IPolkadotXCMTransfer, type PolkadotXCMTransferInput, @@ -27,6 +27,7 @@ import { generateAddressMultiLocationV4 } from '../../utils/generateAddressMulti import { generateAddressPayload } from '../../utils/generateAddressPayload' import { ETHEREUM_JUNCTION } from '../../const' import { createEthereumTokenLocation } from '../../utils/multiLocation/createEthereumTokenLocation' +import { isForeignAsset } from '../../utils/assets' const createCustomXcmToBifrost = ( { api, address, scenario }: PolkadotXCMTransferInput, @@ -57,8 +58,8 @@ class AssetHubPolkadot targetChain: 'Polkadot' | 'Kusama' ) { if ( - (targetChain === 'Kusama' && input.currencySymbol?.toUpperCase() === 'KSM') || - (targetChain === 'Polkadot' && input.currencySymbol?.toUpperCase() === 'DOT') + (targetChain === 'Kusama' && input.asset.symbol?.toUpperCase() === 'KSM') || + (targetChain === 'Polkadot' && input.asset.symbol?.toUpperCase() === 'DOT') ) { const modifiedInput: PolkadotXCMTransferInput = { ...input, @@ -77,8 +78,8 @@ class AssetHubPolkadot 'Unlimited' ) } else if ( - (targetChain === 'Polkadot' && input.currencySymbol?.toUpperCase() === 'KSM') || - (targetChain === 'Kusama' && input.currencySymbol?.toUpperCase() === 'DOT') + (targetChain === 'Polkadot' && input.asset.symbol?.toUpperCase() === 'KSM') || + (targetChain === 'Kusama' && input.asset.symbol?.toUpperCase() === 'DOT') ) { const modifiedInput: PolkadotXCMTransferInput = { ...input, @@ -102,23 +103,23 @@ class AssetHubPolkadot ) } throw new InvalidCurrencyError( - `Polkadot <-> Kusama bridge does not support currency ${input.currencySymbol}` + `Polkadot <-> Kusama bridge does not support currency ${input.asset.symbol}` ) } public handleEthBridgeTransfer(input: PolkadotXCMTransferInput) { - const { api, scenario, destination, paraIdTo, address, currencySymbol } = input + const { api, scenario, destination, paraIdTo, address, asset } = input if (!ethers.isAddress(address)) { throw new Error('Only Ethereum addresses are supported for Ethereum transfers') } const ethAssets = getOtherAssets('Ethereum') - const ethAsset = ethAssets.find(asset => asset.symbol === currencySymbol) + const ethAsset = ethAssets.find(asset => asset.symbol === asset.symbol) if (!ethAsset) { throw new InvalidCurrencyError( - `Currency ${currencySymbol} is not supported for Ethereum transfers` + `Currency ${asset.symbol} is not supported for Ethereum transfers` ) } @@ -160,7 +161,7 @@ class AssetHubPolkadot } handleMythosTransfer(input: PolkadotXCMTransferInput) { - const { api, address, amount, currencyId, overridedCurrency, scenario, destination, paraIdTo } = + const { api, address, amount, asset, overridedCurrency, scenario, destination, paraIdTo } = input const version = Version.V2 const paraId = @@ -190,7 +191,7 @@ class AssetHubPolkadot amount, scenario, version, - currencyId, + asset, overridedCurrency ?? customMultiLocation ) } @@ -204,10 +205,16 @@ class AssetHubPolkadot handleBifrostEthTransfer = ( input: PolkadotXCMTransferInput ): TTransferReturn => { - const { api, amount, scenario, version, destination, currencyId } = input + const { api, amount, scenario, version, destination, asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } const versionOrDefault = version ?? this.version + const ethereumTokenLocation = createEthereumTokenLocation(asset.assetId) + const call: TSerializedApiCallV2 = { module: 'PolkadotXcm', section: 'transfer_assets_using_type_and_then', @@ -221,19 +228,14 @@ class AssetHubPolkadot assets: { [versionOrDefault]: [ Object.values( - createCurrencySpec( - amount, - versionOrDefault, - Parents.TWO, - createEthereumTokenLocation(currencyId ?? '') - ) + createCurrencySpec(amount, versionOrDefault, Parents.TWO, ethereumTokenLocation) )[0][0] ] }, assets_transfer_type: 'LocalReserve', remote_fees_id: { [versionOrDefault]: { - Concrete: createEthereumTokenLocation(currencyId ?? '') + Concrete: ethereumTokenLocation } }, fees_transfer_type: 'LocalReserve', @@ -249,8 +251,7 @@ class AssetHubPolkadot input: PolkadotXCMTransferInput ): PolkadotXCMTransferInput { const { - currencySymbol, - currencyId, + asset, destination, paraIdTo, amount, @@ -262,7 +263,7 @@ class AssetHubPolkadot } = input if ( - (currencySymbol?.toUpperCase() === 'USDT' || currencySymbol?.toUpperCase() === 'USDC') && + (asset.symbol?.toUpperCase() === 'USDT' || asset.symbol?.toUpperCase() === 'USDC') && destination === 'BifrostPolkadot' ) { const versionOrDefault = input.version ?? Version.V2 @@ -281,7 +282,7 @@ class AssetHubPolkadot amount, scenario, versionOrDefault, - currencyId, + asset, overridedCurrency ) } @@ -291,7 +292,8 @@ class AssetHubPolkadot if ( destination === 'Hydration' && - (currencySymbol === dotAsset?.symbol || currencyId === dotAsset?.assetId) + (asset.symbol === dotAsset?.symbol || + (isForeignAsset(asset) && asset.assetId === dotAsset?.assetId)) ) { const versionOrDefault = version ?? this.version return { @@ -300,7 +302,7 @@ class AssetHubPolkadot amount, 'ParaToRelay', versionOrDefault, - currencyId, + asset, overridedCurrency ) } @@ -312,7 +314,7 @@ class AssetHubPolkadot transferPolkadotXCM( input: PolkadotXCMTransferInput ): Promise> { - const { scenario, currencySymbol, currencyId, destination } = input + const { scenario, asset, destination } = input if (destination === 'AssetHubKusama') { return Promise.resolve(this.handleBridgeTransfer(input, 'Kusama')) @@ -326,19 +328,20 @@ class AssetHubPolkadot return Promise.resolve(this.handleMythosTransfer(input)) } - const wethAsset = getOtherAssets('Ethereum').find(({ symbol }) => symbol === 'WETH') + const ethereumAssets = getOtherAssets('Ethereum') + const isEthereumAsset = ethereumAssets.some( + ({ symbol, assetId }) => + asset.symbol === symbol && isForeignAsset(asset) && asset.assetId === assetId + ) - if ( - destination === 'BifrostPolkadot' && - (currencySymbol === wethAsset?.symbol || currencyId === wethAsset?.assetId) - ) { + if (destination === 'BifrostPolkadot' && isEthereumAsset) { return Promise.resolve(this.handleBifrostEthTransfer(input)) } if ( scenario === 'ParaToPara' && - currencySymbol === 'DOT' && - currencyId === undefined && + asset.symbol === 'DOT' && + !isForeignAsset(asset) && destination !== 'Hydration' ) { throw new ScenarioNotSupportedError( @@ -348,7 +351,7 @@ class AssetHubPolkadot ) } - if (scenario === 'ParaToPara' && currencySymbol === 'KSM' && currencyId === undefined) { + if (scenario === 'ParaToPara' && asset.symbol === 'KSM' && !isForeignAsset(asset)) { throw new ScenarioNotSupportedError( this.node, scenario, @@ -379,7 +382,7 @@ class AssetHubPolkadot amount: string, scenario: TScenario, version: Version, - currencyId?: string, + asset?: TAsset, overridedMultiLocation?: TMultiLocation | TMultiAsset[] ) { if (scenario === 'ParaToPara') { @@ -389,14 +392,14 @@ class AssetHubPolkadot PalletInstance: 50 }, { - // TODO: Handle the case where currencyId is undefined - GeneralIndex: currencyId ?? '' + // TODO: Handle missing assedId + GeneralIndex: asset && isForeignAsset(asset) ? asset.assetId : 0 } ] } return createCurrencySpec(amount, version, Parents.ZERO, overridedMultiLocation, interior) } else { - return super.createCurrencySpec(amount, scenario, version, currencyId) + return super.createCurrencySpec(amount, scenario, version, asset) } } } diff --git a/packages/sdk/src/nodes/supported/Astar.test.ts b/packages/sdk/src/nodes/supported/Astar.test.ts index 2312313b..2aac644a 100644 --- a/packages/sdk/src/nodes/supported/Astar.test.ts +++ b/packages/sdk/src/nodes/supported/Astar.test.ts @@ -24,12 +24,12 @@ describe('Astar', () => { let astar: Astar const mockPolkadotXCMInput = { scenario: 'ParaToPara', - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100' } as PolkadotXCMTransferInput const mockXTokensInput = { - currencyID: '123', + asset: { assetId: '123' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Astar.ts b/packages/sdk/src/nodes/supported/Astar.ts index 18f0e76e..a12abab9 100644 --- a/packages/sdk/src/nodes/supported/Astar.ts +++ b/packages/sdk/src/nodes/supported/Astar.ts @@ -1,5 +1,6 @@ // Contains detailed structure of XCM call construction for Astar Parachain +import { InvalidCurrencyError } from '../../errors' import type { TTransferReturn } from '../../types' import { Version, @@ -9,6 +10,7 @@ import { type TSendInternalOptions, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import PolkadotXCMTransferImpl from '../polkadotXcm' import XTokensTransferImpl from '../xTokens' @@ -31,15 +33,17 @@ class Astar } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID ? BigInt(currencyID) : undefined) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } - protected canUseXTokens({ - currencySymbol, - currencyId - }: TSendInternalOptions): boolean { - return currencySymbol !== this.getNativeAssetSymbol() || !!currencyId + protected canUseXTokens({ asset }: TSendInternalOptions): boolean { + return asset.symbol !== this.getNativeAssetSymbol() || isForeignAsset(asset) } } diff --git a/packages/sdk/src/nodes/supported/Bajun.test.ts b/packages/sdk/src/nodes/supported/Bajun.test.ts index c5008207..2cad81cd 100644 --- a/packages/sdk/src/nodes/supported/Bajun.test.ts +++ b/packages/sdk/src/nodes/supported/Bajun.test.ts @@ -22,7 +22,7 @@ describe('Bajun', () => { let bajun: Bajun const mockInput = { scenario: 'ParaToPara', - currency: 'BAJ', + asset: { symbol: 'BAJ' }, amount: '100' } as XTokensTransferInput @@ -49,7 +49,12 @@ describe('Bajun', () => { }) it('should throw InvalidCurrencyError when currency does not match native asset', () => { - const invalidInput = { ...mockInput, currency: 'INVALID' } + const invalidInput = { + ...mockInput, + asset: { + symbol: 'INVALID' + } + } vi.spyOn(bajun, 'getNativeAssetSymbol').mockReturnValue('BAJ') expect(() => bajun.transferXTokens(invalidInput)).toThrowError( diff --git a/packages/sdk/src/nodes/supported/Bajun.ts b/packages/sdk/src/nodes/supported/Bajun.ts index 5a59fa2a..516929ba 100644 --- a/packages/sdk/src/nodes/supported/Bajun.ts +++ b/packages/sdk/src/nodes/supported/Bajun.ts @@ -20,17 +20,17 @@ class Bajun extends ParachainNode implements IXTokensTra } transferXTokens(input: XTokensTransferInput) { - const { scenario, currency } = input + const { scenario, asset } = input if (scenario !== 'ParaToPara') { throw new ScenarioNotSupportedError(this.node, scenario) } const nativeSymbol = this.getNativeAssetSymbol() - if (currency !== nativeSymbol) { - throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${currency}`) + if (asset.symbol !== nativeSymbol) { + throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${asset.symbol}`) } - return XTokensTransferImpl.transferXTokens(input, currency) + return XTokensTransferImpl.transferXTokens(input, asset.symbol) } transferRelayToPara(): TSerializedApiCallV2 { diff --git a/packages/sdk/src/nodes/supported/Basilisk.test.ts b/packages/sdk/src/nodes/supported/Basilisk.test.ts index fa04f6d3..9b64615d 100644 --- a/packages/sdk/src/nodes/supported/Basilisk.test.ts +++ b/packages/sdk/src/nodes/supported/Basilisk.test.ts @@ -21,7 +21,7 @@ vi.mock('../../utils', () => ({ describe('Basilisk', () => { let basilisk: Basilisk const mockInput = { - currencyID: '123', + asset: { symbol: 'BSX', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -44,7 +44,7 @@ describe('Basilisk', () => { basilisk.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, Number(123)) }) it('should return the second provider URL from getProvider', () => { diff --git a/packages/sdk/src/nodes/supported/Basilisk.ts b/packages/sdk/src/nodes/supported/Basilisk.ts index 0bd683cb..8cd839b1 100644 --- a/packages/sdk/src/nodes/supported/Basilisk.ts +++ b/packages/sdk/src/nodes/supported/Basilisk.ts @@ -1,5 +1,6 @@ // Contains detailed structure of XCM call construction for Basilisk Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, type TNodePolkadotKusama, @@ -7,6 +8,7 @@ import { type XTokensTransferInput } from '../../types' import { getAllNodeProviders } from '../../utils' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -16,8 +18,13 @@ class Basilisk extends ParachainNode implements IXTokens } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, Number(asset.assetId)) } getProvider(): string { diff --git a/packages/sdk/src/nodes/supported/BifrostKusama.test.ts b/packages/sdk/src/nodes/supported/BifrostKusama.test.ts index 78992386..a89ab7fc 100644 --- a/packages/sdk/src/nodes/supported/BifrostKusama.test.ts +++ b/packages/sdk/src/nodes/supported/BifrostKusama.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('BifrostKusama', () => { let bifrostKusama: BifrostKusama const mockInput = { - currency: 'BNC', + asset: { symbol: 'BNC' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/BifrostPolkadot.test.ts b/packages/sdk/src/nodes/supported/BifrostPolkadot.test.ts index c289c9fc..fe7bb562 100644 --- a/packages/sdk/src/nodes/supported/BifrostPolkadot.test.ts +++ b/packages/sdk/src/nodes/supported/BifrostPolkadot.test.ts @@ -30,13 +30,13 @@ vi.mock('../polkadotXcm', () => ({ describe('BifrostPolkadot', () => { let bifrostPolkadot: BifrostPolkadot const mockXTokensInput = { - currency: 'BNC', + asset: { symbol: 'BNC' }, amount: '100' } as XTokensTransferInput const mockPolkadotXCMInput = { amount: '200', - currencySymbol: 'WETH' + asset: { symbol: 'WETH' } } as PolkadotXCMTransferInput beforeEach(() => { @@ -90,12 +90,12 @@ describe('BifrostPolkadot', () => { it('should call transferPolkadotXCM with correct parameters for DOT transfer', async () => { const spy = vi.spyOn(PolkadotXCMTransferImpl, 'transferPolkadotXCM') - await bifrostPolkadot.transferPolkadotXCM({ ...mockPolkadotXCMInput, currencySymbol: 'DOT' }) + await bifrostPolkadot.transferPolkadotXCM({ ...mockPolkadotXCMInput, asset: { symbol: 'DOT' } }) expect(spy).toHaveBeenCalledWith( { ...mockPolkadotXCMInput, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, currencySelection: createCurrencySpec( mockPolkadotXCMInput.amount, Version.V3, @@ -108,48 +108,42 @@ describe('BifrostPolkadot', () => { ) }) - it('should throw error when currency symbol is undefined in transferXTokens', () => { - expect(() => - bifrostPolkadot.transferXTokens({ ...mockXTokensInput, currency: undefined }) - ).toThrowError('Currency symbol is undefined') - }) - describe('canUseXTokens', () => { - it('should return false when currencySymbol is WETH and destination is AssetHubPolkadot', () => { + it('should return false when currency symbol is WETH and destination is AssetHubPolkadot', () => { const options = { - currencySymbol: 'WETH', + asset: { symbol: 'WETH' }, destination: 'AssetHubPolkadot' } as TSendInternalOptions expect(bifrostPolkadot['canUseXTokens'](options)).toBe(false) }) - it('should return false when currencySymbol is DOT and destination is AssetHubPolkadot', () => { + it('should return false when currency symbol is DOT and destination is AssetHubPolkadot', () => { const options: TSendInternalOptions = { - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, destination: 'AssetHubPolkadot' } as TSendInternalOptions expect(bifrostPolkadot['canUseXTokens'](options)).toBe(false) }) - it('should return true when currencySymbol is not WETH or DOT and destination is AssetHubPolkadot', () => { + it('should return true when currency symbol is not WETH or DOT and destination is AssetHubPolkadot', () => { const options: TSendInternalOptions = { - currencySymbol: 'BNC', + asset: { symbol: 'BNC' }, destination: 'AssetHubPolkadot' } as TSendInternalOptions expect(bifrostPolkadot['canUseXTokens'](options)).toBe(true) }) - it('should return true when currencySymbol is WETH but destination is not AssetHubPolkadot', () => { + it('should return true when currency symbol is WETH but destination is not AssetHubPolkadot', () => { const options: TSendInternalOptions = { - currencySymbol: 'WETH', + asset: { symbol: 'WETH' }, destination: 'Acala' } as TSendInternalOptions expect(bifrostPolkadot['canUseXTokens'](options)).toBe(true) }) - it('should return true when currencySymbol is DOT but destination is not AssetHubPolkadot', () => { + it('should return true when currency ymbol is DOT but destination is not AssetHubPolkadot', () => { const options: TSendInternalOptions = { - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, destination: 'Acala' } as TSendInternalOptions expect(bifrostPolkadot['canUseXTokens'](options)).toBe(true) diff --git a/packages/sdk/src/nodes/supported/BifrostPolkadot.ts b/packages/sdk/src/nodes/supported/BifrostPolkadot.ts index b1a2574e..c16c3372 100644 --- a/packages/sdk/src/nodes/supported/BifrostPolkadot.ts +++ b/packages/sdk/src/nodes/supported/BifrostPolkadot.ts @@ -5,6 +5,7 @@ import { getAssetId } from '../../pallets/assets' import type { IPolkadotXCMTransfer, PolkadotXCMTransferInput, + TAsset, TSendInternalOptions, TTransferReturn } from '../../types' @@ -13,6 +14,7 @@ import ParachainNode from '../ParachainNode' import PolkadotXCMTransferImpl from '../polkadotXcm' import XTokensTransferImpl from '../xTokens' import { ETHEREUM_JUNCTION } from '../../const' +import { isForeignAsset } from '../../utils/assets' export class BifrostPolkadot extends ParachainNode @@ -22,21 +24,21 @@ export class BifrostPolkadot super('BifrostPolkadot', 'bifrost', 'polkadot', Version.V3) } - private getCurrencySelection(currency: string, currencyId: string | undefined) { + private getCurrencySelection(asset: TAsset) { const nativeAssetSymbol = this.getNativeAssetSymbol() - if (currency === nativeAssetSymbol) { + if (asset.symbol === nativeAssetSymbol) { return { Native: nativeAssetSymbol } } - const isVToken = currency.startsWith('v') - const isVSToken = currency.startsWith('vs') + const isVToken = asset.symbol && asset.symbol.startsWith('v') + const isVSToken = asset.symbol && asset.symbol.startsWith('vs') - if (!currencyId) { - return isVToken ? { VToken: currency.substring(1) } : { Token: currency } + if (!isForeignAsset(asset)) { + return isVToken ? { VToken: asset.symbol.substring(1) } : { Token: asset.symbol } } - const id = Number(currencyId) + const id = Number(asset.assetId) if (isVSToken) { return { VSToken2: id } } @@ -45,13 +47,9 @@ export class BifrostPolkadot } transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input + const { asset } = input - if (!currency) { - throw new Error('Currency symbol is undefined') - } - - const currencySelection = this.getCurrencySelection(currency, currencyID) + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } @@ -59,7 +57,7 @@ export class BifrostPolkadot transferPolkadotXCM( input: PolkadotXCMTransferInput ): Promise> { - const { amount, overridedCurrency, currencySymbol } = input + const { amount, overridedCurrency, asset } = input return Promise.resolve( PolkadotXCMTransferImpl.transferPolkadotXCM( @@ -68,9 +66,9 @@ export class BifrostPolkadot currencySelection: createCurrencySpec( amount, this.version, - currencySymbol === 'DOT' ? Parents.ONE : Parents.TWO, + asset.symbol === 'DOT' ? Parents.ONE : Parents.TWO, overridedCurrency, - currencySymbol === 'WETH' + asset.symbol === 'WETH' ? { X2: [ ETHEREUM_JUNCTION, @@ -88,12 +86,7 @@ export class BifrostPolkadot ) } - protected canUseXTokens({ - currencySymbol, - destination - }: TSendInternalOptions): boolean { - return ( - (currencySymbol !== 'WETH' && currencySymbol !== 'DOT') || destination !== 'AssetHubPolkadot' - ) + protected canUseXTokens({ asset, destination }: TSendInternalOptions): boolean { + return (asset.symbol !== 'WETH' && asset.symbol !== 'DOT') || destination !== 'AssetHubPolkadot' } } diff --git a/packages/sdk/src/nodes/supported/BridgeHubKusama.test.ts b/packages/sdk/src/nodes/supported/BridgeHubKusama.test.ts index 67f7112c..7090ff4e 100644 --- a/packages/sdk/src/nodes/supported/BridgeHubKusama.test.ts +++ b/packages/sdk/src/nodes/supported/BridgeHubKusama.test.ts @@ -23,7 +23,7 @@ describe('BridgeHubKusama', () => { let bridgeHubKusama: BridgeHubKusama const mockInput = { scenario: 'RelayToPara', - currencySymbol: 'KSM', + asset: { symbol: 'KSM' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/BridgeHubPolkadot.test.ts b/packages/sdk/src/nodes/supported/BridgeHubPolkadot.test.ts index 38e8aeb3..1313997f 100644 --- a/packages/sdk/src/nodes/supported/BridgeHubPolkadot.test.ts +++ b/packages/sdk/src/nodes/supported/BridgeHubPolkadot.test.ts @@ -23,7 +23,7 @@ describe('BridgeHubPolkadot', () => { let bridgeHubPolkadot: BridgeHubPolkadot const mockInput = { scenario: 'RelayToPara', - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Calamari.test.ts b/packages/sdk/src/nodes/supported/Calamari.test.ts index 8e5f58f3..fdadbcac 100644 --- a/packages/sdk/src/nodes/supported/Calamari.test.ts +++ b/packages/sdk/src/nodes/supported/Calamari.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Calamari', () => { let calamari: Calamari const mockInput = { - currencyID: '123', + asset: { assetId: '123' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Calamari.ts b/packages/sdk/src/nodes/supported/Calamari.ts index 4bb47b0e..0e93876f 100644 --- a/packages/sdk/src/nodes/supported/Calamari.ts +++ b/packages/sdk/src/nodes/supported/Calamari.ts @@ -1,11 +1,13 @@ // Contains detailed structure of XCM call construction for Calamari Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TMantaAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -16,9 +18,14 @@ class Calamari extends ParachainNode implements IXTokens transferXTokens(input: XTokensTransferInput) { // Currently only option for XCM transfer - const { currencyID } = input + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + const currencySelection: TMantaAsset = { - MantaCurrency: currencyID ? BigInt(currencyID) : undefined + MantaCurrency: BigInt(asset.assetId) } return XTokensTransferImpl.transferXTokens(input, currencySelection) } diff --git a/packages/sdk/src/nodes/supported/Centrifuge.test.ts b/packages/sdk/src/nodes/supported/Centrifuge.test.ts index 3534e744..1f6e721e 100644 --- a/packages/sdk/src/nodes/supported/Centrifuge.test.ts +++ b/packages/sdk/src/nodes/supported/Centrifuge.test.ts @@ -16,8 +16,10 @@ vi.mock('../xTokens', () => ({ describe('Centrifuge', () => { let centrifuge: Centrifuge const mockInput = { - currency: 'CFG', - currencyID: '123', + asset: { + symbol: 'CFG', + assetId: '123' + }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Centrifuge.ts b/packages/sdk/src/nodes/supported/Centrifuge.ts index 202f53cb..1bf3b4d9 100644 --- a/packages/sdk/src/nodes/supported/Centrifuge.ts +++ b/packages/sdk/src/nodes/supported/Centrifuge.ts @@ -1,6 +1,9 @@ // Contains detailed structure of XCM call construction for Centrifuge Parachain +import { InvalidCurrencyError } from '../../errors' +import type { TAsset } from '../../types' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -9,10 +12,19 @@ export class Centrifuge extends ParachainNode implements super('Centrifuge', 'centrifuge', 'polkadot', Version.V3) } + private getCurrencySelection(asset: TAsset) { + if (asset.symbol === this.getNativeAssetSymbol()) return 'Native' + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { ForeignAsset: Number(asset.assetId) } + } + transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection = - currency === this.getNativeAssetSymbol() ? 'Native' : { ForeignAsset: Number(currencyID) } + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/Collectives.test.ts b/packages/sdk/src/nodes/supported/Collectives.test.ts index f16e8eb0..2c6c1dce 100644 --- a/packages/sdk/src/nodes/supported/Collectives.test.ts +++ b/packages/sdk/src/nodes/supported/Collectives.test.ts @@ -23,7 +23,7 @@ describe('Collectives', () => { let collectives: Collectives const mockInput = { scenario: 'RelayToPara', - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Collectives.ts b/packages/sdk/src/nodes/supported/Collectives.ts index 4516dc39..4316a470 100644 --- a/packages/sdk/src/nodes/supported/Collectives.ts +++ b/packages/sdk/src/nodes/supported/Collectives.ts @@ -2,7 +2,7 @@ import { ScenarioNotSupportedError } from '../../errors' import { constructRelayToParaParameters } from '../../pallets/xcmPallet/utils' -import type { TTransferReturn } from '../../types' +import type { TAsset, TTransferReturn } from '../../types' import { type IPolkadotXCMTransfer, type PolkadotXCMTransferInput, @@ -40,11 +40,11 @@ class Collectives extends ParachainNode implements IPolk } } - createCurrencySpec(amount: string, scenario: TScenario, version: Version, currencyId?: string) { + createCurrencySpec(amount: string, scenario: TScenario, version: Version, asset?: TAsset) { if (scenario === 'ParaToPara') { return {} } else { - return super.createCurrencySpec(amount, scenario, version, currencyId) + return super.createCurrencySpec(amount, scenario, version, asset) } } } diff --git a/packages/sdk/src/nodes/supported/ComposableFinance.test.ts b/packages/sdk/src/nodes/supported/ComposableFinance.test.ts index f00d295e..f0aae876 100644 --- a/packages/sdk/src/nodes/supported/ComposableFinance.test.ts +++ b/packages/sdk/src/nodes/supported/ComposableFinance.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('ComposableFinance', () => { let composableFinance: ComposableFinance const mockInput = { - currencyID: '123', + asset: { symbol: 'LAYR', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('ComposableFinance', () => { composableFinance.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123)) }) }) diff --git a/packages/sdk/src/nodes/supported/ComposableFinance.ts b/packages/sdk/src/nodes/supported/ComposableFinance.ts index 9e1b6179..d5b181e1 100644 --- a/packages/sdk/src/nodes/supported/ComposableFinance.ts +++ b/packages/sdk/src/nodes/supported/ComposableFinance.ts @@ -1,6 +1,8 @@ // Contains detailed structure of XCM call construction for ComposableFinance Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,8 +12,13 @@ class ComposableFinance extends ParachainNode implements } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } } diff --git a/packages/sdk/src/nodes/supported/CoretimeKusama.test.ts b/packages/sdk/src/nodes/supported/CoretimeKusama.test.ts index e7022310..8d27b942 100644 --- a/packages/sdk/src/nodes/supported/CoretimeKusama.test.ts +++ b/packages/sdk/src/nodes/supported/CoretimeKusama.test.ts @@ -22,7 +22,7 @@ describe('CoretimeKusama', () => { let coretimeKusama: CoretimeKusama const mockInput = { scenario: 'ParaToPara', - currencySymbol: 'KSM', + asset: { symbol: 'KSM' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Crab.test.ts b/packages/sdk/src/nodes/supported/Crab.test.ts index 08a64982..f58edb78 100644 --- a/packages/sdk/src/nodes/supported/Crab.test.ts +++ b/packages/sdk/src/nodes/supported/Crab.test.ts @@ -23,7 +23,7 @@ describe('Crab', () => { let crab: Crab const mockInput = { scenario: 'ParaToPara', - currencySymbol: 'KSM', + asset: { symbol: 'KSM' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Crab.ts b/packages/sdk/src/nodes/supported/Crab.ts index b3a535dc..38a9b2aa 100644 --- a/packages/sdk/src/nodes/supported/Crab.ts +++ b/packages/sdk/src/nodes/supported/Crab.ts @@ -1,6 +1,6 @@ // Contains detailed structure of XCM call construction for Crab Parachain -import type { TCurrencySelectionHeaderArr, TTransferReturn } from '../../types' +import type { TAsset, TCurrencySelectionHeaderArr, TTransferReturn } from '../../types' import { type IPolkadotXCMTransfer, type PolkadotXCMTransferInput, @@ -39,13 +39,13 @@ class Crab extends ParachainNode implements IPolkadotXCM amount: string, scenario: TScenario, version: Version, - currencyId?: string + asset?: TAsset ): TCurrencySelectionHeaderArr { return getNode('Darwinia').createCurrencySpec( amount, scenario, version, - currencyId + asset ) } } diff --git a/packages/sdk/src/nodes/supported/Crust.test.ts b/packages/sdk/src/nodes/supported/Crust.test.ts index 882169c5..28188f55 100644 --- a/packages/sdk/src/nodes/supported/Crust.test.ts +++ b/packages/sdk/src/nodes/supported/Crust.test.ts @@ -17,8 +17,10 @@ vi.mock('../xTokens', () => ({ describe('Crust', () => { let crust: Crust const mockInput = { - currency: 'CRU', - currencyID: '456', + asset: { + symbol: 'CRU', + assetId: '123' + }, amount: '100' } as XTokensTransferInput @@ -48,15 +50,18 @@ describe('Crust', () => { crust.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { OtherReserve: '456' } as TReserveAsset) + expect(spy).toHaveBeenCalledWith(mockInput, { OtherReserve: BigInt(123) } as TReserveAsset) }) it('should throw InvalidCurrencyError when currencyID is undefined and currency does not match native asset', () => { - const invalidInput = { ...mockInput, currencyID: undefined } + const invalidInput = { + ...mockInput, + asset: { + symbol: 'XYZ' + } + } vi.spyOn(crust, 'getNativeAssetSymbol').mockReturnValue('NOT_CRU') - expect(() => crust.transferXTokens(invalidInput)).toThrowError( - new InvalidCurrencyError('Asset CRU is not supported by node Crust.') - ) + expect(() => crust.transferXTokens(invalidInput)).toThrowError(InvalidCurrencyError) }) }) diff --git a/packages/sdk/src/nodes/supported/Crust.ts b/packages/sdk/src/nodes/supported/Crust.ts index c4d94589..fa22c9b7 100644 --- a/packages/sdk/src/nodes/supported/Crust.ts +++ b/packages/sdk/src/nodes/supported/Crust.ts @@ -1,12 +1,14 @@ // Contains detailed structure of XCM call construction for Crust Parachain import { InvalidCurrencyError } from '../../errors/InvalidCurrencyError' +import type { TAsset } from '../../types' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TReserveAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,23 +17,22 @@ class Crust extends ParachainNode implements IXTokensTra super('Crust', 'crustParachain', 'polkadot', Version.V3) } - getCurrencySelection({ - currency, - currencyID - }: XTokensTransferInput): TReserveAsset { - if (currency === this.getNativeAssetSymbol()) { + private getCurrencySelection(asset: TAsset): TReserveAsset { + if (asset.symbol === this.getNativeAssetSymbol()) { return 'SelfReserve' } - if (currencyID === undefined) { - throw new InvalidCurrencyError(`Asset ${currency} is not supported by node ${this.node}.`) + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) } - return { OtherReserve: currencyID } + return { OtherReserve: BigInt(asset.assetId) } } transferXTokens(input: XTokensTransferInput) { - return XTokensTransferImpl.transferXTokens(input, this.getCurrencySelection(input)) + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) + return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/CrustShadow.test.ts b/packages/sdk/src/nodes/supported/CrustShadow.test.ts index 25ff013a..22b8e356 100644 --- a/packages/sdk/src/nodes/supported/CrustShadow.test.ts +++ b/packages/sdk/src/nodes/supported/CrustShadow.test.ts @@ -17,8 +17,10 @@ vi.mock('../xTokens', () => ({ describe('CrustShadow', () => { let crustShadow: CrustShadow const mockInput = { - currency: 'CRU', - currencyID: '456', + asset: { + symbol: 'CRU', + assetId: '123' + }, amount: '100' } as XTokensTransferInput @@ -48,15 +50,18 @@ describe('CrustShadow', () => { crustShadow.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { OtherReserve: '456' } as TReserveAsset) + expect(spy).toHaveBeenCalledWith(mockInput, { OtherReserve: BigInt(123) } as TReserveAsset) }) it('should throw InvalidCurrencyError when currencyID is undefined and currency does not match native asset', () => { - const invalidInput = { ...mockInput, currencyID: undefined } + const invalidInput = { + ...mockInput, + asset: { + symbol: 'INVALID' + } + } vi.spyOn(crustShadow, 'getNativeAssetSymbol').mockReturnValue('NOT_CRU') - expect(() => crustShadow.transferXTokens(invalidInput)).toThrowError( - new InvalidCurrencyError('Asset CRU is not supported by node CrustShadow.') - ) + expect(() => crustShadow.transferXTokens(invalidInput)).toThrowError(InvalidCurrencyError) }) }) diff --git a/packages/sdk/src/nodes/supported/CrustShadow.ts b/packages/sdk/src/nodes/supported/CrustShadow.ts index aaf44057..6d817dc6 100644 --- a/packages/sdk/src/nodes/supported/CrustShadow.ts +++ b/packages/sdk/src/nodes/supported/CrustShadow.ts @@ -1,12 +1,14 @@ // Contains detailed structure of XCM call construction for CrustShadow Parachain import { InvalidCurrencyError } from '../../errors/InvalidCurrencyError' +import type { TAsset } from '../../types' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TReserveAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,23 +17,22 @@ class CrustShadow extends ParachainNode implements IXTok super('CrustShadow', 'shadow', 'kusama', Version.V3) } - getCurrencySelection({ - currency, - currencyID - }: XTokensTransferInput): TReserveAsset { - if (currency === this.getNativeAssetSymbol()) { + private getCurrencySelection(asset: TAsset): TReserveAsset { + if (asset.symbol === this.getNativeAssetSymbol()) { return 'SelfReserve' } - if (currencyID === undefined) { - throw new InvalidCurrencyError(`Asset ${currency} is not supported by node ${this.node}.`) + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) } - return { OtherReserve: currencyID } + return { OtherReserve: BigInt(asset.assetId) } } transferXTokens(input: XTokensTransferInput) { - return XTokensTransferImpl.transferXTokens(input, this.getCurrencySelection(input)) + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) + return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/Curio.test.ts b/packages/sdk/src/nodes/supported/Curio.test.ts index 346d9abe..d7065293 100644 --- a/packages/sdk/src/nodes/supported/Curio.test.ts +++ b/packages/sdk/src/nodes/supported/Curio.test.ts @@ -16,8 +16,10 @@ vi.mock('../xTokens', () => ({ describe('Curio', () => { let curio: Curio const mockInput = { - currency: 'CUR', - currencyID: '123', + asset: { + symbol: 'CUR', + assetId: '123' + }, amount: '100' } as XTokensTransferInput @@ -42,7 +44,12 @@ describe('Curio', () => { it('should call transferXTokens with Token when currencyID is undefined', () => { const spy = vi.spyOn(XTokensTransferImpl, 'transferXTokens') - const inputWithoutCurrencyID = { ...mockInput, currencyID: undefined } + const inputWithoutCurrencyID = { + ...mockInput, + asset: { + symbol: 'CUR' + } + } curio.transferXTokens(inputWithoutCurrencyID) diff --git a/packages/sdk/src/nodes/supported/Curio.ts b/packages/sdk/src/nodes/supported/Curio.ts index ac694b45..cdfc7856 100644 --- a/packages/sdk/src/nodes/supported/Curio.ts +++ b/packages/sdk/src/nodes/supported/Curio.ts @@ -2,6 +2,7 @@ import type { TForeignOrTokenAsset } from '../../types' import { Version, type IXTokensTransfer, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -11,10 +12,10 @@ class Curio extends ParachainNode implements IXTokensTra } transferXTokens(input: XTokensTransferInput) { - const { currencyID, currency } = input - const currencySelection: TForeignOrTokenAsset = currencyID - ? { ForeignAsset: Number(currencyID) } - : { Token: currency } + const { asset } = input + const currencySelection: TForeignOrTokenAsset = isForeignAsset(asset) + ? { ForeignAsset: Number(asset.assetId) } + : { Token: asset.symbol } return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/Darwinia.test.ts b/packages/sdk/src/nodes/supported/Darwinia.test.ts index c9e491ff..517db022 100644 --- a/packages/sdk/src/nodes/supported/Darwinia.test.ts +++ b/packages/sdk/src/nodes/supported/Darwinia.test.ts @@ -3,7 +3,7 @@ import type { XTokensTransferInput, TScenario, TSelfReserveAsset, - TForeignAsset, + TXcmForeignAsset, TCurrencySelectionHeaderArr } from '../../types' import { Version, Parents } from '../../types' @@ -29,8 +29,7 @@ vi.mock('../../pallets/xcmPallet/utils', () => ({ describe('Darwinia', () => { let darwinia: Darwinia const mockInput = { - currency: 'RING', - currencyID: '456', + asset: { symbol: 'RING', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -60,7 +59,7 @@ describe('Darwinia', () => { darwinia.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { ForeignAsset: BigInt(456) } as TForeignAsset) + expect(spy).toHaveBeenCalledWith(mockInput, { ForeignAsset: BigInt(123) } as TXcmForeignAsset) }) it('should throw NodeNotSupportedError for transferRelayToPara', () => { diff --git a/packages/sdk/src/nodes/supported/Darwinia.ts b/packages/sdk/src/nodes/supported/Darwinia.ts index dce6a708..64c10a9c 100644 --- a/packages/sdk/src/nodes/supported/Darwinia.ts +++ b/packages/sdk/src/nodes/supported/Darwinia.ts @@ -1,6 +1,6 @@ // Contains detailed structure of XCM call construction for Darwinia Parachain -import type { TNodePolkadotKusama } from '../../types' +import type { TAsset, TNodePolkadotKusama } from '../../types' import { Version, type TSerializedApiCallV2, @@ -9,26 +9,36 @@ import { type TScenario, Parents, type TSelfReserveAsset, - type TForeignAsset + type TXcmForeignAsset } from '../../types' import ParachainNode from '../ParachainNode' -import { NodeNotSupportedError } from '../../errors' +import { InvalidCurrencyError, NodeNotSupportedError } from '../../errors' import XTokensTransferImpl from '../xTokens' import { createCurrencySpec } from '../../pallets/xcmPallet/utils' import { type TMultiLocation } from '../../types/TMultiLocation' import { getAllNodeProviders } from '../../utils' +import { isForeignAsset } from '../../utils/assets' class Darwinia extends ParachainNode implements IXTokensTransfer { constructor() { super('Darwinia', 'darwinia', 'polkadot', Version.V3) } + private getCurrencySelection(asset: TAsset): TSelfReserveAsset | TXcmForeignAsset { + if (asset.symbol === this.getNativeAssetSymbol()) { + return 'SelfReserve' + } + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { ForeignAsset: BigInt(asset.assetId) } + } + transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - const currencySelection: TSelfReserveAsset | TForeignAsset = - input.currency === this.getNativeAssetSymbol() - ? 'SelfReserve' - : { ForeignAsset: currencyID ? BigInt(currencyID) : undefined } + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } @@ -40,7 +50,7 @@ class Darwinia extends ParachainNode implements IXTokens amount: string, scenario: TScenario, version: Version, - currencyId?: string, + _asset?: TAsset, overridedMultiLocation?: TMultiLocation ) { if (scenario === 'ParaToPara') { @@ -51,7 +61,7 @@ class Darwinia extends ParachainNode implements IXTokens } return createCurrencySpec(amount, version, Parents.ZERO, overridedMultiLocation, interior) } else { - return super.createCurrencySpec(amount, scenario, version, currencyId, overridedMultiLocation) + return super.createCurrencySpec(amount, scenario, version, undefined, overridedMultiLocation) } } diff --git a/packages/sdk/src/nodes/supported/Encointer.test.ts b/packages/sdk/src/nodes/supported/Encointer.test.ts index aac25e95..c40e4559 100644 --- a/packages/sdk/src/nodes/supported/Encointer.test.ts +++ b/packages/sdk/src/nodes/supported/Encointer.test.ts @@ -23,7 +23,7 @@ describe('Encointer', () => { let encointer: Encointer const mockInput = { scenario: 'ParaToRelay', - currencySymbol: 'KSM', + asset: { symbol: 'KSM' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Hydration.test.ts b/packages/sdk/src/nodes/supported/Hydration.test.ts index 3c183125..f23b16e5 100644 --- a/packages/sdk/src/nodes/supported/Hydration.test.ts +++ b/packages/sdk/src/nodes/supported/Hydration.test.ts @@ -42,7 +42,7 @@ describe('Hydration', () => { it('should call transferXTokens with currencyID', () => { const mockInput = { - currencyID: '123', + asset: { assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -50,7 +50,7 @@ describe('Hydration', () => { hydration.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, Number(123)) }) describe('transferPolkadotXCM', () => { @@ -69,11 +69,10 @@ describe('Hydration', () => { mockInput = { api: mockApi, address: ethers.Wallet.createRandom().address, - currencySymbol: 'WETH', + asset: { symbol: 'WETH', assetId: '0x1234567890abcdef' }, scenario: 'RelayToPara', destination: 'Ethereum', amount: '1000', - currencyId: '0x1234567890abcdef', ahAddress: '5Gw3s7q4QLkSWwknsiixu9GR7x6xN5PWQ1YbQGxwSz1Y7DZT' } as PolkadotXCMTransferInput }) @@ -87,7 +86,7 @@ describe('Hydration', () => { }) it('should throw InvalidCurrencyError for unsupported currency', async () => { - mockInput.currencySymbol = 'DOT' + mockInput.asset.symbol = 'DOT' await expect(hydration.transferPolkadotXCM(mockInput)).rejects.toThrow(InvalidCurrencyError) }) @@ -115,8 +114,9 @@ describe('Hydration', () => { it('should create call for AssetHub destination DOT transfer', async () => { mockInput.destination = 'AssetHubPolkadot' - mockInput.currencySymbol = 'DOT' - mockInput.currencyId = undefined + mockInput.asset = { + symbol: 'DOT' + } const transferToAhSpy = vi.spyOn(hydration, 'transferToAssetHub') const spy = vi.spyOn(mockApi, 'callTxMethod') @@ -134,7 +134,7 @@ describe('Hydration', () => { it('should create call for AssetHub destination DOT transfer', async () => { mockInput.destination = 'AssetHubPolkadot' - mockInput.currencyId = '3' + mockInput.asset = { symbol: 'DOT', assetId: '3' } const transferToAhSpy = vi.spyOn(hydration, 'transferToAssetHub') const spy = vi.spyOn(mockApi, 'callTxMethod') @@ -170,7 +170,7 @@ describe('Hydration', () => { it('should return false when destination AssetHubPolkadot and currency is DOT', () => { const result = hydration['canUseXTokens']({ destination: 'AssetHubPolkadot', - currencySymbol: 'DOT' + asset: { symbol: 'DOT' } } as TSendInternalOptions) expect(result).toBe(false) }) diff --git a/packages/sdk/src/nodes/supported/Hydration.ts b/packages/sdk/src/nodes/supported/Hydration.ts index c41f0a6d..16cefef7 100644 --- a/packages/sdk/src/nodes/supported/Hydration.ts +++ b/packages/sdk/src/nodes/supported/Hydration.ts @@ -18,6 +18,7 @@ import { ETHEREUM_JUNCTION } from '../../const' import { generateAddressPayload } from '../../utils' import type { IPolkadotApi } from '../../api' import { createEthereumTokenLocation } from '../../utils/multiLocation/createEthereumTokenLocation' +import { isForeignAsset } from '../../utils/assets' const calculateFee = async (api: IPolkadotApi) => { const DEFAULT_FEE = BigInt(2_750_872_500_000) @@ -51,76 +52,82 @@ const createCustomXcmAh = ( }) const createCustomXcmOnDest = ( - { api, address, currencyId, scenario, ahAddress }: PolkadotXCMTransferInput, + { api, address, asset, scenario, ahAddress }: PolkadotXCMTransferInput, version: Version -) => ({ - [version]: [ - { - SetAppendix: [ - { - DepositAsset: { - assets: { Wild: 'All' }, - beneficiary: Object.values( - generateAddressPayload( - api, - scenario, - 'PolkadotXcm', - ahAddress ?? '', - version, - undefined - ) - )[0] - } - } - ] - }, - { - InitiateReserveWithdraw: { - assets: { - Wild: { AllOf: { id: createEthereumTokenLocation(currencyId ?? ''), fun: 'Fungible' } } - }, - reserve: { - parents: Parents.TWO, - interior: { X1: [ETHEREUM_JUNCTION] } - }, - xcm: [ +) => { + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { + [version]: [ + { + SetAppendix: [ { - BuyExecution: { - fees: { - id: { - parents: Parents.ZERO, - interior: { - X1: [{ AccountKey20: { network: null, key: currencyId } }] - } - }, - fun: { Fungible: BigInt(1) } - }, - weight_limit: 'Unlimited' + DepositAsset: { + assets: { Wild: 'All' }, + beneficiary: Object.values( + generateAddressPayload( + api, + scenario, + 'PolkadotXcm', + ahAddress ?? '', + version, + undefined + ) + )[0] } + } + ] + }, + { + InitiateReserveWithdraw: { + assets: { + Wild: { AllOf: { id: createEthereumTokenLocation(asset.assetId), fun: 'Fungible' } } }, - { - DepositAsset: { - assets: { Wild: { AllCounted: 1 } }, - beneficiary: { - parents: Parents.ZERO, - interior: { - X1: [ - { - AccountKey20: { - network: null, - key: address - } + reserve: { + parents: Parents.TWO, + interior: { X1: [ETHEREUM_JUNCTION] } + }, + xcm: [ + { + BuyExecution: { + fees: { + id: { + parents: Parents.ZERO, + interior: { + X1: [{ AccountKey20: { network: null, key: asset.assetId } }] } - ] + }, + fun: { Fungible: BigInt(1) } + }, + weight_limit: 'Unlimited' + } + }, + { + DepositAsset: { + assets: { Wild: { AllCounted: 1 } }, + beneficiary: { + parents: Parents.ZERO, + interior: { + X1: [ + { + AccountKey20: { + network: null, + key: address + } + } + ] + } } } } - } - ] + ] + } } - } - ] -}) + ] + } +} class Hydration extends ParachainNode @@ -133,27 +140,21 @@ class Hydration async transferToEthereum( input: PolkadotXCMTransferInput ): Promise> { - const { - api, - address, - currencySymbol, - scenario, - version, - destination, - amount, - currencyId, - ahAddress - } = input + const { api, address, asset, scenario, version, destination, amount, ahAddress } = input if (!ethers.isAddress(address)) { throw new Error('Only Ethereum addresses are supported for Ethereum transfers') } - if (currencySymbol?.toUpperCase() !== 'WETH') { + if (asset.symbol?.toUpperCase() !== 'WETH') { throw new InvalidCurrencyError( - `Currency ${currencySymbol} is not supported for Ethereum transfers from Hydration` + `Currency ${asset.symbol} is not supported for Ethereum transfers from Hydration` ) } + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + if (ahAddress === undefined) { throw new Error('AssetHub address is required for Ethereum transfers') } @@ -165,7 +166,7 @@ class Hydration amount, versionOrDefault, Parents.TWO, - createEthereumTokenLocation(currencyId ?? '') + createEthereumTokenLocation(asset.assetId) ) )[0][0] @@ -256,22 +257,24 @@ class Hydration } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, Number(asset.assetId)) } - protected canUseXTokens({ - destination, - currencySymbol, - currencyId - }: TSendInternalOptions): boolean { + protected canUseXTokens({ destination, asset }: TSendInternalOptions): boolean { const dotAsset = getOtherAssets(this.node).find(({ symbol }) => symbol === 'DOT') return ( destination !== 'Ethereum' && !( destination === 'AssetHubPolkadot' && - (currencySymbol === dotAsset?.symbol || currencyId === dotAsset?.assetId) + (asset.symbol === dotAsset?.symbol || + (isForeignAsset(asset) && asset.symbol === dotAsset?.assetId)) ) ) } diff --git a/packages/sdk/src/nodes/supported/Imbue.test.ts b/packages/sdk/src/nodes/supported/Imbue.test.ts index 338a6768..8b277be3 100644 --- a/packages/sdk/src/nodes/supported/Imbue.test.ts +++ b/packages/sdk/src/nodes/supported/Imbue.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Imbue', () => { let imbue: Imbue const mockInput = { - currency: 'IMBU', + asset: { symbol: 'IMBU' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Imbue.ts b/packages/sdk/src/nodes/supported/Imbue.ts index 33e239d3..dd5bb11a 100644 --- a/packages/sdk/src/nodes/supported/Imbue.ts +++ b/packages/sdk/src/nodes/supported/Imbue.ts @@ -10,8 +10,8 @@ class Imbue extends ParachainNode implements IXTokensTra } transferXTokens(input: XTokensTransferInput) { - const { currency } = input - return XTokensTransferImpl.transferXTokens(input, currency) + const { asset } = input + return XTokensTransferImpl.transferXTokens(input, asset.symbol) } } diff --git a/packages/sdk/src/nodes/supported/Integritee.test.ts b/packages/sdk/src/nodes/supported/Integritee.test.ts index fb174b52..a5e7a8cb 100644 --- a/packages/sdk/src/nodes/supported/Integritee.test.ts +++ b/packages/sdk/src/nodes/supported/Integritee.test.ts @@ -17,7 +17,7 @@ vi.mock('../xTokens', () => ({ describe('Integritee', () => { let integritee: Integritee const mockInput = { - currency: 'TEER', + asset: { symbol: 'TEER' }, amount: '100' } as XTokensTransferInput @@ -41,7 +41,7 @@ describe('Integritee', () => { }) it('should throw InvalidCurrencyError for unsupported currency KSM', () => { - const invalidInput = { ...mockInput, currency: 'KSM' } + const invalidInput = { ...mockInput, asset: { symbol: 'KSM' } } expect(() => integritee.transferXTokens(invalidInput)).toThrowError( new InvalidCurrencyError('Node Integritee does not support currency KSM') diff --git a/packages/sdk/src/nodes/supported/Integritee.ts b/packages/sdk/src/nodes/supported/Integritee.ts index 5f66af9f..80665fbc 100644 --- a/packages/sdk/src/nodes/supported/Integritee.ts +++ b/packages/sdk/src/nodes/supported/Integritee.ts @@ -16,9 +16,10 @@ class Integritee extends ParachainNode implements IXToke } transferXTokens(input: XTokensTransferInput) { - if (input.currency === 'KSM') + const { asset } = input + if (asset.symbol === 'KSM') throw new InvalidCurrencyError(`Node ${this.node} does not support currency KSM`) - return XTokensTransferImpl.transferXTokens(input, input.currency) + return XTokensTransferImpl.transferXTokens(input, asset.symbol) } transferRelayToPara(): TSerializedApiCallV2 { diff --git a/packages/sdk/src/nodes/supported/Interlay.test.ts b/packages/sdk/src/nodes/supported/Interlay.test.ts index 95dd9099..5ababc7c 100644 --- a/packages/sdk/src/nodes/supported/Interlay.test.ts +++ b/packages/sdk/src/nodes/supported/Interlay.test.ts @@ -16,8 +16,10 @@ vi.mock('../xTokens', () => ({ describe('Interlay', () => { let interlay: Interlay const mockInput = { - currency: 'INTR', - currencyID: '456', + asset: { + symbol: 'INTR', + assetId: '456' + }, amount: '100' } as XTokensTransferInput @@ -42,7 +44,12 @@ describe('Interlay', () => { it('should call transferXTokens with Token when currencyID is undefined', () => { const spy = vi.spyOn(XTokensTransferImpl, 'transferXTokens') - const inputWithoutCurrencyID = { ...mockInput, currencyID: undefined } + const inputWithoutCurrencyID = { + ...mockInput, + asset: { + symbol: 'INTR' + } + } interlay.transferXTokens(inputWithoutCurrencyID) diff --git a/packages/sdk/src/nodes/supported/Interlay.ts b/packages/sdk/src/nodes/supported/Interlay.ts index f36a429e..c99a08f3 100644 --- a/packages/sdk/src/nodes/supported/Interlay.ts +++ b/packages/sdk/src/nodes/supported/Interlay.ts @@ -6,6 +6,7 @@ import { type XTokensTransferInput, type TForeignOrTokenAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,9 +16,10 @@ class Interlay extends ParachainNode implements IXTokens } transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection: TForeignOrTokenAsset = - currencyID !== undefined ? { ForeignAsset: Number(currencyID) } : { Token: currency } + const { asset } = input + const currencySelection: TForeignOrTokenAsset = isForeignAsset(asset) + ? { ForeignAsset: Number(asset.assetId) } + : { Token: asset.symbol } return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/InvArchTinker.test.ts b/packages/sdk/src/nodes/supported/InvArchTinker.test.ts index 01c63e0d..c0370c46 100644 --- a/packages/sdk/src/nodes/supported/InvArchTinker.test.ts +++ b/packages/sdk/src/nodes/supported/InvArchTinker.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('InvArchTinker', () => { let invArchTinker: InvArchTinker const mockInput = { - currencyID: '123', + asset: { symbol: 'TNKR', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('InvArchTinker', () => { invArchTinker.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123)) }) }) diff --git a/packages/sdk/src/nodes/supported/InvArchTinker.ts b/packages/sdk/src/nodes/supported/InvArchTinker.ts index 00fe33b6..250475b5 100644 --- a/packages/sdk/src/nodes/supported/InvArchTinker.ts +++ b/packages/sdk/src/nodes/supported/InvArchTinker.ts @@ -1,6 +1,8 @@ // Contains detailed structure of XCM call construction for InvArchTinker Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,8 +12,13 @@ class InvArchTinker extends ParachainNode implements IXT } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } } diff --git a/packages/sdk/src/nodes/supported/Khala.test.ts b/packages/sdk/src/nodes/supported/Khala.test.ts index 1b1f0070..e47e0354 100644 --- a/packages/sdk/src/nodes/supported/Khala.test.ts +++ b/packages/sdk/src/nodes/supported/Khala.test.ts @@ -17,7 +17,7 @@ vi.mock('../xTransfer', () => ({ describe('Khala', () => { let khala: Khala const mockInput = { - currency: 'PHA', + asset: { symbol: 'PHA' }, amount: '100' } as XTransferTransferInput @@ -41,10 +41,8 @@ describe('Khala', () => { }) it('should throw InvalidCurrencyError for unsupported currency', () => { - const invalidInput = { ...mockInput, currency: 'INVALID' } + const invalidInput = { ...mockInput, asset: { symbol: 'INVALID' } } - expect(() => khala.transferXTransfer(invalidInput)).toThrowError( - new InvalidCurrencyError(`Node Khala does not support currency INVALID`) - ) + expect(() => khala.transferXTransfer(invalidInput)).toThrowError(InvalidCurrencyError) }) }) diff --git a/packages/sdk/src/nodes/supported/Khala.ts b/packages/sdk/src/nodes/supported/Khala.ts index ddfddde5..f6112bc0 100644 --- a/packages/sdk/src/nodes/supported/Khala.ts +++ b/packages/sdk/src/nodes/supported/Khala.ts @@ -9,9 +9,9 @@ class Khala extends ParachainNode implements IXTransferT } transferXTransfer(input: XTransferTransferInput) { - const { currency } = input - if (currency !== 'PHA') { - throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${currency}`) + const { asset } = input + if (asset.symbol !== 'PHA') { + throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${asset.symbol}`) } return XTransferTransferImpl.transferXTransfer(input) } diff --git a/packages/sdk/src/nodes/supported/KiltSpiritnet.test.ts b/packages/sdk/src/nodes/supported/KiltSpiritnet.test.ts index f391b16a..3f33460e 100644 --- a/packages/sdk/src/nodes/supported/KiltSpiritnet.test.ts +++ b/packages/sdk/src/nodes/supported/KiltSpiritnet.test.ts @@ -18,7 +18,9 @@ describe('KiltSpiritnet', () => { let kiltSpiritnet: KiltSpiritnet const mockInput = { scenario: 'ParaToPara', - currencySymbol: 'KILT', + asset: { + symbol: 'KILT' + }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Kintsugi.test.ts b/packages/sdk/src/nodes/supported/Kintsugi.test.ts index c78863f1..1c8d51a4 100644 --- a/packages/sdk/src/nodes/supported/Kintsugi.test.ts +++ b/packages/sdk/src/nodes/supported/Kintsugi.test.ts @@ -16,8 +16,10 @@ vi.mock('../xTokens', () => ({ describe('Kintsugi', () => { let kintsugi: Kintsugi const mockInput = { - currency: 'KINT', - currencyID: '123', + asset: { + symbol: 'KINT', + assetId: '123' + }, amount: '100' } as XTokensTransferInput @@ -42,7 +44,12 @@ describe('Kintsugi', () => { it('should call transferXTokens with Token when currencyID is undefined', () => { const spy = vi.spyOn(XTokensTransferImpl, 'transferXTokens') - const inputWithoutCurrencyID = { ...mockInput, currencyID: undefined } + const inputWithoutCurrencyID = { + ...mockInput, + asset: { + symbol: 'KINT' + } + } kintsugi.transferXTokens(inputWithoutCurrencyID) diff --git a/packages/sdk/src/nodes/supported/Kintsugi.ts b/packages/sdk/src/nodes/supported/Kintsugi.ts index 53774089..02febe27 100644 --- a/packages/sdk/src/nodes/supported/Kintsugi.ts +++ b/packages/sdk/src/nodes/supported/Kintsugi.ts @@ -6,6 +6,7 @@ import { type XTokensTransferInput, type TForeignOrTokenAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,9 +16,10 @@ class Kintsugi extends ParachainNode implements IXTokens } transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection: TForeignOrTokenAsset = - currencyID !== undefined ? { ForeignAsset: Number(currencyID) } : { Token: currency } + const { asset } = input + const currencySelection: TForeignOrTokenAsset = isForeignAsset(asset) + ? { ForeignAsset: Number(asset.assetId) } + : { Token: asset.symbol } return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/Litentry.test.ts b/packages/sdk/src/nodes/supported/Litentry.test.ts index 3071a931..f637fce6 100644 --- a/packages/sdk/src/nodes/supported/Litentry.test.ts +++ b/packages/sdk/src/nodes/supported/Litentry.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Litentry', () => { let litentry: Litentry const mockInput = { - currencyID: '123', + asset: { assetId: '123' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Manta.test.ts b/packages/sdk/src/nodes/supported/Manta.test.ts index 26decd76..caf2c6d8 100644 --- a/packages/sdk/src/nodes/supported/Manta.test.ts +++ b/packages/sdk/src/nodes/supported/Manta.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Manta', () => { let manta: Manta const mockInput = { - currencyID: '123', + asset: { assetId: '123' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Manta.ts b/packages/sdk/src/nodes/supported/Manta.ts index f3023b52..ed63b74e 100644 --- a/packages/sdk/src/nodes/supported/Manta.ts +++ b/packages/sdk/src/nodes/supported/Manta.ts @@ -1,11 +1,13 @@ // Contains detailed structure of XCM call construction for Manta Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TMantaAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,9 +17,14 @@ class Manta extends ParachainNode implements IXTokensTra } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + const currencySelection: TMantaAsset = { - MantaCurrency: currencyID ? BigInt(currencyID) : undefined + MantaCurrency: BigInt(asset.assetId) } return XTokensTransferImpl.transferXTokens(input, currencySelection) } diff --git a/packages/sdk/src/nodes/supported/Moonbeam.test.ts b/packages/sdk/src/nodes/supported/Moonbeam.test.ts index e43c6da5..bec13f37 100644 --- a/packages/sdk/src/nodes/supported/Moonbeam.test.ts +++ b/packages/sdk/src/nodes/supported/Moonbeam.test.ts @@ -26,8 +26,10 @@ vi.mock('../../utils/getAllNodeProviders', () => ({ describe('Moonbeam', () => { let moonbeam: Moonbeam const mockInput = { - currency: 'GLMR', - currencyID: '123', + asset: { + symbol: 'GLMR', + assetId: '123' + }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Moonbeam.ts b/packages/sdk/src/nodes/supported/Moonbeam.ts index b02733d0..5e14fb7c 100644 --- a/packages/sdk/src/nodes/supported/Moonbeam.ts +++ b/packages/sdk/src/nodes/supported/Moonbeam.ts @@ -1,7 +1,8 @@ // Contains detailed structure of XCM call construction for Moonbeam Parachain +import { InvalidCurrencyError } from '../../errors' import { constructRelayToParaParameters } from '../../pallets/xcmPallet/utils' -import type { TSerializedApiCallV2 } from '../../types' +import type { TAsset, TSerializedApiCallV2 } from '../../types' import { type IXTokensTransfer, Version, @@ -9,9 +10,10 @@ import { type TRelayToParaOptions, type TNodePolkadotKusama, type TSelfReserveAsset, - type TForeignAsset + type TXcmForeignAsset } from '../../types' import { getAllNodeProviders } from '../../utils' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -20,12 +22,19 @@ class Moonbeam extends ParachainNode implements IXTokens super('Moonbeam', 'moonbeam', 'polkadot', Version.V3) } + private getCurrencySelection(asset: TAsset): TSelfReserveAsset | TXcmForeignAsset { + if (asset.symbol === this.getNativeAssetSymbol()) return 'SelfReserve' + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { ForeignAsset: BigInt(asset.assetId) } + } + transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection: TSelfReserveAsset | TForeignAsset = - currency === this.getNativeAssetSymbol() - ? 'SelfReserve' - : { ForeignAsset: currencyID ? BigInt(currencyID) : undefined } + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } diff --git a/packages/sdk/src/nodes/supported/Moonriver.test.ts b/packages/sdk/src/nodes/supported/Moonriver.test.ts index 11785a8d..4907c03d 100644 --- a/packages/sdk/src/nodes/supported/Moonriver.test.ts +++ b/packages/sdk/src/nodes/supported/Moonriver.test.ts @@ -21,8 +21,7 @@ vi.mock('../../pallets/xcmPallet/utils', () => ({ describe('Moonriver', () => { let moonriver: Moonriver const mockInput = { - currency: 'MOVR', - currencyID: '123', + asset: { symbol: 'MOVR', assetId: '123' }, amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Moonriver.ts b/packages/sdk/src/nodes/supported/Moonriver.ts index 38003063..7f1b3f59 100644 --- a/packages/sdk/src/nodes/supported/Moonriver.ts +++ b/packages/sdk/src/nodes/supported/Moonriver.ts @@ -1,17 +1,19 @@ // Contains detailed structure of XCM call construction for Moonriver Parachain +import { InvalidCurrencyError } from '../../errors' import { constructRelayToParaParameters } from '../../pallets/xcmPallet/utils' -import type { TNodePolkadotKusama } from '../../types' +import type { TAsset, TNodePolkadotKusama } from '../../types' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TSerializedApiCallV2, type TRelayToParaOptions, - type TForeignAsset, + type TXcmForeignAsset, type TSelfReserveAsset } from '../../types' import { getAllNodeProviders } from '../../utils' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -20,12 +22,19 @@ class Moonriver extends ParachainNode implements IXToken super('Moonriver', 'moonriver', 'kusama', Version.V3) } + private getCurrencySelection(asset: TAsset): TSelfReserveAsset | TXcmForeignAsset { + if (asset.symbol === this.getNativeAssetSymbol()) return 'SelfReserve' + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { ForeignAsset: BigInt(asset.assetId) } + } + transferXTokens(input: XTokensTransferInput) { - const { currency, currencyID } = input - const currencySelection: TSelfReserveAsset | TForeignAsset = - currency === this.getNativeAssetSymbol() - ? 'SelfReserve' - : { ForeignAsset: currencyID ? BigInt(currencyID) : undefined } + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } diff --git a/packages/sdk/src/nodes/supported/Mythos.test.ts b/packages/sdk/src/nodes/supported/Mythos.test.ts index 17714842..05b2eb52 100644 --- a/packages/sdk/src/nodes/supported/Mythos.test.ts +++ b/packages/sdk/src/nodes/supported/Mythos.test.ts @@ -21,7 +21,7 @@ vi.mock('../polkadotXcm', () => ({ describe('Mythos', () => { let mythos: Mythos const mockInput = { - currencySymbol: 'MYTH', + asset: { symbol: 'MYTH' }, scenario: 'ParaToPara', destination: 'Acala', amount: '100' diff --git a/packages/sdk/src/nodes/supported/Mythos.ts b/packages/sdk/src/nodes/supported/Mythos.ts index 90663d21..c5ba5a7e 100644 --- a/packages/sdk/src/nodes/supported/Mythos.ts +++ b/packages/sdk/src/nodes/supported/Mythos.ts @@ -23,16 +23,14 @@ class Mythos extends ParachainNode implements IPolkadotX transferPolkadotXCM( input: PolkadotXCMTransferInput ): Promise> { - const { scenario, currencySymbol, destination } = input + const { scenario, asset, destination } = input if (scenario !== 'ParaToPara') { throw new ScenarioNotSupportedError(this.node, scenario) } const nativeSymbol = this.getNativeAssetSymbol() - if (currencySymbol !== nativeSymbol) { - throw new InvalidCurrencyError( - `Node ${this.node} does not support currency ${currencySymbol}` - ) + if (asset.symbol !== nativeSymbol) { + throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${asset.symbol}`) } return Promise.resolve( diff --git a/packages/sdk/src/nodes/supported/NeuroWeb.test.ts b/packages/sdk/src/nodes/supported/NeuroWeb.test.ts index 968b1557..635b0109 100644 --- a/packages/sdk/src/nodes/supported/NeuroWeb.test.ts +++ b/packages/sdk/src/nodes/supported/NeuroWeb.test.ts @@ -16,7 +16,7 @@ vi.mock('../polkadotXcm', () => ({ describe('NeuroWeb', () => { let neuroweb: NeuroWeb const mockInput = { - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, amount: '100' } as PolkadotXCMTransferInput diff --git a/packages/sdk/src/nodes/supported/Nodle.test.ts b/packages/sdk/src/nodes/supported/Nodle.test.ts index 75999051..dec2afa6 100644 --- a/packages/sdk/src/nodes/supported/Nodle.test.ts +++ b/packages/sdk/src/nodes/supported/Nodle.test.ts @@ -1,30 +1,25 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import { - InvalidCurrencyError, - NodeNotSupportedError, - ScenarioNotSupportedError -} from '../../errors' -import type { XTokensTransferInput, TNodleAsset } from '../../types' +import type { PolkadotXCMTransferInput } from '../../types' import { Version } from '../../types' -import XTokensTransferImpl from '../xTokens' -import type Nodle from './Nodle' -import { getNode } from '../../utils/getNode' +import PolkadotXCMTransferImpl from '../polkadotXcm' +import { getNode } from '../../utils' import type { ApiPromise } from '@polkadot/api' import type { Extrinsic } from '../../pjs/types' +import type Nodle from './Nodle' -vi.mock('../xTokens', () => ({ +vi.mock('../polkadotXcm', () => ({ default: { - transferXTokens: vi.fn() + transferPolkadotXCM: vi.fn() } })) describe('Nodle', () => { let nodle: Nodle const mockInput = { - currency: 'NODL', - scenario: 'ParaToPara', - amount: '100' - } as XTokensTransferInput + asset: { symbol: 'NODL' }, + amount: '100', + scenario: 'ParaToPara' + } as PolkadotXCMTransferInput beforeEach(() => { nodle = getNode('Nodle') @@ -37,33 +32,11 @@ describe('Nodle', () => { expect(nodle.version).toBe(Version.V3) }) - it('should call transferXTokens with valid scenario and currency', () => { - const spy = vi.spyOn(XTokensTransferImpl, 'transferXTokens') - vi.spyOn(nodle, 'getNativeAssetSymbol').mockReturnValue('NODL') - - nodle.transferXTokens(mockInput) - - expect(spy).toHaveBeenCalledWith(mockInput, 'NodleNative' as TNodleAsset) - }) - - it('should throw ScenarioNotSupportedError for unsupported scenario', () => { - const invalidInput = { ...mockInput, scenario: 'ParaToRelay' } as XTokensTransferInput< - ApiPromise, - Extrinsic - > - - expect(() => nodle.transferXTokens(invalidInput)).toThrowError(ScenarioNotSupportedError) - }) + it('should call transferPolkadotXCM with the correct arguments', async () => { + const spy = vi.spyOn(PolkadotXCMTransferImpl, 'transferPolkadotXCM') - it('should throw InvalidCurrencyError for unsupported currency', () => { - vi.spyOn(nodle, 'getNativeAssetSymbol').mockReturnValue('NOT_NODL') - - expect(() => nodle.transferXTokens(mockInput)).toThrowError( - new InvalidCurrencyError(`Asset NODL is not supported by node Nodle.`) - ) - }) + await nodle.transferPolkadotXCM(mockInput) - it('should throw NodeNotSupportedError for transferRelayToPara', () => { - expect(() => nodle.transferRelayToPara()).toThrowError(NodeNotSupportedError) + expect(spy).toHaveBeenCalledWith(mockInput, 'limited_reserve_transfer_assets', 'Unlimited') }) }) diff --git a/packages/sdk/src/nodes/supported/Nodle.ts b/packages/sdk/src/nodes/supported/Nodle.ts index 1504c99e..68dee550 100644 --- a/packages/sdk/src/nodes/supported/Nodle.ts +++ b/packages/sdk/src/nodes/supported/Nodle.ts @@ -5,35 +5,36 @@ import { NodeNotSupportedError, ScenarioNotSupportedError } from '../../errors' -import { - type IXTokensTransfer, - Version, - type XTokensTransferInput, - type TSerializedApiCallV2, - type TNodleAsset -} from '../../types' +import type { IPolkadotXCMTransfer, PolkadotXCMTransferInput, TTransferReturn } from '../../types' +import { Version, type TSerializedApiCallV2 } from '../../types' import ParachainNode from '../ParachainNode' -import XTokensTransferImpl from '../xTokens' +import PolkadotXCMTransferImpl from '../polkadotXcm' -class Nodle extends ParachainNode implements IXTokensTransfer { +class Nodle extends ParachainNode implements IPolkadotXCMTransfer { constructor() { super('Nodle', 'nodle', 'polkadot', Version.V3) } - transferXTokens(input: XTokensTransferInput) { - if (input.scenario !== 'ParaToPara') { - throw new ScenarioNotSupportedError(this.node, input.scenario) - } + transferPolkadotXCM( + input: PolkadotXCMTransferInput + ): Promise> { + const { asset, scenario } = input - if (input.currency !== this.getNativeAssetSymbol()) { - throw new InvalidCurrencyError( - `Asset ${input.currency} is not supported by node ${this.node}.` - ) + if (scenario !== 'ParaToPara') { + throw new ScenarioNotSupportedError(this.node, scenario) } - const currencySelection: TNodleAsset = 'NodleNative' + if (asset.symbol !== this.getNativeAssetSymbol()) { + throw new InvalidCurrencyError(`Asset ${asset.symbol} is not supported by node ${this.node}.`) + } - return XTokensTransferImpl.transferXTokens(input, currencySelection) + return Promise.resolve( + PolkadotXCMTransferImpl.transferPolkadotXCM( + input, + 'limited_reserve_transfer_assets', + 'Unlimited' + ) + ) } transferRelayToPara(): TSerializedApiCallV2 { diff --git a/packages/sdk/src/nodes/supported/Parallel.test.ts b/packages/sdk/src/nodes/supported/Parallel.test.ts index 7b3cd775..f6935a80 100644 --- a/packages/sdk/src/nodes/supported/Parallel.test.ts +++ b/packages/sdk/src/nodes/supported/Parallel.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Parallel', () => { let parallel: Parallel const mockInput = { - currencyID: '123', + asset: { symbol: 'PARA', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('Parallel', () => { parallel.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123)) }) }) diff --git a/packages/sdk/src/nodes/supported/Parallel.ts b/packages/sdk/src/nodes/supported/Parallel.ts index 45b6bc4e..39171f8f 100644 --- a/packages/sdk/src/nodes/supported/Parallel.ts +++ b/packages/sdk/src/nodes/supported/Parallel.ts @@ -1,6 +1,8 @@ // Contains detailed structure of XCM call construction for Parallel Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,8 +12,13 @@ class Parallel extends ParachainNode implements IXTokens } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } } diff --git a/packages/sdk/src/nodes/supported/Peaq.test.ts b/packages/sdk/src/nodes/supported/Peaq.test.ts index b66bbf23..45dd55fa 100644 --- a/packages/sdk/src/nodes/supported/Peaq.test.ts +++ b/packages/sdk/src/nodes/supported/Peaq.test.ts @@ -17,7 +17,7 @@ vi.mock('../xTokens', () => ({ describe('Peaq', () => { let peaq: Peaq const mockInput = { - currencyID: '123', + asset: { assetId: '123' }, scenario: 'ParaToPara', amount: '100' } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Peaq.ts b/packages/sdk/src/nodes/supported/Peaq.ts index b6711d5b..455f469c 100644 --- a/packages/sdk/src/nodes/supported/Peaq.ts +++ b/packages/sdk/src/nodes/supported/Peaq.ts @@ -1,12 +1,17 @@ // Contains detailed structure of XCM call construction for Peaq Parachain -import { NodeNotSupportedError, ScenarioNotSupportedError } from '../../errors' +import { + InvalidCurrencyError, + NodeNotSupportedError, + ScenarioNotSupportedError +} from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TSerializedApiCallV2 } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -16,11 +21,16 @@ class Peaq extends ParachainNode implements IXTokensTran } transferXTokens(input: XTokensTransferInput) { - const { scenario, currencyID } = input + const { scenario, asset } = input if (scenario !== 'ParaToPara') { throw new ScenarioNotSupportedError(this.node, scenario) } - return XTokensTransferImpl.transferXTokens(input, currencyID ? BigInt(currencyID) : undefined) + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } transferRelayToPara(): TSerializedApiCallV2 { diff --git a/packages/sdk/src/nodes/supported/Pendulum.test.ts b/packages/sdk/src/nodes/supported/Pendulum.test.ts index a3689949..b9468a2a 100644 --- a/packages/sdk/src/nodes/supported/Pendulum.test.ts +++ b/packages/sdk/src/nodes/supported/Pendulum.test.ts @@ -21,8 +21,7 @@ vi.mock('../xTokens', () => ({ describe('Pendulum', () => { let pendulum: Pendulum const mockInput = { - currency: 'PEN', - currencyID: '123', + asset: { symbol: 'PEN', assetId: '123' }, scenario: 'ParaToPara', amount: '100' } as XTokensTransferInput @@ -44,7 +43,7 @@ describe('Pendulum', () => { pendulum.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { XCM: '123' } as TXcmAsset) + expect(spy).toHaveBeenCalledWith(mockInput, { XCM: 123 } as TXcmAsset) }) it('should throw ScenarioNotSupportedError for unsupported scenario', () => { diff --git a/packages/sdk/src/nodes/supported/Pendulum.ts b/packages/sdk/src/nodes/supported/Pendulum.ts index 86b2c2ca..afa38c2e 100644 --- a/packages/sdk/src/nodes/supported/Pendulum.ts +++ b/packages/sdk/src/nodes/supported/Pendulum.ts @@ -5,13 +5,9 @@ import { NodeNotSupportedError, ScenarioNotSupportedError } from '../../errors' -import { - type IXTokensTransfer, - Version, - type XTokensTransferInput, - type TSerializedApiCallV2, - type TXcmAsset -} from '../../types' +import type { IXTokensTransfer, TXcmAsset, XTokensTransferInput } from '../../types' +import { Version, type TSerializedApiCallV2 } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -21,17 +17,21 @@ class Pendulum extends ParachainNode implements IXTokens } transferXTokens(input: XTokensTransferInput) { - if (input.scenario !== 'ParaToPara') { + const { scenario, asset } = input + + if (scenario !== 'ParaToPara') { throw new ScenarioNotSupportedError(this.node, input.scenario) } - if (input.currency !== this.getNativeAssetSymbol()) { - throw new InvalidCurrencyError( - `Asset ${input.currency} is not supported by node ${this.node}.` - ) + if (asset.symbol !== this.getNativeAssetSymbol()) { + throw new InvalidCurrencyError(`Asset ${asset.symbol} is not supported by node ${this.node}.`) + } + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) } - const currencySelection: TXcmAsset = { XCM: input.currencyID } + const currencySelection: TXcmAsset = { XCM: Number(asset.assetId) } return XTokensTransferImpl.transferXTokens(input, currencySelection) } diff --git a/packages/sdk/src/nodes/supported/Phala.test.ts b/packages/sdk/src/nodes/supported/Phala.test.ts index bd09ba0b..f65c933a 100644 --- a/packages/sdk/src/nodes/supported/Phala.test.ts +++ b/packages/sdk/src/nodes/supported/Phala.test.ts @@ -17,7 +17,7 @@ vi.mock('../xTransfer', () => ({ describe('Phala', () => { let phala: Phala const mockInput = { - currency: 'PHA', + asset: { symbol: 'PHA' }, amount: '100' } as XTransferTransferInput diff --git a/packages/sdk/src/nodes/supported/Phala.ts b/packages/sdk/src/nodes/supported/Phala.ts index ad42e96e..562b2e8c 100644 --- a/packages/sdk/src/nodes/supported/Phala.ts +++ b/packages/sdk/src/nodes/supported/Phala.ts @@ -9,9 +9,9 @@ class Phala extends ParachainNode implements IXTransferT } transferXTransfer(input: XTransferTransferInput) { - const { currency } = input - if (currency !== this.getNativeAssetSymbol()) { - throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${currency}`) + const { asset } = input + if (asset.symbol !== this.getNativeAssetSymbol()) { + throw new InvalidCurrencyError(`Node ${this.node} does not support currency ${asset.symbol}`) } return XTransferTransferImpl.transferXTransfer(input) } diff --git a/packages/sdk/src/nodes/supported/Picasso.test.ts b/packages/sdk/src/nodes/supported/Picasso.test.ts index ed711931..cc2b6db1 100644 --- a/packages/sdk/src/nodes/supported/Picasso.test.ts +++ b/packages/sdk/src/nodes/supported/Picasso.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Picasso', () => { let picasso: Picasso const mockInput = { - currencyID: '123', + asset: { symbol: 'PIC', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('Picasso', () => { picasso.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123)) }) }) diff --git a/packages/sdk/src/nodes/supported/Picasso.ts b/packages/sdk/src/nodes/supported/Picasso.ts index 2c0300ac..b80ade3f 100644 --- a/packages/sdk/src/nodes/supported/Picasso.ts +++ b/packages/sdk/src/nodes/supported/Picasso.ts @@ -1,6 +1,8 @@ // Contains detailed structure of XCM call construction for Picasso Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,8 +12,13 @@ class Picasso extends ParachainNode implements IXTokensT } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } } diff --git a/packages/sdk/src/nodes/supported/Pioneer.test.ts b/packages/sdk/src/nodes/supported/Pioneer.test.ts index 4fc0e071..febe38c4 100644 --- a/packages/sdk/src/nodes/supported/Pioneer.test.ts +++ b/packages/sdk/src/nodes/supported/Pioneer.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Pioneer', () => { let pioneer: Pioneer const mockInput = { - currencyID: '123', + asset: { assetId: '123' }, amount: '100', fees: 0.01 } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/supported/Polkadex.test.ts b/packages/sdk/src/nodes/supported/Polkadex.test.ts index d61a3b71..92839096 100644 --- a/packages/sdk/src/nodes/supported/Polkadex.test.ts +++ b/packages/sdk/src/nodes/supported/Polkadex.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Polkadex', () => { let polkadex: Polkadex const mockInput = { - currencyID: '123', + asset: { symbol: 'PDEX', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('Polkadex', () => { polkadex.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123)) }) }) diff --git a/packages/sdk/src/nodes/supported/Polkadex.ts b/packages/sdk/src/nodes/supported/Polkadex.ts index 72b2d2ab..41e587f5 100644 --- a/packages/sdk/src/nodes/supported/Polkadex.ts +++ b/packages/sdk/src/nodes/supported/Polkadex.ts @@ -1,6 +1,8 @@ // Contains detailed structure of XCM call construction for Polkadex Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,8 +12,13 @@ class Polkadex extends ParachainNode implements IXTokens } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } } diff --git a/packages/sdk/src/nodes/supported/Quartz.test.ts b/packages/sdk/src/nodes/supported/Quartz.test.ts index 515563c0..3350f8da 100644 --- a/packages/sdk/src/nodes/supported/Quartz.test.ts +++ b/packages/sdk/src/nodes/supported/Quartz.test.ts @@ -16,9 +16,8 @@ vi.mock('../xTokens', () => ({ describe('Quartz', () => { let quartz: Quartz const mockInput = { - currency: 'QTZ', - amount: '100', - currencyID: '123' + asset: { symbol: 'QTZ', assetId: '123' }, + amount: '100' } as XTokensTransferInput beforeEach(() => { @@ -38,6 +37,6 @@ describe('Quartz', () => { quartz.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { ForeignAssetId: '123' }) + expect(spy).toHaveBeenCalledWith(mockInput, { ForeignAssetId: BigInt(123) }) }) }) diff --git a/packages/sdk/src/nodes/supported/Shiden.ts b/packages/sdk/src/nodes/supported/Shiden.ts index 9861ee45..2373b6a4 100644 --- a/packages/sdk/src/nodes/supported/Shiden.ts +++ b/packages/sdk/src/nodes/supported/Shiden.ts @@ -10,6 +10,7 @@ import { type XTokensTransferInput } from '../../types' import { getNode } from '../../utils' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' class Shiden @@ -32,11 +33,8 @@ class Shiden return getNode('Astar').transferXTokens(input) } - protected canUseXTokens({ - currencySymbol, - currencyId - }: TSendInternalOptions): boolean { - return currencySymbol !== this.getNativeAssetSymbol() || !!currencyId + protected canUseXTokens({ asset }: TSendInternalOptions): boolean { + return asset.symbol !== this.getNativeAssetSymbol() || isForeignAsset(asset) } } diff --git a/packages/sdk/src/nodes/supported/Subsocial.test.ts b/packages/sdk/src/nodes/supported/Subsocial.test.ts index a7165500..0ec94b2c 100644 --- a/packages/sdk/src/nodes/supported/Subsocial.test.ts +++ b/packages/sdk/src/nodes/supported/Subsocial.test.ts @@ -35,18 +35,18 @@ describe('Subsocial', () => { }) it('should only support native currency', () => { - const input = { scenario: 'ParaToPara', currencySymbol: 'XYZ' } as PolkadotXCMTransferInput< - ApiPromise, - Extrinsic - > + const input = { + scenario: 'ParaToPara', + asset: { symbol: 'XYZ' } + } as PolkadotXCMTransferInput expect(() => subsocial.transferPolkadotXCM(input)).toThrow(InvalidCurrencyError) }) it('should use limitedReserveTransferAssets when scenario is ParaToPara', async () => { - const input = { scenario: 'ParaToPara', currencySymbol: 'SUB' } as PolkadotXCMTransferInput< - ApiPromise, - Extrinsic - > + const input = { + scenario: 'ParaToPara', + asset: { symbol: 'SUB' } + } as PolkadotXCMTransferInput const spy = vi.spyOn(PolkadotXCMTransferImpl, 'transferPolkadotXCM') diff --git a/packages/sdk/src/nodes/supported/Subsocial.ts b/packages/sdk/src/nodes/supported/Subsocial.ts index 230bf4ec..293843df 100644 --- a/packages/sdk/src/nodes/supported/Subsocial.ts +++ b/packages/sdk/src/nodes/supported/Subsocial.ts @@ -14,16 +14,14 @@ class Subsocial extends ParachainNode implements IPolkad transferPolkadotXCM( input: PolkadotXCMTransferInput ): Promise> { - const { scenario, currencySymbol } = input + const { scenario, asset } = input if (scenario !== 'ParaToPara') { throw new ScenarioNotSupportedError(this.node, scenario) } - if (currencySymbol !== this.getNativeAssetSymbol()) { - throw new InvalidCurrencyError( - `Asset ${currencySymbol} is not supported by node ${this.node}.` - ) + if (asset.symbol !== this.getNativeAssetSymbol()) { + throw new InvalidCurrencyError(`Asset ${asset.symbol} is not supported by node ${this.node}.`) } return Promise.resolve( PolkadotXCMTransferImpl.transferPolkadotXCM( diff --git a/packages/sdk/src/nodes/supported/Turing.test.ts b/packages/sdk/src/nodes/supported/Turing.test.ts index e2b22477..1ec79152 100644 --- a/packages/sdk/src/nodes/supported/Turing.test.ts +++ b/packages/sdk/src/nodes/supported/Turing.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Turing', () => { let turing: Turing const mockInput = { - currencyID: '123', + asset: { symbol: 'TUR', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('Turing', () => { turing.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, '123') + expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123)) }) }) diff --git a/packages/sdk/src/nodes/supported/Turing.ts b/packages/sdk/src/nodes/supported/Turing.ts index 8229f70b..d4d2988a 100644 --- a/packages/sdk/src/nodes/supported/Turing.ts +++ b/packages/sdk/src/nodes/supported/Turing.ts @@ -1,6 +1,8 @@ // Contains detailed structure of XCM call construction for Turing Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -10,8 +12,13 @@ class Turing extends ParachainNode implements IXTokensTr } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - return XTokensTransferImpl.transferXTokens(input, currencyID) + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return XTokensTransferImpl.transferXTokens(input, BigInt(asset.assetId)) } } diff --git a/packages/sdk/src/nodes/supported/Unique.test.ts b/packages/sdk/src/nodes/supported/Unique.test.ts index abedfbcf..52359570 100644 --- a/packages/sdk/src/nodes/supported/Unique.test.ts +++ b/packages/sdk/src/nodes/supported/Unique.test.ts @@ -16,7 +16,7 @@ vi.mock('../xTokens', () => ({ describe('Unique', () => { let unique: Unique const mockInput = { - currencyID: '123', + asset: { symbol: 'UNQ', assetId: '123' }, amount: '100' } as XTokensTransferInput @@ -36,6 +36,6 @@ describe('Unique', () => { unique.transferXTokens(mockInput) - expect(spy).toHaveBeenCalledWith(mockInput, { ForeignAssetId: '123' }) + expect(spy).toHaveBeenCalledWith(mockInput, { ForeignAssetId: BigInt(123) }) }) }) diff --git a/packages/sdk/src/nodes/supported/Unique.ts b/packages/sdk/src/nodes/supported/Unique.ts index 6ec684d3..ae024d4d 100644 --- a/packages/sdk/src/nodes/supported/Unique.ts +++ b/packages/sdk/src/nodes/supported/Unique.ts @@ -1,11 +1,13 @@ // Contains detailed structure of XCM call construction for Unique Parachain +import { InvalidCurrencyError } from '../../errors' import { type IXTokensTransfer, Version, type XTokensTransferInput, type TForeignAssetId } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,8 +17,13 @@ class Unique extends ParachainNode implements IXTokensTr } transferXTokens(input: XTokensTransferInput) { - const { currencyID } = input - const currencySelection: TForeignAssetId = { ForeignAssetId: currencyID } + const { asset } = input + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + const currencySelection: TForeignAssetId = { ForeignAssetId: BigInt(asset.assetId) } return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/supported/Zeitgeist.test.ts b/packages/sdk/src/nodes/supported/Zeitgeist.test.ts index 64f6e23d..41628ad6 100644 --- a/packages/sdk/src/nodes/supported/Zeitgeist.test.ts +++ b/packages/sdk/src/nodes/supported/Zeitgeist.test.ts @@ -16,9 +16,8 @@ vi.mock('../xTokens', () => ({ describe('Zeitgeist', () => { let zeitgeist: Zeitgeist const mockInput = { - currency: 'ZTG', - amount: '100', - currencyID: '123' + asset: { symbol: 'ZTG', assetId: '123' }, + amount: '100' } as XTokensTransferInput beforeEach(() => { diff --git a/packages/sdk/src/nodes/supported/Zeitgeist.ts b/packages/sdk/src/nodes/supported/Zeitgeist.ts index 670ef135..63ad0422 100644 --- a/packages/sdk/src/nodes/supported/Zeitgeist.ts +++ b/packages/sdk/src/nodes/supported/Zeitgeist.ts @@ -1,12 +1,15 @@ // Contains detailed structure of XCM call construction for Zeitgeist Parachain +import { InvalidCurrencyError } from '../../errors' +import type { TAsset } from '../../types' import { type IXTokensTransfer, Version, type XTokensTransferInput, - type TForeignAsset, + type TXcmForeignAsset, type TZeitgeistAsset } from '../../types' +import { isForeignAsset } from '../../utils/assets' import ParachainNode from '../ParachainNode' import XTokensTransferImpl from '../xTokens' @@ -15,11 +18,19 @@ class Zeitgeist extends ParachainNode implements IXToken super('Zeitgeist', 'zeitgeist', 'polkadot', Version.V3) } + private getCurrencySelection(asset: TAsset): TZeitgeistAsset | TXcmForeignAsset { + if (asset.symbol === this.getNativeAssetSymbol()) return 'Ztg' + + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + + return { ForeignAsset: Number(asset.assetId) } + } + transferXTokens(input: XTokensTransferInput) { - const currencySelection: TZeitgeistAsset | TForeignAsset = - input.currency === this.getNativeAssetSymbol() - ? 'Ztg' - : { ForeignAsset: Number(input.currencyID) } + const { asset } = input + const currencySelection = this.getCurrencySelection(asset) return XTokensTransferImpl.transferXTokens(input, currencySelection) } } diff --git a/packages/sdk/src/nodes/xTokens/XTokensTransferImpl.test.ts b/packages/sdk/src/nodes/xTokens/XTokensTransferImpl.test.ts index 06a14b34..90359c21 100644 --- a/packages/sdk/src/nodes/xTokens/XTokensTransferImpl.test.ts +++ b/packages/sdk/src/nodes/xTokens/XTokensTransferImpl.test.ts @@ -30,8 +30,10 @@ describe('XTokensTransferImpl', () => { const input = { api: mockApi, origin: 'Acala', - currency: '123', - currencyID: '123', + asset: { + symbol: 'ACA', + assetId: '123' + }, fees: 1000, amount: '1000', addressSelection: 'Address', @@ -51,8 +53,10 @@ describe('XTokensTransferImpl', () => { api: mockApi, origin: 'Acala', amount: '2000', - currency: '123', - currencyID: '123', + asset: { + symbol: 'ACA', + assetId: '123' + }, fees: 2000, scenario: 'ParaToPara', addressSelection: 'Address', @@ -83,8 +87,10 @@ describe('XTokensTransferImpl', () => { api: mockApi, origin: 'Acala', amount: '2000', - currency: '123', - currencyID: '123', + asset: { + symbol: 'ACA', + assetId: '123' + }, fees: 2000, scenario: 'ParaToPara', addressSelection: 'Address', @@ -113,8 +119,10 @@ describe('XTokensTransferImpl', () => { api: mockApi, origin: 'Acala', amount: '3000', - currency: '123', - currencyID: '123', + asset: { + symbol: 'ACA', + assetId: '123' + }, fees: 3000, scenario: 'ParaToPara', addressSelection: 'Address', diff --git a/packages/sdk/src/nodes/xTokens/getCurrencySelection.test.ts b/packages/sdk/src/nodes/xTokens/getCurrencySelection.test.ts index 6a7cd7d0..e9d61c41 100644 --- a/packages/sdk/src/nodes/xTokens/getCurrencySelection.test.ts +++ b/packages/sdk/src/nodes/xTokens/getCurrencySelection.test.ts @@ -37,7 +37,9 @@ describe('getCurrencySelection', () => { const input = { origin: 'Acala', amount: '1000', - currencyID: '123', + asset: { + assetId: '123' + }, paraIdTo: 2000, overridedCurrencyMultiLocation: { parents: Parents.ZERO, @@ -60,7 +62,9 @@ describe('getCurrencySelection', () => { const input = { origin: 'Acala', amount: '2000', - currencyID: '123', + asset: { + assetId: '123' + }, paraIdTo: 1000, overridedCurrencyMultiLocation: undefined } as XTokensTransferInput @@ -81,7 +85,9 @@ describe('getCurrencySelection', () => { const input = { origin: 'Acala', amount: '3000', - currencyID: '123', + asset: { + assetId: '123' + }, paraIdTo: 3000, overridedCurrencyMultiLocation: undefined } as XTokensTransferInput diff --git a/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.test.ts b/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.test.ts index d92d7b04..fe378cd8 100644 --- a/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.test.ts +++ b/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.test.ts @@ -15,7 +15,9 @@ describe('getModifiedCurrencySelection', () => { const xTransferInput = { amount, - currencyID, + asset: { + assetId: currencyID + }, paraIdTo } as XTokensTransferInput @@ -26,7 +28,11 @@ describe('getModifiedCurrencySelection', () => { Concrete: { parents: Parents.ONE, interior: { - X3: [{ Parachain: paraIdTo }, { PalletInstance: '50' }, { GeneralIndex: currencyID }] + X3: [ + { Parachain: paraIdTo }, + { PalletInstance: '50' }, + { GeneralIndex: BigInt(currencyID) } + ] } } }, @@ -44,7 +50,9 @@ describe('getModifiedCurrencySelection', () => { const xTransferInput = { amount, - currencyID: undefined, + asset: { + assetId: '' + }, paraIdTo } as XTokensTransferInput @@ -62,7 +70,9 @@ describe('getModifiedCurrencySelection', () => { const xTransferInput = { amount, - currencyID, + asset: { + assetId: currencyID + }, paraIdTo, feeAsset } as XTokensTransferInput @@ -78,7 +88,7 @@ describe('getModifiedCurrencySelection', () => { X3: [ { Parachain: paraIdTo }, { PalletInstance: '50' }, - { GeneralIndex: currencyID } + { GeneralIndex: BigInt(currencyID) } ] } } diff --git a/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.ts b/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.ts index 42c413e2..5f405620 100644 --- a/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.ts +++ b/packages/sdk/src/nodes/xTokens/getModifiedCurrencySelection.ts @@ -7,12 +7,13 @@ import type { XTokensTransferInput } from '../../types' import { Parents } from '../../types' +import { isForeignAsset } from '../../utils/assets' export const getModifiedCurrencySelection = ( version: Version, - { paraIdTo, currencyID, amount, feeAsset }: XTokensTransferInput + { paraIdTo, asset, amount, feeAsset }: XTokensTransferInput ): TCurrencySelectionHeader | TCurrencySelectionHeaderArr => { - if (currencyID === undefined || currencyID === '') { + if (!isForeignAsset(asset) || asset.assetId === '') { throw new InvalidCurrencyError('The selected asset has no currency ID') } @@ -21,7 +22,11 @@ export const getModifiedCurrencySelection = ( Concrete: { parents: Parents.ONE, interior: { - X3: [{ Parachain: paraIdTo }, { PalletInstance: '50' }, { GeneralIndex: currencyID }] + X3: [ + { Parachain: paraIdTo }, + { PalletInstance: '50' }, + { GeneralIndex: BigInt(asset.assetId) } + ] } } }, diff --git a/packages/sdk/src/nodes/xTransfer/XTransferTransferImpl.test.ts b/packages/sdk/src/nodes/xTransfer/XTransferTransferImpl.test.ts index 8b350f00..e119ba19 100644 --- a/packages/sdk/src/nodes/xTransfer/XTransferTransferImpl.test.ts +++ b/packages/sdk/src/nodes/xTransfer/XTransferTransferImpl.test.ts @@ -63,8 +63,10 @@ describe('XTransferTransferImpl', () => { const input = { api: mockApi, amount: '100', - currency: '123', - currencyID: '123', + asset: { + symbol: 'PHA', + assetId: '123' + }, recipientAddress: 'Recipient', origin: 'Phala', destination: 'Acala', diff --git a/packages/sdk/src/pallets/assets/assetSelectors.test.ts b/packages/sdk/src/pallets/assets/assetSelectors.test.ts new file mode 100644 index 00000000..3719a884 --- /dev/null +++ b/packages/sdk/src/pallets/assets/assetSelectors.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest' +import { Native, Foreign, ForeignAbstract } from './assetSelectors' +import type { TSymbolSpecifier } from '../../types' + +describe('Symbol Specifiers', () => { + const symbol = 'TEST' + + it('should create a Native symbol specifier', () => { + const result: TSymbolSpecifier = Native(symbol) + expect(result).toEqual({ + type: 'Native', + value: symbol + }) + }) + + it('should create a Foreign symbol specifier', () => { + const result: TSymbolSpecifier = Foreign(symbol) + expect(result).toEqual({ + type: 'Foreign', + value: symbol + }) + }) + + it('should create a ForeignAbstract symbol specifier', () => { + const result: TSymbolSpecifier = ForeignAbstract(symbol) + expect(result).toEqual({ + type: 'ForeignAbstract', + value: symbol + }) + }) +}) diff --git a/packages/sdk/src/pallets/assets/assetSelectors.ts b/packages/sdk/src/pallets/assets/assetSelectors.ts new file mode 100644 index 00000000..b8c53a16 --- /dev/null +++ b/packages/sdk/src/pallets/assets/assetSelectors.ts @@ -0,0 +1,16 @@ +import type { TSymbolSpecifier } from '../../types' + +export const Native = (symbol: string): TSymbolSpecifier => ({ + type: 'Native', + value: symbol +}) + +export const Foreign = (symbol: string): TSymbolSpecifier => ({ + type: 'Foreign', + value: symbol +}) + +export const ForeignAbstract = (symbol: string): TSymbolSpecifier => ({ + type: 'ForeignAbstract', + value: symbol +}) diff --git a/packages/sdk/src/pallets/assets/assets.ts b/packages/sdk/src/pallets/assets/assets.ts index 9b9893b1..bc987578 100644 --- a/packages/sdk/src/pallets/assets/assets.ts +++ b/packages/sdk/src/pallets/assets/assets.ts @@ -6,15 +6,15 @@ import type { TNodeWithRelayChains, TAsset, TRelayChainType, - TNodeDotKsmWithRelayChains + TNodeDotKsmWithRelayChains, + TForeignAsset } from '../../types' import { type TNodeAssets, type TAssetJsonMap, type TNode, type TRelayChainSymbol, - type TNativeAssetDetails, - type TAssetDetails + type TNativeAsset } from '../../types' import { determineRelayChain, getNode } from '../../utils' import { getAssetBySymbolOrId } from './getAssetBySymbolOrId' @@ -56,8 +56,7 @@ export const getRelayChainSymbol = (node: TNodeWithRelayChains): TRelayChainSymb * @param node - The node for which to get native assets. * @returns An array of native asset details. */ -export const getNativeAssets = (node: TNode): TNativeAssetDetails[] => - getAssetsObject(node).nativeAssets +export const getNativeAssets = (node: TNode): TNativeAsset[] => getAssetsObject(node).nativeAssets /** * Retrieves the list of other (non-native) assets for a specified node. @@ -65,7 +64,7 @@ export const getNativeAssets = (node: TNode): TNativeAssetDetails[] => * @param node - The node for which to get other assets. * @returns An array of other asset details. */ -export const getOtherAssets = (node: TNode): TAssetDetails[] => getAssetsObject(node).otherAssets +export const getOtherAssets = (node: TNode): TForeignAsset[] => getAssetsObject(node).otherAssets /** * Retrieves the complete list of assets for a specified node, including relay chain asset, native, and other assets. diff --git a/packages/sdk/src/pallets/assets/assetsUtils.ts b/packages/sdk/src/pallets/assets/assetsUtils.ts index 134681a0..b3b92ade 100644 --- a/packages/sdk/src/pallets/assets/assetsUtils.ts +++ b/packages/sdk/src/pallets/assets/assetsUtils.ts @@ -3,80 +3,73 @@ import { DuplicateAssetError, DuplicateAssetIdError, InvalidCurrencyError } from '../../errors' import type { TAsset, - TAssetDetails, TCurrency, - TNativeAssetDetails, + TNativeAsset, TNodePolkadotKusama, - TNodeWithRelayChains + TNodeWithRelayChains, + TForeignAsset, + TCurrencySymbolValue } from '../../types' import { isRelayChain } from '../../utils' +import { isSymbolSpecifier } from '../../utils/assets/isSymbolSpecifier' import { getDefaultPallet } from '../pallets' import { getOtherAssets } from './assets' -const findBestMatches = (assets: TAsset[], symbol: string): TAsset[] => { +export const throwDuplicateAssetError = ( + symbol: string, + nativeMatches: TNativeAsset[], + foreignMatches: TForeignAsset[] +) => { + if (nativeMatches.length > 0 && foreignMatches.length > 0) { + throw new DuplicateAssetError( + `Multiple matches found for symbol ${symbol}. Please specify with Native() or Foreign() selector.` + ) + } else if (foreignMatches.length > 1) { + const aliases = foreignMatches.map(asset => `${asset.alias} (ID:${asset.assetId})`).join(', ') + throw new DuplicateAssetError( + `Multiple foreign assets found for symbol ${symbol}. Please specify with ForeignAbstract() selector. Available aliases: ${aliases}` + ) + } +} + +export const findBestMatches = ( + assets: TAsset[], + value: string, + property: 'symbol' | 'alias' = 'symbol' +): TAsset[] => { // First, exact match - let matches = assets.filter(asset => asset.symbol === symbol) + let matches = assets.filter(asset => asset[property] === value) if (matches.length > 0) { return matches } // Uppercase match - const upperSymbol = symbol.toUpperCase() - matches = assets.filter(asset => asset.symbol === upperSymbol) + const upperValue = value.toUpperCase() + matches = assets.filter(asset => asset[property] === upperValue) if (matches.length > 0) { return matches } // Lowercase match - const lowerSymbol = symbol.toLowerCase() - matches = assets.filter(asset => asset.symbol === lowerSymbol) + const lowerValue = value.toLowerCase() + matches = assets.filter(asset => asset[property] === lowerValue) if (matches.length > 0) { return matches } // Case-insensitive match - matches = assets.filter(asset => asset.symbol?.toLowerCase() === lowerSymbol) + matches = assets.filter(asset => asset[property]?.toLowerCase() === lowerValue) return matches } export const findAssetBySymbol = ( node: TNodeWithRelayChains, destination: TNodeWithRelayChains | undefined, - otherAssets: TAssetDetails[], - nativeAssets: TNativeAssetDetails[], - symbol: string, + otherAssets: TForeignAsset[], + nativeAssets: TNativeAsset[], + symbol: TCurrencySymbolValue, isRelayDestination: boolean ) => { - const lowerSymbol = symbol.toLowerCase() - - const isPolkadotXcm = - !isRelayChain(node) && - node !== 'Ethereum' && - getDefaultPallet(node as TNodePolkadotKusama) === 'PolkadotXcm' - - let otherAssetsMatches: TAssetDetails[] = [] - let nativeAssetsMatches: TNativeAssetDetails[] = [] - - if (destination === 'Ethereum') { - const ethereumAssets = getOtherAssets('Ethereum') - - let assetsMatches = findBestMatches(ethereumAssets, symbol) - - if (assetsMatches.length === 0) { - if (lowerSymbol.endsWith('.e')) { - // Symbol ends with '.e', strip it and search again - const strippedSymbol = symbol.slice(0, -2).toLowerCase() - assetsMatches = findBestMatches(ethereumAssets, strippedSymbol) - } else { - // Symbol does not end with '.e', add '.e' suffix and search - const suffixedSymbol = `${symbol}.e`.toLowerCase() - assetsMatches = findBestMatches(ethereumAssets, suffixedSymbol) - } - } - - return assetsMatches[0] - } - const supportsESuffix = node === 'AssetHubPolkadot' || node === 'AssetHubKusama' || @@ -84,100 +77,229 @@ export const findAssetBySymbol = ( destination === 'AssetHubKusama' || node === 'Ethereum' - if (lowerSymbol.endsWith('.e') && supportsESuffix) { - // Symbol ends with '.e', indicating a Snowbridge asset - const strippedSymbol = symbol.slice(0, -2) + const isSpecifier = isSymbolSpecifier(symbol) + let assetsMatches: TAsset[] = [] - // Search in Ethereum assets without the '.e' suffix - const ethereumAssets = getOtherAssets('Ethereum') - const ethereumMatches = findBestMatches(ethereumAssets, strippedSymbol) + if (isSpecifier) { + const { type, value } = symbol - if (ethereumMatches.length > 0) { - return ethereumMatches[0] - } + if (type === 'Native') { + assetsMatches = findBestMatches(nativeAssets, value, 'symbol') + } else if (type === 'Foreign') { + const lowerSymbol = value.toLowerCase() - // If not found, search normal assets with '.e' suffix - otherAssetsMatches = findBestMatches(otherAssets, symbol) as TAssetDetails[] - nativeAssetsMatches = findBestMatches(nativeAssets, symbol) as TNativeAssetDetails[] + let otherAssetsMatches: TForeignAsset[] = [] - if (nativeAssetsMatches.length > 0 || otherAssetsMatches.length > 0) { - if (isPolkadotXcm) { - return nativeAssetsMatches[0] || otherAssetsMatches[0] - } - return otherAssetsMatches[0] || nativeAssetsMatches[0] - } + if (destination === 'Ethereum') { + const ethereumAssets = getOtherAssets('Ethereum') + + let assetsMatches = findBestMatches(ethereumAssets, value) - // If still not found, search normal assets without suffix - otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TAssetDetails[] - nativeAssetsMatches = findBestMatches(nativeAssets, strippedSymbol) as TNativeAssetDetails[] + if (assetsMatches.length === 0) { + if (lowerSymbol.endsWith('.e')) { + // Symbol ends with '.e', strip it and search again + const strippedSymbol = value.slice(0, -2).toLowerCase() + assetsMatches = findBestMatches(ethereumAssets, strippedSymbol) + } else { + // Symbol does not end with '.e', add '.e' suffix and search + const suffixedSymbol = `${value}.e`.toLowerCase() + assetsMatches = findBestMatches(ethereumAssets, suffixedSymbol) + } + } - if (nativeAssetsMatches.length > 0 || otherAssetsMatches.length > 0) { - if (isPolkadotXcm) { - return nativeAssetsMatches[0] || otherAssetsMatches[0] + return assetsMatches[0] } - return otherAssetsMatches[0] || nativeAssetsMatches[0] - } - // No matches found - return undefined - } else { - // Symbol does not end with '.e', proceed with existing logic - otherAssetsMatches = findBestMatches(otherAssets, symbol) as TAssetDetails[] - nativeAssetsMatches = findBestMatches(nativeAssets, symbol) as TNativeAssetDetails[] + if (lowerSymbol.endsWith('.e') && supportsESuffix) { + // Symbol ends with '.e', indicating a Snowbridge asset + const strippedSymbol = value.slice(0, -2) + + // Search in Ethereum assets without the '.e' suffix + const ethereumAssets = getOtherAssets('Ethereum') + const ethereumMatches = findBestMatches(ethereumAssets, strippedSymbol) - if (otherAssetsMatches.length === 0 && nativeAssetsMatches.length === 0) { - if (lowerSymbol.startsWith('xc')) { - // Symbol starts with 'xc', try stripping 'xc' prefix - const strippedSymbol = symbol.substring(2) + if (ethereumMatches.length > 0) { + return ethereumMatches[0] + } - otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TAssetDetails[] - nativeAssetsMatches = findBestMatches(nativeAssets, strippedSymbol) as TNativeAssetDetails[] + // If not found, search normal assets with '.e' suffix + otherAssetsMatches = findBestMatches(otherAssets, value) as TForeignAsset[] - if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) { - return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined + if (otherAssetsMatches.length > 1) { + throwDuplicateAssetError(value, [], otherAssetsMatches) + } else if (otherAssetsMatches.length > 0) { + return otherAssetsMatches[0] } - const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length + // If still not found, search normal assets without suffix + otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TForeignAsset[] - if (totalMatches > 1) { - throw new InvalidCurrencyError( - `Multiple assets found for symbol ${symbol} after stripping 'xc' prefix. Please specify by ID.` - ) + if (otherAssetsMatches.length > 1) { + throwDuplicateAssetError(value, [], otherAssetsMatches) + } else if (otherAssetsMatches.length > 0) { + return otherAssetsMatches[0] } + + // No matches found + return undefined } else { - // Try adding 'xc' prefix - const prefixedSymbol = `xc${symbol}` + // Symbol does not end with '.e', proceed with existing logic + otherAssetsMatches = findBestMatches(otherAssets, value) as TForeignAsset[] - otherAssetsMatches = findBestMatches(otherAssets, prefixedSymbol) as TAssetDetails[] - nativeAssetsMatches = findBestMatches(nativeAssets, prefixedSymbol) as TNativeAssetDetails[] + if (otherAssetsMatches.length === 0) { + if (lowerSymbol.startsWith('xc')) { + // Symbol starts with 'xc', try stripping 'xc' prefix + const strippedSymbol = value.substring(2) - if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) { - return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined - } + otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TForeignAsset[] + } else { + // Try adding 'xc' prefix + const prefixedSymbol = `xc${value}` - const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length + otherAssetsMatches = findBestMatches(otherAssets, prefixedSymbol) as TForeignAsset[] + } + } - if (totalMatches > 1) { - throw new InvalidCurrencyError( - `Multiple assets found for symbol ${symbol} after adding 'xc' prefix. Please specify by ID.` - ) + if (otherAssetsMatches.length > 1) { + throwDuplicateAssetError(value, [], otherAssetsMatches) } + + return otherAssetsMatches[0] || undefined + } + } else if (type === 'ForeignAbstract') { + assetsMatches = findBestMatches(otherAssets, value, 'alias') + if (assetsMatches.length === 0) { + throw new InvalidCurrencyError( + `No matches found for abstract foreign asset alias ${value}.` + ) } } + } else { + const lowerSymbol = symbol.toLowerCase() + + const isPolkadotXcm = + !isRelayChain(node) && + node !== 'Ethereum' && + getDefaultPallet(node as TNodePolkadotKusama) === 'PolkadotXcm' + + let otherAssetsMatches: TForeignAsset[] = [] + let nativeAssetsMatches: TNativeAsset[] = [] + + if (destination === 'Ethereum') { + const ethereumAssets = getOtherAssets('Ethereum') + + let assetsMatches = findBestMatches(ethereumAssets, symbol) + + if (assetsMatches.length === 0) { + if (lowerSymbol.endsWith('.e')) { + // Symbol ends with '.e', strip it and search again + const strippedSymbol = symbol.slice(0, -2).toLowerCase() + assetsMatches = findBestMatches(ethereumAssets, strippedSymbol) + } else { + // Symbol does not end with '.e', add '.e' suffix and search + const suffixedSymbol = `${symbol}.e`.toLowerCase() + assetsMatches = findBestMatches(ethereumAssets, suffixedSymbol) + } + } - if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) { - return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined + return assetsMatches[0] } - if (otherAssetsMatches.length > 1 && !isRelayDestination) { - throw new DuplicateAssetError(symbol) - } + if (lowerSymbol.endsWith('.e') && supportsESuffix) { + // Symbol ends with '.e', indicating a Snowbridge asset + const strippedSymbol = symbol.slice(0, -2) + + // Search in Ethereum assets without the '.e' suffix + const ethereumAssets = getOtherAssets('Ethereum') + const ethereumMatches = findBestMatches(ethereumAssets, strippedSymbol) + + if (ethereumMatches.length > 0) { + return ethereumMatches[0] + } + + // If not found, search normal assets with '.e' suffix + otherAssetsMatches = findBestMatches(otherAssets, symbol) as TForeignAsset[] + nativeAssetsMatches = findBestMatches(nativeAssets, symbol) as TNativeAsset[] + + if (nativeAssetsMatches.length > 0 || otherAssetsMatches.length > 0) { + if (isPolkadotXcm) { + return nativeAssetsMatches[0] || otherAssetsMatches[0] + } + return otherAssetsMatches[0] || nativeAssetsMatches[0] + } + + // If still not found, search normal assets without suffix + otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TForeignAsset[] + nativeAssetsMatches = findBestMatches(nativeAssets, strippedSymbol) as TNativeAsset[] + + if (nativeAssetsMatches.length > 0 || otherAssetsMatches.length > 0) { + if (isPolkadotXcm) { + return nativeAssetsMatches[0] || otherAssetsMatches[0] + } + return otherAssetsMatches[0] || nativeAssetsMatches[0] + } + + // No matches found + return undefined + } else { + // Symbol does not end with '.e' + otherAssetsMatches = findBestMatches(otherAssets, symbol) as TForeignAsset[] + nativeAssetsMatches = findBestMatches(nativeAssets, symbol) as TNativeAsset[] + + if (otherAssetsMatches.length === 0 && nativeAssetsMatches.length === 0) { + if (lowerSymbol.startsWith('xc')) { + // Symbol starts with 'xc', try stripping 'xc' prefix + const strippedSymbol = symbol.substring(2) + + otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TForeignAsset[] + nativeAssetsMatches = findBestMatches(nativeAssets, strippedSymbol) as TNativeAsset[] - return otherAssetsMatches[0] || nativeAssetsMatches[0] || undefined + if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) { + return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined + } + + const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length + + if (totalMatches > 1) { + throwDuplicateAssetError(symbol, nativeAssetsMatches, otherAssetsMatches) + } + } else { + // Try adding 'xc' prefix + const prefixedSymbol = `xc${symbol}` + + otherAssetsMatches = findBestMatches(otherAssets, prefixedSymbol) as TForeignAsset[] + nativeAssetsMatches = findBestMatches(nativeAssets, prefixedSymbol) as TNativeAsset[] + + if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) { + return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined + } + + const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length + + if (totalMatches > 1) { + throwDuplicateAssetError(symbol, nativeAssetsMatches, otherAssetsMatches) + } + } + } + + if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) { + return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined + } + + const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length + + if (totalMatches > 1 && !isRelayDestination) { + throwDuplicateAssetError(symbol, nativeAssetsMatches, otherAssetsMatches) + } + + return otherAssetsMatches[0] || nativeAssetsMatches[0] || undefined + } } + + return assetsMatches[0] || undefined } -export const findAssetById = (assets: TAsset[], assetId: TCurrency) => { +export const findAssetById = (assets: TForeignAsset[], assetId: TCurrency) => { const otherAssetsMatches = assets.filter( ({ assetId: currentAssetId }) => currentAssetId === assetId.toString() ) diff --git a/packages/sdk/src/pallets/assets/balance/getBalanceForeignPolkadotXcm.ts b/packages/sdk/src/pallets/assets/balance/getBalanceForeignPolkadotXcm.ts index e38ed523..1742d254 100644 --- a/packages/sdk/src/pallets/assets/balance/getBalanceForeignPolkadotXcm.ts +++ b/packages/sdk/src/pallets/assets/balance/getBalanceForeignPolkadotXcm.ts @@ -3,6 +3,7 @@ import { getAssetHubMultiLocation } from './getAssetHubMultiLocation' import type { IPolkadotApi } from '../../../api/IPolkadotApi' import { InvalidCurrencyError } from '../../../errors' import { ethers } from 'ethers' +import { isForeignAsset } from '../../../utils/assets' export const getBalanceForeignPolkadotXcm = async ( api: IPolkadotApi, @@ -14,15 +15,16 @@ export const getBalanceForeignPolkadotXcm = async ( return await api.getMythosForeignBalance(address) } + if (!isForeignAsset(asset)) { + throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) + } + if (node === 'AssetHubPolkadot') { const multiLocation = getAssetHubMultiLocation(asset.symbol) // Ethereum address ID indicates that it is an Ethereum asset - if (multiLocation && ethers.isAddress(asset.assetId)) { + if (multiLocation && isForeignAsset(asset) && ethers.isAddress(asset.assetId)) { return api.getAssetHubForeignBalance(address, multiLocation) } else { - if (asset.assetId === undefined) { - throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) - } return api.getBalanceForeignAssetsAccount(address, Number(asset.assetId)) } } diff --git a/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.test.ts b/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.test.ts index 99b23d18..06bfba9e 100644 --- a/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.test.ts +++ b/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import type { ApiPromise } from '@polkadot/api' import type { IPolkadotApi } from '../../../api' -import type { TAsset, TNativeAssetDetails } from '../../../types' +import type { TAsset, TNativeAsset } from '../../../types' import { InvalidCurrencyError, type Extrinsic } from '../../../pjs' import { getBalanceForeignXTokens } from './getBalanceForeignXTokens' @@ -60,9 +60,8 @@ describe('getBalanceForeignXTokens', () => { it('throws InvalidCurrencyError if asset has no assetId', async () => { await expect( getBalanceForeignXTokens(mockApi, 'Moonbeam', address, { - symbol: 'AssetName', - assetId: undefined - } as TNativeAssetDetails) + symbol: 'AssetName' + } as TNativeAsset) ).rejects.toThrow(InvalidCurrencyError) }) }) diff --git a/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.ts b/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.ts index 87c67d59..82412e41 100644 --- a/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.ts +++ b/packages/sdk/src/pallets/assets/balance/getBalanceForeignXTokens.ts @@ -1,6 +1,7 @@ import type { IPolkadotApi } from '../../../api' import { InvalidCurrencyError } from '../../../errors' import type { TAsset, TNodePolkadotKusama } from '../../../types' +import { isForeignAsset } from '../../../utils/assets' export const getBalanceForeignXTokens = async ( api: IPolkadotApi, @@ -9,7 +10,7 @@ export const getBalanceForeignXTokens = async ( asset: TAsset ): Promise => { if (node === 'Moonbeam' || node === 'Moonriver' || node === 'Astar' || node === 'Shiden') { - if (asset.assetId === undefined) { + if (!isForeignAsset(asset)) { throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`) } diff --git a/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.test.ts b/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.test.ts index 7df7006c..066f3884 100644 --- a/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.test.ts +++ b/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.test.ts @@ -6,7 +6,9 @@ import { getAssetBySymbolOrId } from './getAssetBySymbolOrId' import * as assetFunctions from './assets' import { getDefaultPallet } from '../pallets' import { isRelayChain } from '../../utils' -import type { TNodePolkadotKusama } from '../../types' +import type { TAsset, TForeignAsset, TNodePolkadotKusama } from '../../types' +import { isForeignAsset } from '../../utils/assets' +import { Foreign, ForeignAbstract, Native } from './assetSelectors' const getAssetsObject = assetFunctions.getAssetsObject @@ -24,7 +26,7 @@ describe('getAssetBySymbolOrId', () => { ({ symbol: assetSymbol }) => assetSymbol?.toLowerCase() === other.symbol?.toLowerCase() ) if (otherAssetsMatches.length < 2) { - const asset = getAssetBySymbolOrId(node, { symbol: other.symbol }) + const asset = getAssetBySymbolOrId(node, { symbol: Foreign(other.symbol) }) expect(asset).toHaveProperty('symbol') expect(other.symbol.toLowerCase()).toEqual(asset?.symbol?.toLowerCase()) if ( @@ -67,22 +69,22 @@ describe('getAssetBySymbolOrId', () => { otherAssets.filter(asset => asset.assetId === other.assetId).length > 1 if (!hasDuplicateIds) { const asset = getAssetBySymbolOrId(node, { id: other.assetId }) + expect(asset).not.toBeNull() + expect(isForeignAsset(asset as TAsset)).toBe(true) expect(asset).toHaveProperty('assetId') - expect(other.assetId).toEqual(asset?.assetId) + expect(other.assetId).toEqual((asset as TForeignAsset).assetId) } }) }) }) - it('should return symbol for every native foreign asset id', () => { + it('should return symbol for every native asset', () => { NODE_NAMES.forEach(node => { const { nativeAssets } = getAssetsObject(node) - nativeAssets.forEach(other => { - if (other.assetId !== undefined) { - const asset = getAssetBySymbolOrId(node, { id: other.assetId }) + nativeAssets.forEach(nativeAsset => { + if (!isForeignAsset(nativeAsset)) { + const asset = getAssetBySymbolOrId(node, { symbol: Native(nativeAsset.symbol) }) expect(asset).toHaveProperty('symbol') - expect(asset).toHaveProperty('assetId') - expect(Number(other.assetId)).toEqual(asset?.assetId) } }) }) @@ -108,24 +110,48 @@ describe('getAssetBySymbolOrId', () => { expect(asset).toHaveProperty('assetId') }) + it('Should find asset starting with "xc" for Moonbeam using Foreign selector', () => { + const asset = getAssetBySymbolOrId('Moonbeam', { symbol: Foreign('xcZTG') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should find asset starting with "xc" for Moonbeam', () => { const asset = getAssetBySymbolOrId('Moonbeam', { symbol: 'xcWETH.e' }) expect(asset).toHaveProperty('symbol') expect(asset).toHaveProperty('assetId') }) + it('Should find asset starting with "xc" for Moonbeam using Foreign selector', () => { + const asset = getAssetBySymbolOrId('Moonbeam', { symbol: Foreign('xcWETH.e') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should find asset ending with .e on AssetHubPolkadot', () => { const asset = getAssetBySymbolOrId('AssetHubPolkadot', { symbol: 'WETH.e' }) expect(asset).toHaveProperty('symbol') expect(asset).toHaveProperty('assetId') }) + it('Should find asset ending with .e on AssetHubPolkadot', () => { + const asset = getAssetBySymbolOrId('AssetHubPolkadot', { symbol: Foreign('WETH.e') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should find asset ending with .e on Ethereum', () => { const asset = getAssetBySymbolOrId('Ethereum', { symbol: 'WETH.e' }) expect(asset).toHaveProperty('symbol') expect(asset).toHaveProperty('assetId') }) + it('Should find asset ending with .e on Ethereum', () => { + const asset = getAssetBySymbolOrId('Ethereum', { symbol: Foreign('WETH.e') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should find asset ending with .e on Ethereum', () => { const asset = getAssetBySymbolOrId('AssetHubPolkadot', { symbol: 'WETH.e' }, false, 'Ethereum') expect(asset).toHaveProperty('symbol') @@ -144,6 +170,89 @@ describe('getAssetBySymbolOrId', () => { expect(asset).toHaveProperty('assetId') }) + it('should find asset without .e to match e', () => { + vi.spyOn(assetFunctions, 'getOtherAssets').mockReturnValue([ + { + assetId: '1', + symbol: 'MON' + } + ]) + const asset = getAssetBySymbolOrId('AssetHubPolkadot', { symbol: Foreign('MON.e') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + + it('should find asset without .e to match e', () => { + vi.spyOn(assetFunctions, 'getOtherAssets').mockImplementation(node => + node === 'Ethereum' + ? [] + : [ + { + assetId: '1', + symbol: 'MON' + }, + { + assetId: '2', + symbol: 'MON' + } + ] + ) + getAssetBySymbolOrId('AssetHubPolkadot', { symbol: Foreign('MON.e') }) + }) + + it('Should find asset ending with .e on Ethereum when entered withou suffix', () => { + vi.spyOn(assetFunctions, 'getOtherAssets').mockReturnValue([ + { + assetId: '1', + symbol: 'WETH.e' + } + ]) + const asset = getAssetBySymbolOrId( + 'AssetHubPolkadot', + { symbol: Foreign('WETH') }, + false, + 'Ethereum' + ) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + + it('should find weth with suffix on Ethereum', () => { + vi.spyOn(assetFunctions, 'getOtherAssets').mockReturnValue([ + { + assetId: '1', + symbol: 'WETH.e' + } + ]) + const asset = getAssetBySymbolOrId('Ethereum', { symbol: Foreign('WETH') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + + it('should find weth with suffix on Ethereum', () => { + vi.spyOn(assetFunctions, 'getOtherAssets').mockReturnValue([ + { + assetId: '1', + symbol: 'WETH' + } + ]) + const asset = getAssetBySymbolOrId('Ethereum', { symbol: Foreign('WETH.e') }, false, 'Ethereum') + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + + it('should find weth with suffix on Ethereum', () => { + vi.spyOn(assetFunctions, 'getOtherAssets').mockReturnValue([ + { + assetId: '1', + symbol: 'WETH.e' + } + ]) + const asset = getAssetBySymbolOrId('Ethereum', { symbol: Foreign('WETH') }, false, 'Ethereum') + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should find asset ending with .e on AssetHubPolkadot with duplicates', () => { vi.spyOn(assetFunctions, 'getAssetsObject').mockImplementation(node => { return node === 'Ethereum' @@ -287,12 +396,132 @@ describe('getAssetBySymbolOrId', () => { expect(() => getAssetBySymbolOrId('Hydration', { symbol: 'DOT' })).toThrow() }) + it('should throw error when multiple assets found for symbol after adding "xc" prefix', () => { + vi.spyOn(assetFunctions, 'getAssetsObject').mockReturnValue({ + nativeAssetSymbol: 'DOT', + relayChainAssetSymbol: 'DOT', + otherAssets: [ + { + assetId: '1', + symbol: 'xcDOT' + }, + { + assetId: '2', + symbol: 'xcDOT' + } + ], + nativeAssets: [] + }) + expect(() => getAssetBySymbolOrId('Hydration', { symbol: Foreign('DOT') })).toThrow() + }) + + it('should find asset with xc prefix on Acala', () => { + vi.spyOn(assetFunctions, 'getAssetsObject').mockReturnValue({ + nativeAssetSymbol: 'DOT', + relayChainAssetSymbol: 'DOT', + otherAssets: [ + { + assetId: '2', + symbol: 'xcDOT' + } + ], + nativeAssets: [] + }) + const asset = getAssetBySymbolOrId('Acala', { symbol: 'DOT' }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should find ethereum assets', () => { const asset = getAssetBySymbolOrId('AssetHubPolkadot', { symbol: 'WETH' }, false, 'Ethereum') expect(asset).toHaveProperty('symbol') expect(asset).toHaveProperty('assetId') }) + it('should find native asset on Acala', () => { + vi.spyOn(assetFunctions, 'getAssetsObject').mockReturnValue({ + nativeAssetSymbol: 'DOT', + relayChainAssetSymbol: 'DOT', + otherAssets: [], + nativeAssets: [ + { + symbol: 'DOT', + decimals: 10 + } + ] + }) + const asset = getAssetBySymbolOrId('Acala', { symbol: Native('DOT') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('decimals') + }) + + it('should find foreign abstract asset on Acala', () => { + vi.spyOn(assetFunctions, 'getAssetsObject').mockReturnValue({ + nativeAssetSymbol: 'DOT', + relayChainAssetSymbol: 'DOT', + otherAssets: [ + { + assetId: '1', + symbol: 'DOT', + alias: 'DOT1' + }, + { + assetId: '2', + symbol: 'DOT', + alias: 'DOT2' + } + ], + nativeAssets: [] + }) + const asset = getAssetBySymbolOrId('Acala', { symbol: ForeignAbstract('DOT1') }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + + it('should throw error when multiple matches in native and foreign assets', () => { + vi.spyOn(assetFunctions, 'getAssetsObject').mockReturnValue({ + nativeAssetSymbol: 'DOT', + relayChainAssetSymbol: 'DOT', + otherAssets: [ + { + assetId: '1', + symbol: 'DOT', + alias: 'DOT1' + }, + { + assetId: '2', + symbol: 'DOT', + alias: 'DOT2' + } + ], + nativeAssets: [ + { + symbol: 'DOT', + decimals: 10 + } + ] + }) + expect(() => getAssetBySymbolOrId('Acala', { symbol: 'DOT' })).toThrow() + }) + + it('should find asset with lowercase matching', () => { + vi.spyOn(assetFunctions, 'getAssetsObject').mockReturnValue({ + nativeAssetSymbol: 'DOT', + relayChainAssetSymbol: 'DOT', + otherAssets: [ + { + assetId: '2', + symbol: 'dot', + alias: 'DOT2' + } + ], + nativeAssets: [] + }) + const asset = getAssetBySymbolOrId('Acala', { symbol: 'Dot' }) + expect(asset).toHaveProperty('symbol') + expect(asset).toHaveProperty('assetId') + }) + it('Should return null when passing a multilocation currency', () => { const asset = getAssetBySymbolOrId('Astar', { multilocation: { diff --git a/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.ts b/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.ts index e36d5d38..700fdc7e 100644 --- a/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.ts +++ b/packages/sdk/src/pallets/assets/getAssetBySymbolOrId.ts @@ -1,4 +1,5 @@ -import type { TAsset, TCurrencyInput, TNativeAssetDetails, TNodeWithRelayChains } from '../../types' +import type { TAsset, TCurrencyInput, TNativeAsset, TNodeWithRelayChains } from '../../types' +import { isSymbolSpecifier } from '../../utils/assets/isSymbolSpecifier' import { getAssetDecimals, getAssetsObject, getOtherAssets } from './assets' import { findAssetById, findAssetBySymbol } from './assetsUtils' @@ -13,8 +14,8 @@ export const getAssetBySymbolOrId = ( } const { otherAssets, nativeAssets, relayChainAssetSymbol } = getAssetsObject(node) - const combinedAssets = - destination === 'Ethereum' ? [...getOtherAssets('Ethereum')] : [...otherAssets, ...nativeAssets] + + const resolvedOtherAssets = destination === 'Ethereum' ? getOtherAssets('Ethereum') : otherAssets let asset: TAsset | undefined if ('symbol' in currency) { @@ -27,7 +28,7 @@ export const getAssetBySymbolOrId = ( isRelayDestination ) } else { - asset = findAssetById(combinedAssets, currency.id) + asset = findAssetById(resolvedOtherAssets, currency.id) } if (asset) { @@ -36,9 +37,13 @@ export const getAssetBySymbolOrId = ( if ( 'symbol' in currency && - relayChainAssetSymbol.toLowerCase() === currency.symbol.toLowerCase() + ((isSymbolSpecifier(currency.symbol) && + currency.symbol.type === 'Native' && + relayChainAssetSymbol.toLowerCase() === currency.symbol.value.toLowerCase()) || + (!isSymbolSpecifier(currency.symbol) && + relayChainAssetSymbol.toLowerCase() === currency.symbol.toLowerCase())) ) { - const relayChainAsset: TNativeAssetDetails = { + const relayChainAsset: TNativeAsset = { symbol: relayChainAssetSymbol, decimals: getAssetDecimals(node, relayChainAssetSymbol) as number } diff --git a/packages/sdk/src/pallets/assets/index.ts b/packages/sdk/src/pallets/assets/index.ts index 6eceef29..2944eb98 100644 --- a/packages/sdk/src/pallets/assets/index.ts +++ b/packages/sdk/src/pallets/assets/index.ts @@ -6,4 +6,5 @@ export * from './balance/getBalanceForeign' export * from './getOriginFeeDetails' export * from './transfer-info/getTransferInfo' export * from './asset-claim' +export * from './assetSelectors' export { getSupportedAssets } from './getSupportedAssets' diff --git a/packages/sdk/src/pallets/assets/transfer-info/getTransferInfo.ts b/packages/sdk/src/pallets/assets/transfer-info/getTransferInfo.ts index 705b4e92..73edf2be 100644 --- a/packages/sdk/src/pallets/assets/transfer-info/getTransferInfo.ts +++ b/packages/sdk/src/pallets/assets/transfer-info/getTransferInfo.ts @@ -44,9 +44,7 @@ export const getTransferInfo = async ({ (origin === 'AssetHubPolkadot' ? getAssetBySymbolOrId('Ethereum', currency) : null) if (!asset) { - throw new InvalidCurrencyError( - `Asset ${'symbol' in currency ? currency.symbol : currency.id} not found on ${origin}` - ) + throw new InvalidCurrencyError(`Asset ${JSON.stringify(currency)} not found on ${origin}`) } return { diff --git a/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.test.ts b/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.test.ts index 1b722c5c..e023f28b 100644 --- a/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.test.ts +++ b/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.test.ts @@ -36,7 +36,7 @@ describe('checkKeepAlive', () => { amount: AMOUNT, originNode: 'Acala', destApi: mockApi, - currencySymbol: 'UNQ', + asset: { symbol: 'UNQ' }, destNode: 'Unique' }) ).resolves.toBeUndefined() @@ -50,7 +50,7 @@ describe('checkKeepAlive', () => { amount: AMOUNT, originNode: 'Acala', destApi: mockApi, - currencySymbol: 'DOT', + asset: { symbol: 'DOT' }, destNode: 'Unique' }) ).rejects.toThrowError(KeepAliveError) @@ -65,7 +65,7 @@ describe('checkKeepAlive', () => { amount: AMOUNT, originNode: 'Acala', destApi: mockApi, - currencySymbol: 'UNQ', + asset: { symbol: 'UNQ' }, destNode: 'Unique' }) ).rejects.toThrowError(KeepAliveError) @@ -80,7 +80,7 @@ describe('checkKeepAlive', () => { amount: '100000', // Amount lower than ED originNode: 'Acala', destApi: mockApi, - currencySymbol: 'UNQ', + asset: { symbol: 'UNQ' }, destNode: 'Unique' }) ).rejects.toThrowError(KeepAliveError) @@ -94,7 +94,7 @@ describe('checkKeepAlive', () => { amount: AMOUNT, originNode: 'Acala', destApi: mockApi, - currencySymbol: undefined, + asset: { symbol: undefined, assetId: '1' }, destNode: 'Unique' }) ).rejects.toThrowError(KeepAliveError) @@ -108,7 +108,7 @@ describe('checkKeepAlive', () => { amount: AMOUNT, originNode: 'Acala', destApi: mockApi, - currencySymbol: 'BBB', + asset: { symbol: 'BBB' }, destNode: 'Unique' }) ).rejects.toThrowError(KeepAliveError) @@ -123,7 +123,7 @@ describe('checkKeepAlive', () => { amount: '1000', originNode: 'Acala', destApi: mockApi, - currencySymbol: 'UNQ', + asset: { symbol: 'UNQ' }, destNode: 'Unique' }) ).resolves.toBeUndefined() @@ -138,7 +138,7 @@ describe('checkKeepAlive', () => { amount: '1000', originNode: 'Astar', destApi: mockApi, - currencySymbol: 'ACA', + asset: { symbol: 'ACA' }, destNode: 'Acala' }) ).resolves.toBeUndefined() @@ -160,7 +160,9 @@ describe('checkKeepAlive', () => { amount: amountOriginBNWithoutFee.toString(), originNode: 'Acala', destApi: mockApi, - currencySymbol, + asset: { + symbol: currencySymbol + }, destNode: 'AssetHubPolkadot' }) ).rejects.toThrowError(KeepAliveError) diff --git a/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.ts b/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.ts index c6fa4365..26f2131c 100644 --- a/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.ts +++ b/packages/sdk/src/pallets/xcmPallet/keepAlive/checkKeepAlive.ts @@ -11,21 +11,21 @@ export const checkKeepAlive = async ({ amount, originNode, destApi, - currencySymbol, + asset, destNode }: CheckKeepAliveOptions): Promise => { if (destApi.getApi() === undefined) { return } - if (currencySymbol === undefined) { + if (asset.symbol === undefined) { throw new KeepAliveError('Currency symbol not found for this asset. Cannot check keep alive.') } if ( originNode !== undefined && destNode !== undefined && - currencySymbol !== getAssetsObject(destNode).nativeAssetSymbol + asset.symbol !== getAssetsObject(destNode).nativeAssetSymbol ) { throw new KeepAliveError( 'Keep alive check is only supported when sending native asset of destination parachain.' @@ -50,7 +50,7 @@ export const checkKeepAlive = async ({ destApi, address, amount, - currencySymbol, + asset.symbol, originNode, destNode ) @@ -75,7 +75,7 @@ export const checkKeepAlive = async ({ if (balance + amountBNWithoutFee < BigInt(ed)) { throw new KeepAliveError( - `Keep alive check failed: Sending ${amount} ${currencySymbol} to ${destNode} would result in an account balance below the required existential deposit. + `Keep alive check failed: Sending ${amount} ${asset.symbol} to ${destNode} would result in an account balance below the required existential deposit. Please increase the amount to meet the minimum balance requirement of the destination chain.` ) } @@ -83,11 +83,11 @@ export const checkKeepAlive = async ({ const amountOriginBNWithoutFee = amountBN - (xcmFee + xcmFee / BigInt(2)) if ( - (currencySymbol === 'DOT' || currencySymbol === 'KSM') && + (asset.symbol === 'DOT' || asset.symbol === 'KSM') && balanceOrigin - amountOriginBNWithoutFee > BigInt(edOrigin) ) { throw new KeepAliveError( - `Keep alive check failed: Sending ${amount} ${currencySymbol} to ${destNode} would result in an account balance below the required existential deposit on origin. + `Keep alive check failed: Sending ${amount} ${asset.symbol} to ${destNode} would result in an account balance below the required existential deposit on origin. Please decrease the amount to meet the minimum balance requirement of the origin chain.` ) } diff --git a/packages/sdk/src/pallets/xcmPallet/transfer.test.ts b/packages/sdk/src/pallets/xcmPallet/transfer.test.ts index b81f91a3..184a2862 100644 --- a/packages/sdk/src/pallets/xcmPallet/transfer.test.ts +++ b/packages/sdk/src/pallets/xcmPallet/transfer.test.ts @@ -3,7 +3,7 @@ import { type ApiPromise } from '@polkadot/api' import { describe, expect, it, beforeEach, vi } from 'vitest' import { NODE_NAMES_DOT_KSM } from '../../maps/consts' -import { getAllAssetsSymbols, getOtherAssets, getRelayChainSymbol } from '../assets' +import { Foreign, getAllAssetsSymbols, getOtherAssets, getRelayChainSymbol } from '../assets' import { InvalidCurrencyError } from '../../errors/InvalidCurrencyError' import { DuplicateAssetError, IncompatibleNodesError } from '../../errors' import { type TSendOptions, type TNode, type TMultiAsset, type TMultiLocation } from '../../types' @@ -593,4 +593,34 @@ describe('send', () => { await expect(send(options)).rejects.toThrow(InvalidCurrencyError) }) + + it('should throw if assetCheck is disabled and we are using symbol specifier', async () => { + const options: TSendOptions = { + api: mockApi, + destApiForKeepAlive: mockApi, + origin: 'CoretimePolkadot' as TNode, + destination: 'AssetHubPolkadot' as TNode, + currency: { symbol: Foreign('DOT') }, + feeAsset: 0, + amount: 1000, + address: '0x789' + } + + await expect(send(options)).rejects.toThrow(InvalidCurrencyError) + }) + + it('should throw if assetCheck is disabled and we are using id specifier', async () => { + const options: TSendOptions = { + api: mockApi, + destApiForKeepAlive: mockApi, + origin: 'CoretimePolkadot' as TNode, + destination: 'AssetHubPolkadot' as TNode, + currency: { id: 123 }, + feeAsset: 0, + amount: 1000, + address: '0x789' + } + + await expect(send(options)).rejects.toThrow(InvalidCurrencyError) + }) }) diff --git a/packages/sdk/src/pallets/xcmPallet/transfer.ts b/packages/sdk/src/pallets/xcmPallet/transfer.ts index 3495b8af..ba502e2b 100644 --- a/packages/sdk/src/pallets/xcmPallet/transfer.ts +++ b/packages/sdk/src/pallets/xcmPallet/transfer.ts @@ -1,6 +1,6 @@ // Contains basic call formatting for different XCM Palletss -import type { TNodePolkadotKusama, TTransferReturn } from '../../types' +import type { TAsset, TNativeAsset, TNodePolkadotKusama, TTransferReturn } from '../../types' import { type TSerializedApiCall, type TRelayToParaOptions, @@ -15,6 +15,7 @@ import { getAssetBySymbolOrId } from '../assets/getAssetBySymbolOrId' import { getDefaultPallet } from '../pallets' import { getNativeAssets, getRelayChainSymbol, hasSupportForAsset } from '../assets' import { getNode, determineRelayChain } from '../../utils' +import { isSymbolSpecifier } from '../../utils/assets/isSymbolSpecifier' const sendCommon = async ( options: TSendOptions, @@ -38,12 +39,6 @@ const sendCommon = async ( throw new Error('Amount is required') } - if ('id' in currency && typeof currency === 'number' && currency > Number.MAX_SAFE_INTEGER) { - throw new InvalidCurrencyError( - 'The provided asset ID is larger than the maximum safe integer value. Please provide it as a string.' - ) - } - // Multi location checks if ('multilocation' in currency && (feeAsset === 0 || feeAsset !== undefined)) { throw new InvalidCurrencyError('Overrided single multi asset cannot be used with fee asset') @@ -116,7 +111,19 @@ const sendCommon = async ( const isBifrost = origin === 'BifrostPolkadot' || origin === 'BifrostKusama' - let asset + if (!assetCheckEnabled && 'symbol' in currency && isSymbolSpecifier(currency.symbol)) { + throw new InvalidCurrencyError( + 'Symbol specifier is not supported when asset check is disabled. Please use normal symbol instead.' + ) + } + + if (!assetCheckEnabled && 'id' in currency) { + throw new InvalidCurrencyError( + 'Asset ID is not supported when asset check is disabled. Please use normal symbol instead' + ) + } + + let asset: TAsset | null // Transfers to AssetHub require the destination asset ID to be used if (!isBridge && isDestAssetHub && pallet === 'XTokens' && !isBifrost) { @@ -128,17 +135,6 @@ const sendCommon = async ( nativeAssets = nativeAssets.filter(nativeAsset => nativeAsset.symbol !== 'DOT') } - if ( - 'symbol' in currency && - nativeAssets.some( - nativeAsset => nativeAsset.symbol.toLowerCase() === currency.symbol.toLowerCase() - ) - ) { - throw new InvalidCurrencyError( - `${currency.symbol} is not supported for transfers to ${destination}.` - ) - } - if (assetCheckEnabled && asset === null) { throw new InvalidCurrencyError( `Destination node ${destination} does not support currency ${JSON.stringify(currency)}.` @@ -150,6 +146,17 @@ const sendCommon = async ( `Origin node ${origin} does not support currency ${asset.symbol}.` ) } + + if ( + 'symbol' in currency && + nativeAssets.some( + nativeAsset => nativeAsset.symbol.toLowerCase() === asset?.symbol?.toLowerCase() + ) + ) { + throw new InvalidCurrencyError( + `${asset?.symbol} is not supported for transfers to ${destination}.` + ) + } } else { asset = assetCheckEnabled ? getAssetBySymbolOrId( @@ -193,6 +200,8 @@ const sendCommon = async ( console.warn('Keep alive check is not supported when using MultiLocation as destination.') } else if (origin === 'Ethereum' || destination === 'Ethereum') { console.warn('Keep alive check is not supported when using Ethereum as origin or destination.') + } else if (!asset) { + console.warn('Keep alive check is not supported when asset check is disabled.') } else { await checkKeepAlive({ originApi: api, @@ -200,18 +209,21 @@ const sendCommon = async ( amount: amountStr ?? '', originNode: origin, destApi: destApiForKeepAlive, - currencySymbol: asset?.symbol ?? ('symbol' in currency ? currency.symbol : undefined), + asset, destNode: destination }) } - const currencyStr = - 'symbol' in currency ? currency.symbol : 'id' in currency ? currency.id.toString() : undefined + // In case asset check is disabled, we create asset object from currency symbol + const resolvedAsset = + asset ?? + ({ + symbol: 'symbol' in currency ? currency.symbol : undefined + } as TNativeAsset) return originNode.transfer({ api, - currencySymbol: asset?.symbol ?? currencyStr, - currencyId: asset?.assetId, + asset: resolvedAsset, amount: amountStr ?? '', address, destination, @@ -266,7 +278,7 @@ export const transferRelayToParaCommon = async ( address, amount: amountStr, destApi: destApiForKeepAlive, - currencySymbol: getRelayChainSymbol(destination), + asset: { symbol: getRelayChainSymbol(destination) }, destNode: destination }) } diff --git a/packages/sdk/src/papi/PapiApi.ts b/packages/sdk/src/papi/PapiApi.ts index 6df34ebd..76d05127 100644 --- a/packages/sdk/src/papi/PapiApi.ts +++ b/packages/sdk/src/papi/PapiApi.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ import type { HexString, TAsset, @@ -12,6 +14,7 @@ import type { IPolkadotApi } from '../api' import { withPolkadotSdkCompat } from 'polkadot-api/polkadot-sdk-compat' import { transform } from './PapiXcmTransformer' import { NodeNotSupportedError } from '../errors' +import { isForeignAsset } from '../utils/assets' const unsupportedNodes = [ 'ComposableFinance', @@ -83,7 +86,7 @@ class PapiApi implements IPolkadotApi { async getBalanceNative(address: string): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const res = await this.api.getUnsafeApi().query.System.Account.getValue(address) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return res.data.free as bigint } @@ -91,14 +94,14 @@ class PapiApi implements IPolkadotApi { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const res = await this.api.getUnsafeApi().query.Assets.Account.getValue(id, address) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return res && res.balance ? BigInt(res.balance) : BigInt(0) } async getMythosForeignBalance(address: string): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const res = await this.api.getUnsafeApi().query.Balances.Account.getValue(address) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return res && res.free ? BigInt(res.free) : BigInt(0) } @@ -111,7 +114,7 @@ class PapiApi implements IPolkadotApi { .getUnsafeApi() .query.ForeignAssets.Account.getValue(transformedMultiLocation, address) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return BigInt(res === undefined ? 0 : res.balance) } @@ -123,27 +126,26 @@ class PapiApi implements IPolkadotApi { const [_address, assetItem] = keyArgs return ( - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access assetItem.toString().toLowerCase() === asset.symbol?.toLowerCase() || - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access - assetItem.toString().toLowerCase() === asset.assetId?.toLowerCase() || + (isForeignAsset(asset) && + assetItem.toString().toLowerCase() === asset.assetId?.toLowerCase()) || (typeof assetItem === 'object' && - 'value' in assetItem && // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + 'value' in assetItem && assetItem.value.toString().toLowerCase() === asset.symbol?.toLowerCase()) || (typeof assetItem === 'object' && 'value' in assetItem && - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + isForeignAsset(asset) && assetItem.value.toString().toLowerCase() === asset.assetId?.toLowerCase()) ) }) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return entry?.value ? BigInt(entry.value.free.toString()) : BigInt(0) } async getBalanceForeignAssetsAccount(address: string, assetId: bigint | number): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const response = await this.api.getUnsafeApi().query.Assets.Account.getValue(assetId, address) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return BigInt(response === undefined ? 0 : response.balance) } diff --git a/packages/sdk/src/papi/assets.ts b/packages/sdk/src/papi/assets.ts index 555627f3..e36ab52c 100644 --- a/packages/sdk/src/papi/assets.ts +++ b/packages/sdk/src/papi/assets.ts @@ -50,4 +50,5 @@ export const getOriginFeeDetails = createPapiApiCall( export * from '../pallets/assets/assets' export * from '../pallets/assets/eds' +export * from '../pallets/assets/assetSelectors' export { getSupportedAssets } from '../pallets/assets/getSupportedAssets' diff --git a/packages/sdk/src/papi/utils.ts b/packages/sdk/src/papi/utils.ts index 430f114e..41e88014 100644 --- a/packages/sdk/src/papi/utils.ts +++ b/packages/sdk/src/papi/utils.ts @@ -24,3 +24,5 @@ export const createPapiApiCall = , TResult return apiCall(optionsWithApi) } } + +export * from '../utils/assets/isForeignAsset' diff --git a/packages/sdk/src/pjs/PolkadotJsApi.ts b/packages/sdk/src/pjs/PolkadotJsApi.ts index d8785a4f..54267b5b 100644 --- a/packages/sdk/src/pjs/PolkadotJsApi.ts +++ b/packages/sdk/src/pjs/PolkadotJsApi.ts @@ -14,6 +14,7 @@ import { u32, type UInt } from '@polkadot/types' import type { AnyTuple, Codec } from '@polkadot/types/types' import type { TBalanceResponse } from '../types/TBalance' import { createApiInstanceForNode } from '../utils' +import { isForeignAsset } from '../utils/assets' const lowercaseFirstLetter = (value: string) => value.charAt(0).toLowerCase() + value.slice(1) @@ -106,13 +107,14 @@ class PolkadotJsApi implements IPolkadotApi { const assetSymbol = assetItem.toString().toLowerCase() return ( assetSymbol === asset.symbol?.toLowerCase() || - assetSymbol === asset.assetId?.toLowerCase() || + (isForeignAsset(asset) && assetSymbol === asset.assetId?.toLowerCase()) || Object.values(assetItem.toHuman() ?? {}) .toString() .toLowerCase() === asset.symbol?.toLowerCase() || - Object.values(assetItem.toHuman() ?? {}) - .toString() - .toLowerCase() === asset.assetId?.toLowerCase() + (isForeignAsset(asset) && + Object.values(assetItem.toHuman() ?? {}) + .toString() + .toLowerCase() === asset.assetId?.toLowerCase()) ) } ) diff --git a/packages/sdk/src/pjs/assets.ts b/packages/sdk/src/pjs/assets.ts index a815d6a5..3373ba6c 100644 --- a/packages/sdk/src/pjs/assets.ts +++ b/packages/sdk/src/pjs/assets.ts @@ -48,4 +48,5 @@ export const getOriginFeeDetails = createPolkadotJsApiCall( export * from '../pallets/assets/assets' export * from '../pallets/assets/eds' +export * from '../pallets/assets/assetSelectors' export { getSupportedAssets } from '../pallets/assets/getSupportedAssets' diff --git a/packages/sdk/src/pjs/utils.ts b/packages/sdk/src/pjs/utils.ts index d2f995b9..55c95532 100644 --- a/packages/sdk/src/pjs/utils.ts +++ b/packages/sdk/src/pjs/utils.ts @@ -24,3 +24,5 @@ export const createPolkadotJsApiCall = , T return apiCall(optionsWithApi) } } + +export * from '../utils/assets/isForeignAsset' diff --git a/packages/sdk/src/types/TAssets.ts b/packages/sdk/src/types/TAssets.ts index f7355f86..f4d9a676 100644 --- a/packages/sdk/src/types/TAssets.ts +++ b/packages/sdk/src/types/TAssets.ts @@ -1,28 +1,29 @@ import type { TMultiLocation, TNodeWithRelayChains } from '../types' import { type TRelayChainSymbol } from '../types' -export type TAsset = TNativeAssetDetails | TAssetDetails - -export type TAssetDetails = { - assetId: string +type TBaseAsset = { symbol?: string decimals?: number manuallyAdded?: boolean + alias?: string } -export type TNativeAssetDetails = { - assetId?: string +export type TNativeAsset = TBaseAsset & { symbol: string - decimals: number - manuallyAdded?: boolean } +export type TForeignAsset = TBaseAsset & { + assetId: string +} + +export type TAsset = TNativeAsset | TForeignAsset + export type TNodeAssets = { paraId?: number relayChainAssetSymbol: TRelayChainSymbol nativeAssetSymbol: string - nativeAssets: TNativeAssetDetails[] - otherAssets: TAssetDetails[] + nativeAssets: TNativeAsset[] + otherAssets: TForeignAsset[] multiLocations?: TMultiLocation[] } diff --git a/packages/sdk/src/types/TCurrency.ts b/packages/sdk/src/types/TCurrency.ts index 4d813d60..5d317eac 100644 --- a/packages/sdk/src/types/TCurrency.ts +++ b/packages/sdk/src/types/TCurrency.ts @@ -4,8 +4,15 @@ import type { Version } from './TTransfer' export type TCurrency = string | number | bigint +export type TSymbolSpecifier = { + type: 'Native' | 'Foreign' | 'ForeignAbstract' + value: string +} + +export type TCurrencySymbolValue = string | TSymbolSpecifier + export type TCurrencySymbol = { - symbol: string + symbol: TCurrencySymbolValue } export type TCurrencyCore = @@ -14,6 +21,14 @@ export type TCurrencyCore = id: TCurrency } +export type TCurrencyCoreV1 = + | { + symbol: string + } + | { + id: TCurrency + } + export type TCurrencyInput = | TCurrencyCore | { @@ -47,20 +62,20 @@ export type TCurrencySelectionHeaderArr = { [key in Version]?: [TCurrencySelection | TCurrencySelectionV4] } -export type TForeignAsset = { +export type TXcmForeignAsset = { ForeignAsset: string | number | bigint | undefined } export type TForeignAssetId = { - ForeignAssetId: string | undefined + ForeignAssetId: bigint | undefined } -export type TForeignOrTokenAsset = TForeignAsset | { Token: string | undefined } +export type TForeignOrTokenAsset = TXcmForeignAsset | { Token: string | undefined } -export type TForeignOrNativeAsset = TForeignAsset | 'Native' +export type TForeignOrNativeAsset = TXcmForeignAsset | 'Native' export type TXcmAsset = { - XCM: string | undefined + XCM: number | undefined } export type TMantaAsset = { @@ -74,7 +89,7 @@ export type TNodleAsset = 'NodleNative' export type TZeitgeistAsset = 'Ztg' export type TOtherReserveAsset = { - OtherReserve: string | undefined + OtherReserve: string | bigint | undefined } export type TSelfReserveAsset = 'SelfReserve' @@ -92,7 +107,7 @@ export type TBifrostToken = export type TXTokensCurrencySelection = | TCurrencySelectionHeader | TCurrencySelectionHeaderArr - | TForeignAsset + | TXcmForeignAsset | TForeignAssetId | TForeignOrTokenAsset | TXcmAsset @@ -101,4 +116,5 @@ export type TXTokensCurrencySelection = | TBifrostToken | string | bigint + | number | undefined diff --git a/packages/sdk/src/types/TTransfer.ts b/packages/sdk/src/types/TTransfer.ts index 4d413531..562c39c0 100644 --- a/packages/sdk/src/types/TTransfer.ts +++ b/packages/sdk/src/types/TTransfer.ts @@ -7,6 +7,7 @@ import type { TCurrency, TCurrencyInput, TCurrencySelectionHeaderArr } from './T import type { IPolkadotApi } from '../api/IPolkadotApi' import type { TPallet } from './TPallet' import type { WithApi } from './TApi' +import type { TAsset } from './TAssets' export type HexString = `0x${string}` @@ -18,8 +19,7 @@ export type PolkadotXCMTransferInput = { address: TAddress currencySelection: TCurrencySelectionHeaderArr scenario: TScenario - currencySymbol: string | undefined - currencyId: string | undefined + asset: TAsset destination?: TDestination paraIdTo?: number feeAsset?: TCurrency @@ -31,8 +31,7 @@ export type PolkadotXCMTransferInput = { export type XTokensTransferInput = { api: IPolkadotApi - currency: string | undefined - currencyID: string | undefined + asset: TAsset amount: string addressSelection: TMultiLocationHeader fees: number @@ -47,8 +46,7 @@ export type XTokensTransferInput = { export type XTransferTransferInput = { api: IPolkadotApi - currency: string | undefined - currencyID: string | undefined + asset: TAsset amount: string recipientAddress: TAddress origin: TNode @@ -152,8 +150,7 @@ export type TSendOptions = WithApi, TAp export type TSendInternalOptions = TSendBaseOptions & { api: IPolkadotApi - currencySymbol: string | undefined - currencyId: string | undefined + asset: TAsset amount: string overridedCurrencyMultiLocation?: TMultiLocation | TMultiAsset[] serializedApiCallEnabled?: boolean @@ -215,7 +212,7 @@ export type CheckKeepAliveOptions = { amount: string originNode?: TNodePolkadotKusama destApi: IPolkadotApi - currencySymbol?: string + asset: TAsset destNode?: TNodePolkadotKusama } diff --git a/packages/sdk/src/utils/assets/index.ts b/packages/sdk/src/utils/assets/index.ts new file mode 100644 index 00000000..9638c95b --- /dev/null +++ b/packages/sdk/src/utils/assets/index.ts @@ -0,0 +1 @@ +export * from './isForeignAsset' diff --git a/packages/sdk/src/utils/assets/isForeignAsset.ts b/packages/sdk/src/utils/assets/isForeignAsset.ts new file mode 100644 index 00000000..ae32ed7d --- /dev/null +++ b/packages/sdk/src/utils/assets/isForeignAsset.ts @@ -0,0 +1,5 @@ +import type { TAsset, TForeignAsset } from '../../types' + +export const isForeignAsset = (asset: TAsset): asset is TForeignAsset => { + return 'assetId' in asset +} diff --git a/packages/sdk/src/utils/assets/isSymbolSpecifier.test.ts b/packages/sdk/src/utils/assets/isSymbolSpecifier.test.ts new file mode 100644 index 00000000..94899a8a --- /dev/null +++ b/packages/sdk/src/utils/assets/isSymbolSpecifier.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest' +import { isSymbolSpecifier } from './isSymbolSpecifier' +import type { TCurrencySymbolValue, TSymbolSpecifier } from '../../types' + +describe('isSymbolSpecifier', () => { + it('should return true for a valid TSymbolSpecifier object', () => { + const validSymbolSpecifier: TSymbolSpecifier = { + type: 'Native', + value: 'TEST' + } + + expect(isSymbolSpecifier(validSymbolSpecifier)).toBe(true) + }) + + it('should return false for an object missing type property', () => { + const invalidSymbolSpecifier = { + value: 'TEST' + } + + expect(isSymbolSpecifier(invalidSymbolSpecifier as TCurrencySymbolValue)).toBe(false) + }) + + it('should return false for an object missing value property', () => { + const invalidSymbolSpecifier = { + type: 'Native' + } + + expect(isSymbolSpecifier(invalidSymbolSpecifier as TCurrencySymbolValue)).toBe(false) + }) + + it('should return false for a non-object input (e.g., string)', () => { + const nonObjectInput = 'TEST' + + expect(isSymbolSpecifier(nonObjectInput as TCurrencySymbolValue)).toBe(false) + }) + + it('should return false for a non-object input (e.g., number)', () => { + const nonObjectInput = 123 + + expect(isSymbolSpecifier(nonObjectInput as unknown as TCurrencySymbolValue)).toBe(false) + }) +}) diff --git a/packages/sdk/src/utils/assets/isSymbolSpecifier.ts b/packages/sdk/src/utils/assets/isSymbolSpecifier.ts new file mode 100644 index 00000000..5c7bd890 --- /dev/null +++ b/packages/sdk/src/utils/assets/isSymbolSpecifier.ts @@ -0,0 +1,11 @@ +import type { TCurrencySymbolValue, TSymbolSpecifier } from '../../types' + +export const isSymbolSpecifier = ( + currencySymbolValue: TCurrencySymbolValue +): currencySymbolValue is TSymbolSpecifier => { + return ( + typeof currencySymbolValue === 'object' && + 'type' in currencySymbolValue && + 'value' in currencySymbolValue + ) +} diff --git a/packages/sdk/src/utils/index.ts b/packages/sdk/src/utils/index.ts index e55b03e0..398e12c5 100644 --- a/packages/sdk/src/utils/index.ts +++ b/packages/sdk/src/utils/index.ts @@ -33,3 +33,4 @@ export { getNode } from './getNode' export { createApiInstanceForNode } from './createApiInstanceForNode' export { getNodeEndpointOption } from './getNodeEndpointOption' export { determineRelayChainSymbol } from './determineRelayChainSymbol' +export * from './assets' diff --git a/packages/xcm-router/src/RouterBuilder.ts b/packages/xcm-router/src/RouterBuilder.ts index 4554be7f..7a11f4b5 100644 --- a/packages/xcm-router/src/RouterBuilder.ts +++ b/packages/xcm-router/src/RouterBuilder.ts @@ -6,7 +6,7 @@ import { TransactionType, type TTransferOptions, } from '.'; -import type { TCurrencyCore } from '@paraspell/sdk'; +import type { TCurrencyCoreV1 } from '@paraspell/sdk'; import { type TNodeWithRelayChains } from '@paraspell/sdk'; import { type Signer as EthSigner } from 'ethers'; @@ -14,8 +14,8 @@ export interface TRouterBuilderOptions { from?: TNodeWithRelayChains; exchange?: TExchangeNode; to?: TNodeWithRelayChains; - currencyFrom?: TCurrencyCore; - currencyTo?: TCurrencyCore; + currencyFrom?: TCurrencyCoreV1; + currencyTo?: TCurrencyCoreV1; amount?: string; injectorAddress?: string; evmInjectorAddress?: string; @@ -79,7 +79,7 @@ export class RouterBuilderObject { * @param currencyFrom - The currency to send. * @returns The current builder instance. */ - currencyFrom(currencyFrom: TCurrencyCore): this { + currencyFrom(currencyFrom: TCurrencyCoreV1): this { this._routerBuilderOptions.currencyFrom = currencyFrom; return this; } @@ -90,7 +90,7 @@ export class RouterBuilderObject { * @param currencyTo - The currency to receive. * @returns The current builder instance. */ - currencyTo(currencyTo: TCurrencyCore): this { + currencyTo(currencyTo: TCurrencyCoreV1): this { this._routerBuilderOptions.currencyTo = currencyTo; return this; } diff --git a/packages/xcm-router/src/assets/assets.test.ts b/packages/xcm-router/src/assets/assets.test.ts index 1834aef5..439ac09d 100644 --- a/packages/xcm-router/src/assets/assets.test.ts +++ b/packages/xcm-router/src/assets/assets.test.ts @@ -6,7 +6,7 @@ import { getSupportedAssetsFrom, getSupportedAssetsTo, } from './assets'; -import type { TAsset, TCurrencyCore, TNodeWithRelayChains } from '@paraspell/sdk'; +import type { TAsset, TCurrencyCoreV1, TNodeWithRelayChains } from '@paraspell/sdk'; import { getAssets } from '@paraspell/sdk'; import type { TAutoSelect, TExchangeNode } from '../types'; @@ -17,7 +17,7 @@ vi.mock('@paraspell/sdk', () => ({ describe('supportsCurrency', () => { it('should return true when currency is supported by exchange node', () => { const exchangeNode: TExchangeNode = 'HydrationDex'; - const currency = { symbol: 'HDX' } as TCurrencyCore; + const currency = { symbol: 'HDX' } as TCurrencyCoreV1; const result = supportsCurrency(exchangeNode, currency); expect(result).toBe(true); @@ -25,7 +25,7 @@ describe('supportsCurrency', () => { it('should return false when currency is not supported by exchange node', () => { const exchangeNode: TExchangeNode = 'AcalaDex'; - const currency = { symbol: 'XYZ' } as TCurrencyCore; + const currency = { symbol: 'XYZ' } as TCurrencyCoreV1; const result = supportsCurrency(exchangeNode, currency); expect(result).toBe(false); @@ -35,7 +35,7 @@ describe('supportsCurrency', () => { describe('findAssetFrom', () => { it('should return correct asset when found in node assets', () => { const fromNode = 'Hydration' as TNodeWithRelayChains; - const currency = { symbol: 'ETH' } as TCurrencyCore; + const currency = { symbol: 'ETH' } as TCurrencyCoreV1; const assets = [{ symbol: 'ETH', assetId: 'eth-id' }]; vi.mocked(getAssets).mockReturnValue(assets); @@ -45,7 +45,7 @@ describe('findAssetFrom', () => { it('should return undefined when asset not found in node', () => { const fromNode = 'Hydration' as TNodeWithRelayChains; - const currency = { symbol: 'BTC' } as TCurrencyCore; + const currency = { symbol: 'BTC' } as TCurrencyCoreV1; const assets = [{ symbol: 'ETH', assetId: 'eth-id' }]; vi.mocked(getAssets).mockReturnValue(assets); @@ -59,7 +59,7 @@ describe('findAssetTo', () => { const fromNode: TNodeWithRelayChains = 'Acala'; const exchange: TExchangeNode = 'HydrationDex'; const toNode: TNodeWithRelayChains = 'Hydration'; - const currency = { symbol: 'USDT' } as TCurrencyCore; + const currency = { symbol: 'USDT' } as TCurrencyCoreV1; vi.mocked(getAssets).mockReturnValue([{ symbol: 'USDT', assetId: '10' }] as TAsset[]); const result = findAssetTo(exchange, fromNode, toNode, currency); diff --git a/packages/xcm-router/src/assets/assets.ts b/packages/xcm-router/src/assets/assets.ts index 898d8bca..078fe8cc 100644 --- a/packages/xcm-router/src/assets/assets.ts +++ b/packages/xcm-router/src/assets/assets.ts @@ -1,11 +1,14 @@ -import type { TCurrencyCore, TNodeWithRelayChains, TAsset as SdkTAsset } from '@paraspell/sdk'; -import { getAssets } from '@paraspell/sdk'; +import type { TNodeWithRelayChains, TAsset as SdkTAsset, TCurrencyCoreV1 } from '@paraspell/sdk'; +import { getAssets, isForeignAsset } from '@paraspell/sdk'; import * as assetsMapJson from '../consts/assets.json' assert { type: 'json' }; import type { TAssetsRecord, TAutoSelect, TExchangeNode } from '../types'; const assetsMap = assetsMapJson as TAssetsRecord; -export const supportsCurrency = (exchangeNode: TExchangeNode, currency: TCurrencyCore): boolean => { +export const supportsCurrency = ( + exchangeNode: TExchangeNode, + currency: TCurrencyCoreV1, +): boolean => { const assets = assetsMap[exchangeNode]; return 'symbol' in currency ? assets.some((asset) => asset.symbol === currency.symbol) @@ -15,17 +18,21 @@ export const supportsCurrency = (exchangeNode: TExchangeNode, currency: TCurrenc export const findAssetFrom = ( from: TNodeWithRelayChains, exchange: TExchangeNode | undefined, - currency: TCurrencyCore, + currency: TCurrencyCoreV1, ): SdkTAsset | undefined => { const fromAssets = getAssets(from); if (exchange === undefined) { return getAssets(from).find((asset) => - 'symbol' in currency ? asset.symbol === currency.symbol : asset.assetId === currency.id, + 'symbol' in currency + ? asset.symbol === currency.symbol + : isForeignAsset(asset) && asset.assetId === currency.id, ); } return fromAssets.find((asset) => - 'symbol' in currency ? asset.symbol === currency.symbol : asset.assetId === currency.id, + 'symbol' in currency + ? asset.symbol === currency.symbol + : isForeignAsset(asset) && asset.assetId === currency.id, ); }; @@ -33,18 +40,22 @@ export const findAssetTo = ( _exchange: TExchangeNode, from: TNodeWithRelayChains, _to: TNodeWithRelayChains, - currency: TCurrencyCore, + currency: TCurrencyCoreV1, isAutomaticSelection = false, ): SdkTAsset | undefined => { const fromAssets = getAssets(from); if (isAutomaticSelection) { return fromAssets.find((asset) => - 'symbol' in currency ? asset.symbol === currency.symbol : asset.assetId === currency.id, + 'symbol' in currency + ? asset.symbol === currency.symbol + : isForeignAsset(asset) && asset.assetId === currency.id, ); } return fromAssets.find((asset) => - 'symbol' in currency ? asset.symbol === currency.symbol : asset.assetId === currency.id, + 'symbol' in currency + ? asset.symbol === currency.symbol + : isForeignAsset(asset) && asset.assetId === currency.id, ); }; diff --git a/packages/xcm-router/src/dexNodes/Hydration/HydrationDex.test.ts b/packages/xcm-router/src/dexNodes/Hydration/HydrationDex.test.ts index 4f8167b8..f7473c27 100644 --- a/packages/xcm-router/src/dexNodes/Hydration/HydrationDex.test.ts +++ b/packages/xcm-router/src/dexNodes/Hydration/HydrationDex.test.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest'; import { type TTransferOptionsModified } from '../../types'; import HydrationExchangeNode from './HydrationDex'; import { MOCK_TRANSFER_OPTIONS, performSwap } from '../../utils/utils.test'; -import type { TCurrencyCore, TNodePolkadotKusama } from '@paraspell/sdk'; +import type { TCurrencyCoreV1, TNodePolkadotKusama } from '@paraspell/sdk'; import { type TNodeWithRelayChains } from '@paraspell/sdk'; import { SmallAmountError } from '../../errors/SmallAmountError'; import type ExchangeNode from '../DexNode'; @@ -13,8 +13,8 @@ import { findAssetFrom, findAssetTo } from '../../assets/assets'; export async function testSwap( dex: ExchangeNode, exchange: TNodePolkadotKusama, - currencyFrom: TCurrencyCore, - currencyTo: TCurrencyCore, + currencyFrom: TCurrencyCoreV1, + currencyTo: TCurrencyCoreV1, amount: string, to: TNodeWithRelayChains, from: TNodeWithRelayChains, diff --git a/packages/xcm-router/src/dexNodes/Hydration/utils.test.ts b/packages/xcm-router/src/dexNodes/Hydration/utils.test.ts index c9a6a70b..fc135169 100644 --- a/packages/xcm-router/src/dexNodes/Hydration/utils.test.ts +++ b/packages/xcm-router/src/dexNodes/Hydration/utils.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import type { Asset, TradeRouter } from '@galacticcouncil/sdk'; -import type { TCurrencyCore } from '@paraspell/sdk'; +import type { TCurrencyCoreV1 } from '@paraspell/sdk'; import { getAssetInfo } from './utils'; describe('getAssetInfo', () => { @@ -21,7 +21,7 @@ describe('getAssetInfo', () => { it('should return asset by symbol if found', async () => { const spy = vi.spyOn(mockTradeRouter, 'getAllAssets').mockResolvedValue(mockAssets); - const currency: TCurrencyCore = { symbol: 'BTC' }; + const currency: TCurrencyCoreV1 = { symbol: 'BTC' }; const asset = await getAssetInfo(mockTradeRouter, currency); expect(asset).toEqual(mockAssets[0]); @@ -31,7 +31,7 @@ describe('getAssetInfo', () => { it('should return asset by id if found', async () => { const spy = vi.spyOn(mockTradeRouter, 'getAllAssets').mockResolvedValue(mockAssets); - const currency: TCurrencyCore = { id: '2' }; + const currency: TCurrencyCoreV1 = { id: '2' }; const asset = await getAssetInfo(mockTradeRouter, currency); expect(asset).toEqual(mockAssets[1]); @@ -41,7 +41,7 @@ describe('getAssetInfo', () => { it('should return undefined if asset is not found', async () => { const spy = vi.spyOn(mockTradeRouter, 'getAllAssets').mockResolvedValue(mockAssets); - const currency: TCurrencyCore = { symbol: 'XRP' }; // Non-existent symbol + const currency: TCurrencyCoreV1 = { symbol: 'XRP' }; // Non-existent symbol const asset = await getAssetInfo(mockTradeRouter, currency); expect(asset).toBeUndefined(); @@ -55,7 +55,7 @@ describe('getAssetInfo', () => { ]; vi.spyOn(mockTradeRouter, 'getAllAssets').mockResolvedValue(duplicateAssets); - const currency: TCurrencyCore = { symbol: 'BTC' }; + const currency: TCurrencyCoreV1 = { symbol: 'BTC' }; await expect(getAssetInfo(mockTradeRouter, currency)).rejects.toThrow( 'Duplicate currency found in HydrationDex.', @@ -69,7 +69,7 @@ describe('getAssetInfo', () => { ]; vi.spyOn(mockTradeRouter, 'getAllAssets').mockResolvedValue(duplicateAssets); - const currency: TCurrencyCore = { id: '1' }; + const currency: TCurrencyCoreV1 = { id: '1' }; await expect(getAssetInfo(mockTradeRouter, currency)).rejects.toThrow( 'Duplicate currency found in HydrationDex.', diff --git a/packages/xcm-router/src/dexNodes/Hydration/utils.ts b/packages/xcm-router/src/dexNodes/Hydration/utils.ts index 5fad9a31..294cef15 100644 --- a/packages/xcm-router/src/dexNodes/Hydration/utils.ts +++ b/packages/xcm-router/src/dexNodes/Hydration/utils.ts @@ -1,6 +1,6 @@ import { BigNumber, type TradeRouter, bnum, type Asset } from '@galacticcouncil/sdk'; import { type TSwapOptions } from '../../types'; -import type { TCurrencyCore } from '@paraspell/sdk'; +import type { TCurrencyCoreV1 } from '@paraspell/sdk'; import { type TNode, type Extrinsic, getAssetDecimals } from '@paraspell/sdk'; import { FEE_BUFFER } from '../../consts/consts'; import { calculateTransactionFee } from '../../utils/utils'; @@ -113,7 +113,7 @@ export const getMinAmountOut = ( export const getAssetInfo = async ( tradeRouter: TradeRouter, - currency: TCurrencyCore, + currency: TCurrencyCoreV1, ): Promise => { const assets = await tradeRouter.getAllAssets(); diff --git a/packages/xcm-router/src/dexNodes/Interlay/InterlayDex.ts b/packages/xcm-router/src/dexNodes/Interlay/InterlayDex.ts index d8c3b5c4..bc8aa586 100644 --- a/packages/xcm-router/src/dexNodes/Interlay/InterlayDex.ts +++ b/packages/xcm-router/src/dexNodes/Interlay/InterlayDex.ts @@ -1,3 +1,4 @@ +import type { TForeignAsset } from '@paraspell/sdk'; import { getAssets, getNodeProvider } from '@paraspell/sdk'; import ExchangeNode from '../DexNode'; import type { TSwapResult, TSwapOptions, TAssets } from '../../types'; @@ -92,7 +93,7 @@ class InterlayExchangeNode extends ExchangeNode { } async getAssets(_api: ApiPromise): Promise { - const assets = getAssets(this.node); + const assets = getAssets(this.node) as TForeignAsset[]; const transformedAssets = assets.map((asset) => ({ symbol: asset.symbol ?? '', id: asset.assetId, diff --git a/packages/xcm-router/src/dexNodes/Interlay/utils.ts b/packages/xcm-router/src/dexNodes/Interlay/utils.ts index fc271dbd..9fcd81e0 100644 --- a/packages/xcm-router/src/dexNodes/Interlay/utils.ts +++ b/packages/xcm-router/src/dexNodes/Interlay/utils.ts @@ -1,9 +1,9 @@ import { type InterBtcApi, type CurrencyExt } from 'inter-exchange'; -import type { TCurrencyCore } from '@paraspell/sdk'; +import type { TCurrencyCoreV1 } from '@paraspell/sdk'; import { type TNode, getAssetId } from '@paraspell/sdk'; export const getCurrency = async ( - currency: TCurrencyCore, + currency: TCurrencyCoreV1, interBTC: InterBtcApi, node: TNode, ): Promise => { diff --git a/packages/xcm-router/src/transfer/utils.ts b/packages/xcm-router/src/transfer/utils.ts index 7358c070..76c20cc7 100644 --- a/packages/xcm-router/src/transfer/utils.ts +++ b/packages/xcm-router/src/transfer/utils.ts @@ -1,4 +1,4 @@ -import type { TAsset, TCurrencyCore } from '@paraspell/sdk'; +import type { TAsset, TCurrencyCoreV1 } from '@paraspell/sdk'; import { type Extrinsic, Builder } from '@paraspell/sdk'; import { type ApiPromise } from '@polkadot/api'; import type { TExchangeNode } from '../types'; @@ -41,9 +41,9 @@ export const buildToExchangeExtrinsic = async ( export const getCurrencyExchange = ( exchange: TExchangeNode, - currencyOrigin: TCurrencyCore, + currencyOrigin: TCurrencyCoreV1, assetToOrigin: TAsset | undefined, -): TCurrencyCore => { +): TCurrencyCoreV1 => { if ('symbol' in currencyOrigin) return { symbol: currencyOrigin.symbol }; const exchangeAsset = findAssetInExchangeBySymbol(exchange, assetToOrigin?.symbol ?? ''); if (!exchangeAsset) { diff --git a/packages/xcm-router/src/types.ts b/packages/xcm-router/src/types.ts index 78d26143..0870b414 100644 --- a/packages/xcm-router/src/types.ts +++ b/packages/xcm-router/src/types.ts @@ -2,9 +2,9 @@ import type { TNodeWithRelayChains, Extrinsic, TSerializedEthTransfer, - TCurrencyCore, TAsset as SdkTAsset, TNodePolkadotKusama, + TCurrencyCoreV1, } from '@paraspell/sdk'; import { type Signer } from '@polkadot/types/types'; import { type EXCHANGE_NODES } from './consts/consts'; @@ -13,8 +13,8 @@ import type { Signer as EthSigner } from 'ethers'; export type TExchangeNode = (typeof EXCHANGE_NODES)[number]; export interface TSwapOptions { - currencyFrom: TCurrencyCore; - currencyTo: TCurrencyCore; + currencyFrom: TCurrencyCoreV1; + currencyTo: TCurrencyCoreV1; assetFrom?: SdkTAsset; assetTo?: SdkTAsset; amount: string; @@ -83,11 +83,11 @@ export interface TTransferOptions { /** * The origin currency. */ - currencyFrom: TCurrencyCore; + currencyFrom: TCurrencyCoreV1; /** * The destination currency that the origin currency will be exchanged to. */ - currencyTo: TCurrencyCore; + currencyTo: TCurrencyCoreV1; /** * The amount to transfer. * @example '1000000000000000' diff --git a/packages/xcm-router/src/utils/utils.ts b/packages/xcm-router/src/utils/utils.ts index 7b1c2499..eb5a4554 100644 --- a/packages/xcm-router/src/utils/utils.ts +++ b/packages/xcm-router/src/utils/utils.ts @@ -1,4 +1,4 @@ -import type { TCurrencyCore } from '@paraspell/sdk'; +import type { TCurrencyCoreV1 } from '@paraspell/sdk'; import { type TNodeWithRelayChains, type Extrinsic, InvalidCurrencyError } from '@paraspell/sdk'; import BigNumber from 'bignumber.js'; import { type TTxProgressInfo } from '../types'; @@ -26,7 +26,7 @@ export const maybeUpdateTransferStatus = ( export const validateRelayChainCurrency = ( originNode: TNodeWithRelayChains, - currency: TCurrencyCore, + currency: TCurrencyCoreV1, ): void => { if ( 'symbol' in currency &&