Skip to content

Commit

Permalink
feat: Generic Message passing (#285)
Browse files Browse the repository at this point in the history
* basic GMP class and helpers

* refactor

* add dynamic fee handler

* fix example

* fix tests

* fix lint

* fix encoding

* remove private key from example

* resolve comments

* move basic transfer params to config

* update param names

* fix test and lint
  • Loading branch information
FSM1 authored Jul 24, 2023
1 parent abcc820 commit 5cc18ee
Show file tree
Hide file tree
Showing 19 changed files with 529 additions and 91 deletions.
5 changes: 1 addition & 4 deletions examples/evm-to-evm-fungible-transfer/src/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ 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 as string, provider);
const assetTransfer = new EVMAssetTransfer();
await assetTransfer.init(provider, Environment.TESTNET);

Expand Down
1 change: 1 addition & 0 deletions examples/evm-to-evm-generic-mesage-passing/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PRIVATE_KEY="YOUR_PRIVATE_KEY_HERE"
61 changes: 61 additions & 0 deletions examples/evm-to-evm-generic-mesage-passing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Sygma SDK Generic Message Example

This is an example script that demonstrates the functionality of the SDK using the Sygma ecosystem. The script showcases generic message passing (execution of a function on the destination chain) between two networks using the Sygma SDK.

## Prerequisites

Before running the script, ensure that you have the following:

- Node.js installed on your machine
- [Yarn](https://yarnpkg.com/) (version 3.4.1 or higher)
- Access to an Ethereum provider

## Getting started

### 1. Clone the repository

To get started, clone this repository to your local machine with:

```bash
git clone [email protected]:sygmaprotocol/sygma-sdk.git
cd sygma-sdk/
```

### 2. Install dependencies

Install the project dependencies by running:

```bash
yarn install
```

### 3. Build the sdk

To start the example you need to build the sdk first with:

```bash
yarn sdk:build
```

## Usage

To call a function on a destination chain contract:

```bash
yarn run transfer
```

The example will use `ethers` in conjuction with the sygma-sdk to
call a function on a smart contract on `Goerli` by calling the Deposit method on `Sepolia` and passing the details of the function to be called.

Replace the placeholder values in the `.env` file with your own Ethereum wallet private key and provider URL.

## Script Functionality

This example script performs the following steps:
- initializes the SDK and establishes a connection to the Ethereum provider.
- retrieves the list of supported domains and resources from the SDK configuration.
- Searches for the Generic Message Passing resource from the list of supported resources registered.
- Searches for the Goerli and Sepolia domains in the list of supported domains based on their chain IDs.
- Constructs a transfer object that defines the details of the destination chain smart contract, function and call-data
- Builds the final transfer transaction and sends it using the Ethereum wallet.
37 changes: 37 additions & 0 deletions examples/evm-to-evm-generic-mesage-passing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@buildwithsygma/sygma-sdk-evm-to-evm-generic-message-example",
"version": "0.1.0",
"description": "Sygma sdk examples",
"type": "module",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/sygmaprotocol/sygma-sdk"
},
"keywords": [
"sygma",
"sygmaprotocol",
"buildwithsygma",
"web3",
"bridge",
"ethereum"
],
"scripts": {
"transfer": "ts-node src/transfer.ts"
},
"author": "Sygmaprotocol Product Team",
"license": "LGPL-3.0-or-later",
"devDependencies": {
"dotenv": "16.3.1",
"eslint": "8",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-only-warn": "1.0.3",
"eslint-plugin-prettier": "4.0.0",
"ts-node": "10.9.1",
"typescript": "5.0.4"
},
"dependencies": {
"@buildwithsygma/sygma-sdk-core": "2.1.0",
"ethers": "5.6.2"
}
}
55 changes: 55 additions & 0 deletions examples/evm-to-evm-generic-mesage-passing/src/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import dotenv from "dotenv";
import {
EVMGenericMessageTransfer,
Environment,
} from "@buildwithsygma/sygma-sdk-core";
import { Wallet, providers, utils } from "ethers";

dotenv.config();

const privateKey = process.env.PRIVATE_KEY;

if (!privateKey) {
throw new Error("Missing environment variable: PRIVATE_KEY");
}

const DESTINATION_CHAIN_ID = 5; // Goerli
const RESOURCE_ID =
"0x0000000000000000000000000000000000000000000000000000000000000500"; // Generic Message Handler
const EXECUTE_CONTRACT_ADDRESS = "0xdFA5621F95675D37248bAc9e536Aab4D86766663";
const EXECUTE_FUNCTION_SIGNATURE = "0xa271ced2";
const MAX_FEE = "3000000";

export async function genericMessage(): Promise<void> {
const provider = new providers.JsonRpcProvider(
"https://gateway.tenderly.co/public/sepolia"
);
const wallet = new Wallet(privateKey as string, provider);
const messageTransfer = new EVMGenericMessageTransfer();
await messageTransfer.init(provider, Environment.DEVNET);

const EXECUTION_DATA = utils.defaultAbiCoder.encode(["uint"], [Date.now()]);

const transfer = messageTransfer.createGenericMessageTransfer(
await wallet.getAddress(),
DESTINATION_CHAIN_ID,
RESOURCE_ID,
EXECUTE_CONTRACT_ADDRESS,
EXECUTE_FUNCTION_SIGNATURE,
EXECUTION_DATA,
MAX_FEE
);

const fee = await messageTransfer.getFee(transfer);
const transferTx = await messageTransfer.buildTransferTransaction(
transfer,
fee
);

const response = await wallet.sendTransaction(
transferTx as providers.TransactionRequest
);
console.log("Sent transfer with hash: ", response.hash);
}

genericMessage().finally(() => { });
25 changes: 25 additions & 0 deletions examples/evm-to-evm-generic-mesage-passing/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"composite": true,
"module": "ES2022",
"allowJs": true,
"declaration": true,
"sourceMap": true,
"declarationMap": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"esModuleInterop": true,
"downlevelIteration": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
},
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
},
"include": [
"src"
]
}
26 changes: 9 additions & 17 deletions packages/sdk/src/chains/BaseAssetTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,20 @@ export abstract class BaseAssetTransfer {
resourceId: string,
amount: string,
): Transfer<Fungible> {
const domains = this.config.getDomains();
const sourceDomain = domains.find(domain => domain.chainId == this.config.chainId);
if (!sourceDomain) {
throw new Error('Source domain not supported');
}
const destinationDomain = domains.find(domain => domain.chainId == destinationChainId);
if (!destinationDomain) {
throw new Error('Destination domain not supported');
}
const resources = this.config.getDomainResources();
const selectedResource = resources.find(resource => resource.resourceId == resourceId);
if (!selectedResource) {
throw new Error('Resource not supported');
}
const { sourceDomain, destinationDomain, resource } = this.config.getBaseTransferParams(
destinationChainId,
resourceId,
);

const transfer: Transfer<Fungible> = {
sender: sourceAddress,
amount: { amount },
details: {
amount,
recipient: destinationAddress,
},
from: sourceDomain,
to: destinationDomain,
resource: selectedResource,
recipient: destinationAddress,
resource: resource,
};

return transfer;
Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/src/chains/EVM/__test__/dynamicFee.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('feeOracle', () => {
toDomainID: 2,
resourceID: '0x0000000000000000000000000000000000000000000000000000000000000001',
}),
).rejects.toThrowError('Internal Error');
).rejects.toThrowError('Error fetching fee from fee oracle');
});

it('return error message from fee oracle server', async () => {
Expand Down Expand Up @@ -117,13 +117,13 @@ describe('feeOracle', () => {
const feeData = await calculateDynamicFee({
provider,
sender: ethers.constants.AddressZero,
recipientAddress: '0x74d2946319bEEe4A140068eb83F9ee3a90B06F4f',
fromDomainID: 1,
toDomainID: 2,
resourceID: '0x0000000000000000000000000000000000000000000000000000000000000001',
tokenAmount: '100',
feeOracleBaseUrl: 'http://localhost:8091',
feeHandlerAddress: '0xa9ddD97e1762920679f3C20ec779D79a81903c0B',
depositData: '0x',
});

expect(feeData?.feeData).toContain(oracleResponse.response.signature);
Expand All @@ -142,13 +142,13 @@ describe('feeOracle', () => {
await calculateDynamicFee({
provider,
sender: ethers.constants.AddressZero,
recipientAddress: '0x74d2946319bEEe4A140068eb83F9ee3a90B06F4f',
fromDomainID: 1,
toDomainID: 2,
resourceID: '0x0000000000000000000000000000000000000000000000000000000000000001',
tokenAmount: '100',
feeOracleBaseUrl: 'http://localhost:8091',
feeHandlerAddress: '0xa9ddD97e1762920679f3C20ec779D79a81903c0B',
depositData: '0x',
});
} catch (e) {
expect(e).toMatch('Err');
Expand All @@ -164,13 +164,13 @@ describe('feeOracle', () => {
calculateDynamicFee({
provider,
sender: ethers.constants.AddressZero,
recipientAddress: '0x74d2946319bEEe4A140068eb83F9ee3a90B06F4f',
fromDomainID: 1,
toDomainID: 2,
resourceID: '0x0000000000000000000000000000000000000000000000000000000000000001',
tokenAmount: '100',
feeOracleBaseUrl: 'http://localhost:8091',
feeHandlerAddress: '0xa9ddD97e1762920679f3C20ec779D79a81903c0B',
depositData: '0x',
}),
).rejects.toThrowError('Empty response data from fee oracle service');
});
Expand Down
23 changes: 14 additions & 9 deletions packages/sdk/src/chains/EVM/assetTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
approve,
calculateBasicfee,
calculateDynamicFee,
createERCDepositData,
erc20Transfer,
erc721Transfer,
getERC20Allowance,
Expand Down Expand Up @@ -112,16 +113,20 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
});
}
case FeeHandlerType.DYNAMIC: {
const fungibleTransfer = transfer as Transfer<Fungible>;
return await calculateDynamicFee({
provider: this.provider,
sender: transfer.sender,
recipientAddress: transfer.recipient,
fromDomainID: Number(transfer.from.id),
toDomainID: Number(transfer.to.id),
resourceID: transfer.resource.resourceId,
tokenAmount: (transfer.amount as Fungible).amount,
tokenAmount: fungibleTransfer.details.amount,
feeOracleBaseUrl: getFeeOracleBaseURL(this.environment),
feeHandlerAddress: feeHandlerAddress,
depositData: createERCDepositData(
fungibleTransfer.details.amount,
fungibleTransfer.details.recipient,
),
});
}
default:
Expand Down Expand Up @@ -198,8 +203,8 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
switch (transfer.resource.type) {
case ResourceType.FUNGIBLE: {
return await erc20Transfer({
amount: (transfer.amount as Fungible).amount,
recipientAddress: transfer.recipient,
amount: (transfer.details as Fungible).amount,
recipientAddress: (transfer.details as Fungible).recipient,
bridgeInstance: bridge,
domainId: transfer.to.id.toString(),
resourceId: transfer.resource.resourceId,
Expand All @@ -208,8 +213,8 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
}
case ResourceType.NON_FUNGIBLE: {
return await erc721Transfer({
id: (transfer.amount as NonFungible).id,
recipientAddress: transfer.recipient,
id: (transfer.details as NonFungible).tokenId,
recipientAddress: (transfer.details as NonFungible).recipient,
bridgeInstance: bridge,
domainId: transfer.to.id.toString(),
resourceId: transfer.resource.resourceId,
Expand All @@ -235,7 +240,7 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
approvals.push(await approve(fee.fee.toString(), erc20, fee.handlerAddress));
}

const transferAmount = BigNumber.from(transfer.amount.amount);
const transferAmount = BigNumber.from(transfer.details.amount);
if ((await getERC20Allowance(transfer.sender, erc20, handlerAddress)).lt(transferAmount)) {
approvals.push(await approve(transferAmount.toString(), erc20, handlerAddress));
}
Expand All @@ -249,8 +254,8 @@ export class EVMAssetTransfer extends BaseAssetTransfer {
handlerAddress: string,
): Promise<Array<PopulatedTransaction>> {
const approvals: Array<PopulatedTransaction> = [];
if (!(await isApproved(Number(transfer.amount.id), erc721, handlerAddress))) {
approvals.push(await approve(transfer.amount.id, erc721, handlerAddress));
if (!(await isApproved(Number(transfer.details.tokenId), erc721, handlerAddress))) {
approvals.push(await approve(transfer.details.tokenId, erc721, handlerAddress));
}

return approvals;
Expand Down
Loading

0 comments on commit 5cc18ee

Please sign in to comment.