@exagent/agent 0.3.5 → 0.3.7

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.
Files changed (78) hide show
  1. package/dist/chunk-7UGLJO6W.js +6392 -0
  2. package/dist/chunk-EHAOPCTJ.js +6406 -0
  3. package/dist/chunk-FGMXTW5I.js +6540 -0
  4. package/dist/chunk-IVA2SCSN.js +6756 -0
  5. package/dist/chunk-JHXCSGPC.js +6352 -0
  6. package/dist/chunk-V6O4UXVN.js +6345 -0
  7. package/dist/chunk-ZRAOPQQW.js +6406 -0
  8. package/dist/cli.js +40 -98
  9. package/dist/index.d.ts +24 -2
  10. package/dist/index.js +1 -1
  11. package/package.json +17 -14
  12. package/.turbo/turbo-build.log +0 -17
  13. package/src/bridge/across.ts +0 -240
  14. package/src/bridge/bridge-manager.ts +0 -87
  15. package/src/bridge/index.ts +0 -9
  16. package/src/bridge/types.ts +0 -77
  17. package/src/chains.ts +0 -105
  18. package/src/cli.ts +0 -244
  19. package/src/config.ts +0 -499
  20. package/src/diagnostics.ts +0 -335
  21. package/src/index.ts +0 -98
  22. package/src/llm/anthropic.ts +0 -63
  23. package/src/llm/base.ts +0 -264
  24. package/src/llm/deepseek.ts +0 -48
  25. package/src/llm/google.ts +0 -63
  26. package/src/llm/groq.ts +0 -48
  27. package/src/llm/index.ts +0 -42
  28. package/src/llm/mistral.ts +0 -48
  29. package/src/llm/ollama.ts +0 -52
  30. package/src/llm/openai.ts +0 -51
  31. package/src/llm/together.ts +0 -48
  32. package/src/llm-providers.ts +0 -100
  33. package/src/logger.ts +0 -137
  34. package/src/paper/executor.ts +0 -201
  35. package/src/paper/index.ts +0 -1
  36. package/src/perp/client.ts +0 -200
  37. package/src/perp/index.ts +0 -12
  38. package/src/perp/msgpack.ts +0 -272
  39. package/src/perp/orders.ts +0 -234
  40. package/src/perp/positions.ts +0 -126
  41. package/src/perp/signer.ts +0 -277
  42. package/src/perp/types.ts +0 -192
  43. package/src/perp/websocket.ts +0 -274
  44. package/src/position-tracker.ts +0 -243
  45. package/src/prediction/client.ts +0 -281
  46. package/src/prediction/index.ts +0 -3
  47. package/src/prediction/order-manager.ts +0 -297
  48. package/src/prediction/types.ts +0 -151
  49. package/src/relay.ts +0 -254
  50. package/src/runtime.ts +0 -1755
  51. package/src/scrub-secrets.ts +0 -39
  52. package/src/setup.ts +0 -384
  53. package/src/signal.ts +0 -212
  54. package/src/spot/aerodrome.ts +0 -158
  55. package/src/spot/client.ts +0 -138
  56. package/src/spot/index.ts +0 -11
  57. package/src/spot/swap-manager.ts +0 -219
  58. package/src/spot/types.ts +0 -203
  59. package/src/spot/uniswap.ts +0 -150
  60. package/src/store.ts +0 -50
  61. package/src/strategy/index.ts +0 -2
  62. package/src/strategy/loader.ts +0 -191
  63. package/src/strategy/templates.ts +0 -125
  64. package/src/trading/index.ts +0 -2
  65. package/src/trading/market.ts +0 -120
  66. package/src/trading/risk.ts +0 -107
  67. package/src/ui.ts +0 -75
  68. package/test-bridge-arb-to-base.mjs +0 -223
  69. package/test-funded-check.mjs +0 -79
  70. package/test-funded-phase19.mjs +0 -933
  71. package/test-hl-deposit-recover.mjs +0 -281
  72. package/test-hl-withdraw.mjs +0 -372
  73. package/test-live-signing.mjs +0 -374
  74. package/test-phase7.mjs +0 -416
  75. package/test-recover-arb.mjs +0 -206
  76. package/test-spot-bridge.mjs +0 -248
  77. package/test-wallet-setup.mjs +0 -126
  78. package/tsconfig.json +0 -8
@@ -1,281 +0,0 @@
1
- /**
2
- * Phase 19 — Hyperliquid deposit fix + fund recovery.
3
- *
4
- * The Bridge2 contract accepts plain USDC transfers (not a function call).
5
- * Test wallet has ~8 USDC + 0.002 ETH on Arbitrum from previous steps.
6
- *
7
- * Steps:
8
- * 1. Deposit 6 USDC to Hyperliquid via plain ERC20 transfer
9
- * 2. Verify deposit on Hyperliquid
10
- * 3. Bridge remaining USDC back from Arbitrum → Base
11
- * 4. Return Base funds to deployer
12
- */
13
-
14
- import {
15
- createPublicClient,
16
- createWalletClient,
17
- http,
18
- formatEther,
19
- formatUnits,
20
- parseUnits,
21
- maxUint256,
22
- } from 'viem';
23
- import { privateKeyToAccount } from 'viem/accounts';
24
- import { base, arbitrum } from 'viem/chains';
25
-
26
- const DEPLOYER_KEY = '0x0991f4e17be491bb11f4cf1d079db1771fe669e2b0d735f2b3ffc0e32d230ac9';
27
- const TEST_KEY = '0xb027d931f6c8b4b2681451716981432130806f01ff87001c74412b504b810dbe';
28
-
29
- const deployer = privateKeyToAccount(DEPLOYER_KEY);
30
- const testWallet = privateKeyToAccount(TEST_KEY);
31
-
32
- const basePublic = createPublicClient({ chain: base, transport: http('https://mainnet.base.org') });
33
- const baseTestWallet = createWalletClient({ account: testWallet, chain: base, transport: http('https://mainnet.base.org') });
34
-
35
- const arbPublic = createPublicClient({ chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
36
- const arbTestWallet = createWalletClient({ account: testWallet, chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
37
-
38
- const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
39
- const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
40
- const HL_BRIDGE_ARB = '0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7';
41
- const ACROSS_SPOKE_ARB = '0xe35e9842fceaCA96570B734083f4a58e8F7C5f2A';
42
- const ACROSS_SPOKE_BASE = '0x09aea4b2242abC8bb4BB78D537A67a245A7bEC64';
43
- const WETH_ARB = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1';
44
- const WETH_BASE = '0x4200000000000000000000000000000000000006';
45
-
46
- const ERC20_ABI = [
47
- { type: 'function', name: 'approve', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable' },
48
- { type: 'function', name: 'transfer', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable' },
49
- { type: 'function', name: 'balanceOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
50
- { type: 'function', name: 'allowance', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
51
- ];
52
-
53
- const ACROSS_SPOKE_ABI = [
54
- {
55
- type: 'function', name: 'depositV3',
56
- inputs: [
57
- { name: 'depositor', type: 'address' },
58
- { name: 'recipient', type: 'address' },
59
- { name: 'inputToken', type: 'address' },
60
- { name: 'outputToken', type: 'address' },
61
- { name: 'inputAmount', type: 'uint256' },
62
- { name: 'outputAmount', type: 'uint256' },
63
- { name: 'destinationChainId', type: 'uint256' },
64
- { name: 'exclusiveRelayer', type: 'address' },
65
- { name: 'quoteTimestamp', type: 'uint32' },
66
- { name: 'fillDeadline', type: 'uint32' },
67
- { name: 'exclusivityDeadline', type: 'uint32' },
68
- { name: 'message', type: 'bytes' },
69
- ],
70
- outputs: [],
71
- stateMutability: 'payable',
72
- },
73
- ];
74
-
75
- function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
76
-
77
- async function waitForTx(client, hash, label) {
78
- console.log(` ⏳ ${label}: waiting for tx ${hash.slice(0, 10)}...`);
79
- const receipt = await client.waitForTransactionReceipt({ hash, timeout: 120_000 });
80
- if (receipt.status === 'reverted') throw new Error(`${label} REVERTED: ${hash}`);
81
- console.log(` ✅ ${label}: confirmed in block ${receipt.blockNumber} (gas: ${receipt.gasUsed})`);
82
- return receipt;
83
- }
84
-
85
- // ─── Check balances ────────────────────────────────────────────
86
-
87
- console.log('\n═══ Current State ═══');
88
- const arbUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
89
- const arbEth = await arbPublic.getBalance({ address: testWallet.address });
90
- console.log(` Test wallet on Arbitrum: ${formatUnits(arbUsdc, 6)} USDC, ${formatEther(arbEth)} ETH`);
91
-
92
- const baseUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
93
- const baseEth = await basePublic.getBalance({ address: testWallet.address });
94
- console.log(` Test wallet on Base: ${formatUnits(baseUsdc, 6)} USDC, ${formatEther(baseEth)} ETH`);
95
-
96
- // ─── Step 1: Deposit to Hyperliquid via plain USDC transfer ───
97
-
98
- console.log('\n═══ Step 1: Deposit USDC to Hyperliquid (plain transfer) ═══');
99
-
100
- const depositAmount = parseUnits('6', 6); // $6 USDC
101
-
102
- if (arbUsdc < depositAmount) {
103
- console.log(` ❌ Not enough USDC (have ${formatUnits(arbUsdc, 6)}, need 6)`);
104
- process.exit(1);
105
- }
106
-
107
- console.log(` Transferring ${formatUnits(depositAmount, 6)} USDC to HL bridge contract...`);
108
- const depositHash = await arbTestWallet.writeContract({
109
- address: USDC_ARB,
110
- abi: ERC20_ABI,
111
- functionName: 'transfer',
112
- args: [HL_BRIDGE_ARB, depositAmount],
113
- });
114
-
115
- await waitForTx(arbPublic, depositHash, 'HL deposit');
116
-
117
- // ─── Step 2: Verify on Hyperliquid ────────────────────────────
118
-
119
- console.log('\n═══ Step 2: Verify Hyperliquid Balance ═══');
120
- console.log(' Waiting 60s for Hyperliquid to process deposit...');
121
-
122
- for (let i = 0; i < 12; i++) {
123
- await sleep(5000);
124
- try {
125
- const stateRes = await fetch('https://api.hyperliquid.xyz/info', {
126
- method: 'POST',
127
- headers: { 'Content-Type': 'application/json' },
128
- body: JSON.stringify({ type: 'clearinghouseState', user: testWallet.address }),
129
- });
130
-
131
- if (stateRes.ok) {
132
- const state = await stateRes.json();
133
- const equity = parseFloat(state.marginSummary?.accountValue ?? '0');
134
- const withdrawable = parseFloat(state.withdrawable ?? '0');
135
-
136
- if (equity > 0) {
137
- console.log(` ✅ Hyperliquid deposit confirmed!`);
138
- console.log(` Account value: $${equity.toFixed(2)}`);
139
- console.log(` Withdrawable: $${withdrawable.toFixed(2)}`);
140
- break;
141
- }
142
- }
143
- } catch {}
144
- if (i % 3 === 0) console.log(` ... waiting (${(i+1)*5}s)`);
145
- }
146
-
147
- // Final check
148
- const finalState = await fetch('https://api.hyperliquid.xyz/info', {
149
- method: 'POST',
150
- headers: { 'Content-Type': 'application/json' },
151
- body: JSON.stringify({ type: 'clearinghouseState', user: testWallet.address }),
152
- });
153
-
154
- if (finalState.ok) {
155
- const state = await finalState.json();
156
- const equity = parseFloat(state.marginSummary?.accountValue ?? '0');
157
- if (equity > 0) {
158
- console.log(` ✅ Hyperliquid account funded: $${equity.toFixed(2)}`);
159
- } else {
160
- console.log(` ⚠️ Deposit may still be processing (can take up to 1 minute)`);
161
- console.log(` Check manually: https://api.hyperliquid.xyz/info with clearinghouseState`);
162
- }
163
- }
164
-
165
- // ─── Step 3: Bridge remaining USDC back from Arbitrum → Base ──
166
-
167
- console.log('\n═══ Step 3: Bridge remaining USDC Arbitrum → Base ═══');
168
-
169
- const remainingUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
170
- console.log(` Remaining USDC on Arbitrum: ${formatUnits(remainingUsdc, 6)}`);
171
-
172
- if (remainingUsdc > parseUnits('0.5', 6)) {
173
- // Get fee estimate
174
- const params = new URLSearchParams({
175
- inputToken: USDC_ARB,
176
- outputToken: USDC_BASE,
177
- originChainId: '42161',
178
- destinationChainId: '8453',
179
- amount: remainingUsdc.toString(),
180
- recipient: testWallet.address,
181
- });
182
-
183
- const feeRes = await fetch(`https://app.across.to/api/suggested-fees?${params}`);
184
- if (feeRes.ok) {
185
- const feeData = await feeRes.json();
186
-
187
- if (feeData.isAmountTooLow) {
188
- console.log(` ⚠️ Amount too low to bridge back. Will leave on Arbitrum.`);
189
- } else {
190
- const outputAmount = BigInt(remainingUsdc) - BigInt(feeData.totalRelayFee.total);
191
- const timestamp = parseInt(feeData.timestamp);
192
-
193
- console.log(` Bridging ${formatUnits(remainingUsdc, 6)} USDC back to Base (output: ${formatUnits(outputAmount, 6)})...`);
194
-
195
- // Approve to Across SpokePool on Arbitrum
196
- const allowance = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'allowance', args: [testWallet.address, ACROSS_SPOKE_ARB] });
197
- if (allowance < remainingUsdc) {
198
- const appHash = await arbTestWallet.writeContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'approve', args: [ACROSS_SPOKE_ARB, maxUint256] });
199
- await waitForTx(arbPublic, appHash, 'Arb approval');
200
- }
201
-
202
- const fillDeadline = Math.floor(Date.now() / 1000) + 21600;
203
- const bridgeHash = await arbTestWallet.writeContract({
204
- address: ACROSS_SPOKE_ARB,
205
- abi: ACROSS_SPOKE_ABI,
206
- functionName: 'depositV3',
207
- args: [
208
- testWallet.address, testWallet.address,
209
- USDC_ARB, USDC_BASE,
210
- remainingUsdc, outputAmount,
211
- 8453n, // Base chainId
212
- '0x0000000000000000000000000000000000000000',
213
- timestamp, fillDeadline, 0, '0x',
214
- ],
215
- });
216
-
217
- await waitForTx(arbPublic, bridgeHash, 'Arb→Base bridge');
218
-
219
- // Poll for fill
220
- console.log(' Polling for bridge fill...');
221
- for (let i = 0; i < 60; i++) {
222
- await sleep(2000);
223
- try {
224
- const statusRes = await fetch(`https://app.across.to/api/deposit/status?depositTxHash=${bridgeHash}&originChainId=42161`);
225
- if (statusRes.ok) {
226
- const status = await statusRes.json();
227
- if (status.status === 'filled') {
228
- console.log(` ✅ Bridge filled!`);
229
- break;
230
- }
231
- if (i % 5 === 0) console.log(` ... status: ${status.status} (${i*2}s)`);
232
- }
233
- } catch {}
234
- }
235
-
236
- await sleep(3000);
237
- }
238
- }
239
- } else {
240
- console.log(' No significant USDC to bridge back.');
241
- }
242
-
243
- // ─── Step 4: Return all Base funds to deployer ────────────────
244
-
245
- console.log('\n═══ Step 4: Return Funds to Deployer ═══');
246
-
247
- // Return USDC
248
- const finalBaseUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
249
- if (finalBaseUsdc > 0n) {
250
- console.log(` Returning ${formatUnits(finalBaseUsdc, 6)} USDC to deployer...`);
251
- const hash = await baseTestWallet.writeContract({
252
- address: USDC_BASE, abi: ERC20_ABI, functionName: 'transfer',
253
- args: [deployer.address, finalBaseUsdc],
254
- });
255
- await waitForTx(basePublic, hash, 'Return USDC');
256
- }
257
-
258
- // Return ETH (keep gas reserve)
259
- const finalBaseEth = await basePublic.getBalance({ address: testWallet.address });
260
- const gasReserve = 100000000000000n; // 0.0001 ETH
261
- if (finalBaseEth > gasReserve * 3n) {
262
- const returnAmount = finalBaseEth - gasReserve;
263
- console.log(` Returning ${formatEther(returnAmount)} ETH to deployer...`);
264
- const hash = await baseTestWallet.sendTransaction({
265
- to: deployer.address,
266
- value: returnAmount,
267
- });
268
- await waitForTx(basePublic, hash, 'Return ETH');
269
- }
270
-
271
- // ─── Final Balance Report ─────────────────────────────────────
272
-
273
- console.log('\n═══ Final Deployer Balance ═══');
274
- const deployerEth = await basePublic.getBalance({ address: deployer.address });
275
- const deployerUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [deployer.address] });
276
- console.log(` Base ETH: ${formatEther(deployerEth)}`);
277
- console.log(` Base USDC: ${formatUnits(deployerUsdc, 6)}`);
278
-
279
- console.log('\n════════════════════════════════════════════════');
280
- console.log('Hyperliquid deposit + recovery completed.');
281
- console.log('════════════════════════════════════════════════');
@@ -1,372 +0,0 @@
1
- /**
2
- * Phase 19 — Hyperliquid Withdrawal Test
3
- *
4
- * Withdraws USDC from Hyperliquid back to Arbitrum using the
5
- * HyperliquidSignTransaction EIP-712 domain (withdraw3 action).
6
- *
7
- * Then bridges USDC from Arbitrum back to Base and returns to deployer.
8
- */
9
-
10
- import {
11
- createPublicClient,
12
- createWalletClient,
13
- http,
14
- formatEther,
15
- formatUnits,
16
- maxUint256,
17
- } from 'viem';
18
- import { privateKeyToAccount } from 'viem/accounts';
19
- import { base, arbitrum } from 'viem/chains';
20
-
21
- const TEST_KEY = '0xb027d931f6c8b4b2681451716981432130806f01ff87001c74412b504b810dbe';
22
- const DEPLOYER_KEY = '0x0991f4e17be491bb11f4cf1d079db1771fe669e2b0d735f2b3ffc0e32d230ac9';
23
-
24
- const testWallet = privateKeyToAccount(TEST_KEY);
25
- const deployer = privateKeyToAccount(DEPLOYER_KEY);
26
-
27
- const arbPublic = createPublicClient({ chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
28
- const arbTestWalletClient = createWalletClient({ account: testWallet, chain: arbitrum, transport: http('https://arb1.arbitrum.io/rpc') });
29
- const basePublic = createPublicClient({ chain: base, transport: http('https://mainnet.base.org') });
30
- const baseTestWalletClient = createWalletClient({ account: testWallet, chain: base, transport: http('https://mainnet.base.org') });
31
-
32
- const USDC_ARB = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
33
- const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
34
- const ACROSS_SPOKE_ARB = '0xe35e9842fceaCA96570B734083f4a58e8F7C5f2A';
35
-
36
- const ERC20_ABI = [
37
- { type: 'function', name: 'balanceOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
38
- { type: 'function', name: 'approve', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable' },
39
- { type: 'function', name: 'allowance', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' },
40
- { type: 'function', name: 'transfer', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }], stateMutability: 'nonpayable' },
41
- ];
42
-
43
- const ACROSS_SPOKE_ABI = [
44
- {
45
- type: 'function', name: 'depositV3',
46
- inputs: [
47
- { name: 'depositor', type: 'address' }, { name: 'recipient', type: 'address' },
48
- { name: 'inputToken', type: 'address' }, { name: 'outputToken', type: 'address' },
49
- { name: 'inputAmount', type: 'uint256' }, { name: 'outputAmount', type: 'uint256' },
50
- { name: 'destinationChainId', type: 'uint256' },
51
- { name: 'exclusiveRelayer', type: 'address' },
52
- { name: 'quoteTimestamp', type: 'uint32' }, { name: 'fillDeadline', type: 'uint32' },
53
- { name: 'exclusivityDeadline', type: 'uint32' }, { name: 'message', type: 'bytes' },
54
- ],
55
- outputs: [],
56
- stateMutability: 'payable',
57
- },
58
- ];
59
-
60
- function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
61
-
62
- async function waitForTx(client, hash, label) {
63
- console.log(` ⏳ ${label}: tx ${hash.slice(0, 10)}...`);
64
- const receipt = await client.waitForTransactionReceipt({ hash, timeout: 120_000 });
65
- if (receipt.status === 'reverted') throw new Error(`${label} REVERTED: ${hash}`);
66
- console.log(` ✅ ${label}: confirmed (gas: ${receipt.gasUsed})`);
67
- return receipt;
68
- }
69
-
70
- // ═══════════════════════════════════════════════════════════
71
- // Step 1: Check Hyperliquid balance
72
- // ═══════════════════════════════════════════════════════════
73
-
74
- console.log('═══ Step 1: Check Hyperliquid Balance ═══');
75
-
76
- const stateRes = await fetch('https://api.hyperliquid.xyz/info', {
77
- method: 'POST',
78
- headers: { 'Content-Type': 'application/json' },
79
- body: JSON.stringify({ type: 'clearinghouseState', user: testWallet.address }),
80
- });
81
-
82
- const state = await stateRes.json();
83
- const equity = parseFloat(state.marginSummary?.accountValue ?? '0');
84
- const withdrawable = parseFloat(state.withdrawable ?? '0');
85
-
86
- console.log(` Account: ${testWallet.address}`);
87
- console.log(` Equity: $${equity.toFixed(2)}`);
88
- console.log(` Withdrawable: $${withdrawable.toFixed(2)}`);
89
-
90
- if (withdrawable <= 0) {
91
- console.log(' No withdrawable balance. Exiting.');
92
- process.exit(0);
93
- }
94
-
95
- // ═══════════════════════════════════════════════════════════
96
- // Step 2: Sign and submit withdraw3 action
97
- // ═══════════════════════════════════════════════════════════
98
-
99
- console.log('\n═══ Step 2: Withdraw from Hyperliquid ═══');
100
-
101
- const withdrawAmount = withdrawable.toString(); // Full withdrawable amount
102
- const timestamp = Date.now();
103
-
104
- console.log(` Withdrawing $${withdrawAmount} to ${testWallet.address} on Arbitrum`);
105
-
106
- // EIP-712 domain for user-signed actions (NOT the phantom agent domain!)
107
- const USER_SIGNED_DOMAIN = {
108
- name: 'HyperliquidSignTransaction',
109
- version: '1',
110
- chainId: 42161, // Arbitrum One mainnet
111
- verifyingContract: '0x0000000000000000000000000000000000000000',
112
- };
113
-
114
- // EIP-712 types for withdrawal
115
- const WITHDRAW_TYPES = {
116
- 'HyperliquidTransaction:Withdraw': [
117
- { name: 'hyperliquidChain', type: 'string' },
118
- { name: 'destination', type: 'string' },
119
- { name: 'amount', type: 'string' },
120
- { name: 'time', type: 'uint64' },
121
- ],
122
- };
123
-
124
- // Sign the withdrawal message
125
- const withdrawMessage = {
126
- hyperliquidChain: 'Mainnet',
127
- destination: testWallet.address,
128
- amount: withdrawAmount,
129
- time: BigInt(timestamp),
130
- };
131
-
132
- console.log(' Signing withdraw3 action...');
133
- const signature = await arbTestWalletClient.signTypedData({
134
- account: testWallet,
135
- domain: USER_SIGNED_DOMAIN,
136
- types: WITHDRAW_TYPES,
137
- primaryType: 'HyperliquidTransaction:Withdraw',
138
- message: withdrawMessage,
139
- });
140
-
141
- console.log(` Signature: ${signature.slice(0, 20)}...${signature.slice(-8)}`);
142
-
143
- // Parse signature into r, s, v
144
- const sigR = signature.slice(0, 66);
145
- const sigS = `0x${signature.slice(66, 130)}`;
146
- const sigV = parseInt(signature.slice(130, 132), 16);
147
-
148
- // Submit to Hyperliquid Exchange API
149
- const action = {
150
- type: 'withdraw3',
151
- hyperliquidChain: 'Mainnet',
152
- signatureChainId: '0xa4b1', // 42161 in hex
153
- destination: testWallet.address,
154
- amount: withdrawAmount,
155
- time: timestamp,
156
- };
157
-
158
- console.log(' Submitting to Hyperliquid Exchange API...');
159
- const resp = await fetch('https://api.hyperliquid.xyz/exchange', {
160
- method: 'POST',
161
- headers: { 'Content-Type': 'application/json' },
162
- body: JSON.stringify({
163
- action,
164
- nonce: timestamp,
165
- signature: { r: sigR, s: sigS, v: sigV },
166
- vaultAddress: null,
167
- }),
168
- });
169
-
170
- const respText = await resp.text();
171
- let respJson;
172
- try { respJson = JSON.parse(respText); } catch { respJson = respText; }
173
-
174
- console.log(` HTTP Status: ${resp.status}`);
175
- console.log(` Response: ${JSON.stringify(respJson)}`);
176
-
177
- const respStr = typeof respJson === 'string' ? respJson : JSON.stringify(respJson);
178
-
179
- if (respStr.toLowerCase().includes('invalid signature') ||
180
- respStr.toLowerCase().includes('bad signature')) {
181
- console.log(' ❌ SIGNATURE REJECTED — withdrawal signing is broken!');
182
- process.exit(1);
183
- }
184
-
185
- if (resp.status !== 200) {
186
- console.log(` ❌ Withdrawal failed: ${respStr}`);
187
- process.exit(1);
188
- }
189
-
190
- console.log(' ✅ Withdrawal submitted successfully!');
191
-
192
- // ═══════════════════════════════════════════════════════════
193
- // Step 3: Wait for USDC to arrive on Arbitrum
194
- // ═══════════════════════════════════════════════════════════
195
-
196
- console.log('\n═══ Step 3: Wait for USDC on Arbitrum ═══');
197
- console.log(' Hyperliquid withdrawals can take a few minutes...');
198
-
199
- const startUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
200
- console.log(` Current Arb USDC: ${formatUnits(startUsdc, 6)}`);
201
-
202
- // Poll for balance change (Hyperliquid withdrawals take ~1-5 minutes)
203
- let finalUsdc = startUsdc;
204
- for (let i = 0; i < 60; i++) { // up to 5 minutes
205
- await sleep(5000);
206
-
207
- const newUsdc = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
208
- if (newUsdc > startUsdc) {
209
- const received = newUsdc - startUsdc;
210
- console.log(` ✅ Received ${formatUnits(received, 6)} USDC on Arbitrum!`);
211
- finalUsdc = newUsdc;
212
- break;
213
- }
214
-
215
- // Also check HL balance
216
- if (i % 6 === 0) {
217
- try {
218
- const hlRes = await fetch('https://api.hyperliquid.xyz/info', {
219
- method: 'POST',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: JSON.stringify({ type: 'clearinghouseState', user: testWallet.address }),
222
- });
223
- const hlState = await hlRes.json();
224
- const hlEq = parseFloat(hlState.marginSummary?.accountValue ?? '0');
225
- console.log(` ... HL equity: $${hlEq.toFixed(2)}, Arb USDC: ${formatUnits(newUsdc, 6)} (${(i+1)*5}s)`);
226
- } catch {
227
- console.log(` ... waiting (${(i+1)*5}s)`);
228
- }
229
- }
230
- }
231
-
232
- if (finalUsdc === startUsdc) {
233
- console.log(' ⚠️ USDC not yet received after 5 minutes.');
234
- console.log(' Withdrawal may still be processing. Check manually later.');
235
- console.log(` Arbitrum address: ${testWallet.address}`);
236
- process.exit(0);
237
- }
238
-
239
- // ═══════════════════════════════════════════════════════════
240
- // Step 4: Bridge USDC from Arbitrum → Base
241
- // ═══════════════════════════════════════════════════════════
242
-
243
- console.log('\n═══ Step 4: Bridge USDC Arb → Base ═══');
244
-
245
- const arbUsdcBal = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
246
- console.log(` Arbitrum USDC: ${formatUnits(arbUsdcBal, 6)}`);
247
-
248
- if (arbUsdcBal === 0n) {
249
- console.log(' No USDC to bridge.');
250
- process.exit(0);
251
- }
252
-
253
- // Get fresh fee quote
254
- const params = new URLSearchParams({
255
- inputToken: USDC_ARB,
256
- outputToken: USDC_BASE,
257
- originChainId: '42161',
258
- destinationChainId: '8453',
259
- amount: arbUsdcBal.toString(),
260
- recipient: testWallet.address,
261
- });
262
-
263
- const feeRes = await fetch(`https://app.across.to/api/suggested-fees?${params}`);
264
- if (!feeRes.ok) {
265
- console.log(` Fee API error: ${await feeRes.text()}`);
266
- console.log(' Funds remain on Arbitrum — manual recovery needed.');
267
- process.exit(1);
268
- }
269
-
270
- const feeData = await feeRes.json();
271
-
272
- if (feeData.isAmountTooLow) {
273
- console.log(' Amount too low for Across. Sending USDC directly to deployer on Arb.');
274
- const h = await arbTestWalletClient.writeContract({
275
- address: USDC_ARB, abi: ERC20_ABI, functionName: 'transfer',
276
- args: [deployer.address, arbUsdcBal],
277
- });
278
- await waitForTx(arbPublic, h, 'Send to deployer');
279
- process.exit(0);
280
- }
281
-
282
- const outputAmount = BigInt(arbUsdcBal) - BigInt(feeData.totalRelayFee.total);
283
- const quoteTimestamp = parseInt(feeData.timestamp);
284
- const fillDeadline = Math.floor(Date.now() / 1000) + 21600;
285
-
286
- console.log(` Output: ${formatUnits(outputAmount, 6)} USDC on Base`);
287
-
288
- // Approve Across SpokePool
289
- const allowance = await arbPublic.readContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'allowance', args: [testWallet.address, ACROSS_SPOKE_ARB] });
290
- if (allowance < arbUsdcBal) {
291
- const h = await arbTestWalletClient.writeContract({ address: USDC_ARB, abi: ERC20_ABI, functionName: 'approve', args: [ACROSS_SPOKE_ARB, maxUint256] });
292
- await waitForTx(arbPublic, h, 'Approval');
293
- }
294
-
295
- // Submit bridge immediately after getting quote
296
- const bridgeHash = await arbTestWalletClient.writeContract({
297
- address: ACROSS_SPOKE_ARB,
298
- abi: ACROSS_SPOKE_ABI,
299
- functionName: 'depositV3',
300
- args: [
301
- testWallet.address, testWallet.address,
302
- USDC_ARB, USDC_BASE,
303
- arbUsdcBal, outputAmount,
304
- 8453n,
305
- '0x0000000000000000000000000000000000000000',
306
- quoteTimestamp, fillDeadline, 0, '0x',
307
- ],
308
- });
309
-
310
- await waitForTx(arbPublic, bridgeHash, 'Arb→Base bridge');
311
-
312
- // Poll for fill
313
- console.log(' Polling for bridge fill...');
314
- for (let i = 0; i < 60; i++) {
315
- await sleep(2000);
316
- try {
317
- const sRes = await fetch(`https://app.across.to/api/deposit/status?depositTxHash=${bridgeHash}&originChainId=42161`);
318
- if (sRes.ok) {
319
- const s = await sRes.json();
320
- if (s.status === 'filled') { console.log(' ✅ Bridge filled!'); break; }
321
- if (i % 5 === 0) console.log(` ... ${s.status} (${i*2}s)`);
322
- }
323
- } catch {}
324
- }
325
-
326
- await sleep(3000);
327
-
328
- // ═══════════════════════════════════════════════════════════
329
- // Step 5: Return all funds to deployer
330
- // ═══════════════════════════════════════════════════════════
331
-
332
- console.log('\n═══ Step 5: Return Funds to Deployer ═══');
333
-
334
- // USDC on Base
335
- const finalBaseUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [testWallet.address] });
336
- if (finalBaseUsdc > 0n) {
337
- console.log(` Returning ${formatUnits(finalBaseUsdc, 6)} USDC...`);
338
- const h = await baseTestWalletClient.writeContract({
339
- address: USDC_BASE, abi: ERC20_ABI, functionName: 'transfer',
340
- args: [deployer.address, finalBaseUsdc],
341
- });
342
- await waitForTx(basePublic, h, 'Return USDC');
343
- }
344
-
345
- // ETH on Base
346
- const finalBaseEth = await basePublic.getBalance({ address: testWallet.address });
347
- if (finalBaseEth > 200000000000000n) {
348
- const returnEth = finalBaseEth - 100000000000000n;
349
- console.log(` Returning ${formatEther(returnEth)} ETH...`);
350
- const h = await baseTestWalletClient.sendTransaction({ to: deployer.address, value: returnEth });
351
- await waitForTx(basePublic, h, 'Return ETH');
352
- }
353
-
354
- // Final report
355
- console.log('\n═══ Final Deployer Balance ═══');
356
- const dEth = await basePublic.getBalance({ address: deployer.address });
357
- const dUsdc = await basePublic.readContract({ address: USDC_BASE, abi: ERC20_ABI, functionName: 'balanceOf', args: [deployer.address] });
358
- console.log(` Base: ${formatEther(dEth)} ETH, ${formatUnits(dUsdc, 6)} USDC`);
359
-
360
- // Check HL is empty
361
- const hlFinal = await fetch('https://api.hyperliquid.xyz/info', {
362
- method: 'POST',
363
- headers: { 'Content-Type': 'application/json' },
364
- body: JSON.stringify({ type: 'clearinghouseState', user: testWallet.address }),
365
- });
366
- const hlFinalState = await hlFinal.json();
367
- const hlFinalEq = parseFloat(hlFinalState.marginSummary?.accountValue ?? '0');
368
- console.log(` Hyperliquid: $${hlFinalEq.toFixed(2)}`);
369
-
370
- console.log('\n════════════════════════════════════════════════');
371
- console.log('Hyperliquid withdrawal + fund recovery complete!');
372
- console.log('════════════════════════════════════════════════');