@forevermoney/sdk 0.5.1 → 0.5.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/CHANGELOG.md +19 -0
- package/README.md +65 -4
- package/dist/index.cjs +406 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -28
- package/dist/index.d.ts +88 -28
- package/dist/index.js +410 -201
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,89 +1,5 @@
|
|
|
1
|
-
// src/core/addresses.ts
|
|
2
|
-
import {
|
|
3
|
-
AccountId,
|
|
4
|
-
Blake2256,
|
|
5
|
-
getSs58AddressInfo
|
|
6
|
-
} from "@polkadot-api/substrate-bindings";
|
|
7
|
-
import {
|
|
8
|
-
bytesToHex,
|
|
9
|
-
concat,
|
|
10
|
-
getAddress,
|
|
11
|
-
hexToBytes,
|
|
12
|
-
isAddress,
|
|
13
|
-
stringToBytes,
|
|
14
|
-
zeroAddress
|
|
15
|
-
} from "viem";
|
|
16
|
-
|
|
17
|
-
// src/core/errors.ts
|
|
18
|
-
var ForeverMoneyError = class extends Error {
|
|
19
|
-
code;
|
|
20
|
-
details;
|
|
21
|
-
constructor(code, message, details) {
|
|
22
|
-
super(message);
|
|
23
|
-
this.name = "ForeverMoneyError";
|
|
24
|
-
this.code = code;
|
|
25
|
-
this.details = details === void 0 ? void 0 : Object.freeze({ ...details });
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// src/core/addresses.ts
|
|
30
|
-
var BITTENSOR_SS58_PREFIX = 42;
|
|
31
|
-
function normalizeEvmAddress(value) {
|
|
32
|
-
const candidate = typeof value === "string" && /^0x[0-9A-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
33
|
-
if (!isAddress(candidate) || candidate === zeroAddress) {
|
|
34
|
-
throw new ForeverMoneyError(
|
|
35
|
-
"INVALID_ADDRESS",
|
|
36
|
-
"Expected a non-zero EVM address."
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
return getAddress(candidate);
|
|
40
|
-
}
|
|
41
|
-
function evmToMirrorSS58(evmAddress) {
|
|
42
|
-
return AccountId(BITTENSOR_SS58_PREFIX).dec(
|
|
43
|
-
Blake2256(
|
|
44
|
-
concat([
|
|
45
|
-
stringToBytes("evm:"),
|
|
46
|
-
hexToBytes(normalizeEvmAddress(evmAddress))
|
|
47
|
-
])
|
|
48
|
-
)
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
function decodeBittensorAddress(value) {
|
|
52
|
-
try {
|
|
53
|
-
if (typeof value !== "string" || !value || value.startsWith("0x"))
|
|
54
|
-
throw new Error("Raw hex is not SS58.");
|
|
55
|
-
const info = getSs58AddressInfo(value);
|
|
56
|
-
if (!info.isValid || info.ss58Format !== BITTENSOR_SS58_PREFIX || info.publicKey.length !== 32)
|
|
57
|
-
throw new Error("Invalid Bittensor address.");
|
|
58
|
-
const publicKey = info.publicKey;
|
|
59
|
-
return publicKey;
|
|
60
|
-
} catch {
|
|
61
|
-
throw new ForeverMoneyError(
|
|
62
|
-
"INVALID_SS58",
|
|
63
|
-
"Expected a Bittensor SS58 address with prefix 42."
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function normalizeSS58(value) {
|
|
68
|
-
return AccountId(BITTENSOR_SS58_PREFIX).dec(decodeBittensorAddress(value));
|
|
69
|
-
}
|
|
70
|
-
function isBittensorSS58(value) {
|
|
71
|
-
try {
|
|
72
|
-
decodeBittensorAddress(value);
|
|
73
|
-
return true;
|
|
74
|
-
} catch {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
function ss58ToPublicKey(value) {
|
|
79
|
-
return bytesToHex(decodeBittensorAddress(value));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/core/amounts.ts
|
|
83
|
-
import { maxUint256, formatUnits, parseUnits } from "viem";
|
|
84
|
-
|
|
85
1
|
// src/chains/deployment.ts
|
|
86
|
-
import { getAddress
|
|
2
|
+
import { getAddress } from "viem";
|
|
87
3
|
var BASE_CHAIN_ID = 8453;
|
|
88
4
|
var ROBINHOOD_CHAIN_ID = 4663;
|
|
89
5
|
var SUBTENSOR_CHAIN_ID = 964;
|
|
@@ -102,34 +18,34 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
102
18
|
chainId: BASE_CHAIN_ID,
|
|
103
19
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
104
20
|
contracts: Object.freeze({
|
|
105
|
-
gateway:
|
|
21
|
+
gateway: getAddress("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
106
22
|
// Retired gateways, oldest first; kept only to track transfers sent
|
|
107
23
|
// through them.
|
|
108
24
|
legacyGateways: [
|
|
109
|
-
|
|
25
|
+
getAddress("0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C")
|
|
110
26
|
],
|
|
111
|
-
wrappedTao:
|
|
27
|
+
wrappedTao: getAddress(
|
|
112
28
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
113
29
|
),
|
|
114
|
-
wrappedSn80:
|
|
30
|
+
wrappedSn80: getAddress(
|
|
115
31
|
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
116
32
|
),
|
|
117
|
-
ccipRouter:
|
|
33
|
+
ccipRouter: getAddress(
|
|
118
34
|
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
119
35
|
),
|
|
120
|
-
ccipOffRampFromSubtensor:
|
|
36
|
+
ccipOffRampFromSubtensor: getAddress(
|
|
121
37
|
"0xf09AFe78d3c7d359b334d7cB88995751F7eC5E13"
|
|
122
38
|
),
|
|
123
|
-
vaultFactory:
|
|
39
|
+
vaultFactory: getAddress(
|
|
124
40
|
"0x9b7F3c7aa335D8BF9f91741E541820466735F868"
|
|
125
41
|
),
|
|
126
|
-
vaultManagerImplementation:
|
|
42
|
+
vaultManagerImplementation: getAddress(
|
|
127
43
|
"0x235Ad8654fe3a1619a9892eBCaD9361F416168da"
|
|
128
44
|
),
|
|
129
|
-
batchLiquidityManagerQuery:
|
|
45
|
+
batchLiquidityManagerQuery: getAddress(
|
|
130
46
|
"0xd117176C5E6809b68500995F59589A8c081565A5"
|
|
131
47
|
),
|
|
132
|
-
weth:
|
|
48
|
+
weth: getAddress("0x4200000000000000000000000000000000000006")
|
|
133
49
|
}),
|
|
134
50
|
deploymentBlock: 50098800
|
|
135
51
|
}),
|
|
@@ -139,17 +55,17 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
139
55
|
chainId: ROBINHOOD_CHAIN_ID,
|
|
140
56
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
141
57
|
contracts: Object.freeze({
|
|
142
|
-
gateway:
|
|
58
|
+
gateway: getAddress("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
143
59
|
legacyGateways: [
|
|
144
|
-
|
|
60
|
+
getAddress("0x53Dcc4FE04193e489BE537F722F65317DB1E65d8")
|
|
145
61
|
],
|
|
146
|
-
wrappedTao:
|
|
62
|
+
wrappedTao: getAddress(
|
|
147
63
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
148
64
|
),
|
|
149
|
-
ccipRouter:
|
|
65
|
+
ccipRouter: getAddress(
|
|
150
66
|
"0x06fC836cf9839B1cd891C440A0a45242DA6Ae1c9"
|
|
151
67
|
),
|
|
152
|
-
ccipOffRampFromSubtensor:
|
|
68
|
+
ccipOffRampFromSubtensor: getAddress(
|
|
153
69
|
"0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3"
|
|
154
70
|
)
|
|
155
71
|
})
|
|
@@ -159,32 +75,32 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
159
75
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
160
76
|
contracts: Object.freeze({
|
|
161
77
|
// V5.1 AlphaGateway: multi-validator staked input (tao-bridge 485c898).
|
|
162
|
-
gateway:
|
|
78
|
+
gateway: getAddress("0xd5Fa238aa4177f6c1341491969d9cBeec94EEd69"),
|
|
163
79
|
// V4 and V5 hubs. The spokes still deliver to the V5 hub (their hub
|
|
164
80
|
// pointer is immutable), so it stays here for delivery tracking.
|
|
165
81
|
legacyGateways: [
|
|
166
|
-
|
|
167
|
-
|
|
82
|
+
getAddress("0x998f20Fea90bF7792774dECc7f994716442B1705"),
|
|
83
|
+
getAddress("0xcd0C6d98D0A126B1c113d15b4c28F38321437787")
|
|
168
84
|
],
|
|
169
|
-
alphaVault:
|
|
85
|
+
alphaVault: getAddress(
|
|
170
86
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
171
87
|
),
|
|
172
|
-
wrappedTao:
|
|
88
|
+
wrappedTao: getAddress(
|
|
173
89
|
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
174
90
|
),
|
|
175
|
-
wrappedSn80:
|
|
91
|
+
wrappedSn80: getAddress(
|
|
176
92
|
"0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222"
|
|
177
93
|
),
|
|
178
|
-
ccipRouter:
|
|
94
|
+
ccipRouter: getAddress(
|
|
179
95
|
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
180
96
|
),
|
|
181
|
-
ccipOffRampFromBase:
|
|
97
|
+
ccipOffRampFromBase: getAddress(
|
|
182
98
|
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
183
99
|
),
|
|
184
|
-
ccipOffRampFromRobinhood:
|
|
100
|
+
ccipOffRampFromRobinhood: getAddress(
|
|
185
101
|
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
186
102
|
),
|
|
187
|
-
stakingPrecompile:
|
|
103
|
+
stakingPrecompile: getAddress(
|
|
188
104
|
"0x0000000000000000000000000000000000000805"
|
|
189
105
|
)
|
|
190
106
|
})
|
|
@@ -194,6 +110,21 @@ function getForeverMoneyEvmDeployment(chain = "base") {
|
|
|
194
110
|
return foreverMoneyDeployment[chain];
|
|
195
111
|
}
|
|
196
112
|
|
|
113
|
+
// src/core/amounts.ts
|
|
114
|
+
import { maxUint256, formatUnits, parseUnits } from "viem";
|
|
115
|
+
|
|
116
|
+
// src/core/errors.ts
|
|
117
|
+
var ForeverMoneyError = class extends Error {
|
|
118
|
+
code;
|
|
119
|
+
details;
|
|
120
|
+
constructor(code, message, details) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "ForeverMoneyError";
|
|
123
|
+
this.code = code;
|
|
124
|
+
this.details = details === void 0 ? void 0 : Object.freeze({ ...details });
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
197
128
|
// src/core/amounts.ts
|
|
198
129
|
var BASIS_POINTS = 10000n;
|
|
199
130
|
var NETWORK_FEE_BUFFER_BPS = 200n;
|
|
@@ -340,6 +271,289 @@ function gasLimitWithBuffer(gasLimit) {
|
|
|
340
271
|
return buffered;
|
|
341
272
|
}
|
|
342
273
|
|
|
274
|
+
// src/core/validation.ts
|
|
275
|
+
import { maxUint256 as maxUint2562, zeroHash, isHex } from "viem";
|
|
276
|
+
function assertBigInt(amount, label) {
|
|
277
|
+
if (typeof amount !== "bigint") {
|
|
278
|
+
throw new ForeverMoneyError(
|
|
279
|
+
"INVALID_TRANSACTION_PLAN",
|
|
280
|
+
`${label} must be a bigint.`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function assertPositiveAmount(amount, label) {
|
|
285
|
+
assertBigInt(amount, label);
|
|
286
|
+
if (amount === 0n) {
|
|
287
|
+
throw new ForeverMoneyError(
|
|
288
|
+
"AMOUNT_ZERO",
|
|
289
|
+
`${label} must be greater than zero.`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (amount < 0n) {
|
|
293
|
+
throw new ForeverMoneyError(
|
|
294
|
+
"INVALID_TRANSACTION_PLAN",
|
|
295
|
+
`${label} cannot be negative.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
if (amount > maxUint2562) {
|
|
299
|
+
throw new ForeverMoneyError(
|
|
300
|
+
"INVALID_TRANSACTION_PLAN",
|
|
301
|
+
`${label} exceeds uint256.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function assertNonNegativeAmount(amount, label) {
|
|
306
|
+
assertBigInt(amount, label);
|
|
307
|
+
if (amount < 0n) {
|
|
308
|
+
throw new ForeverMoneyError(
|
|
309
|
+
"INVALID_TRANSACTION_PLAN",
|
|
310
|
+
`${label} cannot be negative.`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (amount > maxUint2562) {
|
|
314
|
+
throw new ForeverMoneyError(
|
|
315
|
+
"INVALID_TRANSACTION_PLAN",
|
|
316
|
+
`${label} exceeds uint256.`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function assertBoolean(value, label) {
|
|
321
|
+
if (typeof value !== "boolean") {
|
|
322
|
+
throw new ForeverMoneyError(
|
|
323
|
+
"INVALID_TRANSACTION_PLAN",
|
|
324
|
+
`${label} must be a boolean.`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function assertArray(value, label) {
|
|
329
|
+
if (!Array.isArray(value)) {
|
|
330
|
+
throw new ForeverMoneyError(
|
|
331
|
+
"INVALID_TRANSACTION_PLAN",
|
|
332
|
+
`${label} must be an array.`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function assertRecord(value, label) {
|
|
337
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
338
|
+
throw new ForeverMoneyError(
|
|
339
|
+
"INVALID_TRANSACTION_PLAN",
|
|
340
|
+
`${label} must be an object.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function normalizeBytes32(value, label) {
|
|
345
|
+
if (!isHex(value, { strict: true }) || value.length !== 66) {
|
|
346
|
+
throw new ForeverMoneyError(
|
|
347
|
+
"INVALID_BYTES32",
|
|
348
|
+
`${label} must be a 32-byte hex value.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
return value.toLowerCase();
|
|
352
|
+
}
|
|
353
|
+
function normalizeOptionalBytes32(value, label) {
|
|
354
|
+
return value === void 0 ? zeroHash : normalizeBytes32(value, label);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/bridge/stake-rounding.ts
|
|
358
|
+
function invalid(message) {
|
|
359
|
+
throw new ForeverMoneyError("INVALID_TRANSACTION_PLAN", message);
|
|
360
|
+
}
|
|
361
|
+
function stakedMinimumOutput(amountWei, sourceCount = 1) {
|
|
362
|
+
assertWholeRao(amountWei);
|
|
363
|
+
if (!Number.isInteger(sourceCount) || sourceCount < 1 || sourceCount > 16)
|
|
364
|
+
invalid("Source count must be between 1 and 16.");
|
|
365
|
+
const rao = amountWei / EVM_WEI_PER_RAO;
|
|
366
|
+
const dust = 2n * BigInt(sourceCount) + 2n;
|
|
367
|
+
return (rao > dust ? rao - dust : rao) * EVM_WEI_PER_RAO;
|
|
368
|
+
}
|
|
369
|
+
function strandedStakeSource(error) {
|
|
370
|
+
const queue = [error];
|
|
371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
372
|
+
while (queue.length && seen.size < 20) {
|
|
373
|
+
const entry = queue.shift();
|
|
374
|
+
if (typeof entry === "string" && /^0x5408a598[0-9a-f]{128}$/i.test(entry))
|
|
375
|
+
return `0x${entry.slice(10, 74)}`.toLowerCase();
|
|
376
|
+
if (entry && typeof entry === "object" && !seen.has(entry)) {
|
|
377
|
+
seen.add(entry);
|
|
378
|
+
for (const key of [
|
|
379
|
+
"data",
|
|
380
|
+
"error",
|
|
381
|
+
"info",
|
|
382
|
+
"cause",
|
|
383
|
+
"details",
|
|
384
|
+
"rpcData"
|
|
385
|
+
]) {
|
|
386
|
+
const nested = entry[key];
|
|
387
|
+
if (nested != null) queue.push(nested);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
async function estimateRoundedStake({
|
|
394
|
+
amountWei,
|
|
395
|
+
minAmountOutWei,
|
|
396
|
+
pulls,
|
|
397
|
+
positions,
|
|
398
|
+
minStakeRao = 0n,
|
|
399
|
+
adjustRounding = true,
|
|
400
|
+
partnerFeeBps = 0,
|
|
401
|
+
quoteAndEstimate,
|
|
402
|
+
isActive = () => true
|
|
403
|
+
}) {
|
|
404
|
+
assertWholeRao(amountWei);
|
|
405
|
+
assertPositiveAmount(minAmountOutWei, "Minimum output");
|
|
406
|
+
if (minAmountOutWei > amountWei)
|
|
407
|
+
invalid("Minimum output cannot exceed input.");
|
|
408
|
+
assertBoolean(adjustRounding, "adjustRounding");
|
|
409
|
+
assertNonNegativeAmount(minStakeRao, "Minimum remaining stake");
|
|
410
|
+
if (!Number.isInteger(partnerFeeBps) || partnerFeeBps < 0 || partnerFeeBps > 1e4)
|
|
411
|
+
invalid("Invalid partner fee basis points.");
|
|
412
|
+
if (!Array.isArray(pulls) || pulls.length < 1 || pulls.length > 16)
|
|
413
|
+
invalid("Provide between 1 and 16 stake pulls.");
|
|
414
|
+
const keys = /* @__PURE__ */ new Set();
|
|
415
|
+
const originalPulls = pulls.map((pull) => {
|
|
416
|
+
const hotkey = normalizeBytes32(pull.hotkey, "Validator").toLowerCase();
|
|
417
|
+
if (keys.has(hotkey)) invalid("Duplicate stake source.");
|
|
418
|
+
keys.add(hotkey);
|
|
419
|
+
assertPositiveAmount(pull.amountRao, "Stake pull");
|
|
420
|
+
return { hotkey, amountRao: pull.amountRao };
|
|
421
|
+
});
|
|
422
|
+
if (originalPulls.reduce((sum, pull) => sum + pull.amountRao, 0n) * EVM_WEI_PER_RAO !== amountWei)
|
|
423
|
+
invalid("Stake pulls must sum to the input amount.");
|
|
424
|
+
const balances = /* @__PURE__ */ new Map();
|
|
425
|
+
if (!Array.isArray(positions)) invalid("Stake positions must be an array.");
|
|
426
|
+
for (const position of positions) {
|
|
427
|
+
const key = normalizeBytes32(position.hotkey, "Validator").toLowerCase();
|
|
428
|
+
if (balances.has(key)) invalid("Duplicate stake position.");
|
|
429
|
+
assertNonNegativeAmount(position.stakeRao, "Stake balance");
|
|
430
|
+
balances.set(key, position.stakeRao);
|
|
431
|
+
}
|
|
432
|
+
const fits = (candidatePulls2) => {
|
|
433
|
+
const total = candidatePulls2.reduce(
|
|
434
|
+
(sum, pull) => sum + pull.amountRao,
|
|
435
|
+
0n
|
|
436
|
+
);
|
|
437
|
+
const cut = total * BigInt(partnerFeeBps) / 10000n;
|
|
438
|
+
const shares = candidatePulls2.map(
|
|
439
|
+
(pull, index) => index ? cut * pull.amountRao / total : 0n
|
|
440
|
+
);
|
|
441
|
+
shares[0] = cut - shares.reduce((sum, share) => sum + share, 0n);
|
|
442
|
+
return candidatePulls2.every((pull, index) => {
|
|
443
|
+
const balance = balances.get(pull.hotkey);
|
|
444
|
+
if (balance == null) return false;
|
|
445
|
+
const remainder = balance - pull.amountRao - shares[index];
|
|
446
|
+
return remainder === 0n || remainder > 0n && remainder >= minStakeRao;
|
|
447
|
+
});
|
|
448
|
+
};
|
|
449
|
+
if (!fits(originalPulls))
|
|
450
|
+
invalid(
|
|
451
|
+
"Stake positions cannot cover the pulls, partner fee, and minimum remainder."
|
|
452
|
+
);
|
|
453
|
+
let candidate = amountWei;
|
|
454
|
+
let candidatePulls = originalPulls;
|
|
455
|
+
for (let attempt = 0; ; attempt++) {
|
|
456
|
+
if (!isActive()) throw new Error("Stake quote cancelled.");
|
|
457
|
+
try {
|
|
458
|
+
const result = await quoteAndEstimate(
|
|
459
|
+
candidate,
|
|
460
|
+
candidatePulls,
|
|
461
|
+
minAmountOutWei
|
|
462
|
+
);
|
|
463
|
+
if (!isActive()) throw new Error("Stake quote cancelled.");
|
|
464
|
+
return Object.freeze({
|
|
465
|
+
...result,
|
|
466
|
+
amountWei: candidate,
|
|
467
|
+
pulls: Object.freeze(
|
|
468
|
+
candidatePulls.map((pull) => Object.freeze({ ...pull }))
|
|
469
|
+
),
|
|
470
|
+
minAmountOutWei
|
|
471
|
+
});
|
|
472
|
+
} catch (error) {
|
|
473
|
+
const hotkey = strandedStakeSource(error);
|
|
474
|
+
if (!isActive() || !adjustRounding || !hotkey || attempt >= 8 || candidate - EVM_WEI_PER_RAO < minAmountOutWei)
|
|
475
|
+
throw error;
|
|
476
|
+
const index = candidatePulls.findIndex(
|
|
477
|
+
(pull) => pull.hotkey === hotkey
|
|
478
|
+
);
|
|
479
|
+
if (index < 0 || candidatePulls[index].amountRao <= 1n) throw error;
|
|
480
|
+
const nextPulls = candidatePulls.map(
|
|
481
|
+
(pull, i) => i === index ? { ...pull, amountRao: pull.amountRao - 1n } : pull
|
|
482
|
+
);
|
|
483
|
+
if (!fits(nextPulls)) throw error;
|
|
484
|
+
candidatePulls = nextPulls;
|
|
485
|
+
candidate -= EVM_WEI_PER_RAO;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/core/addresses.ts
|
|
491
|
+
import {
|
|
492
|
+
AccountId,
|
|
493
|
+
Blake2256,
|
|
494
|
+
getSs58AddressInfo
|
|
495
|
+
} from "@polkadot-api/substrate-bindings";
|
|
496
|
+
import {
|
|
497
|
+
bytesToHex,
|
|
498
|
+
concat,
|
|
499
|
+
getAddress as getAddress2,
|
|
500
|
+
hexToBytes,
|
|
501
|
+
isAddress,
|
|
502
|
+
stringToBytes,
|
|
503
|
+
zeroAddress
|
|
504
|
+
} from "viem";
|
|
505
|
+
var BITTENSOR_SS58_PREFIX = 42;
|
|
506
|
+
function normalizeEvmAddress(value) {
|
|
507
|
+
const candidate = typeof value === "string" && /^0x[0-9A-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
508
|
+
if (!isAddress(candidate) || candidate === zeroAddress) {
|
|
509
|
+
throw new ForeverMoneyError(
|
|
510
|
+
"INVALID_ADDRESS",
|
|
511
|
+
"Expected a non-zero EVM address."
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return getAddress2(candidate);
|
|
515
|
+
}
|
|
516
|
+
function evmToMirrorSS58(evmAddress) {
|
|
517
|
+
return AccountId(BITTENSOR_SS58_PREFIX).dec(
|
|
518
|
+
Blake2256(
|
|
519
|
+
concat([
|
|
520
|
+
stringToBytes("evm:"),
|
|
521
|
+
hexToBytes(normalizeEvmAddress(evmAddress))
|
|
522
|
+
])
|
|
523
|
+
)
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
function decodeBittensorAddress(value) {
|
|
527
|
+
try {
|
|
528
|
+
if (typeof value !== "string" || !value || value.startsWith("0x"))
|
|
529
|
+
throw new Error("Raw hex is not SS58.");
|
|
530
|
+
const info = getSs58AddressInfo(value);
|
|
531
|
+
if (!info.isValid || info.ss58Format !== BITTENSOR_SS58_PREFIX || info.publicKey.length !== 32)
|
|
532
|
+
throw new Error("Invalid Bittensor address.");
|
|
533
|
+
const publicKey = info.publicKey;
|
|
534
|
+
return publicKey;
|
|
535
|
+
} catch {
|
|
536
|
+
throw new ForeverMoneyError(
|
|
537
|
+
"INVALID_SS58",
|
|
538
|
+
"Expected a Bittensor SS58 address with prefix 42."
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function normalizeSS58(value) {
|
|
543
|
+
return AccountId(BITTENSOR_SS58_PREFIX).dec(decodeBittensorAddress(value));
|
|
544
|
+
}
|
|
545
|
+
function isBittensorSS58(value) {
|
|
546
|
+
try {
|
|
547
|
+
decodeBittensorAddress(value);
|
|
548
|
+
return true;
|
|
549
|
+
} catch {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function ss58ToPublicKey(value) {
|
|
554
|
+
return bytesToHex(decodeBittensorAddress(value));
|
|
555
|
+
}
|
|
556
|
+
|
|
343
557
|
// src/abis/index.ts
|
|
344
558
|
var ERC20_ABI = Object.freeze([
|
|
345
559
|
"function allowance(address owner,address spender) view returns (uint256)",
|
|
@@ -358,7 +572,9 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
358
572
|
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
359
573
|
"function bridgeFeeBps() view returns (uint16)",
|
|
360
574
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
575
|
+
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit) view returns (uint256 fee)",
|
|
361
576
|
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) payable returns (bytes32 messageId)",
|
|
577
|
+
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit) payable returns (bytes32 messageId)",
|
|
362
578
|
"function quoteBridgeToFinneyWithFee(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit,(address recipient,uint16 bps) integrator) view returns (uint256 fee,uint256 cut,uint256 amountCrossing)",
|
|
363
579
|
"function bridgeToFinneyWithFee(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
364
580
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
@@ -376,7 +592,6 @@ var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
|
376
592
|
"function bridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
377
593
|
"function bridgeOutFromValidators(uint64 destSelector,address token,address recipient,uint256 taoAmount,(bytes32 validator,uint256 alphaRao)[] sources,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
378
594
|
"function bridgeOutFromValidatorsWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,(bytes32 validator,uint256 alphaRao)[] sources,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
379
|
-
"function minStakeRequired() view returns (uint256)",
|
|
380
595
|
"function MAX_STAKE_SOURCES() view returns (uint256)",
|
|
381
596
|
"function GATEWAY_COLDKEY() view returns (bytes32)",
|
|
382
597
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
@@ -455,89 +670,6 @@ function createTransactionPlan(plan) {
|
|
|
455
670
|
});
|
|
456
671
|
}
|
|
457
672
|
|
|
458
|
-
// src/core/validation.ts
|
|
459
|
-
import { maxUint256 as maxUint2562, zeroHash, isHex } from "viem";
|
|
460
|
-
function assertBigInt(amount, label) {
|
|
461
|
-
if (typeof amount !== "bigint") {
|
|
462
|
-
throw new ForeverMoneyError(
|
|
463
|
-
"INVALID_TRANSACTION_PLAN",
|
|
464
|
-
`${label} must be a bigint.`
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
function assertPositiveAmount(amount, label) {
|
|
469
|
-
assertBigInt(amount, label);
|
|
470
|
-
if (amount === 0n) {
|
|
471
|
-
throw new ForeverMoneyError(
|
|
472
|
-
"AMOUNT_ZERO",
|
|
473
|
-
`${label} must be greater than zero.`
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
if (amount < 0n) {
|
|
477
|
-
throw new ForeverMoneyError(
|
|
478
|
-
"INVALID_TRANSACTION_PLAN",
|
|
479
|
-
`${label} cannot be negative.`
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
if (amount > maxUint2562) {
|
|
483
|
-
throw new ForeverMoneyError(
|
|
484
|
-
"INVALID_TRANSACTION_PLAN",
|
|
485
|
-
`${label} exceeds uint256.`
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
function assertNonNegativeAmount(amount, label) {
|
|
490
|
-
assertBigInt(amount, label);
|
|
491
|
-
if (amount < 0n) {
|
|
492
|
-
throw new ForeverMoneyError(
|
|
493
|
-
"INVALID_TRANSACTION_PLAN",
|
|
494
|
-
`${label} cannot be negative.`
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
if (amount > maxUint2562) {
|
|
498
|
-
throw new ForeverMoneyError(
|
|
499
|
-
"INVALID_TRANSACTION_PLAN",
|
|
500
|
-
`${label} exceeds uint256.`
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
function assertBoolean(value, label) {
|
|
505
|
-
if (typeof value !== "boolean") {
|
|
506
|
-
throw new ForeverMoneyError(
|
|
507
|
-
"INVALID_TRANSACTION_PLAN",
|
|
508
|
-
`${label} must be a boolean.`
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
function assertArray(value, label) {
|
|
513
|
-
if (!Array.isArray(value)) {
|
|
514
|
-
throw new ForeverMoneyError(
|
|
515
|
-
"INVALID_TRANSACTION_PLAN",
|
|
516
|
-
`${label} must be an array.`
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
function assertRecord(value, label) {
|
|
521
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
522
|
-
throw new ForeverMoneyError(
|
|
523
|
-
"INVALID_TRANSACTION_PLAN",
|
|
524
|
-
`${label} must be an object.`
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function normalizeBytes32(value, label) {
|
|
529
|
-
if (!isHex(value, { strict: true }) || value.length !== 66) {
|
|
530
|
-
throw new ForeverMoneyError(
|
|
531
|
-
"INVALID_BYTES32",
|
|
532
|
-
`${label} must be a 32-byte hex value.`
|
|
533
|
-
);
|
|
534
|
-
}
|
|
535
|
-
return value.toLowerCase();
|
|
536
|
-
}
|
|
537
|
-
function normalizeOptionalBytes32(value, label) {
|
|
538
|
-
return value === void 0 ? zeroHash : normalizeBytes32(value, label);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
673
|
// src/bridge/plans.ts
|
|
542
674
|
var erc20Abi = parseAbi(ERC20_ABI);
|
|
543
675
|
var spokeAbi = parseAbi(SPOKE_GATEWAY_ABI);
|
|
@@ -546,6 +678,18 @@ var stakingAbi = parseAbi(STAKING_ABI);
|
|
|
546
678
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
547
679
|
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
548
680
|
var MIN_LIQUID_SUBTENSOR_TO_EVM_WEI = 2000000n * EVM_WEI_PER_RAO;
|
|
681
|
+
var EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT = 3500000n;
|
|
682
|
+
function resolveDestinationGasLimit(value) {
|
|
683
|
+
const gasLimit = value ?? EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT;
|
|
684
|
+
if (typeof gasLimit !== "bigint" || gasLimit <= 0n) {
|
|
685
|
+
throw new ForeverMoneyError(
|
|
686
|
+
"INVALID_TRANSACTION_PLAN",
|
|
687
|
+
"Destination gas limit must be a positive bigint.",
|
|
688
|
+
{ destinationGasLimit: String(gasLimit) }
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return gasLimit;
|
|
692
|
+
}
|
|
549
693
|
var MAX_PARTNER_FEE_BPS = 1e4;
|
|
550
694
|
var MAX_STAKE_PULLS = 16;
|
|
551
695
|
var NO_PARTNER_FEE = {
|
|
@@ -722,15 +866,15 @@ function partnerFeeSummary(charged, unit, fee) {
|
|
|
722
866
|
if (fee.bps === 0) return "";
|
|
723
867
|
return ` Partner fee: ${charged} wei of ${unit} (${fee.bps} bps) on top, paid to ${fee.recipient}.`;
|
|
724
868
|
}
|
|
725
|
-
function encodeSpokeBridge(token, amountWei, exit, fee) {
|
|
869
|
+
function encodeSpokeBridge(token, amountWei, exit, destinationGasLimit, fee) {
|
|
726
870
|
return fee.bps === 0 ? encodeFunctionData({
|
|
727
871
|
abi: spokeAbi,
|
|
728
872
|
functionName: "bridgeToFinney",
|
|
729
|
-
args: [token, amountWei, exit]
|
|
873
|
+
args: [token, amountWei, exit, destinationGasLimit]
|
|
730
874
|
}) : encodeFunctionData({
|
|
731
875
|
abi: spokeAbi,
|
|
732
876
|
functionName: "bridgeToFinneyWithFee",
|
|
733
|
-
args: [token, amountWei, exit,
|
|
877
|
+
args: [token, amountWei, exit, destinationGasLimit, fee]
|
|
734
878
|
});
|
|
735
879
|
}
|
|
736
880
|
function resolveStakePulls(pulls, amountRao) {
|
|
@@ -817,7 +961,7 @@ function encodeHubBridge(destSelector, token, recipient, taoAmount, stakedAlphaR
|
|
|
817
961
|
args: [...args, fee]
|
|
818
962
|
});
|
|
819
963
|
}
|
|
820
|
-
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee) {
|
|
964
|
+
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, destinationGasLimit, fee) {
|
|
821
965
|
const maxBps = await provider.readContract({
|
|
822
966
|
address: gateway,
|
|
823
967
|
abi: spokeAbi,
|
|
@@ -828,7 +972,7 @@ async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee)
|
|
|
828
972
|
address: gateway,
|
|
829
973
|
abi: spokeAbi,
|
|
830
974
|
functionName: "quoteBridgeToFinneyWithFee",
|
|
831
|
-
args: [token, amountWei, exit,
|
|
975
|
+
args: [token, amountWei, exit, destinationGasLimit, fee]
|
|
832
976
|
});
|
|
833
977
|
return networkFee;
|
|
834
978
|
}
|
|
@@ -874,6 +1018,9 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
874
1018
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
875
1019
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
876
1020
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
1021
|
+
const destinationGasLimit = resolveDestinationGasLimit(
|
|
1022
|
+
input.destinationGasLimit
|
|
1023
|
+
);
|
|
877
1024
|
const destination = normalizeSS58(input.destination);
|
|
878
1025
|
const exit = {
|
|
879
1026
|
ss58: ss58ToPublicKey(destination),
|
|
@@ -917,6 +1064,7 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
917
1064
|
asset.evmToken,
|
|
918
1065
|
input.amountWei,
|
|
919
1066
|
exit,
|
|
1067
|
+
destinationGasLimit,
|
|
920
1068
|
partnerFee
|
|
921
1069
|
),
|
|
922
1070
|
value,
|
|
@@ -1032,6 +1180,9 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1032
1180
|
assertDelivery(input.delivery);
|
|
1033
1181
|
assertAssetMode(input.asset, input.delivery);
|
|
1034
1182
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
1183
|
+
const destinationGasLimit = resolveDestinationGasLimit(
|
|
1184
|
+
input.destinationGasLimit
|
|
1185
|
+
);
|
|
1035
1186
|
const destination = normalizeSS58(input.destination);
|
|
1036
1187
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
1037
1188
|
const exit = {
|
|
@@ -1056,13 +1207,19 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1056
1207
|
address: evm.contracts.gateway,
|
|
1057
1208
|
abi: spokeAbi,
|
|
1058
1209
|
functionName: "quoteBridgeToFinney",
|
|
1059
|
-
args: [
|
|
1210
|
+
args: [
|
|
1211
|
+
asset.evmToken,
|
|
1212
|
+
input.amountWei,
|
|
1213
|
+
exit,
|
|
1214
|
+
destinationGasLimit
|
|
1215
|
+
]
|
|
1060
1216
|
}) : quoteSpokeWithFee(
|
|
1061
1217
|
provider,
|
|
1062
1218
|
evm.contracts.gateway,
|
|
1063
1219
|
asset.evmToken,
|
|
1064
1220
|
input.amountWei,
|
|
1065
1221
|
exit,
|
|
1222
|
+
destinationGasLimit,
|
|
1066
1223
|
partnerFee
|
|
1067
1224
|
)
|
|
1068
1225
|
]);
|
|
@@ -1072,6 +1229,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1072
1229
|
asset.evmToken,
|
|
1073
1230
|
input.amountWei,
|
|
1074
1231
|
exit,
|
|
1232
|
+
destinationGasLimit,
|
|
1075
1233
|
partnerFee
|
|
1076
1234
|
);
|
|
1077
1235
|
estimatedBridgeGas = await provider.estimateGas({
|
|
@@ -1097,7 +1255,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1097
1255
|
function prepareBaseToSubtensor(provider, input) {
|
|
1098
1256
|
return prepareEvmToSubtensor(provider, { ...input, evmChain: "base" });
|
|
1099
1257
|
}
|
|
1100
|
-
async function
|
|
1258
|
+
async function prepareSubtensorToEvmExact(provider, input) {
|
|
1101
1259
|
const sender = normalizeEvmAddress(input.sender);
|
|
1102
1260
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
1103
1261
|
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
@@ -1179,6 +1337,53 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
1179
1337
|
plan
|
|
1180
1338
|
});
|
|
1181
1339
|
}
|
|
1340
|
+
async function prepareSubtensorToEvm(provider, input) {
|
|
1341
|
+
const options = input.stakeRounding;
|
|
1342
|
+
if (options != null && options !== false && typeof options.enabled !== "boolean") {
|
|
1343
|
+
throw new ForeverMoneyError(
|
|
1344
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1345
|
+
"stakeRounding.enabled must be a boolean."
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
if (!options || !options.enabled)
|
|
1349
|
+
return prepareSubtensorToEvmExact(provider, input);
|
|
1350
|
+
if (input.source !== "staked" || !input.stakePulls?.length) {
|
|
1351
|
+
throw new ForeverMoneyError(
|
|
1352
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1353
|
+
"Stake rounding requires a staked source with named stakePulls."
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
const minimum = minimumSubtensorOutput(input);
|
|
1357
|
+
const result = await estimateRoundedStake({
|
|
1358
|
+
amountWei: input.amountWei,
|
|
1359
|
+
minAmountOutWei: minimum,
|
|
1360
|
+
pulls: input.stakePulls,
|
|
1361
|
+
positions: options.positions,
|
|
1362
|
+
minStakeRao: options.minStakeRao ?? 0n,
|
|
1363
|
+
partnerFeeBps: input.partnerFee?.bps ?? 0,
|
|
1364
|
+
quoteAndEstimate: async (amountWei, stakePulls, minAmountOutWei) => ({
|
|
1365
|
+
prepared: await prepareSubtensorToEvmExact(provider, {
|
|
1366
|
+
...input,
|
|
1367
|
+
amountWei,
|
|
1368
|
+
stakePulls,
|
|
1369
|
+
minAmountOutWei,
|
|
1370
|
+
stakeRounding: false
|
|
1371
|
+
})
|
|
1372
|
+
})
|
|
1373
|
+
});
|
|
1374
|
+
return Object.freeze({
|
|
1375
|
+
...result.prepared,
|
|
1376
|
+
stakeRounding: Object.freeze({
|
|
1377
|
+
requestedAmountWei: input.amountWei,
|
|
1378
|
+
amountWei: result.amountWei,
|
|
1379
|
+
minAmountOutWei: result.minAmountOutWei,
|
|
1380
|
+
pulls: result.pulls,
|
|
1381
|
+
simulationComplete: !result.prepared.plan.steps.some(
|
|
1382
|
+
(step2) => step2.kind === "approval"
|
|
1383
|
+
)
|
|
1384
|
+
})
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1182
1387
|
function prepareSubtensorToBase(provider, input) {
|
|
1183
1388
|
return prepareSubtensorToEvm(provider, { ...input, evmChain: "base" });
|
|
1184
1389
|
}
|
|
@@ -2240,6 +2445,7 @@ export {
|
|
|
2240
2445
|
BASE_CCIP_SELECTOR,
|
|
2241
2446
|
BASE_CHAIN_ID,
|
|
2242
2447
|
BASIS_POINTS,
|
|
2448
|
+
EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT,
|
|
2243
2449
|
EVM_WEI_PER_RAO,
|
|
2244
2450
|
FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
2245
2451
|
ForeverMoneyError,
|
|
@@ -2269,6 +2475,7 @@ export {
|
|
|
2269
2475
|
buildWithdrawVaultPlan,
|
|
2270
2476
|
createForeverMoneyClient,
|
|
2271
2477
|
destinationChainId,
|
|
2478
|
+
estimateRoundedStake,
|
|
2272
2479
|
evmChainFromBridgeDirection,
|
|
2273
2480
|
evmToMirrorSS58,
|
|
2274
2481
|
feeWithBuffer,
|
|
@@ -2290,6 +2497,8 @@ export {
|
|
|
2290
2497
|
partnerFeeTaoTopUp,
|
|
2291
2498
|
sourceChainId,
|
|
2292
2499
|
ss58ToPublicKey,
|
|
2500
|
+
stakedMinimumOutput,
|
|
2501
|
+
strandedStakeSource,
|
|
2293
2502
|
toEip1193Transaction,
|
|
2294
2503
|
toEthersTransaction,
|
|
2295
2504
|
toViemTransaction,
|