@t2000/sdk 0.6.0 → 0.7.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @t2000/sdk
2
2
 
3
- The complete TypeScript SDK for AI agent bank accounts on Sui. Send USDC, earn yield via NAVI Protocol, swap on Cetus DEX, borrow against collateral — all from a single class.
3
+ The complete TypeScript SDK for AI agent bank accounts on Sui. Send USDC, earn yield via NAVI + Suilend, swap on Cetus DEX, borrow against collateral — all from a single class.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@t2000/sdk)](https://www.npmjs.com/package/@t2000/sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
@@ -37,7 +37,7 @@ console.log(`$${balance.available} USDC available`);
37
37
  // Send USDC
38
38
  await agent.send({ to: '0x...', amount: 10 });
39
39
 
40
- // Save (earn yield via NAVI Protocol)
40
+ // Save (earn yield auto-selects best rate across NAVI + Suilend)
41
41
  await agent.save({ amount: 50, asset: 'USDC' });
42
42
 
43
43
  // Swap USDC → SUI (via Cetus DEX)
@@ -89,7 +89,7 @@ const agent = T2000.fromPrivateKey('suiprivkey1q...');
89
89
  | `agent.address()` | Wallet Sui address | `string` |
90
90
  | `agent.balance()` | Available USDC + savings + gas reserve | `BalanceResponse` |
91
91
  | `agent.send({ to, amount, asset? })` | Transfer USDC to any Sui address | `SendResult` |
92
- | `agent.save({ amount, asset })` | Deposit USDC to NAVI Protocol (earn APY). `amount` can be `'all'`. | `SaveResult` |
92
+ | `agent.save({ amount, asset, protocol? })` | Deposit USDC to savings (earn APY). Auto-selects best rate or specify `protocol`. `amount` can be `'all'`. | `SaveResult` |
93
93
  | `agent.withdraw({ amount, asset })` | Withdraw USDC from savings. `amount` can be `'all'`. | `WithdrawResult` |
94
94
  | `agent.swap({ from, to, amount, maxSlippage? })` | Swap via Cetus CLMM DEX. `maxSlippage` in % (default: 3). | `SwapResult` |
95
95
  | `agent.swapQuote({ from, to, amount })` | Get swap quote without executing | `SwapQuote` |
@@ -103,7 +103,7 @@ const agent = T2000.fromPrivateKey('suiprivkey1q...');
103
103
  |--------|-------------|---------|
104
104
  | `agent.healthFactor()` | Lending health factor | `HealthFactorResult` |
105
105
  | `agent.earnings()` | Yield earned to date | `EarningsResult` |
106
- | `agent.rates()` | Current save/borrow APYs | `RatesResult` |
106
+ | `agent.rates()` | Best save/borrow APYs across protocols | `RatesResult` |
107
107
  | `agent.positions()` | All open DeFi positions | `PositionsResult` |
108
108
  | `agent.fundStatus()` | Complete savings summary | `FundStatusResult` |
109
109
  | `agent.maxWithdraw()` | Max safe withdrawal amount | `MaxWithdrawResult` |
@@ -227,7 +227,7 @@ Every operation (send, save, borrow, repay, withdraw, swap) routes through a 3-s
227
227
 
228
228
  Every transaction result includes a `gasMethod` field (`'self-funded'` | `'auto-topup'` | `'sponsored'`) indicating which strategy was used.
229
229
 
230
- **Architecture:** Each protocol operation (NAVI, Cetus, send) exposes a `buildXxxTx()` function that returns a `Transaction` without executing it. `executeWithGas()` then handles execution with the fallback chain. This separation ensures gas management is consistent across all operations.
230
+ **Architecture:** Each protocol operation (NAVI, Suilend, Cetus, send) exposes a `buildXxxTx()` function that returns a `Transaction` without executing it. `executeWithGas()` then handles execution with the fallback chain. This separation ensures gas management is consistent across all operations.
231
231
 
232
232
  ## Configuration
233
233
 
@@ -264,7 +264,7 @@ Common error codes: `INSUFFICIENT_BALANCE` · `INVALID_ADDRESS` · `INVALID_AMOU
264
264
  ## Testing
265
265
 
266
266
  ```bash
267
- # Run all SDK unit tests (122 tests)
267
+ # Run all SDK unit tests (262 tests)
268
268
  pnpm --filter @t2000/sdk test
269
269
  ```
270
270
 
@@ -5,6 +5,28 @@ var bcs = require('@mysten/sui/bcs');
5
5
  var aggregatorSdk = require('@cetusprotocol/aggregator-sdk');
6
6
  var utils = require('@mysten/sui/utils');
7
7
 
8
+ // src/errors.ts
9
+ var T2000Error = class extends Error {
10
+ code;
11
+ data;
12
+ retryable;
13
+ constructor(code, message, data, retryable = false) {
14
+ super(message);
15
+ this.name = "T2000Error";
16
+ this.code = code;
17
+ this.data = data;
18
+ this.retryable = retryable;
19
+ }
20
+ toJSON() {
21
+ return {
22
+ error: this.code,
23
+ message: this.message,
24
+ ...this.data && { data: this.data },
25
+ retryable: this.retryable
26
+ };
27
+ }
28
+ };
29
+
8
30
  // src/adapters/registry.ts
9
31
  var ProtocolRegistry = class {
10
32
  lending = /* @__PURE__ */ new Map();
@@ -27,7 +49,7 @@ var ProtocolRegistry = class {
27
49
  }
28
50
  }
29
51
  if (candidates.length === 0) {
30
- throw new Error(`No lending adapter supports saving ${asset}`);
52
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `No lending adapter supports saving ${asset}`);
31
53
  }
32
54
  candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
33
55
  return candidates[0];
@@ -45,7 +67,7 @@ var ProtocolRegistry = class {
45
67
  }
46
68
  }
47
69
  if (candidates.length === 0) {
48
- throw new Error(`No lending adapter supports borrowing ${asset}`);
70
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `No lending adapter supports borrowing ${asset}`);
49
71
  }
50
72
  candidates.sort((a, b) => a.rate.borrowApy - b.rate.borrowApy);
51
73
  return candidates[0];
@@ -62,7 +84,7 @@ var ProtocolRegistry = class {
62
84
  }
63
85
  }
64
86
  if (candidates.length === 0) {
65
- throw new Error(`No swap adapter supports ${from} \u2192 ${to}`);
87
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `No swap adapter supports ${from} \u2192 ${to}`);
66
88
  }
67
89
  candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
68
90
  return candidates[0];
@@ -128,28 +150,9 @@ var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523
128
150
  var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
129
151
  process.env.T2000_API_URL ?? "https://api.t2000.ai";
130
152
  var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
131
-
132
- // src/errors.ts
133
- var T2000Error = class extends Error {
134
- code;
135
- data;
136
- retryable;
137
- constructor(code, message, data, retryable = false) {
138
- super(message);
139
- this.name = "T2000Error";
140
- this.code = code;
141
- this.data = data;
142
- this.retryable = retryable;
143
- }
144
- toJSON() {
145
- return {
146
- error: this.code,
147
- message: this.message,
148
- ...this.data && { data: this.data },
149
- retryable: this.retryable
150
- };
151
- }
152
- };
153
+ var CETUS_PACKAGE = "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb";
154
+ var SENTINEL = {
155
+ PACKAGE: "0x88b83f36dafcd5f6dcdcf1d2cb5889b03f61264ab3cee9cae35db7aa940a21b7"};
153
156
 
154
157
  // src/utils/format.ts
155
158
  function usdcToRaw(amount) {
@@ -252,6 +255,24 @@ async function getUsdcPool() {
252
255
  if (!usdc) throw new T2000Error("PROTOCOL_UNAVAILABLE", "USDC pool not found on NAVI");
253
256
  return usdc;
254
257
  }
258
+ function addOracleUpdate(tx, config, pool) {
259
+ const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
260
+ if (!feed) {
261
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `Oracle feed not found for asset ${pool.token?.symbol ?? pool.id}`);
262
+ }
263
+ tx.moveCall({
264
+ target: `${config.oracle.packageId}::oracle_pro::update_single_price_v2`,
265
+ arguments: [
266
+ tx.object(CLOCK),
267
+ tx.object(config.oracle.oracleConfig),
268
+ tx.object(config.oracle.priceOracle),
269
+ tx.object(config.oracle.supraOracleHolder),
270
+ tx.object(feed.pythPriceInfoObject),
271
+ tx.object(config.oracle.switchboardAggregator),
272
+ tx.pure.address(feed.feedId)
273
+ ]
274
+ });
275
+ }
255
276
  function rateToApy(rawRate) {
256
277
  if (!rawRate || rawRate === "0") return 0;
257
278
  return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
@@ -274,7 +295,7 @@ function compoundBalance(rawBalance, currentIndex) {
274
295
  if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
275
296
  const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
276
297
  const half = scale / 2n;
277
- const result = (rawBalance * scale + half) / BigInt(currentIndex);
298
+ const result = (rawBalance * BigInt(currentIndex) + half) / scale;
278
299
  return Number(result) / 10 ** NAVI_BALANCE_DECIMALS;
279
300
  }
280
301
  async function getUserState(client, address) {
@@ -317,6 +338,9 @@ function mergeCoins(tx, coins) {
317
338
  return primary;
318
339
  }
319
340
  async function buildSaveTx(client, address, amount, options = {}) {
341
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
342
+ throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
343
+ }
320
344
  const rawAmount = Number(usdcToRaw(amount));
321
345
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
322
346
  const coins = await fetchCoins(client, address, USDC_TYPE);
@@ -357,6 +381,7 @@ async function buildWithdrawTx(client, address, amount) {
357
381
  const rawAmount = Number(usdcToRaw(effectiveAmount));
358
382
  const tx = new transactions.Transaction();
359
383
  tx.setSender(address);
384
+ addOracleUpdate(tx, config, pool);
360
385
  const [balance] = tx.moveCall({
361
386
  target: `${config.package}::incentive_v3::withdraw_v2`,
362
387
  arguments: [
@@ -381,10 +406,14 @@ async function buildWithdrawTx(client, address, amount) {
381
406
  return { tx, effectiveAmount };
382
407
  }
383
408
  async function buildBorrowTx(client, address, amount, options = {}) {
409
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
410
+ throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
411
+ }
384
412
  const rawAmount = Number(usdcToRaw(amount));
385
413
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
386
414
  const tx = new transactions.Transaction();
387
415
  tx.setSender(address);
416
+ addOracleUpdate(tx, config, pool);
388
417
  const [balance] = tx.moveCall({
389
418
  target: `${config.package}::incentive_v3::borrow_v2`,
390
419
  arguments: [
@@ -405,16 +434,23 @@ async function buildBorrowTx(client, address, amount, options = {}) {
405
434
  arguments: [balance],
406
435
  typeArguments: [pool.suiCoinType]
407
436
  });
437
+ if (options.collectFee) {
438
+ addCollectFeeToTx(tx, borrowedCoin, "borrow");
439
+ }
408
440
  tx.transferObjects([borrowedCoin], address);
409
441
  return tx;
410
442
  }
411
443
  async function buildRepayTx(client, address, amount) {
444
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
445
+ throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
446
+ }
412
447
  const rawAmount = Number(usdcToRaw(amount));
413
448
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
414
449
  const coins = await fetchCoins(client, address, USDC_TYPE);
415
450
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
416
451
  const tx = new transactions.Transaction();
417
452
  tx.setSender(address);
453
+ addOracleUpdate(tx, config, pool);
418
454
  const coinObj = mergeCoins(tx, coins);
419
455
  tx.moveCall({
420
456
  target: `${config.package}::incentive_v3::entry_repay`,
@@ -553,6 +589,22 @@ async function maxBorrowAmount(client, addressOrKeypair) {
553
589
  }
554
590
 
555
591
  // src/adapters/navi.ts
592
+ var descriptor = {
593
+ id: "navi",
594
+ name: "NAVI Protocol",
595
+ packages: [],
596
+ dynamicPackageId: true,
597
+ actionMap: {
598
+ "incentive_v3::entry_deposit": "save",
599
+ "incentive_v3::deposit": "save",
600
+ "incentive_v3::withdraw_v2": "withdraw",
601
+ "incentive_v3::entry_withdraw": "withdraw",
602
+ "incentive_v3::borrow_v2": "borrow",
603
+ "incentive_v3::entry_borrow": "borrow",
604
+ "incentive_v3::entry_repay": "repay",
605
+ "incentive_v3::repay": "repay"
606
+ }
607
+ };
556
608
  var NaviAdapter = class {
557
609
  id = "navi";
558
610
  name = "NAVI Protocol";
@@ -571,7 +623,7 @@ var NaviAdapter = class {
571
623
  const rates = await getRates(this.client);
572
624
  const key = asset.toUpperCase();
573
625
  const r = rates[key];
574
- if (!r) throw new Error(`NAVI does not support ${asset}`);
626
+ if (!r) throw new T2000Error("ASSET_NOT_SUPPORTED", `NAVI does not support ${asset}`);
575
627
  return { asset, saveApy: r.saveApy, borrowApy: r.borrowApy };
576
628
  }
577
629
  async getPositions(address) {
@@ -697,6 +749,18 @@ function fallbackQuote(fromAsset, amount, poolPrice) {
697
749
  }
698
750
 
699
751
  // src/adapters/cetus.ts
752
+ var descriptor2 = {
753
+ id: "cetus",
754
+ name: "Cetus DEX",
755
+ packages: [CETUS_PACKAGE],
756
+ actionMap: {
757
+ "router::swap": "swap",
758
+ "router::swap_ab_bc": "swap",
759
+ "router::swap_ab_cb": "swap",
760
+ "router::swap_ba_bc": "swap",
761
+ "router::swap_ba_cb": "swap"
762
+ }
763
+ };
700
764
  var CetusAdapter = class {
701
765
  id = "cetus";
702
766
  name = "Cetus";
@@ -751,6 +815,20 @@ var LENDING_MARKET_TYPE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea3
751
815
  var SUILEND_PACKAGE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf";
752
816
  var UPGRADE_CAP_ID = "0x3d4ef1859c3ee9fc72858f588b56a09da5466e64f8cc4e90a7b3b909fba8a7ae";
753
817
  var FALLBACK_PUBLISHED_AT = "0xd2a67633ccb8de063163e25bcfca242929caf5cf1a26c2929dab519ee0b8f331";
818
+ var descriptor3 = {
819
+ id: "suilend",
820
+ name: "Suilend",
821
+ packages: [SUILEND_PACKAGE],
822
+ actionMap: {
823
+ "lending_market::deposit_liquidity_and_mint_ctokens": "save",
824
+ "lending_market::deposit_ctokens_into_obligation": "save",
825
+ "lending_market::create_obligation": "save",
826
+ "lending_market::withdraw_ctokens": "withdraw",
827
+ "lending_market::redeem_ctokens_and_withdraw_liquidity": "withdraw",
828
+ "lending_market::borrow": "borrow",
829
+ "lending_market::repay": "repay"
830
+ }
831
+ };
754
832
  function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
755
833
  if (utilBreakpoints.length === 0) return 0;
756
834
  if (utilizationPct <= utilBreakpoints[0]) return aprBreakpoints[0];
@@ -1128,10 +1206,32 @@ var SuilendAdapter = class {
1128
1206
  return all;
1129
1207
  }
1130
1208
  };
1209
+ var descriptor4 = {
1210
+ id: "sentinel",
1211
+ name: "Sui Sentinel",
1212
+ packages: [SENTINEL.PACKAGE],
1213
+ actionMap: {
1214
+ "sentinel::request_attack": "sentinel_attack",
1215
+ "sentinel::consume_prompt": "sentinel_settle"
1216
+ }
1217
+ };
1218
+
1219
+ // src/adapters/index.ts
1220
+ var allDescriptors = [
1221
+ descriptor,
1222
+ descriptor3,
1223
+ descriptor2,
1224
+ descriptor4
1225
+ ];
1131
1226
 
1132
1227
  exports.CetusAdapter = CetusAdapter;
1133
1228
  exports.NaviAdapter = NaviAdapter;
1134
1229
  exports.ProtocolRegistry = ProtocolRegistry;
1135
1230
  exports.SuilendAdapter = SuilendAdapter;
1231
+ exports.allDescriptors = allDescriptors;
1232
+ exports.cetusDescriptor = descriptor2;
1233
+ exports.naviDescriptor = descriptor;
1234
+ exports.sentinelDescriptor = descriptor4;
1235
+ exports.suilendDescriptor = descriptor3;
1136
1236
  //# sourceMappingURL=index.cjs.map
1137
1237
  //# sourceMappingURL=index.cjs.map