sovereignclaw 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/README.md +187 -0
- package/SKILL.md +186 -0
- package/bin/sovereignclaw.js +146 -0
- package/package.json +48 -0
- package/scripts/bridge.ts +431 -0
- package/scripts/bungee.ts +557 -0
- package/scripts/wallet.ts +195 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bungee Auto Integration
|
|
3
|
+
* Cross-chain swaps and bridges via Bungee (mainnet)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createWalletClient, createPublicClient, http } from 'viem';
|
|
7
|
+
import { privateKeyToAccount, mnemonicToAccount } from 'viem/accounts';
|
|
8
|
+
import { base, arbitrum, optimism, mainnet, polygon } from 'viem/chains';
|
|
9
|
+
import { createWallet, importWallet, loadWallet, getWalletAddress, walletExists } from './wallet.js';
|
|
10
|
+
|
|
11
|
+
const BUNGEE_API = 'https://public-backend.bungee.exchange';
|
|
12
|
+
|
|
13
|
+
// Treasury for fee collection (20 bps = 0.2%)
|
|
14
|
+
const FEE_TAKER_ADDRESS = '0x02Bc8c352b58d929Cc3D60545511872c85F30650';
|
|
15
|
+
const FEE_BPS = '20'; // 0.2%
|
|
16
|
+
|
|
17
|
+
const CHAINS: Record<number, typeof base> = {
|
|
18
|
+
1: mainnet,
|
|
19
|
+
8453: base,
|
|
20
|
+
42161: arbitrum,
|
|
21
|
+
10: optimism,
|
|
22
|
+
137: polygon,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ============ Token List ============
|
|
26
|
+
|
|
27
|
+
export interface TokenBalance {
|
|
28
|
+
chainId: number;
|
|
29
|
+
address: string;
|
|
30
|
+
name: string;
|
|
31
|
+
symbol: string;
|
|
32
|
+
decimals: number;
|
|
33
|
+
balance: string;
|
|
34
|
+
balanceInUsd: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getTokenBalances(userAddress: string): Promise<TokenBalance[]> {
|
|
38
|
+
const url = `${BUNGEE_API}/api/v1/tokens/list?userAddress=${userAddress}`;
|
|
39
|
+
const response = await fetch(url);
|
|
40
|
+
const data = await response.json() as { success: boolean; result: Record<string, TokenBalance[]> };
|
|
41
|
+
|
|
42
|
+
if (!data.success) {
|
|
43
|
+
throw new Error('Failed to fetch token list');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const allTokens: TokenBalance[] = [];
|
|
47
|
+
for (const [chainId, tokens] of Object.entries(data.result)) {
|
|
48
|
+
for (const token of tokens) {
|
|
49
|
+
if (token.balanceInUsd > 0) {
|
|
50
|
+
allTokens.push(token);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return allTokens.sort((a, b) => b.balanceInUsd - a.balanceInUsd);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatPortfolio(tokens: TokenBalance[]): string {
|
|
59
|
+
if (tokens.length === 0) return 'No tokens found.';
|
|
60
|
+
|
|
61
|
+
const totalUsd = tokens.reduce((sum, t) => sum + t.balanceInUsd, 0);
|
|
62
|
+
const lines = tokens.map(t => {
|
|
63
|
+
const amount = (Number(t.balance) / Math.pow(10, t.decimals)).toFixed(t.decimals > 6 ? 6 : 2);
|
|
64
|
+
return ` ${t.symbol} (${getChainName(t.chainId)}): ${amount} ($${t.balanceInUsd.toFixed(2)})`;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return `Portfolio: $${totalUsd.toFixed(2)}\n${lines.join('\n')}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getChainName(chainId: number): string {
|
|
71
|
+
const names: Record<number, string> = {
|
|
72
|
+
1: 'Ethereum',
|
|
73
|
+
8453: 'Base',
|
|
74
|
+
42161: 'Arbitrum',
|
|
75
|
+
10: 'Optimism',
|
|
76
|
+
137: 'Polygon',
|
|
77
|
+
};
|
|
78
|
+
return names[chainId] || `Chain ${chainId}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ============ Bungee Auto ============
|
|
82
|
+
|
|
83
|
+
interface QuoteResult {
|
|
84
|
+
quoteId: string;
|
|
85
|
+
requestType: string;
|
|
86
|
+
witness: any;
|
|
87
|
+
signTypedData: any;
|
|
88
|
+
approvalData: any;
|
|
89
|
+
txData: { to: string; data: string; value: string; chainId: number } | null;
|
|
90
|
+
userOp: string | null; // 'tx' for native tokens, null for permit2
|
|
91
|
+
requestHash: string | null; // For native token flow
|
|
92
|
+
inputAmount: string;
|
|
93
|
+
outputAmount: string;
|
|
94
|
+
outputToken: string;
|
|
95
|
+
originChain: number;
|
|
96
|
+
destChain: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function getQuote(params: {
|
|
100
|
+
userAddress: string;
|
|
101
|
+
receiverAddress?: string;
|
|
102
|
+
originChainId: number;
|
|
103
|
+
destinationChainId: number;
|
|
104
|
+
inputToken: string;
|
|
105
|
+
outputToken: string;
|
|
106
|
+
inputAmount: string;
|
|
107
|
+
}): Promise<QuoteResult> {
|
|
108
|
+
const quoteParams = new URLSearchParams({
|
|
109
|
+
userAddress: params.userAddress,
|
|
110
|
+
receiverAddress: params.receiverAddress || params.userAddress,
|
|
111
|
+
originChainId: String(params.originChainId),
|
|
112
|
+
destinationChainId: String(params.destinationChainId),
|
|
113
|
+
inputToken: params.inputToken,
|
|
114
|
+
outputToken: params.outputToken,
|
|
115
|
+
inputAmount: params.inputAmount,
|
|
116
|
+
feeTakerAddress: FEE_TAKER_ADDRESS,
|
|
117
|
+
feeBps: FEE_BPS,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const url = `${BUNGEE_API}/api/v1/bungee/quote?${quoteParams}`;
|
|
121
|
+
const response = await fetch(url);
|
|
122
|
+
const data = await response.json() as any;
|
|
123
|
+
const serverReqId = response.headers.get('server-req-id');
|
|
124
|
+
|
|
125
|
+
if (!data.success) {
|
|
126
|
+
throw new Error(`Quote error: ${data.message}. server-req-id: ${serverReqId}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!data.result.autoRoute) {
|
|
130
|
+
throw new Error(`No autoRoute available. server-req-id: ${serverReqId}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const auto = data.result.autoRoute;
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
quoteId: auto.quoteId,
|
|
137
|
+
requestType: auto.requestType,
|
|
138
|
+
witness: auto.signTypedData?.values?.witness || null,
|
|
139
|
+
signTypedData: auto.signTypedData,
|
|
140
|
+
approvalData: auto.approvalData,
|
|
141
|
+
txData: auto.txData || null,
|
|
142
|
+
userOp: auto.userOp || null,
|
|
143
|
+
requestHash: auto.requestHash || null,
|
|
144
|
+
inputAmount: params.inputAmount,
|
|
145
|
+
outputAmount: auto.output?.amount || auto.outputAmount,
|
|
146
|
+
outputToken: params.outputToken,
|
|
147
|
+
originChain: params.originChainId,
|
|
148
|
+
destChain: params.destinationChainId,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function executeSwap(
|
|
153
|
+
privateKeyOrMnemonic: string,
|
|
154
|
+
quote: QuoteResult,
|
|
155
|
+
onStatus?: (status: string) => void
|
|
156
|
+
): Promise<{ requestHash: string; status: any }> {
|
|
157
|
+
const log = onStatus || console.log;
|
|
158
|
+
|
|
159
|
+
// Handle mnemonic vs private key
|
|
160
|
+
let account;
|
|
161
|
+
if (privateKeyOrMnemonic.includes(' ')) {
|
|
162
|
+
account = mnemonicToAccount(privateKeyOrMnemonic);
|
|
163
|
+
} else {
|
|
164
|
+
const normalizedKey = privateKeyOrMnemonic.startsWith('0x') ? privateKeyOrMnemonic : `0x${privateKeyOrMnemonic}`;
|
|
165
|
+
account = privateKeyToAccount(normalizedKey as `0x${string}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const chain = CHAINS[quote.originChain];
|
|
169
|
+
if (!chain) throw new Error(`Unsupported chain: ${quote.originChain}`);
|
|
170
|
+
|
|
171
|
+
const publicClient = createPublicClient({ chain, transport: http() });
|
|
172
|
+
const walletClient = createWalletClient({ account, chain, transport: http() });
|
|
173
|
+
|
|
174
|
+
// Native token flow (userOp = 'tx')
|
|
175
|
+
if (quote.userOp === 'tx' && quote.txData) {
|
|
176
|
+
return executeNativeTokenSwap(publicClient, walletClient, quote, log);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ERC20 token flow (permit2 signature)
|
|
180
|
+
return executePermit2Swap(publicClient, walletClient, account, quote, log);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function executeNativeTokenSwap(
|
|
184
|
+
publicClient: any,
|
|
185
|
+
walletClient: any,
|
|
186
|
+
quote: QuoteResult,
|
|
187
|
+
log: (msg: string) => void
|
|
188
|
+
): Promise<{ requestHash: string; status: any }> {
|
|
189
|
+
if (!quote.txData) throw new Error('Missing txData for native token swap');
|
|
190
|
+
if (!quote.requestHash) throw new Error('Missing requestHash for native token swap');
|
|
191
|
+
|
|
192
|
+
log('đ Native token swap (direct tx)');
|
|
193
|
+
|
|
194
|
+
// Step 1: Send the transaction
|
|
195
|
+
log('đ¤ Step 1/2: Sending transaction...');
|
|
196
|
+
const hash = await walletClient.sendTransaction({
|
|
197
|
+
to: quote.txData.to as `0x${string}`,
|
|
198
|
+
data: quote.txData.data as `0x${string}`,
|
|
199
|
+
value: BigInt(quote.txData.value),
|
|
200
|
+
});
|
|
201
|
+
log(` TX hash: ${hash}`);
|
|
202
|
+
|
|
203
|
+
// Wait for confirmation
|
|
204
|
+
log(' Waiting for confirmation...');
|
|
205
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
206
|
+
log(' â
Transaction confirmed');
|
|
207
|
+
|
|
208
|
+
// Step 2: Poll for completion using requestHash from quote
|
|
209
|
+
log('âŗ Step 2/2: Waiting for Bungee completion...');
|
|
210
|
+
const finalStatus = await pollStatus(quote.requestHash, log);
|
|
211
|
+
|
|
212
|
+
return { requestHash: quote.requestHash, status: finalStatus };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function executePermit2Swap(
|
|
216
|
+
publicClient: any,
|
|
217
|
+
walletClient: any,
|
|
218
|
+
account: any,
|
|
219
|
+
quote: QuoteResult,
|
|
220
|
+
log: (msg: string) => void
|
|
221
|
+
): Promise<{ requestHash: string; status: any }> {
|
|
222
|
+
log('đ ERC20 token swap (permit2)');
|
|
223
|
+
|
|
224
|
+
// Step 1: Handle approval if needed
|
|
225
|
+
if (quote.approvalData?.tokenAddress) {
|
|
226
|
+
log('â
Step 1/4: Checking token approval...');
|
|
227
|
+
await handleApproval(publicClient, walletClient, account.address, quote.approvalData, log);
|
|
228
|
+
} else {
|
|
229
|
+
log('â
Step 1/4: No approval needed');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Step 2: Sign the typed data
|
|
233
|
+
if (!quote.signTypedData || !quote.witness) {
|
|
234
|
+
throw new Error('Missing signTypedData or witness - cannot proceed');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
log('âī¸ Step 2/4: Signing permit...');
|
|
238
|
+
const signature = await account.signTypedData({
|
|
239
|
+
types: quote.signTypedData.types,
|
|
240
|
+
primaryType: 'PermitWitnessTransferFrom',
|
|
241
|
+
message: quote.signTypedData.values,
|
|
242
|
+
domain: quote.signTypedData.domain,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Step 3: Submit to Bungee
|
|
246
|
+
log('đ¤ Step 3/4: Submitting to Bungee...');
|
|
247
|
+
const submitPayload = {
|
|
248
|
+
requestType: quote.requestType,
|
|
249
|
+
request: quote.witness,
|
|
250
|
+
userSignature: signature,
|
|
251
|
+
quoteId: quote.quoteId,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const submitResponse = await fetch(`${BUNGEE_API}/api/v1/bungee/submit`, {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
headers: { 'Content-Type': 'application/json' },
|
|
257
|
+
body: JSON.stringify(submitPayload),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const submitData = await submitResponse.json() as any;
|
|
261
|
+
if (!submitData.success) {
|
|
262
|
+
throw new Error(`Submit error: ${submitData.error?.message || JSON.stringify(submitData)}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const requestHash = submitData.result.requestHash;
|
|
266
|
+
log(` Request hash: ${requestHash}`);
|
|
267
|
+
|
|
268
|
+
// Step 4: Poll for completion
|
|
269
|
+
log('âŗ Step 4/4: Waiting for completion...');
|
|
270
|
+
const finalStatus = await pollStatus(requestHash, log);
|
|
271
|
+
|
|
272
|
+
return { requestHash, status: finalStatus };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function handleApproval(
|
|
276
|
+
publicClient: any,
|
|
277
|
+
walletClient: any,
|
|
278
|
+
userAddress: string,
|
|
279
|
+
approvalData: any,
|
|
280
|
+
log: (msg: string) => void
|
|
281
|
+
) {
|
|
282
|
+
const erc20Abi = [
|
|
283
|
+
{
|
|
284
|
+
inputs: [
|
|
285
|
+
{ name: 'owner', type: 'address' },
|
|
286
|
+
{ name: 'spender', type: 'address' },
|
|
287
|
+
],
|
|
288
|
+
name: 'allowance',
|
|
289
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
290
|
+
stateMutability: 'view',
|
|
291
|
+
type: 'function',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
inputs: [
|
|
295
|
+
{ name: 'spender', type: 'address' },
|
|
296
|
+
{ name: 'amount', type: 'uint256' },
|
|
297
|
+
],
|
|
298
|
+
name: 'approve',
|
|
299
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
300
|
+
stateMutability: 'nonpayable',
|
|
301
|
+
type: 'function',
|
|
302
|
+
},
|
|
303
|
+
] as const;
|
|
304
|
+
|
|
305
|
+
const spender = approvalData.spenderAddress === '0'
|
|
306
|
+
? '0x000000000022D473030F116dDEE9F6B43aC78BA3' // Permit2
|
|
307
|
+
: approvalData.spenderAddress;
|
|
308
|
+
|
|
309
|
+
const currentAllowance = await publicClient.readContract({
|
|
310
|
+
address: approvalData.tokenAddress,
|
|
311
|
+
abi: erc20Abi,
|
|
312
|
+
functionName: 'allowance',
|
|
313
|
+
args: [userAddress, spender],
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (BigInt(currentAllowance) >= BigInt(approvalData.amount)) {
|
|
317
|
+
log(' Already approved.');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
log(' Sending approval...');
|
|
322
|
+
const hash = await walletClient.writeContract({
|
|
323
|
+
address: approvalData.tokenAddress,
|
|
324
|
+
abi: erc20Abi,
|
|
325
|
+
functionName: 'approve',
|
|
326
|
+
args: [spender, approvalData.amount],
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
log(` Approval TX: ${hash}`);
|
|
330
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
331
|
+
log(' Approval confirmed.');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function pollStatus(
|
|
335
|
+
requestHash: string,
|
|
336
|
+
log: (msg: string) => void,
|
|
337
|
+
maxAttempts = 60
|
|
338
|
+
): Promise<any> {
|
|
339
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
340
|
+
const response = await fetch(`${BUNGEE_API}/api/v1/bungee/status?requestHash=${requestHash}`);
|
|
341
|
+
const data = await response.json() as any;
|
|
342
|
+
|
|
343
|
+
if (!data.success) {
|
|
344
|
+
throw new Error(`Status error: ${data.error?.message}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const status = data.result[0];
|
|
348
|
+
const code = status?.bungeeStatusCode;
|
|
349
|
+
|
|
350
|
+
if (code === 3 || code === 4) {
|
|
351
|
+
log(` â
Complete! Dest TX: ${status.destinationData?.txHash}`);
|
|
352
|
+
return status;
|
|
353
|
+
}
|
|
354
|
+
if (code === 5) throw new Error('Request expired');
|
|
355
|
+
if (code === 6) throw new Error('Request cancelled');
|
|
356
|
+
if (code === 7) throw new Error('Request refunded');
|
|
357
|
+
|
|
358
|
+
if (i % 6 === 0 && i > 0) {
|
|
359
|
+
log(` Still waiting... (${i * 5}s)`);
|
|
360
|
+
}
|
|
361
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
throw new Error('Polling timed out');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ============ CLI ============
|
|
368
|
+
|
|
369
|
+
async function main() {
|
|
370
|
+
const args = process.argv.slice(2);
|
|
371
|
+
const [action] = args;
|
|
372
|
+
|
|
373
|
+
// Wallet commands
|
|
374
|
+
if (action === 'wallet-create') {
|
|
375
|
+
if (walletExists()) {
|
|
376
|
+
console.error('â Wallet already exists. Delete .wallet.enc to create a new one.');
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const { mnemonic, address } = createWallet();
|
|
381
|
+
console.log(`
|
|
382
|
+
đ SovereignClaw Treasury Created
|
|
383
|
+
|
|
384
|
+
â ī¸ CRITICAL: Save this seed phrase NOW.
|
|
385
|
+
It will NEVER be shown again.
|
|
386
|
+
|
|
387
|
+
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
388
|
+
${mnemonic}
|
|
389
|
+
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
390
|
+
|
|
391
|
+
đ Address: ${address}
|
|
392
|
+
|
|
393
|
+
After you've saved the seed phrase securely, you can use:
|
|
394
|
+
npx tsx bungee.ts portfolio
|
|
395
|
+
npx tsx bungee.ts swap ...
|
|
396
|
+
`);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (action === 'wallet-import') {
|
|
401
|
+
const keyOrMnemonic = args.slice(1).join(' ');
|
|
402
|
+
if (!keyOrMnemonic) {
|
|
403
|
+
console.error('Usage: npx tsx bungee.ts wallet-import <private-key-or-mnemonic>');
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const address = importWallet(keyOrMnemonic);
|
|
408
|
+
console.log(`â
Wallet imported: ${address}`);
|
|
409
|
+
console.log('â ī¸ Delete the message/command containing your key!');
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (action === 'wallet-address') {
|
|
414
|
+
const address = getWalletAddress();
|
|
415
|
+
if (!address) {
|
|
416
|
+
console.error('No wallet found. Run: npx tsx bungee.ts wallet-create');
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
console.log(address);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (action === 'portfolio') {
|
|
424
|
+
const address = args[1] || getWalletAddress() || process.env.USER_ADDRESS;
|
|
425
|
+
if (!address) {
|
|
426
|
+
console.error('Usage: npx tsx bungee.ts portfolio [address]');
|
|
427
|
+
console.error('Or create a wallet first: npx tsx bungee.ts wallet-create');
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
const tokens = await getTokenBalances(address);
|
|
431
|
+
console.log(formatPortfolio(tokens));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (action === 'status') {
|
|
436
|
+
const requestHash = args[1];
|
|
437
|
+
if (!requestHash) {
|
|
438
|
+
console.error('Usage: npx tsx bungee.ts status <requestHash>');
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const response = await fetch(`${BUNGEE_API}/api/v1/bungee/status?requestHash=${requestHash}`);
|
|
443
|
+
const data = await response.json() as any;
|
|
444
|
+
|
|
445
|
+
if (!data.success) {
|
|
446
|
+
console.error('Error:', data.error?.message || 'Unknown error');
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const status = data.result[0];
|
|
451
|
+
const codes: Record<number, string> = {
|
|
452
|
+
1: 'âŗ PENDING',
|
|
453
|
+
2: 'đ IN PROGRESS',
|
|
454
|
+
3: 'â
COMPLETED',
|
|
455
|
+
4: 'â
COMPLETED (partial)',
|
|
456
|
+
5: 'â EXPIRED',
|
|
457
|
+
6: 'â CANCELLED',
|
|
458
|
+
7: 'âŠī¸ REFUNDED',
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
console.log(`Status: ${codes[status?.bungeeStatusCode] || 'UNKNOWN'}`);
|
|
462
|
+
if (status?.originData?.txHash) {
|
|
463
|
+
console.log(`Origin TX: ${status.originData.txHash}`);
|
|
464
|
+
}
|
|
465
|
+
if (status?.destinationData?.txHash) {
|
|
466
|
+
console.log(`Dest TX: ${status.destinationData.txHash}`);
|
|
467
|
+
}
|
|
468
|
+
console.log(`\nSocketScan: https://socketscan.io/tx/${requestHash}`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (action === 'quote') {
|
|
473
|
+
// quote <originChain> <destChain> <inputToken> <outputToken> <amount> <userAddress>
|
|
474
|
+
const [, originChain, destChain, inputToken, outputToken, amount, userAddress] = args;
|
|
475
|
+
if (!userAddress) {
|
|
476
|
+
console.error('Usage: npx tsx bungee.ts quote <originChain> <destChain> <inputToken> <outputToken> <amount> <userAddress>');
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
const quote = await getQuote({
|
|
480
|
+
userAddress,
|
|
481
|
+
originChainId: parseInt(originChain),
|
|
482
|
+
destinationChainId: parseInt(destChain),
|
|
483
|
+
inputToken,
|
|
484
|
+
outputToken,
|
|
485
|
+
inputAmount: amount,
|
|
486
|
+
});
|
|
487
|
+
console.log('Quote:', JSON.stringify(quote, null, 2));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (action === 'swap') {
|
|
492
|
+
// Try stored wallet first, then env var
|
|
493
|
+
const wallet = loadWallet();
|
|
494
|
+
const privateKey = wallet?.privateKey || process.env.PRIVATE_KEY;
|
|
495
|
+
|
|
496
|
+
if (!privateKey) {
|
|
497
|
+
console.error('No wallet found. Either:');
|
|
498
|
+
console.error(' 1. Create wallet: npx tsx bungee.ts wallet-create');
|
|
499
|
+
console.error(' 2. Set PRIVATE_KEY env var');
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// swap <originChain> <destChain> <inputToken> <outputToken> <amount>
|
|
504
|
+
const [, originChain, destChain, inputToken, outputToken, amount] = args;
|
|
505
|
+
|
|
506
|
+
// Handle mnemonic vs private key
|
|
507
|
+
let account;
|
|
508
|
+
if (privateKey.includes(' ')) {
|
|
509
|
+
account = mnemonicToAccount(privateKey);
|
|
510
|
+
} else {
|
|
511
|
+
const normalized = privateKey.startsWith('0x') ? privateKey as `0x${string}` : `0x${privateKey}`;
|
|
512
|
+
account = privateKeyToAccount(normalized);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
console.log('Getting quote...');
|
|
516
|
+
const quote = await getQuote({
|
|
517
|
+
userAddress: account.address,
|
|
518
|
+
originChainId: parseInt(originChain),
|
|
519
|
+
destinationChainId: parseInt(destChain),
|
|
520
|
+
inputToken,
|
|
521
|
+
outputToken,
|
|
522
|
+
inputAmount: amount,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
console.log('Executing swap...');
|
|
526
|
+
const result = await executeSwap(privateKey, quote);
|
|
527
|
+
console.log('Done!', result);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
console.log(`
|
|
532
|
+
SovereignClaw â Economic Sovereignty for AI Agents
|
|
533
|
+
|
|
534
|
+
Wallet:
|
|
535
|
+
wallet-create Create new wallet (shows seed phrase ONCE)
|
|
536
|
+
wallet-import <key|phrase> Import existing wallet
|
|
537
|
+
wallet-address Show wallet address
|
|
538
|
+
|
|
539
|
+
Trading:
|
|
540
|
+
portfolio [address] View cross-chain portfolio
|
|
541
|
+
quote <params...> Get a swap quote
|
|
542
|
+
swap <origin> <dest> <in> <out> <amount> Execute a swap
|
|
543
|
+
status <requestHash> Check transaction status
|
|
544
|
+
|
|
545
|
+
Examples:
|
|
546
|
+
npx tsx bungee.ts wallet-create
|
|
547
|
+
npx tsx bungee.ts portfolio
|
|
548
|
+
npx tsx bungee.ts swap 8453 8453 0xEeee...EEEE 0x833589...02913 1000000000000000
|
|
549
|
+
npx tsx bungee.ts status 0xa6b977a6d65f2be870bd7b2b1464d15759a69aa4b321bffafa1f8cbf19343c58
|
|
550
|
+
|
|
551
|
+
Environment:
|
|
552
|
+
WALLET_KEY - Encryption key for stored wallet (optional)
|
|
553
|
+
PRIVATE_KEY - Direct private key (alternative to wallet-create)
|
|
554
|
+
`);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
main().catch(console.error);
|