@t2000/sdk 0.1.3 → 0.1.5

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