@t2000/sdk 0.16.2 → 0.16.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.
package/dist/index.d.cts CHANGED
@@ -582,6 +582,13 @@ interface AutoTopUpResult {
582
582
  suiReceived: number;
583
583
  }
584
584
  declare function shouldAutoTopUp(client: SuiJsonRpcClient, address: string): Promise<boolean>;
585
+ /**
586
+ * Self-fund a USDC→SUI swap to replenish gas.
587
+ *
588
+ * Uses the agent's remaining SUI to pay for the swap gas (~0.007 SUI).
589
+ * This avoids the chicken-and-egg problem of needing gas station sponsorship
590
+ * to get gas, and works even when the gas station is down.
591
+ */
585
592
  declare function executeAutoTopUp(client: SuiJsonRpcClient, keypair: Ed25519Keypair): Promise<AutoTopUpResult>;
586
593
 
587
594
  type GasRequestType = 'bootstrap' | 'auto-topup' | 'fallback';
package/dist/index.d.ts CHANGED
@@ -582,6 +582,13 @@ interface AutoTopUpResult {
582
582
  suiReceived: number;
583
583
  }
584
584
  declare function shouldAutoTopUp(client: SuiJsonRpcClient, address: string): Promise<boolean>;
585
+ /**
586
+ * Self-fund a USDC→SUI swap to replenish gas.
587
+ *
588
+ * Uses the agent's remaining SUI to pay for the swap gas (~0.007 SUI).
589
+ * This avoids the chicken-and-egg problem of needing gas station sponsorship
590
+ * to get gas, and works even when the gas station is down.
591
+ */
585
592
  declare function executeAutoTopUp(client: SuiJsonRpcClient, keypair: Ed25519Keypair): Promise<AutoTopUpResult>;
586
593
 
587
594
  type GasRequestType = 'bootstrap' | 'auto-topup' | 'fallback';
package/dist/index.js CHANGED
@@ -2127,12 +2127,13 @@ var SuilendAdapter = class {
2127
2127
  if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
2128
2128
  const caps = await this.fetchObligationCaps(address);
2129
2129
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
2130
- const positions = await this.getPositions(address);
2131
- const deposited = positions.supplies.find((s) => s.asset === assetKey)?.amount ?? 0;
2130
+ const obligation = await this.fetchObligation(caps[0].obligationId);
2131
+ const dep = obligation.deposits.find((d) => d.reserveIdx === reserve.arrayIndex);
2132
+ const ratio = cTokenRatio(reserve);
2133
+ const deposited = dep ? dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals : 0;
2132
2134
  const effectiveAmount = Math.min(amount, deposited);
2133
2135
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
2134
- const ratio = cTokenRatio(reserve);
2135
- const ctokenAmount = Math.ceil(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
2136
+ const ctokenAmount = dep && effectiveAmount >= deposited * 0.999 ? dep.ctokenAmount : Math.floor(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
2136
2137
  const tx = new Transaction();
2137
2138
  tx.setSender(address);
2138
2139
  const [ctokens] = tx.moveCall({
@@ -2173,12 +2174,13 @@ var SuilendAdapter = class {
2173
2174
  if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
2174
2175
  const caps = await this.fetchObligationCaps(address);
2175
2176
  if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
2176
- const positions = await this.getPositions(address);
2177
- const deposited = positions.supplies.find((s) => s.asset === assetKey)?.amount ?? 0;
2177
+ const obligation = await this.fetchObligation(caps[0].obligationId);
2178
+ const dep = obligation.deposits.find((d) => d.reserveIdx === reserve.arrayIndex);
2179
+ const ratio = cTokenRatio(reserve);
2180
+ const deposited = dep ? dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals : 0;
2178
2181
  const effectiveAmount = Math.min(amount, deposited);
2179
2182
  if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
2180
- const ratio = cTokenRatio(reserve);
2181
- const ctokenAmount = Math.ceil(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
2183
+ const ctokenAmount = dep && effectiveAmount >= deposited * 0.999 ? dep.ctokenAmount : Math.floor(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
2182
2184
  const [ctokens] = tx.moveCall({
2183
2185
  target: `${pkg}::lending_market::withdraw_ctokens`,
2184
2186
  typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
@@ -2386,13 +2388,62 @@ function solveHashcash(challenge) {
2386
2388
  }
2387
2389
  }
2388
2390
 
2391
+ // src/gas/autoTopUp.ts
2392
+ var AUTO_TOPUP_MIN_SUI_FOR_GAS = 5000000n;
2393
+ async function shouldAutoTopUp(client, address) {
2394
+ const [suiBalance, usdcBalance] = await Promise.all([
2395
+ client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
2396
+ client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.USDC.type })
2397
+ ]);
2398
+ const suiRaw = BigInt(suiBalance.totalBalance);
2399
+ const usdcRaw = BigInt(usdcBalance.totalBalance);
2400
+ return suiRaw < AUTO_TOPUP_THRESHOLD && suiRaw >= AUTO_TOPUP_MIN_SUI_FOR_GAS && usdcRaw >= AUTO_TOPUP_MIN_USDC;
2401
+ }
2402
+ async function executeAutoTopUp(client, keypair) {
2403
+ const address = keypair.getPublicKey().toSuiAddress();
2404
+ const topupAmountHuman = Number(AUTO_TOPUP_AMOUNT) / 1e6;
2405
+ const { tx } = await buildSwapTx({
2406
+ client,
2407
+ address,
2408
+ fromAsset: "USDC",
2409
+ toAsset: "SUI",
2410
+ amount: topupAmountHuman
2411
+ });
2412
+ const result = await client.signAndExecuteTransaction({
2413
+ signer: keypair,
2414
+ transaction: tx,
2415
+ options: { showEffects: true, showBalanceChanges: true }
2416
+ });
2417
+ await client.waitForTransaction({ digest: result.digest });
2418
+ let suiReceived = 0;
2419
+ if (result.balanceChanges) {
2420
+ for (const change of result.balanceChanges) {
2421
+ if (change.coinType === SUPPORTED_ASSETS.SUI.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
2422
+ suiReceived += Number(change.amount) / Number(MIST_PER_SUI);
2423
+ }
2424
+ }
2425
+ }
2426
+ return {
2427
+ success: true,
2428
+ tx: result.digest,
2429
+ usdcSpent: topupAmountHuman,
2430
+ suiReceived: Math.abs(suiReceived)
2431
+ };
2432
+ }
2433
+
2389
2434
  // src/gas/gasStation.ts
2390
- async function requestGasSponsorship(txJson, sender, type) {
2391
- const txBytes = Buffer.from(txJson).toString("base64");
2435
+ async function requestGasSponsorship(txJson, sender, type, txBcsBytes) {
2436
+ const payload = { sender, type };
2437
+ if (txBcsBytes) {
2438
+ payload.txBcsBytes = txBcsBytes;
2439
+ } else {
2440
+ payload.txJson = txJson;
2441
+ payload.txBytes = Buffer.from(txJson).toString("base64");
2442
+ }
2392
2443
  const res = await fetch(`${API_BASE_URL}/api/gas`, {
2393
2444
  method: "POST",
2394
2445
  headers: { "Content-Type": "application/json" },
2395
- body: JSON.stringify({ txJson, txBytes, sender, type })
2446
+ body: JSON.stringify(payload)
2396
2447
  });
2397
2448
  const data = await res.json();
2398
2449
  if (!res.ok) {
@@ -2442,53 +2493,6 @@ async function getGasStatus(address) {
2442
2493
  return await res.json();
2443
2494
  }
2444
2495
 
2445
- // src/gas/autoTopUp.ts
2446
- async function shouldAutoTopUp(client, address) {
2447
- const [suiBalance, usdcBalance] = await Promise.all([
2448
- client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
2449
- client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.USDC.type })
2450
- ]);
2451
- const suiRaw = BigInt(suiBalance.totalBalance);
2452
- const usdcRaw = BigInt(usdcBalance.totalBalance);
2453
- return suiRaw < AUTO_TOPUP_THRESHOLD && usdcRaw >= AUTO_TOPUP_MIN_USDC;
2454
- }
2455
- async function executeAutoTopUp(client, keypair) {
2456
- const address = keypair.getPublicKey().toSuiAddress();
2457
- const topupAmountHuman = Number(AUTO_TOPUP_AMOUNT) / 1e6;
2458
- const { tx } = await buildSwapTx({
2459
- client,
2460
- address,
2461
- fromAsset: "USDC",
2462
- toAsset: "SUI",
2463
- amount: topupAmountHuman
2464
- });
2465
- const txJson = tx.serialize();
2466
- const sponsoredResult = await requestGasSponsorship(txJson, address, "auto-topup");
2467
- const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
2468
- const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
2469
- const result = await client.executeTransactionBlock({
2470
- transactionBlock: sponsoredResult.txBytes,
2471
- signature: [agentSig, sponsoredResult.sponsorSignature],
2472
- options: { showEffects: true, showBalanceChanges: true }
2473
- });
2474
- await client.waitForTransaction({ digest: result.digest });
2475
- let suiReceived = 0;
2476
- if (result.balanceChanges) {
2477
- for (const change of result.balanceChanges) {
2478
- if (change.coinType === SUPPORTED_ASSETS.SUI.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
2479
- suiReceived += Number(change.amount) / Number(MIST_PER_SUI);
2480
- }
2481
- }
2482
- }
2483
- reportGasUsage(address, result.digest, 0, 0, "auto-topup");
2484
- return {
2485
- success: true,
2486
- tx: result.digest,
2487
- usdcSpent: topupAmountHuman,
2488
- suiReceived: Math.abs(suiReceived)
2489
- };
2490
- }
2491
-
2492
2496
  // src/gas/manager.ts
2493
2497
  function extractGasCost(effects) {
2494
2498
  if (!effects?.gasUsed) return 0;
@@ -2516,11 +2520,12 @@ async function trySelfFunded(client, keypair, tx) {
2516
2520
  gasCostSui: extractGasCost(result.effects)
2517
2521
  };
2518
2522
  }
2519
- async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
2523
+ async function tryAutoTopUpThenSelfFund(client, keypair, buildTx) {
2520
2524
  const address = keypair.getPublicKey().toSuiAddress();
2521
2525
  const canTopUp = await shouldAutoTopUp(client, address);
2522
2526
  if (!canTopUp) return null;
2523
2527
  await executeAutoTopUp(client, keypair);
2528
+ const tx = await buildTx();
2524
2529
  tx.setSender(address);
2525
2530
  const result = await client.signAndExecuteTransaction({
2526
2531
  signer: keypair,
@@ -2538,8 +2543,15 @@ async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
2538
2543
  async function trySponsored(client, keypair, tx) {
2539
2544
  const address = keypair.getPublicKey().toSuiAddress();
2540
2545
  tx.setSender(address);
2541
- const txJson = tx.serialize();
2542
- const sponsoredResult = await requestGasSponsorship(txJson, address);
2546
+ let txJson;
2547
+ let txBcsBase64;
2548
+ try {
2549
+ txJson = tx.serialize();
2550
+ } catch {
2551
+ const bcsBytes = await tx.build({ client });
2552
+ txBcsBase64 = Buffer.from(bcsBytes).toString("base64");
2553
+ }
2554
+ const sponsoredResult = await requestGasSponsorship(txJson ?? "", address, void 0, txBcsBase64);
2543
2555
  const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
2544
2556
  const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
2545
2557
  const result = await client.executeTransactionBlock({
@@ -2575,8 +2587,7 @@ async function executeWithGas(client, keypair, buildTx, options) {
2575
2587
  errors.push(`self-funded: ${msg}`);
2576
2588
  }
2577
2589
  try {
2578
- const tx = await buildTx();
2579
- const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
2590
+ const result = await tryAutoTopUpThenSelfFund(client, keypair, buildTx);
2580
2591
  if (result) return result;
2581
2592
  errors.push("auto-topup: not eligible (low USDC or sufficient SUI)");
2582
2593
  } catch (err) {