@t2000/sdk 0.1.4 → 0.1.6

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 CHANGED
@@ -236,39 +236,27 @@ function formatSui(amount) {
236
236
  }
237
237
 
238
238
  // src/wallet/send.ts
239
- async function buildAndExecuteSend({
239
+ async function buildSendTx({
240
240
  client,
241
- keypair,
241
+ address,
242
242
  to,
243
243
  amount,
244
244
  asset = "USDC"
245
245
  }) {
246
246
  const recipient = validateAddress(to);
247
247
  const assetInfo = SUPPORTED_ASSETS[asset];
248
- if (!assetInfo) {
249
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
250
- }
251
- if (amount <= 0) {
252
- throw new T2000Error("INVALID_AMOUNT", "Amount must be greater than zero");
253
- }
254
- const senderAddress = keypair.getPublicKey().toSuiAddress();
248
+ if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
249
+ if (amount <= 0) throw new T2000Error("INVALID_AMOUNT", "Amount must be greater than zero");
255
250
  const rawAmount = displayToRaw(amount, assetInfo.decimals);
256
251
  const tx = new Transaction();
252
+ tx.setSender(address);
257
253
  if (asset === "SUI") {
258
254
  const [coin] = tx.splitCoins(tx.gas, [rawAmount]);
259
255
  tx.transferObjects([coin], recipient);
260
256
  } else {
261
- const coins = await client.getCoins({
262
- owner: senderAddress,
263
- coinType: assetInfo.type
264
- });
265
- if (coins.data.length === 0) {
266
- throw new T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
267
- }
268
- const totalBalance = coins.data.reduce(
269
- (sum, c) => sum + BigInt(c.balance),
270
- 0n
271
- );
257
+ const coins = await client.getCoins({ owner: address, coinType: assetInfo.type });
258
+ if (coins.data.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
259
+ const totalBalance = coins.data.reduce((sum, c) => sum + BigInt(c.balance), 0n);
272
260
  if (totalBalance < rawAmount) {
273
261
  throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
274
262
  available: Number(totalBalance) / 10 ** assetInfo.decimals,
@@ -277,28 +265,12 @@ async function buildAndExecuteSend({
277
265
  }
278
266
  const primaryCoin = tx.object(coins.data[0].coinObjectId);
279
267
  if (coins.data.length > 1) {
280
- tx.mergeCoins(
281
- primaryCoin,
282
- coins.data.slice(1).map((c) => tx.object(c.coinObjectId))
283
- );
268
+ tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
284
269
  }
285
270
  const [sendCoin] = tx.splitCoins(primaryCoin, [rawAmount]);
286
271
  tx.transferObjects([sendCoin], recipient);
287
272
  }
288
- const result = await client.signAndExecuteTransaction({
289
- signer: keypair,
290
- transaction: tx,
291
- options: { showEffects: true }
292
- });
293
- await client.waitForTransaction({ digest: result.digest });
294
- const gasUsed = result.effects?.gasUsed;
295
- const gasCost = gasUsed ? Math.abs(
296
- (Number(gasUsed.computationCost) + Number(gasUsed.storageCost) - Number(gasUsed.storageRebate)) / 1e9
297
- ) : 0;
298
- return {
299
- digest: result.digest,
300
- gasCost
301
- };
273
+ return tx;
302
274
  }
303
275
 
304
276
  // src/wallet/balance.ts
@@ -396,12 +368,6 @@ var NAVI_BALANCE_DECIMALS = 9;
396
368
  function clientOpt(client, fresh = false) {
397
369
  return { client, ...ENV, ...fresh ? { disableCache: true } : {} };
398
370
  }
399
- function extractGasCost(effects) {
400
- if (!effects?.gasUsed) return 0;
401
- return Math.abs(
402
- (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9
403
- );
404
- }
405
371
  function rateToApy(rawRate) {
406
372
  if (!rawRate || rawRate === "0") return 0;
407
373
  return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
@@ -434,8 +400,7 @@ async function updateOracle(tx, client, address) {
434
400
  } catch {
435
401
  }
436
402
  }
437
- async function save(client, keypair, amount) {
438
- const address = keypair.getPublicKey().toSuiAddress();
403
+ async function buildSaveTx(client, address, amount) {
439
404
  const rawAmount = Number(usdcToRaw(amount));
440
405
  const coins = await getCoins(address, { coinType: USDC_TYPE, client });
441
406
  if (!coins || coins.length === 0) {
@@ -445,25 +410,9 @@ async function save(client, keypair, amount) {
445
410
  tx.setSender(address);
446
411
  const coinObj = mergeCoinsPTB(tx, coins, { balance: rawAmount });
447
412
  await depositCoinPTB(tx, USDC_TYPE, coinObj, ENV);
448
- const result = await client.signAndExecuteTransaction({
449
- signer: keypair,
450
- transaction: tx,
451
- options: { showEffects: true }
452
- });
453
- await client.waitForTransaction({ digest: result.digest });
454
- const rates = await getRates();
455
- return {
456
- success: true,
457
- tx: result.digest,
458
- amount,
459
- apy: rates.USDC.saveApy,
460
- fee: 0,
461
- gasCost: extractGasCost(result.effects),
462
- gasMethod: "self-funded"
463
- };
413
+ return tx;
464
414
  }
465
- async function withdraw(client, keypair, amount) {
466
- const address = keypair.getPublicKey().toSuiAddress();
415
+ async function buildWithdrawTx(client, address, amount) {
467
416
  const state = await getLendingState(address, clientOpt(client, true));
468
417
  const usdcPos = findUsdcPosition(state);
469
418
  const deposited = usdcPos ? Number(usdcPos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
@@ -475,47 +424,18 @@ async function withdraw(client, keypair, amount) {
475
424
  await updateOracle(tx, client, address);
476
425
  const withdrawnCoin = await withdrawCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
477
426
  tx.transferObjects([withdrawnCoin], address);
478
- const result = await client.signAndExecuteTransaction({
479
- signer: keypair,
480
- transaction: tx,
481
- options: { showEffects: true }
482
- });
483
- await client.waitForTransaction({ digest: result.digest });
484
- return {
485
- success: true,
486
- tx: result.digest,
487
- amount: effectiveAmount,
488
- gasCost: extractGasCost(result.effects),
489
- gasMethod: "self-funded"
490
- };
427
+ return { tx, effectiveAmount };
491
428
  }
492
- async function borrow(client, keypair, amount) {
493
- const address = keypair.getPublicKey().toSuiAddress();
429
+ async function buildBorrowTx(client, address, amount) {
494
430
  const rawAmount = Number(usdcToRaw(amount));
495
431
  const tx = new Transaction();
496
432
  tx.setSender(address);
497
433
  await updateOracle(tx, client, address);
498
434
  const borrowedCoin = await borrowCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
499
435
  tx.transferObjects([borrowedCoin], address);
500
- const result = await client.signAndExecuteTransaction({
501
- signer: keypair,
502
- transaction: tx,
503
- options: { showEffects: true }
504
- });
505
- await client.waitForTransaction({ digest: result.digest });
506
- const hf = await getHealthFactor$1(address, clientOpt(client, true));
507
- return {
508
- success: true,
509
- tx: result.digest,
510
- amount,
511
- fee: 0,
512
- healthFactor: hf,
513
- gasCost: extractGasCost(result.effects),
514
- gasMethod: "self-funded"
515
- };
436
+ return tx;
516
437
  }
517
- async function repay(client, keypair, amount) {
518
- const address = keypair.getPublicKey().toSuiAddress();
438
+ async function buildRepayTx(client, address, amount) {
519
439
  const rawAmount = Number(usdcToRaw(amount));
520
440
  const coins = await getCoins(address, { coinType: USDC_TYPE, client });
521
441
  if (!coins || coins.length === 0) {
@@ -525,23 +445,7 @@ async function repay(client, keypair, amount) {
525
445
  tx.setSender(address);
526
446
  const coinObj = mergeCoinsPTB(tx, coins, { balance: rawAmount });
527
447
  await repayCoinPTB(tx, USDC_TYPE, coinObj, { ...ENV, amount: rawAmount });
528
- const result = await client.signAndExecuteTransaction({
529
- signer: keypair,
530
- transaction: tx,
531
- options: { showEffects: true }
532
- });
533
- await client.waitForTransaction({ digest: result.digest });
534
- const state = await getLendingState(address, clientOpt(client, true));
535
- const usdcPos = findUsdcPosition(state);
536
- const remainingDebt = usdcPos ? Number(usdcPos.borrowBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
537
- return {
538
- success: true,
539
- tx: result.digest,
540
- amount,
541
- remainingDebt,
542
- gasCost: extractGasCost(result.effects),
543
- gasMethod: "self-funded"
544
- };
448
+ return tx;
545
449
  }
546
450
  async function getHealthFactor(client, keypair) {
547
451
  const address = keypair.getPublicKey().toSuiAddress();
@@ -633,10 +537,6 @@ async function maxBorrowAmount(client, keypair) {
633
537
  };
634
538
  }
635
539
  var DEFAULT_SLIPPAGE_BPS = 300;
636
- function extractGasCost2(effects) {
637
- if (!effects?.gasUsed) return 0;
638
- return (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9;
639
- }
640
540
  function isA2B(from) {
641
541
  return from === "USDC";
642
542
  }
@@ -647,9 +547,8 @@ function getCetusSDK() {
647
547
  }
648
548
  return _cetusSDK;
649
549
  }
650
- async function executeSwap(params) {
651
- const { client, keypair, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
652
- const address = keypair.getPublicKey().toSuiAddress();
550
+ async function buildSwapTx(params) {
551
+ const { address, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
653
552
  const a2b = isA2B(fromAsset);
654
553
  const fromInfo = SUPPORTED_ASSETS[fromAsset];
655
554
  const toInfo = SUPPORTED_ASSETS[toAsset];
@@ -680,32 +579,10 @@ async function executeSwap(params) {
680
579
  amount: preSwapResult.amount.toString(),
681
580
  amount_limit: amountLimit.toString()
682
581
  });
683
- const result = await client.signAndExecuteTransaction({
684
- signer: keypair,
685
- transaction: swapPayload,
686
- options: { showEffects: true, showBalanceChanges: true }
687
- });
688
- await client.waitForTransaction({ digest: result.digest });
689
- let actualReceived = 0;
690
- if (result.balanceChanges) {
691
- for (const change of result.balanceChanges) {
692
- if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
693
- const amt = Number(change.amount) / 10 ** toInfo.decimals;
694
- if (amt > 0) actualReceived += amt;
695
- }
696
- }
697
- }
698
- const expectedOutput = estimatedOut / 10 ** toInfo.decimals;
699
- if (actualReceived === 0) actualReceived = expectedOutput;
700
- const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
701
582
  return {
702
- digest: result.digest,
703
- fromAmount: amount,
704
- fromAsset,
705
- toAmount: actualReceived,
706
- toAsset,
707
- priceImpact,
708
- gasCost: extractGasCost2(result.effects)
583
+ tx: swapPayload,
584
+ estimatedOut,
585
+ toDecimals: toInfo.decimals
709
586
  };
710
587
  }
711
588
  async function getPoolPrice(client) {
@@ -982,12 +859,127 @@ async function executeAutoTopUp(client, keypair) {
982
859
  };
983
860
  }
984
861
 
862
+ // src/gas/manager.ts
863
+ function extractGasCost(effects) {
864
+ if (!effects?.gasUsed) return 0;
865
+ return (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9;
866
+ }
867
+ async function getSuiBalance(client, address) {
868
+ const bal = await client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type });
869
+ return BigInt(bal.totalBalance);
870
+ }
871
+ async function trySelfFunded(client, keypair, tx) {
872
+ const address = keypair.getPublicKey().toSuiAddress();
873
+ const suiBalance = await getSuiBalance(client, address);
874
+ if (suiBalance < AUTO_TOPUP_THRESHOLD) return null;
875
+ tx.setSender(address);
876
+ const result = await client.signAndExecuteTransaction({
877
+ signer: keypair,
878
+ transaction: tx,
879
+ options: { showEffects: true }
880
+ });
881
+ await client.waitForTransaction({ digest: result.digest });
882
+ return {
883
+ digest: result.digest,
884
+ effects: result.effects,
885
+ gasMethod: "self-funded",
886
+ gasCostSui: extractGasCost(result.effects)
887
+ };
888
+ }
889
+ async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
890
+ const address = keypair.getPublicKey().toSuiAddress();
891
+ const canTopUp = await shouldAutoTopUp(client, address);
892
+ if (!canTopUp) return null;
893
+ try {
894
+ await executeAutoTopUp(client, keypair);
895
+ } catch {
896
+ return null;
897
+ }
898
+ tx.setSender(address);
899
+ const result = await client.signAndExecuteTransaction({
900
+ signer: keypair,
901
+ transaction: tx,
902
+ options: { showEffects: true }
903
+ });
904
+ await client.waitForTransaction({ digest: result.digest });
905
+ return {
906
+ digest: result.digest,
907
+ effects: result.effects,
908
+ gasMethod: "auto-topup",
909
+ gasCostSui: extractGasCost(result.effects)
910
+ };
911
+ }
912
+ async function trySponsored(client, keypair, tx) {
913
+ const address = keypair.getPublicKey().toSuiAddress();
914
+ tx.setSender(address);
915
+ let txBytes;
916
+ try {
917
+ txBytes = await tx.build({ client, onlyTransactionKind: true });
918
+ } catch {
919
+ return null;
920
+ }
921
+ const txBytesBase64 = Buffer.from(txBytes).toString("base64");
922
+ let sponsoredResult;
923
+ try {
924
+ sponsoredResult = await requestGasSponsorship(txBytesBase64, address);
925
+ } catch {
926
+ return null;
927
+ }
928
+ const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
929
+ const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
930
+ const result = await client.executeTransactionBlock({
931
+ transactionBlock: sponsoredResult.txBytes,
932
+ signature: [agentSig, sponsoredResult.sponsorSignature],
933
+ options: { showEffects: true }
934
+ });
935
+ await client.waitForTransaction({ digest: result.digest });
936
+ const gasCost = extractGasCost(result.effects);
937
+ reportGasUsage(address, result.digest, gasCost, 0, sponsoredResult.type);
938
+ return {
939
+ digest: result.digest,
940
+ effects: result.effects,
941
+ gasMethod: "sponsored",
942
+ gasCostSui: gasCost
943
+ };
944
+ }
945
+ async function executeWithGas(client, keypair, buildTx) {
946
+ const errors = [];
947
+ try {
948
+ const tx = await buildTx();
949
+ const result = await trySelfFunded(client, keypair, tx);
950
+ if (result) return result;
951
+ errors.push("self-funded: SUI below threshold");
952
+ } catch (err) {
953
+ errors.push(`self-funded: ${err instanceof Error ? err.message : String(err)}`);
954
+ }
955
+ try {
956
+ const tx = await buildTx();
957
+ const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
958
+ if (result) return result;
959
+ errors.push("auto-topup: not eligible (low USDC or sufficient SUI)");
960
+ } catch (err) {
961
+ errors.push(`auto-topup: ${err instanceof Error ? err.message : String(err)}`);
962
+ }
963
+ try {
964
+ const tx = await buildTx();
965
+ const result = await trySponsored(client, keypair, tx);
966
+ if (result) return result;
967
+ errors.push("sponsored: returned null");
968
+ } catch (err) {
969
+ errors.push(`sponsored: ${err instanceof Error ? err.message : String(err)}`);
970
+ }
971
+ throw new T2000Error(
972
+ "INSUFFICIENT_GAS",
973
+ `No SUI for gas and Gas Station unavailable. Fund your wallet with SUI or USDC. [${errors.join(" | ")}]`,
974
+ { reason: "all_gas_methods_exhausted", errors }
975
+ );
976
+ }
977
+
985
978
  // src/t2000.ts
986
979
  var T2000 = class _T2000 extends EventEmitter {
987
980
  keypair;
988
981
  client;
989
982
  _address;
990
- _lastGasMethod = "self-funded";
991
983
  constructor(keypair, client) {
992
984
  super();
993
985
  this.keypair = keypair;
@@ -1041,29 +1033,6 @@ var T2000 = class _T2000 extends EventEmitter {
1041
1033
  return { agent, address, sponsored };
1042
1034
  }
1043
1035
  // -- Gas --
1044
- /**
1045
- * Ensure the agent has enough SUI for gas.
1046
- * If SUI is low and USDC is available, auto-swaps $1 USDC → SUI.
1047
- */
1048
- async ensureGas() {
1049
- this._lastGasMethod = "self-funded";
1050
- const needsTopUp = await shouldAutoTopUp(this.client, this._address);
1051
- if (!needsTopUp) return;
1052
- try {
1053
- const result = await executeAutoTopUp(this.client, this.keypair);
1054
- this._lastGasMethod = "auto-topup";
1055
- this.emit("gasAutoTopUp", {
1056
- usdcSpent: result.usdcSpent,
1057
- suiReceived: result.suiReceived
1058
- });
1059
- } catch {
1060
- this.emit("gasStationFallback", {
1061
- reason: "auto-topup failed",
1062
- method: "self-funded",
1063
- suiUsed: 0
1064
- });
1065
- }
1066
- }
1067
1036
  /** SuiClient used by this agent — exposed for x402 and other integrations. */
1068
1037
  get suiClient() {
1069
1038
  return this.client;
@@ -1081,24 +1050,23 @@ var T2000 = class _T2000 extends EventEmitter {
1081
1050
  if (!(asset in SUPPORTED_ASSETS)) {
1082
1051
  throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
1083
1052
  }
1084
- await this.ensureGas();
1085
- const result = await buildAndExecuteSend({
1086
- client: this.client,
1087
- keypair: this.keypair,
1088
- to: params.to,
1089
- amount: params.amount,
1090
- asset
1091
- });
1053
+ const sendAmount = params.amount;
1054
+ const sendTo = params.to;
1055
+ const gasResult = await executeWithGas(
1056
+ this.client,
1057
+ this.keypair,
1058
+ () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset })
1059
+ );
1092
1060
  const balance = await this.balance();
1093
- this.emitBalanceChange(asset, params.amount, "send", result.digest);
1061
+ this.emitBalanceChange(asset, sendAmount, "send", gasResult.digest);
1094
1062
  return {
1095
1063
  success: true,
1096
- tx: result.digest,
1097
- amount: params.amount,
1064
+ tx: gasResult.digest,
1065
+ amount: sendAmount,
1098
1066
  to: params.to,
1099
- gasCost: result.gasCost,
1067
+ gasCost: gasResult.gasCostSui,
1100
1068
  gasCostUnit: "SUI",
1101
- gasMethod: this._lastGasMethod,
1069
+ gasMethod: gasResult.gasMethod,
1102
1070
  balance
1103
1071
  };
1104
1072
  }
@@ -1154,11 +1122,24 @@ var T2000 = class _T2000 extends EventEmitter {
1154
1122
  amount = params.amount;
1155
1123
  }
1156
1124
  const fee = calculateFee("save", amount);
1157
- await this.ensureGas();
1158
- const result = await save(this.client, this.keypair, amount);
1159
- reportFee(this._address, "save", fee.amount, fee.rate, result.tx);
1160
- this.emitBalanceChange("USDC", amount, "save", result.tx);
1161
- return { ...result, fee: fee.amount, gasMethod: this._lastGasMethod };
1125
+ const saveAmount = amount;
1126
+ const gasResult = await executeWithGas(
1127
+ this.client,
1128
+ this.keypair,
1129
+ () => buildSaveTx(this.client, this._address, saveAmount)
1130
+ );
1131
+ const rates = await getRates(this.client);
1132
+ reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
1133
+ this.emitBalanceChange("USDC", saveAmount, "save", gasResult.digest);
1134
+ return {
1135
+ success: true,
1136
+ tx: gasResult.digest,
1137
+ amount: saveAmount,
1138
+ apy: rates.USDC.saveApy,
1139
+ fee: fee.amount,
1140
+ gasCost: gasResult.gasCostSui,
1141
+ gasMethod: gasResult.gasMethod
1142
+ };
1162
1143
  }
1163
1144
  async withdraw(params) {
1164
1145
  let amount;
@@ -1186,10 +1167,21 @@ var T2000 = class _T2000 extends EventEmitter {
1186
1167
  }
1187
1168
  }
1188
1169
  }
1189
- await this.ensureGas();
1190
- const result = await withdraw(this.client, this.keypair, amount);
1191
- this.emitBalanceChange("USDC", amount, "withdraw", result.tx);
1192
- return { ...result, gasMethod: this._lastGasMethod };
1170
+ const withdrawAmount = amount;
1171
+ let effectiveAmount = withdrawAmount;
1172
+ const gasResult = await executeWithGas(this.client, this.keypair, async () => {
1173
+ const built = await buildWithdrawTx(this.client, this._address, withdrawAmount);
1174
+ effectiveAmount = built.effectiveAmount;
1175
+ return built.tx;
1176
+ });
1177
+ this.emitBalanceChange("USDC", effectiveAmount, "withdraw", gasResult.digest);
1178
+ return {
1179
+ success: true,
1180
+ tx: gasResult.digest,
1181
+ amount: effectiveAmount,
1182
+ gasCost: gasResult.gasCostSui,
1183
+ gasMethod: gasResult.gasMethod
1184
+ };
1193
1185
  }
1194
1186
  async maxWithdraw() {
1195
1187
  return maxWithdrawAmount(this.client, this.keypair);
@@ -1204,27 +1196,52 @@ var T2000 = class _T2000 extends EventEmitter {
1204
1196
  });
1205
1197
  }
1206
1198
  const fee = calculateFee("borrow", params.amount);
1207
- await this.ensureGas();
1208
- const result = await borrow(this.client, this.keypair, params.amount);
1209
- reportFee(this._address, "borrow", fee.amount, fee.rate, result.tx);
1210
- this.emitBalanceChange("USDC", params.amount, "borrow", result.tx);
1211
- return { ...result, fee: fee.amount, gasMethod: this._lastGasMethod };
1199
+ const borrowAmount = params.amount;
1200
+ const gasResult = await executeWithGas(
1201
+ this.client,
1202
+ this.keypair,
1203
+ () => buildBorrowTx(this.client, this._address, borrowAmount)
1204
+ );
1205
+ const hf = await getHealthFactor(this.client, this.keypair);
1206
+ reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
1207
+ this.emitBalanceChange("USDC", borrowAmount, "borrow", gasResult.digest);
1208
+ return {
1209
+ success: true,
1210
+ tx: gasResult.digest,
1211
+ amount: borrowAmount,
1212
+ fee: fee.amount,
1213
+ healthFactor: hf.healthFactor,
1214
+ gasCost: gasResult.gasCostSui,
1215
+ gasMethod: gasResult.gasMethod
1216
+ };
1212
1217
  }
1213
1218
  async repay(params) {
1214
1219
  let amount;
1215
1220
  if (params.amount === "all") {
1216
- const hf = await this.healthFactor();
1217
- amount = hf.borrowed;
1221
+ const hf2 = await this.healthFactor();
1222
+ amount = hf2.borrowed;
1218
1223
  if (amount <= 0) {
1219
1224
  throw new T2000Error("NO_COLLATERAL", "No outstanding borrow to repay");
1220
1225
  }
1221
1226
  } else {
1222
1227
  amount = params.amount;
1223
1228
  }
1224
- await this.ensureGas();
1225
- const result = await repay(this.client, this.keypair, amount);
1226
- this.emitBalanceChange("USDC", amount, "repay", result.tx);
1227
- return { ...result, gasMethod: this._lastGasMethod };
1229
+ const repayAmount = amount;
1230
+ const gasResult = await executeWithGas(
1231
+ this.client,
1232
+ this.keypair,
1233
+ () => buildRepayTx(this.client, this._address, repayAmount)
1234
+ );
1235
+ const hf = await getHealthFactor(this.client, this.keypair);
1236
+ this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
1237
+ return {
1238
+ success: true,
1239
+ tx: gasResult.digest,
1240
+ amount: repayAmount,
1241
+ remainingDebt: hf.borrowed,
1242
+ gasCost: gasResult.gasCostSui,
1243
+ gasMethod: gasResult.gasMethod
1244
+ };
1228
1245
  }
1229
1246
  async maxBorrow() {
1230
1247
  return maxBorrowAmount(this.client, this.keypair);
@@ -1249,28 +1266,51 @@ var T2000 = class _T2000 extends EventEmitter {
1249
1266
  throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
1250
1267
  }
1251
1268
  const fee = calculateFee("swap", params.amount);
1252
- await this.ensureGas();
1253
- const result = await executeSwap({
1254
- client: this.client,
1255
- keypair: this.keypair,
1256
- fromAsset,
1257
- toAsset,
1258
- amount: params.amount,
1259
- maxSlippageBps: params.maxSlippage ? params.maxSlippage * 100 : void 0
1269
+ const swapAmount = params.amount;
1270
+ const slippageBps = params.maxSlippage ? params.maxSlippage * 100 : void 0;
1271
+ let swapMeta = { estimatedOut: 0, toDecimals: 0 };
1272
+ const gasResult = await executeWithGas(this.client, this.keypair, async () => {
1273
+ const built = await buildSwapTx({
1274
+ client: this.client,
1275
+ address: this._address,
1276
+ fromAsset,
1277
+ toAsset,
1278
+ amount: swapAmount,
1279
+ maxSlippageBps: slippageBps
1280
+ });
1281
+ swapMeta = { estimatedOut: built.estimatedOut, toDecimals: built.toDecimals };
1282
+ return built.tx;
1260
1283
  });
1261
- reportFee(this._address, "swap", fee.amount, fee.rate, result.digest);
1262
- this.emitBalanceChange(result.fromAsset, result.fromAmount, "swap", result.digest);
1284
+ const toInfo = SUPPORTED_ASSETS[toAsset];
1285
+ const txDetail = await this.client.getTransactionBlock({
1286
+ digest: gasResult.digest,
1287
+ options: { showBalanceChanges: true }
1288
+ });
1289
+ let actualReceived = 0;
1290
+ if (txDetail.balanceChanges) {
1291
+ for (const change of txDetail.balanceChanges) {
1292
+ if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === this._address) {
1293
+ const amt = Number(change.amount) / 10 ** toInfo.decimals;
1294
+ if (amt > 0) actualReceived += amt;
1295
+ }
1296
+ }
1297
+ }
1298
+ const expectedOutput = swapMeta.estimatedOut / 10 ** swapMeta.toDecimals;
1299
+ if (actualReceived === 0) actualReceived = expectedOutput;
1300
+ const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
1301
+ reportFee(this._address, "swap", fee.amount, fee.rate, gasResult.digest);
1302
+ this.emitBalanceChange(fromAsset, swapAmount, "swap", gasResult.digest);
1263
1303
  return {
1264
1304
  success: true,
1265
- tx: result.digest,
1266
- fromAmount: result.fromAmount,
1267
- fromAsset: result.fromAsset,
1268
- toAmount: result.toAmount,
1269
- toAsset: result.toAsset,
1270
- priceImpact: result.priceImpact,
1305
+ tx: gasResult.digest,
1306
+ fromAmount: swapAmount,
1307
+ fromAsset,
1308
+ toAmount: actualReceived,
1309
+ toAsset,
1310
+ priceImpact,
1271
1311
  fee: fee.amount,
1272
- gasCost: result.gasCost,
1273
- gasMethod: this._lastGasMethod
1312
+ gasCost: gasResult.gasCostSui,
1313
+ gasMethod: gasResult.gasMethod
1274
1314
  };
1275
1315
  }
1276
1316
  async swapQuote(params) {
@@ -1401,115 +1441,6 @@ function parseMoveAbort(errorStr) {
1401
1441
  return { reason: errorStr };
1402
1442
  }
1403
1443
 
1404
- // src/gas/manager.ts
1405
- function extractGasCost3(effects) {
1406
- if (!effects?.gasUsed) return 0;
1407
- return (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9;
1408
- }
1409
- async function getSuiBalance(client, address) {
1410
- const bal = await client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type });
1411
- return BigInt(bal.totalBalance);
1412
- }
1413
- async function trySelfFunded(client, keypair, tx) {
1414
- const address = keypair.getPublicKey().toSuiAddress();
1415
- const suiBalance = await getSuiBalance(client, address);
1416
- if (suiBalance < AUTO_TOPUP_THRESHOLD) return null;
1417
- tx.setSender(address);
1418
- const result = await client.signAndExecuteTransaction({
1419
- signer: keypair,
1420
- transaction: tx,
1421
- options: { showEffects: true }
1422
- });
1423
- await client.waitForTransaction({ digest: result.digest });
1424
- return {
1425
- digest: result.digest,
1426
- effects: result.effects,
1427
- gasMethod: "self-funded",
1428
- gasCostSui: extractGasCost3(result.effects)
1429
- };
1430
- }
1431
- async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
1432
- const address = keypair.getPublicKey().toSuiAddress();
1433
- const canTopUp = await shouldAutoTopUp(client, address);
1434
- if (!canTopUp) return null;
1435
- try {
1436
- await executeAutoTopUp(client, keypair);
1437
- } catch {
1438
- return null;
1439
- }
1440
- tx.setSender(address);
1441
- const result = await client.signAndExecuteTransaction({
1442
- signer: keypair,
1443
- transaction: tx,
1444
- options: { showEffects: true }
1445
- });
1446
- await client.waitForTransaction({ digest: result.digest });
1447
- return {
1448
- digest: result.digest,
1449
- effects: result.effects,
1450
- gasMethod: "auto-topup",
1451
- gasCostSui: extractGasCost3(result.effects)
1452
- };
1453
- }
1454
- async function trySponsored(client, keypair, tx) {
1455
- const address = keypair.getPublicKey().toSuiAddress();
1456
- tx.setSender(address);
1457
- let txBytes;
1458
- try {
1459
- txBytes = await tx.build({ client, onlyTransactionKind: true });
1460
- } catch {
1461
- return null;
1462
- }
1463
- const txBytesBase64 = Buffer.from(txBytes).toString("base64");
1464
- let sponsoredResult;
1465
- try {
1466
- sponsoredResult = await requestGasSponsorship(txBytesBase64, address);
1467
- } catch {
1468
- return null;
1469
- }
1470
- const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
1471
- const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
1472
- const result = await client.executeTransactionBlock({
1473
- transactionBlock: sponsoredResult.txBytes,
1474
- signature: [agentSig, sponsoredResult.sponsorSignature],
1475
- options: { showEffects: true }
1476
- });
1477
- await client.waitForTransaction({ digest: result.digest });
1478
- const gasCost = extractGasCost3(result.effects);
1479
- reportGasUsage(address, result.digest, gasCost, 0, sponsoredResult.type);
1480
- return {
1481
- digest: result.digest,
1482
- effects: result.effects,
1483
- gasMethod: "sponsored",
1484
- gasCostSui: gasCost
1485
- };
1486
- }
1487
- async function executeWithGas(client, keypair, buildTx) {
1488
- try {
1489
- const tx = await buildTx();
1490
- const result = await trySelfFunded(client, keypair, tx);
1491
- if (result) return result;
1492
- } catch {
1493
- }
1494
- try {
1495
- const tx = await buildTx();
1496
- const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
1497
- if (result) return result;
1498
- } catch {
1499
- }
1500
- try {
1501
- const tx = await buildTx();
1502
- const result = await trySponsored(client, keypair, tx);
1503
- if (result) return result;
1504
- } catch {
1505
- }
1506
- throw new T2000Error(
1507
- "INSUFFICIENT_GAS",
1508
- "No SUI for gas and Gas Station unavailable. Fund your wallet with SUI or USDC.",
1509
- { reason: "all_gas_methods_exhausted" }
1510
- );
1511
- }
1512
-
1513
1444
  export { BPS_DENOMINATOR, CLOCK_ID, DEFAULT_NETWORK, MIST_PER_SUI, SUI_DECIMALS, SUPPORTED_ASSETS, T2000, T2000Error, USDC_DECIMALS, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatSui, formatUsd, generateKeypair, getAddress, getGasStatus, getPoolPrice, getRates, getSwapQuote, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
1514
1445
  //# sourceMappingURL=index.js.map
1515
1446
  //# sourceMappingURL=index.js.map