@t2000/sdk 0.17.29 → 0.18.3
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/adapters/index.d.cts +1 -1
- package/dist/adapters/index.d.ts +4 -10
- package/dist/adapters/index.js +2107 -15
- package/dist/adapters/index.js.map +1 -1
- package/dist/{index-DdtOBw42.d.ts → index-Co0lp99l.d.cts} +23 -1
- package/dist/{index-DdtOBw42.d.cts → index-Co0lp99l.d.ts} +23 -1
- package/dist/index.cjs +102 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +654 -30
- package/dist/index.js +6154 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/adapters/cetus.d.ts +0 -29
- package/dist/adapters/cetus.d.ts.map +0 -1
- package/dist/adapters/cetus.js +0 -74
- package/dist/adapters/cetus.js.map +0 -1
- package/dist/adapters/cetus.test.d.ts +0 -2
- package/dist/adapters/cetus.test.d.ts.map +0 -1
- package/dist/adapters/cetus.test.js +0 -57
- package/dist/adapters/cetus.test.js.map +0 -1
- package/dist/adapters/compliance.test.d.ts +0 -8
- package/dist/adapters/compliance.test.d.ts.map +0 -1
- package/dist/adapters/compliance.test.js +0 -202
- package/dist/adapters/compliance.test.js.map +0 -1
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/navi.d.ts +0 -41
- package/dist/adapters/navi.d.ts.map +0 -1
- package/dist/adapters/navi.js +0 -102
- package/dist/adapters/navi.js.map +0 -1
- package/dist/adapters/navi.test.d.ts +0 -2
- package/dist/adapters/navi.test.d.ts.map +0 -1
- package/dist/adapters/navi.test.js +0 -164
- package/dist/adapters/navi.test.js.map +0 -1
- package/dist/adapters/registry.d.ts +0 -47
- package/dist/adapters/registry.d.ts.map +0 -1
- package/dist/adapters/registry.js +0 -162
- package/dist/adapters/registry.js.map +0 -1
- package/dist/adapters/registry.test.d.ts +0 -2
- package/dist/adapters/registry.test.d.ts.map +0 -1
- package/dist/adapters/registry.test.js +0 -197
- package/dist/adapters/registry.test.js.map +0 -1
- package/dist/adapters/suilend.d.ts +0 -71
- package/dist/adapters/suilend.d.ts.map +0 -1
- package/dist/adapters/suilend.js +0 -826
- package/dist/adapters/suilend.js.map +0 -1
- package/dist/adapters/suilend.test.d.ts +0 -2
- package/dist/adapters/suilend.test.d.ts.map +0 -1
- package/dist/adapters/suilend.test.js +0 -294
- package/dist/adapters/suilend.test.js.map +0 -1
- package/dist/adapters/types.d.ts +0 -160
- package/dist/adapters/types.d.ts.map +0 -1
- package/dist/adapters/types.js +0 -2
- package/dist/adapters/types.js.map +0 -1
- package/dist/auto-invest.d.ts +0 -23
- package/dist/auto-invest.d.ts.map +0 -1
- package/dist/auto-invest.js +0 -131
- package/dist/auto-invest.js.map +0 -1
- package/dist/auto-invest.test.d.ts +0 -2
- package/dist/auto-invest.test.d.ts.map +0 -1
- package/dist/auto-invest.test.js +0 -220
- package/dist/auto-invest.test.js.map +0 -1
- package/dist/constants.d.ts +0 -177
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -135
- package/dist/constants.js.map +0 -1
- package/dist/contacts.d.ts +0 -25
- package/dist/contacts.d.ts.map +0 -1
- package/dist/contacts.js +0 -83
- package/dist/contacts.js.map +0 -1
- package/dist/contacts.test.d.ts +0 -2
- package/dist/contacts.test.d.ts.map +0 -1
- package/dist/contacts.test.js +0 -164
- package/dist/contacts.test.js.map +0 -1
- package/dist/errors.d.ts +0 -26
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -81
- package/dist/errors.js.map +0 -1
- package/dist/errors.test.d.ts +0 -2
- package/dist/errors.test.d.ts.map +0 -1
- package/dist/errors.test.js +0 -48
- package/dist/errors.test.js.map +0 -1
- package/dist/gas/autoTopUp.d.ts +0 -18
- package/dist/gas/autoTopUp.d.ts.map +0 -1
- package/dist/gas/autoTopUp.js +0 -55
- package/dist/gas/autoTopUp.js.map +0 -1
- package/dist/gas/autoTopUp.test.d.ts +0 -2
- package/dist/gas/autoTopUp.test.d.ts.map +0 -1
- package/dist/gas/autoTopUp.test.js +0 -59
- package/dist/gas/autoTopUp.test.js.map +0 -1
- package/dist/gas/gasStation.d.ts +0 -23
- package/dist/gas/gasStation.d.ts.map +0 -1
- package/dist/gas/gasStation.js +0 -58
- package/dist/gas/gasStation.js.map +0 -1
- package/dist/gas/index.d.ts +0 -4
- package/dist/gas/index.d.ts.map +0 -1
- package/dist/gas/index.js +0 -4
- package/dist/gas/index.js.map +0 -1
- package/dist/gas/manager.d.ts +0 -24
- package/dist/gas/manager.d.ts.map +0 -1
- package/dist/gas/manager.js +0 -142
- package/dist/gas/manager.js.map +0 -1
- package/dist/gas/manager.test.d.ts +0 -2
- package/dist/gas/manager.test.d.ts.map +0 -1
- package/dist/gas/manager.test.js +0 -220
- package/dist/gas/manager.test.js.map +0 -1
- package/dist/gas/serialization.test.d.ts +0 -2
- package/dist/gas/serialization.test.d.ts.map +0 -1
- package/dist/gas/serialization.test.js +0 -47
- package/dist/gas/serialization.test.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/invest.test.d.ts +0 -2
- package/dist/invest.test.d.ts.map +0 -1
- package/dist/invest.test.js +0 -256
- package/dist/invest.test.js.map +0 -1
- package/dist/portfolio.d.ts +0 -39
- package/dist/portfolio.d.ts.map +0 -1
- package/dist/portfolio.js +0 -201
- package/dist/portfolio.js.map +0 -1
- package/dist/portfolio.test.d.ts +0 -2
- package/dist/portfolio.test.d.ts.map +0 -1
- package/dist/portfolio.test.js +0 -301
- package/dist/portfolio.test.js.map +0 -1
- package/dist/protocols/cetus.d.ts +0 -73
- package/dist/protocols/cetus.d.ts.map +0 -1
- package/dist/protocols/cetus.js +0 -267
- package/dist/protocols/cetus.js.map +0 -1
- package/dist/protocols/cetus.test.d.ts +0 -2
- package/dist/protocols/cetus.test.d.ts.map +0 -1
- package/dist/protocols/cetus.test.js +0 -325
- package/dist/protocols/cetus.test.js.map +0 -1
- package/dist/protocols/navi.d.ts +0 -59
- package/dist/protocols/navi.d.ts.map +0 -1
- package/dist/protocols/navi.js +0 -945
- package/dist/protocols/navi.js.map +0 -1
- package/dist/protocols/navi.test.d.ts +0 -2
- package/dist/protocols/navi.test.d.ts.map +0 -1
- package/dist/protocols/navi.test.js +0 -339
- package/dist/protocols/navi.test.js.map +0 -1
- package/dist/protocols/protocolFee.d.ts +0 -17
- package/dist/protocols/protocolFee.d.ts.map +0 -1
- package/dist/protocols/protocolFee.js +0 -62
- package/dist/protocols/protocolFee.js.map +0 -1
- package/dist/protocols/protocolFee.test.d.ts +0 -2
- package/dist/protocols/protocolFee.test.d.ts.map +0 -1
- package/dist/protocols/protocolFee.test.js +0 -137
- package/dist/protocols/protocolFee.test.js.map +0 -1
- package/dist/protocols/sentinel.d.ts +0 -18
- package/dist/protocols/sentinel.d.ts.map +0 -1
- package/dist/protocols/sentinel.js +0 -188
- package/dist/protocols/sentinel.js.map +0 -1
- package/dist/protocols/sentinel.test.d.ts +0 -2
- package/dist/protocols/sentinel.test.d.ts.map +0 -1
- package/dist/protocols/sentinel.test.js +0 -199
- package/dist/protocols/sentinel.test.js.map +0 -1
- package/dist/protocols/yieldTracker.d.ts +0 -6
- package/dist/protocols/yieldTracker.d.ts.map +0 -1
- package/dist/protocols/yieldTracker.js +0 -29
- package/dist/protocols/yieldTracker.js.map +0 -1
- package/dist/safeguards/enforcer.d.ts +0 -18
- package/dist/safeguards/enforcer.d.ts.map +0 -1
- package/dist/safeguards/enforcer.js +0 -130
- package/dist/safeguards/enforcer.js.map +0 -1
- package/dist/safeguards/enforcer.test.d.ts +0 -2
- package/dist/safeguards/enforcer.test.d.ts.map +0 -1
- package/dist/safeguards/enforcer.test.js +0 -212
- package/dist/safeguards/enforcer.test.js.map +0 -1
- package/dist/safeguards/errors.d.ts +0 -24
- package/dist/safeguards/errors.d.ts.map +0 -1
- package/dist/safeguards/errors.js +0 -31
- package/dist/safeguards/errors.js.map +0 -1
- package/dist/safeguards/index.d.ts +0 -6
- package/dist/safeguards/index.d.ts.map +0 -1
- package/dist/safeguards/index.js +0 -4
- package/dist/safeguards/index.js.map +0 -1
- package/dist/safeguards/types.d.ts +0 -16
- package/dist/safeguards/types.d.ts.map +0 -1
- package/dist/safeguards/types.js +0 -13
- package/dist/safeguards/types.js.map +0 -1
- package/dist/strategy.d.ts +0 -22
- package/dist/strategy.d.ts.map +0 -1
- package/dist/strategy.js +0 -113
- package/dist/strategy.js.map +0 -1
- package/dist/strategy.test.d.ts +0 -2
- package/dist/strategy.test.d.ts.map +0 -1
- package/dist/strategy.test.js +0 -212
- package/dist/strategy.test.js.map +0 -1
- package/dist/t2000.d.ts +0 -219
- package/dist/t2000.d.ts.map +0 -1
- package/dist/t2000.integration.test.d.ts +0 -2
- package/dist/t2000.integration.test.d.ts.map +0 -1
- package/dist/t2000.integration.test.js +0 -954
- package/dist/t2000.integration.test.js.map +0 -1
- package/dist/t2000.js +0 -2416
- package/dist/t2000.js.map +0 -1
- package/dist/types.d.ts +0 -419
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils/format.d.ts +0 -22
- package/dist/utils/format.d.ts.map +0 -1
- package/dist/utils/format.js +0 -71
- package/dist/utils/format.js.map +0 -1
- package/dist/utils/format.test.d.ts +0 -2
- package/dist/utils/format.test.d.ts.map +0 -1
- package/dist/utils/format.test.js +0 -187
- package/dist/utils/format.test.js.map +0 -1
- package/dist/utils/hashcash.d.ts +0 -2
- package/dist/utils/hashcash.d.ts.map +0 -1
- package/dist/utils/hashcash.js +0 -27
- package/dist/utils/hashcash.js.map +0 -1
- package/dist/utils/hashcash.test.d.ts +0 -2
- package/dist/utils/hashcash.test.d.ts.map +0 -1
- package/dist/utils/hashcash.test.js +0 -40
- package/dist/utils/hashcash.test.js.map +0 -1
- package/dist/utils/retry.d.ts +0 -9
- package/dist/utils/retry.d.ts.map +0 -1
- package/dist/utils/retry.js +0 -47
- package/dist/utils/retry.js.map +0 -1
- package/dist/utils/simulate.d.ts +0 -15
- package/dist/utils/simulate.d.ts.map +0 -1
- package/dist/utils/simulate.js +0 -75
- package/dist/utils/simulate.js.map +0 -1
- package/dist/utils/simulate.test.d.ts +0 -2
- package/dist/utils/simulate.test.d.ts.map +0 -1
- package/dist/utils/simulate.test.js +0 -80
- package/dist/utils/simulate.test.js.map +0 -1
- package/dist/utils/sui.d.ts +0 -6
- package/dist/utils/sui.d.ts.map +0 -1
- package/dist/utils/sui.js +0 -28
- package/dist/utils/sui.js.map +0 -1
- package/dist/utils/sui.test.d.ts +0 -2
- package/dist/utils/sui.test.d.ts.map +0 -1
- package/dist/utils/sui.test.js +0 -58
- package/dist/utils/sui.test.js.map +0 -1
- package/dist/wallet/balance.d.ts +0 -4
- package/dist/wallet/balance.d.ts.map +0 -1
- package/dist/wallet/balance.js +0 -98
- package/dist/wallet/balance.js.map +0 -1
- package/dist/wallet/history.d.ts +0 -4
- package/dist/wallet/history.d.ts.map +0 -1
- package/dist/wallet/history.js +0 -38
- package/dist/wallet/history.js.map +0 -1
- package/dist/wallet/keyManager.d.ts +0 -9
- package/dist/wallet/keyManager.d.ts.map +0 -1
- package/dist/wallet/keyManager.js +0 -113
- package/dist/wallet/keyManager.js.map +0 -1
- package/dist/wallet/keyManager.test.d.ts +0 -2
- package/dist/wallet/keyManager.test.d.ts.map +0 -1
- package/dist/wallet/keyManager.test.js +0 -55
- package/dist/wallet/keyManager.test.js.map +0 -1
- package/dist/wallet/send.d.ts +0 -24
- package/dist/wallet/send.d.ts.map +0 -1
- package/dist/wallet/send.js +0 -95
- package/dist/wallet/send.js.map +0 -1
- package/dist/wallet/send.test.d.ts +0 -2
- package/dist/wallet/send.test.d.ts.map +0 -1
- package/dist/wallet/send.test.js +0 -69
- package/dist/wallet/send.test.js.map +0 -1
package/dist/adapters/index.js
CHANGED
|
@@ -1,17 +1,2109 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { Transaction } from '@mysten/sui/transactions';
|
|
2
|
+
import { bcs } from '@mysten/sui/bcs';
|
|
3
|
+
import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
|
|
4
|
+
import { normalizeStructTag } from '@mysten/sui/utils';
|
|
5
|
+
|
|
6
|
+
// src/constants.ts
|
|
7
|
+
var SAVE_FEE_BPS = 10n;
|
|
8
|
+
var SWAP_FEE_BPS = 0n;
|
|
9
|
+
var BORROW_FEE_BPS = 5n;
|
|
10
|
+
var SUPPORTED_ASSETS = {
|
|
11
|
+
USDC: {
|
|
12
|
+
type: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
|
|
13
|
+
decimals: 6,
|
|
14
|
+
symbol: "USDC",
|
|
15
|
+
displayName: "USDC"
|
|
16
|
+
},
|
|
17
|
+
USDT: {
|
|
18
|
+
type: "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT",
|
|
19
|
+
decimals: 6,
|
|
20
|
+
symbol: "USDT",
|
|
21
|
+
displayName: "suiUSDT"
|
|
22
|
+
},
|
|
23
|
+
USDe: {
|
|
24
|
+
type: "0x41d587e5336f1c86cad50d38a7136db99333bb9bda91cea4ba69115defeb1402::sui_usde::SUI_USDE",
|
|
25
|
+
decimals: 6,
|
|
26
|
+
symbol: "USDe",
|
|
27
|
+
displayName: "suiUSDe"
|
|
28
|
+
},
|
|
29
|
+
USDsui: {
|
|
30
|
+
type: "0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI",
|
|
31
|
+
decimals: 6,
|
|
32
|
+
symbol: "USDsui",
|
|
33
|
+
displayName: "USDsui"
|
|
34
|
+
},
|
|
35
|
+
SUI: {
|
|
36
|
+
type: "0x2::sui::SUI",
|
|
37
|
+
decimals: 9,
|
|
38
|
+
symbol: "SUI",
|
|
39
|
+
displayName: "SUI"
|
|
40
|
+
},
|
|
41
|
+
BTC: {
|
|
42
|
+
type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC",
|
|
43
|
+
decimals: 8,
|
|
44
|
+
symbol: "BTC",
|
|
45
|
+
displayName: "Bitcoin"
|
|
46
|
+
},
|
|
47
|
+
ETH: {
|
|
48
|
+
type: "0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH",
|
|
49
|
+
decimals: 8,
|
|
50
|
+
symbol: "ETH",
|
|
51
|
+
displayName: "Ethereum"
|
|
52
|
+
},
|
|
53
|
+
GOLD: {
|
|
54
|
+
type: "0x9d297676e7a4b771ab023291377b2adfaa4938fb9080b8d12430e4b108b836a9::xaum::XAUM",
|
|
55
|
+
decimals: 9,
|
|
56
|
+
symbol: "GOLD",
|
|
57
|
+
displayName: "Gold"
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var STABLE_ASSETS = ["USDC", "USDT", "USDe", "USDsui"];
|
|
61
|
+
var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
|
|
62
|
+
var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
|
|
63
|
+
var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
|
|
64
|
+
process.env.T2000_API_URL ?? "https://api.t2000.ai";
|
|
65
|
+
var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
|
|
66
|
+
var CETUS_PACKAGE = "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb";
|
|
67
|
+
var INVESTMENT_ASSETS = {
|
|
68
|
+
SUI: SUPPORTED_ASSETS.SUI,
|
|
69
|
+
BTC: SUPPORTED_ASSETS.BTC,
|
|
70
|
+
ETH: SUPPORTED_ASSETS.ETH,
|
|
71
|
+
GOLD: SUPPORTED_ASSETS.GOLD
|
|
72
|
+
};
|
|
73
|
+
var SENTINEL = {
|
|
74
|
+
PACKAGE: "0x88b83f36dafcd5f6dcdcf1d2cb5889b03f61264ab3cee9cae35db7aa940a21b7"};
|
|
75
|
+
|
|
76
|
+
// src/errors.ts
|
|
77
|
+
var T2000Error = class extends Error {
|
|
78
|
+
code;
|
|
79
|
+
data;
|
|
80
|
+
retryable;
|
|
81
|
+
constructor(code, message, data, retryable = false) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = "T2000Error";
|
|
84
|
+
this.code = code;
|
|
85
|
+
this.data = data;
|
|
86
|
+
this.retryable = retryable;
|
|
87
|
+
}
|
|
88
|
+
toJSON() {
|
|
89
|
+
return {
|
|
90
|
+
error: this.code,
|
|
91
|
+
message: this.message,
|
|
92
|
+
...this.data && { data: this.data },
|
|
93
|
+
retryable: this.retryable
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/adapters/registry.ts
|
|
99
|
+
var ProtocolRegistry = class {
|
|
100
|
+
lending = /* @__PURE__ */ new Map();
|
|
101
|
+
swap = /* @__PURE__ */ new Map();
|
|
102
|
+
registerLending(adapter) {
|
|
103
|
+
this.lending.set(adapter.id, adapter);
|
|
104
|
+
}
|
|
105
|
+
registerSwap(adapter) {
|
|
106
|
+
this.swap.set(adapter.id, adapter);
|
|
107
|
+
}
|
|
108
|
+
async bestSaveRate(asset) {
|
|
109
|
+
const candidates = [];
|
|
110
|
+
for (const adapter of this.lending.values()) {
|
|
111
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
112
|
+
if (!adapter.capabilities.includes("save")) continue;
|
|
113
|
+
try {
|
|
114
|
+
const rate = await adapter.getRates(asset);
|
|
115
|
+
candidates.push({ adapter, rate });
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (candidates.length === 0) {
|
|
120
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No lending adapter supports saving ${asset}`);
|
|
121
|
+
}
|
|
122
|
+
candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
|
|
123
|
+
return candidates[0];
|
|
124
|
+
}
|
|
125
|
+
async bestBorrowRate(asset, opts) {
|
|
126
|
+
const candidates = [];
|
|
127
|
+
for (const adapter of this.lending.values()) {
|
|
128
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
129
|
+
if (!adapter.capabilities.includes("borrow")) continue;
|
|
130
|
+
if (opts?.requireSameAssetBorrow && !adapter.supportsSameAssetBorrow) continue;
|
|
131
|
+
try {
|
|
132
|
+
const rate = await adapter.getRates(asset);
|
|
133
|
+
candidates.push({ adapter, rate });
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (candidates.length === 0) {
|
|
138
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No lending adapter supports borrowing ${asset}`);
|
|
139
|
+
}
|
|
140
|
+
candidates.sort((a, b) => a.rate.borrowApy - b.rate.borrowApy);
|
|
141
|
+
return candidates[0];
|
|
142
|
+
}
|
|
143
|
+
async bestSwapQuote(from, to, amount) {
|
|
144
|
+
const candidates = [];
|
|
145
|
+
for (const adapter of this.swap.values()) {
|
|
146
|
+
const pairs = adapter.getSupportedPairs();
|
|
147
|
+
if (!pairs.some((p) => p.from === from && p.to === to)) continue;
|
|
148
|
+
try {
|
|
149
|
+
const quote = await adapter.getQuote(from, to, amount);
|
|
150
|
+
candidates.push({ adapter, quote });
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (candidates.length === 0) {
|
|
155
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `No swap adapter supports ${from} \u2192 ${to}`);
|
|
156
|
+
}
|
|
157
|
+
candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
|
|
158
|
+
return candidates[0];
|
|
159
|
+
}
|
|
160
|
+
async bestSaveRateAcrossAssets() {
|
|
161
|
+
const candidates = [];
|
|
162
|
+
for (const asset of STABLE_ASSETS) {
|
|
163
|
+
for (const adapter of this.lending.values()) {
|
|
164
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
165
|
+
if (!adapter.capabilities.includes("save")) continue;
|
|
166
|
+
try {
|
|
167
|
+
const rate = await adapter.getRates(asset);
|
|
168
|
+
candidates.push({ adapter, rate, asset });
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (candidates.length === 0) {
|
|
174
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", "No lending adapter found for any stablecoin");
|
|
175
|
+
}
|
|
176
|
+
candidates.sort((a, b) => b.rate.saveApy - a.rate.saveApy);
|
|
177
|
+
return candidates[0];
|
|
178
|
+
}
|
|
179
|
+
async allRatesAcrossAssets() {
|
|
180
|
+
const results = [];
|
|
181
|
+
const allAssets = [...STABLE_ASSETS, ...Object.keys(INVESTMENT_ASSETS)];
|
|
182
|
+
const seen = /* @__PURE__ */ new Set();
|
|
183
|
+
for (const asset of allAssets) {
|
|
184
|
+
if (seen.has(asset)) continue;
|
|
185
|
+
seen.add(asset);
|
|
186
|
+
for (const adapter of this.lending.values()) {
|
|
187
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
188
|
+
try {
|
|
189
|
+
const rates = await adapter.getRates(asset);
|
|
190
|
+
if (rates.saveApy > 0 || rates.borrowApy > 0) {
|
|
191
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, asset, rates });
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return results;
|
|
198
|
+
}
|
|
199
|
+
async allRates(asset) {
|
|
200
|
+
const results = [];
|
|
201
|
+
for (const adapter of this.lending.values()) {
|
|
202
|
+
if (!adapter.supportedAssets.includes(asset)) continue;
|
|
203
|
+
try {
|
|
204
|
+
const rates = await adapter.getRates(asset);
|
|
205
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, rates });
|
|
206
|
+
} catch {
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
async allPositions(address) {
|
|
212
|
+
const results = [];
|
|
213
|
+
for (const adapter of this.lending.values()) {
|
|
214
|
+
try {
|
|
215
|
+
const positions = await adapter.getPositions(address);
|
|
216
|
+
if (positions.supplies.length > 0 || positions.borrows.length > 0) {
|
|
217
|
+
results.push({ protocol: adapter.name, protocolId: adapter.id, positions });
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
224
|
+
getLending(id) {
|
|
225
|
+
return this.lending.get(id);
|
|
226
|
+
}
|
|
227
|
+
getSwap(id) {
|
|
228
|
+
return this.swap.get(id);
|
|
229
|
+
}
|
|
230
|
+
listLending() {
|
|
231
|
+
return [...this.lending.values()];
|
|
232
|
+
}
|
|
233
|
+
listSwap() {
|
|
234
|
+
return [...this.swap.values()];
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/utils/format.ts
|
|
239
|
+
function stableToRaw(amount, decimals) {
|
|
240
|
+
return BigInt(Math.round(amount * 10 ** decimals));
|
|
241
|
+
}
|
|
242
|
+
var ASSET_LOOKUP = /* @__PURE__ */ new Map();
|
|
243
|
+
for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
244
|
+
ASSET_LOOKUP.set(key.toUpperCase(), key);
|
|
245
|
+
if (info.displayName && info.displayName.toUpperCase() !== key.toUpperCase()) {
|
|
246
|
+
ASSET_LOOKUP.set(info.displayName.toUpperCase(), key);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function normalizeAsset(input) {
|
|
250
|
+
return ASSET_LOOKUP.get(input.toUpperCase()) ?? input;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/protocols/protocolFee.ts
|
|
254
|
+
var FEE_RATES = {
|
|
255
|
+
save: SAVE_FEE_BPS,
|
|
256
|
+
swap: SWAP_FEE_BPS,
|
|
257
|
+
borrow: BORROW_FEE_BPS
|
|
258
|
+
};
|
|
259
|
+
var OP_CODES = {
|
|
260
|
+
save: 0,
|
|
261
|
+
swap: 1,
|
|
262
|
+
borrow: 2
|
|
263
|
+
};
|
|
264
|
+
function addCollectFeeToTx(tx, paymentCoin, operation) {
|
|
265
|
+
const bps = FEE_RATES[operation];
|
|
266
|
+
if (bps <= 0n) return;
|
|
267
|
+
tx.moveCall({
|
|
268
|
+
target: `${T2000_PACKAGE_ID}::treasury::collect_fee`,
|
|
269
|
+
typeArguments: [SUPPORTED_ASSETS.USDC.type],
|
|
270
|
+
arguments: [
|
|
271
|
+
tx.object(T2000_TREASURY_ID),
|
|
272
|
+
tx.object(T2000_CONFIG_ID),
|
|
273
|
+
paymentCoin,
|
|
274
|
+
tx.pure.u8(OP_CODES[operation])
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
var RATE_DECIMALS = 27;
|
|
279
|
+
var LTV_DECIMALS = 27;
|
|
280
|
+
var MIN_HEALTH_FACTOR = 1.5;
|
|
281
|
+
function withdrawDustBuffer(decimals) {
|
|
282
|
+
return 1e3 / 10 ** decimals;
|
|
283
|
+
}
|
|
284
|
+
var CLOCK = "0x06";
|
|
285
|
+
var SUI_SYSTEM_STATE = "0x05";
|
|
286
|
+
var NAVI_BALANCE_DECIMALS = 9;
|
|
287
|
+
var CONFIG_API = "https://open-api.naviprotocol.io/api/navi/config?env=prod";
|
|
288
|
+
var POOLS_API = "https://open-api.naviprotocol.io/api/navi/pools?env=prod";
|
|
289
|
+
var PACKAGE_API = "https://open-api.naviprotocol.io/api/package";
|
|
290
|
+
var packageCache = null;
|
|
291
|
+
function toBigInt(v) {
|
|
292
|
+
if (typeof v === "bigint") return v;
|
|
293
|
+
return BigInt(String(v));
|
|
294
|
+
}
|
|
295
|
+
var UserStateInfo = bcs.struct("UserStateInfo", {
|
|
296
|
+
asset_id: bcs.u8(),
|
|
297
|
+
borrow_balance: bcs.u256(),
|
|
298
|
+
supply_balance: bcs.u256()
|
|
299
|
+
});
|
|
300
|
+
function decodeDevInspect(result, schema) {
|
|
301
|
+
const rv = result.results?.[0]?.returnValues?.[0];
|
|
302
|
+
if (result.error || !rv) return void 0;
|
|
303
|
+
const bytes = Uint8Array.from(rv[0]);
|
|
304
|
+
return schema.parse(bytes);
|
|
305
|
+
}
|
|
306
|
+
var configCache = null;
|
|
307
|
+
var poolsCache = null;
|
|
308
|
+
var CACHE_TTL = 5 * 6e4;
|
|
309
|
+
async function fetchJson(url) {
|
|
310
|
+
const res = await fetch(url);
|
|
311
|
+
if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI API error: ${res.status}`);
|
|
312
|
+
const json = await res.json();
|
|
313
|
+
return json.data ?? json;
|
|
314
|
+
}
|
|
315
|
+
async function getLatestPackageId() {
|
|
316
|
+
if (packageCache && Date.now() - packageCache.ts < CACHE_TTL) return packageCache.id;
|
|
317
|
+
const res = await fetch(PACKAGE_API);
|
|
318
|
+
if (!res.ok) throw new T2000Error("PROTOCOL_UNAVAILABLE", `NAVI package API error: ${res.status}`);
|
|
319
|
+
const json = await res.json();
|
|
320
|
+
if (!json.packageId) throw new T2000Error("PROTOCOL_UNAVAILABLE", "NAVI package API returned no packageId");
|
|
321
|
+
packageCache = { id: json.packageId, ts: Date.now() };
|
|
322
|
+
return json.packageId;
|
|
323
|
+
}
|
|
324
|
+
async function getConfig(fresh = false) {
|
|
325
|
+
if (configCache && !fresh && Date.now() - configCache.ts < CACHE_TTL) return configCache.data;
|
|
326
|
+
const [data, latestPkg] = await Promise.all([
|
|
327
|
+
fetchJson(CONFIG_API),
|
|
328
|
+
getLatestPackageId()
|
|
329
|
+
]);
|
|
330
|
+
data.package = latestPkg;
|
|
331
|
+
configCache = { data, ts: Date.now() };
|
|
332
|
+
return data;
|
|
333
|
+
}
|
|
334
|
+
async function getPools(fresh = false) {
|
|
335
|
+
if (poolsCache && !fresh && Date.now() - poolsCache.ts < CACHE_TTL) return poolsCache.data;
|
|
336
|
+
const data = await fetchJson(POOLS_API);
|
|
337
|
+
poolsCache = { data, ts: Date.now() };
|
|
338
|
+
return data;
|
|
339
|
+
}
|
|
340
|
+
function matchesCoinType(poolType, targetType) {
|
|
341
|
+
const poolSuffix = poolType.split("::").slice(1).join("::").toLowerCase();
|
|
342
|
+
const targetSuffix = targetType.split("::").slice(1).join("::").toLowerCase();
|
|
343
|
+
return poolSuffix === targetSuffix;
|
|
344
|
+
}
|
|
345
|
+
function resolvePoolSymbol(pool) {
|
|
346
|
+
const coinType = pool.suiCoinType || pool.coinType || "";
|
|
347
|
+
for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
348
|
+
if (matchesCoinType(coinType, info.type)) return key;
|
|
349
|
+
}
|
|
350
|
+
return pool.token?.symbol ?? "UNKNOWN";
|
|
351
|
+
}
|
|
352
|
+
function resolveAssetInfo(asset) {
|
|
353
|
+
if (asset in SUPPORTED_ASSETS) {
|
|
354
|
+
const info = SUPPORTED_ASSETS[asset];
|
|
355
|
+
return { type: info.type, decimals: info.decimals, displayName: info.displayName };
|
|
356
|
+
}
|
|
357
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
|
|
358
|
+
}
|
|
359
|
+
async function getPool(asset = "USDC") {
|
|
360
|
+
const pools = await getPools();
|
|
361
|
+
const { type: targetType, displayName } = resolveAssetInfo(asset);
|
|
362
|
+
const pool = pools.find(
|
|
363
|
+
(p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType)
|
|
364
|
+
);
|
|
365
|
+
if (!pool) {
|
|
366
|
+
throw new T2000Error(
|
|
367
|
+
"ASSET_NOT_SUPPORTED",
|
|
368
|
+
`${displayName} pool not found on NAVI`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
return pool;
|
|
372
|
+
}
|
|
373
|
+
function addOracleUpdate(tx, config, pool) {
|
|
374
|
+
const feed = config.oracle.feeds?.find((f2) => f2.assetId === pool.id);
|
|
375
|
+
if (!feed) {
|
|
376
|
+
throw new T2000Error("PROTOCOL_UNAVAILABLE", `Oracle feed not found for asset ${pool.token?.symbol ?? pool.id}`);
|
|
377
|
+
}
|
|
378
|
+
tx.moveCall({
|
|
379
|
+
target: `${config.oracle.packageId}::oracle_pro::update_single_price_v2`,
|
|
380
|
+
arguments: [
|
|
381
|
+
tx.object(CLOCK),
|
|
382
|
+
tx.object(config.oracle.oracleConfig),
|
|
383
|
+
tx.object(config.oracle.priceOracle),
|
|
384
|
+
tx.object(config.oracle.supraOracleHolder),
|
|
385
|
+
tx.object(feed.pythPriceInfoObject),
|
|
386
|
+
tx.object(config.oracle.switchboardAggregator),
|
|
387
|
+
tx.pure.address(feed.feedId)
|
|
388
|
+
]
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function refreshOracles(tx, config, pools, opts) {
|
|
392
|
+
const assetsToRefresh = NAVI_SUPPORTED_ASSETS;
|
|
393
|
+
const targetTypes = assetsToRefresh.map((a) => SUPPORTED_ASSETS[a].type);
|
|
394
|
+
const matchedPools = pools.filter((p) => {
|
|
395
|
+
const ct = p.suiCoinType || p.coinType || "";
|
|
396
|
+
return targetTypes.some((t) => matchesCoinType(ct, t));
|
|
397
|
+
});
|
|
398
|
+
for (const pool of matchedPools) {
|
|
399
|
+
addOracleUpdate(tx, config, pool);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function rateToApy(rawRate) {
|
|
403
|
+
if (!rawRate || rawRate === "0") return 0;
|
|
404
|
+
return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
|
|
405
|
+
}
|
|
406
|
+
function poolSaveApy(pool) {
|
|
407
|
+
const incentive = parseFloat(pool.supplyIncentiveApyInfo?.apy ?? "0");
|
|
408
|
+
if (incentive > 0) return incentive;
|
|
409
|
+
return rateToApy(pool.currentSupplyRate);
|
|
410
|
+
}
|
|
411
|
+
function poolBorrowApy(pool) {
|
|
412
|
+
const incentive = parseFloat(pool.borrowIncentiveApyInfo?.apy ?? "0");
|
|
413
|
+
if (incentive > 0) return incentive;
|
|
414
|
+
return rateToApy(pool.currentBorrowRate);
|
|
415
|
+
}
|
|
416
|
+
function parseLtv(rawLtv) {
|
|
417
|
+
if (!rawLtv || rawLtv === "0") return 0.75;
|
|
418
|
+
return Number(BigInt(rawLtv)) / 10 ** LTV_DECIMALS;
|
|
419
|
+
}
|
|
420
|
+
function parseLiqThreshold(val) {
|
|
421
|
+
if (typeof val === "number") return val;
|
|
422
|
+
const n = Number(val);
|
|
423
|
+
if (n > 1) return Number(BigInt(val)) / 10 ** LTV_DECIMALS;
|
|
424
|
+
return n;
|
|
425
|
+
}
|
|
426
|
+
function normalizeHealthFactor(raw) {
|
|
427
|
+
const v = raw / 10 ** RATE_DECIMALS;
|
|
428
|
+
return v > 1e5 ? Infinity : v;
|
|
429
|
+
}
|
|
430
|
+
function naviStorageDecimals(poolId, tokenDecimals) {
|
|
431
|
+
if (poolId <= 10) return NAVI_BALANCE_DECIMALS;
|
|
432
|
+
return tokenDecimals;
|
|
433
|
+
}
|
|
434
|
+
function compoundBalance(rawBalance, currentIndex, pool) {
|
|
435
|
+
if (!rawBalance || !currentIndex || currentIndex === "0") return 0;
|
|
436
|
+
const scale = BigInt("1" + "0".repeat(RATE_DECIMALS));
|
|
437
|
+
const half = scale / 2n;
|
|
438
|
+
const result = (rawBalance * BigInt(currentIndex) + half) / scale;
|
|
439
|
+
const decimals = pool ? naviStorageDecimals(pool.id, pool.token.decimals) : NAVI_BALANCE_DECIMALS;
|
|
440
|
+
return Number(result) / 10 ** decimals;
|
|
441
|
+
}
|
|
442
|
+
async function getUserState(client, address) {
|
|
443
|
+
const config = await getConfig();
|
|
444
|
+
const tx = new Transaction();
|
|
445
|
+
tx.moveCall({
|
|
446
|
+
target: `${config.uiGetter}::getter_unchecked::get_user_state`,
|
|
447
|
+
arguments: [tx.object(config.storage), tx.pure.address(address)]
|
|
448
|
+
});
|
|
449
|
+
const result = await client.devInspectTransactionBlock({
|
|
450
|
+
transactionBlock: tx,
|
|
451
|
+
sender: address
|
|
452
|
+
});
|
|
453
|
+
const decoded = decodeDevInspect(result, bcs.vector(UserStateInfo));
|
|
454
|
+
if (!decoded) return [];
|
|
455
|
+
const mapped = decoded.map((s) => ({
|
|
456
|
+
assetId: s.asset_id,
|
|
457
|
+
supplyBalance: toBigInt(s.supply_balance),
|
|
458
|
+
borrowBalance: toBigInt(s.borrow_balance)
|
|
459
|
+
}));
|
|
460
|
+
return mapped.filter((s) => s.supplyBalance !== 0n || s.borrowBalance !== 0n);
|
|
461
|
+
}
|
|
462
|
+
async function fetchCoins(client, owner, coinType) {
|
|
463
|
+
const all = [];
|
|
464
|
+
let cursor;
|
|
465
|
+
let hasNext = true;
|
|
466
|
+
while (hasNext) {
|
|
467
|
+
const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
|
|
468
|
+
all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
|
|
469
|
+
cursor = page.nextCursor;
|
|
470
|
+
hasNext = page.hasNextPage;
|
|
471
|
+
}
|
|
472
|
+
return all;
|
|
473
|
+
}
|
|
474
|
+
function mergeCoins(tx, coins) {
|
|
475
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
|
|
476
|
+
const primary = tx.object(coins[0].coinObjectId);
|
|
477
|
+
if (coins.length > 1) {
|
|
478
|
+
tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
479
|
+
}
|
|
480
|
+
return primary;
|
|
481
|
+
}
|
|
482
|
+
async function buildSaveTx(client, address, amount, options = {}) {
|
|
483
|
+
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
484
|
+
throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
|
|
485
|
+
}
|
|
486
|
+
const asset = options.asset ?? "USDC";
|
|
487
|
+
const assetInfo = resolveAssetInfo(asset);
|
|
488
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
489
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
490
|
+
const coins = await fetchCoins(client, address, assetInfo.type);
|
|
491
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
|
|
492
|
+
const tx = new Transaction();
|
|
493
|
+
tx.setSender(address);
|
|
494
|
+
const coinObj = mergeCoins(tx, coins);
|
|
495
|
+
if (options.collectFee) {
|
|
496
|
+
addCollectFeeToTx(tx, coinObj, "save");
|
|
497
|
+
}
|
|
498
|
+
tx.moveCall({
|
|
499
|
+
target: `${config.package}::incentive_v3::entry_deposit`,
|
|
500
|
+
arguments: [
|
|
501
|
+
tx.object(CLOCK),
|
|
502
|
+
tx.object(config.storage),
|
|
503
|
+
tx.object(pool.contract.pool),
|
|
504
|
+
tx.pure.u8(pool.id),
|
|
505
|
+
coinObj,
|
|
506
|
+
tx.pure.u64(rawAmount),
|
|
507
|
+
tx.object(config.incentiveV2),
|
|
508
|
+
tx.object(config.incentiveV3)
|
|
509
|
+
],
|
|
510
|
+
typeArguments: [pool.suiCoinType]
|
|
511
|
+
});
|
|
512
|
+
return tx;
|
|
513
|
+
}
|
|
514
|
+
async function buildWithdrawTx(client, address, amount, options = {}) {
|
|
515
|
+
const asset = options.asset ?? "USDC";
|
|
516
|
+
const assetInfo = resolveAssetInfo(asset);
|
|
517
|
+
const [config, pool, pools, states] = await Promise.all([
|
|
518
|
+
getConfig(),
|
|
519
|
+
getPool(asset),
|
|
520
|
+
getPools(),
|
|
521
|
+
getUserState(client, address)
|
|
522
|
+
]);
|
|
523
|
+
const assetState = states.find((s) => s.assetId === pool.id);
|
|
524
|
+
const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex, pool) : 0;
|
|
525
|
+
const effectiveAmount = Math.min(amount, Math.max(0, deposited - withdrawDustBuffer(assetInfo.decimals)));
|
|
526
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
|
|
527
|
+
const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
|
|
528
|
+
if (rawAmount <= 0) {
|
|
529
|
+
throw new T2000Error("INVALID_AMOUNT", `Withdrawal amount rounds to zero \u2014 balance is dust`);
|
|
530
|
+
}
|
|
531
|
+
const tx = new Transaction();
|
|
532
|
+
tx.setSender(address);
|
|
533
|
+
refreshOracles(tx, config, pools);
|
|
534
|
+
const [balance] = tx.moveCall({
|
|
535
|
+
target: `${config.package}::incentive_v3::withdraw_v2`,
|
|
536
|
+
arguments: [
|
|
537
|
+
tx.object(CLOCK),
|
|
538
|
+
tx.object(config.oracle.priceOracle),
|
|
539
|
+
tx.object(config.storage),
|
|
540
|
+
tx.object(pool.contract.pool),
|
|
541
|
+
tx.pure.u8(pool.id),
|
|
542
|
+
tx.pure.u64(rawAmount),
|
|
543
|
+
tx.object(config.incentiveV2),
|
|
544
|
+
tx.object(config.incentiveV3),
|
|
545
|
+
tx.object(SUI_SYSTEM_STATE)
|
|
546
|
+
],
|
|
547
|
+
typeArguments: [pool.suiCoinType]
|
|
548
|
+
});
|
|
549
|
+
const [coin] = tx.moveCall({
|
|
550
|
+
target: "0x2::coin::from_balance",
|
|
551
|
+
arguments: [balance],
|
|
552
|
+
typeArguments: [pool.suiCoinType]
|
|
553
|
+
});
|
|
554
|
+
tx.transferObjects([coin], address);
|
|
555
|
+
return { tx, effectiveAmount };
|
|
556
|
+
}
|
|
557
|
+
async function addWithdrawToTx(tx, client, address, amount, options = {}) {
|
|
558
|
+
const asset = options.asset ?? "USDC";
|
|
559
|
+
const assetInfo = resolveAssetInfo(asset);
|
|
560
|
+
const [config, pool, pools, states] = await Promise.all([
|
|
561
|
+
getConfig(),
|
|
562
|
+
getPool(asset),
|
|
563
|
+
getPools(),
|
|
564
|
+
getUserState(client, address)
|
|
565
|
+
]);
|
|
566
|
+
const assetState = states.find((s) => s.assetId === pool.id);
|
|
567
|
+
const deposited = assetState ? compoundBalance(assetState.supplyBalance, pool.currentSupplyIndex, pool) : 0;
|
|
568
|
+
const effectiveAmount = Math.min(amount, Math.max(0, deposited - withdrawDustBuffer(assetInfo.decimals)));
|
|
569
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on NAVI`);
|
|
570
|
+
const rawAmount = Number(stableToRaw(effectiveAmount, assetInfo.decimals));
|
|
571
|
+
if (rawAmount <= 0) {
|
|
572
|
+
const [coin2] = tx.moveCall({
|
|
573
|
+
target: "0x2::coin::zero",
|
|
574
|
+
typeArguments: [pool.suiCoinType]
|
|
575
|
+
});
|
|
576
|
+
return { coin: coin2, effectiveAmount: 0 };
|
|
577
|
+
}
|
|
578
|
+
refreshOracles(tx, config, pools);
|
|
579
|
+
const [balance] = tx.moveCall({
|
|
580
|
+
target: `${config.package}::incentive_v3::withdraw_v2`,
|
|
581
|
+
arguments: [
|
|
582
|
+
tx.object(CLOCK),
|
|
583
|
+
tx.object(config.oracle.priceOracle),
|
|
584
|
+
tx.object(config.storage),
|
|
585
|
+
tx.object(pool.contract.pool),
|
|
586
|
+
tx.pure.u8(pool.id),
|
|
587
|
+
tx.pure.u64(rawAmount),
|
|
588
|
+
tx.object(config.incentiveV2),
|
|
589
|
+
tx.object(config.incentiveV3),
|
|
590
|
+
tx.object(SUI_SYSTEM_STATE)
|
|
591
|
+
],
|
|
592
|
+
typeArguments: [pool.suiCoinType]
|
|
593
|
+
});
|
|
594
|
+
const [coin] = tx.moveCall({
|
|
595
|
+
target: "0x2::coin::from_balance",
|
|
596
|
+
arguments: [balance],
|
|
597
|
+
typeArguments: [pool.suiCoinType]
|
|
598
|
+
});
|
|
599
|
+
return { coin, effectiveAmount };
|
|
600
|
+
}
|
|
601
|
+
async function addSaveToTx(tx, _client, _address, coin, options = {}) {
|
|
602
|
+
const asset = options.asset ?? "USDC";
|
|
603
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
604
|
+
if (options.collectFee) {
|
|
605
|
+
addCollectFeeToTx(tx, coin, "save");
|
|
606
|
+
}
|
|
607
|
+
const [coinValue] = tx.moveCall({
|
|
608
|
+
target: "0x2::coin::value",
|
|
609
|
+
typeArguments: [pool.suiCoinType],
|
|
610
|
+
arguments: [coin]
|
|
611
|
+
});
|
|
612
|
+
tx.moveCall({
|
|
613
|
+
target: `${config.package}::incentive_v3::entry_deposit`,
|
|
614
|
+
arguments: [
|
|
615
|
+
tx.object(CLOCK),
|
|
616
|
+
tx.object(config.storage),
|
|
617
|
+
tx.object(pool.contract.pool),
|
|
618
|
+
tx.pure.u8(pool.id),
|
|
619
|
+
coin,
|
|
620
|
+
coinValue,
|
|
621
|
+
tx.object(config.incentiveV2),
|
|
622
|
+
tx.object(config.incentiveV3)
|
|
623
|
+
],
|
|
624
|
+
typeArguments: [pool.suiCoinType]
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
async function addRepayToTx(tx, _client, _address, coin, options = {}) {
|
|
628
|
+
const asset = options.asset ?? "USDC";
|
|
629
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
630
|
+
addOracleUpdate(tx, config, pool);
|
|
631
|
+
const [coinValue] = tx.moveCall({
|
|
632
|
+
target: "0x2::coin::value",
|
|
633
|
+
typeArguments: [pool.suiCoinType],
|
|
634
|
+
arguments: [coin]
|
|
635
|
+
});
|
|
636
|
+
tx.moveCall({
|
|
637
|
+
target: `${config.package}::incentive_v3::entry_repay`,
|
|
638
|
+
arguments: [
|
|
639
|
+
tx.object(CLOCK),
|
|
640
|
+
tx.object(config.oracle.priceOracle),
|
|
641
|
+
tx.object(config.storage),
|
|
642
|
+
tx.object(pool.contract.pool),
|
|
643
|
+
tx.pure.u8(pool.id),
|
|
644
|
+
coin,
|
|
645
|
+
coinValue,
|
|
646
|
+
tx.object(config.incentiveV2),
|
|
647
|
+
tx.object(config.incentiveV3)
|
|
648
|
+
],
|
|
649
|
+
typeArguments: [pool.suiCoinType]
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
async function buildBorrowTx(client, address, amount, options = {}) {
|
|
653
|
+
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
654
|
+
throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
|
|
655
|
+
}
|
|
656
|
+
const asset = options.asset ?? "USDC";
|
|
657
|
+
const assetInfo = resolveAssetInfo(asset);
|
|
658
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
659
|
+
const [config, pool, pools] = await Promise.all([
|
|
660
|
+
getConfig(),
|
|
661
|
+
getPool(asset),
|
|
662
|
+
getPools()
|
|
663
|
+
]);
|
|
664
|
+
const tx = new Transaction();
|
|
665
|
+
tx.setSender(address);
|
|
666
|
+
refreshOracles(tx, config, pools);
|
|
667
|
+
const [balance] = tx.moveCall({
|
|
668
|
+
target: `${config.package}::incentive_v3::borrow_v2`,
|
|
669
|
+
arguments: [
|
|
670
|
+
tx.object(CLOCK),
|
|
671
|
+
tx.object(config.oracle.priceOracle),
|
|
672
|
+
tx.object(config.storage),
|
|
673
|
+
tx.object(pool.contract.pool),
|
|
674
|
+
tx.pure.u8(pool.id),
|
|
675
|
+
tx.pure.u64(rawAmount),
|
|
676
|
+
tx.object(config.incentiveV2),
|
|
677
|
+
tx.object(config.incentiveV3),
|
|
678
|
+
tx.object(SUI_SYSTEM_STATE)
|
|
679
|
+
],
|
|
680
|
+
typeArguments: [pool.suiCoinType]
|
|
681
|
+
});
|
|
682
|
+
const [borrowedCoin] = tx.moveCall({
|
|
683
|
+
target: "0x2::coin::from_balance",
|
|
684
|
+
arguments: [balance],
|
|
685
|
+
typeArguments: [pool.suiCoinType]
|
|
686
|
+
});
|
|
687
|
+
if (options.collectFee) {
|
|
688
|
+
addCollectFeeToTx(tx, borrowedCoin, "borrow");
|
|
689
|
+
}
|
|
690
|
+
tx.transferObjects([borrowedCoin], address);
|
|
691
|
+
return tx;
|
|
692
|
+
}
|
|
693
|
+
async function buildRepayTx(client, address, amount, options = {}) {
|
|
694
|
+
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
|
|
695
|
+
throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
|
|
696
|
+
}
|
|
697
|
+
const asset = options.asset ?? "USDC";
|
|
698
|
+
const assetInfo = resolveAssetInfo(asset);
|
|
699
|
+
const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
|
|
700
|
+
const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
|
|
701
|
+
const coins = await fetchCoins(client, address, assetInfo.type);
|
|
702
|
+
if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
|
|
703
|
+
const tx = new Transaction();
|
|
704
|
+
tx.setSender(address);
|
|
705
|
+
addOracleUpdate(tx, config, pool);
|
|
706
|
+
const coinObj = mergeCoins(tx, coins);
|
|
707
|
+
tx.moveCall({
|
|
708
|
+
target: `${config.package}::incentive_v3::entry_repay`,
|
|
709
|
+
arguments: [
|
|
710
|
+
tx.object(CLOCK),
|
|
711
|
+
tx.object(config.oracle.priceOracle),
|
|
712
|
+
tx.object(config.storage),
|
|
713
|
+
tx.object(pool.contract.pool),
|
|
714
|
+
tx.pure.u8(pool.id),
|
|
715
|
+
coinObj,
|
|
716
|
+
tx.pure.u64(rawAmount),
|
|
717
|
+
tx.object(config.incentiveV2),
|
|
718
|
+
tx.object(config.incentiveV3)
|
|
719
|
+
],
|
|
720
|
+
typeArguments: [pool.suiCoinType]
|
|
721
|
+
});
|
|
722
|
+
return tx;
|
|
723
|
+
}
|
|
724
|
+
async function getHealthFactor(client, addressOrKeypair) {
|
|
725
|
+
const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
|
|
726
|
+
const [config, pools, states] = await Promise.all([
|
|
727
|
+
getConfig(),
|
|
728
|
+
getPools(),
|
|
729
|
+
getUserState(client, address)
|
|
730
|
+
]);
|
|
731
|
+
let supplied = 0;
|
|
732
|
+
let borrowed = 0;
|
|
733
|
+
let weightedLtv = 0;
|
|
734
|
+
let weightedLiqThreshold = 0;
|
|
735
|
+
for (const state of states) {
|
|
736
|
+
const pool = pools.find((p) => p.id === state.assetId);
|
|
737
|
+
if (!pool) continue;
|
|
738
|
+
const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex, pool);
|
|
739
|
+
const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex, pool);
|
|
740
|
+
const price = pool.token?.price ?? 1;
|
|
741
|
+
supplied += supplyBal * price;
|
|
742
|
+
borrowed += borrowBal * price;
|
|
743
|
+
if (supplyBal > 0) {
|
|
744
|
+
weightedLtv += supplyBal * price * parseLtv(pool.ltv);
|
|
745
|
+
weightedLiqThreshold += supplyBal * price * parseLiqThreshold(pool.liquidationFactor.threshold);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const ltv = supplied > 0 ? weightedLtv / supplied : 0.75;
|
|
749
|
+
const liqThreshold = supplied > 0 ? weightedLiqThreshold / supplied : 0.75;
|
|
750
|
+
const maxBorrowVal = Math.max(0, supplied * ltv - borrowed);
|
|
751
|
+
const usdcPool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", SUPPORTED_ASSETS.USDC.type));
|
|
752
|
+
let healthFactor;
|
|
753
|
+
if (borrowed <= 0) {
|
|
754
|
+
healthFactor = Infinity;
|
|
755
|
+
} else if (usdcPool) {
|
|
756
|
+
try {
|
|
757
|
+
const tx = new Transaction();
|
|
758
|
+
tx.moveCall({
|
|
759
|
+
target: `${config.uiGetter}::calculator_unchecked::dynamic_health_factor`,
|
|
760
|
+
arguments: [
|
|
761
|
+
tx.object(CLOCK),
|
|
762
|
+
tx.object(config.storage),
|
|
763
|
+
tx.object(config.oracle.priceOracle),
|
|
764
|
+
tx.pure.u8(usdcPool.id),
|
|
765
|
+
tx.pure.address(address),
|
|
766
|
+
tx.pure.u8(usdcPool.id),
|
|
767
|
+
tx.pure.u64(0),
|
|
768
|
+
tx.pure.u64(0),
|
|
769
|
+
tx.pure.bool(false)
|
|
770
|
+
],
|
|
771
|
+
typeArguments: [usdcPool.suiCoinType]
|
|
772
|
+
});
|
|
773
|
+
const result = await client.devInspectTransactionBlock({
|
|
774
|
+
transactionBlock: tx,
|
|
775
|
+
sender: address
|
|
776
|
+
});
|
|
777
|
+
const decoded = decodeDevInspect(result, bcs.u256());
|
|
778
|
+
if (decoded !== void 0) {
|
|
779
|
+
healthFactor = normalizeHealthFactor(Number(decoded));
|
|
780
|
+
} else {
|
|
781
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
782
|
+
}
|
|
783
|
+
} catch {
|
|
784
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
785
|
+
}
|
|
786
|
+
} else {
|
|
787
|
+
healthFactor = supplied * liqThreshold / borrowed;
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
healthFactor,
|
|
791
|
+
supplied,
|
|
792
|
+
borrowed,
|
|
793
|
+
maxBorrow: maxBorrowVal,
|
|
794
|
+
liquidationThreshold: liqThreshold
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
var NAVI_SUPPORTED_ASSETS = [...STABLE_ASSETS, "SUI", "ETH", "GOLD"];
|
|
798
|
+
async function getRates(client) {
|
|
799
|
+
try {
|
|
800
|
+
const pools = await getPools();
|
|
801
|
+
const result = {};
|
|
802
|
+
for (const asset of NAVI_SUPPORTED_ASSETS) {
|
|
803
|
+
const targetType = SUPPORTED_ASSETS[asset].type;
|
|
804
|
+
const pool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType));
|
|
805
|
+
if (!pool) continue;
|
|
806
|
+
let saveApy = poolSaveApy(pool);
|
|
807
|
+
let borrowApy = poolBorrowApy(pool);
|
|
808
|
+
if (saveApy <= 0 || saveApy > 200) saveApy = 0;
|
|
809
|
+
if (borrowApy <= 0 || borrowApy > 200) borrowApy = 0;
|
|
810
|
+
result[asset] = { saveApy, borrowApy };
|
|
811
|
+
}
|
|
812
|
+
if (!result.USDC) result.USDC = { saveApy: 4, borrowApy: 6 };
|
|
813
|
+
return result;
|
|
814
|
+
} catch {
|
|
815
|
+
return { USDC: { saveApy: 4, borrowApy: 6 } };
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function getPositions(client, addressOrKeypair) {
|
|
819
|
+
const address = typeof addressOrKeypair === "string" ? addressOrKeypair : addressOrKeypair.getPublicKey().toSuiAddress();
|
|
820
|
+
const [states, pools] = await Promise.all([getUserState(client, address), getPools()]);
|
|
821
|
+
const positions = [];
|
|
822
|
+
for (const state of states) {
|
|
823
|
+
const pool = pools.find((p) => p.id === state.assetId);
|
|
824
|
+
if (!pool) continue;
|
|
825
|
+
const symbol = resolvePoolSymbol(pool);
|
|
826
|
+
const supplyBal = compoundBalance(state.supplyBalance, pool.currentSupplyIndex, pool);
|
|
827
|
+
const borrowBal = compoundBalance(state.borrowBalance, pool.currentBorrowIndex, pool);
|
|
828
|
+
if (supplyBal > 1e-4) {
|
|
829
|
+
positions.push({
|
|
830
|
+
protocol: "navi",
|
|
831
|
+
asset: symbol,
|
|
832
|
+
type: "save",
|
|
833
|
+
amount: supplyBal,
|
|
834
|
+
apy: poolSaveApy(pool)
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
if (borrowBal > 1e-4) {
|
|
838
|
+
positions.push({
|
|
839
|
+
protocol: "navi",
|
|
840
|
+
asset: symbol,
|
|
841
|
+
type: "borrow",
|
|
842
|
+
amount: borrowBal,
|
|
843
|
+
apy: poolBorrowApy(pool)
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return { positions };
|
|
848
|
+
}
|
|
849
|
+
async function maxWithdrawAmount(client, addressOrKeypair) {
|
|
850
|
+
const hf = await getHealthFactor(client, addressOrKeypair);
|
|
851
|
+
const ltv = hf.liquidationThreshold > 0 ? hf.liquidationThreshold : 0.75;
|
|
852
|
+
let maxAmount;
|
|
853
|
+
if (hf.borrowed === 0) {
|
|
854
|
+
maxAmount = hf.supplied;
|
|
855
|
+
} else {
|
|
856
|
+
maxAmount = Math.max(0, hf.supplied - hf.borrowed * MIN_HEALTH_FACTOR / ltv);
|
|
857
|
+
}
|
|
858
|
+
const remainingSupply = hf.supplied - maxAmount;
|
|
859
|
+
const hfAfter = hf.borrowed > 0 ? remainingSupply * ltv / hf.borrowed : Infinity;
|
|
860
|
+
return { maxAmount, healthFactorAfter: hfAfter, currentHF: hf.healthFactor };
|
|
861
|
+
}
|
|
862
|
+
async function maxBorrowAmount(client, addressOrKeypair) {
|
|
863
|
+
const hf = await getHealthFactor(client, addressOrKeypair);
|
|
864
|
+
const ltv = hf.liquidationThreshold > 0 ? hf.liquidationThreshold : 0.75;
|
|
865
|
+
const maxAmount = Math.max(0, hf.supplied * ltv / MIN_HEALTH_FACTOR - hf.borrowed);
|
|
866
|
+
return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR, currentHF: hf.healthFactor };
|
|
867
|
+
}
|
|
868
|
+
var CERT_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
|
|
869
|
+
var DEEP_TYPE = "0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270::deep::DEEP";
|
|
870
|
+
var REWARD_FUNDS = {
|
|
871
|
+
[CERT_TYPE]: "0x7093cf7549d5e5b35bfde2177223d1050f71655c7f676a5e610ee70eb4d93b5c",
|
|
872
|
+
[DEEP_TYPE]: "0xc889d78b634f954979e80e622a2ae0fece824c0f6d9590044378a2563035f32f"
|
|
873
|
+
};
|
|
874
|
+
var REWARD_SYMBOLS = {
|
|
875
|
+
[CERT_TYPE]: "vSUI",
|
|
876
|
+
[DEEP_TYPE]: "DEEP"
|
|
877
|
+
};
|
|
878
|
+
var incentiveRulesCache = null;
|
|
879
|
+
async function getIncentiveRules(client) {
|
|
880
|
+
if (incentiveRulesCache && Date.now() - incentiveRulesCache.ts < CACHE_TTL) {
|
|
881
|
+
return incentiveRulesCache.data;
|
|
882
|
+
}
|
|
883
|
+
const [pools, obj] = await Promise.all([
|
|
884
|
+
getPools(),
|
|
885
|
+
client.getObject({
|
|
886
|
+
id: "0x62982dad27fb10bb314b3384d5de8d2ac2d72ab2dbeae5d801dbdb9efa816c80",
|
|
887
|
+
options: { showContent: true }
|
|
888
|
+
})
|
|
889
|
+
]);
|
|
890
|
+
const rewardCoinMap = /* @__PURE__ */ new Map();
|
|
891
|
+
for (const pool of pools) {
|
|
892
|
+
const ct = (pool.suiCoinType || pool.coinType || "").toLowerCase();
|
|
893
|
+
const suffix = ct.split("::").slice(1).join("::");
|
|
894
|
+
const coins = pool.supplyIncentiveApyInfo?.rewardCoin;
|
|
895
|
+
if (Array.isArray(coins) && coins.length > 0) {
|
|
896
|
+
rewardCoinMap.set(suffix, coins[0]);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const result = /* @__PURE__ */ new Map();
|
|
900
|
+
if (obj.data?.content?.dataType !== "moveObject") {
|
|
901
|
+
incentiveRulesCache = { data: result, ts: Date.now() };
|
|
902
|
+
return result;
|
|
903
|
+
}
|
|
904
|
+
const fields = obj.data.content.fields;
|
|
905
|
+
const poolsObj = fields.pools;
|
|
906
|
+
const entries = poolsObj?.fields?.contents;
|
|
907
|
+
if (!Array.isArray(entries)) {
|
|
908
|
+
incentiveRulesCache = { data: result, ts: Date.now() };
|
|
909
|
+
return result;
|
|
910
|
+
}
|
|
911
|
+
for (const entry of entries) {
|
|
912
|
+
const ef = entry?.fields;
|
|
913
|
+
if (!ef) continue;
|
|
914
|
+
const key = String(ef.key ?? "");
|
|
915
|
+
const value = ef.value;
|
|
916
|
+
const rules = value?.fields?.rules;
|
|
917
|
+
const ruleEntries = rules?.fields?.contents;
|
|
918
|
+
if (!Array.isArray(ruleEntries)) continue;
|
|
919
|
+
const ruleIds = ruleEntries.map((re) => {
|
|
920
|
+
const rf = re?.fields;
|
|
921
|
+
return String(rf?.key ?? "");
|
|
922
|
+
}).filter(Boolean);
|
|
923
|
+
const suffix = key.split("::").slice(1).join("::").toLowerCase();
|
|
924
|
+
const full = key.toLowerCase();
|
|
925
|
+
const rewardCoin = rewardCoinMap.get(suffix) ?? rewardCoinMap.get(full) ?? null;
|
|
926
|
+
result.set(key, { ruleIds, rewardCoinType: rewardCoin });
|
|
927
|
+
}
|
|
928
|
+
incentiveRulesCache = { data: result, ts: Date.now() };
|
|
929
|
+
return result;
|
|
930
|
+
}
|
|
931
|
+
function stripPrefix(coinType) {
|
|
932
|
+
return coinType.replace(/^0x0*/, "");
|
|
933
|
+
}
|
|
934
|
+
async function getPendingRewards(client, address) {
|
|
935
|
+
const [pools, states] = await Promise.all([
|
|
936
|
+
getPools(),
|
|
937
|
+
getUserState(client, address)
|
|
938
|
+
]);
|
|
939
|
+
const rewards = [];
|
|
940
|
+
const deposited = states.filter((s) => s.supplyBalance > 0n);
|
|
941
|
+
if (deposited.length === 0) return rewards;
|
|
942
|
+
for (const state of deposited) {
|
|
943
|
+
const pool = pools.find((p) => p.id === state.assetId);
|
|
944
|
+
if (!pool) continue;
|
|
945
|
+
const boostedApr = parseFloat(pool.supplyIncentiveApyInfo?.boostedApr ?? "0");
|
|
946
|
+
if (boostedApr <= 0) continue;
|
|
947
|
+
const rewardCoins = pool.supplyIncentiveApyInfo?.rewardCoin;
|
|
948
|
+
if (!Array.isArray(rewardCoins) || rewardCoins.length === 0) continue;
|
|
949
|
+
const rewardType = rewardCoins[0];
|
|
950
|
+
const assetSymbol = resolvePoolSymbol(pool);
|
|
951
|
+
rewards.push({
|
|
952
|
+
protocol: "navi",
|
|
953
|
+
asset: assetSymbol,
|
|
954
|
+
coinType: rewardType,
|
|
955
|
+
symbol: REWARD_SYMBOLS[rewardType] ?? rewardType.split("::").pop() ?? "UNKNOWN",
|
|
956
|
+
amount: 0,
|
|
957
|
+
estimatedValueUsd: 0
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
return rewards;
|
|
961
|
+
}
|
|
962
|
+
async function addClaimRewardsToTx(tx, client, address) {
|
|
963
|
+
const [config, pools, states, rules] = await Promise.all([
|
|
964
|
+
getConfig(),
|
|
965
|
+
getPools(),
|
|
966
|
+
getUserState(client, address),
|
|
967
|
+
getIncentiveRules(client)
|
|
968
|
+
]);
|
|
969
|
+
const deposited = states.filter((s) => s.supplyBalance > 0n);
|
|
970
|
+
if (deposited.length === 0) return [];
|
|
971
|
+
const claimGroups = /* @__PURE__ */ new Map();
|
|
972
|
+
for (const state of deposited) {
|
|
973
|
+
const pool = pools.find((p) => p.id === state.assetId);
|
|
974
|
+
if (!pool) continue;
|
|
975
|
+
const boostedApr = parseFloat(pool.supplyIncentiveApyInfo?.boostedApr ?? "0");
|
|
976
|
+
if (boostedApr <= 0) continue;
|
|
977
|
+
const rewardCoins = pool.supplyIncentiveApyInfo?.rewardCoin;
|
|
978
|
+
if (!Array.isArray(rewardCoins) || rewardCoins.length === 0) continue;
|
|
979
|
+
const rewardType = rewardCoins[0];
|
|
980
|
+
const fundId = REWARD_FUNDS[rewardType];
|
|
981
|
+
if (!fundId) continue;
|
|
982
|
+
const coinType = pool.suiCoinType || pool.coinType || "";
|
|
983
|
+
const strippedType = stripPrefix(coinType);
|
|
984
|
+
const ruleData = Array.from(rules.entries()).find(
|
|
985
|
+
([key]) => stripPrefix(key) === strippedType || key.split("::").slice(1).join("::").toLowerCase() === coinType.split("::").slice(1).join("::").toLowerCase()
|
|
986
|
+
);
|
|
987
|
+
if (!ruleData || ruleData[1].ruleIds.length === 0) continue;
|
|
988
|
+
const group = claimGroups.get(rewardType) ?? { assets: [], ruleIds: [] };
|
|
989
|
+
for (const ruleId of ruleData[1].ruleIds) {
|
|
990
|
+
group.assets.push(strippedType);
|
|
991
|
+
group.ruleIds.push(ruleId);
|
|
992
|
+
}
|
|
993
|
+
claimGroups.set(rewardType, group);
|
|
994
|
+
}
|
|
995
|
+
const claimed = [];
|
|
996
|
+
for (const [rewardType, { assets, ruleIds }] of claimGroups) {
|
|
997
|
+
const fundId = REWARD_FUNDS[rewardType];
|
|
998
|
+
const [balance] = tx.moveCall({
|
|
999
|
+
target: `${config.package}::incentive_v3::claim_reward`,
|
|
1000
|
+
typeArguments: [rewardType],
|
|
1001
|
+
arguments: [
|
|
1002
|
+
tx.object(CLOCK),
|
|
1003
|
+
tx.object(config.incentiveV3),
|
|
1004
|
+
tx.object(config.storage),
|
|
1005
|
+
tx.object(fundId),
|
|
1006
|
+
tx.pure(bcs.vector(bcs.string()).serialize(assets)),
|
|
1007
|
+
tx.pure(bcs.vector(bcs.Address).serialize(ruleIds))
|
|
1008
|
+
]
|
|
1009
|
+
});
|
|
1010
|
+
const [coin] = tx.moveCall({
|
|
1011
|
+
target: "0x2::coin::from_balance",
|
|
1012
|
+
typeArguments: [rewardType],
|
|
1013
|
+
arguments: [balance]
|
|
1014
|
+
});
|
|
1015
|
+
tx.transferObjects([coin], address);
|
|
1016
|
+
claimed.push({
|
|
1017
|
+
protocol: "navi",
|
|
1018
|
+
asset: assets.join(", "),
|
|
1019
|
+
coinType: rewardType,
|
|
1020
|
+
symbol: REWARD_SYMBOLS[rewardType] ?? "UNKNOWN",
|
|
1021
|
+
amount: 0,
|
|
1022
|
+
estimatedValueUsd: 0
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
return claimed;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/adapters/navi.ts
|
|
1029
|
+
var descriptor = {
|
|
1030
|
+
id: "navi",
|
|
1031
|
+
name: "NAVI Protocol",
|
|
1032
|
+
packages: [],
|
|
1033
|
+
dynamicPackageId: true,
|
|
1034
|
+
actionMap: {
|
|
1035
|
+
"incentive_v3::entry_deposit": "save",
|
|
1036
|
+
"incentive_v3::deposit": "save",
|
|
1037
|
+
"incentive_v3::withdraw_v2": "withdraw",
|
|
1038
|
+
"incentive_v3::entry_withdraw": "withdraw",
|
|
1039
|
+
"incentive_v3::borrow_v2": "borrow",
|
|
1040
|
+
"incentive_v3::entry_borrow": "borrow",
|
|
1041
|
+
"incentive_v3::entry_repay": "repay",
|
|
1042
|
+
"incentive_v3::repay": "repay"
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
var NaviAdapter = class {
|
|
1046
|
+
id = "navi";
|
|
1047
|
+
name = "NAVI Protocol";
|
|
1048
|
+
version = "1.0.0";
|
|
1049
|
+
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
1050
|
+
supportedAssets = [...STABLE_ASSETS, "SUI", "ETH", "GOLD"];
|
|
1051
|
+
supportsSameAssetBorrow = true;
|
|
1052
|
+
client;
|
|
1053
|
+
async init(client) {
|
|
1054
|
+
this.client = client;
|
|
1055
|
+
}
|
|
1056
|
+
initSync(client) {
|
|
1057
|
+
this.client = client;
|
|
1058
|
+
}
|
|
1059
|
+
async getRates(asset) {
|
|
1060
|
+
const rates = await getRates(this.client);
|
|
1061
|
+
const normalized = normalizeAsset(asset);
|
|
1062
|
+
const r = rates[normalized];
|
|
1063
|
+
if (!r) throw new T2000Error("ASSET_NOT_SUPPORTED", `NAVI does not support ${asset}`);
|
|
1064
|
+
return { asset: normalized, saveApy: r.saveApy, borrowApy: r.borrowApy };
|
|
1065
|
+
}
|
|
1066
|
+
async getPositions(address) {
|
|
1067
|
+
const result = await getPositions(this.client, address);
|
|
1068
|
+
return {
|
|
1069
|
+
supplies: result.positions.filter((p) => p.type === "save").map((p) => ({ asset: p.asset, amount: p.amount, apy: p.apy })),
|
|
1070
|
+
borrows: result.positions.filter((p) => p.type === "borrow").map((p) => ({ asset: p.asset, amount: p.amount, apy: p.apy }))
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
async getHealth(address) {
|
|
1074
|
+
return getHealthFactor(this.client, address);
|
|
1075
|
+
}
|
|
1076
|
+
async buildSaveTx(address, amount, asset, options) {
|
|
1077
|
+
const normalized = normalizeAsset(asset);
|
|
1078
|
+
const tx = await buildSaveTx(this.client, address, amount, { ...options, asset: normalized });
|
|
1079
|
+
return { tx };
|
|
1080
|
+
}
|
|
1081
|
+
async buildWithdrawTx(address, amount, asset) {
|
|
1082
|
+
const normalized = normalizeAsset(asset);
|
|
1083
|
+
const result = await buildWithdrawTx(this.client, address, amount, { asset: normalized });
|
|
1084
|
+
return { tx: result.tx, effectiveAmount: result.effectiveAmount };
|
|
1085
|
+
}
|
|
1086
|
+
async buildBorrowTx(address, amount, asset, options) {
|
|
1087
|
+
const normalized = normalizeAsset(asset);
|
|
1088
|
+
const tx = await buildBorrowTx(this.client, address, amount, { ...options, asset: normalized });
|
|
1089
|
+
return { tx };
|
|
1090
|
+
}
|
|
1091
|
+
async buildRepayTx(address, amount, asset) {
|
|
1092
|
+
const normalized = normalizeAsset(asset);
|
|
1093
|
+
const tx = await buildRepayTx(this.client, address, amount, { asset: normalized });
|
|
1094
|
+
return { tx };
|
|
1095
|
+
}
|
|
1096
|
+
async maxWithdraw(address, _asset) {
|
|
1097
|
+
return maxWithdrawAmount(this.client, address);
|
|
1098
|
+
}
|
|
1099
|
+
async maxBorrow(address, _asset) {
|
|
1100
|
+
return maxBorrowAmount(this.client, address);
|
|
1101
|
+
}
|
|
1102
|
+
async addWithdrawToTx(tx, address, amount, asset) {
|
|
1103
|
+
const normalized = normalizeAsset(asset);
|
|
1104
|
+
return addWithdrawToTx(tx, this.client, address, amount, { asset: normalized });
|
|
1105
|
+
}
|
|
1106
|
+
async addSaveToTx(tx, address, coin, asset, options) {
|
|
1107
|
+
const normalized = normalizeAsset(asset);
|
|
1108
|
+
return addSaveToTx(tx, this.client, address, coin, { ...options, asset: normalized });
|
|
1109
|
+
}
|
|
1110
|
+
async addRepayToTx(tx, address, coin, asset) {
|
|
1111
|
+
const normalized = normalizeAsset(asset);
|
|
1112
|
+
return addRepayToTx(tx, this.client, address, coin, { asset: normalized });
|
|
1113
|
+
}
|
|
1114
|
+
async getPendingRewards(address) {
|
|
1115
|
+
return getPendingRewards(this.client, address);
|
|
1116
|
+
}
|
|
1117
|
+
async addClaimRewardsToTx(tx, address) {
|
|
1118
|
+
return addClaimRewardsToTx(tx, this.client, address);
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
var DEFAULT_SLIPPAGE_BPS = 300;
|
|
1122
|
+
function createAggregatorClient(client, signer) {
|
|
1123
|
+
return new AggregatorClient({
|
|
1124
|
+
client,
|
|
1125
|
+
signer,
|
|
1126
|
+
env: Env.Mainnet
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
async function buildSwapTx(params) {
|
|
1130
|
+
const { client, address, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
|
|
1131
|
+
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
1132
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
1133
|
+
if (!fromInfo || !toInfo) {
|
|
1134
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
|
|
1135
|
+
}
|
|
1136
|
+
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
1137
|
+
const aggClient = createAggregatorClient(client, address);
|
|
1138
|
+
const _origLog = console.log;
|
|
1139
|
+
console.log = () => {
|
|
1140
|
+
};
|
|
1141
|
+
let result;
|
|
1142
|
+
try {
|
|
1143
|
+
result = await aggClient.findRouters({
|
|
1144
|
+
from: fromInfo.type,
|
|
1145
|
+
target: toInfo.type,
|
|
1146
|
+
amount: rawAmount,
|
|
1147
|
+
byAmountIn: true
|
|
1148
|
+
});
|
|
1149
|
+
} finally {
|
|
1150
|
+
console.log = _origLog;
|
|
1151
|
+
}
|
|
1152
|
+
if (!result || result.insufficientLiquidity) {
|
|
1153
|
+
throw new T2000Error(
|
|
1154
|
+
"ASSET_NOT_SUPPORTED",
|
|
1155
|
+
`No swap route found for ${fromAsset} \u2192 ${toAsset}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
const tx = new Transaction();
|
|
1159
|
+
const slippage = maxSlippageBps / 1e4;
|
|
1160
|
+
console.log = () => {
|
|
1161
|
+
};
|
|
1162
|
+
try {
|
|
1163
|
+
await aggClient.fastRouterSwap({
|
|
1164
|
+
router: result,
|
|
1165
|
+
txb: tx,
|
|
1166
|
+
slippage
|
|
1167
|
+
});
|
|
1168
|
+
} finally {
|
|
1169
|
+
console.log = _origLog;
|
|
1170
|
+
}
|
|
1171
|
+
const estimatedOut = Number(result.amountOut.toString());
|
|
1172
|
+
return {
|
|
1173
|
+
tx,
|
|
1174
|
+
estimatedOut,
|
|
1175
|
+
toDecimals: toInfo.decimals
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
async function addSwapToTx(params) {
|
|
1179
|
+
const { tx, client, address, inputCoin, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
|
|
1180
|
+
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
1181
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
1182
|
+
if (!fromInfo || !toInfo) {
|
|
1183
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
|
|
1184
|
+
}
|
|
1185
|
+
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
1186
|
+
const aggClient = createAggregatorClient(client, address);
|
|
1187
|
+
const _origLog = console.log;
|
|
1188
|
+
console.log = () => {
|
|
1189
|
+
};
|
|
1190
|
+
let result;
|
|
1191
|
+
try {
|
|
1192
|
+
result = await aggClient.findRouters({
|
|
1193
|
+
from: fromInfo.type,
|
|
1194
|
+
target: toInfo.type,
|
|
1195
|
+
amount: rawAmount,
|
|
1196
|
+
byAmountIn: true
|
|
1197
|
+
});
|
|
1198
|
+
} finally {
|
|
1199
|
+
console.log = _origLog;
|
|
1200
|
+
}
|
|
1201
|
+
if (!result || result.insufficientLiquidity) {
|
|
1202
|
+
throw new T2000Error(
|
|
1203
|
+
"ASSET_NOT_SUPPORTED",
|
|
1204
|
+
`No swap route found for ${fromAsset} \u2192 ${toAsset}`
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
const slippage = maxSlippageBps / 1e4;
|
|
1208
|
+
console.log = () => {
|
|
1209
|
+
};
|
|
1210
|
+
let outputCoin;
|
|
1211
|
+
try {
|
|
1212
|
+
outputCoin = await aggClient.routerSwap({
|
|
1213
|
+
router: result,
|
|
1214
|
+
txb: tx,
|
|
1215
|
+
inputCoin,
|
|
1216
|
+
slippage
|
|
1217
|
+
});
|
|
1218
|
+
} finally {
|
|
1219
|
+
console.log = _origLog;
|
|
1220
|
+
}
|
|
1221
|
+
const estimatedOut = Number(result.amountOut.toString());
|
|
1222
|
+
return {
|
|
1223
|
+
outputCoin,
|
|
1224
|
+
estimatedOut,
|
|
1225
|
+
toDecimals: toInfo.decimals
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
async function getPoolPrice(client) {
|
|
1229
|
+
try {
|
|
1230
|
+
const pool = await client.getObject({
|
|
1231
|
+
id: CETUS_USDC_SUI_POOL,
|
|
1232
|
+
options: { showContent: true }
|
|
1233
|
+
});
|
|
1234
|
+
if (pool.data?.content?.dataType === "moveObject") {
|
|
1235
|
+
const fields = pool.data.content.fields;
|
|
1236
|
+
const currentSqrtPrice = BigInt(String(fields.current_sqrt_price ?? "0"));
|
|
1237
|
+
if (currentSqrtPrice > 0n) {
|
|
1238
|
+
const Q64 = 2n ** 64n;
|
|
1239
|
+
const sqrtPriceFloat = Number(currentSqrtPrice) / Number(Q64);
|
|
1240
|
+
const rawPrice = sqrtPriceFloat * sqrtPriceFloat;
|
|
1241
|
+
const suiPriceUsd = 1e3 / rawPrice;
|
|
1242
|
+
if (suiPriceUsd > 0.01 && suiPriceUsd < 1e3) return suiPriceUsd;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
return 3.5;
|
|
1248
|
+
}
|
|
1249
|
+
async function getSwapQuote(client, fromAsset, toAsset, amount) {
|
|
1250
|
+
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
1251
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
1252
|
+
if (!fromInfo || !toInfo) {
|
|
1253
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
|
|
1254
|
+
}
|
|
1255
|
+
const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
|
|
1256
|
+
const poolPrice = await getPoolPrice(client);
|
|
1257
|
+
try {
|
|
1258
|
+
const aggClient = createAggregatorClient(client);
|
|
1259
|
+
const result = await aggClient.findRouters({
|
|
1260
|
+
from: fromInfo.type,
|
|
1261
|
+
target: toInfo.type,
|
|
1262
|
+
amount: rawAmount,
|
|
1263
|
+
byAmountIn: true
|
|
1264
|
+
});
|
|
1265
|
+
if (!result || result.insufficientLiquidity) {
|
|
1266
|
+
return fallbackQuote(fromAsset, amount, poolPrice);
|
|
1267
|
+
}
|
|
1268
|
+
const expectedOutput = Number(result.amountOut.toString()) / 10 ** toInfo.decimals;
|
|
1269
|
+
const priceImpact = result.deviationRatio ?? 0;
|
|
1270
|
+
return { expectedOutput, priceImpact, poolPrice };
|
|
1271
|
+
} catch {
|
|
1272
|
+
return fallbackQuote(fromAsset, amount, poolPrice);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function fallbackQuote(fromAsset, amount, poolPrice) {
|
|
1276
|
+
const expectedOutput = fromAsset === "USDC" ? amount / poolPrice : amount * poolPrice;
|
|
1277
|
+
return { expectedOutput, priceImpact: 0, poolPrice };
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/adapters/cetus.ts
|
|
1281
|
+
var descriptor2 = {
|
|
1282
|
+
id: "cetus",
|
|
1283
|
+
name: "Cetus DEX",
|
|
1284
|
+
packages: [CETUS_PACKAGE],
|
|
1285
|
+
actionMap: {
|
|
1286
|
+
"router::swap": "swap",
|
|
1287
|
+
"router::swap_ab_bc": "swap",
|
|
1288
|
+
"router::swap_ab_cb": "swap",
|
|
1289
|
+
"router::swap_ba_bc": "swap",
|
|
1290
|
+
"router::swap_ba_cb": "swap"
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
var CetusAdapter = class {
|
|
1294
|
+
id = "cetus";
|
|
1295
|
+
name = "Cetus";
|
|
1296
|
+
version = "1.0.0";
|
|
1297
|
+
capabilities = ["swap"];
|
|
1298
|
+
client;
|
|
1299
|
+
async init(client) {
|
|
1300
|
+
this.client = client;
|
|
1301
|
+
}
|
|
1302
|
+
initSync(client) {
|
|
1303
|
+
this.client = client;
|
|
1304
|
+
}
|
|
1305
|
+
async getQuote(from, to, amount) {
|
|
1306
|
+
return getSwapQuote(this.client, from, to, amount);
|
|
1307
|
+
}
|
|
1308
|
+
async buildSwapTx(address, from, to, amount, maxSlippageBps) {
|
|
1309
|
+
const result = await buildSwapTx({
|
|
1310
|
+
client: this.client,
|
|
1311
|
+
address,
|
|
1312
|
+
fromAsset: from,
|
|
1313
|
+
toAsset: to,
|
|
1314
|
+
amount,
|
|
1315
|
+
maxSlippageBps
|
|
1316
|
+
});
|
|
1317
|
+
return {
|
|
1318
|
+
tx: result.tx,
|
|
1319
|
+
estimatedOut: result.estimatedOut,
|
|
1320
|
+
toDecimals: result.toDecimals
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
getSupportedPairs() {
|
|
1324
|
+
const pairs = [];
|
|
1325
|
+
for (const asset of Object.keys(INVESTMENT_ASSETS)) {
|
|
1326
|
+
pairs.push({ from: "USDC", to: asset }, { from: asset, to: "USDC" });
|
|
1327
|
+
}
|
|
1328
|
+
for (const a of STABLE_ASSETS) {
|
|
1329
|
+
for (const b of STABLE_ASSETS) {
|
|
1330
|
+
if (a !== b) pairs.push({ from: a, to: b });
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return pairs;
|
|
1334
|
+
}
|
|
1335
|
+
async getPoolPrice() {
|
|
1336
|
+
return getPoolPrice(this.client);
|
|
1337
|
+
}
|
|
1338
|
+
async addSwapToTx(tx, address, inputCoin, from, to, amount, maxSlippageBps) {
|
|
1339
|
+
return addSwapToTx({
|
|
1340
|
+
tx,
|
|
1341
|
+
client: this.client,
|
|
1342
|
+
address,
|
|
1343
|
+
inputCoin,
|
|
1344
|
+
fromAsset: from,
|
|
1345
|
+
toAsset: to,
|
|
1346
|
+
amount,
|
|
1347
|
+
maxSlippageBps
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
var WAD = 1e18;
|
|
1352
|
+
var MIN_HEALTH_FACTOR2 = 1.5;
|
|
1353
|
+
var CLOCK2 = "0x6";
|
|
1354
|
+
var SUI_SYSTEM_STATE2 = "0x5";
|
|
1355
|
+
var LENDING_MARKET_ID = "0x84030d26d85eaa7035084a057f2f11f701b7e2e4eda87551becbc7c97505ece1";
|
|
1356
|
+
var LENDING_MARKET_TYPE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf::suilend::MAIN_POOL";
|
|
1357
|
+
var SUILEND_PACKAGE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf";
|
|
1358
|
+
var UPGRADE_CAP_ID = "0x3d4ef1859c3ee9fc72858f588b56a09da5466e64f8cc4e90a7b3b909fba8a7ae";
|
|
1359
|
+
var FALLBACK_PUBLISHED_AT = "0x3d4353f3bd3565329655e6b77bc2abfd31e558b86662ebd078ae453d416bc10f";
|
|
1360
|
+
var descriptor3 = {
|
|
1361
|
+
id: "suilend",
|
|
1362
|
+
name: "Suilend",
|
|
1363
|
+
packages: [SUILEND_PACKAGE],
|
|
1364
|
+
actionMap: {
|
|
1365
|
+
"lending_market::deposit_liquidity_and_mint_ctokens": "save",
|
|
1366
|
+
"lending_market::deposit_ctokens_into_obligation": "save",
|
|
1367
|
+
"lending_market::create_obligation": "save",
|
|
1368
|
+
"lending_market::withdraw_ctokens": "withdraw",
|
|
1369
|
+
"lending_market::redeem_ctokens_and_withdraw_liquidity": "withdraw",
|
|
1370
|
+
"lending_market::redeem_ctokens_and_withdraw_liquidity_request": "withdraw",
|
|
1371
|
+
"lending_market::fulfill_liquidity_request": "withdraw",
|
|
1372
|
+
"lending_market::unstake_sui_from_staker": "withdraw",
|
|
1373
|
+
"lending_market::borrow": "borrow",
|
|
1374
|
+
"lending_market::repay": "repay"
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
function interpolateRate(utilBreakpoints, aprBreakpoints, utilizationPct) {
|
|
1378
|
+
if (utilBreakpoints.length === 0) return 0;
|
|
1379
|
+
if (utilizationPct <= utilBreakpoints[0]) return aprBreakpoints[0];
|
|
1380
|
+
if (utilizationPct >= utilBreakpoints[utilBreakpoints.length - 1]) {
|
|
1381
|
+
return aprBreakpoints[aprBreakpoints.length - 1];
|
|
1382
|
+
}
|
|
1383
|
+
for (let i = 1; i < utilBreakpoints.length; i++) {
|
|
1384
|
+
if (utilizationPct <= utilBreakpoints[i]) {
|
|
1385
|
+
const t = (utilizationPct - utilBreakpoints[i - 1]) / (utilBreakpoints[i] - utilBreakpoints[i - 1]);
|
|
1386
|
+
return aprBreakpoints[i - 1] + t * (aprBreakpoints[i] - aprBreakpoints[i - 1]);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return aprBreakpoints[aprBreakpoints.length - 1];
|
|
1390
|
+
}
|
|
1391
|
+
function computeRates(reserve) {
|
|
1392
|
+
const available = reserve.availableAmount / 10 ** reserve.mintDecimals;
|
|
1393
|
+
const borrowed = reserve.borrowedAmountWad / WAD / 10 ** reserve.mintDecimals;
|
|
1394
|
+
const totalDeposited = available + borrowed;
|
|
1395
|
+
const utilizationPct = totalDeposited > 0 ? borrowed / totalDeposited * 100 : 0;
|
|
1396
|
+
if (reserve.interestRateUtils.length === 0) return { borrowAprPct: 0, depositAprPct: 0 };
|
|
1397
|
+
const aprs = reserve.interestRateAprs.map((a) => a / 100);
|
|
1398
|
+
const borrowAprPct = interpolateRate(reserve.interestRateUtils, aprs, utilizationPct);
|
|
1399
|
+
const depositAprPct = utilizationPct / 100 * (borrowAprPct / 100) * (1 - reserve.spreadFeeBps / 1e4) * 100;
|
|
1400
|
+
return { borrowAprPct, depositAprPct };
|
|
1401
|
+
}
|
|
1402
|
+
var MS_PER_YEAR = 365.25 * 24 * 3600 * 1e3;
|
|
1403
|
+
function computeDepositRewardApr(reserve, allReserves) {
|
|
1404
|
+
if (reserve.depositTotalShares <= 0 || reserve.price <= 0) return 0;
|
|
1405
|
+
const totalDepositValue = reserve.depositTotalShares / 10 ** reserve.mintDecimals * reserve.price;
|
|
1406
|
+
if (totalDepositValue <= 0) return 0;
|
|
1407
|
+
const priceMap = /* @__PURE__ */ new Map();
|
|
1408
|
+
for (const r of allReserves) {
|
|
1409
|
+
if (r.price > 0) priceMap.set(r.coinType, { price: r.price, decimals: r.mintDecimals });
|
|
1410
|
+
}
|
|
1411
|
+
let rewardApr = 0;
|
|
1412
|
+
for (const rw of reserve.depositPoolRewards) {
|
|
1413
|
+
const info = priceMap.get(rw.coinType);
|
|
1414
|
+
if (!info || info.price <= 0) continue;
|
|
1415
|
+
const durationMs = rw.endTimeMs - rw.startTimeMs;
|
|
1416
|
+
if (durationMs <= 0) continue;
|
|
1417
|
+
const annualTokens = rw.totalRewards / 10 ** info.decimals * (MS_PER_YEAR / durationMs);
|
|
1418
|
+
rewardApr += annualTokens * info.price / totalDepositValue * 100;
|
|
1419
|
+
}
|
|
1420
|
+
return rewardApr;
|
|
1421
|
+
}
|
|
1422
|
+
function cTokenRatio(reserve) {
|
|
1423
|
+
if (reserve.ctokenSupply === 0) return 1;
|
|
1424
|
+
const totalSupply = reserve.availableAmount + reserve.borrowedAmountWad / WAD - reserve.unclaimedSpreadFeesWad / WAD;
|
|
1425
|
+
return totalSupply / reserve.ctokenSupply;
|
|
1426
|
+
}
|
|
1427
|
+
function f(obj) {
|
|
1428
|
+
if (obj && typeof obj === "object" && "fields" in obj) return obj.fields;
|
|
1429
|
+
return obj;
|
|
1430
|
+
}
|
|
1431
|
+
function str(v) {
|
|
1432
|
+
return String(v ?? "0");
|
|
1433
|
+
}
|
|
1434
|
+
function num(v) {
|
|
1435
|
+
return Number(str(v));
|
|
1436
|
+
}
|
|
1437
|
+
function parseReserve(raw, index) {
|
|
1438
|
+
const r = f(raw);
|
|
1439
|
+
const coinTypeField = f(r.coin_type);
|
|
1440
|
+
const config = f(f(r.config)?.element);
|
|
1441
|
+
const dMgr = f(r.deposits_pool_reward_manager);
|
|
1442
|
+
const rawRewards = Array.isArray(dMgr?.pool_rewards) ? dMgr.pool_rewards : [];
|
|
1443
|
+
const now = Date.now();
|
|
1444
|
+
const depositPoolRewards = rawRewards.map((rw, idx) => {
|
|
1445
|
+
if (rw === null) return null;
|
|
1446
|
+
const rwf = f(rw);
|
|
1447
|
+
return {
|
|
1448
|
+
coinType: str(f(rwf.coin_type)?.name),
|
|
1449
|
+
totalRewards: num(rwf.total_rewards),
|
|
1450
|
+
startTimeMs: num(rwf.start_time_ms),
|
|
1451
|
+
endTimeMs: num(rwf.end_time_ms),
|
|
1452
|
+
rewardIndex: idx
|
|
1453
|
+
};
|
|
1454
|
+
}).filter((rw) => rw !== null && rw.endTimeMs > now && rw.totalRewards > 0);
|
|
1455
|
+
return {
|
|
1456
|
+
coinType: str(coinTypeField?.name),
|
|
1457
|
+
mintDecimals: num(r.mint_decimals),
|
|
1458
|
+
availableAmount: num(r.available_amount),
|
|
1459
|
+
borrowedAmountWad: num(f(r.borrowed_amount)?.value),
|
|
1460
|
+
ctokenSupply: num(r.ctoken_supply),
|
|
1461
|
+
unclaimedSpreadFeesWad: num(f(r.unclaimed_spread_fees)?.value),
|
|
1462
|
+
cumulativeBorrowRateWad: num(f(r.cumulative_borrow_rate)?.value),
|
|
1463
|
+
openLtvPct: num(config?.open_ltv_pct),
|
|
1464
|
+
closeLtvPct: num(config?.close_ltv_pct),
|
|
1465
|
+
spreadFeeBps: num(config?.spread_fee_bps),
|
|
1466
|
+
interestRateUtils: Array.isArray(config?.interest_rate_utils) ? config.interest_rate_utils.map(num) : [],
|
|
1467
|
+
interestRateAprs: Array.isArray(config?.interest_rate_aprs) ? config.interest_rate_aprs.map(num) : [],
|
|
1468
|
+
arrayIndex: index,
|
|
1469
|
+
price: num(f(r.price)?.value) / WAD,
|
|
1470
|
+
depositTotalShares: num(dMgr?.total_shares),
|
|
1471
|
+
depositPoolRewards
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
function parseObligation(raw) {
|
|
1475
|
+
const deposits = Array.isArray(raw.deposits) ? raw.deposits.map((d) => {
|
|
1476
|
+
const df = f(d);
|
|
1477
|
+
return {
|
|
1478
|
+
coinType: str(f(df.coin_type)?.name),
|
|
1479
|
+
ctokenAmount: num(df.deposited_ctoken_amount),
|
|
1480
|
+
reserveIdx: num(df.reserve_array_index)
|
|
1481
|
+
};
|
|
1482
|
+
}) : [];
|
|
1483
|
+
const borrows = Array.isArray(raw.borrows) ? raw.borrows.map((b) => {
|
|
1484
|
+
const bf = f(b);
|
|
1485
|
+
return {
|
|
1486
|
+
coinType: str(f(bf.coin_type)?.name),
|
|
1487
|
+
borrowedWad: num(f(bf.borrowed_amount)?.value),
|
|
1488
|
+
cumBorrowRateWad: num(f(bf.cumulative_borrow_rate)?.value),
|
|
1489
|
+
reserveIdx: num(bf.reserve_array_index)
|
|
1490
|
+
};
|
|
1491
|
+
}) : [];
|
|
1492
|
+
return { deposits, borrows };
|
|
1493
|
+
}
|
|
1494
|
+
var SuilendAdapter = class {
|
|
1495
|
+
id = "suilend";
|
|
1496
|
+
name = "Suilend";
|
|
1497
|
+
version = "2.0.0";
|
|
1498
|
+
capabilities = ["save", "withdraw", "borrow", "repay"];
|
|
1499
|
+
supportedAssets = [...STABLE_ASSETS, "SUI", "ETH", "BTC", "GOLD"];
|
|
1500
|
+
supportsSameAssetBorrow = false;
|
|
1501
|
+
client;
|
|
1502
|
+
publishedAt = null;
|
|
1503
|
+
reserveCache = null;
|
|
1504
|
+
async init(client) {
|
|
1505
|
+
this.client = client;
|
|
1506
|
+
}
|
|
1507
|
+
initSync(client) {
|
|
1508
|
+
this.client = client;
|
|
1509
|
+
}
|
|
1510
|
+
// -- On-chain reads -------------------------------------------------------
|
|
1511
|
+
async resolvePackage() {
|
|
1512
|
+
if (this.publishedAt) return this.publishedAt;
|
|
1513
|
+
try {
|
|
1514
|
+
const cap = await this.client.getObject({ id: UPGRADE_CAP_ID, options: { showContent: true } });
|
|
1515
|
+
if (cap.data?.content?.dataType === "moveObject") {
|
|
1516
|
+
const fields = cap.data.content.fields;
|
|
1517
|
+
this.publishedAt = str(fields.package);
|
|
1518
|
+
return this.publishedAt;
|
|
1519
|
+
}
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1522
|
+
this.publishedAt = FALLBACK_PUBLISHED_AT;
|
|
1523
|
+
return this.publishedAt;
|
|
1524
|
+
}
|
|
1525
|
+
async loadReserves(fresh = false) {
|
|
1526
|
+
if (this.reserveCache && !fresh) return this.reserveCache;
|
|
1527
|
+
const market = await this.client.getObject({
|
|
1528
|
+
id: LENDING_MARKET_ID,
|
|
1529
|
+
options: { showContent: true }
|
|
1530
|
+
});
|
|
1531
|
+
if (market.data?.content?.dataType !== "moveObject") {
|
|
1532
|
+
throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend lending market");
|
|
1533
|
+
}
|
|
1534
|
+
const fields = market.data.content.fields;
|
|
1535
|
+
const reservesRaw = fields.reserves;
|
|
1536
|
+
if (!Array.isArray(reservesRaw)) {
|
|
1537
|
+
throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to parse Suilend reserves");
|
|
1538
|
+
}
|
|
1539
|
+
this.reserveCache = reservesRaw.map((r, i) => parseReserve(r, i));
|
|
1540
|
+
return this.reserveCache;
|
|
1541
|
+
}
|
|
1542
|
+
findReserve(reserves, asset) {
|
|
1543
|
+
let coinType;
|
|
1544
|
+
if (asset in SUPPORTED_ASSETS) {
|
|
1545
|
+
coinType = SUPPORTED_ASSETS[asset].type;
|
|
1546
|
+
} else if (asset.includes("::")) {
|
|
1547
|
+
coinType = asset;
|
|
1548
|
+
} else {
|
|
1549
|
+
return void 0;
|
|
1550
|
+
}
|
|
1551
|
+
try {
|
|
1552
|
+
const normalized = normalizeStructTag(coinType);
|
|
1553
|
+
return reserves.find((r) => {
|
|
1554
|
+
try {
|
|
1555
|
+
return normalizeStructTag(r.coinType) === normalized;
|
|
1556
|
+
} catch {
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
} catch {
|
|
1561
|
+
return void 0;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
async fetchObligationCaps(address) {
|
|
1565
|
+
const capType = `${SUILEND_PACKAGE}::lending_market::ObligationOwnerCap<${LENDING_MARKET_TYPE}>`;
|
|
1566
|
+
const caps = [];
|
|
1567
|
+
let cursor;
|
|
1568
|
+
let hasNext = true;
|
|
1569
|
+
while (hasNext) {
|
|
1570
|
+
const page = await this.client.getOwnedObjects({
|
|
1571
|
+
owner: address,
|
|
1572
|
+
filter: { StructType: capType },
|
|
1573
|
+
options: { showContent: true },
|
|
1574
|
+
cursor: cursor ?? void 0
|
|
1575
|
+
});
|
|
1576
|
+
for (const item of page.data) {
|
|
1577
|
+
if (item.data?.content?.dataType !== "moveObject") continue;
|
|
1578
|
+
const fields = item.data.content.fields;
|
|
1579
|
+
caps.push({
|
|
1580
|
+
id: item.data.objectId,
|
|
1581
|
+
obligationId: str(fields.obligation_id)
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
cursor = page.nextCursor;
|
|
1585
|
+
hasNext = page.hasNextPage;
|
|
1586
|
+
}
|
|
1587
|
+
return caps;
|
|
1588
|
+
}
|
|
1589
|
+
async fetchObligation(obligationId) {
|
|
1590
|
+
const obj = await this.client.getObject({ id: obligationId, options: { showContent: true } });
|
|
1591
|
+
if (obj.data?.content?.dataType !== "moveObject") {
|
|
1592
|
+
throw new T2000Error("PROTOCOL_UNAVAILABLE", "Failed to read Suilend obligation");
|
|
1593
|
+
}
|
|
1594
|
+
return parseObligation(obj.data.content.fields);
|
|
1595
|
+
}
|
|
1596
|
+
resolveSymbol(coinType) {
|
|
1597
|
+
try {
|
|
1598
|
+
const normalized = normalizeStructTag(coinType);
|
|
1599
|
+
for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
1600
|
+
try {
|
|
1601
|
+
if (normalizeStructTag(info.type) === normalized) return key;
|
|
1602
|
+
} catch {
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
} catch {
|
|
1606
|
+
}
|
|
1607
|
+
const parts = coinType.split("::");
|
|
1608
|
+
return parts[parts.length - 1] || "UNKNOWN";
|
|
1609
|
+
}
|
|
1610
|
+
// -- Adapter interface ----------------------------------------------------
|
|
1611
|
+
async getRates(asset) {
|
|
1612
|
+
const reserves = await this.loadReserves();
|
|
1613
|
+
const reserve = this.findReserve(reserves, asset);
|
|
1614
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
|
|
1615
|
+
const { borrowAprPct, depositAprPct } = computeRates(reserve);
|
|
1616
|
+
const rewardApr = computeDepositRewardApr(reserve, reserves);
|
|
1617
|
+
return { asset, saveApy: depositAprPct + rewardApr, borrowApy: borrowAprPct };
|
|
1618
|
+
}
|
|
1619
|
+
async getPositions(address) {
|
|
1620
|
+
const supplies = [];
|
|
1621
|
+
const borrows = [];
|
|
1622
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1623
|
+
if (caps.length === 0) return { supplies, borrows };
|
|
1624
|
+
const [reserves, obligation] = await Promise.all([
|
|
1625
|
+
this.loadReserves(),
|
|
1626
|
+
this.fetchObligation(caps[0].obligationId)
|
|
1627
|
+
]);
|
|
1628
|
+
for (const dep of obligation.deposits) {
|
|
1629
|
+
const reserve = reserves[dep.reserveIdx];
|
|
1630
|
+
if (!reserve) continue;
|
|
1631
|
+
const ratio = cTokenRatio(reserve);
|
|
1632
|
+
const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
|
|
1633
|
+
const { depositAprPct } = computeRates(reserve);
|
|
1634
|
+
const rewardApr = computeDepositRewardApr(reserve, reserves);
|
|
1635
|
+
supplies.push({ asset: this.resolveSymbol(dep.coinType), amount, apy: depositAprPct + rewardApr });
|
|
1636
|
+
}
|
|
1637
|
+
for (const bor of obligation.borrows) {
|
|
1638
|
+
const reserve = reserves[bor.reserveIdx];
|
|
1639
|
+
if (!reserve) continue;
|
|
1640
|
+
const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
|
|
1641
|
+
const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
|
|
1642
|
+
const posRate = bor.cumBorrowRateWad / WAD;
|
|
1643
|
+
const compounded = posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
|
|
1644
|
+
const { borrowAprPct } = computeRates(reserve);
|
|
1645
|
+
borrows.push({ asset: this.resolveSymbol(bor.coinType), amount: compounded, apy: borrowAprPct });
|
|
1646
|
+
}
|
|
1647
|
+
return { supplies, borrows };
|
|
1648
|
+
}
|
|
1649
|
+
async getHealth(address) {
|
|
1650
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1651
|
+
if (caps.length === 0) {
|
|
1652
|
+
return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
|
|
1653
|
+
}
|
|
1654
|
+
const [reserves, obligation] = await Promise.all([
|
|
1655
|
+
this.loadReserves(),
|
|
1656
|
+
this.fetchObligation(caps[0].obligationId)
|
|
1657
|
+
]);
|
|
1658
|
+
let supplied = 0;
|
|
1659
|
+
let borrowed = 0;
|
|
1660
|
+
let weightedCloseLtv = 0;
|
|
1661
|
+
let weightedOpenLtv = 0;
|
|
1662
|
+
for (const dep of obligation.deposits) {
|
|
1663
|
+
const reserve = reserves[dep.reserveIdx];
|
|
1664
|
+
if (!reserve) continue;
|
|
1665
|
+
const ratio = cTokenRatio(reserve);
|
|
1666
|
+
const amount = dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals;
|
|
1667
|
+
supplied += amount;
|
|
1668
|
+
weightedCloseLtv += amount * (reserve.closeLtvPct / 100);
|
|
1669
|
+
weightedOpenLtv += amount * (reserve.openLtvPct / 100);
|
|
1670
|
+
}
|
|
1671
|
+
for (const bor of obligation.borrows) {
|
|
1672
|
+
const reserve = reserves[bor.reserveIdx];
|
|
1673
|
+
if (!reserve) continue;
|
|
1674
|
+
const rawAmount = bor.borrowedWad / WAD / 10 ** reserve.mintDecimals;
|
|
1675
|
+
const reserveRate = reserve.cumulativeBorrowRateWad / WAD;
|
|
1676
|
+
const posRate = bor.cumBorrowRateWad / WAD;
|
|
1677
|
+
borrowed += posRate > 0 ? rawAmount * (reserveRate / posRate) : rawAmount;
|
|
1678
|
+
}
|
|
1679
|
+
const liqThreshold = supplied > 0 ? weightedCloseLtv / supplied : 0.75;
|
|
1680
|
+
const openLtv = supplied > 0 ? weightedOpenLtv / supplied : 0.7;
|
|
1681
|
+
const healthFactor = borrowed > 0 ? supplied * liqThreshold / borrowed : Infinity;
|
|
1682
|
+
const maxBorrow = Math.max(0, supplied * openLtv - borrowed);
|
|
1683
|
+
return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
|
|
1684
|
+
}
|
|
1685
|
+
async buildSaveTx(address, amount, asset, options) {
|
|
1686
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1687
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1688
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1689
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1690
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
|
|
1691
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1692
|
+
const tx = new Transaction();
|
|
1693
|
+
tx.setSender(address);
|
|
1694
|
+
let capRef;
|
|
1695
|
+
if (caps.length === 0) {
|
|
1696
|
+
const [newCap] = tx.moveCall({
|
|
1697
|
+
target: `${pkg}::lending_market::create_obligation`,
|
|
1698
|
+
typeArguments: [LENDING_MARKET_TYPE],
|
|
1699
|
+
arguments: [tx.object(LENDING_MARKET_ID)]
|
|
1700
|
+
});
|
|
1701
|
+
capRef = newCap;
|
|
1702
|
+
} else {
|
|
1703
|
+
capRef = caps[0].id;
|
|
1704
|
+
}
|
|
1705
|
+
const allCoins = await this.fetchAllCoins(address, assetInfo.type);
|
|
1706
|
+
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
|
|
1707
|
+
const primaryCoinId = allCoins[0].coinObjectId;
|
|
1708
|
+
if (allCoins.length > 1) {
|
|
1709
|
+
tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
1710
|
+
}
|
|
1711
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals).toString();
|
|
1712
|
+
const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount]);
|
|
1713
|
+
if (options?.collectFee) {
|
|
1714
|
+
addCollectFeeToTx(tx, depositCoin, "save");
|
|
1715
|
+
}
|
|
1716
|
+
const [ctokens] = tx.moveCall({
|
|
1717
|
+
target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
|
|
1718
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1719
|
+
arguments: [
|
|
1720
|
+
tx.object(LENDING_MARKET_ID),
|
|
1721
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1722
|
+
tx.object(CLOCK2),
|
|
1723
|
+
depositCoin
|
|
1724
|
+
]
|
|
1725
|
+
});
|
|
1726
|
+
tx.moveCall({
|
|
1727
|
+
target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
|
|
1728
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1729
|
+
arguments: [
|
|
1730
|
+
tx.object(LENDING_MARKET_ID),
|
|
1731
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1732
|
+
typeof capRef === "string" ? tx.object(capRef) : capRef,
|
|
1733
|
+
tx.object(CLOCK2),
|
|
1734
|
+
ctokens
|
|
1735
|
+
]
|
|
1736
|
+
});
|
|
1737
|
+
if (typeof capRef !== "string") {
|
|
1738
|
+
tx.transferObjects([capRef], address);
|
|
1739
|
+
}
|
|
1740
|
+
return { tx };
|
|
1741
|
+
}
|
|
1742
|
+
async buildWithdrawTx(address, amount, asset) {
|
|
1743
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1744
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1745
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
|
|
1746
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1747
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1748
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1749
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
|
|
1750
|
+
const obligation = await this.fetchObligation(caps[0].obligationId);
|
|
1751
|
+
const dep = obligation.deposits.find((d) => d.reserveIdx === reserve.arrayIndex);
|
|
1752
|
+
const ratio = cTokenRatio(reserve);
|
|
1753
|
+
const deposited = dep ? dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals : 0;
|
|
1754
|
+
const effectiveAmount = Math.min(amount, deposited);
|
|
1755
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
|
|
1756
|
+
const U64_MAX = "18446744073709551615";
|
|
1757
|
+
const isFullWithdraw = dep && effectiveAmount >= deposited * 0.999;
|
|
1758
|
+
const withdrawArg = isFullWithdraw ? U64_MAX : String(Math.floor(effectiveAmount * 10 ** reserve.mintDecimals / ratio));
|
|
1759
|
+
const tx = new Transaction();
|
|
1760
|
+
tx.setSender(address);
|
|
1761
|
+
const [ctokens] = tx.moveCall({
|
|
1762
|
+
target: `${pkg}::lending_market::withdraw_ctokens`,
|
|
1763
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1764
|
+
arguments: [
|
|
1765
|
+
tx.object(LENDING_MARKET_ID),
|
|
1766
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1767
|
+
tx.object(caps[0].id),
|
|
1768
|
+
tx.object(CLOCK2),
|
|
1769
|
+
tx.pure("u64", BigInt(withdrawArg))
|
|
1770
|
+
]
|
|
1771
|
+
});
|
|
1772
|
+
const coin = this.redeemCtokens(tx, pkg, reserve, assetInfo.type, assetKey, ctokens);
|
|
1773
|
+
tx.transferObjects([coin], address);
|
|
1774
|
+
return { tx, effectiveAmount };
|
|
1775
|
+
}
|
|
1776
|
+
async addWithdrawToTx(tx, address, amount, asset) {
|
|
1777
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1778
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1779
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves(true)]);
|
|
1780
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1781
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1782
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1783
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
|
|
1784
|
+
const obligation = await this.fetchObligation(caps[0].obligationId);
|
|
1785
|
+
const dep = obligation.deposits.find((d) => d.reserveIdx === reserve.arrayIndex);
|
|
1786
|
+
const ratio = cTokenRatio(reserve);
|
|
1787
|
+
const deposited = dep ? dep.ctokenAmount * ratio / 10 ** reserve.mintDecimals : 0;
|
|
1788
|
+
const effectiveAmount = Math.min(amount, deposited);
|
|
1789
|
+
if (effectiveAmount <= 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend`);
|
|
1790
|
+
const ctokenAmount = dep && effectiveAmount >= deposited * 0.999 ? dep.ctokenAmount : Math.floor(effectiveAmount * 10 ** reserve.mintDecimals / ratio);
|
|
1791
|
+
const [ctokens] = tx.moveCall({
|
|
1792
|
+
target: `${pkg}::lending_market::withdraw_ctokens`,
|
|
1793
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1794
|
+
arguments: [
|
|
1795
|
+
tx.object(LENDING_MARKET_ID),
|
|
1796
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1797
|
+
tx.object(caps[0].id),
|
|
1798
|
+
tx.object(CLOCK2),
|
|
1799
|
+
tx.pure.u64(ctokenAmount)
|
|
1800
|
+
]
|
|
1801
|
+
});
|
|
1802
|
+
const coin = this.redeemCtokens(tx, pkg, reserve, assetInfo.type, assetKey, ctokens);
|
|
1803
|
+
return { coin, effectiveAmount };
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* 3-step cToken redemption matching the official Suilend SDK flow:
|
|
1807
|
+
* 1. redeem_ctokens_and_withdraw_liquidity_request — creates a LiquidityRequest
|
|
1808
|
+
* 2. unstake_sui_from_staker — (SUI only) unstakes from validators to replenish available_liquidity
|
|
1809
|
+
* 3. fulfill_liquidity_request — splits underlying tokens from the reserve
|
|
1810
|
+
*/
|
|
1811
|
+
redeemCtokens(tx, pkg, reserve, coinType, assetKey, ctokens) {
|
|
1812
|
+
const exemptionType = `${SUILEND_PACKAGE}::lending_market::RateLimiterExemption<${LENDING_MARKET_TYPE}, ${coinType}>`;
|
|
1813
|
+
const [none] = tx.moveCall({
|
|
1814
|
+
target: "0x1::option::none",
|
|
1815
|
+
typeArguments: [exemptionType]
|
|
1816
|
+
});
|
|
1817
|
+
const [liquidityRequest] = tx.moveCall({
|
|
1818
|
+
target: `${pkg}::lending_market::redeem_ctokens_and_withdraw_liquidity_request`,
|
|
1819
|
+
typeArguments: [LENDING_MARKET_TYPE, coinType],
|
|
1820
|
+
arguments: [
|
|
1821
|
+
tx.object(LENDING_MARKET_ID),
|
|
1822
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1823
|
+
tx.object(CLOCK2),
|
|
1824
|
+
ctokens,
|
|
1825
|
+
none
|
|
1826
|
+
]
|
|
1827
|
+
});
|
|
1828
|
+
if (assetKey === "SUI") {
|
|
1829
|
+
tx.moveCall({
|
|
1830
|
+
target: `${pkg}::lending_market::unstake_sui_from_staker`,
|
|
1831
|
+
typeArguments: [LENDING_MARKET_TYPE],
|
|
1832
|
+
arguments: [
|
|
1833
|
+
tx.object(LENDING_MARKET_ID),
|
|
1834
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1835
|
+
liquidityRequest,
|
|
1836
|
+
tx.object(SUI_SYSTEM_STATE2)
|
|
1837
|
+
]
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
const [coin] = tx.moveCall({
|
|
1841
|
+
target: `${pkg}::lending_market::fulfill_liquidity_request`,
|
|
1842
|
+
typeArguments: [LENDING_MARKET_TYPE, coinType],
|
|
1843
|
+
arguments: [
|
|
1844
|
+
tx.object(LENDING_MARKET_ID),
|
|
1845
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1846
|
+
liquidityRequest
|
|
1847
|
+
]
|
|
1848
|
+
});
|
|
1849
|
+
return coin;
|
|
1850
|
+
}
|
|
1851
|
+
async addSaveToTx(tx, address, coin, asset, options) {
|
|
1852
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1853
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1854
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1855
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1856
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1857
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1858
|
+
let capRef;
|
|
1859
|
+
if (caps.length === 0) {
|
|
1860
|
+
const [newCap] = tx.moveCall({
|
|
1861
|
+
target: `${pkg}::lending_market::create_obligation`,
|
|
1862
|
+
typeArguments: [LENDING_MARKET_TYPE],
|
|
1863
|
+
arguments: [tx.object(LENDING_MARKET_ID)]
|
|
1864
|
+
});
|
|
1865
|
+
capRef = newCap;
|
|
1866
|
+
} else {
|
|
1867
|
+
capRef = caps[0].id;
|
|
1868
|
+
}
|
|
1869
|
+
if (options?.collectFee) {
|
|
1870
|
+
addCollectFeeToTx(tx, coin, "save");
|
|
1871
|
+
}
|
|
1872
|
+
const [ctokens] = tx.moveCall({
|
|
1873
|
+
target: `${pkg}::lending_market::deposit_liquidity_and_mint_ctokens`,
|
|
1874
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1875
|
+
arguments: [
|
|
1876
|
+
tx.object(LENDING_MARKET_ID),
|
|
1877
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1878
|
+
tx.object(CLOCK2),
|
|
1879
|
+
coin
|
|
1880
|
+
]
|
|
1881
|
+
});
|
|
1882
|
+
tx.moveCall({
|
|
1883
|
+
target: `${pkg}::lending_market::deposit_ctokens_into_obligation`,
|
|
1884
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1885
|
+
arguments: [
|
|
1886
|
+
tx.object(LENDING_MARKET_ID),
|
|
1887
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1888
|
+
typeof capRef === "string" ? tx.object(capRef) : capRef,
|
|
1889
|
+
tx.object(CLOCK2),
|
|
1890
|
+
ctokens
|
|
1891
|
+
]
|
|
1892
|
+
});
|
|
1893
|
+
if (typeof capRef !== "string") {
|
|
1894
|
+
tx.transferObjects([capRef], address);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
async buildBorrowTx(address, amount, asset, options) {
|
|
1898
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1899
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1900
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1901
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1902
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend. Try: NAVI or a different asset.`);
|
|
1903
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1904
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found. Deposit collateral first with: t2000 save <amount>");
|
|
1905
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals);
|
|
1906
|
+
const tx = new Transaction();
|
|
1907
|
+
tx.setSender(address);
|
|
1908
|
+
const [coin] = tx.moveCall({
|
|
1909
|
+
target: `${pkg}::lending_market::borrow`,
|
|
1910
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1911
|
+
arguments: [
|
|
1912
|
+
tx.object(LENDING_MARKET_ID),
|
|
1913
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1914
|
+
tx.object(caps[0].id),
|
|
1915
|
+
tx.object(CLOCK2),
|
|
1916
|
+
tx.pure.u64(rawAmount)
|
|
1917
|
+
]
|
|
1918
|
+
});
|
|
1919
|
+
if (options?.collectFee) {
|
|
1920
|
+
addCollectFeeToTx(tx, coin, "borrow");
|
|
1921
|
+
}
|
|
1922
|
+
tx.transferObjects([coin], address);
|
|
1923
|
+
return { tx };
|
|
1924
|
+
}
|
|
1925
|
+
async buildRepayTx(address, amount, asset) {
|
|
1926
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1927
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1928
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1929
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1930
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1931
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1932
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
|
|
1933
|
+
const allCoins = await this.fetchAllCoins(address, assetInfo.type);
|
|
1934
|
+
if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with`);
|
|
1935
|
+
const rawAmount = stableToRaw(amount, assetInfo.decimals);
|
|
1936
|
+
const tx = new Transaction();
|
|
1937
|
+
tx.setSender(address);
|
|
1938
|
+
const primaryCoinId = allCoins[0].coinObjectId;
|
|
1939
|
+
if (allCoins.length > 1) {
|
|
1940
|
+
tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
1941
|
+
}
|
|
1942
|
+
const [repayCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawAmount.toString()]);
|
|
1943
|
+
tx.moveCall({
|
|
1944
|
+
target: `${pkg}::lending_market::repay`,
|
|
1945
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1946
|
+
arguments: [
|
|
1947
|
+
tx.object(LENDING_MARKET_ID),
|
|
1948
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1949
|
+
tx.object(caps[0].id),
|
|
1950
|
+
tx.object(CLOCK2),
|
|
1951
|
+
repayCoin
|
|
1952
|
+
]
|
|
1953
|
+
});
|
|
1954
|
+
return { tx };
|
|
1955
|
+
}
|
|
1956
|
+
async addRepayToTx(tx, address, coin, asset) {
|
|
1957
|
+
const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
|
|
1958
|
+
const assetInfo = SUPPORTED_ASSETS[assetKey];
|
|
1959
|
+
const [pkg, reserves] = await Promise.all([this.resolvePackage(), this.loadReserves()]);
|
|
1960
|
+
const reserve = this.findReserve(reserves, assetKey);
|
|
1961
|
+
if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `${assetInfo.displayName} reserve not found on Suilend`);
|
|
1962
|
+
const caps = await this.fetchObligationCaps(address);
|
|
1963
|
+
if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
|
|
1964
|
+
tx.moveCall({
|
|
1965
|
+
target: `${pkg}::lending_market::repay`,
|
|
1966
|
+
typeArguments: [LENDING_MARKET_TYPE, assetInfo.type],
|
|
1967
|
+
arguments: [
|
|
1968
|
+
tx.object(LENDING_MARKET_ID),
|
|
1969
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
1970
|
+
tx.object(caps[0].id),
|
|
1971
|
+
tx.object(CLOCK2),
|
|
1972
|
+
coin
|
|
1973
|
+
]
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
async maxWithdraw(address, _asset) {
|
|
1977
|
+
const health = await this.getHealth(address);
|
|
1978
|
+
let maxAmount;
|
|
1979
|
+
if (health.borrowed === 0) {
|
|
1980
|
+
maxAmount = health.supplied;
|
|
1981
|
+
} else {
|
|
1982
|
+
maxAmount = Math.max(0, health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold);
|
|
1983
|
+
}
|
|
1984
|
+
const remainingSupply = health.supplied - maxAmount;
|
|
1985
|
+
const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
|
|
1986
|
+
return { maxAmount, healthFactorAfter: hfAfter, currentHF: health.healthFactor };
|
|
1987
|
+
}
|
|
1988
|
+
async maxBorrow(address, _asset) {
|
|
1989
|
+
const health = await this.getHealth(address);
|
|
1990
|
+
const maxAmount = health.maxBorrow;
|
|
1991
|
+
return { maxAmount, healthFactorAfter: MIN_HEALTH_FACTOR2, currentHF: health.healthFactor };
|
|
1992
|
+
}
|
|
1993
|
+
async fetchAllCoins(owner, coinType) {
|
|
1994
|
+
const all = [];
|
|
1995
|
+
let cursor = null;
|
|
1996
|
+
let hasNext = true;
|
|
1997
|
+
while (hasNext) {
|
|
1998
|
+
const page = await this.client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
|
|
1999
|
+
all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
|
|
2000
|
+
cursor = page.nextCursor;
|
|
2001
|
+
hasNext = page.hasNextPage;
|
|
2002
|
+
}
|
|
2003
|
+
return all;
|
|
2004
|
+
}
|
|
2005
|
+
// -- Claim Rewards --------------------------------------------------------
|
|
2006
|
+
isClaimableReward(coinType) {
|
|
2007
|
+
const ct = coinType.toLowerCase();
|
|
2008
|
+
return ct.includes("spring_sui") || ct.includes("deep::deep") || ct.includes("cert::cert");
|
|
2009
|
+
}
|
|
2010
|
+
async getPendingRewards(address) {
|
|
2011
|
+
const caps = await this.fetchObligationCaps(address);
|
|
2012
|
+
if (caps.length === 0) return [];
|
|
2013
|
+
const [reserves, obligation] = await Promise.all([
|
|
2014
|
+
this.loadReserves(true),
|
|
2015
|
+
this.fetchObligation(caps[0].obligationId)
|
|
2016
|
+
]);
|
|
2017
|
+
const rewards = [];
|
|
2018
|
+
for (const dep of obligation.deposits) {
|
|
2019
|
+
const reserve = reserves[dep.reserveIdx];
|
|
2020
|
+
if (!reserve) continue;
|
|
2021
|
+
for (const rw of reserve.depositPoolRewards) {
|
|
2022
|
+
if (!this.isClaimableReward(rw.coinType)) continue;
|
|
2023
|
+
const durationMs = rw.endTimeMs - rw.startTimeMs;
|
|
2024
|
+
if (durationMs <= 0) continue;
|
|
2025
|
+
const assetSymbol = this.resolveSymbol(dep.coinType);
|
|
2026
|
+
if (!(assetSymbol in SUPPORTED_ASSETS)) continue;
|
|
2027
|
+
rewards.push({
|
|
2028
|
+
protocol: "suilend",
|
|
2029
|
+
asset: assetSymbol,
|
|
2030
|
+
coinType: rw.coinType,
|
|
2031
|
+
symbol: rw.coinType.includes("spring_sui") ? "sSUI" : rw.coinType.includes("deep::") ? "DEEP" : rw.coinType.split("::").pop() ?? "UNKNOWN",
|
|
2032
|
+
amount: 0,
|
|
2033
|
+
estimatedValueUsd: 0
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return rewards;
|
|
2038
|
+
}
|
|
2039
|
+
async addClaimRewardsToTx(tx, address) {
|
|
2040
|
+
const caps = await this.fetchObligationCaps(address);
|
|
2041
|
+
if (caps.length === 0) return [];
|
|
2042
|
+
const [pkg, reserves, obligation] = await Promise.all([
|
|
2043
|
+
this.resolvePackage(),
|
|
2044
|
+
this.loadReserves(true),
|
|
2045
|
+
this.fetchObligation(caps[0].obligationId)
|
|
2046
|
+
]);
|
|
2047
|
+
const claimsByToken = /* @__PURE__ */ new Map();
|
|
2048
|
+
const claimed = [];
|
|
2049
|
+
for (const dep of obligation.deposits) {
|
|
2050
|
+
const reserve = reserves[dep.reserveIdx];
|
|
2051
|
+
if (!reserve) continue;
|
|
2052
|
+
for (const rw of reserve.depositPoolRewards) {
|
|
2053
|
+
if (!this.isClaimableReward(rw.coinType)) continue;
|
|
2054
|
+
const [coin] = tx.moveCall({
|
|
2055
|
+
target: `${pkg}::lending_market::claim_rewards`,
|
|
2056
|
+
typeArguments: [LENDING_MARKET_TYPE, rw.coinType],
|
|
2057
|
+
arguments: [
|
|
2058
|
+
tx.object(LENDING_MARKET_ID),
|
|
2059
|
+
tx.object(caps[0].id),
|
|
2060
|
+
tx.object(CLOCK2),
|
|
2061
|
+
tx.pure.u64(reserve.arrayIndex),
|
|
2062
|
+
tx.pure.u64(rw.rewardIndex),
|
|
2063
|
+
tx.pure.bool(true)
|
|
2064
|
+
]
|
|
2065
|
+
});
|
|
2066
|
+
const existing = claimsByToken.get(rw.coinType) ?? [];
|
|
2067
|
+
existing.push(coin);
|
|
2068
|
+
claimsByToken.set(rw.coinType, existing);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
for (const [coinType, coins] of claimsByToken) {
|
|
2072
|
+
if (coins.length > 1) {
|
|
2073
|
+
tx.mergeCoins(coins[0], coins.slice(1));
|
|
2074
|
+
}
|
|
2075
|
+
tx.transferObjects([coins[0]], address);
|
|
2076
|
+
const symbol = coinType.includes("spring_sui") ? "SPRING_SUI" : coinType.includes("deep::") ? "DEEP" : coinType.split("::").pop() ?? "UNKNOWN";
|
|
2077
|
+
claimed.push({
|
|
2078
|
+
protocol: "suilend",
|
|
2079
|
+
asset: "",
|
|
2080
|
+
coinType,
|
|
2081
|
+
symbol,
|
|
2082
|
+
amount: 0,
|
|
2083
|
+
estimatedValueUsd: 0
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
return claimed;
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
var descriptor4 = {
|
|
2090
|
+
id: "sentinel",
|
|
2091
|
+
name: "Sui Sentinel",
|
|
2092
|
+
packages: [SENTINEL.PACKAGE],
|
|
2093
|
+
actionMap: {
|
|
2094
|
+
"sentinel::request_attack": "sentinel_attack",
|
|
2095
|
+
"sentinel::consume_prompt": "sentinel_settle"
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
|
|
2099
|
+
// src/adapters/index.ts
|
|
2100
|
+
var allDescriptors = [
|
|
2101
|
+
descriptor,
|
|
2102
|
+
descriptor3,
|
|
2103
|
+
descriptor2,
|
|
2104
|
+
descriptor4
|
|
16
2105
|
];
|
|
2106
|
+
|
|
2107
|
+
export { CetusAdapter, NaviAdapter, ProtocolRegistry, SuilendAdapter, allDescriptors, descriptor2 as cetusDescriptor, descriptor as naviDescriptor, descriptor4 as sentinelDescriptor, descriptor3 as suilendDescriptor };
|
|
2108
|
+
//# sourceMappingURL=index.js.map
|
|
17
2109
|
//# sourceMappingURL=index.js.map
|