Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Native asset transfers #65

Merged
merged 16 commits into from
Sep 7, 2023
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
83 changes: 73 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,
cuteolaf marked this conversation as resolved.
Show resolved Hide resolved
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,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,
receiverKeypair.pairs[0].publicKey,
amount * Math.pow(10, selectedAsset.decimals),
activeSigner
);

setTransferring(false);

return;
}

const reserveChainId = getParaIdFromXcmInterior(
selectedAsset.xcmInteriorKey
);
Expand All @@ -225,7 +288,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 +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)
),
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