Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 2cb56a2

Browse files
feat(evm): Update SDK to Support New ERC20Handler with Optional Contract Call (#504)
## Implementation details With the introduction of the new `ERC20Handler`, the SDK needs to be updated to handle the enhanced functionality that supports both ERC20 token transfers and optional contract calls in a single transaction. [New ERC20Handler PR](sprintertech/sygma-solidity#266) - Modify the SDK to work with the updated `ERC20Handler` by allowing it to handle additional data that encodes contract calls alongside ERC20 transfers. - Maintain compatibility with existing handlers and workflows that do not involve contract calls, ensuring that the SDK remains functional for all users. ## Closes: #493 ## Testing details - **Unit Tests**: Develop unit tests that cover scenarios where both ERC20 transfers and contract calls are involved. - **Compatibility Tests**: Ensure that existing functionality remains unaffected by the changes. - **Error Handling**: Test how the SDK handles invalid or failed contract calls within the transaction flow. ## Acceptance Criteria - [ ] The SDK supports interactions with the new `ERC20Handler`, allowing for both ERC20 transfers and optional contract calls within the same transaction. - [ ] The SDK maintains backward compatibility and continues to function as expected with existing handlers. - [ ] All new functionality is covered by tests, ensuring reliable and consistent behavior.
1 parent 90c75be commit 2cb56a2

16 files changed

+482
-588
lines changed

examples/evm-to-evm-fungible-transfer/src/transfer.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Eip1193Provider, getSygmaScanLink } from "@buildwithsygma/core";
1+
import { getSygmaScanLink, type Eip1193Provider } from "@buildwithsygma/core";
22
import {
33
createFungibleAssetTransfer,
44
FungibleTransferParams,
@@ -16,11 +16,12 @@ if (!privateKey) {
1616
}
1717

1818
const SEPOLIA_CHAIN_ID = 11155111;
19-
const AMOY_CHAIN_ID = 80002;
19+
const AMOY_CHAIN_ID = 84532;
2020
const RESOURCE_ID =
21-
"0x0000000000000000000000000000000000000000000000000000000000000300";
21+
"0x0000000000000000000000000000000000000000000000000000000000001200";
2222
const SEPOLIA_RPC_URL =
23-
process.env.SEPOLIA_RPC_URL || "https://eth-sepolia-public.unifra.io";
23+
process.env.SEPOLIA_RPC_URL ||
24+
"https://eth-sepolia.g.alchemy.com/v2/MeCKDrpxLkGOn4LMlBa3cKy1EzzOzwzG";
2425

2526
const explorerUrls: Record<number, string> = {
2627
[SEPOLIA_CHAIN_ID]: "https://sepolia.etherscan.io",
@@ -42,9 +43,10 @@ export async function erc20Transfer(): Promise<void> {
4243
destination: AMOY_CHAIN_ID,
4344
sourceNetworkProvider: web3Provider as unknown as Eip1193Provider,
4445
resource: RESOURCE_ID,
45-
amount: BigInt(1) * BigInt(1e18),
46+
amount: BigInt(1) * BigInt(1e6),
4647
recipientAddress: destinationAddress,
47-
sourceAddress,
48+
sourceAddress: sourceAddress,
49+
optionalGas: BigInt(500000),
4850
};
4951

5052
const transfer = await createFungibleAssetTransfer(params);

examples/substrate-to-evm-fungible-transfer/src/transfer.ts

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const substrateTransfer = async (): Promise<void> => {
4444
resource: RESOURCE_ID_SYGMA_USD,
4545
amount: BigInt("1"),
4646
destinationAddress: recipient,
47-
sourceAddress: account.address,
4847
};
4948

5049
const transfer = await createSubstrateFungibleAssetTransfer(transferParams);

packages/evm/src/evmAssetTransfer.ts

+11-9
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import type { Config } from '@buildwithsygma/core';
22
import { isValidAddressForNetwork } from '@buildwithsygma/core';
33
import { Bridge__factory } from '@buildwithsygma/sygma-contracts';
44
import { Web3Provider } from '@ethersproject/providers';
5+
import type { PayableOverrides } from 'ethers';
56
import { constants, utils } from 'ethers';
67

78
import { EvmTransfer } from './evmTransfer.js';
89
import type { EvmAssetTransferParams, EvmFee, TransactionRequest } from './types.js';
9-
import { assetTransfer } from './utils/index.js';
10+
import { executeDeposit } from './utils/depositFn.js';
1011
import { createTransactionRequest } from './utils/transaction.js';
1112

1213
/**
@@ -62,7 +63,7 @@ export abstract class AssetTransfer extends EvmTransfer implements IAssetTransfe
6263
* Get transfer transaction
6364
* @returns {Promise<TransactionRequest>}
6465
*/
65-
public async getTransferTransaction(): Promise<TransactionRequest> {
66+
public async getTransferTransaction(overrides?: PayableOverrides): Promise<TransactionRequest> {
6667
const domainConfig = this.config.getDomainConfig(this.source);
6768
const provider = new Web3Provider(this.sourceNetworkProvider);
6869
const bridge = Bridge__factory.connect(domainConfig.bridge, provider);
@@ -71,13 +72,14 @@ export abstract class AssetTransfer extends EvmTransfer implements IAssetTransfe
7172
const hasBalance = await this.hasEnoughBalance(fee);
7273
if (!hasBalance) throw new Error('Insufficient token balance');
7374

74-
const transferTx = await assetTransfer({
75-
depositData: this.getDepositData(),
76-
bridgeInstance: bridge,
77-
domainId: this.destination.id.toString(),
78-
resourceId: this.resource.resourceId,
79-
feeData: fee,
80-
});
75+
const transferTx = await executeDeposit(
76+
this.destination.id.toString(),
77+
this.resource.resourceId,
78+
this.getDepositData(),
79+
fee,
80+
bridge,
81+
overrides,
82+
);
8183

8284
return createTransactionRequest(transferTx);
8385
}

packages/evm/src/fungibleAssetTransfer.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import { Web3Provider } from '@ethersproject/providers';
55
import { BigNumber, constants, type PopulatedTransaction, utils } from 'ethers';
66

77
import { AssetTransfer } from './evmAssetTransfer.js';
8-
import type { EvmFee, FungibleTransferParams, TransactionRequest } from './types.js';
8+
import type {
9+
EvmFee,
10+
FungibleTransferOptionalMessage,
11+
FungibleTransferParams,
12+
TransactionRequest,
13+
} from './types.js';
914
import { approve, getERC20Allowance } from './utils/approveAndCheckFns.js';
10-
import { createERCDepositData } from './utils/helpers.js';
15+
import { createFungibleDepositData } from './utils/assetTransferHelpers.js';
1116
import { createTransactionRequest } from './utils/transaction.js';
1217

1318
/**
@@ -36,6 +41,9 @@ class FungibleAssetTransfer extends AssetTransfer {
3641
protected declare adjustedAmount: bigint;
3742
protected specifiedAmount: bigint;
3843

44+
protected optionalGas?: bigint;
45+
protected optionalMessage?: FungibleTransferOptionalMessage;
46+
3947
/**
4048
* Returns amount to be transferred considering the fee
4149
*/
@@ -47,6 +55,8 @@ class FungibleAssetTransfer extends AssetTransfer {
4755
super(transfer, config);
4856
this.specifiedAmount = transfer.amount;
4957
this.securityModel = transfer.securityModel ?? SecurityModel.MPC;
58+
this.optionalGas = transfer.optionalGas;
59+
this.optionalMessage = transfer.optionalMessage;
5060
}
5161

5262
/**
@@ -55,7 +65,13 @@ class FungibleAssetTransfer extends AssetTransfer {
5565
* @returns {string}
5666
*/
5767
protected getDepositData(): string {
58-
return createERCDepositData(this.adjustedAmount, this.recipient, this.destination.parachainId);
68+
return createFungibleDepositData({
69+
destination: this.destination,
70+
recipientAddress: this.recipientAddress,
71+
amount: this.adjustedAmount,
72+
optionalGas: this.optionalGas,
73+
optionalMessage: this.optionalMessage,
74+
});
5975
}
6076

6177
/**

packages/evm/src/genericMessageTransfer.ts

+28-72
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EthereumConfig, EvmResource, SubstrateResource } from '@buildwithsygma/core';
1+
import type { EthereumConfig } from '@buildwithsygma/core';
22
import { Config, Network, ResourceType } from '@buildwithsygma/core';
33
import { Bridge__factory } from '@buildwithsygma/sygma-contracts';
44
import { Web3Provider } from '@ethersproject/providers';
@@ -8,16 +8,14 @@ import type {
88
ExtractAbiFunction,
99
ExtractAbiFunctionNames,
1010
} from 'abitype';
11-
import { constants, ethers } from 'ethers';
11+
import type { ethers } from 'ethers';
12+
import { constants } from 'ethers';
1213

1314
import { EvmTransfer } from './evmTransfer.js';
1415
import { getFeeInformation } from './fee/getFeeInformation.js';
1516
import type { GenericMessageTransferParams, TransactionRequest } from './types.js';
16-
import {
17-
createPermissionlessGenericDepositData,
18-
serializeGenericCallParameters,
19-
} from './utils/helpers.js';
20-
import { genericMessageTransfer } from './utils/index.js';
17+
import { createGenericCallDepositData } from './utils/genericTransferHelpers.js';
18+
import { executeDeposit } from './utils/index.js';
2119
import { createTransactionRequest } from './utils/transaction.js';
2220

2321
/**
@@ -57,43 +55,6 @@ class GenericMessageTransfer<
5755
this.maxFee = params.maxFee;
5856
}
5957

60-
/**
61-
* Get prepared additional deposit data
62-
* in hex string format
63-
* @returns {string}
64-
*/
65-
protected getDepositData(): string {
66-
const { executeFunctionSignature, executionData } = this.prepareFunctionCallEncodings();
67-
return createPermissionlessGenericDepositData(
68-
executeFunctionSignature,
69-
this.destinationContractAddress,
70-
this.maxFee.toString(),
71-
this.sourceAddress,
72-
executionData,
73-
);
74-
}
75-
76-
/**
77-
* Prepare function call encodings
78-
* @returns {{ executionData: string; executionFunctionSignature: string; }}
79-
*/
80-
private prepareFunctionCallEncodings(): {
81-
executionData: string;
82-
executeFunctionSignature: string;
83-
} {
84-
const contractInterface = new ethers.utils.Interface(
85-
JSON.stringify(this.destinationContractAbi),
86-
);
87-
88-
let executionData = ``;
89-
if (Array.isArray(this.functionParameters)) {
90-
executionData = serializeGenericCallParameters(this.functionParameters);
91-
}
92-
93-
const executeFunctionSignature = contractInterface.getSighash(this.functionName);
94-
return { executionData, executeFunctionSignature };
95-
}
96-
9758
/**
9859
* Checks validity of the transfer
9960
* @returns {Promise<boolean>}
@@ -166,17 +127,6 @@ class GenericMessageTransfer<
166127
): void {
167128
this.functionParameters = parameters;
168129
}
169-
170-
public setResource(resource: EvmResource | SubstrateResource): void {
171-
if (
172-
resource.type !== ResourceType.PERMISSIONED_GENERIC &&
173-
resource.type !== ResourceType.PERMISSIONLESS_GENERIC
174-
) {
175-
throw new Error('Unsupported Resource type.');
176-
}
177-
this.transferResource = resource;
178-
}
179-
180130
/**
181131
* Get the cross chain generic message transfer
182132
* transaction
@@ -187,33 +137,39 @@ class GenericMessageTransfer<
187137
const isValid = await this.isValidTransfer();
188138
if (!isValid) throw new Error('Invalid Transfer.');
189139

190-
const { executeFunctionSignature, executionData } = this.prepareFunctionCallEncodings();
191-
const { resourceId } = this.resource;
192-
193-
const executeContractAddress = this.destinationContractAddress;
194140
const sourceDomain = this.config.getDomainConfig(this.source);
195-
const domainId = this.config.getDomainConfig(this.destination).id.toString();
196141
const provider = new Web3Provider(this.sourceNetworkProvider);
197-
const depositor = this.sourceAddress;
198-
const maxFee = this.maxFee.toString();
199142
const bridgeInstance = Bridge__factory.connect(sourceDomain.bridge, provider);
200143
const feeData = await this.getFee();
144+
const depositData = this.getDepositData();
201145

202-
const transaction = await genericMessageTransfer({
203-
executeFunctionSignature,
204-
executeContractAddress,
205-
maxFee,
206-
depositor,
207-
executionData,
208-
bridgeInstance,
209-
domainId,
210-
resourceId,
146+
const transaction = await executeDeposit(
147+
this.destination.id.toString(),
148+
this.resource.resourceId,
149+
depositData,
211150
feeData,
151+
bridgeInstance,
212152
overrides,
213-
});
153+
);
214154

215155
return createTransactionRequest(transaction);
216156
}
157+
/**
158+
* Get prepared additional deposit data
159+
* in hex string format
160+
* @returns {string}
161+
*/
162+
protected getDepositData(): string {
163+
return createGenericCallDepositData({
164+
abi: this.destinationContractAbi,
165+
functionName: this.functionName,
166+
functionParams: this.functionParameters,
167+
contractAddress: this.destinationContractAddress,
168+
destination: this.destination,
169+
maxFee: this.maxFee,
170+
depositor: this.sourceAddress as `0x${string}`,
171+
});
172+
}
217173
}
218174

219175
/**

packages/evm/src/nonFungibleAssetTransfer.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { providers } from 'ethers';
99

1010
import { AssetTransfer } from './evmAssetTransfer.js';
1111
import type { EvmFee, NonFungibleTransferParams, TransactionRequest } from './types.js';
12-
import { createERCDepositData } from './utils/helpers.js';
12+
import { createFungibleDepositData } from './utils/assetTransferHelpers.js';
1313
import { approve, isApproved } from './utils/index.js';
1414
import { createTransactionRequest } from './utils/transaction.js';
1515

@@ -31,7 +31,11 @@ class NonFungibleAssetTransfer extends AssetTransfer {
3131
* @returns {string}
3232
*/
3333
protected getDepositData(): string {
34-
return createERCDepositData(BigInt(this.tokenId), this.recipient, this.destination.parachainId);
34+
return createFungibleDepositData({
35+
destination: this.destination,
36+
recipientAddress: this.recipientAddress,
37+
tokenId: this.tokenId,
38+
});
3539
}
3640

3741
/**

packages/evm/src/types.ts

+17
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ export type EvmFee = {
4545
/** An EVM resource is accepted as either the resource object or it's Sygma ID */
4646
export type EvmResourceish = string | EvmResource;
4747

48+
interface FungibleDepositAction {
49+
nativeValue: bigint;
50+
callTo: string;
51+
approveTo: string;
52+
tokenSend: string;
53+
tokenReceive: string;
54+
data: string;
55+
}
56+
57+
export interface FungibleTransferOptionalMessage {
58+
transactionId: string;
59+
actions: FungibleDepositAction[];
60+
receiver: string;
61+
}
62+
4863
export interface EvmTransferParams extends BaseTransferParams {
4964
sourceAddress: string;
5065
sourceNetworkProvider: Eip1193Provider;
@@ -59,6 +74,8 @@ export interface EvmAssetTransferParams extends EvmTransferParams {
5974
export interface FungibleTransferParams extends EvmAssetTransferParams {
6075
amount: bigint;
6176
securityModel?: SecurityModel;
77+
optionalGas?: bigint;
78+
optionalMessage?: FungibleTransferOptionalMessage;
6279
}
6380

6481
export interface NonFungibleTransferParams extends EvmAssetTransferParams {

0 commit comments

Comments
 (0)