@megaflow-labs/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1375 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ERC20_ABI: () => ERC20_ABI,
24
+ ERC721_ABI: () => ERC721_ABI,
25
+ KYBERSWAP_API_BASE: () => KYBERSWAP_API_BASE,
26
+ MEGAETH_BASE_FEE: () => MEGAETH_BASE_FEE,
27
+ MEGAETH_GAS_LIMITS: () => MEGAETH_GAS_LIMITS,
28
+ MEGAETH_L1_BRIDGE: () => MEGAETH_L1_BRIDGE,
29
+ MEGAETH_TOKENS: () => MEGAETH_TOKENS,
30
+ MEGA_ROUTER_ABI: () => MEGA_ROUTER_ABI,
31
+ MegaFlowBuilder: () => MegaFlowBuilder,
32
+ MegaFlowClient: () => MegaFlowClient,
33
+ MegaFlowError: () => MegaFlowError,
34
+ UNISWAP_V2_ROUTER_ABI: () => UNISWAP_V2_ROUTER_ABI,
35
+ WETH_ABI: () => WETH_ABI,
36
+ applyMegaethGasBuffer: () => applyMegaethGasBuffer,
37
+ buildKyberSwap: () => buildKyberSwap,
38
+ buildSafeApproveCalls: () => buildSafeApproveCalls,
39
+ calculateTotalValue: () => calculateTotalValue,
40
+ chainById: () => chainById,
41
+ chunkCalls: () => chunkCalls,
42
+ createMegaFlow: () => createMegaFlow,
43
+ createMegaFlowClient: () => createMegaFlowClient,
44
+ createMegaFlowClientMainnet: () => createMegaFlowClientMainnet,
45
+ createMegaFlowClientTestnet: () => createMegaFlowClientTestnet,
46
+ createMegaFlowMainnet: () => createMegaFlowMainnet,
47
+ createMegaFlowTestnet: () => createMegaFlowTestnet,
48
+ deadlineInMinutes: () => deadlineInMinutes,
49
+ getKyberQuote: () => getKyberQuote,
50
+ isUserRejection: () => isUserRejection,
51
+ isValidAddress: () => isValidAddress,
52
+ isValidHex: () => isValidHex,
53
+ megaethChains: () => megaethChains,
54
+ megaethMainnet: () => megaethMainnet,
55
+ megaethTestnet: () => megaethTestnet,
56
+ parseBatchExecutedEvent: () => parseBatchExecutedEvent,
57
+ parseCallExecutedEvents: () => parseCallExecutedEvents,
58
+ parseCallResults: () => parseCallResults
59
+ });
60
+ module.exports = __toCommonJS(index_exports);
61
+
62
+ // src/chains.ts
63
+ var megaethMainnet = {
64
+ id: 4326,
65
+ name: "MegaETH Mainnet",
66
+ nativeCurrency: {
67
+ decimals: 18,
68
+ name: "Ether",
69
+ symbol: "ETH"
70
+ },
71
+ rpcUrls: {
72
+ default: {
73
+ http: ["https://mainnet.megaeth.com/rpc"],
74
+ webSocket: ["wss://mainnet.megaeth.com/ws"]
75
+ }
76
+ },
77
+ blockExplorers: {
78
+ default: {
79
+ name: "MegaETH Explorer",
80
+ url: "https://mega.etherscan.io"
81
+ }
82
+ }
83
+ };
84
+ var megaethTestnet = {
85
+ id: 6343,
86
+ name: "MegaETH Testnet",
87
+ nativeCurrency: {
88
+ decimals: 18,
89
+ name: "Ether",
90
+ symbol: "ETH"
91
+ },
92
+ rpcUrls: {
93
+ default: {
94
+ http: ["https://carrot.megaeth.com/rpc"],
95
+ webSocket: ["wss://carrot.megaeth.com/ws"]
96
+ }
97
+ },
98
+ blockExplorers: {
99
+ default: {
100
+ name: "MegaETH Testnet Explorer",
101
+ url: "https://megaeth-testnet-v2.blockscout.com"
102
+ }
103
+ },
104
+ testnet: true
105
+ };
106
+ var megaethChains = [megaethMainnet, megaethTestnet];
107
+ var chainById = {
108
+ [megaethMainnet.id]: megaethMainnet,
109
+ [megaethTestnet.id]: megaethTestnet
110
+ };
111
+
112
+ // src/constants.ts
113
+ var import_viem = require("viem");
114
+ var MEGA_ROUTER_ABI = (0, import_viem.parseAbi)([
115
+ // Core batch execution
116
+ "function executeBatch((address target, uint256 value, bytes callData)[] calls) payable returns ((bool success, bytes returnData)[] results)",
117
+ "function executeBatchWithOutput((address target, uint256 value, bytes callData)[] calls, uint256 outputCallIndex, uint256 outputOffset, uint256 outputLength) payable returns ((bool success, bytes returnData)[] results, bytes extractedOutput)",
118
+ // View helpers
119
+ "function calculateRequiredETH((address target, uint256 value, bytes callData)[] calls) view returns (uint256)",
120
+ "function flatFee() view returns (uint256)",
121
+ "function feeCollector() view returns (address)",
122
+ // Events
123
+ "event BatchExecuted(address indexed sender, uint256 callCount, uint256 totalValue)",
124
+ "event CallExecuted(uint256 indexed callIndex, address indexed target, uint256 value, bool success)",
125
+ // Errors (for decoding reverts)
126
+ "error ReentrancyGuard()",
127
+ "error InsufficientETH(uint256 required, uint256 provided)",
128
+ "error InvalidTarget(uint256 callIndex)",
129
+ "error EmptyCalldata(uint256 callIndex)",
130
+ "error CallFailed(uint256 callIndex, bytes returnData)",
131
+ "error FeeTransferFailed()",
132
+ "error SweepFailed()"
133
+ ]);
134
+ var ERC20_ABI = (0, import_viem.parseAbi)([
135
+ "function name() view returns (string)",
136
+ "function symbol() view returns (string)",
137
+ "function decimals() view returns (uint8)",
138
+ "function totalSupply() view returns (uint256)",
139
+ "function balanceOf(address account) view returns (uint256)",
140
+ "function allowance(address owner, address spender) view returns (uint256)",
141
+ "function transfer(address to, uint256 amount) returns (bool)",
142
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
143
+ "function approve(address spender, uint256 amount) returns (bool)",
144
+ "event Transfer(address indexed from, address indexed to, uint256 value)",
145
+ "event Approval(address indexed owner, address indexed spender, uint256 value)"
146
+ ]);
147
+ var ERC721_ABI = (0, import_viem.parseAbi)([
148
+ "function name() view returns (string)",
149
+ "function symbol() view returns (string)",
150
+ "function tokenURI(uint256 tokenId) view returns (string)",
151
+ "function ownerOf(uint256 tokenId) view returns (address)",
152
+ "function balanceOf(address owner) view returns (uint256)",
153
+ "function isApprovedForAll(address owner, address operator) view returns (bool)",
154
+ "function getApproved(uint256 tokenId) view returns (address)",
155
+ "function transferFrom(address from, address to, uint256 tokenId)",
156
+ "function safeTransferFrom(address from, address to, uint256 tokenId)",
157
+ "function approve(address to, uint256 tokenId)",
158
+ "function setApprovalForAll(address operator, bool approved)"
159
+ ]);
160
+ var UNISWAP_V2_ROUTER_ABI = (0, import_viem.parseAbi)([
161
+ "function factory() pure returns (address)",
162
+ "function WETH() pure returns (address)",
163
+ "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] path, address to, uint deadline) returns (uint[] amounts)",
164
+ "function swapTokensForExactTokens(uint amountOut, uint amountInMax, address[] path, address to, uint deadline) returns (uint[] amounts)",
165
+ "function swapExactETHForTokens(uint amountOutMin, address[] path, address to, uint deadline) payable returns (uint[] amounts)",
166
+ "function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] path, address to, uint deadline) returns (uint[] amounts)",
167
+ "function swapETHForExactTokens(uint amountOut, address[] path, address to, uint deadline) payable returns (uint[] amounts)",
168
+ "function getAmountsOut(uint amountIn, address[] path) view returns (uint[] amounts)",
169
+ "function getAmountsIn(uint amountOut, address[] path) view returns (uint[] amounts)",
170
+ "function addLiquidity(address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline) returns (uint amountA, uint amountB, uint liquidity)",
171
+ "function removeLiquidity(address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address to, uint deadline) returns (uint amountA, uint amountB)"
172
+ ]);
173
+ var WETH_ABI = (0, import_viem.parseAbi)([
174
+ "function name() view returns (string)",
175
+ "function symbol() view returns (string)",
176
+ "function decimals() view returns (uint8)",
177
+ "function totalSupply() view returns (uint256)",
178
+ "function balanceOf(address account) view returns (uint256)",
179
+ "function allowance(address owner, address spender) view returns (uint256)",
180
+ "function transfer(address to, uint256 amount) returns (bool)",
181
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
182
+ "function approve(address spender, uint256 amount) returns (bool)",
183
+ "function deposit() payable",
184
+ "function withdraw(uint256 wad)",
185
+ "event Transfer(address indexed from, address indexed to, uint256 value)",
186
+ "event Approval(address indexed owner, address indexed spender, uint256 value)"
187
+ ]);
188
+ var MEGAETH_GAS_LIMITS = {
189
+ /** Simple ETH transfer — 60k on MegaETH, not 21k */
190
+ TRANSFER: 60000n,
191
+ /** ERC20 transfer */
192
+ ERC20_TRANSFER: 100000n,
193
+ /** ERC20 approve */
194
+ ERC20_APPROVE: 80000n,
195
+ /** DEX swap */
196
+ SWAP: 350000n
197
+ };
198
+ var MEGAETH_BASE_FEE = 1000000n;
199
+ var MAINNET_ROUTER_ADDRESS = "0x9c1528e7688ee7122781dc8ed05686383b763ccb";
200
+ var MEGAETH_TOKENS = {
201
+ /** Wrapped ETH */
202
+ WETH: "0x4200000000000000000000000000000000000006",
203
+ /** MEGA governance token */
204
+ MEGA: "0x28B7E77f82B25B95953825F1E3eA0E36c1c29861",
205
+ /** USDM stablecoin */
206
+ USDM: "0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7"
207
+ };
208
+ var MEGAETH_L1_BRIDGE = "0x0CA3A2FBC3D770b578223FBB6b062fa875a2eE75";
209
+ var KYBERSWAP_API_BASE = "https://aggregator-api.kyberswap.com/megaeth/api/v1";
210
+
211
+ // src/utils.ts
212
+ var import_viem2 = require("viem");
213
+ function decodeRevertError(returnData) {
214
+ if (!returnData || returnData === "0x" || returnData.length < 10) {
215
+ return "Execution reverted with no data";
216
+ }
217
+ const selector = returnData.slice(0, 10).toLowerCase();
218
+ if (selector === "0x08c379a0") {
219
+ try {
220
+ const [message] = (0, import_viem2.decodeAbiParameters)(
221
+ [{ type: "string" }],
222
+ `0x${returnData.slice(10)}`
223
+ );
224
+ return `Reverted: ${message}`;
225
+ } catch {
226
+ return `Reverted (Error selector found but decode failed)`;
227
+ }
228
+ }
229
+ if (selector === "0x4e487b71") {
230
+ try {
231
+ const [code] = (0, import_viem2.decodeAbiParameters)(
232
+ [{ type: "uint256" }],
233
+ `0x${returnData.slice(10)}`
234
+ );
235
+ return `Panic: ${decodePanicCode(Number(code))}`;
236
+ } catch {
237
+ return "Panic: unknown code";
238
+ }
239
+ }
240
+ const customErrors = {
241
+ "0x3e3f8f73": "ReentrancyGuard: re-entrant call",
242
+ "0xfe9ba5cd": "InsufficientETH: not enough ETH sent",
243
+ "0xd4a5e2bf": "InsufficientFee: flat fee not covered",
244
+ "0x1425f8f8": "InvalidTarget: call target is address(0)",
245
+ "0x4a3f0f2d": "EmptyCalldata: callData must not be empty",
246
+ "0x85d9d0b8": "CallFailed: one of the batch calls reverted",
247
+ "0xb5e9e6d2": "FeeTransferFailed: could not send fee to collector",
248
+ "0x750b219c": "SweepFailed: could not return leftover ETH"
249
+ };
250
+ if (customErrors[selector]) {
251
+ if (selector === "0x85d9d0b8") {
252
+ try {
253
+ const [callIndex, innerData] = (0, import_viem2.decodeAbiParameters)(
254
+ [{ type: "uint256" }, { type: "bytes" }],
255
+ `0x${returnData.slice(10)}`
256
+ );
257
+ const inner = decodeRevertError(innerData);
258
+ return `Call #${callIndex} failed \u2192 ${inner}`;
259
+ } catch {
260
+ return customErrors[selector];
261
+ }
262
+ }
263
+ return customErrors[selector];
264
+ }
265
+ return `Reverted with unknown selector ${selector}`;
266
+ }
267
+ function decodePanicCode(code) {
268
+ const codes = {
269
+ 0: "generic panic",
270
+ 1: "assert failed",
271
+ 17: "arithmetic overflow/underflow",
272
+ 18: "division by zero",
273
+ 33: "invalid enum value",
274
+ 34: "storage access out of bounds",
275
+ 49: "pop on empty array",
276
+ 50: "array index out of bounds",
277
+ 65: "out of memory",
278
+ 81: "uninitialized function pointer"
279
+ };
280
+ return codes[code] ?? `code 0x${code.toString(16)}`;
281
+ }
282
+ function parseCallResults(receipt, callCount, rawReturnData) {
283
+ if (rawReturnData && rawReturnData !== "0x" && rawReturnData.length > 2) {
284
+ try {
285
+ const decoded = (0, import_viem2.decodeAbiParameters)(
286
+ [
287
+ {
288
+ type: "tuple[]",
289
+ components: [
290
+ { name: "success", type: "bool" },
291
+ { name: "returnData", type: "bytes" }
292
+ ]
293
+ }
294
+ ],
295
+ rawReturnData
296
+ );
297
+ const results = decoded[0];
298
+ return results.map((r) => ({
299
+ success: r.success,
300
+ returnData: r.returnData
301
+ }));
302
+ } catch {
303
+ }
304
+ }
305
+ const resultMap = /* @__PURE__ */ new Map();
306
+ for (const log of receipt.logs) {
307
+ try {
308
+ const decoded = (0, import_viem2.decodeEventLog)({
309
+ abi: MEGA_ROUTER_ABI,
310
+ data: log.data,
311
+ topics: log.topics,
312
+ strict: false
313
+ });
314
+ if (decoded.eventName === "CallExecuted") {
315
+ const { callIndex, success } = decoded.args;
316
+ resultMap.set(Number(callIndex), {
317
+ success,
318
+ returnData: "0x"
319
+ // events don't carry returnData
320
+ });
321
+ }
322
+ } catch {
323
+ }
324
+ }
325
+ if (resultMap.size > 0) {
326
+ return Array.from(
327
+ { length: callCount },
328
+ (_, i) => resultMap.get(i) ?? { success: true, returnData: "0x" }
329
+ );
330
+ }
331
+ return Array.from({ length: callCount }, () => ({
332
+ success: true,
333
+ returnData: "0x"
334
+ }));
335
+ }
336
+ function parseBatchExecutedEvent(receipt) {
337
+ for (const log of receipt.logs) {
338
+ try {
339
+ const decoded = (0, import_viem2.decodeEventLog)({
340
+ abi: MEGA_ROUTER_ABI,
341
+ data: log.data,
342
+ topics: log.topics,
343
+ strict: false
344
+ });
345
+ if (decoded.eventName === "BatchExecuted") {
346
+ const args = decoded.args;
347
+ return {
348
+ sender: args.sender,
349
+ callCount: args.callCount,
350
+ totalValue: args.totalValue,
351
+ blockNumber: receipt.blockNumber,
352
+ transactionHash: receipt.transactionHash
353
+ };
354
+ }
355
+ } catch {
356
+ }
357
+ }
358
+ return null;
359
+ }
360
+ function parseCallExecutedEvents(receipt) {
361
+ const events = [];
362
+ for (const log of receipt.logs) {
363
+ try {
364
+ const decoded = (0, import_viem2.decodeEventLog)({
365
+ abi: MEGA_ROUTER_ABI,
366
+ data: log.data,
367
+ topics: log.topics,
368
+ strict: false
369
+ });
370
+ if (decoded.eventName === "CallExecuted") {
371
+ const args = decoded.args;
372
+ events.push({
373
+ callIndex: args.callIndex,
374
+ target: args.target,
375
+ value: args.value,
376
+ success: args.success,
377
+ blockNumber: receipt.blockNumber,
378
+ transactionHash: receipt.transactionHash
379
+ });
380
+ }
381
+ } catch {
382
+ }
383
+ }
384
+ return events.sort((a, b) => Number(a.callIndex - b.callIndex));
385
+ }
386
+ function applyMegaethGasBuffer(estimate, bufferPct = 10) {
387
+ return estimate * BigInt(100 + bufferPct) / 100n;
388
+ }
389
+ function deadlineInMinutes(minutes) {
390
+ return BigInt(Math.floor(Date.now() / 1e3) + minutes * 60);
391
+ }
392
+ function buildSafeApproveCalls(params) {
393
+ const { token, spender, amount, currentAllowance } = params;
394
+ const approveCalldata = (amt) => {
395
+ const selector = "0x095ea7b3";
396
+ const paddedSpender = spender.slice(2).padStart(64, "0");
397
+ const paddedAmount = amt.toString(16).padStart(64, "0");
398
+ return `${selector}${paddedSpender}${paddedAmount}`;
399
+ };
400
+ const calls = [];
401
+ if (currentAllowance > 0n && currentAllowance !== amount) {
402
+ calls.push({ target: token, value: 0n, callData: approveCalldata(0n) });
403
+ }
404
+ calls.push({ target: token, value: 0n, callData: approveCalldata(amount) });
405
+ return calls;
406
+ }
407
+ async function getKyberQuote(params) {
408
+ const ETH_PLACEHOLDER = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
409
+ const url = new URL(`${KYBERSWAP_API_BASE}/routes`);
410
+ url.searchParams.set("tokenIn", params.tokenIn === "ETH" ? ETH_PLACEHOLDER : params.tokenIn);
411
+ url.searchParams.set("tokenOut", params.tokenOut === "ETH" ? ETH_PLACEHOLDER : params.tokenOut);
412
+ url.searchParams.set("amountIn", params.amountIn.toString());
413
+ url.searchParams.set("gasInclude", "true");
414
+ const res = await fetch(url.toString());
415
+ if (!res.ok) {
416
+ throw new Error(`KyberSwap API error ${res.status}: ${await res.text()}`);
417
+ }
418
+ const json = await res.json();
419
+ if (json.code !== 0) throw new Error(`KyberSwap error: ${json.message}`);
420
+ return json.data.routeSummary;
421
+ }
422
+ async function buildKyberSwap(params) {
423
+ const res = await fetch(`${KYBERSWAP_API_BASE}/route/build`, {
424
+ method: "POST",
425
+ headers: { "Content-Type": "application/json" },
426
+ body: JSON.stringify({
427
+ routeSummary: params.routeSummary,
428
+ sender: params.sender,
429
+ recipient: params.recipient,
430
+ slippageTolerance: params.slippageBps ?? 50
431
+ })
432
+ });
433
+ if (!res.ok) {
434
+ throw new Error(`KyberSwap build error ${res.status}: ${await res.text()}`);
435
+ }
436
+ const json = await res.json();
437
+ if (json.code !== 0) throw new Error(`KyberSwap build error: ${json.message}`);
438
+ return json.data;
439
+ }
440
+ function assertCallsNotEmpty(calls) {
441
+ if (calls.length === 0) {
442
+ throw new MegaFlowError(
443
+ "No calls in batch. Add at least one call before executing.",
444
+ "EMPTY_BATCH"
445
+ );
446
+ }
447
+ }
448
+ function assertWalletConnected(walletClient) {
449
+ if (!walletClient) {
450
+ throw new MegaFlowError(
451
+ "Wallet not connected. Call connect() or connectWithAccount() first.",
452
+ "WALLET_NOT_CONNECTED"
453
+ );
454
+ }
455
+ }
456
+ function assertAccount(walletClient) {
457
+ if (!walletClient.account) {
458
+ throw new MegaFlowError(
459
+ "Wallet has no account attached.",
460
+ "NO_ACCOUNT"
461
+ );
462
+ }
463
+ }
464
+ function isUserRejection(error) {
465
+ if (error instanceof Error) {
466
+ return error.message.includes("User rejected") || error.message.includes("user rejected") || error.message.includes("User denied") || error.message.includes("ACTION_REJECTED");
467
+ }
468
+ return false;
469
+ }
470
+ function isValidAddress(address) {
471
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
472
+ }
473
+ function isValidHex(hex) {
474
+ return /^0x[a-fA-F0-9]*$/.test(hex);
475
+ }
476
+ function chunkCalls(calls, maxPerBatch = 64) {
477
+ const chunks = [];
478
+ for (let i = 0; i < calls.length; i += maxPerBatch) {
479
+ chunks.push(calls.slice(i, i + maxPerBatch));
480
+ }
481
+ return chunks;
482
+ }
483
+ function calculateTotalValue(calls) {
484
+ return calls.reduce((sum, call) => sum + call.value, 0n);
485
+ }
486
+ var MegaFlowError = class extends Error {
487
+ constructor(message, code, cause) {
488
+ super(`[MegaFlow] ${message}`);
489
+ this.code = code;
490
+ this.cause = cause;
491
+ this.name = "MegaFlowError";
492
+ }
493
+ };
494
+
495
+ // src/builder.ts
496
+ var import_viem3 = require("viem");
497
+ var MegaFlowBuilder = class {
498
+ constructor(config = {}) {
499
+ this.calls = [];
500
+ this.operations = [];
501
+ this.routerAddress = config.routerAddress ?? MAINNET_ROUTER_ADDRESS;
502
+ this.chain = config.chain ?? megaethMainnet;
503
+ this.debug = config.debug ?? false;
504
+ const rpcUrl = config.rpcUrl ?? this.chain.rpcUrls.default.http[0];
505
+ this.publicClient = (0, import_viem3.createPublicClient)({
506
+ chain: this.chain,
507
+ transport: (0, import_viem3.http)(rpcUrl, {
508
+ timeout: 3e4
509
+ })
510
+ });
511
+ }
512
+ // ==========================================================================
513
+ // Connection
514
+ // ==========================================================================
515
+ /**
516
+ * Connect a pre-built viem WalletClient.
517
+ */
518
+ connect(walletClient) {
519
+ this.walletClient = walletClient;
520
+ this.log("Wallet connected", { account: walletClient.account?.address });
521
+ return this;
522
+ }
523
+ /**
524
+ * Connect using a viem Account object.
525
+ * Creates an internal WalletClient bound to MegaETH's chain/RPC.
526
+ */
527
+ connectWithAccount(account, rpcUrl) {
528
+ this.walletClient = (0, import_viem3.createWalletClient)({
529
+ account,
530
+ chain: this.chain,
531
+ transport: (0, import_viem3.http)(rpcUrl ?? this.chain.rpcUrls.default.http[0])
532
+ });
533
+ return this;
534
+ }
535
+ /**
536
+ * Disconnect the wallet (for cleanup).
537
+ */
538
+ disconnect() {
539
+ this.walletClient = void 0;
540
+ return this;
541
+ }
542
+ // ==========================================================================
543
+ // Generic Call Builders
544
+ // ==========================================================================
545
+ /**
546
+ * Add a raw call (pre-encoded calldata).
547
+ */
548
+ add(target, callData, value = 0n) {
549
+ const call = { target, value, callData };
550
+ this.calls.push(call);
551
+ this.recordOperation("custom", call);
552
+ return this;
553
+ }
554
+ /**
555
+ * Add a type-safe ABI-encoded call.
556
+ *
557
+ * @example
558
+ * builder.addCall({
559
+ * target: '0x...',
560
+ * abi: myAbi,
561
+ * functionName: 'mint',
562
+ * args: [recipient, amount],
563
+ * })
564
+ */
565
+ addCall(params) {
566
+ const callData = (0, import_viem3.encodeFunctionData)({
567
+ abi: params.abi,
568
+ functionName: params.functionName,
569
+ args: params.args ?? []
570
+ });
571
+ return this.add(params.target, callData, params.value ?? 0n);
572
+ }
573
+ /**
574
+ * Send native ETH to an address.
575
+ * Uses a minimal `receive()` call (empty calldata is not allowed by MegaRouter,
576
+ * so we send a 1-byte noop that any contract with a `receive()` will handle).
577
+ */
578
+ sendETH(to, value) {
579
+ const call = { target: to, value, callData: "0x00" };
580
+ this.calls.push(call);
581
+ this.recordOperation("custom", call, { type: "sendETH", to, value: value.toString() });
582
+ return this;
583
+ }
584
+ // ==========================================================================
585
+ // ERC20 Helpers
586
+ // ==========================================================================
587
+ /**
588
+ * ERC20 approve.
589
+ * Uses the simple single-approve pattern.
590
+ * For safe two-step approve, use `safeApprove()`.
591
+ */
592
+ approve(token, spender, amount) {
593
+ return this.addCall({
594
+ target: token,
595
+ abi: ERC20_ABI,
596
+ functionName: "approve",
597
+ args: [spender, amount]
598
+ });
599
+ }
600
+ /**
601
+ * Safe ERC20 approve — resets allowance to 0 first if non-zero,
602
+ * then sets the new amount. Prevents ERC20 approval race conditions
603
+ * (required by some tokens like USDT).
604
+ *
605
+ * @param currentAllowance Current on-chain allowance. Pass 0n if unknown and you want a single approve.
606
+ */
607
+ safeApprove(token, spender, amount, currentAllowance) {
608
+ const approvalCalls = buildSafeApproveCalls({
609
+ token,
610
+ spender,
611
+ amount,
612
+ currentAllowance
613
+ });
614
+ for (const call of approvalCalls) {
615
+ this.calls.push(call);
616
+ this.recordOperation("safeApprove", call, { token, spender, amount: amount.toString() });
617
+ }
618
+ return this;
619
+ }
620
+ /**
621
+ * ERC20 transfer.
622
+ */
623
+ transfer(token, to, amount) {
624
+ return this.addCall({
625
+ target: token,
626
+ abi: ERC20_ABI,
627
+ functionName: "transfer",
628
+ args: [to, amount]
629
+ });
630
+ }
631
+ /**
632
+ * ERC20 transferFrom.
633
+ */
634
+ transferFrom(token, from, to, amount) {
635
+ return this.addCall({
636
+ target: token,
637
+ abi: ERC20_ABI,
638
+ functionName: "transferFrom",
639
+ args: [from, to, amount]
640
+ });
641
+ }
642
+ /**
643
+ * Batch transfer same token to multiple recipients in one batch.
644
+ * @example .multiTransfer(USDC, [{ to: addr1, amount: 100n }, { to: addr2, amount: 200n }])
645
+ */
646
+ multiTransfer(token, transfers) {
647
+ for (const { to, amount } of transfers) {
648
+ this.transfer(token, to, amount);
649
+ }
650
+ return this;
651
+ }
652
+ // ==========================================================================
653
+ // ERC721 Helpers
654
+ // ==========================================================================
655
+ /**
656
+ * ERC721 safeTransferFrom.
657
+ */
658
+ transferNFT(nft, from, to, tokenId) {
659
+ return this.addCall({
660
+ target: nft,
661
+ abi: ERC721_ABI,
662
+ functionName: "safeTransferFrom",
663
+ args: [from, to, tokenId]
664
+ });
665
+ }
666
+ /**
667
+ * ERC721 approve (single token).
668
+ */
669
+ approveNFT(nft, to, tokenId) {
670
+ return this.addCall({
671
+ target: nft,
672
+ abi: ERC721_ABI,
673
+ functionName: "approve",
674
+ args: [to, tokenId]
675
+ });
676
+ }
677
+ /**
678
+ * ERC721 setApprovalForAll.
679
+ */
680
+ setApprovalForAll(nft, operator, approved) {
681
+ return this.addCall({
682
+ target: nft,
683
+ abi: ERC721_ABI,
684
+ functionName: "setApprovalForAll",
685
+ args: [operator, approved]
686
+ });
687
+ }
688
+ // ==========================================================================
689
+ // WETH Helpers
690
+ // ==========================================================================
691
+ /**
692
+ * Wrap ETH → WETH.
693
+ * The value is sent as ETH and WETH is received in return.
694
+ */
695
+ wrapETH(wethAddress = MEGAETH_TOKENS.WETH, amount) {
696
+ return this.addCall({
697
+ target: wethAddress,
698
+ abi: WETH_ABI,
699
+ functionName: "deposit",
700
+ value: amount
701
+ });
702
+ }
703
+ /**
704
+ * Unwrap WETH → ETH.
705
+ */
706
+ unwrapWETH(wethAddress = MEGAETH_TOKENS.WETH, amount) {
707
+ return this.addCall({
708
+ target: wethAddress,
709
+ abi: WETH_ABI,
710
+ functionName: "withdraw",
711
+ args: [amount]
712
+ });
713
+ }
714
+ // ==========================================================================
715
+ // DEX Helpers (Uniswap V2 interface — works with any V2-compatible DEX)
716
+ // ==========================================================================
717
+ /**
718
+ * Token→Token swap via Uniswap V2 router interface.
719
+ */
720
+ swapExactTokensForTokens(params) {
721
+ return this.addCall({
722
+ target: params.router,
723
+ abi: UNISWAP_V2_ROUTER_ABI,
724
+ functionName: "swapExactTokensForTokens",
725
+ args: [
726
+ params.amountIn,
727
+ params.amountOutMin,
728
+ params.path,
729
+ params.to,
730
+ params.deadline ?? deadlineInMinutes(5)
731
+ ]
732
+ });
733
+ }
734
+ /**
735
+ * ETH→Token swap via Uniswap V2 router interface.
736
+ */
737
+ swapExactETHForTokens(params) {
738
+ return this.addCall({
739
+ target: params.router,
740
+ abi: UNISWAP_V2_ROUTER_ABI,
741
+ functionName: "swapExactETHForTokens",
742
+ args: [
743
+ params.amountOutMin,
744
+ params.path,
745
+ params.to,
746
+ params.deadline ?? deadlineInMinutes(5)
747
+ ],
748
+ value: params.amountIn
749
+ });
750
+ }
751
+ /**
752
+ * Token→ETH swap via Uniswap V2 router interface.
753
+ */
754
+ swapExactTokensForETH(params) {
755
+ return this.addCall({
756
+ target: params.router,
757
+ abi: UNISWAP_V2_ROUTER_ABI,
758
+ functionName: "swapExactTokensForETH",
759
+ args: [
760
+ params.amountIn,
761
+ params.amountOutMin,
762
+ params.path,
763
+ params.to,
764
+ params.deadline ?? deadlineInMinutes(5)
765
+ ]
766
+ });
767
+ }
768
+ /**
769
+ * Approve a token and then swap in the same batch (2 calls, 1 tx).
770
+ * This is the core "Sui-style composability" pattern.
771
+ */
772
+ approveAndSwap(params) {
773
+ return this.approve(params.token, params.router, params.amountIn).swapExactTokensForTokens(params);
774
+ }
775
+ // ==========================================================================
776
+ // KyberSwap Integration (Best-Route Aggregator on MegaETH)
777
+ // ==========================================================================
778
+ /**
779
+ * Add a KyberSwap aggregator swap call.
780
+ * Fetches route and encodes the on-chain call automatically.
781
+ *
782
+ * @example
783
+ * await builder.kyberSwap({
784
+ * tokenIn: USDC,
785
+ * tokenOut: WETH,
786
+ * amountIn: parseUnits('100', 6),
787
+ * slippageBps: 30, // 0.3%
788
+ * }, senderAddress);
789
+ */
790
+ async kyberSwap(params, sender) {
791
+ const route = await getKyberQuote(params);
792
+ const built = await buildKyberSwap({
793
+ routeSummary: route,
794
+ sender,
795
+ recipient: sender,
796
+ slippageBps: params.slippageBps
797
+ });
798
+ return this.add(
799
+ built.routerAddress,
800
+ built.data,
801
+ BigInt(built.value)
802
+ );
803
+ }
804
+ // ==========================================================================
805
+ // Utility Methods
806
+ // ==========================================================================
807
+ /** Get a copy of queued calls */
808
+ getCalls() {
809
+ return [...this.calls];
810
+ }
811
+ /** Get recorded operations (for debugging/tracking) */
812
+ getOperations() {
813
+ return [...this.operations];
814
+ }
815
+ /** Get full builder state (for serialization/debugging) */
816
+ getState() {
817
+ return {
818
+ calls: [...this.calls],
819
+ operations: [...this.operations],
820
+ totalValue: this.getTotalValue(),
821
+ chainId: this.chain.id
822
+ };
823
+ }
824
+ /** Clear all queued calls and operation history */
825
+ clear() {
826
+ this.calls = [];
827
+ this.operations = [];
828
+ return this;
829
+ }
830
+ /** Remove and return the last call */
831
+ pop() {
832
+ this.operations.pop();
833
+ return this.calls.pop();
834
+ }
835
+ /** Total ETH value across all calls */
836
+ getTotalValue() {
837
+ return this.calls.reduce((sum, c) => sum + c.value, 0n);
838
+ }
839
+ /** Number of calls in the current batch */
840
+ get length() {
841
+ return this.calls.length;
842
+ }
843
+ /** Whether batch is empty */
844
+ get isEmpty() {
845
+ return this.calls.length === 0;
846
+ }
847
+ /** Whether a wallet is connected */
848
+ get isConnected() {
849
+ return !!this.walletClient?.account;
850
+ }
851
+ /** The connected account address (or undefined) */
852
+ get account() {
853
+ return this.walletClient?.account?.address;
854
+ }
855
+ /**
856
+ * Get a human-readable summary of the queued batch.
857
+ * Great for logging/debugging before execution.
858
+ */
859
+ summary() {
860
+ const lines = [
861
+ `MegaFlow Batch (${this.calls.length} calls)`,
862
+ `Chain: ${this.chain.name} (${this.chain.id})`,
863
+ `Router: ${this.routerAddress}`,
864
+ `Total Value: ${this.getTotalValue()} wei`,
865
+ "",
866
+ "Operations:"
867
+ ];
868
+ this.operations.forEach((op, i) => {
869
+ const target = op.call.target.slice(0, 10) + "...";
870
+ lines.push(` ${i + 1}. ${op.type} \u2192 ${target}`);
871
+ });
872
+ return lines.join("\n");
873
+ }
874
+ // ==========================================================================
875
+ // On-Chain Reads
876
+ // ==========================================================================
877
+ /** Read the protocol flat fee from the MegaRouter contract */
878
+ async getFlatFee() {
879
+ return this.publicClient.readContract({
880
+ address: this.routerAddress,
881
+ abi: MEGA_ROUTER_ABI,
882
+ functionName: "flatFee"
883
+ });
884
+ }
885
+ /** Calculate total ETH required (sum of call values + flat fee) */
886
+ async calculateRequiredETH() {
887
+ if (this.calls.length === 0) return 0n;
888
+ return this.publicClient.readContract({
889
+ address: this.routerAddress,
890
+ abi: MEGA_ROUTER_ABI,
891
+ functionName: "calculateRequiredETH",
892
+ args: [this.calls]
893
+ });
894
+ }
895
+ // ==========================================================================
896
+ // Simulation (Dry Run)
897
+ // ==========================================================================
898
+ /**
899
+ * Simulate the batch via eth_call — no state changes, no gas spent.
900
+ * Very useful for pre-flight checks before submitting the real transaction.
901
+ */
902
+ async simulate() {
903
+ assertCallsNotEmpty(this.calls);
904
+ try {
905
+ const requiredETH = await this.calculateRequiredETH();
906
+ const { result } = await this.publicClient.simulateContract({
907
+ address: this.routerAddress,
908
+ abi: MEGA_ROUTER_ABI,
909
+ functionName: "executeBatch",
910
+ args: [this.calls],
911
+ value: requiredETH,
912
+ account: this.walletClient?.account
913
+ });
914
+ const gasEstimate = await this.publicClient.estimateContractGas({
915
+ address: this.routerAddress,
916
+ abi: MEGA_ROUTER_ABI,
917
+ functionName: "executeBatch",
918
+ args: [this.calls],
919
+ value: requiredETH,
920
+ account: this.walletClient?.account
921
+ });
922
+ return {
923
+ success: true,
924
+ results: result,
925
+ gasEstimate: applyMegaethGasBuffer(gasEstimate)
926
+ };
927
+ } catch (err) {
928
+ const errorMessage = err instanceof Error ? err.message : String(err);
929
+ const revertReason = decodeRevertError(errorMessage);
930
+ const failedCallIndex = this.extractFailedCallIndex(errorMessage);
931
+ this.log("Simulation failed", { error: errorMessage, failedCallIndex });
932
+ return {
933
+ success: false,
934
+ error: errorMessage,
935
+ revertReason,
936
+ failedCallIndex
937
+ };
938
+ }
939
+ }
940
+ // ==========================================================================
941
+ // Execution — Standard (async)
942
+ // ==========================================================================
943
+ /**
944
+ * Execute the batch using the standard async flow:
945
+ * 1. Simulate to get request params
946
+ * 2. walletClient.writeContract → txHash
947
+ * 3. waitForTransactionReceipt
948
+ *
949
+ * On MegaETH this is still very fast (~100-200ms) due to 10ms blocks.
950
+ * For true synchronous receipts use `executeSync()`.
951
+ */
952
+ async execute(options = {}) {
953
+ assertCallsNotEmpty(this.calls);
954
+ assertWalletConnected(this.walletClient);
955
+ assertAccount(this.walletClient);
956
+ this.log("Executing batch", { callCount: this.calls.length });
957
+ const requiredETH = await this.calculateRequiredETH();
958
+ const { request } = await this.publicClient.simulateContract({
959
+ address: this.routerAddress,
960
+ abi: MEGA_ROUTER_ABI,
961
+ functionName: "executeBatch",
962
+ args: [this.calls],
963
+ value: requiredETH,
964
+ account: this.walletClient.account,
965
+ gas: options.gasLimit,
966
+ maxFeePerGas: options.maxFeePerGas,
967
+ maxPriorityFeePerGas: options.maxPriorityFeePerGas,
968
+ nonce: options.nonce
969
+ });
970
+ const hash = await this.walletClient.writeContract(request);
971
+ this.log("Transaction sent", { hash });
972
+ const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
973
+ this.log("Transaction confirmed", { status: receipt.status, gasUsed: receipt.gasUsed.toString() });
974
+ const results = parseCallResults(receipt, this.calls.length);
975
+ return {
976
+ hash,
977
+ receipt,
978
+ results,
979
+ gasUsed: receipt.gasUsed,
980
+ effectiveGasPrice: receipt.effectiveGasPrice,
981
+ totalCost: receipt.gasUsed * receipt.effectiveGasPrice
982
+ };
983
+ }
984
+ // ==========================================================================
985
+ // Execution — Synchronous (MegaETH-specific, EIP-7966)
986
+ // ==========================================================================
987
+ /**
988
+ * Execute using MegaETH's `eth_sendRawTransactionSync` (EIP-7966).
989
+ * Returns the full receipt in one RPC round-trip (~10ms).
990
+ *
991
+ * This is the preferred method on MegaETH — no polling required.
992
+ */
993
+ async executeSync(options = {}) {
994
+ assertCallsNotEmpty(this.calls);
995
+ assertWalletConnected(this.walletClient);
996
+ assertAccount(this.walletClient);
997
+ const startTime = performance.now();
998
+ this.log("Executing batch (sync)", { callCount: this.calls.length });
999
+ const requiredETH = await this.calculateRequiredETH();
1000
+ const { request } = await this.publicClient.simulateContract({
1001
+ address: this.routerAddress,
1002
+ abi: MEGA_ROUTER_ABI,
1003
+ functionName: "executeBatch",
1004
+ args: [this.calls],
1005
+ value: requiredETH,
1006
+ account: this.walletClient.account,
1007
+ gas: options.gasLimit
1008
+ });
1009
+ const signedTx = await this.walletClient.signTransaction({
1010
+ ...request,
1011
+ account: this.walletClient.account
1012
+ });
1013
+ const receipt = await this.publicClient.request({
1014
+ method: "eth_sendRawTransactionSync",
1015
+ params: [signedTx]
1016
+ });
1017
+ const executionTimeMs = performance.now() - startTime;
1018
+ this.log("Sync execution completed", { executionTimeMs: executionTimeMs.toFixed(2) });
1019
+ const results = parseCallResults(receipt, this.calls.length);
1020
+ return { receipt, results, gasUsed: receipt.gasUsed, executionTimeMs };
1021
+ }
1022
+ /**
1023
+ * Same as executeSync but uses `realtime_sendRawTransaction` (MegaETH original).
1024
+ * Both are functionally identical; use `executeSync` for cross-chain compatibility.
1025
+ */
1026
+ async executeRealtime(options = {}) {
1027
+ assertCallsNotEmpty(this.calls);
1028
+ assertWalletConnected(this.walletClient);
1029
+ assertAccount(this.walletClient);
1030
+ const startTime = performance.now();
1031
+ const requiredETH = await this.calculateRequiredETH();
1032
+ const { request } = await this.publicClient.simulateContract({
1033
+ address: this.routerAddress,
1034
+ abi: MEGA_ROUTER_ABI,
1035
+ functionName: "executeBatch",
1036
+ args: [this.calls],
1037
+ value: requiredETH,
1038
+ account: this.walletClient.account,
1039
+ gas: options.gasLimit
1040
+ });
1041
+ const signedTx = await this.walletClient.signTransaction({
1042
+ ...request,
1043
+ account: this.walletClient.account
1044
+ });
1045
+ const receipt = await this.publicClient.request({
1046
+ method: "realtime_sendRawTransaction",
1047
+ params: [signedTx]
1048
+ });
1049
+ const executionTimeMs = performance.now() - startTime;
1050
+ const results = parseCallResults(receipt, this.calls.length);
1051
+ return { receipt, results, gasUsed: receipt.gasUsed, executionTimeMs };
1052
+ }
1053
+ // ==========================================================================
1054
+ // Private Helpers
1055
+ // ==========================================================================
1056
+ recordOperation(type, call, metadata) {
1057
+ this.operations.push({
1058
+ type,
1059
+ call,
1060
+ metadata,
1061
+ timestamp: Date.now()
1062
+ });
1063
+ }
1064
+ extractFailedCallIndex(errorMessage) {
1065
+ const match = errorMessage.match(/CallFailed\((\d+)/);
1066
+ if (match) return parseInt(match[1], 10);
1067
+ const indexMatch = errorMessage.match(/call\s*#?\s*(\d+)/i);
1068
+ if (indexMatch) return parseInt(indexMatch[1], 10);
1069
+ return void 0;
1070
+ }
1071
+ log(message, data) {
1072
+ if (this.debug) {
1073
+ console.log(`[MegaFlow] ${message}`, data ?? "");
1074
+ }
1075
+ }
1076
+ };
1077
+
1078
+ // src/client.ts
1079
+ var import_viem4 = require("viem");
1080
+ var import_actions = require("viem/actions");
1081
+ var MegaFlowClient = class {
1082
+ constructor(config = {}) {
1083
+ // Local nonce cache: address → last used nonce
1084
+ this.nonceCache = /* @__PURE__ */ new Map();
1085
+ this.config = {
1086
+ ...config,
1087
+ routerAddress: config.routerAddress ?? MAINNET_ROUTER_ADDRESS
1088
+ };
1089
+ if (config.publicClient) {
1090
+ this.publicClient = config.publicClient;
1091
+ } else {
1092
+ const rpcUrl = config.rpcUrl ?? (config.chain ?? megaethMainnet).rpcUrls.default.http[0];
1093
+ this.publicClient = (0, import_viem4.createPublicClient)({
1094
+ chain: config.chain ?? megaethMainnet,
1095
+ transport: (0, import_viem4.http)(rpcUrl, { timeout: 3e4 })
1096
+ });
1097
+ }
1098
+ if (config.walletClient) {
1099
+ this.walletClient = config.walletClient;
1100
+ }
1101
+ }
1102
+ // ==========================================================================
1103
+ // Connection
1104
+ // ==========================================================================
1105
+ connectWithAccount(account, rpcUrl) {
1106
+ const chain = this.config.chain ?? megaethMainnet;
1107
+ this.walletClient = (0, import_viem4.createWalletClient)({
1108
+ account,
1109
+ chain,
1110
+ transport: (0, import_viem4.http)(rpcUrl ?? chain.rpcUrls.default.http[0])
1111
+ });
1112
+ return this;
1113
+ }
1114
+ connect(walletClient) {
1115
+ this.walletClient = walletClient;
1116
+ return this;
1117
+ }
1118
+ /**
1119
+ * Enable a WebSocket client for lower latency.
1120
+ * WebSocket is 5-6x faster than HTTP on MegaETH.
1121
+ *
1122
+ * Requires the chain to have a webSocket URL configured.
1123
+ */
1124
+ enableWebSocket(wsUrl) {
1125
+ const chain = this.config.chain ?? megaethMainnet;
1126
+ const url = wsUrl ?? chain.rpcUrls.default.webSocket?.[0];
1127
+ if (!url) throw new Error("No WebSocket URL available for this chain.");
1128
+ this.wsClient = (0, import_viem4.createPublicClient)({
1129
+ chain,
1130
+ // viem's webSocket transport handles reconnection internally.
1131
+ // MegaETH requires a keepalive ping every 30s — viem handles this
1132
+ // via the underlying WebSocket ping frames automatically.
1133
+ transport: (0, import_viem4.webSocket)(url)
1134
+ });
1135
+ return this;
1136
+ }
1137
+ /** Active public client (prefers WebSocket when enabled) */
1138
+ get activeClient() {
1139
+ return this.wsClient ?? this.publicClient;
1140
+ }
1141
+ // ==========================================================================
1142
+ // Batch Builder Factory
1143
+ // ==========================================================================
1144
+ /**
1145
+ * Create a new MegaFlowBuilder linked to this client's configuration.
1146
+ * Start building calls, then call `.execute()` or `.executeSync()`.
1147
+ */
1148
+ batch() {
1149
+ const builder = new MegaFlowBuilder({
1150
+ routerAddress: this.config.routerAddress,
1151
+ rpcUrl: this.config.rpcUrl,
1152
+ chain: this.config.chain
1153
+ });
1154
+ if (this.walletClient) builder.connect(this.walletClient);
1155
+ return builder;
1156
+ }
1157
+ // ==========================================================================
1158
+ // Token Reads (Multicall batched for efficiency)
1159
+ // ==========================================================================
1160
+ /**
1161
+ * Get the native ETH balance of an address.
1162
+ */
1163
+ async getETHBalance(address) {
1164
+ return this.publicClient.getBalance({ address });
1165
+ }
1166
+ /**
1167
+ * Get the ERC20 token balance of an address.
1168
+ */
1169
+ async getTokenBalance(token, owner) {
1170
+ return this.publicClient.readContract({
1171
+ address: token,
1172
+ abi: ERC20_ABI,
1173
+ functionName: "balanceOf",
1174
+ args: [owner]
1175
+ });
1176
+ }
1177
+ /**
1178
+ * Get the current ERC20 allowance.
1179
+ */
1180
+ async getAllowance(token, owner, spender) {
1181
+ return this.publicClient.readContract({
1182
+ address: token,
1183
+ abi: ERC20_ABI,
1184
+ functionName: "allowance",
1185
+ args: [owner, spender]
1186
+ });
1187
+ }
1188
+ /**
1189
+ * Get multiple token balances in a single multicall.
1190
+ * Much faster than sequential calls on MegaETH (v2.0.14+).
1191
+ */
1192
+ async getMultipleBalances(tokens, owner) {
1193
+ const contracts = tokens.map((token) => ({
1194
+ address: token,
1195
+ abi: ERC20_ABI,
1196
+ functionName: "balanceOf",
1197
+ args: [owner]
1198
+ }));
1199
+ const results = await (0, import_actions.multicall)(this.publicClient, { contracts });
1200
+ return tokens.map((token, i) => ({
1201
+ token,
1202
+ balance: results[i].status === "success" ? results[i].result : 0n
1203
+ }));
1204
+ }
1205
+ /**
1206
+ * Get token metadata (name, symbol, decimals) in a single multicall round-trip.
1207
+ */
1208
+ async getTokenInfo(token) {
1209
+ const [name, symbol, decimals, totalSupply] = await (0, import_actions.multicall)(this.publicClient, {
1210
+ contracts: [
1211
+ { address: token, abi: ERC20_ABI, functionName: "name" },
1212
+ { address: token, abi: ERC20_ABI, functionName: "symbol" },
1213
+ { address: token, abi: ERC20_ABI, functionName: "decimals" },
1214
+ { address: token, abi: ERC20_ABI, functionName: "totalSupply" }
1215
+ ]
1216
+ });
1217
+ return {
1218
+ name: name.result ?? "",
1219
+ symbol: symbol.result ?? "",
1220
+ decimals: decimals.result ?? 18,
1221
+ totalSupply: totalSupply.result ?? 0n
1222
+ };
1223
+ }
1224
+ // ==========================================================================
1225
+ // Router Reads
1226
+ // ==========================================================================
1227
+ /** Get protocol flat fee from the deployed router */
1228
+ async getFlatFee() {
1229
+ return this.publicClient.readContract({
1230
+ address: this.config.routerAddress,
1231
+ abi: MEGA_ROUTER_ABI,
1232
+ functionName: "flatFee"
1233
+ });
1234
+ }
1235
+ /** Get fee collector address from the deployed router */
1236
+ async getFeeCollector() {
1237
+ return this.publicClient.readContract({
1238
+ address: this.config.routerAddress,
1239
+ abi: MEGA_ROUTER_ABI,
1240
+ functionName: "feeCollector"
1241
+ });
1242
+ }
1243
+ // ==========================================================================
1244
+ // Nonce Management
1245
+ // ==========================================================================
1246
+ /**
1247
+ * Get the next usable nonce for an address, respecting locally tracked nonces.
1248
+ *
1249
+ * Critical for MegaETH: 10ms blocks mean sequential txs can race.
1250
+ * Local nonce tracking prevents "already known" / "nonce too low" errors.
1251
+ */
1252
+ async getNextNonce(address) {
1253
+ const key = address.toLowerCase();
1254
+ const networkNonce = await this.publicClient.getTransactionCount({
1255
+ address,
1256
+ blockTag: "pending"
1257
+ });
1258
+ const lastUsed = this.nonceCache.get(key) ?? -1;
1259
+ const nonce = Math.max(networkNonce, lastUsed + 1);
1260
+ this.nonceCache.set(key, nonce);
1261
+ return nonce;
1262
+ }
1263
+ /** Reset locally cached nonce for an address (call after a tx confirms or fails). */
1264
+ resetNonce(address) {
1265
+ this.nonceCache.delete(address.toLowerCase());
1266
+ }
1267
+ // ==========================================================================
1268
+ // Gas Helpers
1269
+ // ==========================================================================
1270
+ /**
1271
+ * Get the current gas price from MegaETH RPC.
1272
+ * Returns the raw value (avoids viem's 20% buffer).
1273
+ */
1274
+ async getGasPrice() {
1275
+ const price = await this.publicClient.request({
1276
+ method: "eth_gasPrice"
1277
+ });
1278
+ return BigInt(price);
1279
+ }
1280
+ // ==========================================================================
1281
+ // MegaETH Bridge Helper
1282
+ // ==========================================================================
1283
+ /**
1284
+ * Bridge ETH from Ethereum mainnet to MegaETH.
1285
+ *
1286
+ * NOTE: This must be called from an Ethereum mainnet wallet client,
1287
+ * not the MegaETH wallet client.
1288
+ *
1289
+ * @param l1WalletClient - Ethereum mainnet wallet client
1290
+ * @param amount - Amount in wei to bridge
1291
+ */
1292
+ async bridgeETHToMegaETH(l1WalletClient, amount) {
1293
+ if (!l1WalletClient.account) {
1294
+ throw new Error("L1 wallet client has no account");
1295
+ }
1296
+ const hash = await l1WalletClient.sendTransaction({
1297
+ account: l1WalletClient.account,
1298
+ to: MEGAETH_L1_BRIDGE,
1299
+ value: amount,
1300
+ chain: l1WalletClient.chain
1301
+ });
1302
+ return hash;
1303
+ }
1304
+ // ==========================================================================
1305
+ // Connection Status
1306
+ // ==========================================================================
1307
+ get isConnected() {
1308
+ return !!this.walletClient?.account;
1309
+ }
1310
+ get account() {
1311
+ return this.walletClient?.account?.address;
1312
+ }
1313
+ get routerAddress() {
1314
+ return this.config.routerAddress;
1315
+ }
1316
+ };
1317
+
1318
+ // src/index.ts
1319
+ function createMegaFlow(config) {
1320
+ return new MegaFlowBuilder(config);
1321
+ }
1322
+ function createMegaFlowClient(config) {
1323
+ return new MegaFlowClient(config);
1324
+ }
1325
+ function createMegaFlowMainnet(routerAddress) {
1326
+ return new MegaFlowBuilder({ routerAddress, chain: megaethMainnet });
1327
+ }
1328
+ function createMegaFlowTestnet(routerAddress) {
1329
+ return new MegaFlowBuilder({ routerAddress, chain: megaethTestnet });
1330
+ }
1331
+ function createMegaFlowClientMainnet(routerAddress, rpcUrl) {
1332
+ return new MegaFlowClient({ routerAddress, chain: megaethMainnet, rpcUrl });
1333
+ }
1334
+ function createMegaFlowClientTestnet(routerAddress, rpcUrl) {
1335
+ return new MegaFlowClient({ routerAddress, chain: megaethTestnet, rpcUrl });
1336
+ }
1337
+ // Annotate the CommonJS export names for ESM import in node:
1338
+ 0 && (module.exports = {
1339
+ ERC20_ABI,
1340
+ ERC721_ABI,
1341
+ KYBERSWAP_API_BASE,
1342
+ MEGAETH_BASE_FEE,
1343
+ MEGAETH_GAS_LIMITS,
1344
+ MEGAETH_L1_BRIDGE,
1345
+ MEGAETH_TOKENS,
1346
+ MEGA_ROUTER_ABI,
1347
+ MegaFlowBuilder,
1348
+ MegaFlowClient,
1349
+ MegaFlowError,
1350
+ UNISWAP_V2_ROUTER_ABI,
1351
+ WETH_ABI,
1352
+ applyMegaethGasBuffer,
1353
+ buildKyberSwap,
1354
+ buildSafeApproveCalls,
1355
+ calculateTotalValue,
1356
+ chainById,
1357
+ chunkCalls,
1358
+ createMegaFlow,
1359
+ createMegaFlowClient,
1360
+ createMegaFlowClientMainnet,
1361
+ createMegaFlowClientTestnet,
1362
+ createMegaFlowMainnet,
1363
+ createMegaFlowTestnet,
1364
+ deadlineInMinutes,
1365
+ getKyberQuote,
1366
+ isUserRejection,
1367
+ isValidAddress,
1368
+ isValidHex,
1369
+ megaethChains,
1370
+ megaethMainnet,
1371
+ megaethTestnet,
1372
+ parseBatchExecutedEvent,
1373
+ parseCallExecutedEvents,
1374
+ parseCallResults
1375
+ });