@t2000/sdk 2.13.1 → 2.14.0

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
 
@@ -1527,26 +1477,16 @@ async function buildSendTx({
1527
1477
  const rawAmount = displayToRaw(amount, assetInfo.decimals);
1528
1478
  const tx = new transactions.Transaction();
1529
1479
  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);
1480
+ const balanceResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
1481
+ const totalBalance = BigInt(balanceResp.totalBalance);
1482
+ if (totalBalance < rawAmount) {
1483
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
1484
+ available: Number(totalBalance) / 10 ** assetInfo.decimals,
1485
+ required: amount
1486
+ });
1549
1487
  }
1488
+ const sendCoin = asset === "SUI" ? tx.splitCoins(tx.gas, [rawAmount])[0] : transactions.coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx);
1489
+ tx.transferObjects([sendCoin], recipient);
1550
1490
  return tx;
1551
1491
  }
1552
1492
  function addSendToTx(tx, coin, recipient) {
@@ -5493,26 +5433,6 @@ function resolveAssetInfo(asset) {
5493
5433
  }
5494
5434
  throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
5495
5435
  }
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
5436
  async function getPositions(client, address) {
5517
5437
  try {
5518
5438
  const naviPositions = await $e(address, {
@@ -5603,13 +5523,15 @@ async function buildSaveTx(client, address, amount, options = {}) {
5603
5523
  }
5604
5524
  const asset = options.asset ?? "USDC";
5605
5525
  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);
5526
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5527
+ const totalBalance = BigInt(balResp.totalBalance);
5528
+ if (totalBalance === 0n) {
5529
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} balance found`);
5530
+ }
5609
5531
  const tx = new transactions.Transaction();
5610
5532
  tx.setSender(address);
5611
- const coinObj = mergeCoins(tx, coins);
5612
5533
  const rawAmount = Math.min(Number(stableToRaw(amount, assetInfo.decimals)), Number(totalBalance));
5534
+ const coinObj = transactions.coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5613
5535
  try {
5614
5536
  await Ce(tx, assetInfo.type, coinObj, {
5615
5537
  ...sdkOptions(client),
@@ -5741,18 +5663,19 @@ async function buildRepayTx(client, address, amount, options = {}) {
5741
5663
  }
5742
5664
  const asset = options.asset ?? "USDC";
5743
5665
  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);
5666
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5667
+ const totalBalance = BigInt(balResp.totalBalance);
5668
+ if (totalBalance === 0n) {
5669
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} to repay with. Withdraw some savings first to get cash.`);
5670
+ }
5747
5671
  const rawRequested = Number(stableToRaw(amount, assetInfo.decimals));
5748
5672
  if (Number(totalBalance) < rawRequested && Number(totalBalance) < 1e3) {
5749
5673
  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
5674
  }
5751
5675
  const tx = new transactions.Transaction();
5752
5676
  tx.setSender(address);
5753
- const coinObj = mergeCoins(tx, coins);
5754
5677
  const rawAmount = Math.min(rawRequested, Number(totalBalance));
5755
- const [repayCoin] = tx.splitCoins(coinObj, [rawAmount]);
5678
+ const repayCoin = transactions.coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5756
5679
  await refreshOracle(tx, client, address, {
5757
5680
  skipOracle: options.skipOracle,
5758
5681
  skipPythUpdate: options.skipPythUpdate
@@ -6415,10 +6338,10 @@ var ContactManager = class {
6415
6338
  }
6416
6339
  };
6417
6340
  var DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".t2000");
6418
- async function executeTx(client, signer, buildTx) {
6341
+ async function executeTx(client, signer, buildTx, options = {}) {
6419
6342
  const tx = await buildTx();
6420
6343
  tx.setSender(signer.getAddress());
6421
- const txBytes = await tx.build({ client });
6344
+ const txBytes = await tx.build({ client: options.buildClient ?? client });
6422
6345
  const { signature } = await signer.signTransaction(txBytes);
6423
6346
  const result = await client.executeTransactionBlock({
6424
6347
  transactionBlock: txBytes,
@@ -6520,9 +6443,15 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6520
6443
  this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
6521
6444
  const { Mppx } = await import('mppx/client');
6522
6445
  const { sui, USDC } = await import('@suimpp/mpp/client');
6446
+ const { SuiGrpcClient } = await import('@mysten/sui/grpc');
6523
6447
  const client = this.client;
6524
6448
  const signer = this._signer;
6525
6449
  const signerAddress = signer.getAddress();
6450
+ let paymentDigest;
6451
+ let gasCostSui = 0;
6452
+ const network = client.network === "testnet" ? "testnet" : "mainnet";
6453
+ const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
6454
+ const grpcClient = new SuiGrpcClient({ baseUrl: grpcBaseUrl, network });
6526
6455
  const mppx = Mppx.create({
6527
6456
  polyfill: false,
6528
6457
  methods: [sui({
@@ -6533,7 +6462,9 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6533
6462
  signPersonalMessage: (bytes) => signer.signPersonalMessage(bytes)
6534
6463
  },
6535
6464
  execute: async (tx) => {
6536
- const result = await executeTx(client, signer, () => tx);
6465
+ const result = await executeTx(client, signer, () => tx, { buildClient: grpcClient });
6466
+ paymentDigest = result.digest;
6467
+ gasCostSui = result.gasCostSui;
6537
6468
  return { digest: result.digest };
6538
6469
  }
6539
6470
  })]
@@ -6552,8 +6483,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6552
6483
  } catch {
6553
6484
  body = null;
6554
6485
  }
6555
- const receiptHeader = response.headers.get("x-payment-receipt");
6556
- const paid = !!receiptHeader;
6486
+ const paid = !!paymentDigest;
6557
6487
  if (paid) {
6558
6488
  this.enforcer.recordUsage(options.maxPrice ?? 1);
6559
6489
  }
@@ -6562,7 +6492,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6562
6492
  body,
6563
6493
  paid,
6564
6494
  cost: paid ? options.maxPrice ?? void 0 : void 0,
6565
- receipt: receiptHeader ? { reference: receiptHeader, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
6495
+ gasCostSui: paid ? gasCostSui : void 0,
6496
+ receipt: paymentDigest ? { reference: paymentDigest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
6566
6497
  };
6567
6498
  }
6568
6499
  // -- VOLO vSUI Staking --
@@ -6591,8 +6522,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6591
6522
  let vSuiAmount;
6592
6523
  if (params.amount === "all") {
6593
6524
  amountMist = "all";
6594
- const coins = await this._fetchCoins(VSUI_TYPE2);
6595
- vSuiAmount = coins.reduce((sum, c) => sum + Number(c.balance), 0) / 1e9;
6525
+ const bal = await this.client.getBalance({ owner: this._address, coinType: VSUI_TYPE2 });
6526
+ vSuiAmount = Number(bal.totalBalance) / 1e9;
6596
6527
  } else {
6597
6528
  amountMist = BigInt(Math.floor(params.amount * 1e9));
6598
6529
  vSuiAmount = params.amount;
@@ -6648,10 +6579,14 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6648
6579
  if (fromType === "0x2::sui::SUI") {
6649
6580
  [inputCoin] = tx.splitCoins(tx.gas, [rawAmount]);
6650
6581
  } else {
6651
- const coins = await this._fetchCoins(fromType);
6652
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${params.from} coins found.`);
6653
- const merged = this._mergeCoinsInTx(tx, coins);
6654
- [inputCoin] = tx.splitCoins(merged, [rawAmount]);
6582
+ const bal = await this.client.getBalance({ owner: this._address, coinType: fromType });
6583
+ if (BigInt(bal.totalBalance) < rawAmount) {
6584
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${params.from} balance.`, {
6585
+ available: Number(bal.totalBalance) / 10 ** fromDecimals,
6586
+ required: params.amount
6587
+ });
6588
+ }
6589
+ inputCoin = transactions.coinWithBalance({ type: fromType, balance: rawAmount })(tx);
6655
6590
  }
6656
6591
  const outputCoin = await buildSwapTx2({
6657
6592
  walletAddress: this._address,
@@ -6908,11 +6843,15 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
6908
6843
  if (canPTB) {
6909
6844
  const tx2 = new transactions.Transaction();
6910
6845
  tx2.setSender(this._address);
6911
- const coins = await this._fetchCoins(assetInfo.type);
6912
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
6913
- const merged = this._mergeCoinsInTx(tx2, coins);
6914
6846
  const rawAmount = BigInt(Math.floor(saveAmount * 10 ** assetInfo.decimals));
6915
- const [inputCoin] = tx2.splitCoins(merged, [rawAmount]);
6847
+ const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
6848
+ if (BigInt(bal.totalBalance) < rawAmount) {
6849
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${assetInfo.displayName} balance`, {
6850
+ available: Number(bal.totalBalance) / 10 ** assetInfo.decimals,
6851
+ required: saveAmount
6852
+ });
6853
+ }
6854
+ const inputCoin = transactions.coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx2);
6916
6855
  await adapter.addSaveToTx(tx2, this._address, inputCoin, asset);
6917
6856
  return tx2;
6918
6857
  }
@@ -7107,52 +7046,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7107
7046
  return 0;
7108
7047
  }
7109
7048
  }
7110
- async _fetchCoins(coinType) {
7111
- const all = [];
7112
- let cursor;
7113
- let hasNext = true;
7114
- while (hasNext) {
7115
- const page = await this.client.getCoins({ owner: this._address, coinType, cursor: cursor ?? void 0 });
7116
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
7117
- cursor = page.nextCursor;
7118
- hasNext = page.hasNextPage;
7119
- }
7120
- if (all.length > 0) {
7121
- this._lastFundDigest = void 0;
7122
- return all;
7123
- }
7124
- if (this._lastFundDigest && coinType === SUPPORTED_ASSETS.USDC.type) {
7125
- const txInfo = await this.client.getTransactionBlock({
7126
- digest: this._lastFundDigest,
7127
- options: { showObjectChanges: true }
7128
- });
7129
- const coinIds = (txInfo.objectChanges ?? []).filter(
7130
- (c) => (c.type === "created" || c.type === "mutated") && "objectType" in c && typeof c.objectType === "string" && c.objectType.includes("0x2::coin::Coin") && c.objectType.includes(coinType)
7131
- ).map((c) => c.objectId);
7132
- if (coinIds.length > 0) {
7133
- const objects = await this.client.multiGetObjects({
7134
- ids: coinIds,
7135
- options: { showContent: true, showOwner: true }
7136
- });
7137
- for (const obj of objects) {
7138
- 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) {
7139
- const fields = obj.data.content.fields;
7140
- all.push({ coinObjectId: obj.data.objectId, balance: String(fields.balance ?? "0") });
7141
- }
7142
- }
7143
- }
7144
- }
7145
- return all;
7146
- }
7147
- _mergeCoinsInTx(tx, coins) {
7148
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
7149
- const primary = tx.object(coins[0].coinObjectId);
7150
- if (coins.length > 1) {
7151
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
7152
- }
7153
- return primary;
7154
- }
7155
- _lastFundDigest;
7156
7049
  async _autoFundFromSavings(shortfall) {
7157
7050
  const positions = await this.positions();
7158
7051
  const savingsTotal = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
@@ -7180,7 +7073,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7180
7073
  if (!usdcReceived) {
7181
7074
  throw new exports.T2000Error("WITHDRAW_FAILED", "Withdraw TX did not produce USDC");
7182
7075
  }
7183
- this._lastFundDigest = result.tx;
7184
7076
  }
7185
7077
  async maxWithdraw() {
7186
7078
  const adapter = await this.resolveLending(void 0, "USDC", "withdraw");
@@ -7249,11 +7141,18 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7249
7141
  if (adapter.addRepayToTx) {
7250
7142
  const tx2 = new transactions.Transaction();
7251
7143
  tx2.setSender(this._address);
7252
- const coins = await this._fetchCoins(targetAssetInfo.type);
7253
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${targetAssetInfo.displayName} coins`);
7254
- const merged = this._mergeCoinsInTx(tx2, coins);
7255
7144
  const raw = BigInt(Math.floor(repayAmount * 10 ** targetAssetInfo.decimals));
7256
- const [repayCoin] = tx2.splitCoins(merged, [raw]);
7145
+ const bal = await this.client.getBalance({
7146
+ owner: this._address,
7147
+ coinType: targetAssetInfo.type
7148
+ });
7149
+ if (BigInt(bal.totalBalance) < raw) {
7150
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${targetAssetInfo.displayName} balance for repayment`, {
7151
+ available: Number(bal.totalBalance) / 10 ** targetAssetInfo.decimals,
7152
+ required: repayAmount
7153
+ });
7154
+ }
7155
+ const repayCoin = transactions.coinWithBalance({ type: targetAssetInfo.type, balance: raw })(tx2);
7257
7156
  await adapter.addRepayToTx(tx2, this._address, repayCoin, target.asset);
7258
7157
  return tx2;
7259
7158
  }
@@ -7284,20 +7183,23 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7284
7183
  if (canPTB) {
7285
7184
  const tx = new transactions.Transaction();
7286
7185
  tx.setSender(this._address);
7287
- const assetMerged = /* @__PURE__ */ new Map();
7288
7186
  const uniqueAssets = Array.from(new Set(entries.map((e) => e.borrow.asset)));
7289
7187
  for (const asset of uniqueAssets) {
7290
7188
  const info = SUPPORTED_ASSETS[asset];
7291
7189
  if (!info) throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Cannot repay unknown asset: ${asset}`);
7292
- const coins = await this._fetchCoins(info.type);
7293
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", `No ${info.displayName} coins for repayment`);
7294
- assetMerged.set(asset, this._mergeCoinsInTx(tx, coins));
7190
+ const required = entries.filter((e) => e.borrow.asset === asset).reduce((sum, e) => sum + BigInt(Math.floor(e.borrow.amount * 10 ** info.decimals)), 0n);
7191
+ const bal = await this.client.getBalance({ owner: this._address, coinType: info.type });
7192
+ if (BigInt(bal.totalBalance) < required) {
7193
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${info.displayName} balance for repayment`, {
7194
+ available: Number(bal.totalBalance) / 10 ** info.decimals,
7195
+ required: Number(required) / 10 ** info.decimals
7196
+ });
7197
+ }
7295
7198
  }
7296
7199
  for (const { borrow, adapter } of entries) {
7297
7200
  const info = SUPPORTED_ASSETS[borrow.asset];
7298
- const merged = assetMerged.get(borrow.asset);
7299
7201
  const raw = BigInt(Math.floor(borrow.amount * 10 ** info.decimals));
7300
- const [repayCoin] = tx.splitCoins(merged, [raw]);
7202
+ const repayCoin = transactions.coinWithBalance({ type: info.type, balance: raw })(tx);
7301
7203
  await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
7302
7204
  totalRepaid += borrow.amount;
7303
7205
  }
@@ -7428,8 +7330,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7428
7330
  totalGasCost: 0
7429
7331
  };
7430
7332
  }
7431
- const preUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7432
- const preUsdcRaw = preUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7333
+ const preUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7334
+ const preUsdcRaw = BigInt(preUsdcBal.totalBalance);
7433
7335
  const claimResult = await this.claimRewards();
7434
7336
  if (!claimResult.tx) {
7435
7337
  return {
@@ -7452,8 +7354,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7452
7354
  const decimals = getDecimalsForCoinType(reward.coinType) ?? 9;
7453
7355
  const rawAmount = BigInt(Math.floor(reward.amount * 10 ** decimals));
7454
7356
  if (rawAmount <= 0n) continue;
7455
- const coins = await this._fetchCoins(reward.coinType);
7456
- if (coins.length === 0) continue;
7357
+ const bal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7358
+ if (BigInt(bal.totalBalance) < rawAmount) continue;
7457
7359
  const route = await findSwapRoute2({
7458
7360
  walletAddress: this._address,
7459
7361
  from: reward.coinType,
@@ -7465,10 +7367,9 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7465
7367
  const swapResult = await executeTx(this.client, this._signer, async () => {
7466
7368
  const tx = new transactions.Transaction();
7467
7369
  tx.setSender(this._address);
7468
- const freshCoins = await this._fetchCoins(reward.coinType);
7469
- if (freshCoins.length === 0) return tx;
7470
- const merged = this._mergeCoinsInTx(tx, freshCoins);
7471
- const [inputCoin] = tx.splitCoins(merged, [rawAmount]);
7370
+ const freshBal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7371
+ if (BigInt(freshBal.totalBalance) < rawAmount) return tx;
7372
+ const inputCoin = transactions.coinWithBalance({ type: reward.coinType, balance: rawAmount })(tx);
7472
7373
  const outputCoin = await buildSwapTx2({
7473
7374
  walletAddress: this._address,
7474
7375
  route,
@@ -7485,8 +7386,8 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7485
7386
  console.warn(`[compound] Failed to swap ${reward.symbol}:`, err instanceof Error ? err.message : err);
7486
7387
  }
7487
7388
  }
7488
- const postUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7489
- const postUsdcRaw = postUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7389
+ const postUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7390
+ const postUsdcRaw = BigInt(postUsdcBal.totalBalance);
7490
7391
  const gainedRaw = postUsdcRaw > preUsdcRaw ? postUsdcRaw - preUsdcRaw : 0n;
7491
7392
  const depositUsdc = Number(gainedRaw) / 10 ** USDC_DEC;
7492
7393
  let depositTx = "";
@@ -7496,10 +7397,11 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
7496
7397
  const depositResult = await executeTx(this.client, this._signer, async () => {
7497
7398
  const tx = new transactions.Transaction();
7498
7399
  tx.setSender(this._address);
7499
- const coins = await this._fetchCoins(USDC_TYPE2);
7500
- if (coins.length === 0) throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No USDC coins after swap");
7501
- const merged = this._mergeCoinsInTx(tx, coins);
7502
- const [inputCoin] = tx.splitCoins(merged, [gainedRaw]);
7400
+ const bal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7401
+ if (BigInt(bal.totalBalance) < gainedRaw) {
7402
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", "No USDC available after swap");
7403
+ }
7404
+ const inputCoin = transactions.coinWithBalance({ type: USDC_TYPE2, balance: gainedRaw })(tx);
7503
7405
  await adapter.addSaveToTx(tx, this._address, inputCoin, "USDC");
7504
7406
  return tx;
7505
7407
  });