Skip to content

Commit

Permalink
Native asset transfers (#65)
Browse files Browse the repository at this point in the history
* Fetch chaindata from Talisman chaindata

* fix linter errors

* add graphql

* Native asset transfers

* native transfer

* token pallet transfers

* orml

* remove

* fix linter errors

* remove accidentally added code

* fix ci

* format

* cleanup & small fixes

* edge case minor problem fix

* error handling

---------

Co-authored-by: cute0laf <oliverlim818@gmail.com>
  • Loading branch information
Szegoo and cuteolaf committed Sep 7, 2023
1 parent fa76aeb commit dfcb1e1
Show file tree
Hide file tree
Showing 23 changed files with 458 additions and 73 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions __tests__/crossChainRouter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
4 changes: 2 additions & 2 deletions __tests__/teleport.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
4 changes: 2 additions & 2 deletions __tests__/transferAsset.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
39 changes: 39 additions & 0 deletions chaindata/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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
}
}
}
`;

const tokensQuery = gql`
query tokens {
tokens(orderBy: id_ASC) {
data
}
}
`;

export const getChains = async (): Promise<Array<any>> => {
const result: any = await request(graphqlUrl, chainsQuery);
return result.chains;
};

export const getTokens = async (): Promise<Array<any>> => {
const result: any = await request(graphqlUrl, tokensQuery);
return result.tokens;
};
15 changes: 15 additions & 0 deletions chaindata/ss58registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import axios from 'axios';

const ss58registry = async (chainName: string): Promise<number | null> => {
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;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"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",
"react": "^18.2.0",
Expand Down
62 changes: 42 additions & 20 deletions src/contracts/identity/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
useContract,
useInkathon,
} from '@scio-labs/use-inkathon';
import { getChains } from 'chaindata';
import ss58registry from 'chaindata/ss58registry';
import {
createContext,
useCallback,
Expand All @@ -15,17 +17,12 @@ import {
useState,
} from 'react';

import { RELAY_CHAIN } from '@/consts';
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;
Expand Down Expand Up @@ -109,30 +106,55 @@ 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
);

if (!chainData) {
return null;
}

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

return {
name,
ss58Prefix,
paraId,
name: chainData.name,
ss58Prefix: ss58Prefix,
paraId: chainId,
};
} catch (e) {
toastError && toastError(`Failed to get chain info for ${rpc}`);
return null;
}
};

const fetchSs58Prefix = async (rpc: string): Promise<number> => {
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(
Expand All @@ -152,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)
Expand Down Expand Up @@ -185,7 +207,7 @@ const IdentityContractProvider = ({ children }: Props) => {
const _addresses: Array<Address> = [];
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,
Expand Down
87 changes: 77 additions & 10 deletions src/pages/transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ 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 TransactionRouter, { isTeleport } from '@/utils/transactionRouter';
import { getTeleportableAssets } from '@/utils/transactionRouter/teleportableRoutes';
import { Fungible } from '@/utils/transactionRouter/types';
import NativeTransfer from '@/utils/nativeTransfer';
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';
Expand Down Expand Up @@ -93,11 +95,37 @@ 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
);
_assets.push(...getTeleportableAssets(sourceChainId, destChainId));

const _assets = [];
if (chainData) {
const tokens = (await getTokens()).filter((token) => {
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,
type: t.data.type,
confidence: 0,
inferred: false
};
return asset;
});
_assets.push(...assets);
}

setSelectedAsset([]);
setAssets(_assets);
}
Expand Down Expand Up @@ -143,10 +171,16 @@ 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 ||
selectedAsset === undefined
selectedAsset === undefined ||
selectedAsset.xcmInteriorKey === undefined
) {
return false;
}
Expand Down Expand Up @@ -200,6 +234,39 @@ 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);

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

const reserveChainId = getParaIdFromXcmInterior(
selectedAsset.xcmInteriorKey
);
Expand All @@ -225,7 +292,7 @@ const TransferPage = () => {
try {
await TransactionRouter.sendTokens(
{
keypair: keypair.pairs[0], // How to convert active account into a keypair?
keypair: keypair.pairs[0],
chain: sourceChainId,
},
{
Expand All @@ -238,7 +305,7 @@ const TransferPage = () => {
},
reserveChainId,
getFungible(
selectedAsset.xcmInteriorKey,
JSON.parse(JSON.stringify(selectedAsset.xcmInteriorKey)), // Make a hard copy.
isSourceParachain,
amount * Math.pow(10, selectedAsset.decimals)
),
Expand Down
3 changes: 3 additions & 0 deletions src/utils/assetRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export type Asset = {
symbol: string;
decimals: number;
xcmInteriorKey?: any;
type?: string;
assetId?: any;
onChainId?: any;
inferred: boolean;
confidence: number;
};
Expand Down
Loading

0 comments on commit dfcb1e1

Please sign in to comment.