@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.
@@ -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];
@@ -129,28 +151,6 @@ var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca590
129
151
  process.env.T2000_API_URL ?? "https://api.t2000.ai";
130
152
  var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
131
153
 
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
-
154
154
  // src/utils/format.ts
155
155
  function usdcToRaw(amount) {
156
156
  return BigInt(Math.round(amount * 10 ** USDC_DECIMALS));
@@ -193,6 +193,8 @@ var SUI_SYSTEM_STATE = "0x05";
193
193
  var NAVI_BALANCE_DECIMALS = 9;
194
194
  var CONFIG_API = "https://open-api.naviprotocol.io/api/navi/config?env=prod";
195
195
  var POOLS_API = "https://open-api.naviprotocol.io/api/navi/pools?env=prod";
196
+ var PACKAGE_API = "https://open-api.naviprotocol.io/api/package";
197
+ var packageCache = null;
196
198
  function toBigInt(v) {
197
199
  if (typeof v === "bigint") return v;
198
200
  return BigInt(String(v));
@@ -217,9 +219,22 @@ async function fetchJson(url) {
217
219
  const json = await res.json();
218
220
  return json.data ?? json;
219
221
  }
222
+ async function getLatestPackageId() {
223
+ if (packageCache && Date.now() - packageCache.ts < CACHE_TTL) return packageCache.id;
224
+ const res = await fetch(PACKAGE_API);
225
+ if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI package API error: ${res.status}`);
226
+ const json = await res.json();
227
+ if (!json.packageId) throw new T2000Error("PROTOCOL_UNAVAILABLE", "NAVI package API returned no packageId");
228
+ packageCache = { id: json.packageId, ts: Date.now() };
229
+ return json.packageId;
230
+ }
220
231
  async function getConfig(fresh = false) {
221
232
  if (configCache && !fresh && Date.now() - configCache.ts < CACHE_TTL) return configCache.data;
222
- const data = await fetchJson(CONFIG_API);
233
+ const [data, latestPkg] = await Promise.all([
234
+ fetchJson(CONFIG_API),
235
+ getLatestPackageId()
236
+ ]);
237
+ data.package = latestPkg;
223
238
  configCache = { data, ts: Date.now() };
224
239
  return data;
225
240
  }
@@ -237,6 +252,24 @@ async function getUsdcPool() {
237
252
  if (!usdc) throw new T2000Error("PROTOCOL_UNAVAILABLE", "USDC pool not found on NAVI");
238
253
  return usdc;
239
254
  }
255
+ function addOracleUpdate(tx, config, pool) {
256
+ const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
257
+ if (!feed) {
258
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `Oracle feed not found for asset ${pool.token?.symbol ?? pool.id}`);
259
+ }
260
+ tx.moveCall({
261
+ target: `${config.oracle.packageId}::oracle_pro::update_single_price_v2`,
262
+ arguments: [
263
+ tx.object(CLOCK),
264
+ tx.object(config.oracle.oracleConfig),
265
+ tx.object(config.oracle.priceOracle),
266
+ tx.object(config.oracle.supraOracleHolder),
267
+ tx.object(feed.pythPriceInfoObject),
268
+ tx.object(config.oracle.switchboardAggregator),
269
+ tx.pure.address(feed.feedId)
270
+ ]
271
+ });
272
+ }
240
273
  function rateToApy(rawRate) {
241
274
  if (!rawRate || rawRate === "0") return 0;
242
275
  return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
@@ -259,7 +292,7 @@ function compoundBalance(rawBalance, currentIndex) {
259
292
  if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
260
293
  const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
261
294
  const half = scale / 2n;
262
- const result = (rawBalance * scale + half) / BigInt(currentIndex);
295
+ const result = (rawBalance * BigInt(currentIndex) + half) / scale;
263
296
  return Number(result) / 10 ** NAVI_BALANCE_DECIMALS;
264
297
  }
265
298
  async function getUserState(client, address) {
@@ -293,23 +326,25 @@ async function fetchCoins(client, owner, coinType) {
293
326
  }
294
327
  return all;
295
328
  }
296
- function mergeCoinsPtb(tx, coins, amount) {
329
+ function mergeCoins(tx, coins) {
297
330
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
298
331
  const primary = tx.object(coins[0].coinObjectId);
299
332
  if (coins.length > 1) {
300
333
  tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
301
334
  }
302
- const [split] = tx.splitCoins(primary, [amount]);
303
- return split;
335
+ return primary;
304
336
  }
305
337
  async function buildSaveTx(client, address, amount, options = {}) {
338
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
339
+ throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
340
+ }
306
341
  const rawAmount = Number(usdcToRaw(amount));
307
342
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
308
343
  const coins = await fetchCoins(client, address, USDC_TYPE);
309
344
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
310
345
  const tx = new transactions.Transaction();
311
346
  tx.setSender(address);
312
- const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
347
+ const coinObj = mergeCoins(tx, coins);
313
348
  if (options.collectFee) {
314
349
  addCollectFeeToTx(tx, coinObj, "save");
315
350
  }
@@ -343,8 +378,9 @@ async function buildWithdrawTx(client, address, amount) {
343
378
  const rawAmount = Number(usdcToRaw(effectiveAmount));
344
379
  const tx = new transactions.Transaction();
345
380
  tx.setSender(address);
346
- tx.moveCall({
347
- target: `${config.package}::incentive_v3::entry_withdraw_v2`,
381
+ addOracleUpdate(tx, config, pool);
382
+ const [balance] = tx.moveCall({
383
+ target: `${config.package}::incentive_v3::withdraw_v2`,
348
384
  arguments: [
349
385
  tx.object(CLOCK),
350
386
  tx.object(config.oracle.priceOracle),
@@ -355,17 +391,28 @@ async function buildWithdrawTx(client, address, amount) {
355
391
  tx.object(config.incentiveV2),
356
392
  tx.object(config.incentiveV3),
357
393
  tx.object(SUI_SYSTEM_STATE)
358
- ]
394
+ ],
395
+ typeArguments: [pool.suiCoinType]
396
+ });
397
+ const [coin] = tx.moveCall({
398
+ target: "0x2::coin::from_balance",
399
+ arguments: [balance],
400
+ typeArguments: [pool.suiCoinType]
359
401
  });
402
+ tx.transferObjects([coin], address);
360
403
  return { tx, effectiveAmount };
361
404
  }
362
405
  async function buildBorrowTx(client, address, amount, options = {}) {
406
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
407
+ throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
408
+ }
363
409
  const rawAmount = Number(usdcToRaw(amount));
364
410
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
365
411
  const tx = new transactions.Transaction();
366
412
  tx.setSender(address);
367
- tx.moveCall({
368
- target: `${config.package}::incentive_v3::entry_borrow_v2`,
413
+ addOracleUpdate(tx, config, pool);
414
+ const [balance] = tx.moveCall({
415
+ target: `${config.package}::incentive_v3::borrow_v2`,
369
416
  arguments: [
370
417
  tx.object(CLOCK),
371
418
  tx.object(config.oracle.priceOracle),
@@ -376,18 +423,32 @@ async function buildBorrowTx(client, address, amount, options = {}) {
376
423
  tx.object(config.incentiveV2),
377
424
  tx.object(config.incentiveV3),
378
425
  tx.object(SUI_SYSTEM_STATE)
379
- ]
426
+ ],
427
+ typeArguments: [pool.suiCoinType]
380
428
  });
429
+ const [borrowedCoin] = tx.moveCall({
430
+ target: "0x2::coin::from_balance",
431
+ arguments: [balance],
432
+ typeArguments: [pool.suiCoinType]
433
+ });
434
+ if (options.collectFee) {
435
+ addCollectFeeToTx(tx, borrowedCoin, "borrow");
436
+ }
437
+ tx.transferObjects([borrowedCoin], address);
381
438
  return tx;
382
439
  }
383
440
  async function buildRepayTx(client, address, amount) {
441
+ if (!amount || amount <= 0 || !Number.isFinite(amount)) {
442
+ throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
443
+ }
384
444
  const rawAmount = Number(usdcToRaw(amount));
385
445
  const [config, pool] = await Promise.all([getConfig(), getUsdcPool()]);
386
446
  const coins = await fetchCoins(client, address, USDC_TYPE);
387
447
  if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins to repay with");
388
448
  const tx = new transactions.Transaction();
389
449
  tx.setSender(address);
390
- const coinObj = mergeCoinsPtb(tx, coins, rawAmount);
450
+ addOracleUpdate(tx, config, pool);
451
+ const coinObj = mergeCoins(tx, coins);
391
452
  tx.moveCall({
392
453
  target: `${config.package}::incentive_v3::entry_repay`,
393
454
  arguments: [
@@ -543,7 +604,7 @@ var NaviAdapter = class {
543
604
  const rates = await getRates(this.client);
544
605
  const key = asset.toUpperCase();
545
606
  const r = rates[key];
546
- if (!r) throw new Error(`NAVI does not support ${asset}`);
607
+ if (!r) throw new T2000Error("ASSET_NOT_SUPPORTED", `NAVI does not support ${asset}`);
547
608
  return { asset, saveApy: r.saveApy, borrowApy: r.borrowApy };
548
609
  }
549
610
  async getPositions(address) {