@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.cjs CHANGED
@@ -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,127 @@ 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
+ const errors = [];
949
+ try {
950
+ const tx = await buildTx();
951
+ const result = await trySelfFunded(client, keypair, tx);
952
+ if (result) return result;
953
+ errors.push("self-funded: SUI below threshold");
954
+ } catch (err) {
955
+ errors.push(`self-funded: ${err instanceof Error ? err.message : String(err)}`);
956
+ }
957
+ try {
958
+ const tx = await buildTx();
959
+ const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
960
+ if (result) return result;
961
+ errors.push("auto-topup: not eligible (low USDC or sufficient SUI)");
962
+ } catch (err) {
963
+ errors.push(`auto-topup: ${err instanceof Error ? err.message : String(err)}`);
964
+ }
965
+ try {
966
+ const tx = await buildTx();
967
+ const result = await trySponsored(client, keypair, tx);
968
+ if (result) return result;
969
+ errors.push("sponsored: returned null");
970
+ } catch (err) {
971
+ errors.push(`sponsored: ${err instanceof Error ? err.message : String(err)}`);
972
+ }
973
+ throw new T2000Error(
974
+ "INSUFFICIENT_GAS",
975
+ `No SUI for gas and Gas Station unavailable. Fund your wallet with SUI or USDC. [${errors.join(" | ")}]`,
976
+ { reason: "all_gas_methods_exhausted", errors }
977
+ );
978
+ }
979
+
987
980
  // src/t2000.ts
988
981
  var T2000 = class _T2000 extends eventemitter3.EventEmitter {
989
982
  keypair;
990
983
  client;
991
984
  _address;
992
- _lastGasMethod = "self-funded";
993
985
  constructor(keypair, client) {
994
986
  super();
995
987
  this.keypair = keypair;
@@ -1043,29 +1035,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1043
1035
  return { agent, address, sponsored };
1044
1036
  }
1045
1037
  // -- 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
1038
  /** SuiClient used by this agent — exposed for x402 and other integrations. */
1070
1039
  get suiClient() {
1071
1040
  return this.client;
@@ -1083,24 +1052,23 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1083
1052
  if (!(asset in SUPPORTED_ASSETS)) {
1084
1053
  throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
1085
1054
  }
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
- });
1055
+ const sendAmount = params.amount;
1056
+ const sendTo = params.to;
1057
+ const gasResult = await executeWithGas(
1058
+ this.client,
1059
+ this.keypair,
1060
+ () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset })
1061
+ );
1094
1062
  const balance = await this.balance();
1095
- this.emitBalanceChange(asset, params.amount, "send", result.digest);
1063
+ this.emitBalanceChange(asset, sendAmount, "send", gasResult.digest);
1096
1064
  return {
1097
1065
  success: true,
1098
- tx: result.digest,
1099
- amount: params.amount,
1066
+ tx: gasResult.digest,
1067
+ amount: sendAmount,
1100
1068
  to: params.to,
1101
- gasCost: result.gasCost,
1069
+ gasCost: gasResult.gasCostSui,
1102
1070
  gasCostUnit: "SUI",
1103
- gasMethod: this._lastGasMethod,
1071
+ gasMethod: gasResult.gasMethod,
1104
1072
  balance
1105
1073
  };
1106
1074
  }
@@ -1156,11 +1124,24 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1156
1124
  amount = params.amount;
1157
1125
  }
1158
1126
  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 };
1127
+ const saveAmount = amount;
1128
+ const gasResult = await executeWithGas(
1129
+ this.client,
1130
+ this.keypair,
1131
+ () => buildSaveTx(this.client, this._address, saveAmount)
1132
+ );
1133
+ const rates = await getRates(this.client);
1134
+ reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
1135
+ this.emitBalanceChange("USDC", saveAmount, "save", gasResult.digest);
1136
+ return {
1137
+ success: true,
1138
+ tx: gasResult.digest,
1139
+ amount: saveAmount,
1140
+ apy: rates.USDC.saveApy,
1141
+ fee: fee.amount,
1142
+ gasCost: gasResult.gasCostSui,
1143
+ gasMethod: gasResult.gasMethod
1144
+ };
1164
1145
  }
1165
1146
  async withdraw(params) {
1166
1147
  let amount;
@@ -1188,10 +1169,21 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1188
1169
  }
1189
1170
  }
1190
1171
  }
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 };
1172
+ const withdrawAmount = amount;
1173
+ let effectiveAmount = withdrawAmount;
1174
+ const gasResult = await executeWithGas(this.client, this.keypair, async () => {
1175
+ const built = await buildWithdrawTx(this.client, this._address, withdrawAmount);
1176
+ effectiveAmount = built.effectiveAmount;
1177
+ return built.tx;
1178
+ });
1179
+ this.emitBalanceChange("USDC", effectiveAmount, "withdraw", gasResult.digest);
1180
+ return {
1181
+ success: true,
1182
+ tx: gasResult.digest,
1183
+ amount: effectiveAmount,
1184
+ gasCost: gasResult.gasCostSui,
1185
+ gasMethod: gasResult.gasMethod
1186
+ };
1195
1187
  }
1196
1188
  async maxWithdraw() {
1197
1189
  return maxWithdrawAmount(this.client, this.keypair);
@@ -1206,27 +1198,52 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1206
1198
  });
1207
1199
  }
1208
1200
  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 };
1201
+ const borrowAmount = params.amount;
1202
+ const gasResult = await executeWithGas(
1203
+ this.client,
1204
+ this.keypair,
1205
+ () => buildBorrowTx(this.client, this._address, borrowAmount)
1206
+ );
1207
+ const hf = await getHealthFactor(this.client, this.keypair);
1208
+ reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
1209
+ this.emitBalanceChange("USDC", borrowAmount, "borrow", gasResult.digest);
1210
+ return {
1211
+ success: true,
1212
+ tx: gasResult.digest,
1213
+ amount: borrowAmount,
1214
+ fee: fee.amount,
1215
+ healthFactor: hf.healthFactor,
1216
+ gasCost: gasResult.gasCostSui,
1217
+ gasMethod: gasResult.gasMethod
1218
+ };
1214
1219
  }
1215
1220
  async repay(params) {
1216
1221
  let amount;
1217
1222
  if (params.amount === "all") {
1218
- const hf = await this.healthFactor();
1219
- amount = hf.borrowed;
1223
+ const hf2 = await this.healthFactor();
1224
+ amount = hf2.borrowed;
1220
1225
  if (amount <= 0) {
1221
1226
  throw new T2000Error("NO_COLLATERAL", "No outstanding borrow to repay");
1222
1227
  }
1223
1228
  } else {
1224
1229
  amount = params.amount;
1225
1230
  }
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 };
1231
+ const repayAmount = amount;
1232
+ const gasResult = await executeWithGas(
1233
+ this.client,
1234
+ this.keypair,
1235
+ () => buildRepayTx(this.client, this._address, repayAmount)
1236
+ );
1237
+ const hf = await getHealthFactor(this.client, this.keypair);
1238
+ this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
1239
+ return {
1240
+ success: true,
1241
+ tx: gasResult.digest,
1242
+ amount: repayAmount,
1243
+ remainingDebt: hf.borrowed,
1244
+ gasCost: gasResult.gasCostSui,
1245
+ gasMethod: gasResult.gasMethod
1246
+ };
1230
1247
  }
1231
1248
  async maxBorrow() {
1232
1249
  return maxBorrowAmount(this.client, this.keypair);
@@ -1251,28 +1268,51 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1251
1268
  throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
1252
1269
  }
1253
1270
  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
1271
+ const swapAmount = params.amount;
1272
+ const slippageBps = params.maxSlippage ? params.maxSlippage * 100 : void 0;
1273
+ let swapMeta = { estimatedOut: 0, toDecimals: 0 };
1274
+ const gasResult = await executeWithGas(this.client, this.keypair, async () => {
1275
+ const built = await buildSwapTx({
1276
+ client: this.client,
1277
+ address: this._address,
1278
+ fromAsset,
1279
+ toAsset,
1280
+ amount: swapAmount,
1281
+ maxSlippageBps: slippageBps
1282
+ });
1283
+ swapMeta = { estimatedOut: built.estimatedOut, toDecimals: built.toDecimals };
1284
+ return built.tx;
1262
1285
  });
1263
- reportFee(this._address, "swap", fee.amount, fee.rate, result.digest);
1264
- this.emitBalanceChange(result.fromAsset, result.fromAmount, "swap", result.digest);
1286
+ const toInfo = SUPPORTED_ASSETS[toAsset];
1287
+ const txDetail = await this.client.getTransactionBlock({
1288
+ digest: gasResult.digest,
1289
+ options: { showBalanceChanges: true }
1290
+ });
1291
+ let actualReceived = 0;
1292
+ if (txDetail.balanceChanges) {
1293
+ for (const change of txDetail.balanceChanges) {
1294
+ if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === this._address) {
1295
+ const amt = Number(change.amount) / 10 ** toInfo.decimals;
1296
+ if (amt > 0) actualReceived += amt;
1297
+ }
1298
+ }
1299
+ }
1300
+ const expectedOutput = swapMeta.estimatedOut / 10 ** swapMeta.toDecimals;
1301
+ if (actualReceived === 0) actualReceived = expectedOutput;
1302
+ const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
1303
+ reportFee(this._address, "swap", fee.amount, fee.rate, gasResult.digest);
1304
+ this.emitBalanceChange(fromAsset, swapAmount, "swap", gasResult.digest);
1265
1305
  return {
1266
1306
  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,
1307
+ tx: gasResult.digest,
1308
+ fromAmount: swapAmount,
1309
+ fromAsset,
1310
+ toAmount: actualReceived,
1311
+ toAsset,
1312
+ priceImpact,
1273
1313
  fee: fee.amount,
1274
- gasCost: result.gasCost,
1275
- gasMethod: this._lastGasMethod
1314
+ gasCost: gasResult.gasCostSui,
1315
+ gasMethod: gasResult.gasMethod
1276
1316
  };
1277
1317
  }
1278
1318
  async swapQuote(params) {
@@ -1403,115 +1443,6 @@ function parseMoveAbort(errorStr) {
1403
1443
  return { reason: errorStr };
1404
1444
  }
1405
1445
 
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
1446
  exports.BPS_DENOMINATOR = BPS_DENOMINATOR;
1516
1447
  exports.CLOCK_ID = CLOCK_ID;
1517
1448
  exports.DEFAULT_NETWORK = DEFAULT_NETWORK;