Skip to content

Commit

Permalink
feat: parachain support (#289)
Browse files Browse the repository at this point in the history
* feat: add support for choosing substrate parachain

* Define known parachain ids

* Revert evm to substrate example changes

* Fix tests

* Update asset transfer test

* Use parachain id as parachain identificator

* Add astar to polkadot parachains

* Add parachain id comment
  • Loading branch information
mpetrun5 authored Aug 2, 2023
1 parent 5cc18ee commit 5f829e8
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 56 deletions.
12 changes: 4 additions & 8 deletions examples/evm-to-substrate-fungible-transfer/src/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { EVMAssetTransfer, Environment } from "@buildwithsygma/sygma-sdk-core";
import { Wallet, providers } from "ethers";
import dotenv from "dotenv";

dotenv.config()
dotenv.config();

const privateKey = process.env.PRIVATE_KEY;

if (!privateKey) {
throw new Error("Missing environment variable: PRIVATE_KEY");
}
Expand All @@ -19,20 +18,17 @@ export async function erc20Transfer(): Promise<void> {
const provider = new providers.JsonRpcProvider(
"https://rpc.goerli.eth.gateway.fm/"
);
const wallet = new Wallet(
privateKey as string,
provider
);
const wallet = new Wallet(privateKey, provider);
const assetTransfer = new EVMAssetTransfer();
await assetTransfer.init(provider, Environment.TESTNET);


const transfer = assetTransfer.createFungibleTransfer(
await wallet.getAddress(),
ROCOCO_PHALA_CHAIN_ID,
DESTINATION_ADDRESS,
RESOURCE_ID,
"5000000000000000000" // 18 decimal places
// optional parachainID (e.g. KusamaParachain.SHIDEN)
);

const fee = await assetTransfer.getFee(transfer);
Expand All @@ -53,4 +49,4 @@ export async function erc20Transfer(): Promise<void> {
console.log("Sent transfer with hash: ", response.hash);
}

erc20Transfer().finally(() => { });
erc20Transfer().finally(() => {});
4 changes: 4 additions & 0 deletions packages/sdk/src/chains/BaseAssetTransfer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Environment, Fungible, Transfer } from '../types';
import { Config } from '../config';
import { ParachainID } from './Substrate';

export abstract class BaseAssetTransfer {
public config!: Config;
Expand All @@ -15,6 +16,7 @@ export abstract class BaseAssetTransfer {
* @param {string} destinationAddress - The address of the recipient on the destination chain
* @param {string} resourceId - The ID of the resource being transferred
* @param {string} amount - The amount of tokens to be transferred. The amount should be in the lowest denomination possible on the source chain. If the token on source chain is configured to use 12 decimals and the amount to be transferred is 1 ETH, then amount should be passed in as 1000000000000
* @param {string} parachainId - Optional parachain id if the substrate destination parachain differs from the target domain.
* @returns {Transfer<Fungible>} - The populated transfer object
* @throws {Error} - Source domain not supported, Destination domain not supported, Resource not supported
*/
Expand All @@ -24,6 +26,7 @@ export abstract class BaseAssetTransfer {
destinationAddress: string,
resourceId: string,
amount: string,
parachainId?: ParachainID,
): Transfer<Fungible> {
const { sourceDomain, destinationDomain, resource } = this.config.getBaseTransferParams(
destinationChainId,
Expand All @@ -35,6 +38,7 @@ export abstract class BaseAssetTransfer {
details: {
amount,
recipient: destinationAddress,
parachainId: parachainId,
},
from: sourceDomain,
to: destinationDomain,
Expand Down
34 changes: 30 additions & 4 deletions packages/sdk/src/chains/EVM/__test__/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { BigNumber, utils } from 'ethers';

import {
getRecipientAddressInBytes,
getEVMRecipientAddressInBytes,
getSubstrateRecipientAddressInBytes,
createERCDepositData,
toHex,
createPermissionedGenericDepositData,
Expand All @@ -23,6 +24,14 @@ describe('createERCDepositData', () => {
});

describe('constructSubstrateRecipient', () => {
it('should create a valid Substrate Multilocation Object with parachain id', () => {
const substrateAddress = '5CDQJk6kxvBcjauhrogUc9B8vhbdXhRscp1tGEUmniryF1Vt';
const result = constructSubstrateRecipient(substrateAddress, 2004);
const expectedResult =
'{"parents":1,"interior":{"X2":[{"parachain":2004},{"AccountId32":{"network":{"any":null},"id":"0x06a220edf5f82b84fc5f9270f8a30a17636bf29c05a5c16279405ca20918aa39"}}]}}';
expect(result).toEqual(expectedResult);
});

it('should create a valid Substrate Multilocation Object', () => {
const substrateAddress = '5CDQJk6kxvBcjauhrogUc9B8vhbdXhRscp1tGEUmniryF1Vt';
const result = constructSubstrateRecipient(substrateAddress);
Expand All @@ -32,24 +41,26 @@ describe('constructSubstrateRecipient', () => {
});
});

describe('getRecipientAddressInBytes', () => {
describe('getEVMRecipientAddressInBytes', () => {
it('should convert an EVM address to a Uint8Array of bytes', () => {
const evmAddress = '0x742d35Cc6634C0532925a3b844Bc454e4438f44e';
expect(utils.isAddress(evmAddress)).toBeTruthy();

const result = getRecipientAddressInBytes(evmAddress);
const result = getEVMRecipientAddressInBytes(evmAddress);
const expectedResult = utils.arrayify(evmAddress);

expect(result).toEqual(expectedResult);
expect(result).toBeInstanceOf(Uint8Array);
});
});

describe('getSubstrateRecipientAddressInBytes', () => {
it('should convert a Substrate address to a Uint8Array of bytes', () => {
const substrateAddress = '5CDQJk6kxvBcjauhrogUc9B8vhbdXhRscp1tGEUmniryF1Vt';

expect(utils.isAddress(substrateAddress)).toBeFalsy();

const result = getRecipientAddressInBytes(substrateAddress);
const result = getSubstrateRecipientAddressInBytes(substrateAddress);
const expectedResult = Uint8Array.from([
0, 1, 1, 0, 6, 162, 32, 237, 245, 248, 43, 132, 252, 95, 146, 112, 248, 163, 10, 23, 99, 107,
242, 156, 5, 165, 193, 98, 121, 64, 92, 162, 9, 24, 170, 57,
Expand All @@ -58,6 +69,21 @@ describe('getRecipientAddressInBytes', () => {
expect(result).toEqual(expectedResult);
expect(result).toBeInstanceOf(Uint8Array);
});

it('should convert a Substrate address on a different parachain to a Uint8Array of bytes', () => {
const substrateAddress = '5CDQJk6kxvBcjauhrogUc9B8vhbdXhRscp1tGEUmniryF1Vt';

expect(utils.isAddress(substrateAddress)).toBeFalsy();

const result = getSubstrateRecipientAddressInBytes(substrateAddress, 1001);
const expectedResult = Uint8Array.from([
1, 2, 0, 165, 15, 1, 0, 6, 162, 32, 237, 245, 248, 43, 132, 252, 95, 146, 112, 248, 163, 10,
23, 99, 107, 242, 156, 5, 165, 193, 98, 121, 64, 92, 162, 9, 24, 170, 57,
]);

expect(result).toEqual(expectedResult);
expect(result).toBeInstanceOf(Uint8Array);
});
});

describe('toHex', () => {
Expand Down
12 changes: 8 additions & 4 deletions packages/sdk/src/chains/EVM/assetTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,19 +202,23 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
const bridge = Bridge__factory.connect(this.config.getDomainConfig().bridge, this.provider);
switch (transfer.resource.type) {
case ResourceType.FUNGIBLE: {
const fungibleTransfer = transfer as Transfer<Fungible>;
return await erc20Transfer({
amount: (transfer.details as Fungible).amount,
recipientAddress: (transfer.details as Fungible).recipient,
amount: fungibleTransfer.details.amount,
recipientAddress: fungibleTransfer.details.recipient,
parachainId: fungibleTransfer.details.parachainId,
bridgeInstance: bridge,
domainId: transfer.to.id.toString(),
resourceId: transfer.resource.resourceId,
feeData: fee,
});
}
case ResourceType.NON_FUNGIBLE: {
const nonfungibleTransfer = transfer as Transfer<NonFungible>;
return await erc721Transfer({
id: (transfer.details as NonFungible).tokenId,
recipientAddress: (transfer.details as NonFungible).recipient,
id: nonfungibleTransfer.details.tokenId,
recipientAddress: nonfungibleTransfer.details.recipient,
parachainId: nonfungibleTransfer.details.parachainId,
bridgeInstance: bridge,
domainId: transfer.to.id.toString(),
resourceId: transfer.resource.resourceId,
Expand Down
69 changes: 55 additions & 14 deletions packages/sdk/src/chains/EVM/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@ const registry = new TypeRegistry();
*
* @param {string} tokenAmount - The amount of tokens to be transferred.
* @param {string} recipientAddress - The address of the recipient.
* @param {number} parachainId - Optional parachain id if the substrate destination targets another parachain.
* @returns {string} The deposit data as hex string
*/
export const createERCDepositData = (tokenAmount: string, recipientAddress: string): string => {
const recipientAddressInBytes = getRecipientAddressInBytes(recipientAddress);
export const createERCDepositData = (
tokenAmount: string,
recipientAddress: string,
parachainId?: number,
): string => {
let recipientAddressInBytes;
if (utils.isAddress(recipientAddress)) {
recipientAddressInBytes = getEVMRecipientAddressInBytes(recipientAddress);
} else {
recipientAddressInBytes = getSubstrateRecipientAddressInBytes(recipientAddress, parachainId);
}
const depositDataBytes = constructMainDepositData(
BigNumber.from(tokenAmount),
recipientAddressInBytes,
Expand Down Expand Up @@ -97,10 +107,32 @@ export const createPermissionlessGenericDepositData = (
* @param {string} recipientAddress - The recipient address as a string.
* @returns {string} The recipient address as a stringified Substrate Multilocation Object
*/
export const constructSubstrateRecipient = (recipientAddress: string): string => {
export const constructSubstrateRecipient = (
recipientAddress: string,
parachainId?: number,
): string => {
const addressPublicKeyBytes = decodeAddress(recipientAddress);
const addressPublicKeyHexString = utils.hexlify(addressPublicKeyBytes);
const substrateMultilocation = JSON.stringify({
if (parachainId) {
return JSON.stringify({
parents: 1,
interior: {
X2: [
{
parachain: parachainId,
},
{
AccountId32: {
network: { any: null },
id: addressPublicKeyHexString,
},
},
],
},
});
}

return JSON.stringify({
parents: 0,
interior: {
X1: {
Expand All @@ -111,24 +143,33 @@ export const constructSubstrateRecipient = (recipientAddress: string): string =>
},
},
});

return substrateMultilocation;
};

/**
* Converts a recipient address to a Uint8Array of bytes.
* Converts a EVM recipient address to a Uint8Array of bytes.
*
* @param {string} recipientAddress - The recipient address, as a string. If the address passed in is not an Ethereum address, a Substrate Multilocation object will be constructed and serialized.
* @param {string} recipientAddress - The recipient address, as a string.
* @returns {Uint8Array} The recipient address as a Uint8Array of bytes
*/
export const getRecipientAddressInBytes = (recipientAddress: string): Uint8Array => {
if (utils.isAddress(recipientAddress)) {
// EVM address
return utils.arrayify(recipientAddress);
}
export const getEVMRecipientAddressInBytes = (recipientAddress: string): Uint8Array => {
return utils.arrayify(recipientAddress);
};

/**
* Converts a Substrate recipient multilocation to a Uint8Array of bytes.
*
* @param {string} recipientAddress - The recipient address, as a string
* @returns {Uint8Array} The recipient address as a Uint8Array of bytes
*/
export const getSubstrateRecipientAddressInBytes = (
recipientAddress: string,
parachainId?: number,
): Uint8Array => {
const result = registry
.createType('MultiLocation', JSON.parse(constructSubstrateRecipient(recipientAddress)))
.createType(
'MultiLocation',
JSON.parse(constructSubstrateRecipient(recipientAddress, parachainId)),
)
.toU8a();

return result;
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/src/chains/EVM/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export type OracleResource = {
export type Erc20TransferParamsType = {
/** The unique identifier for the destination network on the bridge. */
domainId: string;
/** Identifier of the substrate destination parachain */
parachainId?: number;
/** The unique identifier for the resource being transferred. */
resourceId: string;
/** The amount of tokens to transfer */
Expand All @@ -44,6 +46,8 @@ export type Erc20TransferParamsType = {
export type Erc721TransferParamsType = {
/** The unique identifier for the destination network on the bridge. */
domainId: string;
/** Identifier of the substrate destination parachain */
parachainId?: number;
/** The unique identifier for the resource being transferred. */
resourceId: string;
/** The tokenId for a specific ERC721 token being transferred. */
Expand Down
6 changes: 4 additions & 2 deletions packages/sdk/src/chains/EVM/utils/depositFns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ export const ASSET_TRANSFER_GAS_LIMIT: BigNumber = BigNumber.from(300000);
export const erc20Transfer = async ({
amount,
recipientAddress,
parachainId,
bridgeInstance,
domainId,
resourceId,
feeData,
overrides,
}: Erc20TransferParamsType): Promise<PopulatedTransaction> => {
// construct the deposit data
const depositData = createERCDepositData(amount, recipientAddress);
const depositData = createERCDepositData(amount, recipientAddress, parachainId);

// pass data to smartcontract function and create a transaction
return executeDeposit(domainId, resourceId, depositData, feeData, bridgeInstance, overrides);
Expand All @@ -63,14 +64,15 @@ export const erc20Transfer = async ({
export const erc721Transfer = async ({
id: tokenId,
recipientAddress,
parachainId,
bridgeInstance,
domainId,
resourceId,
feeData,
overrides,
}: Erc721TransferParamsType): Promise<PopulatedTransaction> => {
// construct the deposit data
const depositData = createERCDepositData(tokenId, recipientAddress);
const depositData = createERCDepositData(tokenId, recipientAddress, parachainId);

// pass data to smartcontract function and create a transaction
return executeDeposit(domainId, resourceId, depositData, feeData, bridgeInstance, overrides);
Expand Down
27 changes: 27 additions & 0 deletions packages/sdk/src/chains/Substrate/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,30 @@ export type SubstrateFee = {
};

export type SubstrateAccountInfo = AccountInfo;

export enum KusamaParachain {
STATEMINE = 1000,
BIFROST = 2001,
SHIDEN = 2007,
MOONRIVER = 2023,
KARURA = 2000,
PARALLEL_HEIKO = 2085,
BASILISK = 2090,
CRAB = 2105,
CALAMARI = 2084,
TURING = 2114,
}

export enum PolkadotParachain {
STATEMINT = 1000,
ASTAR = 2006,
MOONBEAM = 2004,
BIFROST = 2030,
PARALLEL = 2012,
HYDRADX = 2034,
DARWINIA = 2046,
EQUILIBRIUM = 2011,
COMPOSABLE = 2019,
}

export type ParachainID = KusamaParachain | PolkadotParachain | number;
8 changes: 7 additions & 1 deletion packages/sdk/src/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { XcmMultiAssetIdType } from 'chains/Substrate/types';
import { ParachainID, XcmMultiAssetIdType } from 'chains/Substrate/types';

export type Domain = {
id: number;
chainId: number;
name: string;
};

export type Recipient = {
address: string;
parachainId?: number;
};

export enum ResourceType {
FUNGIBLE = 'fungible',
NON_FUNGIBLE = 'nonfungible',
Expand Down Expand Up @@ -44,6 +49,7 @@ export enum FeeHandlerType {

type AssetTransfer = {
recipient: string;
parachainId?: ParachainID;
};

export type Fungible = AssetTransfer & {
Expand Down
Loading

0 comments on commit 5f829e8

Please sign in to comment.