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

Commit 201942b

Browse files
authored
fix!: throw an error when the source account has low balance (#417)
Closes #338
1 parent 4200f28 commit 201942b

25 files changed

+449
-145
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Eip1193Provider, Environment } from "@buildwithsygma/core";
1+
import type { Eip1193Provider } from "@buildwithsygma/core";
2+
import { Environment } from "@buildwithsygma/core";
23
import { createEvmFungibleAssetTransfer } from "@buildwithsygma/evm";
34
import dotenv from "dotenv";
45
import { Wallet, providers } from "ethers";
@@ -13,12 +14,19 @@ if (!privateKey) {
1314
}
1415

1516
const SEPOLIA_CHAIN_ID = 11155111;
16-
const HOLESKY_CHAIN_ID = 17000;
17-
const RESOURCE_ID = "0x0000000000000000000000000000000000000000000000000000000000000200";
18-
const SEPOLIA_RPC_URL = process.env.SEPOLIA_RPC_URL || "https://eth-sepolia-public.unifra.io"
17+
const AMOY_CHAIN_ID = 80002;
18+
const RESOURCE_ID =
19+
"0x0000000000000000000000000000000000000000000000000000000000000300";
20+
const SEPOLIA_RPC_URL =
21+
process.env.SEPOLIA_RPC_URL || "https://eth-sepolia-public.unifra.io";
1922

20-
const explorerUrls: Record<number, string> = { [SEPOLIA_CHAIN_ID]: 'https://sepolia.etherscan.io' };
21-
const getTxExplorerUrl = (params: { txHash: string; chainId: number }): string => `${explorerUrls[params.chainId]}/tx/${params.txHash}`;
23+
const explorerUrls: Record<number, string> = {
24+
[SEPOLIA_CHAIN_ID]: "https://sepolia.etherscan.io",
25+
};
26+
const getTxExplorerUrl = (params: {
27+
txHash: string;
28+
chainId: number;
29+
}): string => `${explorerUrls[params.chainId]}/tx/${params.txHash}`;
2230

2331
export async function erc20Transfer(): Promise<void> {
2432
const web3Provider = new Web3HttpProvider(SEPOLIA_RPC_URL);
@@ -28,29 +36,32 @@ export async function erc20Transfer(): Promise<void> {
2836

2937
const params = {
3038
source: SEPOLIA_CHAIN_ID,
31-
destination: HOLESKY_CHAIN_ID,
39+
destination: AMOY_CHAIN_ID,
3240
sourceNetworkProvider: web3Provider as unknown as Eip1193Provider,
3341
resource: RESOURCE_ID,
34-
amount: BigInt(2) * BigInt(1e18),
42+
amount: BigInt(1) * BigInt(1e18),
3543
destinationAddress: destinationAddress,
36-
environment: Environment.DEVNET,
44+
environment: (process.env.SYGMA_ENV as Environment) || Environment.TESTNET,
3745
sourceAddress: destinationAddress,
3846
};
3947

4048
const transfer = await createEvmFungibleAssetTransfer(params);
41-
4249
const approvals = await transfer.getApprovalTransactions();
4350
console.log(`Approving Tokens (${approvals.length})...`);
4451
for (const approval of approvals) {
4552
const response = await wallet.sendTransaction(approval);
4653
await response.wait();
47-
console.log(`Approved, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`);
54+
console.log(
55+
`Approved, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`,
56+
);
4857
}
4958

5059
const transferTx = await transfer.getTransferTransaction();
5160
const response = await wallet.sendTransaction(transferTx);
5261
await response.wait();
53-
console.log(`Depositted, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`);
62+
console.log(
63+
`Deposited, transaction: ${getTxExplorerUrl({ txHash: response.hash, chainId: SEPOLIA_CHAIN_ID })}`,
64+
);
5465
}
5566

5667
erc20Transfer().finally(() => {});

examples/substrate-to-evm-fungible-transfer/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"dependencies": {
3131
"@buildwithsygma/core": "workspace:*",
3232
"@buildwithsygma/substrate": "workspace:*",
33-
"@polkadot/api": "^12.2.1",
33+
"@polkadot/api": "^12.3.1",
3434
"@polkadot/keyring": "^12.6.2",
3535
"@polkadot/util-crypto": "^12.6.2",
3636
"tsx": "^4.15.4"

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

+4-3
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ const substrateTransfer = async (): Promise<void> => {
3737
const api = await ApiPromise.create({ provider: wsProvider });
3838

3939
const transferParams: SubstrateAssetTransferRequest = {
40-
sourceDomain: RHALA_CHAIN_ID,
41-
destinationDomain: SEPOLIA_CHAIN_ID,
40+
source: RHALA_CHAIN_ID,
41+
destination: SEPOLIA_CHAIN_ID,
4242
sourceNetworkProvider: api,
4343
resource: RESOURCE_ID_SYGMA_USD,
44-
amount: BigInt("5000000"),
44+
amount: BigInt("1"),
4545
destinationAddress: recipient,
46+
sourceAddress: account.address,
4647
};
4748

4849
const transfer = await createSubstrateFungibleAssetTransfer(transferParams);

packages/core/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"resolveJsonModule": true
2626
},
2727
"exclude": ["node_modules/**"],
28-
"include": ["./src/**/*.ts", "./test/**/*.ts", "substrate-asset-transfer.ts"],
28+
"include": ["./src/**/*.ts", "./test/**/*.ts", "substrate-asset-transfer.ts", "./src/environment.d.ts"],
2929
"ts-node": {
3030
"esm": true,
3131
"experimentalSpecifierResolution": "node"

packages/evm/src/__test__/fungible.test.ts

+97-2
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,21 @@ jest.mock('@buildwithsygma/core', () => ({
4545
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
4646
jest.mock('@ethersproject/providers', () => ({
4747
...jest.requireActual('@ethersproject/providers'),
48-
Web3Provider: jest.fn(),
48+
Web3Provider: jest.fn().mockImplementation(() => {
49+
return {
50+
getBalance: jest.fn().mockResolvedValue(BigNumber.from('110000000000000000')),
51+
};
52+
}),
4953
}));
5054

5155
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
5256
jest.mock('@buildwithsygma/sygma-contracts', () => ({
5357
...jest.requireActual('@buildwithsygma/sygma-contracts'),
5458
Bridge__factory: { connect: jest.fn() },
55-
ERC20__factory: { connect: jest.fn() },
59+
ERC20__factory: {
60+
balanceOf: jest.fn().mockResolvedValue(BigInt(1)),
61+
connect: jest.fn(),
62+
},
5663
BasicFeeHandler__factory: { connect: jest.fn() },
5764
PercentageERC20FeeHandler__factory: { connect: jest.fn() },
5865
FeeHandlerRouter__factory: { connect: jest.fn() },
@@ -182,6 +189,7 @@ describe('Fungible - Approvals', () => {
182189
});
183190

184191
(ERC20__factory.connect as jest.Mock).mockReturnValue({
192+
balanceOf: jest.fn().mockResolvedValue(BigNumber.from(parseEther('50'))),
185193
populateTransaction: {
186194
approve: jest.fn().mockResolvedValue({}),
187195
},
@@ -206,6 +214,55 @@ describe('Fungible - Approvals', () => {
206214

207215
expect(approvals.length).toBeGreaterThan(0);
208216
});
217+
218+
it('should throw an error if balance is not sufficient - Basic', async () => {
219+
(BasicFeeHandler__factory.connect as jest.Mock).mockReturnValue({
220+
feeHandlerType: jest.fn().mockResolvedValue('basic'),
221+
calculateFee: jest.fn().mockResolvedValue([parseEther('1')]),
222+
});
223+
224+
const transfer = await createEvmFungibleAssetTransfer({
225+
...TRANSFER_PARAMS,
226+
amount: parseEther('0').toBigInt(),
227+
});
228+
229+
await expect(transfer.getApprovalTransactions()).rejects.toThrow(
230+
'Insufficient native token balance for network',
231+
);
232+
});
233+
234+
it('should throw an error if balance is not sufficient - Percentage', async () => {
235+
(BasicFeeHandler__factory.connect as jest.Mock).mockReturnValue({
236+
feeHandlerType: jest.fn().mockResolvedValue('percentage'),
237+
calculateFee: jest.fn().mockResolvedValue([BigNumber.from(0)]),
238+
});
239+
(PercentageERC20FeeHandler__factory.connect as jest.Mock).mockReturnValue({
240+
feeHandlerType: jest.fn().mockResolvedValue('percentage'),
241+
calculateFee: jest.fn().mockResolvedValue([BigNumber.from(0)]),
242+
_resourceIDToFeeBounds: jest.fn().mockResolvedValue({
243+
lowerBound: parseEther('10'),
244+
upperBound: parseEther('100'),
245+
}),
246+
_domainResourceIDToFee: jest.fn().mockResolvedValue(BigNumber.from(100)),
247+
HUNDRED_PERCENT: jest.fn().mockResolvedValue(10000),
248+
});
249+
(ERC20__factory.connect as jest.Mock).mockReturnValue({
250+
balanceOf: jest.fn().mockResolvedValue(BigNumber.from(parseEther('1').toBigInt())), // Mock balance less than the required amount
251+
populateTransaction: {
252+
approve: jest.fn().mockResolvedValue({}),
253+
},
254+
allowance: jest.fn().mockResolvedValue(parseEther('0')),
255+
});
256+
257+
const transfer = await createEvmFungibleAssetTransfer({
258+
...TRANSFER_PARAMS,
259+
amount: parseEther('100').toBigInt(),
260+
});
261+
262+
await expect(transfer.getApprovalTransactions()).rejects.toThrow(
263+
'Insufficient ERC20 token balance',
264+
);
265+
});
209266
});
210267

211268
describe('Fungible - Deposit', () => {
@@ -223,6 +280,14 @@ describe('Fungible - Deposit', () => {
223280
.mockResolvedValue('0x98729c03c4D5e820F5e8c45558ae07aE63F97461'),
224281
});
225282

283+
(ERC20__factory.connect as jest.Mock).mockReturnValue({
284+
balanceOf: jest.fn().mockResolvedValue(BigNumber.from(parseEther('50').toBigInt())),
285+
populateTransaction: {
286+
approve: jest.fn().mockResolvedValue({}),
287+
},
288+
allowance: jest.fn().mockResolvedValue(parseEther('0')),
289+
});
290+
226291
(Bridge__factory.connect as jest.Mock).mockReturnValue({
227292
populateTransaction: {
228293
deposit: jest.fn().mockReturnValue({
@@ -249,4 +314,34 @@ describe('Fungible - Deposit', () => {
249314

250315
expect(depositTransaction).toBeTruthy();
251316
});
317+
318+
it('should throw ERROR - Insufficient account balance - Percentage', async () => {
319+
(BasicFeeHandler__factory.connect as jest.Mock).mockReturnValue({
320+
feeHandlerType: jest.fn().mockResolvedValue('percentage'),
321+
calculateFee: jest.fn().mockResolvedValue([BigNumber.from(0)]),
322+
});
323+
(PercentageERC20FeeHandler__factory.connect as jest.Mock).mockReturnValue({
324+
feeHandlerType: jest.fn().mockResolvedValue('percentage'),
325+
calculateFee: jest.fn().mockResolvedValue([BigNumber.from(0)]),
326+
_resourceIDToFeeBounds: jest.fn().mockResolvedValue({
327+
lowerBound: parseEther('10'),
328+
upperBound: parseEther('100'),
329+
}),
330+
_domainResourceIDToFee: jest.fn().mockResolvedValue(BigNumber.from(100)),
331+
HUNDRED_PERCENT: jest.fn().mockResolvedValue(10000),
332+
});
333+
(ERC20__factory.connect as jest.Mock).mockReturnValue({
334+
balanceOf: jest.fn().mockResolvedValue(BigNumber.from(parseEther('1').toBigInt())), // Mock balance less than the required amount
335+
populateTransaction: {
336+
approve: jest.fn().mockResolvedValue({}),
337+
},
338+
allowance: jest.fn().mockResolvedValue(parseEther('0')),
339+
});
340+
341+
const transfer = await createEvmFungibleAssetTransfer(TRANSFER_PARAMS);
342+
343+
await expect(transfer.getTransferTransaction()).rejects.toThrow(
344+
'Insufficient ERC20 token balance',
345+
);
346+
});
252347
});

packages/evm/src/baseTransfer.ts

Whitespace-only changes.

packages/evm/src/evmTransfer.ts

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export class EvmTransfer extends BaseTransfer {
2424

2525
/**
2626
* Returns fee based on transfer amount.
27-
* @param amount By default it is original amount passed in constructor
2827
*/
2928
async getFee(): Promise<EvmFee> {
3029
const provider = new providers.Web3Provider(this.sourceNetworkProvider);

packages/evm/src/fee/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './BasicFee.js';
22
export * from './getFeeInformation.js';
33
export * from './PercentageFee.js';
4+
export * from './TwapFee.js';
45
export * from './types.js';

packages/evm/src/fee/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { FeeHandlerType } from '@buildwithsygma/core';
22
import type { ethers } from 'ethers';
3-
import type { EvmFee } from 'types';
3+
4+
import type { EvmFee } from '../types.js';
45

56
/**
67
* Parameters that are required to

0 commit comments

Comments
 (0)