@t2000/sdk 2.13.2 → 2.14.1

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
@@ -701,22 +701,18 @@ async function buildStakeVSuiTx(_client, address, amountMist) {
701
701
  return tx;
702
702
  }
703
703
  async function buildUnstakeVSuiTx(client, address, amountMist) {
704
- const coins = await fetchCoinsByType(client, address, exports.VSUI_TYPE);
705
- if (coins.length === 0) {
704
+ const balResp = await client.getBalance({ owner: address, coinType: exports.VSUI_TYPE });
705
+ const totalBalance = BigInt(balResp.totalBalance);
706
+ if (totalBalance === 0n) {
706
707
  throw new Error("No vSUI found in wallet.");
707
708
  }
708
709
  const tx = new transactions.Transaction();
709
710
  tx.setSender(address);
710
- const primary = tx.object(coins[0].coinObjectId);
711
- if (coins.length > 1) {
712
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
713
- }
714
- let vSuiCoin;
715
- if (amountMist === "all") {
716
- vSuiCoin = primary;
717
- } else {
718
- [vSuiCoin] = tx.splitCoins(primary, [amountMist]);
711
+ const requested = amountMist === "all" ? totalBalance : amountMist;
712
+ if (requested > totalBalance) {
713
+ throw new Error(`Insufficient vSUI: need ${requested} MIST, have ${totalBalance}`);
719
714
  }
715
+ const vSuiCoin = transactions.coinWithBalance({ type: exports.VSUI_TYPE, balance: requested })(tx);
720
716
  const [suiCoin] = tx.moveCall({
721
717
  target: `${exports.VOLO_PKG}::stake_pool::unstake`,
722
718
  arguments: [
@@ -737,19 +733,16 @@ async function addStakeVSuiToTx(tx, client, address, input) {
737
733
  if (input.inputCoin) {
738
734
  suiCoin = input.inputCoin;
739
735
  } else {
740
- const coins = await fetchCoinsByType(client, address, exports.SUI_TYPE);
741
- if (coins.length === 0) {
742
- throw new Error("No SUI coins found in wallet");
743
- }
744
- const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
736
+ const balResp = await client.getBalance({ owner: address, coinType: exports.SUI_TYPE });
737
+ const totalBalance = BigInt(balResp.totalBalance);
745
738
  if (totalBalance < input.amountMist) {
746
739
  throw new Error(`Insufficient SUI: need ${input.amountMist} MIST, have ${totalBalance}`);
747
740
  }
748
- const primary = tx.object(coins[0].coinObjectId);
749
- if (coins.length > 1) {
750
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
751
- }
752
- [suiCoin] = tx.splitCoins(primary, [input.amountMist]);
741
+ suiCoin = transactions.coinWithBalance({
742
+ type: exports.SUI_TYPE,
743
+ balance: input.amountMist,
744
+ useGasCoin: false
745
+ })(tx);
753
746
  }
754
747
  const [vSuiCoin] = tx.moveCall({
755
748
  target: `${exports.VOLO_PKG}::stake_pool::stake`,
@@ -771,19 +764,16 @@ async function addUnstakeVSuiToTx(tx, client, address, input) {
771
764
  [vSuiCoin] = tx.splitCoins(input.inputCoin, [input.amountMist]);
772
765
  }
773
766
  } else {
774
- const coins = await fetchCoinsByType(client, address, exports.VSUI_TYPE);
775
- if (coins.length === 0) {
767
+ const balResp = await client.getBalance({ owner: address, coinType: exports.VSUI_TYPE });
768
+ const totalBalance = BigInt(balResp.totalBalance);
769
+ if (totalBalance === 0n) {
776
770
  throw new Error("No vSUI found in wallet.");
777
771
  }
778
- const primary = tx.object(coins[0].coinObjectId);
779
- if (coins.length > 1) {
780
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
781
- }
782
- if (input.amountMist === "all") {
783
- vSuiCoin = primary;
784
- } else {
785
- [vSuiCoin] = tx.splitCoins(primary, [input.amountMist]);
772
+ const requested = input.amountMist === "all" ? totalBalance : input.amountMist;
773
+ if (requested > totalBalance) {
774
+ throw new Error(`Insufficient vSUI: need ${requested} MIST, have ${totalBalance}`);
786
775
  }
776
+ vSuiCoin = transactions.coinWithBalance({ type: exports.VSUI_TYPE, balance: requested })(tx);
787
777
  }
788
778
  const [suiCoin] = tx.moveCall({
789
779
  target: `${exports.VOLO_PKG}::stake_pool::unstake`,
@@ -796,22 +786,6 @@ async function addUnstakeVSuiToTx(tx, client, address, input) {
796
786
  });
797
787
  return { coin: suiCoin, effectiveAmountMist: input.amountMist };
798
788
  }
799
- async function fetchCoinsByType(client, owner, coinType) {
800
- const all = [];
801
- let cursor;
802
- let hasNext = true;
803
- while (hasNext) {
804
- const page = await client.getCoins({
805
- owner,
806
- coinType,
807
- cursor: cursor ?? void 0
808
- });
809
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
810
- cursor = page.nextCursor;
811
- hasNext = page.hasNextPage;
812
- }
813
- return all;
814
- }
815
789
  exports.VOLO_PKG = void 0; exports.VOLO_POOL = void 0; exports.VOLO_METADATA = void 0; exports.VSUI_TYPE = void 0; var SUI_SYSTEM_STATE, MIN_STAKE_MIST, VOLO_STATS_URL;
816
790
  var init_volo = __esm({
817
791
  "src/protocols/volo.ts"() {
@@ -833,52 +807,29 @@ __export(coinSelection_exports, {
833
807
  selectAndSplitCoin: () => selectAndSplitCoin,
834
808
  selectSuiCoin: () => selectSuiCoin
835
809
  });
836
- function getMergeCache(tx) {
837
- let cache = ptbMergeCache.get(tx);
838
- if (!cache) {
839
- cache = /* @__PURE__ */ new Map();
840
- ptbMergeCache.set(tx, cache);
841
- }
842
- return cache;
843
- }
844
810
  async function fetchAllCoins(client, owner, coinType) {
845
- const ids = [];
846
- let totalBalance = 0n;
847
- let cursor;
848
- let hasNext = true;
849
- while (hasNext) {
850
- const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
851
- for (const c of page.data) {
852
- ids.push(c.coinObjectId);
853
- totalBalance += BigInt(c.balance);
854
- }
855
- cursor = page.nextCursor;
856
- hasNext = page.hasNextPage;
857
- }
858
- return { ids, totalBalance };
811
+ const [balance, ids] = await Promise.all([
812
+ client.getBalance({ owner, coinType }),
813
+ (async () => {
814
+ const out = [];
815
+ let cursor;
816
+ let hasNext = true;
817
+ while (hasNext) {
818
+ const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
819
+ for (const c of page.data) out.push(c.coinObjectId);
820
+ cursor = page.nextCursor;
821
+ hasNext = page.hasNextPage;
822
+ }
823
+ return out;
824
+ })()
825
+ ]);
826
+ return { ids, totalBalance: BigInt(balance.totalBalance) };
859
827
  }
860
828
  async function selectAndSplitCoin(tx, client, owner, coinType, amount, options = {}) {
861
- const cache = getMergeCache(tx);
862
- const cacheKey = `${owner}|${coinType}`;
863
- const cached = cache.get(cacheKey);
864
- if (cached) {
865
- const allowSwapAll2 = options.allowSwapAll ?? true;
866
- if (amount !== "all" && amount > cached.remaining && !allowSwapAll2) {
867
- throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance for ${coinType}`, {
868
- available: cached.remaining.toString(),
869
- required: amount.toString()
870
- });
871
- }
872
- const requested2 = amount === "all" ? cached.remaining : amount;
873
- const swapAll2 = amount === "all" || requested2 >= cached.remaining;
874
- const effectiveAmount2 = swapAll2 ? cached.remaining : requested2;
875
- const coin2 = swapAll2 ? cached.primary : tx.splitCoins(cached.primary, [effectiveAmount2])[0];
876
- cached.remaining = swapAll2 ? 0n : cached.remaining - effectiveAmount2;
877
- return { coin: coin2, effectiveAmount: effectiveAmount2, swapAll: swapAll2 };
878
- }
879
- const { ids, totalBalance } = await fetchAllCoins(client, owner, coinType);
880
- if (ids.length === 0) {
881
- throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No coins found for ${coinType}`);
829
+ const balanceResp = await client.getBalance({ owner, coinType });
830
+ const totalBalance = BigInt(balanceResp.totalBalance);
831
+ if (totalBalance === 0n) {
832
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No balance found for ${coinType}`);
882
833
  }
883
834
  const allowSwapAll = options.allowSwapAll ?? true;
884
835
  if (amount !== "all" && amount > totalBalance && !allowSwapAll) {
@@ -890,30 +841,29 @@ async function selectAndSplitCoin(tx, client, owner, coinType, amount, options =
890
841
  const requested = amount === "all" ? totalBalance : amount;
891
842
  const swapAll = amount === "all" || requested >= totalBalance;
892
843
  const effectiveAmount = swapAll ? totalBalance : requested;
893
- const primary = tx.object(ids[0]);
894
- if (ids.length > 1) {
895
- tx.mergeCoins(primary, ids.slice(1).map((id) => tx.object(id)));
896
- }
897
- const coin = swapAll ? primary : tx.splitCoins(primary, [effectiveAmount])[0];
898
- cache.set(cacheKey, {
899
- primary,
900
- remaining: swapAll ? 0n : totalBalance - effectiveAmount
901
- });
844
+ const coin = transactions.coinWithBalance({ type: coinType, balance: effectiveAmount })(tx);
902
845
  return { coin, effectiveAmount, swapAll };
903
846
  }
904
847
  async function selectSuiCoin(tx, client, owner, amountMist, sponsoredContext) {
905
848
  if (sponsoredContext) {
906
849
  const { SUI_TYPE: SUI_TYPE2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
907
- return selectAndSplitCoin(tx, client, owner, SUI_TYPE2, amountMist);
850
+ const balanceResp = await client.getBalance({ owner, coinType: SUI_TYPE2 });
851
+ const totalBalance = BigInt(balanceResp.totalBalance);
852
+ if (totalBalance < amountMist) {
853
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient SUI balance`, {
854
+ available: totalBalance.toString(),
855
+ required: amountMist.toString()
856
+ });
857
+ }
858
+ const coin2 = transactions.coinWithBalance({ type: SUI_TYPE2, balance: amountMist, useGasCoin: false })(tx);
859
+ return { coin: coin2, effectiveAmount: amountMist, swapAll: false };
908
860
  }
909
861
  const [coin] = tx.splitCoins(tx.gas, [amountMist]);
910
862
  return { coin, effectiveAmount: amountMist, swapAll: false };
911
863
  }
912
- var ptbMergeCache;
913
864
  var init_coinSelection = __esm({
914
865
  "src/wallet/coinSelection.ts"() {
915
866
  init_errors();
916
- ptbMergeCache = /* @__PURE__ */ new WeakMap();
917
867
  }
918
868
  });
919
869
 
@@ -1121,6 +1071,17 @@ async function addSwapToTx(tx, client, address, input) {
1121
1071
  if (input.inputCoin) {
1122
1072
  inputCoin = input.inputCoin;
1123
1073
  effectiveRaw = requestedRaw;
1074
+ } else if (fromType === "0x2::sui::SUI") {
1075
+ const { selectSuiCoin: selectSuiCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
1076
+ const result = await selectSuiCoin2(
1077
+ tx,
1078
+ client,
1079
+ address,
1080
+ requestedRaw,
1081
+ input.sponsoredContext ?? false
1082
+ );
1083
+ inputCoin = result.coin;
1084
+ effectiveRaw = result.effectiveAmount;
1124
1085
  } else {
1125
1086
  const { selectAndSplitCoin: selectAndSplitCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
1126
1087
  const result = await selectAndSplitCoin2(tx, client, address, fromType, requestedRaw);
@@ -1527,26 +1488,16 @@ async function buildSendTx({
1527
1488
  const rawAmount = displayToRaw(amount, assetInfo.decimals);
1528
1489
  const tx = new transactions.Transaction();
1529
1490
  tx.setSender(address);
1530
- if (asset === "SUI") {
1531
- const [coin] = tx.splitCoins(tx.gas, [rawAmount]);
1532
- tx.transferObjects([coin], recipient);
1533
- } else {
1534
- const coins = await client.getCoins({ owner: address, coinType: assetInfo.type });
1535
- if (coins.data.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
1536
- const totalBalance = coins.data.reduce((sum, c) => sum + BigInt(c.balance), 0n);
1537
- if (totalBalance < rawAmount) {
1538
- throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
1539
- available: Number(totalBalance) / 10 ** assetInfo.decimals,
1540
- required: amount
1541
- });
1542
- }
1543
- const primaryCoin = tx.object(coins.data[0].coinObjectId);
1544
- if (coins.data.length > 1) {
1545
- tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
1546
- }
1547
- const [sendCoin] = tx.splitCoins(primaryCoin, [rawAmount]);
1548
- tx.transferObjects([sendCoin], recipient);
1491
+ const balanceResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
1492
+ const totalBalance = BigInt(balanceResp.totalBalance);
1493
+ if (totalBalance < rawAmount) {
1494
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
1495
+ available: Number(totalBalance) / 10 ** assetInfo.decimals,
1496
+ required: amount
1497
+ });
1549
1498
  }
1499
+ const sendCoin = asset === "SUI" ? tx.splitCoins(tx.gas, [rawAmount])[0] : transactions.coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx);
1500
+ tx.transferObjects([sendCoin], recipient);
1550
1501
  return tx;
1551
1502
  }
1552
1503
  function addSendToTx(tx, coin, recipient) {
@@ -5493,26 +5444,6 @@ function resolveAssetInfo(asset) {
5493
5444
  }
5494
5445
  throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
5495
5446
  }
5496
- async function fetchCoins(client, owner, coinType) {
5497
- const all = [];
5498
- let cursor;
5499
- let hasNext = true;
5500
- while (hasNext) {
5501
- const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
5502
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
5503
- cursor = page.nextCursor;
5504
- hasNext = page.hasNextPage;
5505
- }
5506
- return all;
5507
- }
5508
- function mergeCoins(tx, coins) {
5509
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
5510
- const primary = tx.object(coins[0].coinObjectId);
5511
- if (coins.length > 1) {
5512
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
5513
- }
5514
- return primary;
5515
- }
5516
5447
  async function getPositions(client, address) {
5517
5448
  try {
5518
5449
  const naviPositions = await $e(address, {
@@ -5603,13 +5534,15 @@ async function buildSaveTx(client, address, amount, options = {}) {
5603
5534
  }
5604
5535
  const asset = options.asset ?? "USDC";
5605
5536
  const assetInfo = resolveAssetInfo(asset);
5606
- const coins = await fetchCoins(client, address, assetInfo.type);
5607
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
5608
- const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
5537
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5538
+ const totalBalance = BigInt(balResp.totalBalance);
5539
+ if (totalBalance === 0n) {
5540
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} balance found`);
5541
+ }
5609
5542
  const tx = new transactions.Transaction();
5610
5543
  tx.setSender(address);
5611
- const coinObj = mergeCoins(tx, coins);
5612
5544
  const rawAmount = Math.min(Number(stableToRaw(amount, assetInfo.decimals)), Number(totalBalance));
5545
+ const coinObj = transactions.coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5613
5546
  try {
5614
5547
  await Ce(tx, assetInfo.type, coinObj, {
5615
5548
  ...sdkOptions(client),
@@ -5741,18 +5674,19 @@ async function buildRepayTx(client, address, amount, options = {}) {
5741
5674
  }
5742
5675
  const asset = options.asset ?? "USDC";
5743
5676
  const assetInfo = resolveAssetInfo(asset);
5744
- const coins = await fetchCoins(client, address, assetInfo.type);
5745
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with. Withdraw some savings first to get cash.`);
5746
- const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
5677
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5678
+ const totalBalance = BigInt(balResp.totalBalance);
5679
+ if (totalBalance === 0n) {
5680
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} to repay with. Withdraw some savings first to get cash.`);
5681
+ }
5747
5682
  const rawRequested = Number(stableToRaw(amount, assetInfo.decimals));
5748
5683
  if (Number(totalBalance) < rawRequested && Number(totalBalance) < 1e3) {
5749
5684
  throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Not enough ${assetInfo.displayName} to repay (need $${amount.toFixed(2)}, wallet has ~$${(Number(totalBalance) / 10 ** assetInfo.decimals).toFixed(4)}). Withdraw some savings first.`);
5750
5685
  }
5751
5686
  const tx = new transactions.Transaction();
5752
5687
  tx.setSender(address);
5753
- const coinObj = mergeCoins(tx, coins);
5754
5688
  const rawAmount = Math.min(rawRequested, Number(totalBalance));
5755
- const [repayCoin] = tx.splitCoins(coinObj, [rawAmount]);
5689
+ const repayCoin = transactions.coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5756
5690
  await refreshOracle(tx, client, address, {
5757
5691
  skipOracle: options.skipOracle,
5758
5692
  skipPythUpdate: options.skipPythUpdate
@@ -6415,10 +6349,10 @@ var ContactManager = class {
6415
6349
  }
6416
6350
  };
6417
6351
  var DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".t2000");
6418
- async function executeTx(client, signer, buildTx) {
6352
+ async function executeTx(client, signer, buildTx, options = {}) {
6419
6353
  const tx = await buildTx();
6420
6354
  tx.setSender(signer.getAddress());
6421
- const txBytes = await tx.build({ client });
6355
+ const txBytes = await tx.build({ client: options.buildClient ?? client });
6422
6356
  const { signature } = await signer.signTransaction(txBytes);
6423
6357
  const result = await client.executeTransactionBlock({
6424
6358
  transactionBlock: txBytes,
@@ -6520,10 +6454,15 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6520
6454
  this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
6521
6455
  const { Mppx } = await import('mppx/client');
6522
6456
  const { sui, USDC } = await import('@suimpp/mpp/client');
6457
+ const { SuiGrpcClient } = await import('@mysten/sui/grpc');
6523
6458
  const client = this.client;
6524
6459
  const signer = this._signer;
6525
6460
  const signerAddress = signer.getAddress();
6526
6461
  let paymentDigest;
6462
+ let gasCostSui = 0;
6463
+ const network = client.network === "testnet" ? "testnet" : "mainnet";
6464
+ const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
6465
+ const grpcClient = new SuiGrpcClient({ baseUrl: grpcBaseUrl, network });
6527
6466
  const mppx = Mppx.create({
6528
6467
  polyfill: false,
6529
6468
  methods: [sui({
@@ -6534,8 +6473,9 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6534
6473
  signPersonalMessage: (bytes) => signer.signPersonalMessage(bytes)
6535
6474
  },
6536
6475
  execute: async (tx) => {
6537
- const result = await executeTx(client, signer, () => tx);
6476
+ const result = await executeTx(client, signer, () => tx, { buildClient: grpcClient });
6538
6477
  paymentDigest = result.digest;
6478
+ gasCostSui = result.gasCostSui;
6539
6479
  return { digest: result.digest };
6540
6480
  }
6541
6481
  })]
@@ -6563,6 +6503,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6563
6503
  body,
6564
6504
  paid,
6565
6505
  cost: paid ? options.maxPrice ?? void 0 : void 0,
6506
+ gasCostSui: paid ? gasCostSui : void 0,
6566
6507
  receipt: paymentDigest ? { reference: paymentDigest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
6567
6508
  };
6568
6509
  }
@@ -6592,8 +6533,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6592
6533
  let vSuiAmount;
6593
6534
  if (params.amount === "all") {
6594
6535
  amountMist = "all";
6595
- const coins = await this._fetchCoins(VSUI_TYPE2);
6596
- vSuiAmount = coins.reduce((sum, c) => sum + Number(c.balance), 0) / 1e9;
6536
+ const bal = await this.client.getBalance({ owner: this._address, coinType: VSUI_TYPE2 });
6537
+ vSuiAmount = Number(bal.totalBalance) / 1e9;
6597
6538
  } else {
6598
6539
  amountMist = BigInt(Math.floor(params.amount * 1e9));
6599
6540
  vSuiAmount = params.amount;
@@ -6649,10 +6590,14 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6649
6590
  if (fromType === "0x2::sui::SUI") {
6650
6591
  [inputCoin] = tx.splitCoins(tx.gas, [rawAmount]);
6651
6592
  } else {
6652
- const coins = await this._fetchCoins(fromType);
6653
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${params.from} coins found.`);
6654
- const merged = this._mergeCoinsInTx(tx, coins);
6655
- [inputCoin] = tx.splitCoins(merged, [rawAmount]);
6593
+ const bal = await this.client.getBalance({ owner: this._address, coinType: fromType });
6594
+ if (BigInt(bal.totalBalance) < rawAmount) {
6595
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${params.from} balance.`, {
6596
+ available: Number(bal.totalBalance) / 10 ** fromDecimals,
6597
+ required: params.amount
6598
+ });
6599
+ }
6600
+ inputCoin = transactions.coinWithBalance({ type: fromType, balance: rawAmount })(tx);
6656
6601
  }
6657
6602
  const outputCoin = await buildSwapTx2({
6658
6603
  walletAddress: this._address,
@@ -6909,11 +6854,15 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6909
6854
  if (canPTB) {
6910
6855
  const tx2 = new transactions.Transaction();
6911
6856
  tx2.setSender(this._address);
6912
- const coins = await this._fetchCoins(assetInfo.type);
6913
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
6914
- const merged = this._mergeCoinsInTx(tx2, coins);
6915
6857
  const rawAmount = BigInt(Math.floor(saveAmount * 10 ** assetInfo.decimals));
6916
- const [inputCoin] = tx2.splitCoins(merged, [rawAmount]);
6858
+ const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
6859
+ if (BigInt(bal.totalBalance) < rawAmount) {
6860
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${assetInfo.displayName} balance`, {
6861
+ available: Number(bal.totalBalance) / 10 ** assetInfo.decimals,
6862
+ required: saveAmount
6863
+ });
6864
+ }
6865
+ const inputCoin = transactions.coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx2);
6917
6866
  await adapter.addSaveToTx(tx2, this._address, inputCoin, asset);
6918
6867
  return tx2;
6919
6868
  }
@@ -7108,52 +7057,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7108
7057
  return 0;
7109
7058
  }
7110
7059
  }
7111
- async _fetchCoins(coinType) {
7112
- const all = [];
7113
- let cursor;
7114
- let hasNext = true;
7115
- while (hasNext) {
7116
- const page = await this.client.getCoins({ owner: this._address, coinType, cursor: cursor ?? void 0 });
7117
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
7118
- cursor = page.nextCursor;
7119
- hasNext = page.hasNextPage;
7120
- }
7121
- if (all.length > 0) {
7122
- this._lastFundDigest = void 0;
7123
- return all;
7124
- }
7125
- if (this._lastFundDigest && coinType === SUPPORTED_ASSETS.USDC.type) {
7126
- const txInfo = await this.client.getTransactionBlock({
7127
- digest: this._lastFundDigest,
7128
- options: { showObjectChanges: true }
7129
- });
7130
- const coinIds = (txInfo.objectChanges ?? []).filter(
7131
- (c) => (c.type === "created" || c.type === "mutated") && "objectType" in c && typeof c.objectType === "string" && c.objectType.includes("0x2::coin::Coin") && c.objectType.includes(coinType)
7132
- ).map((c) => c.objectId);
7133
- if (coinIds.length > 0) {
7134
- const objects = await this.client.multiGetObjects({
7135
- ids: coinIds,
7136
- options: { showContent: true, showOwner: true }
7137
- });
7138
- for (const obj of objects) {
7139
- if (obj.data?.content?.dataType === "moveObject" && obj.data.owner && typeof obj.data.owner === "object" && "AddressOwner" in obj.data.owner && obj.data.owner.AddressOwner === this._address) {
7140
- const fields = obj.data.content.fields;
7141
- all.push({ coinObjectId: obj.data.objectId, balance: String(fields.balance ?? "0") });
7142
- }
7143
- }
7144
- }
7145
- }
7146
- return all;
7147
- }
7148
- _mergeCoinsInTx(tx, coins) {
7149
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
7150
- const primary = tx.object(coins[0].coinObjectId);
7151
- if (coins.length > 1) {
7152
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
7153
- }
7154
- return primary;
7155
- }
7156
- _lastFundDigest;
7157
7060
  async _autoFundFromSavings(shortfall) {
7158
7061
  const positions = await this.positions();
7159
7062
  const savingsTotal = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
@@ -7181,7 +7084,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7181
7084
  if (!usdcReceived) {
7182
7085
  throw new exports.T2000Error("WITHDRAW_FAILED", "Withdraw TX did not produce USDC");
7183
7086
  }
7184
- this._lastFundDigest = result.tx;
7185
7087
  }
7186
7088
  async maxWithdraw() {
7187
7089
  const adapter = await this.resolveLending(void 0, "USDC", "withdraw");
@@ -7250,11 +7152,18 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7250
7152
  if (adapter.addRepayToTx) {
7251
7153
  const tx2 = new transactions.Transaction();
7252
7154
  tx2.setSender(this._address);
7253
- const coins = await this._fetchCoins(targetAssetInfo.type);
7254
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${targetAssetInfo.displayName} coins`);
7255
- const merged = this._mergeCoinsInTx(tx2, coins);
7256
7155
  const raw = BigInt(Math.floor(repayAmount * 10 ** targetAssetInfo.decimals));
7257
- const [repayCoin] = tx2.splitCoins(merged, [raw]);
7156
+ const bal = await this.client.getBalance({
7157
+ owner: this._address,
7158
+ coinType: targetAssetInfo.type
7159
+ });
7160
+ if (BigInt(bal.totalBalance) < raw) {
7161
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${targetAssetInfo.displayName} balance for repayment`, {
7162
+ available: Number(bal.totalBalance) / 10 ** targetAssetInfo.decimals,
7163
+ required: repayAmount
7164
+ });
7165
+ }
7166
+ const repayCoin = transactions.coinWithBalance({ type: targetAssetInfo.type, balance: raw })(tx2);
7258
7167
  await adapter.addRepayToTx(tx2, this._address, repayCoin, target.asset);
7259
7168
  return tx2;
7260
7169
  }
@@ -7285,20 +7194,23 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7285
7194
  if (canPTB) {
7286
7195
  const tx = new transactions.Transaction();
7287
7196
  tx.setSender(this._address);
7288
- const assetMerged = /* @__PURE__ */ new Map();
7289
7197
  const uniqueAssets = Array.from(new Set(entries.map((e) => e.borrow.asset)));
7290
7198
  for (const asset of uniqueAssets) {
7291
7199
  const info = SUPPORTED_ASSETS[asset];
7292
7200
  if (!info) throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Cannot repay unknown asset: ${asset}`);
7293
- const coins = await this._fetchCoins(info.type);
7294
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${info.displayName} coins for repayment`);
7295
- assetMerged.set(asset, this._mergeCoinsInTx(tx, coins));
7201
+ const required = entries.filter((e) => e.borrow.asset === asset).reduce((sum, e) => sum + BigInt(Math.floor(e.borrow.amount * 10 ** info.decimals)), 0n);
7202
+ const bal = await this.client.getBalance({ owner: this._address, coinType: info.type });
7203
+ if (BigInt(bal.totalBalance) < required) {
7204
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${info.displayName} balance for repayment`, {
7205
+ available: Number(bal.totalBalance) / 10 ** info.decimals,
7206
+ required: Number(required) / 10 ** info.decimals
7207
+ });
7208
+ }
7296
7209
  }
7297
7210
  for (const { borrow, adapter } of entries) {
7298
7211
  const info = SUPPORTED_ASSETS[borrow.asset];
7299
- const merged = assetMerged.get(borrow.asset);
7300
7212
  const raw = BigInt(Math.floor(borrow.amount * 10 ** info.decimals));
7301
- const [repayCoin] = tx.splitCoins(merged, [raw]);
7213
+ const repayCoin = transactions.coinWithBalance({ type: info.type, balance: raw })(tx);
7302
7214
  await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
7303
7215
  totalRepaid += borrow.amount;
7304
7216
  }
@@ -7429,8 +7341,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7429
7341
  totalGasCost: 0
7430
7342
  };
7431
7343
  }
7432
- const preUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7433
- const preUsdcRaw = preUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7344
+ const preUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7345
+ const preUsdcRaw = BigInt(preUsdcBal.totalBalance);
7434
7346
  const claimResult = await this.claimRewards();
7435
7347
  if (!claimResult.tx) {
7436
7348
  return {
@@ -7453,8 +7365,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7453
7365
  const decimals = getDecimalsForCoinType(reward.coinType) ?? 9;
7454
7366
  const rawAmount = BigInt(Math.floor(reward.amount * 10 ** decimals));
7455
7367
  if (rawAmount <= 0n) continue;
7456
- const coins = await this._fetchCoins(reward.coinType);
7457
- if (coins.length === 0) continue;
7368
+ const bal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7369
+ if (BigInt(bal.totalBalance) < rawAmount) continue;
7458
7370
  const route = await findSwapRoute2({
7459
7371
  walletAddress: this._address,
7460
7372
  from: reward.coinType,
@@ -7466,10 +7378,9 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7466
7378
  const swapResult = await executeTx(this.client, this._signer, async () => {
7467
7379
  const tx = new transactions.Transaction();
7468
7380
  tx.setSender(this._address);
7469
- const freshCoins = await this._fetchCoins(reward.coinType);
7470
- if (freshCoins.length === 0) return tx;
7471
- const merged = this._mergeCoinsInTx(tx, freshCoins);
7472
- const [inputCoin] = tx.splitCoins(merged, [rawAmount]);
7381
+ const freshBal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7382
+ if (BigInt(freshBal.totalBalance) < rawAmount) return tx;
7383
+ const inputCoin = transactions.coinWithBalance({ type: reward.coinType, balance: rawAmount })(tx);
7473
7384
  const outputCoin = await buildSwapTx2({
7474
7385
  walletAddress: this._address,
7475
7386
  route,
@@ -7486,8 +7397,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7486
7397
  console.warn(`[compound] Failed to swap ${reward.symbol}:`, err instanceof Error ? err.message : err);
7487
7398
  }
7488
7399
  }
7489
- const postUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7490
- const postUsdcRaw = postUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7400
+ const postUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7401
+ const postUsdcRaw = BigInt(postUsdcBal.totalBalance);
7491
7402
  const gainedRaw = postUsdcRaw > preUsdcRaw ? postUsdcRaw - preUsdcRaw : 0n;
7492
7403
  const depositUsdc = Number(gainedRaw) / 10 ** USDC_DEC;
7493
7404
  let depositTx = "";
@@ -7497,10 +7408,11 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7497
7408
  const depositResult = await executeTx(this.client, this._signer, async () => {
7498
7409
  const tx = new transactions.Transaction();
7499
7410
  tx.setSender(this._address);
7500
- const coins = await this._fetchCoins(USDC_TYPE2);
7501
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No USDC coins after swap");
7502
- const merged = this._mergeCoinsInTx(tx, coins);
7503
- const [inputCoin] = tx.splitCoins(merged, [gainedRaw]);
7411
+ const bal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7412
+ if (BigInt(bal.totalBalance) < gainedRaw) {
7413
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No USDC available after swap");
7414
+ }
7415
+ const inputCoin = transactions.coinWithBalance({ type: USDC_TYPE2, balance: gainedRaw })(tx);
7504
7416
  await adapter.addSaveToTx(tx, this._address, inputCoin, "USDC");
7505
7417
  return tx;
7506
7418
  });
@@ -7996,7 +7908,8 @@ var WRITE_APPENDER_REGISTRY = {
7996
7908
  overlayFee: ctx.overlayFee,
7997
7909
  providers,
7998
7910
  inputCoin: ctx.chainedCoin,
7999
- precomputedRoute: input.precomputedRoute
7911
+ precomputedRoute: input.precomputedRoute,
7912
+ sponsoredContext: ctx.sponsoredContext
8000
7913
  });
8001
7914
  if (!ctx.isOutputConsumed) {
8002
7915
  tx.transferObjects([result.coin], ctx.sender);