@spield/sdk 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +94 -0
- package/RELEASE.md +8 -0
- package/USAGE.md +215 -0
- package/dist/index.cjs +1520 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +783 -0
- package/dist/index.d.ts +783 -0
- package/dist/index.js +1474 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1520 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BlendStrategyClient: () => BlendStrategyClient,
|
|
24
|
+
DEFAULT_ROUTER_SLIPPAGE_BPS: () => DEFAULT_ROUTER_SLIPPAGE_BPS,
|
|
25
|
+
FreighterSigner: () => FreighterSigner,
|
|
26
|
+
ManualSigner: () => ManualSigner,
|
|
27
|
+
SCALAR_12: () => SCALAR_12,
|
|
28
|
+
STELLAR_NETWORK_PASSPHRASES: () => STELLAR_NETWORK_PASSPHRASES,
|
|
29
|
+
SpieldClient: () => SpieldClient,
|
|
30
|
+
SpieldConfigurationError: () => SpieldConfigurationError,
|
|
31
|
+
SpieldContractError: () => SpieldContractError,
|
|
32
|
+
SpieldRpcError: () => SpieldRpcError,
|
|
33
|
+
SpieldSdkError: () => SpieldSdkError,
|
|
34
|
+
SpieldSignerError: () => SpieldSignerError,
|
|
35
|
+
SpieldValidationError: () => SpieldValidationError,
|
|
36
|
+
SrMarketClient: () => SrMarketClient,
|
|
37
|
+
SrPortfolioClient: () => SrPortfolioClient,
|
|
38
|
+
SrProtocolClient: () => SrProtocolClient,
|
|
39
|
+
SrRouterClient: () => SrRouterClient,
|
|
40
|
+
SrTokensClient: () => SrTokensClient,
|
|
41
|
+
SrVaultClient: () => SrVaultClient,
|
|
42
|
+
StandardizedReturnClient: () => StandardizedReturnClient,
|
|
43
|
+
YT_MAX_IN_PAD_BPS: () => YT_MAX_IN_PAD_BPS,
|
|
44
|
+
YieldEngineClient: () => YieldEngineClient,
|
|
45
|
+
assertNonNegativeAmount: () => assertNonNegativeAmount,
|
|
46
|
+
customNetwork: () => customNetwork,
|
|
47
|
+
fromBaseUnits: () => fromBaseUnits,
|
|
48
|
+
fromScalar12: () => fromScalar12,
|
|
49
|
+
testnet: () => testnet,
|
|
50
|
+
toBaseUnits: () => toBaseUnits,
|
|
51
|
+
tokenAmount: () => tokenAmount
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(index_exports);
|
|
54
|
+
|
|
55
|
+
// src/errors.ts
|
|
56
|
+
var SpieldSdkError = class extends Error {
|
|
57
|
+
constructor(message, code, cause) {
|
|
58
|
+
super(message);
|
|
59
|
+
this.code = code;
|
|
60
|
+
this.cause = cause;
|
|
61
|
+
this.name = "SpieldSdkError";
|
|
62
|
+
}
|
|
63
|
+
code;
|
|
64
|
+
cause;
|
|
65
|
+
};
|
|
66
|
+
var SpieldValidationError = class extends SpieldSdkError {
|
|
67
|
+
constructor(message, cause) {
|
|
68
|
+
super(message, "validation_error", cause);
|
|
69
|
+
this.name = "SpieldValidationError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var SpieldSignerError = class extends SpieldSdkError {
|
|
73
|
+
constructor(message, cause) {
|
|
74
|
+
super(message, "signer_error", cause);
|
|
75
|
+
this.name = "SpieldSignerError";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var SpieldRpcError = class extends SpieldSdkError {
|
|
79
|
+
constructor(message, cause) {
|
|
80
|
+
super(message, "rpc_error", cause);
|
|
81
|
+
this.name = "SpieldRpcError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var SpieldContractError = class extends SpieldSdkError {
|
|
85
|
+
constructor(message, cause) {
|
|
86
|
+
super(message, "contract_error", cause);
|
|
87
|
+
this.name = "SpieldContractError";
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var SpieldConfigurationError = class extends SpieldSdkError {
|
|
91
|
+
constructor(message, cause) {
|
|
92
|
+
super(message, "configuration_error", cause);
|
|
93
|
+
this.name = "SpieldConfigurationError";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/amount.ts
|
|
98
|
+
var toBaseUnits = (amount, decimals = 7) => {
|
|
99
|
+
if (typeof amount === "bigint") return amount;
|
|
100
|
+
const s = String(amount).trim();
|
|
101
|
+
if (!/^-?\d+(\.\d+)?$/.test(s)) {
|
|
102
|
+
throw new SpieldValidationError(`Invalid token amount: ${String(amount)}`);
|
|
103
|
+
}
|
|
104
|
+
const neg = s.startsWith("-");
|
|
105
|
+
const [wholeRaw, fracRaw = ""] = s.replace("-", "").split(".");
|
|
106
|
+
if (fracRaw.length > decimals) {
|
|
107
|
+
throw new SpieldValidationError(`Amount has more than ${decimals} decimal places.`);
|
|
108
|
+
}
|
|
109
|
+
const scale = 10n ** BigInt(decimals);
|
|
110
|
+
const whole = BigInt(wholeRaw || "0") * scale;
|
|
111
|
+
const frac = BigInt((fracRaw + "0".repeat(decimals)).slice(0, decimals) || "0");
|
|
112
|
+
const value = whole + frac;
|
|
113
|
+
return neg ? -value : value;
|
|
114
|
+
};
|
|
115
|
+
var assertPositiveAmount = (amount, decimals = 7) => {
|
|
116
|
+
const raw = toBaseUnits(amount, decimals);
|
|
117
|
+
if (raw <= 0n) throw new SpieldValidationError("Amount must be greater than zero.");
|
|
118
|
+
return raw;
|
|
119
|
+
};
|
|
120
|
+
var assertNonNegativeAmount = (amount, decimals = 7) => {
|
|
121
|
+
const raw = toBaseUnits(amount, decimals);
|
|
122
|
+
if (raw < 0n) throw new SpieldValidationError("Amount must be zero or greater.");
|
|
123
|
+
return raw;
|
|
124
|
+
};
|
|
125
|
+
var fromBaseUnits = (units, decimals = 7) => {
|
|
126
|
+
const value = BigInt(units);
|
|
127
|
+
const sign = value < 0n ? "-" : "";
|
|
128
|
+
const abs = value < 0n ? -value : value;
|
|
129
|
+
const scale = 10n ** BigInt(decimals);
|
|
130
|
+
const whole = abs / scale;
|
|
131
|
+
const frac = (abs % scale).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
132
|
+
return `${sign}${whole.toString()}${frac ? `.${frac}` : ""}`;
|
|
133
|
+
};
|
|
134
|
+
var tokenAmount = (raw, symbol, decimals = 7) => ({
|
|
135
|
+
raw: BigInt(raw),
|
|
136
|
+
formatted: fromBaseUnits(raw, decimals),
|
|
137
|
+
decimals,
|
|
138
|
+
symbol
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// src/soroban.ts
|
|
142
|
+
var import_stellar_sdk = require("@stellar/stellar-sdk");
|
|
143
|
+
|
|
144
|
+
// src/config.ts
|
|
145
|
+
var STELLAR_NETWORK_PASSPHRASES = {
|
|
146
|
+
PUBLIC: "Public Global Stellar Network ; September 2015",
|
|
147
|
+
TESTNET: "Test SDF Network ; September 2015"
|
|
148
|
+
};
|
|
149
|
+
var testnet = () => ({
|
|
150
|
+
key: "testnet",
|
|
151
|
+
networkName: "TESTNET",
|
|
152
|
+
networkPassphrase: STELLAR_NETWORK_PASSPHRASES.TESTNET,
|
|
153
|
+
rpcUrl: "https://soroban-testnet.stellar.org",
|
|
154
|
+
horizonUrl: "https://horizon-testnet.stellar.org",
|
|
155
|
+
explorerUrl: "https://stellar.expert/explorer/testnet",
|
|
156
|
+
contracts: {
|
|
157
|
+
sr: "CDYAM3NGY5I3SUGPCDQUS25MGCIWT2YOBDSWYT6SJNIPN6A6OOUSSCZY",
|
|
158
|
+
strategy: "CDPNSWSBVBRF52SED6UD7T2VQH6XODLEHXSCFTZHQP73SNYTKIHP5R2B",
|
|
159
|
+
yieldEngine: "CDS2Q6L3QCUK4KX633M7QH53GC76EVOUAK7WJ54T3AP3J6IGXIM3LURD",
|
|
160
|
+
market: "CBL7Z3BONITNSWO7NJLT67464HLVVQF5G3REI3XT6KUCK7YNHPICJX5A",
|
|
161
|
+
vault: "CDKV7Z7FF3DA57LSO2JA6GFKAIDDIDMZD5XWCMV7G5I3E4ZA3NN2NMH7",
|
|
162
|
+
router: "CDP3VUYH3GEGNOF4XUHKMP5GBTH3SBCL5R3GM5ZTVQAGV7FOAPAQJTGE",
|
|
163
|
+
pt: "CB3T6FOMAH77Z2FMSA2IEVLQEIYOQRRFP7JMJMJGMUCO6HGOOZJZJ7OC",
|
|
164
|
+
usdc: "CAQCFVLOBK5GIULPNZRGATJJMIZL5BSP7X5YJVMGCPTUEPFM4AVSRCJU"
|
|
165
|
+
},
|
|
166
|
+
assets: {
|
|
167
|
+
pt: { code: "SPLDPT7", issuer: "GDTM2UMJEO6LV5HE2SI56IEWNX5OAF5HV2XNZMVZEDXMMPHZUWXSSLQU" }
|
|
168
|
+
},
|
|
169
|
+
decimals: 7
|
|
170
|
+
});
|
|
171
|
+
var customNetwork = (config) => config;
|
|
172
|
+
var explorerTx = (network, hash) => `${network.explorerUrl}/tx/${hash}`;
|
|
173
|
+
|
|
174
|
+
// src/soroban.ts
|
|
175
|
+
var DEFAULT_SIMULATION_SOURCE = "GBNK7ZOQIZL3HM2LPY7WWJJL3C5YCOEUIJAF4UBSPIEWCEIC2HFSBVVI";
|
|
176
|
+
var DEFAULT_CONFIRMATION_TIMEOUT_MS = 12e4;
|
|
177
|
+
var DEFAULT_CONFIRMATION_POLL_INTERVAL_MS = 1e3;
|
|
178
|
+
var createRpcServer = (network) => new import_stellar_sdk.rpc.Server(network.rpcUrl, { allowHttp: network.rpcUrl.startsWith("http://") });
|
|
179
|
+
var i128 = (v) => (0, import_stellar_sdk.nativeToScVal)(v, { type: "i128" });
|
|
180
|
+
var u64 = (v) => (0, import_stellar_sdk.nativeToScVal)(BigInt(v), { type: "u64" });
|
|
181
|
+
var u32 = (v) => (0, import_stellar_sdk.nativeToScVal)(v, { type: "u32" });
|
|
182
|
+
var addr = (a) => new import_stellar_sdk.Address(a).toScVal();
|
|
183
|
+
var toBig = (v) => {
|
|
184
|
+
if (typeof v === "bigint") return v;
|
|
185
|
+
if (typeof v === "number") return BigInt(Math.trunc(v));
|
|
186
|
+
if (typeof v === "string") return BigInt(v);
|
|
187
|
+
return 0n;
|
|
188
|
+
};
|
|
189
|
+
var toNum = (v) => Number(toBig(v));
|
|
190
|
+
var SorobanClient = class {
|
|
191
|
+
constructor(context) {
|
|
192
|
+
this.context = context;
|
|
193
|
+
this.server = createRpcServer(context.network);
|
|
194
|
+
}
|
|
195
|
+
context;
|
|
196
|
+
server;
|
|
197
|
+
async read(contractId, method, args = []) {
|
|
198
|
+
const contract = new import_stellar_sdk.Contract(contractId);
|
|
199
|
+
const account = new import_stellar_sdk.Account(this.context.simulationSource, "0");
|
|
200
|
+
const tx = new import_stellar_sdk.TransactionBuilder(account, {
|
|
201
|
+
fee: import_stellar_sdk.BASE_FEE,
|
|
202
|
+
networkPassphrase: this.context.network.networkPassphrase
|
|
203
|
+
}).addOperation(contract.call(method, ...args)).setTimeout(30).build();
|
|
204
|
+
let sim;
|
|
205
|
+
try {
|
|
206
|
+
sim = await this.server.simulateTransaction(tx);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
throw new SpieldContractError(
|
|
209
|
+
`${method} response could not be parsed (SDK/protocol version mismatch): ${err instanceof Error ? err.message : String(err)}`,
|
|
210
|
+
err
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (import_stellar_sdk.rpc.Api.isSimulationError(sim)) {
|
|
214
|
+
throw new SpieldContractError(`${method} failed: ${sim.error}`, sim.error);
|
|
215
|
+
}
|
|
216
|
+
const retval = sim.result?.retval;
|
|
217
|
+
return retval ? (0, import_stellar_sdk.scValToNative)(retval) : void 0;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* **Simulate a value-moving entry point AS `invoker`, and return what it would actually pay.**
|
|
221
|
+
*
|
|
222
|
+
* The `quote_*` views cannot synchronize SR — a view may not write — so they price on SR's stored
|
|
223
|
+
* high-water rate, which lags whenever nothing has synced since the last mutation. The real entry
|
|
224
|
+
* points *do* synchronize (`FINAL_CHECK.md` V2-01), so a quote can OVER-state a sale: derive
|
|
225
|
+
* `minOut` from it and the trade can trip its own slippage bound and revert.
|
|
226
|
+
*
|
|
227
|
+
* Simulating the real function runs the same synchronized code the submission will. Nothing is
|
|
228
|
+
* signed and nothing is sent.
|
|
229
|
+
*
|
|
230
|
+
* Unlike {@link read}, this simulates as the **user** rather than the shared simulation source,
|
|
231
|
+
* because the result depends on their balances.
|
|
232
|
+
*
|
|
233
|
+
* Returns `null` whenever the simulation cannot run — an unaffordable size, a route the pool
|
|
234
|
+
* cannot fill, a deployment predating the entry point — so callers fall back to the quote instead
|
|
235
|
+
* of failing the trade.
|
|
236
|
+
*/
|
|
237
|
+
async simulateAs(invoker, contractId, method, args = []) {
|
|
238
|
+
try {
|
|
239
|
+
const tx = new import_stellar_sdk.TransactionBuilder(new import_stellar_sdk.Account(invoker, "0"), {
|
|
240
|
+
fee: import_stellar_sdk.BASE_FEE,
|
|
241
|
+
networkPassphrase: this.context.network.networkPassphrase
|
|
242
|
+
}).addOperation(new import_stellar_sdk.Contract(contractId).call(method, ...args)).setTimeout(30).build();
|
|
243
|
+
const sim = await this.server.simulateTransaction(tx);
|
|
244
|
+
if (import_stellar_sdk.rpc.Api.isSimulationError(sim)) return null;
|
|
245
|
+
const retval = sim.result?.retval;
|
|
246
|
+
if (!retval) return null;
|
|
247
|
+
const native = (0, import_stellar_sdk.scValToNative)(retval);
|
|
248
|
+
return typeof native === "bigint" ? native : BigInt(String(native));
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async write(contractId, method, args = [], signer = this.context.signer) {
|
|
254
|
+
if (!signer) throw new SpieldSignerError("A signer is required for write transactions.");
|
|
255
|
+
const walletAddress = await signer.getAddress();
|
|
256
|
+
const contract = new import_stellar_sdk.Contract(contractId);
|
|
257
|
+
const sourceAccount = await this.server.getAccount(walletAddress);
|
|
258
|
+
const built = new import_stellar_sdk.TransactionBuilder(sourceAccount, {
|
|
259
|
+
fee: import_stellar_sdk.BASE_FEE,
|
|
260
|
+
networkPassphrase: this.context.network.networkPassphrase
|
|
261
|
+
}).addOperation(contract.call(method, ...args)).setTimeout(60).build();
|
|
262
|
+
const sim = await this.server.simulateTransaction(built);
|
|
263
|
+
if (import_stellar_sdk.rpc.Api.isSimulationError(sim)) {
|
|
264
|
+
throw new SpieldContractError(parseContractError(sim.error, method, this.context.network), sim.error);
|
|
265
|
+
}
|
|
266
|
+
const prepared = import_stellar_sdk.rpc.assembleTransaction(built, sim).build();
|
|
267
|
+
const { signedTxXdr } = await signer.signTransaction(prepared.toXDR(), {
|
|
268
|
+
networkPassphrase: this.context.network.networkPassphrase,
|
|
269
|
+
address: walletAddress
|
|
270
|
+
});
|
|
271
|
+
const signed = import_stellar_sdk.TransactionBuilder.fromXDR(signedTxXdr, this.context.network.networkPassphrase);
|
|
272
|
+
const sent = await this.server.sendTransaction(signed);
|
|
273
|
+
if (sent.status === "ERROR") {
|
|
274
|
+
throw new SpieldRpcError(`Submission failed: ${JSON.stringify(sent.errorResult)}`, sent.errorResult);
|
|
275
|
+
}
|
|
276
|
+
return this.waitForConfirmation(sent.hash);
|
|
277
|
+
}
|
|
278
|
+
async waitForConfirmation(hash) {
|
|
279
|
+
const timeoutMs = positiveMs(this.context.confirmationTimeoutMs, DEFAULT_CONFIRMATION_TIMEOUT_MS);
|
|
280
|
+
const pollIntervalMs = positiveMs(this.context.confirmationPollIntervalMs, DEFAULT_CONFIRMATION_POLL_INTERVAL_MS);
|
|
281
|
+
const deadline = Date.now() + timeoutMs;
|
|
282
|
+
let lastRpcError;
|
|
283
|
+
do {
|
|
284
|
+
try {
|
|
285
|
+
const result = await this.server.getTransaction(hash);
|
|
286
|
+
if (result.status === import_stellar_sdk.rpc.Api.GetTransactionStatus.SUCCESS) {
|
|
287
|
+
return { hash, status: "success", explorerUrl: explorerTx(this.context.network, hash) };
|
|
288
|
+
}
|
|
289
|
+
if (result.status === import_stellar_sdk.rpc.Api.GetTransactionStatus.FAILED) {
|
|
290
|
+
throw new SpieldRpcError(`Transaction failed on-chain: ${hash}`, result);
|
|
291
|
+
}
|
|
292
|
+
} catch (err) {
|
|
293
|
+
if (err instanceof SpieldRpcError) throw err;
|
|
294
|
+
lastRpcError = err;
|
|
295
|
+
}
|
|
296
|
+
const remaining = deadline - Date.now();
|
|
297
|
+
if (remaining <= 0) break;
|
|
298
|
+
await sleep(Math.min(pollIntervalMs, remaining));
|
|
299
|
+
} while (Date.now() <= deadline);
|
|
300
|
+
throw new SpieldRpcError(
|
|
301
|
+
`Timed out waiting ${timeoutMs}ms for transaction confirmation: ${hash}. The transaction was submitted and may still settle; check the explorer before resubmitting.`,
|
|
302
|
+
lastRpcError
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
307
|
+
var positiveMs = (value, fallback) => Number.isFinite(value) && (value ?? 0) > 0 ? Math.floor(value) : fallback;
|
|
308
|
+
var CONTRACT_ERRORS = {
|
|
309
|
+
1: "The contract is already initialized.",
|
|
310
|
+
2: "The contract is not initialized yet.",
|
|
311
|
+
3: "The caller is not authorized for this operation.",
|
|
312
|
+
4: "The protocol is currently paused.",
|
|
313
|
+
5: "Amount must be greater than zero.",
|
|
314
|
+
6: "The operation overflowed.",
|
|
315
|
+
11: "The token does not use the 7 decimals the protocol is calibrated for.",
|
|
316
|
+
22: "PT can only be redeemed at or after maturity.",
|
|
317
|
+
23: "Insufficient balance for this operation.",
|
|
318
|
+
24: "The operation would break the solvency invariant and was refused.",
|
|
319
|
+
40: "Blend reported a rate outside the configured sanity bound.",
|
|
320
|
+
41: "The strategy holds no Blend position for this asset.",
|
|
321
|
+
42: "Blend returned less underlying than required. Try a smaller amount.",
|
|
322
|
+
60: "That vault receipt does not exist.",
|
|
323
|
+
61: "The caller does not own that vault receipt.",
|
|
324
|
+
62: "The vault has not matured yet.",
|
|
325
|
+
63: "That vault receipt has already been redeemed.",
|
|
326
|
+
64: "The vault lacks the spare PT to back a coupon for this deposit.",
|
|
327
|
+
65: "The quoted fixed rate is outside the allowed range.",
|
|
328
|
+
66: "The vault series has expired; deposits are closed.",
|
|
329
|
+
80: "Not enough market liquidity for this trade.",
|
|
330
|
+
81: "The output fell below your slippage floor and the trade was reverted.",
|
|
331
|
+
82: "The market has expired; PT now redeems at face value instead of trading.",
|
|
332
|
+
83: "You do not hold that many LP shares.",
|
|
333
|
+
84: "Liquidity must be added in the pool's current PT/SR proportion.",
|
|
334
|
+
100: "The spender allowance is smaller than the amount being moved.",
|
|
335
|
+
101: "The series has expired; this operation is only valid before expiry.",
|
|
336
|
+
102: "This operation is only valid at or after the series expiry.",
|
|
337
|
+
103: "The caller is not the contract permitted to drive this entrypoint.",
|
|
338
|
+
105: "The output fell below your minimum and the transaction was reverted.",
|
|
339
|
+
106: "The amount rounds to zero at the current exchange rate. Use a larger amount.",
|
|
340
|
+
107: "The deposit would exceed the SR deposit cap. Check the remaining headroom."
|
|
341
|
+
};
|
|
342
|
+
var parseContractError = (raw, method, network) => {
|
|
343
|
+
const text = raw || "";
|
|
344
|
+
const events = [...text.matchAll(
|
|
345
|
+
/contract:(C[A-Z2-7]{55}), topics:\[error, Error\(Contract, #(\d+)\)\], data:(?:"([^"]*)"|\[)/g
|
|
346
|
+
)];
|
|
347
|
+
const root = events.at(-1);
|
|
348
|
+
const rootContract = root?.[1];
|
|
349
|
+
const rootCode = root ? Number(root[2]) : void 0;
|
|
350
|
+
const rootMessage = root?.[3];
|
|
351
|
+
const assetOf = (id) => id === network.contracts.usdc ? "USDC" : id === network.contracts.pt ? "PT" : void 0;
|
|
352
|
+
if (/trustline entry is missing/.test(text)) {
|
|
353
|
+
const asset = assetOf(rootContract) ?? assetOf(/contract:(C[A-Z2-7]{55})[^\n]*trustline entry is missing/.exec(text)?.[1]);
|
|
354
|
+
if (asset === "PT") {
|
|
355
|
+
return "The wallet needs a PT trustline before it can hold PT. Call tokens.ensurePtTrustline().";
|
|
356
|
+
}
|
|
357
|
+
if (asset === "USDC") {
|
|
358
|
+
return "The wallet needs a USDC trustline (and a USDC balance) for this transaction.";
|
|
359
|
+
}
|
|
360
|
+
return "The wallet is missing a required asset trustline for this transaction.";
|
|
361
|
+
}
|
|
362
|
+
const isSpieldContract = !!rootContract && ["sr", "strategy", "yieldEngine", "market", "vault", "router"].some((key) => network.contracts[key] === rootContract);
|
|
363
|
+
if (rootCode !== void 0 && isSpieldContract) {
|
|
364
|
+
const mapped = CONTRACT_ERRORS[rootCode];
|
|
365
|
+
if (mapped) return mapped;
|
|
366
|
+
return `${method} failed with Spield contract error #${rootCode}.`;
|
|
367
|
+
}
|
|
368
|
+
if (rootMessage) return `${method} failed: ${rootMessage}`;
|
|
369
|
+
const code = /Error\(Contract, #(\d+)\)/.exec(text);
|
|
370
|
+
if (code) return `${method} failed with contract error #${code[1]}.`;
|
|
371
|
+
return `${method} simulation failed: ${text.slice(0, 200)}`;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// src/blend-strategy.ts
|
|
375
|
+
var BlendStrategyClient = class {
|
|
376
|
+
constructor(network, soroban) {
|
|
377
|
+
this.network = network;
|
|
378
|
+
this.soroban = soroban;
|
|
379
|
+
}
|
|
380
|
+
network;
|
|
381
|
+
soroban;
|
|
382
|
+
get contract() {
|
|
383
|
+
return this.network.contracts.strategy;
|
|
384
|
+
}
|
|
385
|
+
async getAvailableLiquidity() {
|
|
386
|
+
const value = await this.soroban.read(this.contract, "available_liquidity");
|
|
387
|
+
return tokenAmount(toBig(value), "USDC", this.network.decimals);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* **BLND accrued to the protocol and not yet claimed.**
|
|
391
|
+
*
|
|
392
|
+
* Measured on mainnet 2026-08-30: Blend allocates emissions to XLM *suppliers* and USDC
|
|
393
|
+
* *borrowers*, not USDC suppliers — which is the side Spield is on — so this reads `0` and has
|
|
394
|
+
* never been anything else. Allocations carry an expiration and rotate each cycle, so it will not
|
|
395
|
+
* necessarily stay zero.
|
|
396
|
+
*
|
|
397
|
+
* Claiming is permissionless and pays a destination fixed in contract storage, so an integrator
|
|
398
|
+
* can trigger it but can never redirect it.
|
|
399
|
+
*/
|
|
400
|
+
async getClaimableEmissions() {
|
|
401
|
+
try {
|
|
402
|
+
const value = await this.soroban.read(this.contract, "claimable_emissions");
|
|
403
|
+
return tokenAmount(toBig(value), "BLND", this.network.decimals);
|
|
404
|
+
} catch {
|
|
405
|
+
return tokenAmount(0n, "BLND", this.network.decimals);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* **What the whole Blend position is really worth**, live rate, no monotonicity guard, no write.
|
|
410
|
+
* Unlike {@link getPositionValue} this keeps answering while a rate decrease has exits frozen.
|
|
411
|
+
*/
|
|
412
|
+
async getPositionValueUnguarded() {
|
|
413
|
+
try {
|
|
414
|
+
const value = await this.soroban.read(this.contract, "position_value_unguarded");
|
|
415
|
+
return tokenAmount(toBig(value), "USDC", this.network.decimals);
|
|
416
|
+
} catch {
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async getRateBound() {
|
|
421
|
+
const tuple = await this.soroban.read(this.contract, "rate_bound");
|
|
422
|
+
const [lastRate, lastTimestamp, maxAprBps] = tuple ?? [];
|
|
423
|
+
return {
|
|
424
|
+
lastRate: toBig(lastRate),
|
|
425
|
+
lastTimestamp: toNum(lastTimestamp),
|
|
426
|
+
maxAprBps: toNum(maxAprBps)
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
async getPositionValue(input) {
|
|
430
|
+
const shares = assertPositiveAmount(input.shares, this.network.decimals);
|
|
431
|
+
const value = await this.soroban.read(this.contract, "position_value", [i128(shares)]);
|
|
432
|
+
return tokenAmount(toBig(value), "USDC", this.network.decimals);
|
|
433
|
+
}
|
|
434
|
+
async getState() {
|
|
435
|
+
const [currentRate, shares, liquidity, underlying, pool, rateBound] = await Promise.all([
|
|
436
|
+
this.soroban.read(this.contract, "current_rate"),
|
|
437
|
+
this.soroban.read(this.contract, "total_shares"),
|
|
438
|
+
this.getAvailableLiquidity(),
|
|
439
|
+
this.soroban.read(this.contract, "underlying"),
|
|
440
|
+
this.soroban.read(this.contract, "pool"),
|
|
441
|
+
this.getRateBound()
|
|
442
|
+
]);
|
|
443
|
+
return {
|
|
444
|
+
currentRate: toBig(currentRate),
|
|
445
|
+
totalShares: tokenAmount(toBig(shares), "STRATEGY_SHARES", this.network.decimals),
|
|
446
|
+
availableLiquidity: liquidity,
|
|
447
|
+
underlying: String(underlying),
|
|
448
|
+
pool: String(pool),
|
|
449
|
+
rateBound
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// src/horizon.ts
|
|
455
|
+
var import_stellar_sdk2 = require("@stellar/stellar-sdk");
|
|
456
|
+
var HorizonClient = class {
|
|
457
|
+
constructor(network) {
|
|
458
|
+
this.network = network;
|
|
459
|
+
this.horizon = new import_stellar_sdk2.Horizon.Server(network.horizonUrl);
|
|
460
|
+
}
|
|
461
|
+
network;
|
|
462
|
+
horizon;
|
|
463
|
+
async getPtTrustline(address) {
|
|
464
|
+
const { code, issuer } = this.network.assets.pt;
|
|
465
|
+
try {
|
|
466
|
+
const account = await this.horizon.loadAccount(address);
|
|
467
|
+
const pt = account.balances.some(
|
|
468
|
+
(balance) => "asset_code" in balance && balance.asset_code === code && "asset_issuer" in balance && balance.asset_issuer === issuer
|
|
469
|
+
);
|
|
470
|
+
return { pt, ready: pt };
|
|
471
|
+
} catch {
|
|
472
|
+
return { pt: false, ready: false };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
async setupPtTrustline(address, signer) {
|
|
476
|
+
if (!signer) throw new SpieldSignerError("A signer is required to setup the PT trustline.");
|
|
477
|
+
if ((await this.getPtTrustline(address)).ready) return null;
|
|
478
|
+
const { code, issuer } = this.network.assets.pt;
|
|
479
|
+
const account = await this.horizon.loadAccount(address);
|
|
480
|
+
const tx = new import_stellar_sdk2.TransactionBuilder(account, {
|
|
481
|
+
fee: import_stellar_sdk2.BASE_FEE,
|
|
482
|
+
networkPassphrase: this.network.networkPassphrase
|
|
483
|
+
}).addOperation(import_stellar_sdk2.Operation.changeTrust({ asset: new import_stellar_sdk2.Asset(code, issuer) })).setTimeout(120).build();
|
|
484
|
+
const { signedTxXdr } = await signer.signTransaction(tx.toXDR(), {
|
|
485
|
+
networkPassphrase: this.network.networkPassphrase,
|
|
486
|
+
address
|
|
487
|
+
});
|
|
488
|
+
const signed = import_stellar_sdk2.TransactionBuilder.fromXDR(signedTxXdr, this.network.networkPassphrase);
|
|
489
|
+
const result = await this.horizon.submitTransaction(
|
|
490
|
+
signed
|
|
491
|
+
);
|
|
492
|
+
return { hash: result.hash, status: "success", explorerUrl: explorerTx(this.network, result.hash) };
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
// src/internal.ts
|
|
497
|
+
var signerAddress = async (signer) => {
|
|
498
|
+
if (!signer) throw new SpieldSignerError("A signer is required for this transaction.");
|
|
499
|
+
return signer.getAddress();
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// src/sr.ts
|
|
503
|
+
var SCALAR_12 = 10n ** 12n;
|
|
504
|
+
var fromScalar12 = (v) => Number(v) / Number(SCALAR_12);
|
|
505
|
+
var StandardizedReturnClient = class {
|
|
506
|
+
constructor(network, soroban, signer) {
|
|
507
|
+
this.network = network;
|
|
508
|
+
this.soroban = soroban;
|
|
509
|
+
this.signer = signer;
|
|
510
|
+
}
|
|
511
|
+
network;
|
|
512
|
+
soroban;
|
|
513
|
+
signer;
|
|
514
|
+
get contract() {
|
|
515
|
+
return this.network.contracts.sr;
|
|
516
|
+
}
|
|
517
|
+
async getExchangeRate() {
|
|
518
|
+
return toBig(await this.soroban.read(this.contract, "exchange_rate"));
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* **The honest twin of {@link getExchangeRate} — what a share is ACTUALLY worth.**
|
|
522
|
+
*
|
|
523
|
+
* `exchange_rate` is a high-water mark: it ratchets up and never falls. That stops the protocol
|
|
524
|
+
* repricing on a transient dip, but it means every value built on it — `previewRedeem`,
|
|
525
|
+
* `getAssetsOf` — reports MORE underlying than a redemption pays once Blend has taken a real loss.
|
|
526
|
+
*
|
|
527
|
+
* This reads the venue with no monotonicity guard, so unlike the guarded paths it keeps answering
|
|
528
|
+
* while exits are frozen — which is exactly when somebody needs the number.
|
|
529
|
+
*
|
|
530
|
+
* Returns `0n` on a deployment predating it. Sitting slightly ABOVE `exchange_rate` in normal
|
|
531
|
+
* operation is expected (it sees accrual the stored rate has not synced); only the other
|
|
532
|
+
* direction is interesting.
|
|
533
|
+
*/
|
|
534
|
+
async getRealizableRate() {
|
|
535
|
+
try {
|
|
536
|
+
return toBig(await this.soroban.read(this.contract, "realizable_rate"));
|
|
537
|
+
} catch {
|
|
538
|
+
return 0n;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** A holder's actual pro-rata claim. See {@link getRealizableRate}. `null` if unavailable. */
|
|
542
|
+
async getRealizableValue(input) {
|
|
543
|
+
const shares = assertPositiveAmount(input.shares, this.network.decimals);
|
|
544
|
+
try {
|
|
545
|
+
const value = await this.soroban.read(this.contract, "realizable_value", [i128(shares)]);
|
|
546
|
+
return tokenAmount(toBig(value), "USDC", this.network.decimals);
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
srToUnderlying(shares, rate) {
|
|
552
|
+
return rate > 0n ? shares * rate / SCALAR_12 : 0n;
|
|
553
|
+
}
|
|
554
|
+
underlyingToSr(amount, rate) {
|
|
555
|
+
return rate > 0n ? amount * SCALAR_12 / rate : 0n;
|
|
556
|
+
}
|
|
557
|
+
async previewDeposit(input) {
|
|
558
|
+
const amount = assertPositiveAmount(input.amount, this.network.decimals);
|
|
559
|
+
const raw = await this.soroban.read(this.contract, "preview_deposit", [i128(amount)]);
|
|
560
|
+
return tokenAmount(toBig(raw), "SR", this.network.decimals);
|
|
561
|
+
}
|
|
562
|
+
async previewRedeem(input) {
|
|
563
|
+
const shares = assertPositiveAmount(input.shares, this.network.decimals);
|
|
564
|
+
const raw = await this.soroban.read(this.contract, "preview_redeem", [i128(shares)]);
|
|
565
|
+
return tokenAmount(toBig(raw), "USDC", this.network.decimals);
|
|
566
|
+
}
|
|
567
|
+
async getBalance(input) {
|
|
568
|
+
const raw = await this.soroban.read(this.contract, "balance", [addr(input.owner)]);
|
|
569
|
+
return tokenAmount(toBig(raw), "SR", this.network.decimals);
|
|
570
|
+
}
|
|
571
|
+
async getAssetsOf(input) {
|
|
572
|
+
const raw = await this.soroban.read(this.contract, "assets_of", [addr(input.owner)]);
|
|
573
|
+
return tokenAmount(toBig(raw), "USDC", this.network.decimals);
|
|
574
|
+
}
|
|
575
|
+
async getState() {
|
|
576
|
+
const [rateRaw, supplyRaw, assetsRaw, maxRaw, capRaw, headroomRaw, paused] = await Promise.all([
|
|
577
|
+
this.soroban.read(this.contract, "exchange_rate"),
|
|
578
|
+
this.soroban.read(this.contract, "total_supply"),
|
|
579
|
+
this.soroban.read(this.contract, "total_assets"),
|
|
580
|
+
this.soroban.read(this.contract, "max_redeemable"),
|
|
581
|
+
this.soroban.read(this.contract, "deposit_cap"),
|
|
582
|
+
this.soroban.read(this.contract, "deposit_headroom"),
|
|
583
|
+
this.soroban.read(this.contract, "is_paused")
|
|
584
|
+
]);
|
|
585
|
+
const rate = toBig(rateRaw);
|
|
586
|
+
const max = toBig(maxRaw);
|
|
587
|
+
const cap = toBig(capRaw);
|
|
588
|
+
const headroom = toBig(headroomRaw);
|
|
589
|
+
return {
|
|
590
|
+
exchangeRate: rate,
|
|
591
|
+
exchangeRateNumber: Number(rate) / Number(SCALAR_12),
|
|
592
|
+
totalSupply: tokenAmount(toBig(supplyRaw), "SR", this.network.decimals),
|
|
593
|
+
totalAssets: tokenAmount(toBig(assetsRaw), "USDC", this.network.decimals),
|
|
594
|
+
maxRedeemable: max === (1n << 127n) - 1n ? null : tokenAmount(max, "SR", this.network.decimals),
|
|
595
|
+
depositCap: cap === 0n ? null : tokenAmount(cap, "USDC", this.network.decimals),
|
|
596
|
+
depositHeadroom: cap === 0n ? null : tokenAmount(headroom, "USDC", this.network.decimals),
|
|
597
|
+
depositCapReached: cap > 0n && headroom === 0n,
|
|
598
|
+
paused
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
async deposit(input) {
|
|
602
|
+
const signer = input.signer ?? this.signer;
|
|
603
|
+
const from = await signerAddress(signer);
|
|
604
|
+
return this.soroban.write(this.contract, "deposit", [
|
|
605
|
+
addr(from),
|
|
606
|
+
addr(input.receiver ?? from),
|
|
607
|
+
i128(assertPositiveAmount(input.amount, this.network.decimals)),
|
|
608
|
+
i128(assertNonNegativeAmount(input.minSharesOut ?? 0n, this.network.decimals))
|
|
609
|
+
], signer);
|
|
610
|
+
}
|
|
611
|
+
async redeem(input) {
|
|
612
|
+
const signer = input.signer ?? this.signer;
|
|
613
|
+
const from = await signerAddress(signer);
|
|
614
|
+
return this.soroban.write(this.contract, "redeem", [
|
|
615
|
+
addr(from),
|
|
616
|
+
addr(input.receiver ?? from),
|
|
617
|
+
i128(assertPositiveAmount(input.shares, this.network.decimals)),
|
|
618
|
+
i128(assertNonNegativeAmount(input.minUnderlyingOut ?? 0n, this.network.decimals))
|
|
619
|
+
], signer);
|
|
620
|
+
}
|
|
621
|
+
async redeemPartial(input) {
|
|
622
|
+
const signer = input.signer ?? this.signer;
|
|
623
|
+
const from = await signerAddress(signer);
|
|
624
|
+
return this.soroban.write(this.contract, "redeem_partial", [
|
|
625
|
+
addr(from),
|
|
626
|
+
addr(input.receiver ?? from),
|
|
627
|
+
i128(assertPositiveAmount(input.shares, this.network.decimals)),
|
|
628
|
+
i128(assertNonNegativeAmount(input.minUnderlyingOut ?? 0n, this.network.decimals))
|
|
629
|
+
], signer);
|
|
630
|
+
}
|
|
631
|
+
/** Permissionless update of SR's stored high-water exchange rate. */
|
|
632
|
+
syncRate(input = {}) {
|
|
633
|
+
return this.soroban.write(this.contract, "sync_rate", [], input.signer ?? this.signer);
|
|
634
|
+
}
|
|
635
|
+
/** Permissionless keep-alive for an SR holder's persistent balance entry. */
|
|
636
|
+
bumpHolder(input = {}) {
|
|
637
|
+
const signer = input.signer ?? this.signer;
|
|
638
|
+
return signerAddress(signer).then((signerOwner) => this.soroban.write(
|
|
639
|
+
this.contract,
|
|
640
|
+
"bump_holder",
|
|
641
|
+
[addr(input.owner ?? signerOwner)],
|
|
642
|
+
signer
|
|
643
|
+
));
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/sr-market.ts
|
|
648
|
+
var YT_MAX_IN_PAD_BPS = 300n;
|
|
649
|
+
var SrMarketClient = class {
|
|
650
|
+
constructor(network, soroban, signer) {
|
|
651
|
+
this.network = network;
|
|
652
|
+
this.soroban = soroban;
|
|
653
|
+
this.signer = signer;
|
|
654
|
+
}
|
|
655
|
+
network;
|
|
656
|
+
soroban;
|
|
657
|
+
signer;
|
|
658
|
+
latestLiquidityAbi;
|
|
659
|
+
get contract() {
|
|
660
|
+
return this.network.contracts.market;
|
|
661
|
+
}
|
|
662
|
+
async getStats() {
|
|
663
|
+
const [reserves, assetRaw, sharesRaw, priceRaw, apyRaw, expiryRaw, earnedRaw, feeShareRaw, paused] = await Promise.all([
|
|
664
|
+
this.soroban.read(this.contract, "reserves"),
|
|
665
|
+
this.soroban.read(this.contract, "asset_reserve"),
|
|
666
|
+
this.soroban.read(this.contract, "total_shares"),
|
|
667
|
+
this.soroban.read(this.contract, "pt_price"),
|
|
668
|
+
this.soroban.read(this.contract, "implied_apy"),
|
|
669
|
+
this.soroban.read(this.contract, "expiry"),
|
|
670
|
+
this.soroban.read(this.contract, "treasury_earned"),
|
|
671
|
+
this.soroban.read(this.contract, "treasury_fee_share_bps"),
|
|
672
|
+
this.soroban.read(this.contract, "is_paused")
|
|
673
|
+
]);
|
|
674
|
+
const [ptRaw, srRaw] = reserves ?? [];
|
|
675
|
+
const price = toBig(priceRaw);
|
|
676
|
+
const apy = toBig(apyRaw);
|
|
677
|
+
const expiry = toNum(expiryRaw);
|
|
678
|
+
return {
|
|
679
|
+
ptReserve: tokenAmount(toBig(ptRaw), "PT", this.network.decimals),
|
|
680
|
+
srReserve: tokenAmount(toBig(srRaw), "SR", this.network.decimals),
|
|
681
|
+
assetReserve: tokenAmount(toBig(assetRaw), "USDC", this.network.decimals),
|
|
682
|
+
totalShares: tokenAmount(toBig(sharesRaw), "LP", this.network.decimals),
|
|
683
|
+
ptPrice: price,
|
|
684
|
+
ptPriceNumber: Number(price) / Number(SCALAR_12),
|
|
685
|
+
impliedApy: apy,
|
|
686
|
+
impliedApyPct: Number(apy) / Number(SCALAR_12) * 100,
|
|
687
|
+
expiry,
|
|
688
|
+
expiryDate: new Date(expiry * 1e3),
|
|
689
|
+
treasuryEarned: tokenAmount(toBig(earnedRaw), "SR", this.network.decimals),
|
|
690
|
+
treasuryFeeShareBps: toNum(feeShareRaw),
|
|
691
|
+
paused
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
async quote(method, amount, inputSymbol, outputSymbol) {
|
|
695
|
+
const input = assertPositiveAmount(amount, this.network.decimals);
|
|
696
|
+
const output = toBig(await this.soroban.read(this.contract, method, [i128(input)]));
|
|
697
|
+
return {
|
|
698
|
+
input: tokenAmount(input, inputSymbol, this.network.decimals),
|
|
699
|
+
output: tokenAmount(output, outputSymbol, this.network.decimals),
|
|
700
|
+
available: output > 0n
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
quoteBuyPt(input) {
|
|
704
|
+
return this.quote("quote_buy_pt", input.srIn, "SR", "PT");
|
|
705
|
+
}
|
|
706
|
+
quoteSellPt(input) {
|
|
707
|
+
return this.quote("quote_sell_pt", input.ptIn, "PT", "SR");
|
|
708
|
+
}
|
|
709
|
+
quoteBuyYt(input) {
|
|
710
|
+
return this.quote("quote_buy_yt", input.ytOut, "YT", "SR");
|
|
711
|
+
}
|
|
712
|
+
quoteSellYt(input) {
|
|
713
|
+
return this.quote("quote_sell_yt", input.ytIn, "YT", "SR");
|
|
714
|
+
}
|
|
715
|
+
async getLpPosition(input) {
|
|
716
|
+
const tuple = await this.soroban.read(this.contract, "lp_position", [addr(input.owner)]);
|
|
717
|
+
const [shares, ptClaim, srClaim] = tuple ?? [];
|
|
718
|
+
return {
|
|
719
|
+
shares: tokenAmount(toBig(shares), "LP", this.network.decimals),
|
|
720
|
+
ptClaim: tokenAmount(toBig(ptClaim), "PT", this.network.decimals),
|
|
721
|
+
srClaim: tokenAmount(toBig(srClaim), "SR", this.network.decimals)
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
async buyPt(input) {
|
|
725
|
+
const signer = input.signer ?? this.signer;
|
|
726
|
+
const owner = await signerAddress(signer);
|
|
727
|
+
return this.soroban.write(this.contract, "swap_exact_sr_for_pt", [
|
|
728
|
+
addr(owner),
|
|
729
|
+
i128(assertPositiveAmount(input.srIn, this.network.decimals)),
|
|
730
|
+
i128(assertNonNegativeAmount(input.minPtOut ?? 0n, this.network.decimals)),
|
|
731
|
+
u32(input.deadlineLedger ?? 0)
|
|
732
|
+
], signer);
|
|
733
|
+
}
|
|
734
|
+
async sellPt(input) {
|
|
735
|
+
const signer = input.signer ?? this.signer;
|
|
736
|
+
const owner = await signerAddress(signer);
|
|
737
|
+
return this.soroban.write(this.contract, "swap_exact_pt_for_sr", [
|
|
738
|
+
addr(owner),
|
|
739
|
+
i128(assertPositiveAmount(input.ptIn, this.network.decimals)),
|
|
740
|
+
i128(assertNonNegativeAmount(input.minSrOut ?? 0n, this.network.decimals)),
|
|
741
|
+
u32(input.deadlineLedger ?? 0)
|
|
742
|
+
], signer);
|
|
743
|
+
}
|
|
744
|
+
async buyYt(input) {
|
|
745
|
+
const signer = input.signer ?? this.signer;
|
|
746
|
+
const owner = await signerAddress(signer);
|
|
747
|
+
const ytOut = assertPositiveAmount(input.ytOut, this.network.decimals);
|
|
748
|
+
let maxSrIn;
|
|
749
|
+
if (input.maxSrIn === void 0) {
|
|
750
|
+
const quoted = (await this.quoteBuyYt({ ytOut })).output.raw;
|
|
751
|
+
if (quoted <= 0n) throw new Error("No YT quote is available for that size.");
|
|
752
|
+
maxSrIn = quoted + quoted * YT_MAX_IN_PAD_BPS / 10000n + 1n;
|
|
753
|
+
} else {
|
|
754
|
+
maxSrIn = assertPositiveAmount(input.maxSrIn, this.network.decimals);
|
|
755
|
+
}
|
|
756
|
+
return this.soroban.write(this.contract, "buy_yt_exact_out", [
|
|
757
|
+
addr(owner),
|
|
758
|
+
i128(ytOut),
|
|
759
|
+
i128(maxSrIn),
|
|
760
|
+
u32(input.deadlineLedger ?? 0)
|
|
761
|
+
], signer);
|
|
762
|
+
}
|
|
763
|
+
async sellYt(input) {
|
|
764
|
+
const signer = input.signer ?? this.signer;
|
|
765
|
+
const owner = await signerAddress(signer);
|
|
766
|
+
return this.soroban.write(this.contract, "sell_yt_exact_in", [
|
|
767
|
+
addr(owner),
|
|
768
|
+
i128(assertPositiveAmount(input.ytIn, this.network.decimals)),
|
|
769
|
+
i128(assertNonNegativeAmount(input.minSrOut ?? 0n, this.network.decimals)),
|
|
770
|
+
u32(input.deadlineLedger ?? 0)
|
|
771
|
+
], signer);
|
|
772
|
+
}
|
|
773
|
+
async addLiquidity(input) {
|
|
774
|
+
const signer = input.signer ?? this.signer;
|
|
775
|
+
const owner = await signerAddress(signer);
|
|
776
|
+
const minShares = assertNonNegativeAmount(input.minShares ?? 0n, this.network.decimals);
|
|
777
|
+
const args = [
|
|
778
|
+
addr(owner),
|
|
779
|
+
i128(assertPositiveAmount(input.ptIn, this.network.decimals)),
|
|
780
|
+
i128(assertPositiveAmount(input.srIn, this.network.decimals))
|
|
781
|
+
];
|
|
782
|
+
if (await this.supportsLatestLiquidityAbi(owner)) {
|
|
783
|
+
return this.soroban.write(this.contract, "add_liquidity", [...args, i128(minShares)], signer);
|
|
784
|
+
}
|
|
785
|
+
if (minShares > 0n) {
|
|
786
|
+
throw new SpieldConfigurationError(
|
|
787
|
+
"This market deployment predates addLiquidity.minShares; use minShares=0 or upgrade the market contract."
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
return this.soroban.write(this.contract, "add_liquidity", args, signer);
|
|
791
|
+
}
|
|
792
|
+
async removeLiquidity(input) {
|
|
793
|
+
const signer = input.signer ?? this.signer;
|
|
794
|
+
const owner = await signerAddress(signer);
|
|
795
|
+
return this.soroban.write(this.contract, "remove_liquidity", [
|
|
796
|
+
addr(owner),
|
|
797
|
+
i128(assertPositiveAmount(input.shares, this.network.decimals)),
|
|
798
|
+
i128(assertNonNegativeAmount(input.minPtOut ?? 0n, this.network.decimals)),
|
|
799
|
+
i128(assertNonNegativeAmount(input.minSrOut ?? 0n, this.network.decimals))
|
|
800
|
+
], signer);
|
|
801
|
+
}
|
|
802
|
+
/** Permissionless keep-alive for an LP share entry. */
|
|
803
|
+
async bumpLp(input = {}) {
|
|
804
|
+
const signer = input.signer ?? this.signer;
|
|
805
|
+
const signerOwner = await signerAddress(signer);
|
|
806
|
+
return this.soroban.write(this.contract, "bump_lp", [addr(input.owner ?? signerOwner)], signer);
|
|
807
|
+
}
|
|
808
|
+
supportsLatestLiquidityAbi(owner) {
|
|
809
|
+
this.latestLiquidityAbi ??= this.soroban.read(this.contract, "bump_lp", [addr(owner)]).then(() => true, () => false);
|
|
810
|
+
return this.latestLiquidityAbi;
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// src/sr-portfolio.ts
|
|
815
|
+
var SrPortfolioClient = class {
|
|
816
|
+
constructor(tokens, sr, yieldEngine, market, vault, decimals) {
|
|
817
|
+
this.tokens = tokens;
|
|
818
|
+
this.sr = sr;
|
|
819
|
+
this.yieldEngine = yieldEngine;
|
|
820
|
+
this.market = market;
|
|
821
|
+
this.vault = vault;
|
|
822
|
+
this.decimals = decimals;
|
|
823
|
+
}
|
|
824
|
+
tokens;
|
|
825
|
+
sr;
|
|
826
|
+
yieldEngine;
|
|
827
|
+
market;
|
|
828
|
+
vault;
|
|
829
|
+
decimals;
|
|
830
|
+
async getPortfolio(input) {
|
|
831
|
+
const [balances, rate, claimableYield, interest, lpPosition, fixedReceipts, trustline] = await Promise.all([
|
|
832
|
+
this.tokens.getBalances({ owner: input.owner }),
|
|
833
|
+
this.sr.getExchangeRate(),
|
|
834
|
+
this.yieldEngine.getClaimableInterest({ owner: input.owner }),
|
|
835
|
+
this.yieldEngine.getInterest({ owner: input.owner }),
|
|
836
|
+
this.market.getLpPosition({ owner: input.owner }),
|
|
837
|
+
this.vault.getOwnerReceipts({ owner: input.owner, maxScan: input.maxReceiptScan }),
|
|
838
|
+
this.tokens.getPtTrustline({ owner: input.owner })
|
|
839
|
+
]);
|
|
840
|
+
return {
|
|
841
|
+
owner: input.owner,
|
|
842
|
+
balances,
|
|
843
|
+
srAsUsdc: tokenAmount(this.sr.srToUnderlying(balances.sr.raw, rate), "USDC", this.decimals),
|
|
844
|
+
claimableYield,
|
|
845
|
+
claimableYieldAsUsdc: tokenAmount(this.sr.srToUnderlying(claimableYield.raw, rate), "USDC", this.decimals),
|
|
846
|
+
interest,
|
|
847
|
+
lpPosition,
|
|
848
|
+
fixedReceipts,
|
|
849
|
+
hasPtTrustline: trustline.ready
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// src/sr-protocol.ts
|
|
855
|
+
var SrProtocolClient = class {
|
|
856
|
+
constructor(sr, strategy, yieldEngine, market, vault) {
|
|
857
|
+
this.sr = sr;
|
|
858
|
+
this.strategy = strategy;
|
|
859
|
+
this.yieldEngine = yieldEngine;
|
|
860
|
+
this.market = market;
|
|
861
|
+
this.vault = vault;
|
|
862
|
+
}
|
|
863
|
+
sr;
|
|
864
|
+
strategy;
|
|
865
|
+
yieldEngine;
|
|
866
|
+
market;
|
|
867
|
+
vault;
|
|
868
|
+
async getHealth() {
|
|
869
|
+
const [sr, strategy, yieldEngine, market, vault] = await Promise.all([
|
|
870
|
+
this.sr.getState(),
|
|
871
|
+
this.strategy.getState(),
|
|
872
|
+
this.yieldEngine.getState(),
|
|
873
|
+
this.market.getStats(),
|
|
874
|
+
this.vault.getStats()
|
|
875
|
+
]);
|
|
876
|
+
return { sr, strategy, yieldEngine, market, vault };
|
|
877
|
+
}
|
|
878
|
+
getSolvency() {
|
|
879
|
+
return this.yieldEngine.getSolvency();
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// src/sr-router.ts
|
|
884
|
+
var DEFAULT_ROUTER_SLIPPAGE_BPS = 100;
|
|
885
|
+
var assertSlippageBps = (bps) => {
|
|
886
|
+
if (!Number.isInteger(bps) || bps < 0 || bps > 1e4) {
|
|
887
|
+
throw new SpieldValidationError("slippageBps must be an integer between 0 and 10000.");
|
|
888
|
+
}
|
|
889
|
+
return bps;
|
|
890
|
+
};
|
|
891
|
+
var slippageFloor = (quote, bps) => {
|
|
892
|
+
assertSlippageBps(bps);
|
|
893
|
+
return quote - quote * BigInt(bps) / 10000n;
|
|
894
|
+
};
|
|
895
|
+
var SrRouterClient = class {
|
|
896
|
+
constructor(network, soroban, signer) {
|
|
897
|
+
this.network = network;
|
|
898
|
+
this.soroban = soroban;
|
|
899
|
+
this.signer = signer;
|
|
900
|
+
}
|
|
901
|
+
network;
|
|
902
|
+
soroban;
|
|
903
|
+
signer;
|
|
904
|
+
get contract() {
|
|
905
|
+
return this.network.contracts.router;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* The basis for `minOut`: what the REAL entry point would pay, falling back to the quote.
|
|
909
|
+
*
|
|
910
|
+
* `FINAL_CHECK.md` V2-01 — the market synchronizes SR on every value-moving call, but `quote_*`
|
|
911
|
+
* is a view and cannot. A quote therefore lags, and for a sale it reports MORE than the trade
|
|
912
|
+
* will pay; sizing `minOut` from it can revert the trade on its own slippage bound.
|
|
913
|
+
*
|
|
914
|
+
* Simulation runs the same synchronized code path the submission runs, so its answer is the one
|
|
915
|
+
* that executes. It legitimately returns `null` (unaffordable size, unfillable route, older
|
|
916
|
+
* deployment) — in which case the quote is used exactly as before.
|
|
917
|
+
*/
|
|
918
|
+
async executableBasis(owner, method, args, quoted) {
|
|
919
|
+
if (typeof this.soroban.simulateAs !== "function") return quoted;
|
|
920
|
+
const simulated = await this.soroban.simulateAs(owner, this.contract, method, args);
|
|
921
|
+
return simulated !== null && simulated > 0n ? simulated : quoted;
|
|
922
|
+
}
|
|
923
|
+
async quote(method, amount, symbol) {
|
|
924
|
+
const raw = await this.soroban.read(this.contract, method, [i128(amount)]);
|
|
925
|
+
return tokenAmount(toBig(raw), symbol, this.network.decimals);
|
|
926
|
+
}
|
|
927
|
+
quoteBuyPt(input) {
|
|
928
|
+
return this.quote(
|
|
929
|
+
"quote_buy_pt_with_usdc",
|
|
930
|
+
assertPositiveAmount(input.usdcIn, this.network.decimals),
|
|
931
|
+
"PT"
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
quoteBuyYt(input) {
|
|
935
|
+
return this.quote(
|
|
936
|
+
"quote_buy_yt_with_usdc",
|
|
937
|
+
assertPositiveAmount(input.ytOut, this.network.decimals),
|
|
938
|
+
"USDC"
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
quoteSellPt(input) {
|
|
942
|
+
return this.quote(
|
|
943
|
+
"quote_sell_pt_for_usdc",
|
|
944
|
+
assertPositiveAmount(input.ptIn, this.network.decimals),
|
|
945
|
+
"USDC"
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
quoteSellYt(input) {
|
|
949
|
+
return this.quote(
|
|
950
|
+
"quote_sell_yt_for_usdc",
|
|
951
|
+
assertPositiveAmount(input.ytIn, this.network.decimals),
|
|
952
|
+
"USDC"
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
quoteRedeemPy(input) {
|
|
956
|
+
return this.quote(
|
|
957
|
+
"quote_redeem_py_for_usdc",
|
|
958
|
+
assertPositiveAmount(input.amount, this.network.decimals),
|
|
959
|
+
"USDC"
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
async quoteClaimYield(input) {
|
|
963
|
+
const raw = await this.soroban.read(this.contract, "quote_claim_yield", [addr(input.owner)]);
|
|
964
|
+
return tokenAmount(toBig(raw), "USDC", this.network.decimals);
|
|
965
|
+
}
|
|
966
|
+
async buyPt(input) {
|
|
967
|
+
const signer = input.signer ?? this.signer;
|
|
968
|
+
const owner = await signerAddress(signer);
|
|
969
|
+
const usdcIn = assertPositiveAmount(input.usdcIn, this.network.decimals);
|
|
970
|
+
assertSlippageBps(input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS);
|
|
971
|
+
const minOut = input.minPtOut === void 0 ? slippageFloor(
|
|
972
|
+
await this.executableBasis(
|
|
973
|
+
owner,
|
|
974
|
+
"buy_pt_with_usdc",
|
|
975
|
+
[addr(owner), i128(usdcIn), i128(0n), u32(input.deadlineLedger ?? 0)],
|
|
976
|
+
(await this.quoteBuyPt({ usdcIn })).raw
|
|
977
|
+
),
|
|
978
|
+
input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS
|
|
979
|
+
) : assertNonNegativeAmount(input.minPtOut, this.network.decimals);
|
|
980
|
+
if (minOut <= 0n) throw new Error("No PT quote is available for that size.");
|
|
981
|
+
return this.soroban.write(this.contract, "buy_pt_with_usdc", [
|
|
982
|
+
addr(owner),
|
|
983
|
+
i128(usdcIn),
|
|
984
|
+
i128(minOut),
|
|
985
|
+
u32(input.deadlineLedger ?? 0)
|
|
986
|
+
], signer);
|
|
987
|
+
}
|
|
988
|
+
async sellPt(input) {
|
|
989
|
+
const signer = input.signer ?? this.signer;
|
|
990
|
+
const owner = await signerAddress(signer);
|
|
991
|
+
const ptIn = assertPositiveAmount(input.ptIn, this.network.decimals);
|
|
992
|
+
assertSlippageBps(input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS);
|
|
993
|
+
const minOut = input.minUsdcOut === void 0 ? slippageFloor(
|
|
994
|
+
await this.executableBasis(
|
|
995
|
+
owner,
|
|
996
|
+
"sell_pt_for_usdc",
|
|
997
|
+
[addr(owner), i128(ptIn), i128(0n), u32(input.deadlineLedger ?? 0)],
|
|
998
|
+
(await this.quoteSellPt({ ptIn })).raw
|
|
999
|
+
),
|
|
1000
|
+
input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS
|
|
1001
|
+
) : assertNonNegativeAmount(input.minUsdcOut, this.network.decimals);
|
|
1002
|
+
return this.soroban.write(this.contract, "sell_pt_for_usdc", [
|
|
1003
|
+
addr(owner),
|
|
1004
|
+
i128(ptIn),
|
|
1005
|
+
i128(minOut),
|
|
1006
|
+
u32(input.deadlineLedger ?? 0)
|
|
1007
|
+
], signer);
|
|
1008
|
+
}
|
|
1009
|
+
async redeemPy(input) {
|
|
1010
|
+
const signer = input.signer ?? this.signer;
|
|
1011
|
+
const owner = await signerAddress(signer);
|
|
1012
|
+
const amount = assertPositiveAmount(input.amount, this.network.decimals);
|
|
1013
|
+
assertSlippageBps(input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS);
|
|
1014
|
+
const minOut = input.minUsdcOut === void 0 ? slippageFloor(
|
|
1015
|
+
await this.executableBasis(
|
|
1016
|
+
owner,
|
|
1017
|
+
"redeem_py_for_usdc",
|
|
1018
|
+
[addr(owner), i128(amount), i128(0n)],
|
|
1019
|
+
(await this.quoteRedeemPy({ amount })).raw
|
|
1020
|
+
),
|
|
1021
|
+
input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS
|
|
1022
|
+
) : assertNonNegativeAmount(input.minUsdcOut, this.network.decimals);
|
|
1023
|
+
return this.soroban.write(this.contract, "redeem_py_for_usdc", [addr(owner), i128(amount), i128(minOut)], signer);
|
|
1024
|
+
}
|
|
1025
|
+
async claimYield(input = {}) {
|
|
1026
|
+
const signer = input.signer ?? this.signer;
|
|
1027
|
+
const owner = await signerAddress(signer);
|
|
1028
|
+
const minOut = input.minUsdcOut === void 0 ? slippageFloor((await this.quoteClaimYield({ owner })).raw, input.slippageBps ?? DEFAULT_ROUTER_SLIPPAGE_BPS) : assertNonNegativeAmount(input.minUsdcOut, this.network.decimals);
|
|
1029
|
+
return this.soroban.write(this.contract, "claim_yield_to_usdc", [addr(owner), i128(minOut)], signer);
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
// src/sr-tokens.ts
|
|
1034
|
+
var SrTokensClient = class {
|
|
1035
|
+
constructor(network, soroban, horizon, signer) {
|
|
1036
|
+
this.network = network;
|
|
1037
|
+
this.soroban = soroban;
|
|
1038
|
+
this.horizon = horizon;
|
|
1039
|
+
this.signer = signer;
|
|
1040
|
+
}
|
|
1041
|
+
network;
|
|
1042
|
+
soroban;
|
|
1043
|
+
horizon;
|
|
1044
|
+
signer;
|
|
1045
|
+
async getBalance(input) {
|
|
1046
|
+
const contracts = this.network.contracts;
|
|
1047
|
+
const contract = {
|
|
1048
|
+
USDC: contracts.usdc,
|
|
1049
|
+
SR: contracts.sr,
|
|
1050
|
+
PT: contracts.pt,
|
|
1051
|
+
// The yield engine itself implements SEP-41 and is the YT token.
|
|
1052
|
+
YT: contracts.yieldEngine
|
|
1053
|
+
}[input.token];
|
|
1054
|
+
try {
|
|
1055
|
+
const raw = await this.soroban.read(contract, "balance", [addr(input.owner)]);
|
|
1056
|
+
return tokenAmount(toBig(raw), input.token, this.network.decimals);
|
|
1057
|
+
} catch {
|
|
1058
|
+
return tokenAmount(0n, input.token, this.network.decimals);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
async getBalances(input) {
|
|
1062
|
+
const [usdc, sr, pt, yt] = await Promise.all([
|
|
1063
|
+
this.getBalance({ token: "USDC", owner: input.owner }),
|
|
1064
|
+
this.getBalance({ token: "SR", owner: input.owner }),
|
|
1065
|
+
this.getBalance({ token: "PT", owner: input.owner }),
|
|
1066
|
+
this.getBalance({ token: "YT", owner: input.owner })
|
|
1067
|
+
]);
|
|
1068
|
+
return { usdc, sr, pt, yt };
|
|
1069
|
+
}
|
|
1070
|
+
getPtTrustline(input) {
|
|
1071
|
+
return this.horizon.getPtTrustline(input.owner);
|
|
1072
|
+
}
|
|
1073
|
+
/** Set up the sole v2 trustline. YT is a contract token and must not get a trustline. */
|
|
1074
|
+
async ensurePtTrustline(input = {}) {
|
|
1075
|
+
const signer = input.signer ?? this.signer;
|
|
1076
|
+
const owner = input.owner ?? await signerAddress(signer);
|
|
1077
|
+
return this.horizon.setupPtTrustline(owner, signer);
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
// src/sr-vault.ts
|
|
1082
|
+
var SrVaultClient = class {
|
|
1083
|
+
constructor(network, soroban, signer) {
|
|
1084
|
+
this.network = network;
|
|
1085
|
+
this.soroban = soroban;
|
|
1086
|
+
this.signer = signer;
|
|
1087
|
+
}
|
|
1088
|
+
network;
|
|
1089
|
+
soroban;
|
|
1090
|
+
signer;
|
|
1091
|
+
get contract() {
|
|
1092
|
+
return this.network.contracts.vault;
|
|
1093
|
+
}
|
|
1094
|
+
async quoteFixedRate(input) {
|
|
1095
|
+
const amount = assertPositiveAmount(input.amount, this.network.decimals);
|
|
1096
|
+
const tuple = await this.soroban.read(this.contract, "quote", [i128(amount)]);
|
|
1097
|
+
const [payoutRaw, couponRaw, rateRaw] = tuple ?? [];
|
|
1098
|
+
const rateBps = toNum(rateRaw);
|
|
1099
|
+
return {
|
|
1100
|
+
input: tokenAmount(amount, "USDC", this.network.decimals),
|
|
1101
|
+
payout: tokenAmount(toBig(payoutRaw), "USDC", this.network.decimals),
|
|
1102
|
+
coupon: tokenAmount(toBig(couponRaw), "USDC", this.network.decimals),
|
|
1103
|
+
rateBps,
|
|
1104
|
+
ratePct: rateBps / 100
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
async getStats() {
|
|
1108
|
+
const [raw, paused] = await Promise.all([
|
|
1109
|
+
this.soroban.read(this.contract, "stats"),
|
|
1110
|
+
this.soroban.read(this.contract, "is_paused")
|
|
1111
|
+
]);
|
|
1112
|
+
const maturity = toNum(raw.maturity);
|
|
1113
|
+
const rateBps = toNum(raw.rate_bps);
|
|
1114
|
+
return {
|
|
1115
|
+
ptInventory: tokenAmount(toBig(raw.pt_inventory), "PT", this.network.decimals),
|
|
1116
|
+
ytInventory: tokenAmount(toBig(raw.yt_inventory), "YT", this.network.decimals),
|
|
1117
|
+
totalLiability: tokenAmount(toBig(raw.total_liability), "USDC", this.network.decimals),
|
|
1118
|
+
totalCollected: tokenAmount(toBig(raw.total_collected), "USDC", this.network.decimals),
|
|
1119
|
+
couponCapacity: tokenAmount(toBig(raw.coupon_capacity), "PT", this.network.decimals),
|
|
1120
|
+
rateBps,
|
|
1121
|
+
ratePct: rateBps / 100,
|
|
1122
|
+
maturity,
|
|
1123
|
+
maturityDate: new Date(maturity * 1e3),
|
|
1124
|
+
openReceipts: toNum(raw.open_receipts),
|
|
1125
|
+
paused
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
async getReceipt(input) {
|
|
1129
|
+
if (!Number.isSafeInteger(input.receiptId) || input.receiptId < 0) {
|
|
1130
|
+
throw new SpieldValidationError("receiptId must be a non-negative safe integer.");
|
|
1131
|
+
}
|
|
1132
|
+
try {
|
|
1133
|
+
const raw = await this.soroban.read(this.contract, "get_receipt", [u64(input.receiptId)]);
|
|
1134
|
+
const maturity = toNum(raw.maturity);
|
|
1135
|
+
const rateBps = toNum(raw.rate_bps);
|
|
1136
|
+
const payout = toBig(raw.payout);
|
|
1137
|
+
const collected = toBig(raw.collected);
|
|
1138
|
+
return {
|
|
1139
|
+
receiptId: input.receiptId,
|
|
1140
|
+
owner: String(raw.owner),
|
|
1141
|
+
principal: tokenAmount(toBig(raw.principal), "USDC", this.network.decimals),
|
|
1142
|
+
payout: tokenAmount(payout, "USDC", this.network.decimals),
|
|
1143
|
+
rateBps,
|
|
1144
|
+
ratePct: rateBps / 100,
|
|
1145
|
+
maturity,
|
|
1146
|
+
maturityDate: new Date(maturity * 1e3),
|
|
1147
|
+
open: Boolean(raw.open),
|
|
1148
|
+
collected: tokenAmount(collected, "USDC", this.network.decimals),
|
|
1149
|
+
remaining: tokenAmount(payout > collected ? payout - collected : 0n, "USDC", this.network.decimals)
|
|
1150
|
+
};
|
|
1151
|
+
} catch {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async getOwnerReceipts(input) {
|
|
1156
|
+
const maxScan = input.maxScan ?? 64;
|
|
1157
|
+
if (!Number.isSafeInteger(maxScan) || maxScan < 0) {
|
|
1158
|
+
throw new SpieldValidationError("maxScan must be a non-negative safe integer.");
|
|
1159
|
+
}
|
|
1160
|
+
const receipts = [];
|
|
1161
|
+
let misses = 0;
|
|
1162
|
+
for (let id = 0; id < maxScan && misses < 5; id += 1) {
|
|
1163
|
+
const receipt = await this.getReceipt({ receiptId: id });
|
|
1164
|
+
if (!receipt) {
|
|
1165
|
+
misses += 1;
|
|
1166
|
+
} else {
|
|
1167
|
+
misses = 0;
|
|
1168
|
+
if (receipt.owner === input.owner && receipt.open) receipts.push(receipt);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return receipts;
|
|
1172
|
+
}
|
|
1173
|
+
async deposit(input) {
|
|
1174
|
+
const signer = input.signer ?? this.signer;
|
|
1175
|
+
const owner = await signerAddress(signer);
|
|
1176
|
+
return this.soroban.write(this.contract, "deposit", [
|
|
1177
|
+
addr(owner),
|
|
1178
|
+
i128(assertPositiveAmount(input.amount, this.network.decimals))
|
|
1179
|
+
], signer);
|
|
1180
|
+
}
|
|
1181
|
+
redeemReceipt(input) {
|
|
1182
|
+
if (!Number.isSafeInteger(input.receiptId) || input.receiptId < 0) {
|
|
1183
|
+
throw new SpieldValidationError("receiptId must be a non-negative safe integer.");
|
|
1184
|
+
}
|
|
1185
|
+
return this.soroban.write(this.contract, "redeem", [u64(input.receiptId)], input.signer ?? this.signer);
|
|
1186
|
+
}
|
|
1187
|
+
/** Remaining USDC owed before a receipt can close. Falls back to receipt data on the old ABI. */
|
|
1188
|
+
async getRedeemRemaining(input) {
|
|
1189
|
+
this.assertReceiptId(input.receiptId);
|
|
1190
|
+
try {
|
|
1191
|
+
const raw = await this.soroban.read(this.contract, "redeem_remaining", [u64(input.receiptId)]);
|
|
1192
|
+
return tokenAmount(toBig(raw), "USDC", this.network.decimals);
|
|
1193
|
+
} catch {
|
|
1194
|
+
const receipt = await this.getReceipt(input);
|
|
1195
|
+
return tokenAmount(receipt?.remaining.raw ?? 0n, "USDC", this.network.decimals);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
/** Read surplus SR, YT and USDC that the latest vault ABI can release after expiry. */
|
|
1199
|
+
async getSurplus() {
|
|
1200
|
+
try {
|
|
1201
|
+
const tuple = await this.soroban.read(this.contract, "surplus");
|
|
1202
|
+
const [sr, yt, usdc] = tuple ?? [];
|
|
1203
|
+
return {
|
|
1204
|
+
sr: tokenAmount(toBig(sr), "SR", this.network.decimals),
|
|
1205
|
+
yt: tokenAmount(toBig(yt), "YT", this.network.decimals),
|
|
1206
|
+
usdc: tokenAmount(toBig(usdc), "USDC", this.network.decimals)
|
|
1207
|
+
};
|
|
1208
|
+
} catch {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
/** Permissionless keep-alive for a long-dated vault receipt. */
|
|
1213
|
+
bumpReceipt(input) {
|
|
1214
|
+
this.assertReceiptId(input.receiptId);
|
|
1215
|
+
return this.soroban.write(this.contract, "bump_receipt", [u64(input.receiptId)], input.signer ?? this.signer);
|
|
1216
|
+
}
|
|
1217
|
+
/** Permissionless claim and reinvestment of the vault's YT yield. */
|
|
1218
|
+
harvest(input = {}) {
|
|
1219
|
+
return this.soroban.write(this.contract, "harvest", [], input.signer ?? this.signer);
|
|
1220
|
+
}
|
|
1221
|
+
assertReceiptId(receiptId) {
|
|
1222
|
+
if (!Number.isSafeInteger(receiptId) || receiptId < 0) {
|
|
1223
|
+
throw new SpieldValidationError("receiptId must be a non-negative safe integer.");
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
// src/yield-engine.ts
|
|
1229
|
+
var YieldEngineClient = class {
|
|
1230
|
+
constructor(network, soroban, signer) {
|
|
1231
|
+
this.network = network;
|
|
1232
|
+
this.soroban = soroban;
|
|
1233
|
+
this.signer = signer;
|
|
1234
|
+
}
|
|
1235
|
+
network;
|
|
1236
|
+
soroban;
|
|
1237
|
+
signer;
|
|
1238
|
+
get contract() {
|
|
1239
|
+
return this.network.contracts.yieldEngine;
|
|
1240
|
+
}
|
|
1241
|
+
async getYtBalance(input) {
|
|
1242
|
+
const raw = await this.soroban.read(this.contract, "balance", [addr(input.owner)]);
|
|
1243
|
+
return tokenAmount(toBig(raw), "YT", this.network.decimals);
|
|
1244
|
+
}
|
|
1245
|
+
async getClaimableInterest(input) {
|
|
1246
|
+
const raw = await this.soroban.read(this.contract, "claimable_interest", [addr(input.owner)]);
|
|
1247
|
+
return tokenAmount(toBig(raw), "SR", this.network.decimals);
|
|
1248
|
+
}
|
|
1249
|
+
async getInterest(input) {
|
|
1250
|
+
const raw = await this.soroban.read(this.contract, "interest_of", [addr(input.owner)]);
|
|
1251
|
+
return {
|
|
1252
|
+
index: toBig(raw.index),
|
|
1253
|
+
accrued: tokenAmount(toBig(raw.accrued), "SR", this.network.decimals),
|
|
1254
|
+
withdrawn: tokenAmount(toBig(raw.withdrawn), "SR", this.network.decimals)
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
async getSolvency() {
|
|
1258
|
+
const tuple = await this.soroban.read(this.contract, "solvency");
|
|
1259
|
+
const [heldRaw, neededRaw, surplusRaw] = tuple ?? [];
|
|
1260
|
+
const held = toBig(heldRaw);
|
|
1261
|
+
const needed = toBig(neededRaw);
|
|
1262
|
+
return {
|
|
1263
|
+
held: tokenAmount(held, "SR", this.network.decimals),
|
|
1264
|
+
needed: tokenAmount(needed, "SR", this.network.decimals),
|
|
1265
|
+
surplus: tokenAmount(toBig(surplusRaw), "SR", this.network.decimals),
|
|
1266
|
+
solvent: held >= needed
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
async getState() {
|
|
1270
|
+
const [solvency, totalPyRaw, totalAccruedRaw, indexRaw, feeRaw, expiryRaw, expired, paused] = await Promise.all([
|
|
1271
|
+
this.getSolvency(),
|
|
1272
|
+
this.soroban.read(this.contract, "total_py"),
|
|
1273
|
+
this.soroban.read(this.contract, "total_accrued"),
|
|
1274
|
+
this.soroban.read(this.contract, "py_index"),
|
|
1275
|
+
this.soroban.read(this.contract, "yield_fee_bps"),
|
|
1276
|
+
this.soroban.read(this.contract, "expiry"),
|
|
1277
|
+
this.soroban.read(this.contract, "is_expired"),
|
|
1278
|
+
this.soroban.read(this.contract, "is_paused")
|
|
1279
|
+
]);
|
|
1280
|
+
const expiry = toNum(expiryRaw);
|
|
1281
|
+
const yieldFeeBps = toNum(feeRaw);
|
|
1282
|
+
return {
|
|
1283
|
+
...solvency,
|
|
1284
|
+
totalPy: tokenAmount(toBig(totalPyRaw), "PT/YT", this.network.decimals),
|
|
1285
|
+
totalAccrued: tokenAmount(toBig(totalAccruedRaw), "SR", this.network.decimals),
|
|
1286
|
+
pyIndex: toBig(indexRaw),
|
|
1287
|
+
yieldFeeBps,
|
|
1288
|
+
yieldFeePct: yieldFeeBps / 100,
|
|
1289
|
+
expiry,
|
|
1290
|
+
expiryDate: new Date(expiry * 1e3),
|
|
1291
|
+
expired,
|
|
1292
|
+
paused
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
async mintPy(input) {
|
|
1296
|
+
const signer = input.signer ?? this.signer;
|
|
1297
|
+
const from = await signerAddress(signer);
|
|
1298
|
+
return this.soroban.write(this.contract, "mint_py", [
|
|
1299
|
+
addr(from),
|
|
1300
|
+
addr(input.receiver ?? from),
|
|
1301
|
+
i128(assertPositiveAmount(input.srIn, this.network.decimals))
|
|
1302
|
+
], signer);
|
|
1303
|
+
}
|
|
1304
|
+
async redeemPy(input) {
|
|
1305
|
+
const signer = input.signer ?? this.signer;
|
|
1306
|
+
const from = await signerAddress(signer);
|
|
1307
|
+
return this.soroban.write(this.contract, "redeem_py", [
|
|
1308
|
+
addr(from),
|
|
1309
|
+
addr(input.receiver ?? from),
|
|
1310
|
+
i128(assertPositiveAmount(input.amount, this.network.decimals))
|
|
1311
|
+
], signer);
|
|
1312
|
+
}
|
|
1313
|
+
async claimYield(input = {}) {
|
|
1314
|
+
const signer = input.signer ?? this.signer;
|
|
1315
|
+
const owner = await signerAddress(signer);
|
|
1316
|
+
return this.soroban.write(this.contract, "redeem_due_interest", [addr(owner)], signer);
|
|
1317
|
+
}
|
|
1318
|
+
async transferYt(input) {
|
|
1319
|
+
const signer = input.signer ?? this.signer;
|
|
1320
|
+
const from = await signerAddress(signer);
|
|
1321
|
+
return this.soroban.write(this.contract, "transfer", [
|
|
1322
|
+
addr(from),
|
|
1323
|
+
addr(input.to),
|
|
1324
|
+
i128(assertPositiveAmount(input.amount, this.network.decimals))
|
|
1325
|
+
], signer);
|
|
1326
|
+
}
|
|
1327
|
+
/** Permissionless expiry-index stamp; only succeeds at or after expiry. */
|
|
1328
|
+
stampExpiryIndex(input = {}) {
|
|
1329
|
+
return this.soroban.write(this.contract, "stamp_expiry_index", [], input.signer ?? this.signer);
|
|
1330
|
+
}
|
|
1331
|
+
/** Keep both a holder's YT balance and interest entries alive. Permissionless on the latest ABI. */
|
|
1332
|
+
async bumpHolder(input = {}) {
|
|
1333
|
+
const signer = input.signer ?? this.signer;
|
|
1334
|
+
const signerOwner = await signerAddress(signer);
|
|
1335
|
+
return this.soroban.write(this.contract, "bump_holder", [addr(input.owner ?? signerOwner)], signer);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
// src/client.ts
|
|
1340
|
+
var SpieldClient = class {
|
|
1341
|
+
network;
|
|
1342
|
+
/** Wrap USDC into yield-bearing SR shares, and unwrap fully or partially. */
|
|
1343
|
+
sr;
|
|
1344
|
+
/** Blend rate guard, position value, and currently withdrawable liquidity. */
|
|
1345
|
+
strategy;
|
|
1346
|
+
/** Mint/redeem PT + YT, read and claim interest, transfer YT. */
|
|
1347
|
+
yield;
|
|
1348
|
+
/** PT/SR quotes, PT and YT trades, liquidity. */
|
|
1349
|
+
market;
|
|
1350
|
+
/** Fixed-rate quotes, receipts, deposit, redeem, harvest. */
|
|
1351
|
+
vault;
|
|
1352
|
+
/** One-transaction USDC routes for PT, redemption, and yield claims. */
|
|
1353
|
+
router;
|
|
1354
|
+
/** USDC/SR/PT/YT balances and the single required PT trustline. */
|
|
1355
|
+
tokens;
|
|
1356
|
+
/** Wallet-wide balances, interest, LP position, and vault receipts. */
|
|
1357
|
+
portfolio;
|
|
1358
|
+
/** Cross-contract health and yield-engine solvency. */
|
|
1359
|
+
protocol;
|
|
1360
|
+
signer;
|
|
1361
|
+
constructor(options = {}) {
|
|
1362
|
+
const network = options.network ?? testnet();
|
|
1363
|
+
this.network = network;
|
|
1364
|
+
this.signer = options.signer;
|
|
1365
|
+
const soroban = new SorobanClient({
|
|
1366
|
+
network,
|
|
1367
|
+
signer: options.signer,
|
|
1368
|
+
simulationSource: options.simulationSource ?? DEFAULT_SIMULATION_SOURCE,
|
|
1369
|
+
confirmationTimeoutMs: options.confirmationTimeoutMs,
|
|
1370
|
+
confirmationPollIntervalMs: options.confirmationPollIntervalMs
|
|
1371
|
+
});
|
|
1372
|
+
const horizon = new HorizonClient(network);
|
|
1373
|
+
this.sr = new StandardizedReturnClient(network, soroban, options.signer);
|
|
1374
|
+
this.strategy = new BlendStrategyClient(network, soroban);
|
|
1375
|
+
this.yield = new YieldEngineClient(network, soroban, options.signer);
|
|
1376
|
+
this.market = new SrMarketClient(network, soroban, options.signer);
|
|
1377
|
+
this.vault = new SrVaultClient(network, soroban, options.signer);
|
|
1378
|
+
this.router = new SrRouterClient(network, soroban, options.signer);
|
|
1379
|
+
this.tokens = new SrTokensClient(network, soroban, horizon, options.signer);
|
|
1380
|
+
this.portfolio = new SrPortfolioClient(
|
|
1381
|
+
this.tokens,
|
|
1382
|
+
this.sr,
|
|
1383
|
+
this.yield,
|
|
1384
|
+
this.market,
|
|
1385
|
+
this.vault,
|
|
1386
|
+
network.decimals
|
|
1387
|
+
);
|
|
1388
|
+
this.protocol = new SrProtocolClient(this.sr, this.strategy, this.yield, this.market, this.vault);
|
|
1389
|
+
}
|
|
1390
|
+
/** All-in USDC cost for an exact YT face amount, using the deployed two-step route. */
|
|
1391
|
+
async quoteBuyYtFromUsdc(input) {
|
|
1392
|
+
const quote = await this.market.quoteBuyYt(input);
|
|
1393
|
+
if (!quote.available) return tokenAmount(0n, "USDC", this.network.decimals);
|
|
1394
|
+
const rate = await this.sr.getExchangeRate();
|
|
1395
|
+
return tokenAmount(this.sr.srToUnderlying(quote.output.raw, rate), "USDC", this.network.decimals);
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Live-safe YT purchase: wrap only the missing SR, then buy YT. The one-call router version exceeds
|
|
1399
|
+
* the deployed Blend pool's Soroban budget, so this deliberately uses up to two signatures.
|
|
1400
|
+
*/
|
|
1401
|
+
async buyYtFromUsdc(input) {
|
|
1402
|
+
const signer = input.signer ?? this.signer;
|
|
1403
|
+
const ytOut = assertPositiveAmount(input.ytOut, this.network.decimals);
|
|
1404
|
+
const quote = await this.market.quoteBuyYt({ ytOut });
|
|
1405
|
+
if (!quote.available) throw new Error("No YT quote is available for that size.");
|
|
1406
|
+
const budget = quote.output.raw + quote.output.raw * YT_MAX_IN_PAD_BPS / 10000n + 1n;
|
|
1407
|
+
const owner = await signer?.getAddress();
|
|
1408
|
+
if (!owner) throw new Error("A signer is required for this transaction.");
|
|
1409
|
+
const held = (await this.tokens.getBalance({ token: "SR", owner })).raw;
|
|
1410
|
+
let first;
|
|
1411
|
+
const needsWrap = held < budget;
|
|
1412
|
+
const total = needsWrap ? 2 : 1;
|
|
1413
|
+
if (needsWrap) {
|
|
1414
|
+
const rate = await this.sr.getExchangeRate();
|
|
1415
|
+
const missing = budget - held;
|
|
1416
|
+
const usdc = (missing * rate + SCALAR_12 - 1n) / SCALAR_12 + 1n;
|
|
1417
|
+
input.onProgress?.("wrap", 1, total);
|
|
1418
|
+
first = await this.sr.deposit({ amount: usdc, signer });
|
|
1419
|
+
}
|
|
1420
|
+
input.onProgress?.("buy", total, total);
|
|
1421
|
+
const second = await this.market.buyYt({ ytOut, maxSrIn: budget, signer });
|
|
1422
|
+
return { first, second };
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Live-safe YT exit: sell to SR, then unwrap the actual proceeds. The combined router route no
|
|
1426
|
+
* longer fits the deployed Blend-backed stack after the SR partial-exit/cap upgrade.
|
|
1427
|
+
*/
|
|
1428
|
+
async sellYtToUsdc(input) {
|
|
1429
|
+
const signer = input.signer ?? this.signer;
|
|
1430
|
+
const owner = await signer?.getAddress();
|
|
1431
|
+
if (!owner) throw new Error("A signer is required for this transaction.");
|
|
1432
|
+
const quote = await this.market.quoteSellYt(input);
|
|
1433
|
+
if (!quote.available) throw new Error("No YT sale quote is available for that size.");
|
|
1434
|
+
const before = (await this.tokens.getBalance({ token: "SR", owner })).raw;
|
|
1435
|
+
input.onProgress?.("sell", 1, 2);
|
|
1436
|
+
const first = await this.market.sellYt({ ytIn: input.ytIn, minSrOut: quote.output.raw * 99n / 100n, signer });
|
|
1437
|
+
const after = (await this.tokens.getBalance({ token: "SR", owner })).raw;
|
|
1438
|
+
const proceeds = after - before;
|
|
1439
|
+
if (proceeds <= 0n) throw new Error("The YT sale completed but produced no SR to unwrap.");
|
|
1440
|
+
input.onProgress?.("unwrap", 2, 2);
|
|
1441
|
+
const second = await this.sr.redeem({ shares: proceeds, signer });
|
|
1442
|
+
return { first, second };
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
// src/signer.ts
|
|
1447
|
+
var FreighterSigner = class {
|
|
1448
|
+
constructor(api) {
|
|
1449
|
+
this.api = api;
|
|
1450
|
+
}
|
|
1451
|
+
api;
|
|
1452
|
+
get freighter() {
|
|
1453
|
+
const injected = this.api ?? globalThis.freighterApi;
|
|
1454
|
+
if (!injected) throw new SpieldSignerError("Freighter API is not available.");
|
|
1455
|
+
return injected;
|
|
1456
|
+
}
|
|
1457
|
+
async getAddress() {
|
|
1458
|
+
const getAddress = this.freighter.getAddress;
|
|
1459
|
+
if (!getAddress) throw new SpieldSignerError("Freighter getAddress() is not available.");
|
|
1460
|
+
const result = await getAddress();
|
|
1461
|
+
const address = typeof result === "string" ? result : result.address;
|
|
1462
|
+
if (!address) throw new SpieldSignerError("Freighter did not return an address.");
|
|
1463
|
+
return address;
|
|
1464
|
+
}
|
|
1465
|
+
async signTransaction(xdr2, opts) {
|
|
1466
|
+
const signTransaction = this.freighter.signTransaction;
|
|
1467
|
+
if (!signTransaction) throw new SpieldSignerError("Freighter signTransaction() is not available.");
|
|
1468
|
+
const result = await signTransaction(xdr2, opts);
|
|
1469
|
+
const signedTxXdr = typeof result === "string" ? result : result.signedTxXdr;
|
|
1470
|
+
if (!signedTxXdr) throw new SpieldSignerError("Freighter did not return a signed transaction.");
|
|
1471
|
+
return { signedTxXdr };
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
var ManualSigner = class {
|
|
1475
|
+
constructor(address, sign) {
|
|
1476
|
+
this.address = address;
|
|
1477
|
+
this.sign = sign;
|
|
1478
|
+
}
|
|
1479
|
+
address;
|
|
1480
|
+
sign;
|
|
1481
|
+
async getAddress() {
|
|
1482
|
+
return this.address;
|
|
1483
|
+
}
|
|
1484
|
+
async signTransaction(xdr2, opts) {
|
|
1485
|
+
return this.sign(xdr2, opts);
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1489
|
+
0 && (module.exports = {
|
|
1490
|
+
BlendStrategyClient,
|
|
1491
|
+
DEFAULT_ROUTER_SLIPPAGE_BPS,
|
|
1492
|
+
FreighterSigner,
|
|
1493
|
+
ManualSigner,
|
|
1494
|
+
SCALAR_12,
|
|
1495
|
+
STELLAR_NETWORK_PASSPHRASES,
|
|
1496
|
+
SpieldClient,
|
|
1497
|
+
SpieldConfigurationError,
|
|
1498
|
+
SpieldContractError,
|
|
1499
|
+
SpieldRpcError,
|
|
1500
|
+
SpieldSdkError,
|
|
1501
|
+
SpieldSignerError,
|
|
1502
|
+
SpieldValidationError,
|
|
1503
|
+
SrMarketClient,
|
|
1504
|
+
SrPortfolioClient,
|
|
1505
|
+
SrProtocolClient,
|
|
1506
|
+
SrRouterClient,
|
|
1507
|
+
SrTokensClient,
|
|
1508
|
+
SrVaultClient,
|
|
1509
|
+
StandardizedReturnClient,
|
|
1510
|
+
YT_MAX_IN_PAD_BPS,
|
|
1511
|
+
YieldEngineClient,
|
|
1512
|
+
assertNonNegativeAmount,
|
|
1513
|
+
customNetwork,
|
|
1514
|
+
fromBaseUnits,
|
|
1515
|
+
fromScalar12,
|
|
1516
|
+
testnet,
|
|
1517
|
+
toBaseUnits,
|
|
1518
|
+
tokenAmount
|
|
1519
|
+
});
|
|
1520
|
+
//# sourceMappingURL=index.cjs.map
|