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
40 changes: 40 additions & 0 deletions chaindata/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ChainConsts } from "@/contracts/types";
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
46 changes: 35 additions & 11 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,6 +17,7 @@ import {
useState,
} from 'react';

import { RELAY_CHAIN } from '@/consts';
import { useToast } from '@/contexts/Toast';

import { IdentityMetadata } from '.';
Expand Down Expand Up @@ -109,30 +112,51 @@ 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 Down
74 changes: 67 additions & 7 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,10 +95,34 @@ 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) {
const tokens = (await getTokens()).filter((token) => {
const isPartOfSourceChain = token.data.id.startsWith(chainData.id);
return isPartOfSourceChain;
});
const assets: Asset[] = tokens.map(t => {
const asset: Asset = {
asset: "",
name: t.data.symbol,
cuteolaf marked this conversation as resolved.
Show resolved Hide resolved
symbol: t.data.symbol,
decimals: t.data.decimals,
xcmInteriorKey: t.data,
Szegoo marked this conversation as resolved.
Show resolved Hide resolved
confidence: 0,
inferred: false
};
return asset;
});
_assets.push(...assets);
}

_assets.push(...getTeleportableAssets(sourceChainId, destChainId));
setSelectedAsset([]);
setAssets(_assets);
Expand Down Expand Up @@ -143,6 +169,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 ||
Expand Down Expand Up @@ -200,6 +231,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
);
Expand All @@ -225,7 +285,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 Down
38 changes: 38 additions & 0 deletions src/utils/nativeTransfer/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ApiPromise } from "@polkadot/api";
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,
sender: KeyringPair,
token: any,
to: AccountIdRaw,
amount: number,
signer?: Signer
): Promise<void> {
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;
38 changes: 38 additions & 0 deletions src/utils/nativeTransfer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ApiPromise } from "@polkadot/api";
import { Signer } from "@polkadot/api/types";
import { KeyringPair } from "@polkadot/keyring/types";

import SubstrateAssets from "./assets";
import SubstrateNative from "./native";
import SubstrateOrml from "./orml";
import SubstrateTokens from "./tokens";
import { AccountIdRaw } from "../xcmTransfer/types";

class NativeTransfer {
public static async transfer(
api: ApiPromise,
sender: KeyringPair,
token: any,
to: AccountIdRaw,
amount: number,
signer?: Signer
): Promise<void> {

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);
break;
case "substrate-tokens":
await SubstrateTokens.transfer(api, sender, token, to, amount, signer);
break;
case "substrate-orml":
await SubstrateOrml.transfer(api, sender, token, to, amount, signer);
break;
}
}
}

export default NativeTransfer;
Loading