Skip to content

Commit 74e9bc8

Browse files
Add crosschain meeclient tests - not tested
1 parent af54ed2 commit 74e9bc8

4 files changed

Lines changed: 1768 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
/**
2+
* 03-crosschain-native-transfer-meeclient.ts
3+
*
4+
* Cross-chain native ETH transfer using MEE Client.
5+
* Sends ETH on Base AND Optimism in a single Supertransaction.
6+
*
7+
* NEW FEATURE:
8+
* - Demonstrates MEE Client's cross-chain capabilities
9+
* - Multiple instructions for different chains
10+
* - Single quote/signature for entire cross-chain bundle
11+
* - Automatic gas sponsorship across all chains
12+
*/
13+
14+
import { ethers } from "ethers";
15+
import * as fs from "fs";
16+
import * as path from "path";
17+
import { createPublicClient, http, parseEther, type Hex } from "viem";
18+
import { privateKeyToAccount } from "viem/accounts";
19+
import { base, optimism } from "viem/chains";
20+
import { createMeeClient, toMultichainNexusAccount, getMEEVersion, MEEVersion } from "@biconomy/abstractjs";
21+
22+
async function crosschainNativeTransferMeeClient() {
23+
console.log("🧪 Cross-Chain Native ETH Transfer - MEE Client\n");
24+
console.log("=".repeat(80));
25+
console.log("📋 Chains: Base Mainnet (8453) + Optimism Mainnet (10)");
26+
console.log("📋 API: createMeeClient() + toMultichainNexusAccount()");
27+
console.log("📋 Sponsorship: Biconomy hosted (all chains)\n");
28+
console.log("=".repeat(80));
29+
30+
// ============================================================================
31+
// WALLET INFO
32+
// ============================================================================
33+
34+
const walletAddress = "0x846A51Ac27990D255Eaa0a732A9411F21cAF91b6" as `0x${string}`;
35+
const ownerAddress = "0xeDC117090236293afEBb179260e8B9dd5bffe4dC" as `0x${string}`;
36+
37+
console.log("\n📋 Migrated Wallet:");
38+
console.log("-".repeat(80));
39+
console.log(` Address: ${walletAddress}`);
40+
console.log(` Owner: ${ownerAddress}`);
41+
console.log(` Type: Passport → Nexus (migrated)\n`);
42+
console.log("=".repeat(80));
43+
44+
// ============================================================================
45+
// SETUP SIGNER
46+
// ============================================================================
47+
48+
console.log("\n⚙️ Setting up signer...");
49+
50+
const privateKeyRaw = process.env.MIGRATION_TEST_OWNER_PK;
51+
52+
if (!privateKeyRaw) {
53+
throw new Error("MIGRATION_TEST_OWNER_PK not found in .env");
54+
}
55+
56+
const privateKey = privateKeyRaw.startsWith("0x") ? privateKeyRaw : `0x${privateKeyRaw}`;
57+
const viemAccount = privateKeyToAccount(privateKey as Hex);
58+
59+
console.log(` Owner EOA: ${viemAccount.address}`);
60+
61+
if (viemAccount.address.toLowerCase() !== ownerAddress.toLowerCase()) {
62+
throw new Error(
63+
`Signer mismatch! Expected ${ownerAddress}, got ${viemAccount.address}`
64+
);
65+
}
66+
67+
console.log(" ✅ Signer configured\n");
68+
console.log("=".repeat(80));
69+
70+
// ============================================================================
71+
// SETUP PUBLIC CLIENTS FOR BOTH CHAINS
72+
// ============================================================================
73+
74+
console.log("\n🔗 Setting up public clients...");
75+
76+
const baseRpcUrl = process.env.BASE_MAINNET_ENDPOINT || "https://mainnet.base.org";
77+
const optimismRpcUrl = process.env.OPTIMISM_MAINNET_ENDPOINT || "https://mainnet.optimism.io";
78+
79+
const basePublicClient = createPublicClient({
80+
chain: base,
81+
transport: http(baseRpcUrl),
82+
});
83+
84+
const optimismPublicClient = createPublicClient({
85+
chain: optimism,
86+
transport: http(optimismRpcUrl),
87+
});
88+
89+
console.log(" ✅ Base client configured");
90+
console.log(" ✅ Optimism client configured\n");
91+
console.log("=".repeat(80));
92+
93+
// ============================================================================
94+
// CHECK WALLET BALANCES ON BOTH CHAINS
95+
// ============================================================================
96+
97+
console.log("\n💰 Checking Wallet Balances...");
98+
99+
const baseBalance = await basePublicClient.getBalance({
100+
address: walletAddress,
101+
});
102+
103+
const optimismBalance = await optimismPublicClient.getBalance({
104+
address: walletAddress,
105+
});
106+
107+
console.log(` Base: ${ethers.utils.formatEther(baseBalance.toString())} ETH`);
108+
console.log(` Optimism: ${ethers.utils.formatEther(optimismBalance.toString())} ETH\n`);
109+
110+
if (baseBalance < parseEther("0.00002")) {
111+
throw new Error("Insufficient balance on Base");
112+
}
113+
114+
if (optimismBalance < parseEther("0.00002")) {
115+
throw new Error("Insufficient balance on Optimism");
116+
}
117+
118+
console.log("=".repeat(80));
119+
120+
// ============================================================================
121+
// CREATE MEE CLIENT WITH MULTICHAIN SUPPORT
122+
// ============================================================================
123+
124+
console.log("\n🚀 Creating Multichain MEE Client...");
125+
126+
const apiKey = process.env.SUPERTX_API_KEY;
127+
if (!apiKey) {
128+
throw new Error("SUPERTX_API_KEY not found in .env");
129+
}
130+
131+
console.log(` API Key: ${apiKey.substring(0, 10)}...${apiKey.substring(apiKey.length - 4)}`);
132+
console.log(` Chains: Base (${base.id}) + Optimism (${optimism.id})`);
133+
console.log(` Account: ${walletAddress}\n`);
134+
135+
try {
136+
// Create Multichain Nexus account with both chains
137+
const nexusAccount = await toMultichainNexusAccount({
138+
signer: viemAccount,
139+
chainConfigurations: [
140+
{
141+
chain: base,
142+
transport: http(baseRpcUrl),
143+
version: getMEEVersion(MEEVersion.V2_1_0),
144+
},
145+
{
146+
chain: optimism,
147+
transport: http(optimismRpcUrl),
148+
version: getMEEVersion(MEEVersion.V2_1_0),
149+
},
150+
],
151+
accountAddress: walletAddress,
152+
});
153+
154+
console.log(" ✅ Multichain Nexus Account created (Base + Optimism)");
155+
156+
// Create MEE Client
157+
const meeClient = await createMeeClient({
158+
account: nexusAccount,
159+
apiKey: apiKey,
160+
});
161+
162+
console.log(" ✅ MEE Client created\n");
163+
console.log("=".repeat(80));
164+
165+
// ========================================================================
166+
// SEND CROSS-CHAIN NATIVE ETH TRANSFER
167+
// ========================================================================
168+
169+
console.log("\n🚀 Sending Cross-Chain Native ETH Transfer...");
170+
171+
const testAmount = parseEther("0.00001");
172+
const recipientAddress = "0xF04fF8e30816858dc4ec5436d3e148D1B9D84b6B" as `0x${string}`; // Native Nexus wallet
173+
174+
console.log(` From: ${walletAddress}`);
175+
console.log(` To: ${recipientAddress}`);
176+
console.log(` Amount: ${ethers.utils.formatEther(testAmount.toString())} ETH (per chain)`);
177+
console.log(` Chains: Base + Optimism`);
178+
console.log(` Sponsored: YES (Biconomy)\n`);
179+
180+
console.log(" 📋 Building cross-chain quote with gas sponsorship...");
181+
182+
const quote = await meeClient.getQuote({
183+
sponsorship: true,
184+
instructions: [
185+
// Instruction 1: Base Mainnet
186+
{
187+
calls: [
188+
{
189+
to: recipientAddress,
190+
value: testAmount,
191+
},
192+
],
193+
chainId: base.id,
194+
},
195+
// Instruction 2: Optimism Mainnet
196+
{
197+
calls: [
198+
{
199+
to: recipientAddress,
200+
value: testAmount,
201+
},
202+
],
203+
chainId: optimism.id,
204+
},
205+
],
206+
});
207+
208+
console.log(" ✅ Cross-chain quote received!\n");
209+
210+
console.log(" 🖊️ Signing quote...");
211+
const signedQuote = await meeClient.signQuote({ quote });
212+
console.log(" ✅ Quote signed!\n");
213+
214+
console.log(" 📤 Executing signed quote (cross-chain)...");
215+
const result = await meeClient.executeSignedQuote({ signedQuote });
216+
const hash = result.hash;
217+
218+
console.log(` ✅ Cross-chain transaction submitted!`);
219+
console.log(` Supertransaction Hash: ${hash}\n`);
220+
221+
console.log(" ⏳ Waiting for confirmation on BOTH chains...\n");
222+
223+
const receipt = await meeClient.waitForSupertransactionReceipt({ hash: hash as `0x${string}` });
224+
225+
const isSuccess = receipt.transactionStatus === "MINED_SUCCESS" ||
226+
(receipt.userOps && receipt.userOps.every((op: any) =>
227+
op.executionStatus === "MINED_SUCCESS"
228+
));
229+
230+
console.log(` Status: ${isSuccess ? "✅ SUCCESS" : "❌ FAILED"}`);
231+
console.log(` Transaction Status: ${receipt.transactionStatus || "N/A"}\n`);
232+
233+
// Show UserOp details (one per chain)
234+
if (receipt.userOps && receipt.userOps.length > 0) {
235+
console.log(` 📊 UserOps executed: ${receipt.userOps.length} (across ${receipt.userOps.length} chains)`);
236+
receipt.userOps.forEach((op: any, idx: number) => {
237+
const chainName = idx === 0 ? "Base" : "Optimism";
238+
console.log(` UserOp #${idx + 1} (${chainName}):`);
239+
console.log(` Execution Status: ${op.executionStatus || "N/A"}`);
240+
console.log(` TX Hash: ${op.executionData || "N/A"}`);
241+
});
242+
console.log();
243+
}
244+
245+
// Show explorer links (one per chain)
246+
if (receipt.explorerLinks && receipt.explorerLinks.length > 0) {
247+
console.log(` 🔗 Explorer Links (${receipt.explorerLinks.length} chains):`);
248+
receipt.explorerLinks.forEach((link: string, idx: number) => {
249+
const chainName = link.includes("basescan") ? "Base" : "Optimism";
250+
console.log(` [${chainName}] ${link}`);
251+
});
252+
console.log();
253+
}
254+
255+
console.log("=".repeat(80));
256+
257+
// ========================================================================
258+
// VERIFY BALANCES ON BOTH CHAINS
259+
// ========================================================================
260+
261+
console.log("\n🔍 Verifying Balances...");
262+
263+
const newBaseBalance = await basePublicClient.getBalance({
264+
address: walletAddress,
265+
});
266+
267+
const newOptimismBalance = await optimismPublicClient.getBalance({
268+
address: walletAddress,
269+
});
270+
271+
console.log(` Base Before: ${ethers.utils.formatEther(baseBalance.toString())} ETH`);
272+
console.log(` Base After: ${ethers.utils.formatEther(newBaseBalance.toString())} ETH`);
273+
console.log(` Optimism Before: ${ethers.utils.formatEther(optimismBalance.toString())} ETH`);
274+
console.log(` Optimism After: ${ethers.utils.formatEther(newOptimismBalance.toString())} ETH\n`);
275+
276+
console.log("=".repeat(80));
277+
278+
// ========================================================================
279+
// SAVE RESULT
280+
// ========================================================================
281+
282+
const resultData = {
283+
timestamp: new Date().toISOString(),
284+
type: "cross-chain-native-transfer",
285+
wallet: walletAddress,
286+
owner: ownerAddress,
287+
supertxHash: hash,
288+
success: isSuccess,
289+
transactionStatus: receipt.transactionStatus,
290+
userOpsCount: receipt.userOps?.length || 0,
291+
chains: [
292+
{
293+
name: "base",
294+
chainId: base.id,
295+
blockchainTxHash: receipt.receipts?.[0]?.transactionHash || null,
296+
balanceBefore: baseBalance.toString(),
297+
balanceAfter: newBaseBalance.toString(),
298+
},
299+
{
300+
name: "optimism",
301+
chainId: optimism.id,
302+
blockchainTxHash: receipt.receipts?.[1]?.transactionHash || null,
303+
balanceBefore: optimismBalance.toString(),
304+
balanceAfter: newOptimismBalance.toString(),
305+
},
306+
],
307+
explorerLinks: receipt.explorerLinks || [],
308+
sponsored: true,
309+
testAmount: testAmount.toString(),
310+
recipient: recipientAddress,
311+
};
312+
313+
const resultPath = path.join(__dirname, "03-result.json");
314+
fs.writeFileSync(resultPath, JSON.stringify(resultData, null, 2));
315+
316+
console.log(`\n📄 Result saved to: ${resultPath}\n`);
317+
318+
console.log("=".repeat(80));
319+
if (isSuccess) {
320+
console.log("\n🎉 CROSS-CHAIN NATIVE TRANSFER: SUCCESS!\n");
321+
console.log("✅ MEE Client cross-chain works perfectly!");
322+
console.log("✅ Gas sponsorship on BOTH chains");
323+
console.log("✅ UserOps confirmed on Base AND Optimism");
324+
console.log(`✅ Supertransaction: ${hash}\n`);
325+
} else {
326+
console.log("\n⚠️ CROSS-CHAIN NATIVE TRANSFER: NEEDS INVESTIGATION\n");
327+
}
328+
console.log("=".repeat(80));
329+
330+
} catch (error: any) {
331+
console.log("\n❌ Test Failed!");
332+
console.log(` Error: ${error.message}\n`);
333+
console.log("=".repeat(80));
334+
throw error;
335+
}
336+
}
337+
338+
crosschainNativeTransferMeeClient()
339+
.then(() => process.exit(0))
340+
.catch((error) => {
341+
console.error("\n❌ Test Failed!");
342+
console.error(` Error: ${error.message}\n`);
343+
process.exit(1);
344+
});
345+

0 commit comments

Comments
 (0)