@t2000/cli 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,1233 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/program.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/output.ts
7
+ import pc from "picocolors";
8
+ var jsonMode = false;
9
+ function setJsonMode(enabled) {
10
+ jsonMode = enabled;
11
+ }
12
+ function isJsonMode() {
13
+ return jsonMode;
14
+ }
15
+ function printJson(data) {
16
+ console.log(JSON.stringify(data, null, 2));
17
+ }
18
+ function printSuccess(message) {
19
+ if (jsonMode) return;
20
+ console.log(` ${pc.green("\u2713")} ${message}`);
21
+ }
22
+ function printError(message) {
23
+ if (jsonMode) return;
24
+ console.error(` ${pc.red("\u2717")} ${message}`);
25
+ }
26
+ function printWarning(message) {
27
+ if (jsonMode) return;
28
+ console.log(` ${pc.yellow("\u26A0")} ${message}`);
29
+ }
30
+ function printInfo(message) {
31
+ if (jsonMode) return;
32
+ console.log(` ${pc.dim(message)}`);
33
+ }
34
+ function printHeader(title) {
35
+ if (jsonMode) return;
36
+ console.log();
37
+ console.log(` ${pc.bold(title)}`);
38
+ console.log();
39
+ }
40
+ function printKeyValue(key, value, indent = 2) {
41
+ if (jsonMode) return;
42
+ const pad = " ".repeat(indent);
43
+ console.log(`${pad}${pc.dim(key + ":")} ${value}`);
44
+ }
45
+ function printBlank() {
46
+ if (jsonMode) return;
47
+ console.log();
48
+ }
49
+ function printDivider(width = 53) {
50
+ if (jsonMode) return;
51
+ console.log(` ${pc.dim("\u2500".repeat(width))}`);
52
+ }
53
+ function printLine(text) {
54
+ if (jsonMode) return;
55
+ console.log(` ${text}`);
56
+ }
57
+ function printSeparator() {
58
+ if (jsonMode) return;
59
+ console.log(` ${pc.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
60
+ }
61
+ function explorerUrl(txHash, network = "mainnet") {
62
+ const base = network === "testnet" ? "https://suiscan.xyz/testnet/tx" : "https://suiscan.xyz/mainnet/tx";
63
+ const suffix = "";
64
+ return `${base}/${txHash}${suffix}`;
65
+ }
66
+ function handleError(error) {
67
+ if (jsonMode) {
68
+ const data = error instanceof Error && "toJSON" in error ? error.toJSON() : { error: "UNKNOWN", message: String(error) };
69
+ printJson(data);
70
+ } else {
71
+ const msg = error instanceof Error ? error.message : String(error);
72
+ printError(msg);
73
+ }
74
+ process.exit(1);
75
+ }
76
+
77
+ // src/commands/init.ts
78
+ import pc2 from "picocolors";
79
+ import { T2000 } from "@t2000/sdk";
80
+
81
+ // src/prompts.ts
82
+ import { password, confirm } from "@inquirer/prompts";
83
+ async function askPassphrase(message = "Enter passphrase:") {
84
+ const value = await password({ message });
85
+ if (!value || value.length < 8) {
86
+ throw new Error("Passphrase must be at least 8 characters");
87
+ }
88
+ return value;
89
+ }
90
+ async function askPassphraseConfirm() {
91
+ const pass = await password({ message: "Create passphrase (min 8 chars):" });
92
+ if (!pass || pass.length < 8) {
93
+ throw new Error("Passphrase must be at least 8 characters");
94
+ }
95
+ const confirm_ = await password({ message: "Confirm passphrase:" });
96
+ if (pass !== confirm_) {
97
+ throw new Error("Passphrases do not match");
98
+ }
99
+ return pass;
100
+ }
101
+ async function askConfirm(message) {
102
+ return confirm({ message });
103
+ }
104
+ function getPassphraseFromEnv() {
105
+ return process.env.T2000_PASSPHRASE;
106
+ }
107
+
108
+ // src/commands/init.ts
109
+ function registerInit(program2) {
110
+ program2.command("init").description("Create a new agent wallet").option("--name <name>", "Agent name").option("--key <path>", "Key file path").option("--no-sponsor", "Skip gas sponsorship").action(async (opts) => {
111
+ try {
112
+ const passphrase = getPassphraseFromEnv() ?? await askPassphraseConfirm();
113
+ if (!isJsonMode()) {
114
+ printBlank();
115
+ printInfo("Creating agent wallet...");
116
+ }
117
+ const { agent, address, sponsored } = await T2000.init({
118
+ passphrase,
119
+ keyPath: opts.key,
120
+ name: opts.name,
121
+ sponsored: opts.sponsor
122
+ });
123
+ if (isJsonMode()) {
124
+ printJson({ address, sponsored });
125
+ return;
126
+ }
127
+ printSuccess("Keypair generated");
128
+ printSuccess(`Network ${pc2.dim("Sui mainnet")}`);
129
+ printSuccess(`Gas sponsorship ${pc2.dim(sponsored ? "enabled" : "disabled")}`);
130
+ printBlank();
131
+ printInfo("Setting up accounts...");
132
+ printLine(
133
+ `${pc2.green("\u2713")} Checking ${pc2.green("\u2713")} Savings ${pc2.green("\u2713")} Credit ${pc2.green("\u2713")} Exchange ${pc2.green("\u2713")} 402 Pay`
134
+ );
135
+ printBlank();
136
+ printLine(`\u{1F389} ${pc2.green("Bank account created")}`);
137
+ printKeyValue("Address", pc2.yellow(address));
138
+ printBlank();
139
+ printLine(`Deposit USDC on Sui network only.`);
140
+ printDivider();
141
+ printBlank();
142
+ printLine(`${pc2.cyan("t2000 balance")} check for funds`);
143
+ printLine(`${pc2.cyan("t2000 save all")} start earning yield`);
144
+ printLine(`${pc2.cyan("t2000 address")} show address again`);
145
+ printBlank();
146
+ } catch (error) {
147
+ handleError(error);
148
+ }
149
+ });
150
+ }
151
+
152
+ // src/commands/send.ts
153
+ import { T2000 as T20002 } from "@t2000/sdk";
154
+ import { truncateAddress, formatUsd } from "@t2000/sdk";
155
+ function registerSend(program2) {
156
+ program2.command("send <amount> <asset> [to_keyword] <address>").description("Send USDC (or other asset) to an address").option("--key <path>", "Key file path").action(async (amount, asset, toOrAddress, address, opts) => {
157
+ try {
158
+ const recipient = address ?? toOrAddress;
159
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
160
+ const agent = await T20002.create({ passphrase, keyPath: opts.key });
161
+ const result = await agent.send({
162
+ to: recipient,
163
+ amount: parseFloat(amount),
164
+ asset: asset.toUpperCase()
165
+ });
166
+ if (isJsonMode()) {
167
+ printJson(result);
168
+ return;
169
+ }
170
+ printBlank();
171
+ printSuccess(`Sent ${formatUsd(result.amount)} ${asset.toUpperCase()} \u2192 ${truncateAddress(result.to)}`);
172
+ printKeyValue("Gas", `${result.gasCost.toFixed(4)} ${result.gasCostUnit} (${result.gasMethod})`);
173
+ printKeyValue("Balance", formatUsd(result.balance.available) + " USDC");
174
+ printKeyValue("Tx", explorerUrl(result.tx));
175
+ printBlank();
176
+ } catch (error) {
177
+ handleError(error);
178
+ }
179
+ });
180
+ }
181
+
182
+ // src/commands/balance.ts
183
+ import pc3 from "picocolors";
184
+ import { T2000 as T20003, formatUsd as formatUsd2, getRates } from "@t2000/sdk";
185
+ async function fetchLimits(agent) {
186
+ const [maxWithdraw, maxBorrow, hf] = await Promise.all([
187
+ agent.maxWithdraw(),
188
+ agent.maxBorrow(),
189
+ agent.healthFactor()
190
+ ]);
191
+ return {
192
+ maxWithdraw: maxWithdraw.maxAmount.toFixed(2),
193
+ maxBorrow: maxBorrow.maxAmount.toFixed(2),
194
+ healthFactor: hf.borrowed > 0 ? hf.healthFactor : null
195
+ };
196
+ }
197
+ function registerBalance(program2) {
198
+ program2.command("balance").description("Show wallet balance").option("--key <path>", "Key file path").option("--show-limits", "Include maxWithdraw, maxBorrow, and health factor").action(async (opts) => {
199
+ try {
200
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
201
+ const agent = await T20003.create({ passphrase, keyPath: opts.key });
202
+ const bal = await agent.balance();
203
+ const limits = opts.showLimits ? await fetchLimits(agent) : void 0;
204
+ if (isJsonMode()) {
205
+ const output = limits ? { ...bal, limits } : bal;
206
+ printJson(output);
207
+ return;
208
+ }
209
+ let apyStr = "";
210
+ if (bal.savings > 0) {
211
+ try {
212
+ const rates = await getRates(agent.suiClient);
213
+ const apy = rates.USDC.saveApy;
214
+ apyStr = ` ${pc3.dim(`(earning ${apy.toFixed(2)}% APY)`)}`;
215
+ } catch {
216
+ }
217
+ }
218
+ printBlank();
219
+ printKeyValue("Available", `${formatUsd2(bal.available)} USDC ${pc3.dim("(checking \u2014 spendable)")}`);
220
+ printKeyValue("Savings", `${formatUsd2(bal.savings)} USDC${apyStr}`);
221
+ printKeyValue("Gas", `${bal.gasReserve.sui.toFixed(2)} SUI ${pc3.dim(`(~${formatUsd2(bal.gasReserve.usdEquiv)})`)}`);
222
+ printSeparator();
223
+ printKeyValue("Total", `${formatUsd2(bal.total)} USDC`);
224
+ if (bal.savings > 0 && apyStr) {
225
+ try {
226
+ const rates = await getRates(agent.suiClient);
227
+ const dailyEarning = bal.savings * rates.USDC.saveApy / 100 / 365;
228
+ if (dailyEarning > 1e-3) {
229
+ printLine(pc3.dim(`Earning ~${formatUsd2(dailyEarning)}/day`));
230
+ }
231
+ } catch {
232
+ }
233
+ }
234
+ if (limits) {
235
+ printBlank();
236
+ printHeader("Limits");
237
+ printKeyValue("Max withdraw", `${formatUsd2(Number(limits.maxWithdraw))} USDC`, 4);
238
+ printKeyValue("Max borrow", `${formatUsd2(Number(limits.maxBorrow))} USDC`, 4);
239
+ const hfDisplay = limits.healthFactor !== null ? limits.healthFactor.toFixed(2) : `${pc3.green("\u221E")} ${pc3.dim("(no active loan)")}`;
240
+ printKeyValue("Health factor", hfDisplay, 4);
241
+ }
242
+ printBlank();
243
+ } catch (error) {
244
+ handleError(error);
245
+ }
246
+ });
247
+ }
248
+
249
+ // src/commands/address.ts
250
+ import { T2000 as T20004 } from "@t2000/sdk";
251
+ function registerAddress(program2) {
252
+ program2.command("address").description("Show wallet address").option("--key <path>", "Key file path").action(async (opts) => {
253
+ try {
254
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
255
+ const agent = await T20004.create({ passphrase, keyPath: opts.key });
256
+ if (isJsonMode()) {
257
+ printJson({ address: agent.address() });
258
+ return;
259
+ }
260
+ printBlank();
261
+ printKeyValue("Address", agent.address());
262
+ printBlank();
263
+ } catch (error) {
264
+ handleError(error);
265
+ }
266
+ });
267
+ }
268
+
269
+ // src/commands/deposit.ts
270
+ import { T2000 as T20005 } from "@t2000/sdk";
271
+ function registerDeposit(program2) {
272
+ program2.command("deposit").description("Show funding instructions").option("--key <path>", "Key file path").action(async (opts) => {
273
+ try {
274
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
275
+ const agent = await T20005.create({ passphrase, keyPath: opts.key });
276
+ const info = await agent.deposit();
277
+ if (isJsonMode()) {
278
+ printJson(info);
279
+ return;
280
+ }
281
+ printHeader("Fund your wallet");
282
+ console.log(info.instructions);
283
+ printBlank();
284
+ } catch (error) {
285
+ handleError(error);
286
+ }
287
+ });
288
+ }
289
+
290
+ // src/commands/history.ts
291
+ import { T2000 as T20006, truncateAddress as truncateAddress2 } from "@t2000/sdk";
292
+ function registerHistory(program2) {
293
+ program2.command("history").description("Show transaction history").option("--limit <n>", "Number of transactions", "20").option("--key <path>", "Key file path").action(async (opts) => {
294
+ try {
295
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
296
+ const agent = await T20006.create({ passphrase, keyPath: opts.key });
297
+ const txns = await agent.history({ limit: parseInt(opts.limit, 10) });
298
+ if (isJsonMode()) {
299
+ printJson(txns);
300
+ return;
301
+ }
302
+ printHeader("Transaction History");
303
+ if (txns.length === 0) {
304
+ console.log(" No transactions yet.");
305
+ } else {
306
+ for (const tx of txns) {
307
+ const time = tx.timestamp ? new Date(tx.timestamp).toLocaleString() : "unknown";
308
+ const gas = tx.gasMethod ? ` (${tx.gasMethod})` : "";
309
+ console.log(` ${truncateAddress2(tx.digest)} ${tx.action}${gas} ${time}`);
310
+ }
311
+ }
312
+ printBlank();
313
+ } catch (error) {
314
+ handleError(error);
315
+ }
316
+ });
317
+ }
318
+
319
+ // src/commands/exportKey.ts
320
+ import { T2000 as T20007 } from "@t2000/sdk";
321
+ function registerExport(program2) {
322
+ program2.command("export").description("Export private key (raw Ed25519 hex)").option("--key <path>", "Key file path").option("--yes", "Skip confirmation").action(async (opts) => {
323
+ try {
324
+ if (!opts.yes && !isJsonMode()) {
325
+ const proceed = await askConfirm(
326
+ "WARNING: This will display your raw private key. Anyone with this key controls your wallet. Continue?"
327
+ );
328
+ if (!proceed) return;
329
+ }
330
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
331
+ const agent = await T20007.create({ passphrase, keyPath: opts.key });
332
+ const hex = agent.exportKey();
333
+ if (isJsonMode()) {
334
+ printJson({ privateKey: hex, format: "ed25519_hex" });
335
+ return;
336
+ }
337
+ printBlank();
338
+ printSuccess("Private key (Ed25519, hex):");
339
+ console.log(` ${hex}`);
340
+ printBlank();
341
+ printInfo("Not a BIP39 mnemonic. Store securely and never share.");
342
+ printBlank();
343
+ } catch (error) {
344
+ handleError(error);
345
+ }
346
+ });
347
+ }
348
+
349
+ // src/commands/importKey.ts
350
+ import { keypairFromPrivateKey, saveKey } from "@t2000/sdk";
351
+ import { password as password2 } from "@inquirer/prompts";
352
+ function registerImport(program2) {
353
+ program2.command("import").description("Import a wallet from private key").option("--key <path>", "Key file path").action(async (opts) => {
354
+ try {
355
+ let privateKey;
356
+ if (process.env.T2000_PRIVATE_KEY) {
357
+ privateKey = process.env.T2000_PRIVATE_KEY;
358
+ } else {
359
+ privateKey = await password2({ message: "Enter private key (hex):" });
360
+ }
361
+ if (!privateKey) throw new Error("Private key is required");
362
+ const passphrase = getPassphraseFromEnv() ?? await askPassphraseConfirm();
363
+ const keypair = keypairFromPrivateKey(privateKey);
364
+ const address = keypair.getPublicKey().toSuiAddress();
365
+ await saveKey(keypair, passphrase, opts.key);
366
+ if (isJsonMode()) {
367
+ printJson({ address, imported: true });
368
+ return;
369
+ }
370
+ printBlank();
371
+ printSuccess("Wallet imported (encrypted)");
372
+ printKeyValue("Address", address);
373
+ printBlank();
374
+ } catch (error) {
375
+ handleError(error);
376
+ }
377
+ });
378
+ }
379
+
380
+ // src/commands/save.ts
381
+ import pc4 from "picocolors";
382
+ import { T2000 as T20009, formatUsd as formatUsd3 } from "@t2000/sdk";
383
+ function registerSave(program2) {
384
+ const action = async (amountStr, opts) => {
385
+ try {
386
+ const amount = amountStr === "all" ? "all" : parseFloat(amountStr);
387
+ if (amount !== "all" && (isNaN(amount) || amount <= 0)) {
388
+ throw new Error('Amount must be a positive number or "all"');
389
+ }
390
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
391
+ const agent = await T20009.create({ passphrase, keyPath: opts.key });
392
+ const globalOpts = program2.optsWithGlobals();
393
+ if (!globalOpts.yes) {
394
+ const label = amount === "all" ? "all available USDC" : `$${amount.toFixed(2)} USDC`;
395
+ const ok = await askConfirm(`Save ${label} to earn yield?`);
396
+ if (!ok) return;
397
+ }
398
+ let gasManagerUsdc = 0;
399
+ agent.on("gasAutoTopUp", (data) => {
400
+ gasManagerUsdc = data.usdcSpent;
401
+ });
402
+ const result = await agent.save({ amount });
403
+ if (isJsonMode()) {
404
+ printJson(result);
405
+ return;
406
+ }
407
+ printBlank();
408
+ if (gasManagerUsdc > 0) {
409
+ printSuccess(`Gas manager: ${pc4.yellow(formatUsd3(gasManagerUsdc))} USDC \u2192 SUI`);
410
+ }
411
+ printSuccess(`Saved ${pc4.yellow(formatUsd3(result.amount))} USDC to NAVI`);
412
+ if (result.fee > 0) {
413
+ const feeRate = (result.fee / result.amount * 100).toFixed(1);
414
+ printSuccess(`Protocol fee: ${pc4.dim(`${formatUsd3(result.fee)} USDC (${feeRate}%)`)}`);
415
+ }
416
+ printSuccess(`Current APY: ${pc4.green(`${result.apy.toFixed(2)}%`)}`);
417
+ const savingsBalance = result.amount - result.fee;
418
+ printSuccess(`Savings balance: ${pc4.yellow(formatUsd3(savingsBalance))} USDC`);
419
+ printKeyValue("Tx", explorerUrl(result.tx));
420
+ printBlank();
421
+ } catch (error) {
422
+ handleError(error);
423
+ }
424
+ };
425
+ program2.command("save").description("Deposit USDC into savings (NAVI Protocol)").argument("<amount>", 'Amount in USDC to save (or "all")').option("--key <path>", "Key file path").action(action);
426
+ program2.command("supply").description("Deposit USDC into savings (alias for save)").argument("<amount>", 'Amount in USDC to save (or "all")').option("--key <path>", "Key file path").action(action);
427
+ }
428
+
429
+ // src/commands/withdraw.ts
430
+ import { T2000 as T200010 } from "@t2000/sdk";
431
+ function registerWithdraw(program2) {
432
+ program2.command("withdraw").description("Withdraw USDC from savings").argument("<amount>", 'Amount in USDC to withdraw (or "all")').option("--key <path>", "Key file path").action(async (amountStr, opts) => {
433
+ try {
434
+ const amount = amountStr === "all" ? "all" : parseFloat(amountStr);
435
+ if (amount !== "all" && (isNaN(amount) || amount <= 0)) {
436
+ throw new Error('Amount must be a positive number or "all"');
437
+ }
438
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
439
+ const agent = await T200010.create({ passphrase, keyPath: opts.key });
440
+ if (amount !== "all") {
441
+ const maxResult = await agent.maxWithdraw();
442
+ if (amount > maxResult.maxAmount) {
443
+ printWarning(`Max safe withdrawal: $${maxResult.maxAmount.toFixed(2)} (HF ${maxResult.currentHF.toFixed(2)} \u2192 ${maxResult.healthFactorAfter.toFixed(2)})`);
444
+ return;
445
+ }
446
+ }
447
+ const globalOpts = program2.optsWithGlobals();
448
+ if (!globalOpts.yes) {
449
+ const label = amount === "all" ? "all savings" : `$${amount.toFixed(2)} USDC`;
450
+ const ok = await askConfirm(`Withdraw ${label} from savings?`);
451
+ if (!ok) return;
452
+ }
453
+ const result = await agent.withdraw({ amount });
454
+ if (isJsonMode()) {
455
+ printJson(result);
456
+ return;
457
+ }
458
+ printBlank();
459
+ printSuccess(`Withdrew $${result.amount.toFixed(2)} USDC`);
460
+ printKeyValue("Tx", explorerUrl(result.tx));
461
+ printBlank();
462
+ } catch (error) {
463
+ handleError(error);
464
+ }
465
+ });
466
+ }
467
+
468
+ // src/commands/borrow.ts
469
+ import { T2000 as T200011 } from "@t2000/sdk";
470
+ function registerBorrow(program2) {
471
+ program2.command("borrow").description("Borrow USDC against savings collateral").argument("<amount>", "Amount in USDC to borrow").option("--key <path>", "Key file path").action(async (amountStr, opts) => {
472
+ try {
473
+ const amount = parseFloat(amountStr);
474
+ if (isNaN(amount) || amount <= 0) {
475
+ throw new Error("Amount must be a positive number");
476
+ }
477
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
478
+ const agent = await T200011.create({ passphrase, keyPath: opts.key });
479
+ const maxResult = await agent.maxBorrow();
480
+ if (amount > maxResult.maxAmount) {
481
+ printWarning(`Max safe borrow: $${maxResult.maxAmount.toFixed(2)} (HF ${maxResult.currentHF.toFixed(2)} \u2192 min 1.5)`);
482
+ return;
483
+ }
484
+ const globalOpts = program2.optsWithGlobals();
485
+ if (!globalOpts.yes) {
486
+ const ok = await askConfirm(`Borrow $${amount.toFixed(2)} USDC?`);
487
+ if (!ok) return;
488
+ }
489
+ const result = await agent.borrow({ amount });
490
+ if (isJsonMode()) {
491
+ printJson(result);
492
+ return;
493
+ }
494
+ printBlank();
495
+ printSuccess(`Borrowed $${amount.toFixed(2)} USDC`);
496
+ printKeyValue("Health Factor", result.healthFactor.toFixed(2));
497
+ printKeyValue("Tx", explorerUrl(result.tx));
498
+ printBlank();
499
+ } catch (error) {
500
+ handleError(error);
501
+ }
502
+ });
503
+ }
504
+
505
+ // src/commands/repay.ts
506
+ import { T2000 as T200012 } from "@t2000/sdk";
507
+ function registerRepay(program2) {
508
+ program2.command("repay").description("Repay borrowed USDC").argument("<amount>", 'Amount in USDC to repay (or "all")').option("--key <path>", "Key file path").action(async (amountStr, opts) => {
509
+ try {
510
+ const amount = amountStr === "all" ? "all" : parseFloat(amountStr);
511
+ if (amount !== "all" && (isNaN(amount) || amount <= 0)) {
512
+ throw new Error('Amount must be a positive number or "all"');
513
+ }
514
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
515
+ const agent = await T200012.create({ passphrase, keyPath: opts.key });
516
+ const globalOpts = program2.optsWithGlobals();
517
+ if (!globalOpts.yes) {
518
+ const label = amount === "all" ? "all outstanding USDC debt" : `$${amount.toFixed(2)} USDC`;
519
+ const ok = await askConfirm(`Repay ${label}?`);
520
+ if (!ok) return;
521
+ }
522
+ const result = await agent.repay({ amount });
523
+ if (isJsonMode()) {
524
+ printJson(result);
525
+ return;
526
+ }
527
+ printBlank();
528
+ printSuccess(`Repaid $${result.amount.toFixed(2)} USDC`);
529
+ printKeyValue("Remaining Debt", `$${result.remainingDebt.toFixed(2)}`);
530
+ printKeyValue("Tx", explorerUrl(result.tx));
531
+ printBlank();
532
+ } catch (error) {
533
+ handleError(error);
534
+ }
535
+ });
536
+ }
537
+
538
+ // src/commands/health.ts
539
+ import { T2000 as T200013 } from "@t2000/sdk";
540
+ function registerHealth(program2) {
541
+ program2.command("health").description("Check savings health factor").option("--key <path>", "Key file path").action(async (opts) => {
542
+ try {
543
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
544
+ const agent = await T200013.create({ passphrase, keyPath: opts.key });
545
+ const hf = await agent.healthFactor();
546
+ if (isJsonMode()) {
547
+ printJson(hf);
548
+ return;
549
+ }
550
+ printBlank();
551
+ const hfStr = hf.healthFactor === Infinity ? "\u221E" : hf.healthFactor.toFixed(2);
552
+ if (hf.healthFactor >= 2) {
553
+ printSuccess(`Health Factor: ${hfStr} (healthy)`);
554
+ } else if (hf.healthFactor >= 1.5) {
555
+ printWarning(`Health Factor: ${hfStr} (moderate)`);
556
+ } else if (hf.healthFactor >= 1.2) {
557
+ printWarning(`Health Factor: ${hfStr} (low)`);
558
+ } else {
559
+ printError(`Health Factor: ${hfStr} (CRITICAL)`);
560
+ }
561
+ printBlank();
562
+ printKeyValue("Supplied", `$${hf.supplied.toFixed(2)} USDC`);
563
+ printKeyValue("Borrowed", `$${hf.borrowed.toFixed(2)} USDC`);
564
+ printKeyValue("Max Borrow", `$${hf.maxBorrow.toFixed(2)} USDC`);
565
+ printBlank();
566
+ } catch (error) {
567
+ handleError(error);
568
+ }
569
+ });
570
+ }
571
+
572
+ // src/commands/rates.ts
573
+ import { T2000 as T200014 } from "@t2000/sdk";
574
+ function registerRates(program2) {
575
+ program2.command("rates").description("Show current NAVI Protocol APY rates").option("--key <path>", "Key file path").action(async (opts) => {
576
+ try {
577
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
578
+ const agent = await T200014.create({ passphrase, keyPath: opts.key });
579
+ const rates = await agent.rates();
580
+ if (isJsonMode()) {
581
+ printJson(rates);
582
+ return;
583
+ }
584
+ printBlank();
585
+ printInfo("USDC Rates (NAVI Protocol)");
586
+ printKeyValue("Save APY", `${rates.USDC.saveApy.toFixed(2)}%`);
587
+ printKeyValue("Borrow APY", `${rates.USDC.borrowApy.toFixed(2)}%`);
588
+ printBlank();
589
+ } catch (error) {
590
+ handleError(error);
591
+ }
592
+ });
593
+ }
594
+
595
+ // src/commands/positions.ts
596
+ import { T2000 as T200015 } from "@t2000/sdk";
597
+ function registerPositions(program2) {
598
+ program2.command("positions").description("Show savings & borrow positions").option("--key <path>", "Key file path").action(async (opts) => {
599
+ try {
600
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
601
+ const agent = await T200015.create({ passphrase, keyPath: opts.key });
602
+ const result = await agent.positions();
603
+ if (isJsonMode()) {
604
+ printJson(result);
605
+ return;
606
+ }
607
+ printBlank();
608
+ if (result.positions.length === 0) {
609
+ printInfo("No positions. Use `t2000 save <amount>` to start earning.");
610
+ } else {
611
+ for (const pos of result.positions) {
612
+ const label = pos.type === "save" ? "\u{1F4C8} Saving" : "\u{1F4C9} Borrowing";
613
+ printKeyValue(label, `$${pos.amount.toFixed(2)} ${pos.asset} (${pos.protocol})`);
614
+ }
615
+ }
616
+ printBlank();
617
+ } catch (error) {
618
+ handleError(error);
619
+ }
620
+ });
621
+ }
622
+
623
+ // src/commands/swap.ts
624
+ import { T2000 as T200016 } from "@t2000/sdk";
625
+ function registerSwap(program2) {
626
+ program2.command("swap").description("Swap between assets (e.g. swap 10 USDC SUI)").argument("<amount>", "Amount to swap").argument("<from>", "Asset to swap from (USDC or SUI)").argument("<to>", "Asset to swap to (USDC or SUI)").option("--slippage <percent>", "Max slippage percentage", "3").option("--key <path>", "Key file path").action(async (amountStr, from, to, opts) => {
627
+ try {
628
+ const amount = parseFloat(amountStr);
629
+ if (isNaN(amount) || amount <= 0) {
630
+ throw new Error("Amount must be a positive number");
631
+ }
632
+ const maxSlippage = parseFloat(opts.slippage ?? "3") / 100;
633
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
634
+ const agent = await T200016.create({ passphrase, keyPath: opts.key });
635
+ const quote = await agent.swapQuote({ from, to, amount });
636
+ const globalOpts = program2.optsWithGlobals();
637
+ if (!globalOpts.yes) {
638
+ printBlank();
639
+ printKeyValue("Swap", `${amount} ${from.toUpperCase()} \u2192 ${quote.expectedOutput.toFixed(4)} ${to.toUpperCase()}`);
640
+ printKeyValue("Pool Price", `1 SUI = $${quote.poolPrice.toFixed(2)}`);
641
+ if (quote.fee.amount > 0) {
642
+ printKeyValue("Protocol Fee", `$${quote.fee.amount.toFixed(4)} (${quote.fee.rate}%)`);
643
+ }
644
+ printBlank();
645
+ const ok = await askConfirm("Execute swap?");
646
+ if (!ok) return;
647
+ }
648
+ const result = await agent.swap({ from, to, amount, maxSlippage });
649
+ if (isJsonMode()) {
650
+ printJson(result);
651
+ return;
652
+ }
653
+ printBlank();
654
+ printSuccess(`Swapped ${result.fromAmount} ${result.fromAsset} \u2192 ${result.toAmount.toFixed(4)} ${result.toAsset}`);
655
+ if (result.priceImpact > 1e-3) {
656
+ printWarning(`Price impact: ${(result.priceImpact * 100).toFixed(2)}%`);
657
+ }
658
+ if (result.fee > 0) {
659
+ printKeyValue("Fee", `$${result.fee.toFixed(4)}`);
660
+ }
661
+ printKeyValue("Tx", explorerUrl(result.tx));
662
+ printBlank();
663
+ } catch (error) {
664
+ handleError(error);
665
+ }
666
+ });
667
+ }
668
+
669
+ // src/commands/earnings.ts
670
+ import { T2000 as T200017 } from "@t2000/sdk";
671
+ function registerEarnings(program2) {
672
+ program2.command("earnings").description("Show yield earned to date").option("--key <path>", "Key file path").action(async (opts) => {
673
+ try {
674
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
675
+ const agent = await T200017.create({ passphrase, keyPath: opts.key });
676
+ const result = await agent.earnings();
677
+ if (isJsonMode()) {
678
+ printJson(result);
679
+ return;
680
+ }
681
+ printBlank();
682
+ printKeyValue("Saved", `$${result.supplied.toFixed(2)} USDC`);
683
+ printKeyValue("APY", `${result.currentApy.toFixed(2)}%`);
684
+ printKeyValue("Daily Yield", `~$${result.dailyEarning.toFixed(4)}/day`);
685
+ printKeyValue("Est. Earned", `~$${result.totalYieldEarned.toFixed(4)}`);
686
+ printBlank();
687
+ } catch (error) {
688
+ handleError(error);
689
+ }
690
+ });
691
+ }
692
+
693
+ // src/commands/fundStatus.ts
694
+ import { T2000 as T200018 } from "@t2000/sdk";
695
+ function registerFundStatus(program2) {
696
+ program2.command("fund-status").description("Full savings summary").option("--key <path>", "Key file path").action(async (opts) => {
697
+ try {
698
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
699
+ const agent = await T200018.create({ passphrase, keyPath: opts.key });
700
+ const result = await agent.fundStatus();
701
+ if (isJsonMode()) {
702
+ printJson(result);
703
+ return;
704
+ }
705
+ printBlank();
706
+ if (result.supplied > 0) {
707
+ printSuccess("Savings: ACTIVE");
708
+ } else {
709
+ console.log(" Savings: INACTIVE \u2014 deposit USDC and run `t2000 save`");
710
+ }
711
+ printBlank();
712
+ printKeyValue("Saved", `$${result.supplied.toFixed(2)} USDC @ ${result.apy.toFixed(1)}% APY`);
713
+ printKeyValue("Earned today", `~$${result.earnedToday.toFixed(4)}`);
714
+ printKeyValue("Earned all time", `~$${result.earnedAllTime.toFixed(4)}`);
715
+ printKeyValue("Monthly projected", `~$${result.projectedMonthly.toFixed(2)}/month`);
716
+ printBlank();
717
+ if (result.supplied > 0) {
718
+ console.log(" Withdraw anytime: t2000 withdraw <amount>");
719
+ }
720
+ printBlank();
721
+ } catch (error) {
722
+ handleError(error);
723
+ }
724
+ });
725
+ }
726
+
727
+ // src/commands/config.ts
728
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
729
+ import { join } from "path";
730
+ import { homedir } from "os";
731
+ var CONFIG_DIR = join(homedir(), ".t2000");
732
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
733
+ function loadConfig() {
734
+ try {
735
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
736
+ } catch {
737
+ return {};
738
+ }
739
+ }
740
+ function saveConfig(config) {
741
+ if (!existsSync(CONFIG_DIR)) {
742
+ mkdirSync(CONFIG_DIR, { recursive: true });
743
+ }
744
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
745
+ }
746
+ function registerConfig(program2) {
747
+ const configCmd = program2.command("config").description("Show or set configuration");
748
+ configCmd.command("get").argument("[key]", "Config key to get (omit for all)").action((key) => {
749
+ try {
750
+ const config = loadConfig();
751
+ if (isJsonMode()) {
752
+ printJson(key ? { [key]: config[key] } : config);
753
+ return;
754
+ }
755
+ printBlank();
756
+ if (key) {
757
+ printKeyValue(key, String(config[key] ?? "(not set)"));
758
+ } else {
759
+ if (Object.keys(config).length === 0) {
760
+ console.log(" No configuration set.");
761
+ } else {
762
+ for (const [k, v] of Object.entries(config)) {
763
+ printKeyValue(k, String(v));
764
+ }
765
+ }
766
+ }
767
+ printBlank();
768
+ } catch (error) {
769
+ handleError(error);
770
+ }
771
+ });
772
+ configCmd.command("set").argument("<key>", "Config key").argument("<value>", "Config value").action((key, value) => {
773
+ try {
774
+ const config = loadConfig();
775
+ let parsed = value;
776
+ if (value === "true") parsed = true;
777
+ else if (value === "false") parsed = false;
778
+ else if (!isNaN(Number(value)) && value.trim() !== "") parsed = Number(value);
779
+ config[key] = parsed;
780
+ saveConfig(config);
781
+ if (isJsonMode()) {
782
+ printJson({ [key]: parsed });
783
+ return;
784
+ }
785
+ console.log(` Set ${key} = ${String(parsed)}`);
786
+ } catch (error) {
787
+ handleError(error);
788
+ }
789
+ });
790
+ }
791
+
792
+ // src/commands/serve.ts
793
+ import { serve } from "@hono/node-server";
794
+ import { Hono } from "hono";
795
+ import { cors } from "hono/cors";
796
+ import { randomBytes } from "crypto";
797
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
798
+ import { join as join2 } from "path";
799
+ import { homedir as homedir2 } from "os";
800
+ import { T2000 as T200019 } from "@t2000/sdk";
801
+ import { streamSSE } from "hono/streaming";
802
+ var CONFIG_DIR2 = join2(homedir2(), ".t2000");
803
+ var TOKEN_PATH = join2(CONFIG_DIR2, "config.json");
804
+ function generateToken() {
805
+ return `t2k_${randomBytes(24).toString("hex")}`;
806
+ }
807
+ function saveToken(token) {
808
+ if (!existsSync2(CONFIG_DIR2)) {
809
+ mkdirSync2(CONFIG_DIR2, { recursive: true });
810
+ }
811
+ let config = {};
812
+ try {
813
+ config = JSON.parse(readFileSync2(TOKEN_PATH, "utf-8"));
814
+ } catch {
815
+ }
816
+ config.authToken = token;
817
+ writeFileSync2(TOKEN_PATH, JSON.stringify(config, null, 2) + "\n");
818
+ }
819
+ function envelope(data) {
820
+ return { success: true, data, timestamp: Math.floor(Date.now() / 1e3) };
821
+ }
822
+ function errorResponse(code, message, data, retryable = false) {
823
+ return {
824
+ success: false,
825
+ error: { code, message, data, retryable },
826
+ timestamp: Math.floor(Date.now() / 1e3)
827
+ };
828
+ }
829
+ function registerServe(program2) {
830
+ program2.command("serve").description("Start HTTP API server").option("--port <port>", "Port number", "3001").option("--rate-limit <rps>", "Max requests per second", "10").option("--key <path>", "Key file path").action(async (opts) => {
831
+ try {
832
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
833
+ const agent = await T200019.create({ passphrase, keyPath: opts.key });
834
+ const port = parseInt(opts.port, 10);
835
+ const rateLimit = parseInt(opts.rateLimit, 10);
836
+ const token = generateToken();
837
+ saveToken(token);
838
+ const app = buildApp(agent, token, rateLimit);
839
+ serve({ fetch: app.fetch, port });
840
+ console.log(` \u2713 API server running on http://localhost:${port}`);
841
+ console.log(` \u2713 Auth token: ${token}`);
842
+ console.log("");
843
+ console.log(" Endpoints:");
844
+ console.log(" GET /v1/balance POST /v1/send");
845
+ console.log(" GET /v1/address POST /v1/save");
846
+ console.log(" GET /v1/history POST /v1/withdraw");
847
+ console.log(" GET /v1/earnings POST /v1/swap");
848
+ console.log(" GET /v1/rates POST /v1/borrow");
849
+ console.log(" GET /v1/health-factor POST /v1/repay");
850
+ console.log(" GET /v1/positions");
851
+ console.log(" GET /v1/events (SSE)");
852
+ console.log("");
853
+ } catch (error) {
854
+ handleError(error);
855
+ }
856
+ });
857
+ }
858
+ function buildApp(agent, authToken, rateLimit) {
859
+ const app = new Hono();
860
+ app.use("*", cors());
861
+ const requestLog = [];
862
+ app.use("/v1/*", async (c, next) => {
863
+ const now = Date.now();
864
+ const windowMs = 1e3;
865
+ while (requestLog.length > 0 && requestLog[0] < now - windowMs) {
866
+ requestLog.shift();
867
+ }
868
+ if (requestLog.length >= rateLimit) {
869
+ c.status(429);
870
+ c.header("Retry-After", "1");
871
+ return c.json(errorResponse("RATE_LIMITED", "Too many requests", null, true));
872
+ }
873
+ requestLog.push(now);
874
+ await next();
875
+ });
876
+ app.use("/v1/*", async (c, next) => {
877
+ const auth = c.req.header("Authorization");
878
+ if (!auth || auth !== `Bearer ${authToken}`) {
879
+ c.status(401);
880
+ return c.json(errorResponse("UNAUTHORIZED", "Invalid or missing Bearer token"));
881
+ }
882
+ await next();
883
+ });
884
+ app.get("/v1/address", (c) => {
885
+ return c.json(envelope({ address: agent.address }));
886
+ });
887
+ app.get("/v1/balance", async (c) => {
888
+ try {
889
+ const balance = await agent.balance();
890
+ return c.json(envelope(balance));
891
+ } catch (err) {
892
+ return c.json(errorResponse("BALANCE_ERROR", errMsg(err)), 500);
893
+ }
894
+ });
895
+ app.get("/v1/history", async (c) => {
896
+ try {
897
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
898
+ const history = await agent.history({ limit });
899
+ return c.json(envelope(history));
900
+ } catch (err) {
901
+ return c.json(errorResponse("HISTORY_ERROR", errMsg(err)), 500);
902
+ }
903
+ });
904
+ app.get("/v1/deposit", (c) => {
905
+ return c.json(envelope({
906
+ address: agent.address,
907
+ network: "mainnet",
908
+ instructions: "Send USDC to this address on Sui mainnet."
909
+ }));
910
+ });
911
+ app.get("/v1/earnings", async (c) => {
912
+ try {
913
+ const earnings = await agent.earnings();
914
+ return c.json(envelope(earnings));
915
+ } catch (err) {
916
+ return c.json(errorResponse("EARNINGS_ERROR", errMsg(err)), 500);
917
+ }
918
+ });
919
+ app.get("/v1/rates", async (c) => {
920
+ try {
921
+ const rates = await agent.rates();
922
+ return c.json(envelope(rates));
923
+ } catch (err) {
924
+ return c.json(errorResponse("RATES_ERROR", errMsg(err)), 500);
925
+ }
926
+ });
927
+ app.get("/v1/health-factor", async (c) => {
928
+ try {
929
+ const hf = await agent.healthFactor();
930
+ return c.json(envelope(hf));
931
+ } catch (err) {
932
+ return c.json(errorResponse("HEALTH_ERROR", errMsg(err)), 500);
933
+ }
934
+ });
935
+ app.get("/v1/max-withdraw", async (c) => {
936
+ try {
937
+ const result = await agent.maxWithdraw();
938
+ return c.json(envelope(result));
939
+ } catch (err) {
940
+ return c.json(errorResponse("MAX_WITHDRAW_ERROR", errMsg(err)), 500);
941
+ }
942
+ });
943
+ app.get("/v1/max-borrow", async (c) => {
944
+ try {
945
+ const result = await agent.maxBorrow();
946
+ return c.json(envelope(result));
947
+ } catch (err) {
948
+ return c.json(errorResponse("MAX_BORROW_ERROR", errMsg(err)), 500);
949
+ }
950
+ });
951
+ app.get("/v1/positions", async (c) => {
952
+ try {
953
+ const positions = await agent.positions();
954
+ return c.json(envelope(positions));
955
+ } catch (err) {
956
+ return c.json(errorResponse("POSITIONS_ERROR", errMsg(err)), 500);
957
+ }
958
+ });
959
+ app.post("/v1/send", async (c) => {
960
+ try {
961
+ const body = await c.req.json();
962
+ const { to, amount, asset } = body;
963
+ if (!to || !amount) {
964
+ c.status(400);
965
+ return c.json(errorResponse("INVALID_PARAMS", "Required: to, amount"));
966
+ }
967
+ const result = await agent.send({ to, amount, asset: asset ?? "USDC" });
968
+ return c.json(envelope(result));
969
+ } catch (err) {
970
+ c.status(getStatusCode(err));
971
+ return c.json(handleApiError(err));
972
+ }
973
+ });
974
+ app.post("/v1/save", async (c) => {
975
+ try {
976
+ const body = await c.req.json();
977
+ const { amount, asset } = body;
978
+ if (!amount) {
979
+ c.status(400);
980
+ return c.json(errorResponse("INVALID_PARAMS", "Required: amount"));
981
+ }
982
+ const result = await agent.save({ amount, asset: asset ?? "USDC" });
983
+ return c.json(envelope(result));
984
+ } catch (err) {
985
+ c.status(getStatusCode(err));
986
+ return c.json(handleApiError(err));
987
+ }
988
+ });
989
+ app.post("/v1/supply", async (c) => {
990
+ try {
991
+ const body = await c.req.json();
992
+ const { amount, asset } = body;
993
+ if (!amount) {
994
+ c.status(400);
995
+ return c.json(errorResponse("INVALID_PARAMS", "Required: amount"));
996
+ }
997
+ const result = await agent.save({ amount, asset: asset ?? "USDC" });
998
+ return c.json(envelope(result));
999
+ } catch (err) {
1000
+ c.status(getStatusCode(err));
1001
+ return c.json(handleApiError(err));
1002
+ }
1003
+ });
1004
+ app.post("/v1/withdraw", async (c) => {
1005
+ try {
1006
+ const body = await c.req.json();
1007
+ const { amount, asset } = body;
1008
+ if (!amount) {
1009
+ c.status(400);
1010
+ return c.json(errorResponse("INVALID_PARAMS", "Required: amount"));
1011
+ }
1012
+ const result = await agent.withdraw({ amount, asset: asset ?? "USDC" });
1013
+ return c.json(envelope(result));
1014
+ } catch (err) {
1015
+ c.status(getStatusCode(err));
1016
+ return c.json(handleApiError(err));
1017
+ }
1018
+ });
1019
+ app.post("/v1/swap", async (c) => {
1020
+ try {
1021
+ const body = await c.req.json();
1022
+ const { from, to, amount, maxSlippage } = body;
1023
+ if (!from || !to || !amount) {
1024
+ c.status(400);
1025
+ return c.json(errorResponse("INVALID_PARAMS", "Required: from, to, amount"));
1026
+ }
1027
+ const result = await agent.swap({ from, to, amount, maxSlippage });
1028
+ return c.json(envelope(result));
1029
+ } catch (err) {
1030
+ c.status(getStatusCode(err));
1031
+ return c.json(handleApiError(err));
1032
+ }
1033
+ });
1034
+ app.post("/v1/borrow", async (c) => {
1035
+ try {
1036
+ const body = await c.req.json();
1037
+ const { amount, asset } = body;
1038
+ if (!amount) {
1039
+ c.status(400);
1040
+ return c.json(errorResponse("INVALID_PARAMS", "Required: amount"));
1041
+ }
1042
+ const result = await agent.borrow({ amount, asset: asset ?? "USDC" });
1043
+ return c.json(envelope(result));
1044
+ } catch (err) {
1045
+ c.status(getStatusCode(err));
1046
+ return c.json(handleApiError(err));
1047
+ }
1048
+ });
1049
+ app.post("/v1/repay", async (c) => {
1050
+ try {
1051
+ const body = await c.req.json();
1052
+ const { amount, asset } = body;
1053
+ if (!amount) {
1054
+ c.status(400);
1055
+ return c.json(errorResponse("INVALID_PARAMS", "Required: amount"));
1056
+ }
1057
+ const result = await agent.repay({ amount, asset: asset ?? "USDC" });
1058
+ return c.json(envelope(result));
1059
+ } catch (err) {
1060
+ c.status(getStatusCode(err));
1061
+ return c.json(handleApiError(err));
1062
+ }
1063
+ });
1064
+ app.get("/v1/events", async (c) => {
1065
+ const subscribeParam = c.req.query("subscribe") ?? "yield,balanceChange,healthWarning";
1066
+ const subscriptions = new Set(subscribeParam.split(",").map((s) => s.trim()));
1067
+ return streamSSE(c, async (stream) => {
1068
+ const handlers = [];
1069
+ for (const eventName of subscriptions) {
1070
+ const handler = (data) => {
1071
+ stream.writeSSE({ event: eventName, data: JSON.stringify(data) }).catch(() => {
1072
+ });
1073
+ };
1074
+ agent.on(eventName, handler);
1075
+ handlers.push({
1076
+ event: eventName,
1077
+ off: () => agent.off(eventName, handler)
1078
+ });
1079
+ }
1080
+ const keepAlive = setInterval(() => {
1081
+ stream.writeSSE({ event: "ping", data: "{}" }).catch(() => {
1082
+ });
1083
+ }, 3e4);
1084
+ stream.onAbort(() => {
1085
+ clearInterval(keepAlive);
1086
+ for (const h of handlers) h.off();
1087
+ });
1088
+ await new Promise(() => {
1089
+ });
1090
+ });
1091
+ });
1092
+ return app;
1093
+ }
1094
+ function errMsg(err) {
1095
+ return err instanceof Error ? err.message : String(err);
1096
+ }
1097
+ function handleApiError(err) {
1098
+ const t2kErr = err;
1099
+ return errorResponse(
1100
+ t2kErr.code ?? "UNKNOWN",
1101
+ t2kErr.message ?? errMsg(err),
1102
+ t2kErr.data,
1103
+ isRetryable(t2kErr.code)
1104
+ );
1105
+ }
1106
+ function getStatusCode(err) {
1107
+ const code = err.code;
1108
+ if (!code) return 500;
1109
+ if (code === "INSUFFICIENT_BALANCE") return 400;
1110
+ if (code === "INVALID_ADDRESS") return 400;
1111
+ if (code === "WITHDRAW_WOULD_LIQUIDATE") return 400;
1112
+ if (code === "NO_COLLATERAL") return 400;
1113
+ return 500;
1114
+ }
1115
+ function isRetryable(code) {
1116
+ if (!code) return false;
1117
+ return ["RPC_ERROR", "RPC_UNREACHABLE", "SPONSOR_UNAVAILABLE", "AUTO_TOPUP_FAILED"].includes(code);
1118
+ }
1119
+
1120
+ // src/commands/pay.ts
1121
+ import pc5 from "picocolors";
1122
+ import { T2000 as T200020 } from "@t2000/sdk";
1123
+ import { x402Client } from "@t2000/x402";
1124
+ function createX402Wallet(agent) {
1125
+ return {
1126
+ client: agent.suiClient,
1127
+ keypair: agent.signer,
1128
+ address: () => agent.address(),
1129
+ signAndExecute: async (tx) => {
1130
+ const result = await agent.suiClient.signAndExecuteTransaction({
1131
+ signer: agent.signer,
1132
+ transaction: tx
1133
+ });
1134
+ return { digest: result.digest };
1135
+ }
1136
+ };
1137
+ }
1138
+ function registerPay(program2) {
1139
+ program2.command("pay <url>").description("Pay for an x402-protected API resource").option("--key <path>", "Key file path").option("--method <method>", "HTTP method (GET, POST, PUT)", "GET").option("--data <json>", "Request body for POST/PUT").option("--header <key=value>", "Additional HTTP header (repeatable)", collectHeaders, {}).option("--max-price <amount>", "Max USDC price to auto-approve", "1.00").option("--timeout <seconds>", "Request timeout in seconds", "30").option("--dry-run", "Show what would be paid without paying").action(async (url, opts) => {
1140
+ try {
1141
+ const passphrase = getPassphraseFromEnv() ?? await askPassphrase();
1142
+ const agent = await T200020.create({ passphrase, keyPath: opts.key });
1143
+ const wallet = createX402Wallet(agent);
1144
+ const client = new x402Client(wallet);
1145
+ const startTime = Date.now();
1146
+ if (!isJsonMode()) {
1147
+ printBlank();
1148
+ printInfo(`\u2192 ${opts.method} ${url}`);
1149
+ }
1150
+ const response = await client.fetch(url, {
1151
+ method: opts.method,
1152
+ headers: opts.header,
1153
+ body: opts.data,
1154
+ maxPrice: parseFloat(opts.maxPrice),
1155
+ timeout: parseInt(opts.timeout, 10) * 1e3,
1156
+ dryRun: opts.dryRun,
1157
+ onPayment: (details) => {
1158
+ if (!isJsonMode()) {
1159
+ printInfo(`\u2190 402 Payment Required: $${details.amount} USDC (Sui)`);
1160
+ printSuccess(`Paid $${details.amount} USDC (tx: ${details.txHash.slice(0, 10)}...)`);
1161
+ }
1162
+ }
1163
+ });
1164
+ const elapsed = Date.now() - startTime;
1165
+ if (!isJsonMode()) {
1166
+ printInfo(`\u2190 ${response.status} ${response.statusText || "OK"} ${pc5.dim(`[${elapsed}ms]`)}`);
1167
+ }
1168
+ const contentType = response.headers.get("content-type") ?? "";
1169
+ const body = contentType.includes("application/json") ? await response.json() : await response.text();
1170
+ if (isJsonMode()) {
1171
+ printJson({
1172
+ status: response.status,
1173
+ url,
1174
+ elapsed,
1175
+ body
1176
+ });
1177
+ } else {
1178
+ printBlank();
1179
+ if (typeof body === "string") {
1180
+ console.log(body);
1181
+ } else {
1182
+ console.log(JSON.stringify(body, null, 2));
1183
+ }
1184
+ printBlank();
1185
+ }
1186
+ } catch (error) {
1187
+ handleError(error);
1188
+ }
1189
+ });
1190
+ }
1191
+ function collectHeaders(value, previous) {
1192
+ const [key, ...rest] = value.split("=");
1193
+ if (key && rest.length > 0) {
1194
+ previous[key.trim()] = rest.join("=").trim();
1195
+ }
1196
+ return previous;
1197
+ }
1198
+
1199
+ // src/program.ts
1200
+ function createProgram() {
1201
+ const program2 = new Command();
1202
+ program2.name("t2000").description("The first wallet for AI agents").version("0.1.0").option("--json", "Output in JSON format").option("--yes", "Skip confirmation prompts").hook("preAction", (thisCommand) => {
1203
+ const opts = thisCommand.optsWithGlobals();
1204
+ if (opts.json) setJsonMode(true);
1205
+ });
1206
+ registerInit(program2);
1207
+ registerSend(program2);
1208
+ registerBalance(program2);
1209
+ registerAddress(program2);
1210
+ registerDeposit(program2);
1211
+ registerHistory(program2);
1212
+ registerExport(program2);
1213
+ registerImport(program2);
1214
+ registerSave(program2);
1215
+ registerWithdraw(program2);
1216
+ registerBorrow(program2);
1217
+ registerRepay(program2);
1218
+ registerHealth(program2);
1219
+ registerRates(program2);
1220
+ registerPositions(program2);
1221
+ registerSwap(program2);
1222
+ registerEarnings(program2);
1223
+ registerFundStatus(program2);
1224
+ registerConfig(program2);
1225
+ registerServe(program2);
1226
+ registerPay(program2);
1227
+ return program2;
1228
+ }
1229
+
1230
+ // src/index.ts
1231
+ var program = createProgram();
1232
+ program.parse();
1233
+ //# sourceMappingURL=index.js.map