From 0f9a06113a60bd2ae37be99260268de5d9675126 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Tue, 5 Sep 2023 17:24:01 +0200 Subject: [PATCH 01/15] Fetch chaindata from Talisman chaindata --- chaindata/index.ts | 26 ++++++++++++++++ chaindata/ss58registry.ts | 15 +++++++++ package.json | 3 +- src/contracts/identity/context.tsx | 50 +++++++++++++++++++++++------- 4 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 chaindata/index.ts create mode 100644 chaindata/ss58registry.ts diff --git a/chaindata/index.ts b/chaindata/index.ts new file mode 100644 index 0000000..887571c --- /dev/null +++ b/chaindata/index.ts @@ -0,0 +1,26 @@ +import { gql, request } from "graphql-request" + +const graphqlUrl = "https://squid.subsquid.io/chaindata/v/v4/graphql" + +/// NOTE: this file is copied from the talisman chaindata. + +const chainsQuery = gql` +query chains { + chains(orderBy: sortIndex_ASC) { + id + name + paraId + relay { + id + } + rpcs { + url + } + } +} +`; + +export const getChains = async (): Promise> => { + const result: any = await request(graphqlUrl, chainsQuery); + return result.chains; +} diff --git a/chaindata/ss58registry.ts b/chaindata/ss58registry.ts new file mode 100644 index 0000000..792eab8 --- /dev/null +++ b/chaindata/ss58registry.ts @@ -0,0 +1,15 @@ +import axios from 'axios'; + +const ss58registry = async (chainName: string): Promise => { + const ss58Registry = (await axios.get('https://raw.githubusercontent.com/paritytech/ss58-registry/main/ss58-registry.json')).data.registry; + + const result: any = ss58Registry.find((chain: any) => chain.network === chainName); + + if (!result) { + return null + } + + return result.prefix; +} + +export default ss58registry; \ No newline at end of file diff --git a/package.json b/package.json index 4f77381..a02be8f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "aes-js": "^3.1.2", "axios": "^1.4.0", "clsx": "^1.1.1", + "graphql-request": "^6.1.0", "material-ui-confirm": "^3.0.9", "next": "^13.3.1", "react": "^18.2.0", @@ -61,4 +62,4 @@ "typescript": "5.1.3", "webpack": "^5.81.0" } -} +} \ No newline at end of file diff --git a/src/contracts/identity/context.tsx b/src/contracts/identity/context.tsx index 238a914..7c08d87 100644 --- a/src/contracts/identity/context.tsx +++ b/src/contracts/identity/context.tsx @@ -26,6 +26,9 @@ import { Chains, IdentityNo, } from '../types'; +import { getChains } from 'chaindata'; +import { RELAY_CHAIN } from '@/consts'; +import ss58registry from 'chaindata/ss58registry'; interface IdentityContract { identityNo: number | null; @@ -109,23 +112,34 @@ const IdentityContractProvider = ({ children }: Props) => { const count = rpcUrls.length; const rpcIndex = Math.min(Math.floor(Math.random() * count), count - 1); const rpc = rpcUrls[rpcIndex]; + try { - const provider = new WsProvider(rpc); - const api = new ApiPromise({ provider, rpc: jsonrpc }); + const chainData = (await getChains()).find( + (chain) => chain.paraId ? + chain.paraId === chainId && chain.relay.id === RELAY_CHAIN + : + chainId === 0 && chain.id === RELAY_CHAIN + ); + + console.log(chainData); + + if (!chainData) { + return null; + } + console.log("HEY"); - await api.isReady; + const ss58Result = await ss58registry(chainData.id); - const ss58Prefix: number = - api.consts.system.ss58Prefix.toPrimitive() as number; - const name = (await api.rpc.system.chain()).toString(); - const paraId = chainId; + const rpcCount = chainData.rpcs.length; + const rpcIndex = Math.min(Math.floor(Math.random() * rpcCount), rpcCount - 1); - await api.disconnect(); + const ss58Prefix = ss58Result ? ss58Result : await fetchSs58Prefix(chainData.rpcs[rpcIndex].url); + console.log(`${chainData.name}: ${ss58Prefix}`); return { - name, - ss58Prefix, - paraId, + name: chainData.name, + ss58Prefix: ss58Prefix, + paraId: chainId, }; } catch (e) { toastError && toastError(`Failed to get chain info for ${rpc}`); @@ -133,6 +147,20 @@ const IdentityContractProvider = ({ children }: Props) => { } }; + const fetchSs58Prefix = async (rpc: string): Promise => { + const provider = new WsProvider(rpc); + const api = new ApiPromise({ provider, rpc: jsonrpc }); + + await api.isReady; + + const ss58Prefix: number = + api.consts.system.ss58Prefix.toPrimitive() as number; + + await api.disconnect(); + + return ss58Prefix; + } + setLoadingChains(true); try { const result = await contractQuery( From db29c2c6450b5cd15995849ad4d2401cf84e24b5 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Tue, 5 Sep 2023 17:27:17 +0200 Subject: [PATCH 02/15] fix linter errors --- src/contracts/identity/context.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/contracts/identity/context.tsx b/src/contracts/identity/context.tsx index 7c08d87..d302678 100644 --- a/src/contracts/identity/context.tsx +++ b/src/contracts/identity/context.tsx @@ -7,6 +7,8 @@ import { useContract, useInkathon, } from '@scio-labs/use-inkathon'; +import { getChains } from 'chaindata'; +import ss58registry from 'chaindata/ss58registry'; import { createContext, useCallback, @@ -15,6 +17,7 @@ import { useState, } from 'react'; +import { RELAY_CHAIN } from '@/consts'; import { useToast } from '@/contexts/Toast'; import { IdentityMetadata } from '.'; @@ -26,9 +29,6 @@ import { Chains, IdentityNo, } from '../types'; -import { getChains } from 'chaindata'; -import { RELAY_CHAIN } from '@/consts'; -import ss58registry from 'chaindata/ss58registry'; interface IdentityContract { identityNo: number | null; @@ -121,12 +121,9 @@ const IdentityContractProvider = ({ children }: Props) => { chainId === 0 && chain.id === RELAY_CHAIN ); - console.log(chainData); - if (!chainData) { return null; } - console.log("HEY"); const ss58Result = await ss58registry(chainData.id); @@ -135,7 +132,6 @@ const IdentityContractProvider = ({ children }: Props) => { const ss58Prefix = ss58Result ? ss58Result : await fetchSs58Prefix(chainData.rpcs[rpcIndex].url); - console.log(`${chainData.name}: ${ss58Prefix}`); return { name: chainData.name, ss58Prefix: ss58Prefix, From 7eceb93ee06656be48b283d63af6cb57a64f6fca Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Tue, 5 Sep 2023 17:31:36 +0200 Subject: [PATCH 03/15] add graphql --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a02be8f..76adfb1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "aes-js": "^3.1.2", "axios": "^1.4.0", "clsx": "^1.1.1", + "graphql": "^16.8.0", "graphql-request": "^6.1.0", "material-ui-confirm": "^3.0.9", "next": "^13.3.1", @@ -62,4 +63,4 @@ "typescript": "5.1.3", "webpack": "^5.81.0" } -} \ No newline at end of file +} From 5403e92cd9d3d4ad3224c4bb5fb39d157a2a045e Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Tue, 5 Sep 2023 19:30:53 +0200 Subject: [PATCH 04/15] Native asset transfers --- __tests__/crossChainRouter.test.ts | 4 +- __tests__/teleport.test.ts | 4 +- __tests__/transactionRouter.test.ts | 313 ++++++++++++++++++ __tests__/transferAsset.test.ts | 4 +- chaindata/index.ts | 14 + src/pages/transfer.tsx | 78 ++++- src/utils/nativeTransfer/assets.ts | 37 +++ src/utils/nativeTransfer/index.ts | 35 ++ .../index.ts | 0 .../reserveTransfer.test.ts | 0 .../reserveTransfer.ts | 0 .../teleportTransfer.ts | 0 .../teleportableRoutes.ts | 0 .../transferAsset.test.ts | 0 .../transferAsset.ts | 0 .../types.ts | 0 16 files changed, 476 insertions(+), 13 deletions(-) create mode 100644 __tests__/transactionRouter.test.ts create mode 100644 src/utils/nativeTransfer/assets.ts create mode 100644 src/utils/nativeTransfer/index.ts rename src/utils/{transactionRouter => xcmTransfer}/index.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/reserveTransfer.test.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/reserveTransfer.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/teleportTransfer.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/teleportableRoutes.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/transferAsset.test.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/transferAsset.ts (100%) rename src/utils/{transactionRouter => xcmTransfer}/types.ts (100%) diff --git a/__tests__/crossChainRouter.test.ts b/__tests__/crossChainRouter.test.ts index e9671e7..9e5bc11 100644 --- a/__tests__/crossChainRouter.test.ts +++ b/__tests__/crossChainRouter.test.ts @@ -2,8 +2,8 @@ import { ApiPromise, Keyring, WsProvider } from "@polkadot/api"; import { KeyringPair } from "@polkadot/keyring/types"; import { u8aToHex } from '@polkadot/util'; -import TransactionRouter from "../src/utils/transactionRouter"; -import { Fungible, Receiver, Sender } from "../src/utils/transactionRouter/types"; +import TransactionRouter from "../src/utils/xcmTransfer"; +import { Fungible, Receiver, Sender } from "../src/utils/xcmTransfer/types"; import IdentityContractFactory from "../types/constructors/identity"; import IdentityContract from "../types/contracts/identity"; import { AccountType, ChainInfo } from "../types/types-arguments/identity"; diff --git a/__tests__/teleport.test.ts b/__tests__/teleport.test.ts index d73f5e0..9f8cc79 100644 --- a/__tests__/teleport.test.ts +++ b/__tests__/teleport.test.ts @@ -1,8 +1,8 @@ import { ApiPromise, Keyring, WsProvider } from "@polkadot/api"; import { KeyringPair } from "@polkadot/keyring/types"; -import TransactionRouter from "../src/utils/transactionRouter"; -import { Fungible, Receiver, Sender } from "../src/utils/transactionRouter/types"; +import TransactionRouter from "../src/utils/xcmTransfer"; +import { Fungible, Receiver, Sender } from "../src/utils/xcmTransfer/types"; import IdentityContractFactory from "../types/constructors/identity"; import IdentityContract from "../types/contracts/identity"; diff --git a/__tests__/transactionRouter.test.ts b/__tests__/transactionRouter.test.ts new file mode 100644 index 0000000..3037434 --- /dev/null +++ b/__tests__/transactionRouter.test.ts @@ -0,0 +1,313 @@ +import { ApiPromise, Keyring, WsProvider } from "@polkadot/api"; +import { KeyringPair } from "@polkadot/keyring/types"; + +import { Fungible, Receiver, Sender } from "@/utils/xcmTransfer/types"; + +import TransactionRouter from "../src/utils/xcmTransfer"; +import IdentityContractFactory from "../types/constructors/identity"; +import IdentityContract from "../types/contracts/identity"; +import { AccountType, ChainInfo } from "../types/types-arguments/identity"; + +const wsProvider = new WsProvider("ws://127.0.0.1:9944"); +const keyring = new Keyring({ type: "sr25519" }); + +describe("TransactionRouter e2e tests", () => { + let swankyApi: ApiPromise; + let alice: KeyringPair; + let bob: KeyringPair; + let identityContract: any; + + beforeEach(async function (): Promise { + swankyApi = await ApiPromise.create({ + provider: wsProvider, + noInitWarn: true, + }); + alice = keyring.addFromUri("//Alice"); + bob = keyring.addFromUri("//Bob"); + + const factory = new IdentityContractFactory(swankyApi, alice); + identityContract = new IdentityContract( + (await factory.new()).address, + alice, + swankyApi + ); + }); + + it("Can't send tokens to yourself", async () => { + // First lets add a chain and create an identity. + + await addChain(identityContract, alice, 1836, { + rpcUrls: ["ws://127.0.0.1:9910"], + accountType: AccountType.accountId32, + }); + + const sender: Sender = { + keypair: alice, + chain: 1836 + }; + + const receiver: Receiver = { + addressRaw: alice.addressRaw, + type: AccountType.accountId32, + chain: 1836, + }; + + const asset: Fungible = { + multiAsset: {}, + amount: 1000 + }; + + const assetReserveChainId = 1836; + + const trappitProvider = new WsProvider("ws://127.0.0.1:9920"); + const trappistApi = await ApiPromise.create({ + provider: trappitProvider, + }); + + await expect( + TransactionRouter.sendTokens( + sender, + receiver, + assetReserveChainId, + asset, + { + originApi: trappistApi, + destApi: trappistApi + } + ) + ).rejects.toThrow("Cannot send tokens to yourself"); + }, 60000); + + it("Sending native asset on the same chain works", async () => { + const sender: Sender = { + keypair: alice, + chain: 0 + }; + + const receiver: Receiver = { + addressRaw: bob.addressRaw, + type: AccountType.accountId32, + chain: 0, + }; + + const rococoProvider = new WsProvider("ws://127.0.0.1:9900"); + const rococoApi = await ApiPromise.create({ provider: rococoProvider }); + + const { data: balance } = (await rococoApi.query.system.account( + receiver.addressRaw + )) as any; + const receiverBalance = parseInt(balance.free.toHuman().replace(/,/g, "")); + + // First lets add a chain. + await addChain(identityContract, alice, 0, { + rpcUrls: ["ws://127.0.0.1:9900"], + accountType: AccountType.accountId32, + }); + + const amount = Math.pow(10, 12); + + const asset: Fungible = { + multiAsset: { + interior: "Here", + parents: 0, + }, + amount + }; + const assetReserveChainId = 0; + + await TransactionRouter.sendTokens( + sender, + receiver, + assetReserveChainId, + asset, + { + originApi: rococoApi, + destApi: rococoApi + } + ); + + const { data: newBalance } = (await rococoApi.query.system.account( + receiver.addressRaw + )) as any; + const newReceiverBalance = parseInt( + newBalance.free.toHuman().replace(/,/g, "") + ); + + expect(newReceiverBalance).toBe(receiverBalance + amount); + }, 30000); + + it("Sending non-native asset on the same chain works", async () => { + const sender: Sender = { + keypair: alice, + chain: 1836 + }; + + const receiver: Receiver = { + addressRaw: bob.addressRaw, + type: AccountType.accountId32, + chain: 1836, + }; + + const trappitProvider = new WsProvider("ws://127.0.0.1:9920"); + const trappistApi = await ApiPromise.create({ + provider: trappitProvider, + }); + + const lockdownMode = await getLockdownMode(trappistApi); + if (lockdownMode) { + await deactivateLockdown(trappistApi, alice); + } + + // First create an asset. + if (!(await getAsset(trappistApi, 0))) { + await createAsset(trappistApi, sender.keypair, 0); + } + + // Mint some assets to the creator. + await mintAsset(trappistApi, sender.keypair, 0, 500); + + const amount = 200; + + const senderAccountBefore: any = (await trappistApi.query.assets.account( + 0, + sender.keypair.address + )).toHuman(); + + const senderBalanceBefore = parseInt(senderAccountBefore.balance.replace(/,/g, "")); + + const receiverAccountBefore: any = (await trappistApi.query.assets.account( + 0, + bob.address + )).toHuman(); + + const receiverBalanceBefore = receiverAccountBefore ? parseInt(receiverAccountBefore.balance.replace(/,/g, "")) : 0; + + // First lets add a chain. + await addChain(identityContract, alice, 1836, { + rpcUrls: ["ws://127.0.0.1:9920"], + accountType: AccountType.accountId32, + }); + + const asset: Fungible = { + multiAsset: { + interior: { + X2: [ + { PalletInstance: 41 }, // assets pallet + { GeneralIndex: 0 }, + ], + }, + parents: 0, + }, + amount + }; + const assetReserveChainId = 1836; + + await TransactionRouter.sendTokens( + sender, + receiver, + assetReserveChainId, + asset, + { + originApi: trappistApi, + destApi: trappistApi + } + ); + + const senderAccountAfter: any = (await trappistApi.query.assets.account( + 0, + sender.keypair.address + )).toHuman(); + + const senderBalanceAfter = parseInt(senderAccountAfter.balance.replace(/,/g, "")); + + const receiverAccountAfter: any = (await trappistApi.query.assets.account( + 0, + bob.address + )).toHuman(); + + const receiverBalanceAfter = parseInt(receiverAccountAfter.balance.replace(/,/g, "")); + + expect(senderBalanceAfter).toBe(senderBalanceBefore - amount); + expect(receiverBalanceAfter).toBe(receiverBalanceBefore + amount); + + }, 180000); +}); + +const addChain = async ( + contract: IdentityContract, + signer: KeyringPair, + chainId: number, + chain: ChainInfo +): Promise => { + await contract + .withSigner(signer) + .tx.addChain(chainId, chain); +}; + +const createAsset = async ( + api: ApiPromise, + signer: KeyringPair, + id: number +): Promise => { + const callTx = async (resolve: () => void) => { + const unsub = await api.tx.assets + .create( + id, + // Admin: + signer.address, + 10 // min balance + ) + .signAndSend(signer, (result: any) => { + if (result.status.isInBlock) { + unsub(); + resolve(); + } + }); + }; + return new Promise(callTx); +}; + +const mintAsset = async ( + api: ApiPromise, + signer: KeyringPair, + id: number, + amount: number +): Promise => { + const callTx = async (resolve: () => void) => { + const unsub = await api.tx.assets + .mint( + id, + signer.address, // beneficiary + amount + ) + .signAndSend(signer, (result: any) => { + if (result.status.isInBlock) { + unsub(); + resolve(); + } + }); + }; + return new Promise(callTx); +}; + +const getAsset = async (api: ApiPromise, id: number): Promise => { + return (await api.query.assets.asset(id)).toHuman(); +}; + +const deactivateLockdown = async (api: ApiPromise, signer: KeyringPair): Promise => { + const callTx = async (resolve: () => void) => { + const forceDisable = api.tx.lockdownMode.deactivateLockdownMode(); + const unsub = await api.tx.sudo.sudo(forceDisable) + .signAndSend(signer, (result: any) => { + if (result.status.isInBlock) { + unsub(); + resolve(); + } + }); + }; + return new Promise(callTx); +} + +const getLockdownMode = async (api: ApiPromise): Promise => { + return (await api.query.lockdownMode.lockdownModeStatus()).toJSON(); +}; diff --git a/__tests__/transferAsset.test.ts b/__tests__/transferAsset.test.ts index 8476935..3037434 100644 --- a/__tests__/transferAsset.test.ts +++ b/__tests__/transferAsset.test.ts @@ -1,9 +1,9 @@ import { ApiPromise, Keyring, WsProvider } from "@polkadot/api"; import { KeyringPair } from "@polkadot/keyring/types"; -import { Fungible, Receiver, Sender } from "@/utils/transactionRouter/types"; +import { Fungible, Receiver, Sender } from "@/utils/xcmTransfer/types"; -import TransactionRouter from "../src/utils/transactionRouter"; +import TransactionRouter from "../src/utils/xcmTransfer"; import IdentityContractFactory from "../types/constructors/identity"; import IdentityContract from "../types/contracts/identity"; import { AccountType, ChainInfo } from "../types/types-arguments/identity"; diff --git a/chaindata/index.ts b/chaindata/index.ts index 887571c..a32564c 100644 --- a/chaindata/index.ts +++ b/chaindata/index.ts @@ -1,3 +1,4 @@ +import { ChainConsts } from "@/contracts/types"; import { gql, request } from "graphql-request" const graphqlUrl = "https://squid.subsquid.io/chaindata/v/v4/graphql" @@ -20,7 +21,20 @@ query chains { } `; +const tokensQuery = gql` +query tokens { + tokens(orderBy: id_ASC) { + data + } +} +`; + export const getChains = async (): Promise> => { const result: any = await request(graphqlUrl, chainsQuery); return result.chains; } + +export const getTokens = async (): Promise> => { + const result: any = await request(graphqlUrl, tokensQuery); + return result.tokens; +} diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index 022cff8..eab467d 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -19,15 +19,17 @@ import { AccountType } from 'types/types-arguments/identity'; import AssetRegistry, { Asset } from '@/utils/assetRegistry'; import IdentityKey from '@/utils/identityKey'; import KeyStore from '@/utils/keyStore'; -import TransactionRouter, { isTeleport } from '@/utils/transactionRouter'; -import { getTeleportableAssets } from '@/utils/transactionRouter/teleportableRoutes'; -import { Fungible } from '@/utils/transactionRouter/types'; +import TransactionRouter, { isTeleport } from '@/utils/xcmTransfer'; +import { getTeleportableAssets } from '@/utils/xcmTransfer/teleportableRoutes'; +import { Fungible } from '@/utils/xcmTransfer/types'; import { chainsSupportingXcmExecute, RELAY_CHAIN } from '@/consts'; import { useRelayApi } from '@/contexts/RelayApi'; import { useToast } from '@/contexts/Toast'; import { useIdentity } from '@/contracts'; import { useAddressBook } from '@/contracts/addressbook/context'; +import { getChains, getTokens } from 'chaindata'; +import NativeTransfer from '@/utils/nativeTransfer'; const TransferPage = () => { const { chains, getAddresses, contract: identityContract } = useIdentity(); @@ -88,10 +90,38 @@ const TransferPage = () => { setAssets(_assets); } } else { - const _assets = await AssetRegistry.getAssetsOnBlockchain( - RELAY_CHAIN, - chains[sourceChainId].paraId + const chainData = (await getChains()).find( + (chain) => chain.paraId ? + chain.paraId === sourceChainId && chain.relay.id === RELAY_CHAIN + : + sourceChainId === 0 && chain.id === RELAY_CHAIN ); + + const _assets = []; + if (chainData) { + console.log(chainData.id); + const tokens = (await getTokens()).filter((token) => { + const isPartOfSourceChain = token.data.id.startsWith(chainData.id); + console.log(isPartOfSourceChain); + return isPartOfSourceChain; + }); + console.log(tokens); + const assets: Asset[] = tokens.map(t => { + const asset: Asset = { + asset: "", + name: t.data.symbol, + symbol: t.data.symbol, + decimals: t.data.decimals, + xcmInteriorKey: t.data, + confidence: 0, + inferred: false + }; + return asset; + }); + console.log(assets); + _assets.push(...assets); + } + _assets.push(...getTeleportableAssets(sourceChainId, destChainId)); setSelectedAsset([]); setAssets(_assets); @@ -138,6 +168,11 @@ const TransferPage = () => { // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars, unused-imports/no-unused-vars const isTransferSupported = (): boolean => { + // We support simple transfers. + if (sourceChainId === destChainId) { + return true; + } + if ( sourceChainId === undefined || destChainId === undefined || @@ -195,6 +230,35 @@ const TransferPage = () => { return; } + if (sourceChainId === destChainId) { + // Just do a simple token transfer. + const count = chains[sourceChainId].rpcUrls.length; + const rpcIndex = Math.min(Math.floor(Math.random() * count), count - 1); + + const api = await getApi(chains[sourceChainId].rpcUrls[rpcIndex]); + + const keypair = new Keyring(); + keypair.addFromAddress(activeAccount.address); + + const receiverKeypair = new Keyring(); + receiverKeypair.addFromAddress(recipientAddress); + + setTransferring(true); + + await NativeTransfer.transfer( + api, + keypair.pairs[0], + selectedAsset.xcmInteriorKey, + receiverKeypair.pairs[0].publicKey, + amount * Math.pow(10, selectedAsset.decimals), + activeSigner + ); + + setTransferring(false); + + return; + } + const reserveChainId = getParaIdFromXcmInterior( selectedAsset.xcmInteriorKey ); @@ -219,7 +283,7 @@ const TransferPage = () => { await TransactionRouter.sendTokens( { - keypair: keypair.pairs[0], // How to convert active account into a keypair? + keypair: keypair.pairs[0], chain: sourceChainId, }, { diff --git a/src/utils/nativeTransfer/assets.ts b/src/utils/nativeTransfer/assets.ts new file mode 100644 index 0000000..c2403fd --- /dev/null +++ b/src/utils/nativeTransfer/assets.ts @@ -0,0 +1,37 @@ +import { ApiPromise } from "@polkadot/api"; +import { AccountIdRaw } from "../xcmTransfer/types"; +import { Signer } from "@polkadot/api/types"; +import { KeyringPair } from "@polkadot/keyring/types"; + +class SubstrateAssets { + public static async transfer( + api: ApiPromise, + sender: KeyringPair, + token: any, + to: AccountIdRaw, + amount: number, + signer?: Signer + ): Promise { + if (token.type !== "substrate-assets") + throw new Error(`This module doesn't handle tokens of type ${token.type}`) + + const id = token.assetId + + const transferCall = api.tx.assets.transfer(id, { Id: to }, amount); + + if (signer) api.setSigner(signer); + + const account = signer ? sender.address : sender; + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + }); + } +} + +export default SubstrateAssets; diff --git a/src/utils/nativeTransfer/index.ts b/src/utils/nativeTransfer/index.ts new file mode 100644 index 0000000..de0872d --- /dev/null +++ b/src/utils/nativeTransfer/index.ts @@ -0,0 +1,35 @@ +import { ApiPromise } from "@polkadot/api"; +import { AccountIdRaw } from "../xcmTransfer/types"; +import SubstrateAssets from "./assets"; +import { Signer } from "@polkadot/api/types"; +import { KeyringPair } from "@polkadot/keyring/types"; + +class NativeTransfer { + public static async transfer( + api: ApiPromise, + sender: KeyringPair, + token: any, + to: AccountIdRaw, + amount: number, + signer?: Signer + ): Promise { + + switch (token.type) { + case "substrate-native": + break; + case "substrate-assets": + await SubstrateAssets.transfer(api, sender, token, to, amount, signer); + break; + case "substrate-tokens": + break; + case "substrate-orml": + break; + case "evm-erc20": + break; + case "evm-native": + break; + } + } +} + +export default NativeTransfer; diff --git a/src/utils/transactionRouter/index.ts b/src/utils/xcmTransfer/index.ts similarity index 100% rename from src/utils/transactionRouter/index.ts rename to src/utils/xcmTransfer/index.ts diff --git a/src/utils/transactionRouter/reserveTransfer.test.ts b/src/utils/xcmTransfer/reserveTransfer.test.ts similarity index 100% rename from src/utils/transactionRouter/reserveTransfer.test.ts rename to src/utils/xcmTransfer/reserveTransfer.test.ts diff --git a/src/utils/transactionRouter/reserveTransfer.ts b/src/utils/xcmTransfer/reserveTransfer.ts similarity index 100% rename from src/utils/transactionRouter/reserveTransfer.ts rename to src/utils/xcmTransfer/reserveTransfer.ts diff --git a/src/utils/transactionRouter/teleportTransfer.ts b/src/utils/xcmTransfer/teleportTransfer.ts similarity index 100% rename from src/utils/transactionRouter/teleportTransfer.ts rename to src/utils/xcmTransfer/teleportTransfer.ts diff --git a/src/utils/transactionRouter/teleportableRoutes.ts b/src/utils/xcmTransfer/teleportableRoutes.ts similarity index 100% rename from src/utils/transactionRouter/teleportableRoutes.ts rename to src/utils/xcmTransfer/teleportableRoutes.ts diff --git a/src/utils/transactionRouter/transferAsset.test.ts b/src/utils/xcmTransfer/transferAsset.test.ts similarity index 100% rename from src/utils/transactionRouter/transferAsset.test.ts rename to src/utils/xcmTransfer/transferAsset.test.ts diff --git a/src/utils/transactionRouter/transferAsset.ts b/src/utils/xcmTransfer/transferAsset.ts similarity index 100% rename from src/utils/transactionRouter/transferAsset.ts rename to src/utils/xcmTransfer/transferAsset.ts diff --git a/src/utils/transactionRouter/types.ts b/src/utils/xcmTransfer/types.ts similarity index 100% rename from src/utils/transactionRouter/types.ts rename to src/utils/xcmTransfer/types.ts From 1de9539ba30ac7361d04c86cd687c03f90676aee Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Tue, 5 Sep 2023 21:18:42 +0200 Subject: [PATCH 05/15] native transfer --- src/utils/nativeTransfer/index.ts | 2 ++ src/utils/nativeTransfer/native.ts | 35 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/utils/nativeTransfer/native.ts diff --git a/src/utils/nativeTransfer/index.ts b/src/utils/nativeTransfer/index.ts index de0872d..f3092ef 100644 --- a/src/utils/nativeTransfer/index.ts +++ b/src/utils/nativeTransfer/index.ts @@ -3,6 +3,7 @@ import { AccountIdRaw } from "../xcmTransfer/types"; import SubstrateAssets from "./assets"; import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; +import SubstrateNative from "./native"; class NativeTransfer { public static async transfer( @@ -16,6 +17,7 @@ class NativeTransfer { switch (token.type) { case "substrate-native": + await SubstrateNative.transfer(api, sender, token, to, amount, signer); break; case "substrate-assets": await SubstrateAssets.transfer(api, sender, token, to, amount, signer); diff --git a/src/utils/nativeTransfer/native.ts b/src/utils/nativeTransfer/native.ts new file mode 100644 index 0000000..2214ce8 --- /dev/null +++ b/src/utils/nativeTransfer/native.ts @@ -0,0 +1,35 @@ +import { ApiPromise } from "@polkadot/api"; +import { KeyringPair } from "@polkadot/keyring/types"; +import { AccountIdRaw } from "../xcmTransfer/types"; +import { Signer } from "@polkadot/api/types"; + +class SubstrateNative { + public static async transfer( + api: ApiPromise, + sender: KeyringPair, + token: any, + to: AccountIdRaw, + amount: number, + signer?: Signer + ): Promise { + if (token.type !== "substrate-native") + throw new Error(`This module doesn't handle tokens of type ${token.type}`) + + const transferCall = api.tx.balances.transfer(to, amount); + + if (signer) api.setSigner(signer); + + const account = signer ? sender.address : sender; + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }); + }); + } +} + +export default SubstrateNative; From ac8207391eb0850a37268b1d33db1f1e3ef32545 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Wed, 6 Sep 2023 07:45:47 +0200 Subject: [PATCH 06/15] token pallet transfers --- src/utils/nativeTransfer/assets.ts | 2 +- src/utils/nativeTransfer/index.ts | 2 ++ src/utils/nativeTransfer/tokens.ts | 47 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/utils/nativeTransfer/tokens.ts diff --git a/src/utils/nativeTransfer/assets.ts b/src/utils/nativeTransfer/assets.ts index c2403fd..621a72d 100644 --- a/src/utils/nativeTransfer/assets.ts +++ b/src/utils/nativeTransfer/assets.ts @@ -15,7 +15,7 @@ class SubstrateAssets { if (token.type !== "substrate-assets") throw new Error(`This module doesn't handle tokens of type ${token.type}`) - const id = token.assetId + const id = token.assetId; const transferCall = api.tx.assets.transfer(id, { Id: to }, amount); diff --git a/src/utils/nativeTransfer/index.ts b/src/utils/nativeTransfer/index.ts index f3092ef..2db7ba0 100644 --- a/src/utils/nativeTransfer/index.ts +++ b/src/utils/nativeTransfer/index.ts @@ -4,6 +4,7 @@ import SubstrateAssets from "./assets"; import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; import SubstrateNative from "./native"; +import SubstrateTokens from "./tokens"; class NativeTransfer { public static async transfer( @@ -23,6 +24,7 @@ class NativeTransfer { await SubstrateAssets.transfer(api, sender, token, to, amount, signer); break; case "substrate-tokens": + await SubstrateTokens.transfer(api, sender, token, to, amount, signer); break; case "substrate-orml": break; diff --git a/src/utils/nativeTransfer/tokens.ts b/src/utils/nativeTransfer/tokens.ts new file mode 100644 index 0000000..85deb53 --- /dev/null +++ b/src/utils/nativeTransfer/tokens.ts @@ -0,0 +1,47 @@ +import { ApiPromise } from "@polkadot/api"; +import { AccountIdRaw } from "../xcmTransfer/types"; +import { Signer } from "@polkadot/api/types"; +import { KeyringPair } from "@polkadot/keyring/types"; + +class SubstrateTokens { + public static async transfer( + api: ApiPromise, + sender: KeyringPair, + token: any, + to: AccountIdRaw, + amount: number, + signer?: Signer + ): Promise { + if (token.type !== "substrate-tokens") + throw new Error(`This module doesn't handle tokens of type ${token.type}`) + + const currencyId = (() => { + try { + // `as string` doesn't matter here because we catch it if it throws + return JSON.parse(token.onChainId as string) + } catch (error) { + return token.onChainId + } + })(); + + const transferCall = api.tx.tokens ? + api.tx.tokens.transfer(to, currencyId, amount) + : + api.tx.currencies.transfer(to, currencyId, amount); + + if (signer) api.setSigner(signer); + + const account = signer ? sender.address : sender; + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + }); + } +} + +export default SubstrateTokens; From b941d763eb26a89a0b177155c2b8fcc603d3557c Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Wed, 6 Sep 2023 16:37:16 +0200 Subject: [PATCH 07/15] orml --- src/utils/nativeTransfer/index.ts | 2 ++ src/utils/nativeTransfer/orml.ts | 41 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/utils/nativeTransfer/orml.ts diff --git a/src/utils/nativeTransfer/index.ts b/src/utils/nativeTransfer/index.ts index 2db7ba0..c55d60d 100644 --- a/src/utils/nativeTransfer/index.ts +++ b/src/utils/nativeTransfer/index.ts @@ -5,6 +5,7 @@ import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; import SubstrateNative from "./native"; import SubstrateTokens from "./tokens"; +import SubstrateOrml from "./orml"; class NativeTransfer { public static async transfer( @@ -27,6 +28,7 @@ class NativeTransfer { await SubstrateTokens.transfer(api, sender, token, to, amount, signer); break; case "substrate-orml": + await SubstrateOrml.transfer(api, sender, token, to, amount, signer); break; case "evm-erc20": break; diff --git a/src/utils/nativeTransfer/orml.ts b/src/utils/nativeTransfer/orml.ts new file mode 100644 index 0000000..b88d7aa --- /dev/null +++ b/src/utils/nativeTransfer/orml.ts @@ -0,0 +1,41 @@ +import { ApiPromise } from "@polkadot/api"; +import { AccountIdRaw } from "../xcmTransfer/types"; +import { Signer } from "@polkadot/api/types"; +import { KeyringPair } from "@polkadot/keyring/types"; + +class SubstrateOrml { + public static async transfer( + api: ApiPromise, + sender: KeyringPair, + token: any, + to: AccountIdRaw, + amount: number, + signer?: Signer + ): Promise { + if (token.type !== "substrate-orml") + throw new Error(`This module doesn't handle tokens of type ${token.type}`) + + const currencyId = { Token: token.symbol.toUpperCase() } + + + const transferCall = api.tx.tokens ? + api.tx.tokens.transfer(to, currencyId, amount) + : + api.tx.currencies.transfer(to, currencyId, amount); + + if (signer) api.setSigner(signer); + + const account = signer ? sender.address : sender; + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve) => { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + }); + } +} + +export default SubstrateOrml; From e84f380a32537845d720df1694cda4ee3f16e7f7 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Wed, 6 Sep 2023 16:42:11 +0200 Subject: [PATCH 08/15] remove --- src/utils/nativeTransfer/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/nativeTransfer/index.ts b/src/utils/nativeTransfer/index.ts index c55d60d..ae7fa2c 100644 --- a/src/utils/nativeTransfer/index.ts +++ b/src/utils/nativeTransfer/index.ts @@ -30,10 +30,6 @@ class NativeTransfer { case "substrate-orml": await SubstrateOrml.transfer(api, sender, token, to, amount, signer); break; - case "evm-erc20": - break; - case "evm-native": - break; } } } From f620e234a37d441515bbb68ebaf89d6fb9885e2b Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Wed, 6 Sep 2023 16:47:04 +0200 Subject: [PATCH 09/15] fix linter errors --- src/pages/transfer.tsx | 8 ++------ src/utils/nativeTransfer/assets.ts | 3 ++- src/utils/nativeTransfer/index.ts | 7 ++++--- src/utils/nativeTransfer/native.ts | 3 ++- src/utils/nativeTransfer/orml.ts | 3 ++- src/utils/nativeTransfer/tokens.ts | 3 ++- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index aea8e96..b397ea4 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -13,12 +13,14 @@ import { import { ApiPromise, Keyring, WsProvider } from '@polkadot/api'; import { useInkathon } from '@scio-labs/use-inkathon'; import styles from '@styles/pages/transfer.module.scss'; +import { getChains, getTokens } from 'chaindata'; import { useCallback, useEffect, useState } from 'react'; import { AccountType } from 'types/types-arguments/identity'; import AssetRegistry, { Asset } from '@/utils/assetRegistry'; import IdentityKey from '@/utils/identityKey'; import KeyStore from '@/utils/keyStore'; +import NativeTransfer from '@/utils/nativeTransfer'; import TransactionRouter, { isTeleport } from '@/utils/xcmTransfer'; import { getTeleportableAssets } from '@/utils/xcmTransfer/teleportableRoutes'; import { Fungible } from '@/utils/xcmTransfer/types'; @@ -28,8 +30,6 @@ import { useRelayApi } from '@/contexts/RelayApi'; import { useToast } from '@/contexts/Toast'; import { useIdentity } from '@/contracts'; import { useAddressBook } from '@/contracts/addressbook/context'; -import { getChains, getTokens } from 'chaindata'; -import NativeTransfer from '@/utils/nativeTransfer'; const TransferPage = () => { const { @@ -104,13 +104,10 @@ const TransferPage = () => { const _assets = []; if (chainData) { - console.log(chainData.id); const tokens = (await getTokens()).filter((token) => { const isPartOfSourceChain = token.data.id.startsWith(chainData.id); - console.log(isPartOfSourceChain); return isPartOfSourceChain; }); - console.log(tokens); const assets: Asset[] = tokens.map(t => { const asset: Asset = { asset: "", @@ -123,7 +120,6 @@ const TransferPage = () => { }; return asset; }); - console.log(assets); _assets.push(...assets); } diff --git a/src/utils/nativeTransfer/assets.ts b/src/utils/nativeTransfer/assets.ts index 621a72d..beaa893 100644 --- a/src/utils/nativeTransfer/assets.ts +++ b/src/utils/nativeTransfer/assets.ts @@ -1,8 +1,9 @@ import { ApiPromise } from "@polkadot/api"; -import { AccountIdRaw } from "../xcmTransfer/types"; import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; +import { AccountIdRaw } from "../xcmTransfer/types"; + class SubstrateAssets { public static async transfer( api: ApiPromise, diff --git a/src/utils/nativeTransfer/index.ts b/src/utils/nativeTransfer/index.ts index ae7fa2c..201ff38 100644 --- a/src/utils/nativeTransfer/index.ts +++ b/src/utils/nativeTransfer/index.ts @@ -1,11 +1,12 @@ import { ApiPromise } from "@polkadot/api"; -import { AccountIdRaw } from "../xcmTransfer/types"; -import SubstrateAssets from "./assets"; import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; + +import SubstrateAssets from "./assets"; import SubstrateNative from "./native"; -import SubstrateTokens from "./tokens"; import SubstrateOrml from "./orml"; +import SubstrateTokens from "./tokens"; +import { AccountIdRaw } from "../xcmTransfer/types"; class NativeTransfer { public static async transfer( diff --git a/src/utils/nativeTransfer/native.ts b/src/utils/nativeTransfer/native.ts index 2214ce8..c46a693 100644 --- a/src/utils/nativeTransfer/native.ts +++ b/src/utils/nativeTransfer/native.ts @@ -1,7 +1,8 @@ import { ApiPromise } from "@polkadot/api"; +import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; + import { AccountIdRaw } from "../xcmTransfer/types"; -import { Signer } from "@polkadot/api/types"; class SubstrateNative { public static async transfer( diff --git a/src/utils/nativeTransfer/orml.ts b/src/utils/nativeTransfer/orml.ts index b88d7aa..8e556ca 100644 --- a/src/utils/nativeTransfer/orml.ts +++ b/src/utils/nativeTransfer/orml.ts @@ -1,8 +1,9 @@ import { ApiPromise } from "@polkadot/api"; -import { AccountIdRaw } from "../xcmTransfer/types"; import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; +import { AccountIdRaw } from "../xcmTransfer/types"; + class SubstrateOrml { public static async transfer( api: ApiPromise, diff --git a/src/utils/nativeTransfer/tokens.ts b/src/utils/nativeTransfer/tokens.ts index 85deb53..9b575ad 100644 --- a/src/utils/nativeTransfer/tokens.ts +++ b/src/utils/nativeTransfer/tokens.ts @@ -1,8 +1,9 @@ import { ApiPromise } from "@polkadot/api"; -import { AccountIdRaw } from "../xcmTransfer/types"; import { Signer } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; +import { AccountIdRaw } from "../xcmTransfer/types"; + class SubstrateTokens { public static async transfer( api: ApiPromise, From d52e0de4f8c3751bef792798de70d5db4e77c0b9 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Wed, 6 Sep 2023 16:48:22 +0200 Subject: [PATCH 10/15] remove accidentally added code --- __tests__/transactionRouter.test.ts | 313 ---------------------------- 1 file changed, 313 deletions(-) delete mode 100644 __tests__/transactionRouter.test.ts diff --git a/__tests__/transactionRouter.test.ts b/__tests__/transactionRouter.test.ts deleted file mode 100644 index 3037434..0000000 --- a/__tests__/transactionRouter.test.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { ApiPromise, Keyring, WsProvider } from "@polkadot/api"; -import { KeyringPair } from "@polkadot/keyring/types"; - -import { Fungible, Receiver, Sender } from "@/utils/xcmTransfer/types"; - -import TransactionRouter from "../src/utils/xcmTransfer"; -import IdentityContractFactory from "../types/constructors/identity"; -import IdentityContract from "../types/contracts/identity"; -import { AccountType, ChainInfo } from "../types/types-arguments/identity"; - -const wsProvider = new WsProvider("ws://127.0.0.1:9944"); -const keyring = new Keyring({ type: "sr25519" }); - -describe("TransactionRouter e2e tests", () => { - let swankyApi: ApiPromise; - let alice: KeyringPair; - let bob: KeyringPair; - let identityContract: any; - - beforeEach(async function (): Promise { - swankyApi = await ApiPromise.create({ - provider: wsProvider, - noInitWarn: true, - }); - alice = keyring.addFromUri("//Alice"); - bob = keyring.addFromUri("//Bob"); - - const factory = new IdentityContractFactory(swankyApi, alice); - identityContract = new IdentityContract( - (await factory.new()).address, - alice, - swankyApi - ); - }); - - it("Can't send tokens to yourself", async () => { - // First lets add a chain and create an identity. - - await addChain(identityContract, alice, 1836, { - rpcUrls: ["ws://127.0.0.1:9910"], - accountType: AccountType.accountId32, - }); - - const sender: Sender = { - keypair: alice, - chain: 1836 - }; - - const receiver: Receiver = { - addressRaw: alice.addressRaw, - type: AccountType.accountId32, - chain: 1836, - }; - - const asset: Fungible = { - multiAsset: {}, - amount: 1000 - }; - - const assetReserveChainId = 1836; - - const trappitProvider = new WsProvider("ws://127.0.0.1:9920"); - const trappistApi = await ApiPromise.create({ - provider: trappitProvider, - }); - - await expect( - TransactionRouter.sendTokens( - sender, - receiver, - assetReserveChainId, - asset, - { - originApi: trappistApi, - destApi: trappistApi - } - ) - ).rejects.toThrow("Cannot send tokens to yourself"); - }, 60000); - - it("Sending native asset on the same chain works", async () => { - const sender: Sender = { - keypair: alice, - chain: 0 - }; - - const receiver: Receiver = { - addressRaw: bob.addressRaw, - type: AccountType.accountId32, - chain: 0, - }; - - const rococoProvider = new WsProvider("ws://127.0.0.1:9900"); - const rococoApi = await ApiPromise.create({ provider: rococoProvider }); - - const { data: balance } = (await rococoApi.query.system.account( - receiver.addressRaw - )) as any; - const receiverBalance = parseInt(balance.free.toHuman().replace(/,/g, "")); - - // First lets add a chain. - await addChain(identityContract, alice, 0, { - rpcUrls: ["ws://127.0.0.1:9900"], - accountType: AccountType.accountId32, - }); - - const amount = Math.pow(10, 12); - - const asset: Fungible = { - multiAsset: { - interior: "Here", - parents: 0, - }, - amount - }; - const assetReserveChainId = 0; - - await TransactionRouter.sendTokens( - sender, - receiver, - assetReserveChainId, - asset, - { - originApi: rococoApi, - destApi: rococoApi - } - ); - - const { data: newBalance } = (await rococoApi.query.system.account( - receiver.addressRaw - )) as any; - const newReceiverBalance = parseInt( - newBalance.free.toHuman().replace(/,/g, "") - ); - - expect(newReceiverBalance).toBe(receiverBalance + amount); - }, 30000); - - it("Sending non-native asset on the same chain works", async () => { - const sender: Sender = { - keypair: alice, - chain: 1836 - }; - - const receiver: Receiver = { - addressRaw: bob.addressRaw, - type: AccountType.accountId32, - chain: 1836, - }; - - const trappitProvider = new WsProvider("ws://127.0.0.1:9920"); - const trappistApi = await ApiPromise.create({ - provider: trappitProvider, - }); - - const lockdownMode = await getLockdownMode(trappistApi); - if (lockdownMode) { - await deactivateLockdown(trappistApi, alice); - } - - // First create an asset. - if (!(await getAsset(trappistApi, 0))) { - await createAsset(trappistApi, sender.keypair, 0); - } - - // Mint some assets to the creator. - await mintAsset(trappistApi, sender.keypair, 0, 500); - - const amount = 200; - - const senderAccountBefore: any = (await trappistApi.query.assets.account( - 0, - sender.keypair.address - )).toHuman(); - - const senderBalanceBefore = parseInt(senderAccountBefore.balance.replace(/,/g, "")); - - const receiverAccountBefore: any = (await trappistApi.query.assets.account( - 0, - bob.address - )).toHuman(); - - const receiverBalanceBefore = receiverAccountBefore ? parseInt(receiverAccountBefore.balance.replace(/,/g, "")) : 0; - - // First lets add a chain. - await addChain(identityContract, alice, 1836, { - rpcUrls: ["ws://127.0.0.1:9920"], - accountType: AccountType.accountId32, - }); - - const asset: Fungible = { - multiAsset: { - interior: { - X2: [ - { PalletInstance: 41 }, // assets pallet - { GeneralIndex: 0 }, - ], - }, - parents: 0, - }, - amount - }; - const assetReserveChainId = 1836; - - await TransactionRouter.sendTokens( - sender, - receiver, - assetReserveChainId, - asset, - { - originApi: trappistApi, - destApi: trappistApi - } - ); - - const senderAccountAfter: any = (await trappistApi.query.assets.account( - 0, - sender.keypair.address - )).toHuman(); - - const senderBalanceAfter = parseInt(senderAccountAfter.balance.replace(/,/g, "")); - - const receiverAccountAfter: any = (await trappistApi.query.assets.account( - 0, - bob.address - )).toHuman(); - - const receiverBalanceAfter = parseInt(receiverAccountAfter.balance.replace(/,/g, "")); - - expect(senderBalanceAfter).toBe(senderBalanceBefore - amount); - expect(receiverBalanceAfter).toBe(receiverBalanceBefore + amount); - - }, 180000); -}); - -const addChain = async ( - contract: IdentityContract, - signer: KeyringPair, - chainId: number, - chain: ChainInfo -): Promise => { - await contract - .withSigner(signer) - .tx.addChain(chainId, chain); -}; - -const createAsset = async ( - api: ApiPromise, - signer: KeyringPair, - id: number -): Promise => { - const callTx = async (resolve: () => void) => { - const unsub = await api.tx.assets - .create( - id, - // Admin: - signer.address, - 10 // min balance - ) - .signAndSend(signer, (result: any) => { - if (result.status.isInBlock) { - unsub(); - resolve(); - } - }); - }; - return new Promise(callTx); -}; - -const mintAsset = async ( - api: ApiPromise, - signer: KeyringPair, - id: number, - amount: number -): Promise => { - const callTx = async (resolve: () => void) => { - const unsub = await api.tx.assets - .mint( - id, - signer.address, // beneficiary - amount - ) - .signAndSend(signer, (result: any) => { - if (result.status.isInBlock) { - unsub(); - resolve(); - } - }); - }; - return new Promise(callTx); -}; - -const getAsset = async (api: ApiPromise, id: number): Promise => { - return (await api.query.assets.asset(id)).toHuman(); -}; - -const deactivateLockdown = async (api: ApiPromise, signer: KeyringPair): Promise => { - const callTx = async (resolve: () => void) => { - const forceDisable = api.tx.lockdownMode.deactivateLockdownMode(); - const unsub = await api.tx.sudo.sudo(forceDisable) - .signAndSend(signer, (result: any) => { - if (result.status.isInBlock) { - unsub(); - resolve(); - } - }); - }; - return new Promise(callTx); -} - -const getLockdownMode = async (api: ApiPromise): Promise => { - return (await api.query.lockdownMode.lockdownModeStatus()).toJSON(); -}; From 3b6928742f17019eef181a84a313ffc64c7240a8 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Wed, 6 Sep 2023 16:59:27 +0200 Subject: [PATCH 11/15] fix ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c211bc0..6e22134 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: node-version: ${{ matrix.node-version }} - run: yarn install - run: 'CI=false yarn build' - - run: yarn test -- src/utils/transactionRouter/reserveTransfer.test.ts + - run: yarn test -- src/utils/xcmTransfer/reserveTransfer.test.ts transfer-asset-test: runs-on: ubuntu-latest strategy: @@ -76,7 +76,7 @@ jobs: node-version: ${{ matrix.node-version }} - run: yarn install - run: 'CI=false yarn build' - - run: yarn test -- src/utils/transactionRouter/transferAsset.test.ts + - run: yarn test -- src/utils/xcmTransfer/transferAsset.test.ts all: needs: [build] From b735863ad8d4d47fdc2353d75637af771143bae9 Mon Sep 17 00:00:00 2001 From: cute0laf Date: Wed, 6 Sep 2023 12:58:21 -0700 Subject: [PATCH 12/15] format --- chaindata/index.ts | 37 +++++++++++++++--------------- src/contracts/identity/context.tsx | 32 ++++++++++++-------------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/chaindata/index.ts b/chaindata/index.ts index a32564c..8d50e3e 100644 --- a/chaindata/index.ts +++ b/chaindata/index.ts @@ -1,40 +1,39 @@ -import { ChainConsts } from "@/contracts/types"; -import { gql, request } from "graphql-request" +import { gql, request } from 'graphql-request'; -const graphqlUrl = "https://squid.subsquid.io/chaindata/v/v4/graphql" +const graphqlUrl = 'https://squid.subsquid.io/chaindata/v/v4/graphql'; /// NOTE: this file is copied from the talisman chaindata. const chainsQuery = gql` -query chains { - chains(orderBy: sortIndex_ASC) { - id - name - paraId - relay { + query chains { + chains(orderBy: sortIndex_ASC) { id - } - rpcs { - url + name + paraId + relay { + id + } + rpcs { + url + } } } -} `; const tokensQuery = gql` -query tokens { - tokens(orderBy: id_ASC) { - data + query tokens { + tokens(orderBy: id_ASC) { + data + } } -} `; export const getChains = async (): Promise> => { const result: any = await request(graphqlUrl, chainsQuery); return result.chains; -} +}; export const getTokens = async (): Promise> => { const result: any = await request(graphqlUrl, tokensQuery); return result.tokens; -} +}; diff --git a/src/contracts/identity/context.tsx b/src/contracts/identity/context.tsx index d302678..02aba88 100644 --- a/src/contracts/identity/context.tsx +++ b/src/contracts/identity/context.tsx @@ -22,13 +22,7 @@ import { useToast } from '@/contexts/Toast'; import { IdentityMetadata } from '.'; import { CONTRACT_IDENTITY } from '..'; -import { - Address, - ChainConsts, - ChainId, - Chains, - IdentityNo, -} from '../types'; +import { Address, ChainConsts, ChainId, Chains, IdentityNo } from '../types'; interface IdentityContract { identityNo: number | null; @@ -114,11 +108,10 @@ const IdentityContractProvider = ({ children }: Props) => { const rpc = rpcUrls[rpcIndex]; try { - const chainData = (await getChains()).find( - (chain) => chain.paraId ? - chain.paraId === chainId && chain.relay.id === RELAY_CHAIN - : - chainId === 0 && chain.id === RELAY_CHAIN + const chainData = (await getChains()).find((chain) => + chain.paraId + ? chain.paraId === chainId && chain.relay.id === RELAY_CHAIN + : chainId === 0 && chain.id === RELAY_CHAIN ); if (!chainData) { @@ -128,9 +121,14 @@ const IdentityContractProvider = ({ children }: Props) => { const ss58Result = await ss58registry(chainData.id); const rpcCount = chainData.rpcs.length; - const rpcIndex = Math.min(Math.floor(Math.random() * rpcCount), rpcCount - 1); + const rpcIndex = Math.min( + Math.floor(Math.random() * rpcCount), + rpcCount - 1 + ); - const ss58Prefix = ss58Result ? ss58Result : await fetchSs58Prefix(chainData.rpcs[rpcIndex].url); + const ss58Prefix = ss58Result + ? ss58Result + : await fetchSs58Prefix(chainData.rpcs[rpcIndex].url); return { name: chainData.name, @@ -155,7 +153,7 @@ const IdentityContractProvider = ({ children }: Props) => { await api.disconnect(); return ss58Prefix; - } + }; setLoadingChains(true); try { @@ -176,7 +174,7 @@ const IdentityContractProvider = ({ children }: Props) => { const _chains: Chains = {}; for await (const item of output) { - const chainId = parseInt(item[0].replace(/,/g, "")); + const chainId = parseInt(item[0].replace(/,/g, '')); const { accountType, rpcUrls } = item[1]; const info = await getChainInfo(rpcUrls, chainId); if (info) @@ -209,7 +207,7 @@ const IdentityContractProvider = ({ children }: Props) => { const _addresses: Array
= []; for (let idx = 0; idx < records.length; ++idx) { const record = records[idx]; - const chainId: ChainId = parseInt(record[0].replace(/,/g, "")); + const chainId: ChainId = parseInt(record[0].replace(/,/g, '')); const address = record[1]; // FIXME: Decode address here _addresses.push({ chainId, From 56ca5b93e01ec46aeb5ec0baeefe8bf642a0e552 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Thu, 7 Sep 2023 13:06:58 +0200 Subject: [PATCH 13/15] cleanup & small fixes --- src/pages/transfer.tsx | 13 ++++++++----- src/utils/assetRegistry.ts | 3 +++ src/utils/nativeTransfer/orml.ts | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index b397ea4..064da87 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -105,16 +105,19 @@ const TransferPage = () => { const _assets = []; if (chainData) { const tokens = (await getTokens()).filter((token) => { - const isPartOfSourceChain = token.data.id.startsWith(chainData.id); + const prefix = `${chainData.id}-${token.data.type}`; + const isPartOfSourceChain = token.data.id.startsWith(prefix); return isPartOfSourceChain; }); const assets: Asset[] = tokens.map(t => { const asset: Asset = { asset: "", + assetId: t.data?.assetId, + onChainId: t.data?.onChainId, name: t.data.symbol, symbol: t.data.symbol, decimals: t.data.decimals, - xcmInteriorKey: t.data, + type: t.data.type, confidence: 0, inferred: false }; @@ -123,7 +126,6 @@ const TransferPage = () => { _assets.push(...assets); } - _assets.push(...getTeleportableAssets(sourceChainId, destChainId)); setSelectedAsset([]); setAssets(_assets); } @@ -177,7 +179,8 @@ const TransferPage = () => { if ( sourceChainId === undefined || destChainId === undefined || - selectedAsset === undefined + selectedAsset === undefined || + selectedAsset.xcmInteriorKey === undefined ) { return false; } @@ -249,7 +252,7 @@ const TransferPage = () => { await NativeTransfer.transfer( api, keypair.pairs[0], - selectedAsset.xcmInteriorKey, + selectedAsset, receiverKeypair.pairs[0].publicKey, amount * Math.pow(10, selectedAsset.decimals), activeSigner diff --git a/src/utils/assetRegistry.ts b/src/utils/assetRegistry.ts index 810b6c2..2b79e42 100644 --- a/src/utils/assetRegistry.ts +++ b/src/utils/assetRegistry.ts @@ -10,6 +10,9 @@ export type Asset = { symbol: string; decimals: number; xcmInteriorKey?: any; + type?: string; + assetId?: any; + onChainId?: any; inferred: boolean; confidence: number; }; diff --git a/src/utils/nativeTransfer/orml.ts b/src/utils/nativeTransfer/orml.ts index 8e556ca..bcd45c5 100644 --- a/src/utils/nativeTransfer/orml.ts +++ b/src/utils/nativeTransfer/orml.ts @@ -18,7 +18,6 @@ class SubstrateOrml { const currencyId = { Token: token.symbol.toUpperCase() } - const transferCall = api.tx.tokens ? api.tx.tokens.transfer(to, currencyId, amount) : From 32317ec252dd3495713e605b29efc31f4341fce9 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Thu, 7 Sep 2023 13:48:56 +0200 Subject: [PATCH 14/15] edge case minor problem fix --- src/pages/transfer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index 064da87..9905b65 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -301,7 +301,7 @@ const TransferPage = () => { }, reserveChainId, getFungible( - selectedAsset.xcmInteriorKey, + JSON.parse(JSON.stringify(selectedAsset.xcmInteriorKey)), // Make a hard copy. isSourceParachain, amount * Math.pow(10, selectedAsset.decimals) ), From 29e5cec96a50d7f679f37cb24720db0a210f8180 Mon Sep 17 00:00:00 2001 From: Sergej Sakac Date: Thu, 7 Sep 2023 17:25:13 +0200 Subject: [PATCH 15/15] error handling --- src/pages/transfer.tsx | 24 +++++----- src/utils/nativeTransfer/assets.ts | 18 +++++--- src/utils/nativeTransfer/native.ts | 18 +++++--- src/utils/nativeTransfer/orml.ts | 18 +++++--- src/utils/nativeTransfer/tokens.ts | 18 +++++--- src/utils/xcmTransfer/reserveTransfer.ts | 54 ++++++++++++++--------- src/utils/xcmTransfer/teleportTransfer.ts | 18 +++++--- src/utils/xcmTransfer/transferAsset.ts | 18 +++++--- 8 files changed, 113 insertions(+), 73 deletions(-) diff --git a/src/pages/transfer.tsx b/src/pages/transfer.tsx index 9905b65..cc9633a 100644 --- a/src/pages/transfer.tsx +++ b/src/pages/transfer.tsx @@ -249,16 +249,20 @@ const TransferPage = () => { setTransferring(true); - await NativeTransfer.transfer( - api, - keypair.pairs[0], - selectedAsset, - receiverKeypair.pairs[0].publicKey, - amount * Math.pow(10, selectedAsset.decimals), - activeSigner - ); - - setTransferring(false); + try { + await NativeTransfer.transfer( + api, + keypair.pairs[0], + selectedAsset, + receiverKeypair.pairs[0].publicKey, + amount * Math.pow(10, selectedAsset.decimals), + activeSigner + ); + } catch (e: any) { + toastError(`Transfer failed. Error: ${e.toString()}`); + } finally { + setTransferring(false); + } return; } diff --git a/src/utils/nativeTransfer/assets.ts b/src/utils/nativeTransfer/assets.ts index beaa893..4d344f2 100644 --- a/src/utils/nativeTransfer/assets.ts +++ b/src/utils/nativeTransfer/assets.ts @@ -24,13 +24,17 @@ class SubstrateAssets { const account = signer ? sender.address : sender; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await transferCall.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); } } diff --git a/src/utils/nativeTransfer/native.ts b/src/utils/nativeTransfer/native.ts index c46a693..36c5370 100644 --- a/src/utils/nativeTransfer/native.ts +++ b/src/utils/nativeTransfer/native.ts @@ -22,13 +22,17 @@ class SubstrateNative { const account = signer ? sender.address : sender; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await transferCall.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }); + return new Promise(async (resolve, reject) => { + try { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }); + } catch (e) { + reject(e); + } }); } } diff --git a/src/utils/nativeTransfer/orml.ts b/src/utils/nativeTransfer/orml.ts index bcd45c5..dd7ffee 100644 --- a/src/utils/nativeTransfer/orml.ts +++ b/src/utils/nativeTransfer/orml.ts @@ -27,13 +27,17 @@ class SubstrateOrml { const account = signer ? sender.address : sender; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await transferCall.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); } } diff --git a/src/utils/nativeTransfer/tokens.ts b/src/utils/nativeTransfer/tokens.ts index 9b575ad..4f7df57 100644 --- a/src/utils/nativeTransfer/tokens.ts +++ b/src/utils/nativeTransfer/tokens.ts @@ -34,13 +34,17 @@ class SubstrateTokens { const account = signer ? sender.address : sender; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await transferCall.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await transferCall.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); } } diff --git a/src/utils/xcmTransfer/reserveTransfer.ts b/src/utils/xcmTransfer/reserveTransfer.ts index 3b1b787..366c973 100644 --- a/src/utils/xcmTransfer/reserveTransfer.ts +++ b/src/utils/xcmTransfer/reserveTransfer.ts @@ -41,13 +41,17 @@ class ReserveTransfer { const account = signer ? sender.keypair.address : sender.keypair; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await reserveTransfer.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await reserveTransfer.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); } @@ -80,13 +84,17 @@ class ReserveTransfer { const account = signer ? sender.keypair.address : sender.keypair; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await reserveTransfer.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await reserveTransfer.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); } @@ -119,13 +127,17 @@ class ReserveTransfer { const account = signer ? sender.keypair.address : sender.keypair; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await reserveTransfer.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await reserveTransfer.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); } diff --git a/src/utils/xcmTransfer/teleportTransfer.ts b/src/utils/xcmTransfer/teleportTransfer.ts index a70cb45..e976cd7 100644 --- a/src/utils/xcmTransfer/teleportTransfer.ts +++ b/src/utils/xcmTransfer/teleportTransfer.ts @@ -46,13 +46,17 @@ class TeleportTransfer { const account = signer ? sender.address : sender; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await teleport.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await teleport.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }); + } catch (e) { + reject(e); + } }); } } diff --git a/src/utils/xcmTransfer/transferAsset.ts b/src/utils/xcmTransfer/transferAsset.ts index 1c26474..a40dac7 100644 --- a/src/utils/xcmTransfer/transferAsset.ts +++ b/src/utils/xcmTransfer/transferAsset.ts @@ -40,13 +40,17 @@ class TransferAsset { const account = signer ? sender.address : sender; // eslint-disable-next-line no-async-promise-executor - return new Promise(async (resolve) => { - const unsub = await xcmExecute.signAndSend(account, (result: any) => { - if (result.status.isFinalized) { - unsub(); - resolve(); - } - }) + return new Promise(async (resolve, reject) => { + try { + const unsub = await xcmExecute.signAndSend(account, (result: any) => { + if (result.status.isFinalized) { + unsub(); + resolve(); + } + }) + } catch (e) { + reject(e); + } }); }