Skip to content

Commit 2ac9cbe

Browse files
committed
Merge branch 'main' of github.com:push-protocol/push-chain-sdk
2 parents 5cae55b + e6f2e59 commit 2ac9cbe

File tree

8 files changed

+262
-7
lines changed

8 files changed

+262
-7
lines changed

packages/core/__e2e__/pushchain.spec.ts

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { CHAIN_INFO } from '../src/lib/constants/chain';
1818
import dotenv from 'dotenv';
1919
import path from 'path';
2020
import { UniversalTxResponse } from '../src/lib/orchestrator/orchestrator.types';
21-
import { sepolia, arbitrumSepolia, baseSepolia } from 'viem/chains';
21+
import { sepolia, arbitrumSepolia, baseSepolia, bscTestnet } from 'viem/chains';
2222
import bs58 from 'bs58';
2323

2424
// Adjust path as needed if your .env is in the root
@@ -446,6 +446,145 @@ describe('PushChain (e2e)', () => {
446446
});
447447
}, 300000);
448448
});
449+
450+
describe('BNB_TESTNET', () => {
451+
const originChain = CHAIN.BNB_TESTNET;
452+
let pushClient: PushChain;
453+
454+
beforeAll(async () => {
455+
const privateKey = process.env['EVM_PRIVATE_KEY'] as Hex;
456+
if (!privateKey) throw new Error('EVM_PRIVATE_KEY not set');
457+
458+
const account = privateKeyToAccount(privateKey);
459+
const walletClient = createWalletClient({
460+
account,
461+
transport: http(CHAIN_INFO[originChain].defaultRPC[0]),
462+
});
463+
464+
universalSigner = await PushChain.utils.signer.toUniversalFromKeypair(
465+
walletClient,
466+
{
467+
chain: originChain,
468+
library: PushChain.CONSTANTS.LIBRARY.ETHEREUM_VIEM,
469+
}
470+
);
471+
472+
pushClient = await PushChain.initialize(universalSigner, {
473+
network: pushNetwork,
474+
progressHook: (val: any) => {
475+
console.log(val);
476+
},
477+
});
478+
479+
// Generate random account
480+
randomAccount = privateKeyToAccount(generatePrivateKey());
481+
// Try to send BNB Testnet ETH to random generated address
482+
const txHash = await walletClient.sendTransaction({
483+
to: randomAccount.address,
484+
chain: bscTestnet,
485+
value: PushChain.utils.helpers.parseUnits('1', 14),
486+
});
487+
const publicClient = createPublicClient({
488+
chain: bscTestnet,
489+
transport: http(),
490+
});
491+
await publicClient.waitForTransactionReceipt({
492+
hash: txHash,
493+
});
494+
}, 100000);
495+
496+
it('should fail to send universal.sendTransaction with invalid feeLockTxHash', async () => {
497+
await expect(
498+
pushClient.universal.sendTransaction({
499+
to,
500+
feeLockTxHash: '0xABC', // Invalid txHash
501+
value: BigInt(1e3),
502+
})
503+
).rejects.toThrow();
504+
}, 30000);
505+
506+
it('should fail to send universal.sendTransaction with fundGas property', async () => {
507+
await expect(
508+
pushClient.universal.sendTransaction({
509+
to,
510+
value: BigInt(1e3),
511+
fundGas: {
512+
chainToken: '0x1234567890123456789012345678901234567890',
513+
},
514+
})
515+
).rejects.toThrow('Unsupported token');
516+
}, 30000);
517+
518+
it('should successfully send universal.sendTransaction without fundGas (default behavior)', async () => {
519+
const tx = await pushClient.universal.sendTransaction({
520+
to,
521+
value: BigInt(1e3),
522+
// fundGas not provided - should work fine
523+
});
524+
expect(tx).toBeDefined();
525+
expect(tx.hash).toMatch(/^0x[a-fA-F0-9]{64}$/);
526+
await txValidator(
527+
tx,
528+
pushClient.universal.origin.address as `0x${string}`,
529+
to
530+
);
531+
}, 300000);
532+
533+
it('should successfully sendTransaction - Transfer Call', async () => {
534+
const tx = await pushClient.universal.sendTransaction({
535+
to,
536+
value: BigInt(1e3),
537+
});
538+
const after = await PushChain.utils.account.convertOriginToExecutor(
539+
universalSigner.account,
540+
{
541+
onlyCompute: true,
542+
}
543+
);
544+
expect(after.deployed).toBe(true);
545+
await txValidator(
546+
tx,
547+
pushClient.universal.origin.address as `0x${string}`,
548+
to
549+
);
550+
}, 300000);
551+
552+
it('should successfully sendTransaction to funded undeployed UEA', async () => {
553+
const walletClient = createWalletClient({
554+
account: randomAccount,
555+
transport: http(CHAIN_INFO[originChain].defaultRPC[0]),
556+
});
557+
const randomUniversalSigner =
558+
await PushChain.utils.signer.toUniversalFromKeypair(walletClient, {
559+
chain: originChain,
560+
library: PushChain.CONSTANTS.LIBRARY.ETHEREUM_VIEM,
561+
});
562+
const UEA = await PushChain.utils.account.convertOriginToExecutor(
563+
randomUniversalSigner.account,
564+
{
565+
onlyCompute: true,
566+
}
567+
);
568+
569+
// Fund Undeployed UEA - 1PC
570+
await pushClient.universal.sendTransaction({
571+
to: UEA.address,
572+
value: BigInt(1e18),
573+
});
574+
575+
// Send Tx Via Random Address
576+
const randomPushClient = await PushChain.initialize(
577+
randomUniversalSigner,
578+
{
579+
network: pushNetwork,
580+
}
581+
);
582+
await randomPushClient.universal.sendTransaction({
583+
to,
584+
value: BigInt(1e6),
585+
});
586+
}, 300000);
587+
});
449588
});
450589
describe('Origin - Push', () => {
451590
const originChain = CHAIN.PUSH_TESTNET_DONUT;

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pushchain/core",
3-
"version": "2.0.21",
3+
"version": "2.1.0",
44
"homepage": "https://push.org",
55
"keywords": [
66
"pushchain",

packages/core/src/lib/constants/chain.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CHAIN, PUSH_NETWORK, VM } from './enums';
2-
import { mainnet, sepolia, arbitrumSepolia, baseSepolia } from 'viem/chains';
2+
import { mainnet, sepolia, arbitrumSepolia, baseSepolia, bscTestnet } from 'viem/chains';
33

44
/**
55
* Maps VM type to its namespace
@@ -97,6 +97,14 @@ export const CHAIN_INFO: Record<
9797
confirmations: 1,
9898
timeout: 30000,
9999
},
100+
[CHAIN.BNB_TESTNET]: {
101+
chainId: '97',
102+
vm: VM.EVM,
103+
lockerContract: '0x44aFFC61983F4348DdddB886349eb992C061EaC0',
104+
defaultRPC: [bscTestnet.rpcUrls.default.http[0]],
105+
confirmations: 1,
106+
timeout: 30000,
107+
},
100108

101109
// Solana
102110
[CHAIN.SOLANA_MAINNET]: {

packages/core/src/lib/constants/enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum CHAIN {
2525
ETHEREUM_SEPOLIA = 'eip155:11155111',
2626
ARBITRUM_SEPOLIA = 'eip155:421614',
2727
BASE_SEPOLIA = 'eip155:84532',
28+
BNB_TESTNET = 'eip155:97',
2829

2930
// Solana
3031
SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',

packages/core/src/lib/constants/tokens.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,22 @@ export const MOVEABLE_TOKENS: Partial<Record<CHAIN, MoveableToken[]>> = {
169169
},
170170
],
171171

172+
// BNB Testnet
173+
[CHAIN.BNB_TESTNET]: [
174+
{
175+
symbol: 'ETH',
176+
decimals: 18,
177+
address: EVM_NATIVE,
178+
mechanism: 'native',
179+
},
180+
{
181+
symbol: 'USDT',
182+
decimals: 6,
183+
address: '0xBC14F348BC9667be46b35Edc9B68653d86013DC5',
184+
mechanism: 'approve',
185+
},
186+
],
187+
172188
// Solana Devnet (decimals are per SPL mint; addresses TBD)
173189
[CHAIN.SOLANA_DEVNET]: [
174190
// Native SOL (lamports) sentinel: use 'solana-native' string; not used as a Pubkey
@@ -260,6 +276,20 @@ export const PAYABLE_TOKENS: Partial<Record<CHAIN, PayableToken[]>> = {
260276
mechanism: 'approve',
261277
},
262278
],
279+
[CHAIN.BNB_TESTNET]: [
280+
{
281+
symbol: 'BNB',
282+
decimals: 18,
283+
address: EVM_NATIVE,
284+
mechanism: 'native',
285+
},
286+
{
287+
symbol: 'USDT',
288+
decimals: 6,
289+
address: '0xBC14F348BC9667be46b35Edc9B68653d86013DC5',
290+
mechanism: 'approve',
291+
},
292+
],
263293
[CHAIN.SOLANA_DEVNET]: [
264294
{
265295
symbol: 'SOL',

packages/core/src/lib/orchestrator/orchestrator.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,12 @@ export class Orchestrator {
135135
chain === CHAIN.ETHEREUM_SEPOLIA ||
136136
chain === CHAIN.ARBITRUM_SEPOLIA ||
137137
chain === CHAIN.BASE_SEPOLIA ||
138+
chain === CHAIN.BNB_TESTNET ||
138139
chain === CHAIN.SOLANA_DEVNET
139140
)
140141
) {
141142
throw new Error(
142-
'Funds bridging is only supported on Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia, and Solana Devnet for now'
143+
'Funds bridging is only supported on Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia, BNB Testnet, and Solana Devnet for now'
143144
);
144145
}
145146

@@ -1693,10 +1694,11 @@ export class Orchestrator {
16931694
chain !== CHAIN.ETHEREUM_SEPOLIA &&
16941695
chain !== CHAIN.ARBITRUM_SEPOLIA &&
16951696
chain !== CHAIN.BASE_SEPOLIA &&
1697+
chain !== CHAIN.BNB_TESTNET &&
16961698
chain !== CHAIN.SOLANA_DEVNET
16971699
) {
16981700
throw new Error(
1699-
'Funds + payload bridging is only supported on Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia, and Solana Devnet for now'
1701+
'Funds + payload bridging is only supported on Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia, BNB Testnet, and Solana Devnet for now'
17001702
);
17011703
}
17021704

packages/core/src/lib/push-chain/push-chain.spec.ts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
parseAbi,
1616
PrivateKeyAccount,
1717
} from 'viem';
18-
import { sepolia, arbitrumSepolia, baseSepolia } from 'viem/chains';
18+
import { sepolia, arbitrumSepolia, baseSepolia, bscTestnet } from 'viem/chains';
1919
import { keccak256, toBytes } from 'viem';
2020
import { MulticallCall } from '../orchestrator/orchestrator.types';
2121
import { CHAIN_INFO } from '../constants/chain';
@@ -36,6 +36,9 @@ const ARBITRUM_SEPOLIA_RPC =
3636
const BASE_SEPOLIA_RPC =
3737
process.env['BASE_SEPOLIA_RPC'] ||
3838
CHAIN_INFO[CHAIN.BASE_SEPOLIA].defaultRPC[0];
39+
const BNB_TESTNET_RPC =
40+
process.env['BNB_TESTNET_RPC'] ||
41+
CHAIN_INFO[CHAIN.BNB_TESTNET].defaultRPC[0];
3942
const SOLANA_RPC =
4043
process.env['SOLANA_RPC_URL'] ||
4144
CHAIN_INFO[CHAIN.SOLANA_DEVNET].defaultRPC[0];
@@ -44,7 +47,7 @@ const SOLANA_RPC =
4447
interface EVMChainTestConfig {
4548
name: string;
4649
chain: CHAIN;
47-
viemChain: typeof sepolia | typeof arbitrumSepolia | typeof baseSepolia;
50+
viemChain: typeof sepolia | typeof arbitrumSepolia | typeof baseSepolia | typeof bscTestnet;
4851
rpcUrl: string;
4952
gatewayAddress: string;
5053
tokens: {
@@ -107,6 +110,22 @@ const EVM_CHAIN_CONFIGS: EVMChainTestConfig[] = [
107110
},
108111
},
109112
},
113+
{
114+
name: 'BNB Testnet',
115+
chain: CHAIN.BNB_TESTNET,
116+
viemChain: bscTestnet,
117+
rpcUrl: BNB_TESTNET_RPC,
118+
gatewayAddress: '0x44aFFC61983F4348DdddB886349eb992C061EaC0',
119+
tokens: {
120+
usdt: {
121+
address: '0xBC14F348BC9667be46b35Edc9B68653d86013DC5',
122+
decimals: 6,
123+
},
124+
eth: {
125+
decimals: 18,
126+
},
127+
},
128+
},
110129
];
111130

112131
// Reusable test helper functions
@@ -273,6 +292,9 @@ async function testSendTxWithFundsUSDT(
273292
expect(resUSDT.hash.startsWith('0x')).toBe(true);
274293
await resUSDT.wait();
275294

295+
// Wait for Push Chain state to finalize
296+
await new Promise(resolve => setTimeout(resolve, 3000));
297+
276298
const afterCount = (await pushPublicClient.readContract({
277299
abi: COUNTER_ABI,
278300
address: COUNTER_ADDRESS,
@@ -794,6 +816,27 @@ describe('PushChain', () => {
794816
}, 300000);
795817
});
796818

819+
describe('Multicall - BNB Testnet', () => {
820+
const config = EVM_CHAIN_CONFIGS[3]; // BNB Testnet
821+
const PRIVATE_KEY = process.env['EVM_PRIVATE_KEY'] as
822+
| `0x${string}`
823+
| undefined;
824+
let client: PushChain;
825+
826+
beforeAll(async () => {
827+
if (!PRIVATE_KEY) {
828+
throw new Error('EVM_PRIVATE_KEY environment variable is not set');
829+
}
830+
831+
const result = await setupEVMChainClient(config, PRIVATE_KEY);
832+
client = result.client;
833+
});
834+
835+
it('integration: should build and send multicall payload', async () => {
836+
await testMulticall(client, config);
837+
}, 300000);
838+
});
839+
797840
describe('signTypedData', () => {
798841
it('should signTypedData - EIP-712 format', async () => {
799842
const domain = {
@@ -1606,6 +1649,37 @@ describe('PushChain', () => {
16061649
}, 500000);
16071650
});
16081651

1652+
describe('Universal.sendTransaction (FUNDS_TX via UniversalGatewayV0) - BNB Testnet', () => {
1653+
const config = EVM_CHAIN_CONFIGS[3]; // BNB Testnet
1654+
const PRIVATE_KEY = process.env['EVM_PRIVATE_KEY'] as
1655+
| `0x${string}`
1656+
| undefined;
1657+
let account: PrivateKeyAccount;
1658+
let client: PushChain;
1659+
1660+
beforeAll(async () => {
1661+
if (!PRIVATE_KEY) {
1662+
throw new Error('EVM_PRIVATE_KEY environment variable is not set');
1663+
}
1664+
1665+
const result = await setupEVMChainClient(config, PRIVATE_KEY);
1666+
account = result.account;
1667+
client = result.client;
1668+
});
1669+
1670+
it('integration: sendFunds USDT via UniversalGatewayV0', async () => {
1671+
await testSendFundsUSDT(client, account, config);
1672+
}, 300000);
1673+
1674+
it('integration: sendFunds BNB via UniversalGatewayV0', async () => {
1675+
await testSendFundsETH(client, config);
1676+
}, 300000);
1677+
1678+
it('integration: sendTxWithFunds USDT via UniversalGatewayV0', async () => {
1679+
await testSendTxWithFundsUSDT(client, account, config);
1680+
}, 500000);
1681+
});
1682+
16091683
// Test for unsupported origin chains (only needs to run once, not per chain)
16101684
describe('Universal.sendTransaction - Unsupported chains', () => {
16111685
it('should throw on unsupported origin chains', async () => {

packages/core/src/lib/universal/account/account.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ function getPushNetworkFromChain(chain: CHAIN): PUSH_NETWORK {
153153
CHAIN.ETHEREUM_SEPOLIA,
154154
CHAIN.ARBITRUM_SEPOLIA,
155155
CHAIN.BASE_SEPOLIA,
156+
CHAIN.BNB_TESTNET,
156157
CHAIN.SOLANA_TESTNET,
157158
CHAIN.SOLANA_DEVNET,
158159
CHAIN.PUSH_TESTNET_DONUT,

0 commit comments

Comments
 (0)