@t2000/sdk 0.5.6 → 0.6.2

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.
@@ -3,6 +3,28 @@ import { bcs } from '@mysten/sui/bcs';
3
3
  import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
4
4
  import { normalizeStructTag } from '@mysten/sui/utils';
5
5
 
6
+ // src/errors.ts
7
+ var T2000Error = class extends Error {
8
+ code;
9
+ data;
10
+ retryable;
11
+ constructor(code, message, data, retryable = false) {
12
+ super(message);
13
+ this.name = "T2000Error";
14
+ this.code = code;
15
+ this.data = data;
16
+ this.retryable = retryable;
17
+ }
18
+ toJSON() {
19
+ return {
20
+ error: this.code,
21
+ message: this.message,
22
+ ...this.data && { data: this.data },
23
+ retryable: this.retryable
24
+ };
25
+ }
26
+ };
27
+
6
28
  // src/adapters/registry.ts
7
29
  var ProtocolRegistry = class {
8
30
  lending = /* @__PURE__ */ new Map();
@@ -25,7 +47,7 @@ var ProtocolRegistry = class {
25
47
  }
26
48
  }
27
49
  if (candidates.length === 0) {
28
- throw new Error(`No lending adapter supports saving ${asset}`);
50
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `No lending adapter supports saving ${asset}`);
29
51
  }
30
52
  candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
31
53
  return candidates[0];
@@ -43,7 +65,7 @@ var ProtocolRegistry = class {
43
65
  }
44
66
  }
45
67
  if (candidates.length === 0) {
46
- throw new Error(`No lending adapter supports borrowing ${asset}`);
68
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `No lending adapter supports borrowing ${asset}`);
47
69
  }
48
70
  candidates.sort((a, b) => a.rate.borrowApy - b.rate.borrowApy);
49
71
  return candidates[0];
@@ -60,7 +82,7 @@ var ProtocolRegistry = class {
60
82
  }
61
83
  }
62
84
  if (candidates.length === 0) {
63
- throw new Error(`No swap adapter supports ${from} \u2192 ${to}`);
85
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `No swap adapter supports ${from} \u2192 ${to}`);
64
86
  }
65
87
  candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
66
88
  return candidates[0];
@@ -127,28 +149,6 @@ var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca590
127
149
  process.env.T2000_API_URL ?? "https://api.t2000.ai";
128
150
  var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
129
151
 
130
- // src/errors.ts
131
- var T2000Error = class extends Error {
132
- code;
133
- data;
134
- retryable;
135
- constructor(code, message, data, retryable = false) {
136
- super(message);
137
- this.name = "T2000Error";
138
- this.code = code;
139
- this.data = data;
140
- this.retryable = retryable;
141
- }
142
- toJSON() {
143
- return {
144
- error: this.code,
145
- message: this.message,
146
- ...this.data && { data: this.data },
147
- retryable: this.retryable
148
- };
149
- }
150
- };
151
-
152
152
  // src/utils/format.ts
153
153
  function usdcToRaw(amount) {
154
154
  return BigInt(Math.round(amount * 10 ** USDC_DECIMALS));
@@ -191,6 +191,8 @@ var SUI_SYSTEM_STATE = "0x05";
191
191
  var NAVI_BALANCE_DECIMALS = 9;
192
192
  var CONFIG_API = "https://open-api.naviprotocol.io/api/navi/config?env=prod";
193
193
  var POOLS_API = "https://open-api.naviprotocol.io/api/navi/pools?env=prod";
194
+ var PACKAGE_API = "https://open-api.naviprotocol.io/api/package";
195
+ var packageCache = null;
194
196
  function toBigInt(v) {
195
197
  if (typeof v === "bigint") return v;
196
198
  return BigInt(String(v));
@@ -215,9 +217,22 @@ async function fetchJson(url) {
215
217
  const json = await res.json();
216
218
  return json.data ?? json;
217
219
  }
220
+ async function getLatestPackageId() {
221
+ if (packageCache && Date.now() - packageCache.ts < CACHE_TTL) return packageCache.id;
222
+ const res = await fetch(PACKAGE_API);
223
+ if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI package API error: ${res.status}`);
224
+ const json = await res.json();
225
+ if (!json.packageId) throw new T2000Error("PROTOCOL_UNAVAILABLE", "NAVI package API returned no packageId");
226
+ packageCache = { id: json.packageId, ts: Date.now() };
227
+ return json.packageId;
228
+ }
218
229
  async function getConfig(fresh = false) {
219
230
  if (configCache && !fresh && Date.now() - configCache.ts < CACHE_TTL) return configCache.data;
220
- const data = await fetchJson(CONFIG_API);
231
+ const [data, latestPkg] = await Promise.all([
232
+ fetchJson(CONFIG_API),
233
+ getLatestPackageId()
234
+ ]);
235
+ data.package = latestPkg;
221
236
  configCache = { data, ts: Date.now() };
222
237
  return data;
223
238
  }
@@ -235,6 +250,24 @@ async function getUsdcPool() {
235
250
  if (!usdc) throw new T2000Error("PROTOCOL_UNAVAILABLE", "USDC pool not found on NAVI");
236
251
  return usdc;
237
252
  }
253
+ function addOracleUpdate(tx, config, pool) {
254
+ const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
255
+ if (!feed) {
256
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `Oracle feed not found for asset ${pool.token?.symbol ?? pool.id}`);
257
+ }
258
+ tx.moveCall({
259
+ target: `${config.oracle.packageId}::oracle_pro::update_single_price_v2`,
260
+ arguments: [
261
+ tx.object(CLOCK),
262
+ tx.object(config.oracle.oracleConfig),
263
+ tx.object(config.oracle.priceOracle),
264
+ tx.object(config.oracle.supraOracleHolder),
265
+ tx.object(feed.pythPriceInfoObject),
266
+ tx.object(config.oracle.switchboardAggregator),
267
+ tx.pure.address(feed.feedId)
268
+ ]
269
+ });
270
+ }
238
271
  function rateToApy(rawRate) {
239
272
  if (!rawRate || rawRate === "0") return 0;
240
273
  return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
@@ -257,7 +290,7 @@ function compoundBalance(rawBalance, currentIndex) {
257
290
  if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
258
291
  const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
259
292
  const half = scale / 2n;
260
- const result = (rawBalance * scale + half) / BigInt(currentIndex);
293
+ const result = (rawBalance * BigInt(currentIndex) + half) / scale;
261
294
  return Number(result) / 10 ** NAVI_BALANCE_DECIMALS;
262
295
  }
263
296
  async function getUserState(client, address) {
@@ -291,23 +324,25 @@ async function fetchCoins(client, owner, coinType) {
291
324
  }
292
325
  return all;
293
326
  }
294
- function mergeCoinsPtb(tx, coins, amount) {
327
+ function mergeCoins(tx, coins) {
295
328
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
296
329
  const primary = tx.object(coins[0].coinObjectId);
297
330
  if (coins.length > 1) {
298
331
  tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
299
332
  }
300
- const [split] = tx.splitCoins(primary, [amount]);
301
- return split;
333
+ return primary;
302
334
  }
303
335
  async function buildSaveTx(client, address, amount, options = {}) {
336
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
337
+ throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
338
+ }
304
339
  const rawAmount = Number(usdcToRaw(amount));
305
340
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
306
341
  const coins = await fetchCoins(client, address, USDC_TYPE);
307
342
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
308
343
  const tx = new Transaction();
309
344
  tx.setSender(address);
310
- const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
345
+ const coinObj = mergeCoins(tx, coins);
311
346
  if (options.collectFee) {
312
347
  addCollectFeeToTx(tx, coinObj, "save");
313
348
  }
@@ -341,8 +376,9 @@ async function buildWithdrawTx(client, address, amount) {
341
376
  const rawAmount = Number(usdcToRaw(effectiveAmount));
342
377
  const tx = new Transaction();
343
378
  tx.setSender(address);
344
- tx.moveCall({
345
- target: `${config.package}::incentive_v3::entry_withdraw_v2`,
379
+ addOracleUpdate(tx, config, pool);
380
+ const [balance] = tx.moveCall({
381
+ target: `${config.package}::incentive_v3::withdraw_v2`,
346
382
  arguments: [
347
383
  tx.object(CLOCK),
348
384
  tx.object(config.oracle.priceOracle),
@@ -353,17 +389,28 @@ async function buildWithdrawTx(client, address, amount) {
353
389
  tx.object(config.incentiveV2),
354
390
  tx.object(config.incentiveV3),
355
391
  tx.object(SUI_SYSTEM_STATE)
356
- ]
392
+ ],
393
+ typeArguments: [pool.suiCoinType]
394
+ });
395
+ const [coin] = tx.moveCall({
396
+ target: "0x2::coin::from_balance",
397
+ arguments: [balance],
398
+ typeArguments: [pool.suiCoinType]
357
399
  });
400
+ tx.transferObjects([coin], address);
358
401
  return { tx, effectiveAmount };
359
402
  }
360
403
  async function buildBorrowTx(client, address, amount, options = {}) {
404
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
405
+ throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
406
+ }
361
407
  const rawAmount = Number(usdcToRaw(amount));
362
408
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
363
409
  const tx = new Transaction();
364
410
  tx.setSender(address);
365
- tx.moveCall({
366
- target: `${config.package}::incentive_v3::entry_borrow_v2`,
411
+ addOracleUpdate(tx, config, pool);
412
+ const [balance] = tx.moveCall({
413
+ target: `${config.package}::incentive_v3::borrow_v2`,
367
414
  arguments: [
368
415
  tx.object(CLOCK),
369
416
  tx.object(config.oracle.priceOracle),
@@ -374,18 +421,32 @@ async function buildBorrowTx(client, address, amount, options = {}) {
374
421
  tx.object(config.incentiveV2),
375
422
  tx.object(config.incentiveV3),
376
423
  tx.object(SUI_SYSTEM_STATE)
377
- ]
424
+ ],
425
+ typeArguments: [pool.suiCoinType]
378
426
  });
427
+ const [borrowedCoin] = tx.moveCall({
428
+ target: "0x2::coin::from_balance",
429
+ arguments: [balance],
430
+ typeArguments: [pool.suiCoinType]
431
+ });
432
+ if (options.collectFee) {
433
+ addCollectFeeToTx(tx, borrowedCoin, "borrow");
434
+ }
435
+ tx.transferObjects([borrowedCoin], address);
379
436
  return tx;
380
437
  }
381
438
  async function buildRepayTx(client, address, amount) {
439
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
440
+ throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
441
+ }
382
442
  const rawAmount = Number(usdcToRaw(amount));
383
443
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
384
444
  const coins = await fetchCoins(client, address, USDC_TYPE);
385
445
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
386
446
  const tx = new Transaction();
387
447
  tx.setSender(address);
388
- const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
448
+ addOracleUpdate(tx, config, pool);
449
+ const coinObj = mergeCoins(tx, coins);
389
450
  tx.moveCall({
390
451
  target: `${config.package}::incentive_v3::entry_repay`,
391
452
  arguments: [
@@ -541,7 +602,7 @@ var NaviAdapter = class {
541
602
  const rates = await getRates(this.client);
542
603
  const key = asset.toUpperCase();
543
604
  const r = rates[key];
544
- if (!r) throw new Error(`NAVI does not support ${asset}`);
605
+ if (!r) throw new T2000Error("ASSET_NOT_SUPPORTED", `NAVI does not support ${asset}`);
545
606
  return { asset, saveApy: r.saveApy, borrowApy: r.borrowApy };
546
607
  }
547
608
  async getPositions(address) {