@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.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
BASE_CCIP_SELECTOR: () => BASE_CCIP_SELECTOR,
|
|
24
24
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
25
25
|
BASIS_POINTS: () => BASIS_POINTS,
|
|
26
|
+
EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT: () => EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT,
|
|
26
27
|
EVM_WEI_PER_RAO: () => EVM_WEI_PER_RAO,
|
|
27
28
|
FOREVERMONEY_DEPLOYMENT_VERSION: () => FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
28
29
|
ForeverMoneyError: () => ForeverMoneyError,
|
|
@@ -52,6 +53,7 @@ __export(index_exports, {
|
|
|
52
53
|
buildWithdrawVaultPlan: () => buildWithdrawVaultPlan,
|
|
53
54
|
createForeverMoneyClient: () => createForeverMoneyClient,
|
|
54
55
|
destinationChainId: () => destinationChainId,
|
|
56
|
+
estimateRoundedStake: () => estimateRoundedStake,
|
|
55
57
|
evmChainFromBridgeDirection: () => evmChainFromBridgeDirection,
|
|
56
58
|
evmToMirrorSS58: () => evmToMirrorSS58,
|
|
57
59
|
feeWithBuffer: () => feeWithBuffer,
|
|
@@ -73,6 +75,8 @@ __export(index_exports, {
|
|
|
73
75
|
partnerFeeTaoTopUp: () => partnerFeeTaoTopUp,
|
|
74
76
|
sourceChainId: () => sourceChainId,
|
|
75
77
|
ss58ToPublicKey: () => ss58ToPublicKey,
|
|
78
|
+
stakedMinimumOutput: () => stakedMinimumOutput,
|
|
79
|
+
strandedStakeSource: () => strandedStakeSource,
|
|
76
80
|
toEip1193Transaction: () => toEip1193Transaction,
|
|
77
81
|
toEthersTransaction: () => toEthersTransaction,
|
|
78
82
|
toViemTransaction: () => toViemTransaction,
|
|
@@ -80,80 +84,8 @@ __export(index_exports, {
|
|
|
80
84
|
});
|
|
81
85
|
module.exports = __toCommonJS(index_exports);
|
|
82
86
|
|
|
83
|
-
// src/core/addresses.ts
|
|
84
|
-
var import_substrate_bindings = require("@polkadot-api/substrate-bindings");
|
|
85
|
-
var import_viem = require("viem");
|
|
86
|
-
|
|
87
|
-
// src/core/errors.ts
|
|
88
|
-
var ForeverMoneyError = class extends Error {
|
|
89
|
-
code;
|
|
90
|
-
details;
|
|
91
|
-
constructor(code, message, details) {
|
|
92
|
-
super(message);
|
|
93
|
-
this.name = "ForeverMoneyError";
|
|
94
|
-
this.code = code;
|
|
95
|
-
this.details = details === void 0 ? void 0 : Object.freeze({ ...details });
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// src/core/addresses.ts
|
|
100
|
-
var BITTENSOR_SS58_PREFIX = 42;
|
|
101
|
-
function normalizeEvmAddress(value) {
|
|
102
|
-
const candidate = typeof value === "string" && /^0x[0-9A-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
103
|
-
if (!(0, import_viem.isAddress)(candidate) || candidate === import_viem.zeroAddress) {
|
|
104
|
-
throw new ForeverMoneyError(
|
|
105
|
-
"INVALID_ADDRESS",
|
|
106
|
-
"Expected a non-zero EVM address."
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
return (0, import_viem.getAddress)(candidate);
|
|
110
|
-
}
|
|
111
|
-
function evmToMirrorSS58(evmAddress) {
|
|
112
|
-
return (0, import_substrate_bindings.AccountId)(BITTENSOR_SS58_PREFIX).dec(
|
|
113
|
-
(0, import_substrate_bindings.Blake2256)(
|
|
114
|
-
(0, import_viem.concat)([
|
|
115
|
-
(0, import_viem.stringToBytes)("evm:"),
|
|
116
|
-
(0, import_viem.hexToBytes)(normalizeEvmAddress(evmAddress))
|
|
117
|
-
])
|
|
118
|
-
)
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
function decodeBittensorAddress(value) {
|
|
122
|
-
try {
|
|
123
|
-
if (typeof value !== "string" || !value || value.startsWith("0x"))
|
|
124
|
-
throw new Error("Raw hex is not SS58.");
|
|
125
|
-
const info = (0, import_substrate_bindings.getSs58AddressInfo)(value);
|
|
126
|
-
if (!info.isValid || info.ss58Format !== BITTENSOR_SS58_PREFIX || info.publicKey.length !== 32)
|
|
127
|
-
throw new Error("Invalid Bittensor address.");
|
|
128
|
-
const publicKey = info.publicKey;
|
|
129
|
-
return publicKey;
|
|
130
|
-
} catch {
|
|
131
|
-
throw new ForeverMoneyError(
|
|
132
|
-
"INVALID_SS58",
|
|
133
|
-
"Expected a Bittensor SS58 address with prefix 42."
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function normalizeSS58(value) {
|
|
138
|
-
return (0, import_substrate_bindings.AccountId)(BITTENSOR_SS58_PREFIX).dec(decodeBittensorAddress(value));
|
|
139
|
-
}
|
|
140
|
-
function isBittensorSS58(value) {
|
|
141
|
-
try {
|
|
142
|
-
decodeBittensorAddress(value);
|
|
143
|
-
return true;
|
|
144
|
-
} catch {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
function ss58ToPublicKey(value) {
|
|
149
|
-
return (0, import_viem.bytesToHex)(decodeBittensorAddress(value));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// src/core/amounts.ts
|
|
153
|
-
var import_viem3 = require("viem");
|
|
154
|
-
|
|
155
87
|
// src/chains/deployment.ts
|
|
156
|
-
var
|
|
88
|
+
var import_viem = require("viem");
|
|
157
89
|
var BASE_CHAIN_ID = 8453;
|
|
158
90
|
var ROBINHOOD_CHAIN_ID = 4663;
|
|
159
91
|
var SUBTENSOR_CHAIN_ID = 964;
|
|
@@ -172,34 +104,34 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
172
104
|
chainId: BASE_CHAIN_ID,
|
|
173
105
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
174
106
|
contracts: Object.freeze({
|
|
175
|
-
gateway: (0,
|
|
107
|
+
gateway: (0, import_viem.getAddress)("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
176
108
|
// Retired gateways, oldest first; kept only to track transfers sent
|
|
177
109
|
// through them.
|
|
178
110
|
legacyGateways: [
|
|
179
|
-
(0,
|
|
111
|
+
(0, import_viem.getAddress)("0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C")
|
|
180
112
|
],
|
|
181
|
-
wrappedTao: (0,
|
|
113
|
+
wrappedTao: (0, import_viem.getAddress)(
|
|
182
114
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
183
115
|
),
|
|
184
|
-
wrappedSn80: (0,
|
|
116
|
+
wrappedSn80: (0, import_viem.getAddress)(
|
|
185
117
|
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
186
118
|
),
|
|
187
|
-
ccipRouter: (0,
|
|
119
|
+
ccipRouter: (0, import_viem.getAddress)(
|
|
188
120
|
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
189
121
|
),
|
|
190
|
-
ccipOffRampFromSubtensor: (0,
|
|
122
|
+
ccipOffRampFromSubtensor: (0, import_viem.getAddress)(
|
|
191
123
|
"0xf09AFe78d3c7d359b334d7cB88995751F7eC5E13"
|
|
192
124
|
),
|
|
193
|
-
vaultFactory: (0,
|
|
125
|
+
vaultFactory: (0, import_viem.getAddress)(
|
|
194
126
|
"0x9b7F3c7aa335D8BF9f91741E541820466735F868"
|
|
195
127
|
),
|
|
196
|
-
vaultManagerImplementation: (0,
|
|
128
|
+
vaultManagerImplementation: (0, import_viem.getAddress)(
|
|
197
129
|
"0x235Ad8654fe3a1619a9892eBCaD9361F416168da"
|
|
198
130
|
),
|
|
199
|
-
batchLiquidityManagerQuery: (0,
|
|
131
|
+
batchLiquidityManagerQuery: (0, import_viem.getAddress)(
|
|
200
132
|
"0xd117176C5E6809b68500995F59589A8c081565A5"
|
|
201
133
|
),
|
|
202
|
-
weth: (0,
|
|
134
|
+
weth: (0, import_viem.getAddress)("0x4200000000000000000000000000000000000006")
|
|
203
135
|
}),
|
|
204
136
|
deploymentBlock: 50098800
|
|
205
137
|
}),
|
|
@@ -209,17 +141,17 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
209
141
|
chainId: ROBINHOOD_CHAIN_ID,
|
|
210
142
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
211
143
|
contracts: Object.freeze({
|
|
212
|
-
gateway: (0,
|
|
144
|
+
gateway: (0, import_viem.getAddress)("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
213
145
|
legacyGateways: [
|
|
214
|
-
(0,
|
|
146
|
+
(0, import_viem.getAddress)("0x53Dcc4FE04193e489BE537F722F65317DB1E65d8")
|
|
215
147
|
],
|
|
216
|
-
wrappedTao: (0,
|
|
148
|
+
wrappedTao: (0, import_viem.getAddress)(
|
|
217
149
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
218
150
|
),
|
|
219
|
-
ccipRouter: (0,
|
|
151
|
+
ccipRouter: (0, import_viem.getAddress)(
|
|
220
152
|
"0x06fC836cf9839B1cd891C440A0a45242DA6Ae1c9"
|
|
221
153
|
),
|
|
222
|
-
ccipOffRampFromSubtensor: (0,
|
|
154
|
+
ccipOffRampFromSubtensor: (0, import_viem.getAddress)(
|
|
223
155
|
"0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3"
|
|
224
156
|
)
|
|
225
157
|
})
|
|
@@ -229,32 +161,32 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
229
161
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
230
162
|
contracts: Object.freeze({
|
|
231
163
|
// V5.1 AlphaGateway: multi-validator staked input (tao-bridge 485c898).
|
|
232
|
-
gateway: (0,
|
|
164
|
+
gateway: (0, import_viem.getAddress)("0xd5Fa238aa4177f6c1341491969d9cBeec94EEd69"),
|
|
233
165
|
// V4 and V5 hubs. The spokes still deliver to the V5 hub (their hub
|
|
234
166
|
// pointer is immutable), so it stays here for delivery tracking.
|
|
235
167
|
legacyGateways: [
|
|
236
|
-
(0,
|
|
237
|
-
(0,
|
|
168
|
+
(0, import_viem.getAddress)("0x998f20Fea90bF7792774dECc7f994716442B1705"),
|
|
169
|
+
(0, import_viem.getAddress)("0xcd0C6d98D0A126B1c113d15b4c28F38321437787")
|
|
238
170
|
],
|
|
239
|
-
alphaVault: (0,
|
|
171
|
+
alphaVault: (0, import_viem.getAddress)(
|
|
240
172
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
241
173
|
),
|
|
242
|
-
wrappedTao: (0,
|
|
174
|
+
wrappedTao: (0, import_viem.getAddress)(
|
|
243
175
|
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
244
176
|
),
|
|
245
|
-
wrappedSn80: (0,
|
|
177
|
+
wrappedSn80: (0, import_viem.getAddress)(
|
|
246
178
|
"0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222"
|
|
247
179
|
),
|
|
248
|
-
ccipRouter: (0,
|
|
180
|
+
ccipRouter: (0, import_viem.getAddress)(
|
|
249
181
|
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
250
182
|
),
|
|
251
|
-
ccipOffRampFromBase: (0,
|
|
183
|
+
ccipOffRampFromBase: (0, import_viem.getAddress)(
|
|
252
184
|
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
253
185
|
),
|
|
254
|
-
ccipOffRampFromRobinhood: (0,
|
|
186
|
+
ccipOffRampFromRobinhood: (0, import_viem.getAddress)(
|
|
255
187
|
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
256
188
|
),
|
|
257
|
-
stakingPrecompile: (0,
|
|
189
|
+
stakingPrecompile: (0, import_viem.getAddress)(
|
|
258
190
|
"0x0000000000000000000000000000000000000805"
|
|
259
191
|
)
|
|
260
192
|
})
|
|
@@ -264,6 +196,21 @@ function getForeverMoneyEvmDeployment(chain = "base") {
|
|
|
264
196
|
return foreverMoneyDeployment[chain];
|
|
265
197
|
}
|
|
266
198
|
|
|
199
|
+
// src/core/amounts.ts
|
|
200
|
+
var import_viem2 = require("viem");
|
|
201
|
+
|
|
202
|
+
// src/core/errors.ts
|
|
203
|
+
var ForeverMoneyError = class extends Error {
|
|
204
|
+
code;
|
|
205
|
+
details;
|
|
206
|
+
constructor(code, message, details) {
|
|
207
|
+
super(message);
|
|
208
|
+
this.name = "ForeverMoneyError";
|
|
209
|
+
this.code = code;
|
|
210
|
+
this.details = details === void 0 ? void 0 : Object.freeze({ ...details });
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
267
214
|
// src/core/amounts.ts
|
|
268
215
|
var BASIS_POINTS = 10000n;
|
|
269
216
|
var NETWORK_FEE_BUFFER_BPS = 200n;
|
|
@@ -284,7 +231,7 @@ function parseTaoAmount(value) {
|
|
|
284
231
|
}
|
|
285
232
|
let amount;
|
|
286
233
|
try {
|
|
287
|
-
amount = (0,
|
|
234
|
+
amount = (0, import_viem2.parseUnits)(value, 18);
|
|
288
235
|
} catch {
|
|
289
236
|
throw new ForeverMoneyError(
|
|
290
237
|
"AMOUNT_NOT_WHOLE_RAO",
|
|
@@ -313,7 +260,7 @@ function assertWholeRao(amountWei) {
|
|
|
313
260
|
"The amount cannot be negative."
|
|
314
261
|
);
|
|
315
262
|
}
|
|
316
|
-
if (amountWei >
|
|
263
|
+
if (amountWei > import_viem2.maxUint256) {
|
|
317
264
|
throw new ForeverMoneyError(
|
|
318
265
|
"INVALID_TRANSACTION_PLAN",
|
|
319
266
|
"The amount exceeds uint256."
|
|
@@ -339,7 +286,7 @@ function formatTaoAmount(amountWei) {
|
|
|
339
286
|
"A TAO amount cannot be negative."
|
|
340
287
|
);
|
|
341
288
|
}
|
|
342
|
-
if (amountWei >
|
|
289
|
+
if (amountWei > import_viem2.maxUint256) {
|
|
343
290
|
throw new ForeverMoneyError(
|
|
344
291
|
"INVALID_TRANSACTION_PLAN",
|
|
345
292
|
"The TAO amount exceeds uint256."
|
|
@@ -351,7 +298,7 @@ function formatTaoAmount(amountWei) {
|
|
|
351
298
|
"TAO amounts must resolve to a whole RAO."
|
|
352
299
|
);
|
|
353
300
|
}
|
|
354
|
-
const formatted = (0,
|
|
301
|
+
const formatted = (0, import_viem2.formatUnits)(amountWei, 18);
|
|
355
302
|
return formatted.includes(".") ? formatted : `${formatted}.0`;
|
|
356
303
|
}
|
|
357
304
|
function mulDivCeil(value, numerator, denominator) {
|
|
@@ -375,7 +322,7 @@ function feeWithBuffer(fee) {
|
|
|
375
322
|
BASIS_POINTS + NETWORK_FEE_BUFFER_BPS,
|
|
376
323
|
BASIS_POINTS
|
|
377
324
|
);
|
|
378
|
-
if (buffered >
|
|
325
|
+
if (buffered > import_viem2.maxUint256) {
|
|
379
326
|
throw new ForeverMoneyError(
|
|
380
327
|
"INVALID_TRANSACTION_PLAN",
|
|
381
328
|
"The buffered network fee exceeds uint256."
|
|
@@ -401,7 +348,7 @@ function gasLimitWithBuffer(gasLimit) {
|
|
|
401
348
|
BASIS_POINTS + GAS_LIMIT_BUFFER_BPS,
|
|
402
349
|
BASIS_POINTS
|
|
403
350
|
);
|
|
404
|
-
if (buffered >
|
|
351
|
+
if (buffered > import_viem2.maxUint256) {
|
|
405
352
|
throw new ForeverMoneyError(
|
|
406
353
|
"INVALID_TRANSACTION_PLAN",
|
|
407
354
|
"The buffered gas limit exceeds uint256."
|
|
@@ -410,6 +357,277 @@ function gasLimitWithBuffer(gasLimit) {
|
|
|
410
357
|
return buffered;
|
|
411
358
|
}
|
|
412
359
|
|
|
360
|
+
// src/core/validation.ts
|
|
361
|
+
var import_viem3 = require("viem");
|
|
362
|
+
function assertBigInt(amount, label) {
|
|
363
|
+
if (typeof amount !== "bigint") {
|
|
364
|
+
throw new ForeverMoneyError(
|
|
365
|
+
"INVALID_TRANSACTION_PLAN",
|
|
366
|
+
`${label} must be a bigint.`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function assertPositiveAmount(amount, label) {
|
|
371
|
+
assertBigInt(amount, label);
|
|
372
|
+
if (amount === 0n) {
|
|
373
|
+
throw new ForeverMoneyError(
|
|
374
|
+
"AMOUNT_ZERO",
|
|
375
|
+
`${label} must be greater than zero.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
if (amount < 0n) {
|
|
379
|
+
throw new ForeverMoneyError(
|
|
380
|
+
"INVALID_TRANSACTION_PLAN",
|
|
381
|
+
`${label} cannot be negative.`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (amount > import_viem3.maxUint256) {
|
|
385
|
+
throw new ForeverMoneyError(
|
|
386
|
+
"INVALID_TRANSACTION_PLAN",
|
|
387
|
+
`${label} exceeds uint256.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function assertNonNegativeAmount(amount, label) {
|
|
392
|
+
assertBigInt(amount, label);
|
|
393
|
+
if (amount < 0n) {
|
|
394
|
+
throw new ForeverMoneyError(
|
|
395
|
+
"INVALID_TRANSACTION_PLAN",
|
|
396
|
+
`${label} cannot be negative.`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
if (amount > import_viem3.maxUint256) {
|
|
400
|
+
throw new ForeverMoneyError(
|
|
401
|
+
"INVALID_TRANSACTION_PLAN",
|
|
402
|
+
`${label} exceeds uint256.`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function assertBoolean(value, label) {
|
|
407
|
+
if (typeof value !== "boolean") {
|
|
408
|
+
throw new ForeverMoneyError(
|
|
409
|
+
"INVALID_TRANSACTION_PLAN",
|
|
410
|
+
`${label} must be a boolean.`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function assertArray(value, label) {
|
|
415
|
+
if (!Array.isArray(value)) {
|
|
416
|
+
throw new ForeverMoneyError(
|
|
417
|
+
"INVALID_TRANSACTION_PLAN",
|
|
418
|
+
`${label} must be an array.`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function assertRecord(value, label) {
|
|
423
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
424
|
+
throw new ForeverMoneyError(
|
|
425
|
+
"INVALID_TRANSACTION_PLAN",
|
|
426
|
+
`${label} must be an object.`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function normalizeBytes32(value, label) {
|
|
431
|
+
if (!(0, import_viem3.isHex)(value, { strict: true }) || value.length !== 66) {
|
|
432
|
+
throw new ForeverMoneyError(
|
|
433
|
+
"INVALID_BYTES32",
|
|
434
|
+
`${label} must be a 32-byte hex value.`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return value.toLowerCase();
|
|
438
|
+
}
|
|
439
|
+
function normalizeOptionalBytes32(value, label) {
|
|
440
|
+
return value === void 0 ? import_viem3.zeroHash : normalizeBytes32(value, label);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/bridge/stake-rounding.ts
|
|
444
|
+
function invalid(message) {
|
|
445
|
+
throw new ForeverMoneyError("INVALID_TRANSACTION_PLAN", message);
|
|
446
|
+
}
|
|
447
|
+
function stakedMinimumOutput(amountWei, sourceCount = 1) {
|
|
448
|
+
assertWholeRao(amountWei);
|
|
449
|
+
if (!Number.isInteger(sourceCount) || sourceCount < 1 || sourceCount > 16)
|
|
450
|
+
invalid("Source count must be between 1 and 16.");
|
|
451
|
+
const rao = amountWei / EVM_WEI_PER_RAO;
|
|
452
|
+
const dust = 2n * BigInt(sourceCount) + 2n;
|
|
453
|
+
return (rao > dust ? rao - dust : rao) * EVM_WEI_PER_RAO;
|
|
454
|
+
}
|
|
455
|
+
function strandedStakeSource(error) {
|
|
456
|
+
const queue = [error];
|
|
457
|
+
const seen = /* @__PURE__ */ new Set();
|
|
458
|
+
while (queue.length && seen.size < 20) {
|
|
459
|
+
const entry = queue.shift();
|
|
460
|
+
if (typeof entry === "string" && /^0x5408a598[0-9a-f]{128}$/i.test(entry))
|
|
461
|
+
return `0x${entry.slice(10, 74)}`.toLowerCase();
|
|
462
|
+
if (entry && typeof entry === "object" && !seen.has(entry)) {
|
|
463
|
+
seen.add(entry);
|
|
464
|
+
for (const key of [
|
|
465
|
+
"data",
|
|
466
|
+
"error",
|
|
467
|
+
"info",
|
|
468
|
+
"cause",
|
|
469
|
+
"details",
|
|
470
|
+
"rpcData"
|
|
471
|
+
]) {
|
|
472
|
+
const nested = entry[key];
|
|
473
|
+
if (nested != null) queue.push(nested);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
async function estimateRoundedStake({
|
|
480
|
+
amountWei,
|
|
481
|
+
minAmountOutWei,
|
|
482
|
+
pulls,
|
|
483
|
+
positions,
|
|
484
|
+
minStakeRao = 0n,
|
|
485
|
+
adjustRounding = true,
|
|
486
|
+
partnerFeeBps = 0,
|
|
487
|
+
quoteAndEstimate,
|
|
488
|
+
isActive = () => true
|
|
489
|
+
}) {
|
|
490
|
+
assertWholeRao(amountWei);
|
|
491
|
+
assertPositiveAmount(minAmountOutWei, "Minimum output");
|
|
492
|
+
if (minAmountOutWei > amountWei)
|
|
493
|
+
invalid("Minimum output cannot exceed input.");
|
|
494
|
+
assertBoolean(adjustRounding, "adjustRounding");
|
|
495
|
+
assertNonNegativeAmount(minStakeRao, "Minimum remaining stake");
|
|
496
|
+
if (!Number.isInteger(partnerFeeBps) || partnerFeeBps < 0 || partnerFeeBps > 1e4)
|
|
497
|
+
invalid("Invalid partner fee basis points.");
|
|
498
|
+
if (!Array.isArray(pulls) || pulls.length < 1 || pulls.length > 16)
|
|
499
|
+
invalid("Provide between 1 and 16 stake pulls.");
|
|
500
|
+
const keys = /* @__PURE__ */ new Set();
|
|
501
|
+
const originalPulls = pulls.map((pull) => {
|
|
502
|
+
const hotkey = normalizeBytes32(pull.hotkey, "Validator").toLowerCase();
|
|
503
|
+
if (keys.has(hotkey)) invalid("Duplicate stake source.");
|
|
504
|
+
keys.add(hotkey);
|
|
505
|
+
assertPositiveAmount(pull.amountRao, "Stake pull");
|
|
506
|
+
return { hotkey, amountRao: pull.amountRao };
|
|
507
|
+
});
|
|
508
|
+
if (originalPulls.reduce((sum, pull) => sum + pull.amountRao, 0n) * EVM_WEI_PER_RAO !== amountWei)
|
|
509
|
+
invalid("Stake pulls must sum to the input amount.");
|
|
510
|
+
const balances = /* @__PURE__ */ new Map();
|
|
511
|
+
if (!Array.isArray(positions)) invalid("Stake positions must be an array.");
|
|
512
|
+
for (const position of positions) {
|
|
513
|
+
const key = normalizeBytes32(position.hotkey, "Validator").toLowerCase();
|
|
514
|
+
if (balances.has(key)) invalid("Duplicate stake position.");
|
|
515
|
+
assertNonNegativeAmount(position.stakeRao, "Stake balance");
|
|
516
|
+
balances.set(key, position.stakeRao);
|
|
517
|
+
}
|
|
518
|
+
const fits = (candidatePulls2) => {
|
|
519
|
+
const total = candidatePulls2.reduce(
|
|
520
|
+
(sum, pull) => sum + pull.amountRao,
|
|
521
|
+
0n
|
|
522
|
+
);
|
|
523
|
+
const cut = total * BigInt(partnerFeeBps) / 10000n;
|
|
524
|
+
const shares = candidatePulls2.map(
|
|
525
|
+
(pull, index) => index ? cut * pull.amountRao / total : 0n
|
|
526
|
+
);
|
|
527
|
+
shares[0] = cut - shares.reduce((sum, share) => sum + share, 0n);
|
|
528
|
+
return candidatePulls2.every((pull, index) => {
|
|
529
|
+
const balance = balances.get(pull.hotkey);
|
|
530
|
+
if (balance == null) return false;
|
|
531
|
+
const remainder = balance - pull.amountRao - shares[index];
|
|
532
|
+
return remainder === 0n || remainder > 0n && remainder >= minStakeRao;
|
|
533
|
+
});
|
|
534
|
+
};
|
|
535
|
+
if (!fits(originalPulls))
|
|
536
|
+
invalid(
|
|
537
|
+
"Stake positions cannot cover the pulls, partner fee, and minimum remainder."
|
|
538
|
+
);
|
|
539
|
+
let candidate = amountWei;
|
|
540
|
+
let candidatePulls = originalPulls;
|
|
541
|
+
for (let attempt = 0; ; attempt++) {
|
|
542
|
+
if (!isActive()) throw new Error("Stake quote cancelled.");
|
|
543
|
+
try {
|
|
544
|
+
const result = await quoteAndEstimate(
|
|
545
|
+
candidate,
|
|
546
|
+
candidatePulls,
|
|
547
|
+
minAmountOutWei
|
|
548
|
+
);
|
|
549
|
+
if (!isActive()) throw new Error("Stake quote cancelled.");
|
|
550
|
+
return Object.freeze({
|
|
551
|
+
...result,
|
|
552
|
+
amountWei: candidate,
|
|
553
|
+
pulls: Object.freeze(
|
|
554
|
+
candidatePulls.map((pull) => Object.freeze({ ...pull }))
|
|
555
|
+
),
|
|
556
|
+
minAmountOutWei
|
|
557
|
+
});
|
|
558
|
+
} catch (error) {
|
|
559
|
+
const hotkey = strandedStakeSource(error);
|
|
560
|
+
if (!isActive() || !adjustRounding || !hotkey || attempt >= 8 || candidate - EVM_WEI_PER_RAO < minAmountOutWei)
|
|
561
|
+
throw error;
|
|
562
|
+
const index = candidatePulls.findIndex(
|
|
563
|
+
(pull) => pull.hotkey === hotkey
|
|
564
|
+
);
|
|
565
|
+
if (index < 0 || candidatePulls[index].amountRao <= 1n) throw error;
|
|
566
|
+
const nextPulls = candidatePulls.map(
|
|
567
|
+
(pull, i) => i === index ? { ...pull, amountRao: pull.amountRao - 1n } : pull
|
|
568
|
+
);
|
|
569
|
+
if (!fits(nextPulls)) throw error;
|
|
570
|
+
candidatePulls = nextPulls;
|
|
571
|
+
candidate -= EVM_WEI_PER_RAO;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/core/addresses.ts
|
|
577
|
+
var import_substrate_bindings = require("@polkadot-api/substrate-bindings");
|
|
578
|
+
var import_viem4 = require("viem");
|
|
579
|
+
var BITTENSOR_SS58_PREFIX = 42;
|
|
580
|
+
function normalizeEvmAddress(value) {
|
|
581
|
+
const candidate = typeof value === "string" && /^0x[0-9A-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
582
|
+
if (!(0, import_viem4.isAddress)(candidate) || candidate === import_viem4.zeroAddress) {
|
|
583
|
+
throw new ForeverMoneyError(
|
|
584
|
+
"INVALID_ADDRESS",
|
|
585
|
+
"Expected a non-zero EVM address."
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return (0, import_viem4.getAddress)(candidate);
|
|
589
|
+
}
|
|
590
|
+
function evmToMirrorSS58(evmAddress) {
|
|
591
|
+
return (0, import_substrate_bindings.AccountId)(BITTENSOR_SS58_PREFIX).dec(
|
|
592
|
+
(0, import_substrate_bindings.Blake2256)(
|
|
593
|
+
(0, import_viem4.concat)([
|
|
594
|
+
(0, import_viem4.stringToBytes)("evm:"),
|
|
595
|
+
(0, import_viem4.hexToBytes)(normalizeEvmAddress(evmAddress))
|
|
596
|
+
])
|
|
597
|
+
)
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
function decodeBittensorAddress(value) {
|
|
601
|
+
try {
|
|
602
|
+
if (typeof value !== "string" || !value || value.startsWith("0x"))
|
|
603
|
+
throw new Error("Raw hex is not SS58.");
|
|
604
|
+
const info = (0, import_substrate_bindings.getSs58AddressInfo)(value);
|
|
605
|
+
if (!info.isValid || info.ss58Format !== BITTENSOR_SS58_PREFIX || info.publicKey.length !== 32)
|
|
606
|
+
throw new Error("Invalid Bittensor address.");
|
|
607
|
+
const publicKey = info.publicKey;
|
|
608
|
+
return publicKey;
|
|
609
|
+
} catch {
|
|
610
|
+
throw new ForeverMoneyError(
|
|
611
|
+
"INVALID_SS58",
|
|
612
|
+
"Expected a Bittensor SS58 address with prefix 42."
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
function normalizeSS58(value) {
|
|
617
|
+
return (0, import_substrate_bindings.AccountId)(BITTENSOR_SS58_PREFIX).dec(decodeBittensorAddress(value));
|
|
618
|
+
}
|
|
619
|
+
function isBittensorSS58(value) {
|
|
620
|
+
try {
|
|
621
|
+
decodeBittensorAddress(value);
|
|
622
|
+
return true;
|
|
623
|
+
} catch {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function ss58ToPublicKey(value) {
|
|
628
|
+
return (0, import_viem4.bytesToHex)(decodeBittensorAddress(value));
|
|
629
|
+
}
|
|
630
|
+
|
|
413
631
|
// src/abis/index.ts
|
|
414
632
|
var ERC20_ABI = Object.freeze([
|
|
415
633
|
"function allowance(address owner,address spender) view returns (uint256)",
|
|
@@ -428,7 +646,9 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
428
646
|
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
429
647
|
"function bridgeFeeBps() view returns (uint16)",
|
|
430
648
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
649
|
+
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit) view returns (uint256 fee)",
|
|
431
650
|
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) payable returns (bytes32 messageId)",
|
|
651
|
+
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit) payable returns (bytes32 messageId)",
|
|
432
652
|
"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)",
|
|
433
653
|
"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)",
|
|
434
654
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
@@ -446,7 +666,6 @@ var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
|
446
666
|
"function bridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
447
667
|
"function bridgeOutFromValidators(uint64 destSelector,address token,address recipient,uint256 taoAmount,(bytes32 validator,uint256 alphaRao)[] sources,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
448
668
|
"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)",
|
|
449
|
-
"function minStakeRequired() view returns (uint256)",
|
|
450
669
|
"function MAX_STAKE_SOURCES() view returns (uint256)",
|
|
451
670
|
"function GATEWAY_COLDKEY() view returns (bytes32)",
|
|
452
671
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
@@ -504,7 +723,7 @@ var foreverMoneyAbis = Object.freeze({
|
|
|
504
723
|
var import_viem6 = require("viem");
|
|
505
724
|
|
|
506
725
|
// src/core/plans.ts
|
|
507
|
-
var
|
|
726
|
+
var import_viem5 = require("viem");
|
|
508
727
|
function createTransactionPlan(plan) {
|
|
509
728
|
const steps = plan.steps.map(
|
|
510
729
|
(step2) => Object.freeze({
|
|
@@ -521,93 +740,10 @@ function createTransactionPlan(plan) {
|
|
|
521
740
|
};
|
|
522
741
|
return Object.freeze({
|
|
523
742
|
...versionedPlan,
|
|
524
|
-
hash: (0,
|
|
743
|
+
hash: (0, import_viem5.keccak256)((0, import_viem5.stringToBytes)(JSON.stringify(versionedPlan)))
|
|
525
744
|
});
|
|
526
745
|
}
|
|
527
746
|
|
|
528
|
-
// src/core/validation.ts
|
|
529
|
-
var import_viem5 = require("viem");
|
|
530
|
-
function assertBigInt(amount, label) {
|
|
531
|
-
if (typeof amount !== "bigint") {
|
|
532
|
-
throw new ForeverMoneyError(
|
|
533
|
-
"INVALID_TRANSACTION_PLAN",
|
|
534
|
-
`${label} must be a bigint.`
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
function assertPositiveAmount(amount, label) {
|
|
539
|
-
assertBigInt(amount, label);
|
|
540
|
-
if (amount === 0n) {
|
|
541
|
-
throw new ForeverMoneyError(
|
|
542
|
-
"AMOUNT_ZERO",
|
|
543
|
-
`${label} must be greater than zero.`
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
if (amount < 0n) {
|
|
547
|
-
throw new ForeverMoneyError(
|
|
548
|
-
"INVALID_TRANSACTION_PLAN",
|
|
549
|
-
`${label} cannot be negative.`
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
if (amount > import_viem5.maxUint256) {
|
|
553
|
-
throw new ForeverMoneyError(
|
|
554
|
-
"INVALID_TRANSACTION_PLAN",
|
|
555
|
-
`${label} exceeds uint256.`
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
function assertNonNegativeAmount(amount, label) {
|
|
560
|
-
assertBigInt(amount, label);
|
|
561
|
-
if (amount < 0n) {
|
|
562
|
-
throw new ForeverMoneyError(
|
|
563
|
-
"INVALID_TRANSACTION_PLAN",
|
|
564
|
-
`${label} cannot be negative.`
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
if (amount > import_viem5.maxUint256) {
|
|
568
|
-
throw new ForeverMoneyError(
|
|
569
|
-
"INVALID_TRANSACTION_PLAN",
|
|
570
|
-
`${label} exceeds uint256.`
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
function assertBoolean(value, label) {
|
|
575
|
-
if (typeof value !== "boolean") {
|
|
576
|
-
throw new ForeverMoneyError(
|
|
577
|
-
"INVALID_TRANSACTION_PLAN",
|
|
578
|
-
`${label} must be a boolean.`
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
function assertArray(value, label) {
|
|
583
|
-
if (!Array.isArray(value)) {
|
|
584
|
-
throw new ForeverMoneyError(
|
|
585
|
-
"INVALID_TRANSACTION_PLAN",
|
|
586
|
-
`${label} must be an array.`
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
function assertRecord(value, label) {
|
|
591
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
592
|
-
throw new ForeverMoneyError(
|
|
593
|
-
"INVALID_TRANSACTION_PLAN",
|
|
594
|
-
`${label} must be an object.`
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
function normalizeBytes32(value, label) {
|
|
599
|
-
if (!(0, import_viem5.isHex)(value, { strict: true }) || value.length !== 66) {
|
|
600
|
-
throw new ForeverMoneyError(
|
|
601
|
-
"INVALID_BYTES32",
|
|
602
|
-
`${label} must be a 32-byte hex value.`
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
return value.toLowerCase();
|
|
606
|
-
}
|
|
607
|
-
function normalizeOptionalBytes32(value, label) {
|
|
608
|
-
return value === void 0 ? import_viem5.zeroHash : normalizeBytes32(value, label);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
747
|
// src/bridge/plans.ts
|
|
612
748
|
var erc20Abi = (0, import_viem6.parseAbi)(ERC20_ABI);
|
|
613
749
|
var spokeAbi = (0, import_viem6.parseAbi)(SPOKE_GATEWAY_ABI);
|
|
@@ -616,6 +752,18 @@ var stakingAbi = (0, import_viem6.parseAbi)(STAKING_ABI);
|
|
|
616
752
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
617
753
|
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
618
754
|
var MIN_LIQUID_SUBTENSOR_TO_EVM_WEI = 2000000n * EVM_WEI_PER_RAO;
|
|
755
|
+
var EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT = 3500000n;
|
|
756
|
+
function resolveDestinationGasLimit(value) {
|
|
757
|
+
const gasLimit = value ?? EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT;
|
|
758
|
+
if (typeof gasLimit !== "bigint" || gasLimit <= 0n) {
|
|
759
|
+
throw new ForeverMoneyError(
|
|
760
|
+
"INVALID_TRANSACTION_PLAN",
|
|
761
|
+
"Destination gas limit must be a positive bigint.",
|
|
762
|
+
{ destinationGasLimit: String(gasLimit) }
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
return gasLimit;
|
|
766
|
+
}
|
|
619
767
|
var MAX_PARTNER_FEE_BPS = 1e4;
|
|
620
768
|
var MAX_STAKE_PULLS = 16;
|
|
621
769
|
var NO_PARTNER_FEE = {
|
|
@@ -792,15 +940,15 @@ function partnerFeeSummary(charged, unit, fee) {
|
|
|
792
940
|
if (fee.bps === 0) return "";
|
|
793
941
|
return ` Partner fee: ${charged} wei of ${unit} (${fee.bps} bps) on top, paid to ${fee.recipient}.`;
|
|
794
942
|
}
|
|
795
|
-
function encodeSpokeBridge(token, amountWei, exit, fee) {
|
|
943
|
+
function encodeSpokeBridge(token, amountWei, exit, destinationGasLimit, fee) {
|
|
796
944
|
return fee.bps === 0 ? (0, import_viem6.encodeFunctionData)({
|
|
797
945
|
abi: spokeAbi,
|
|
798
946
|
functionName: "bridgeToFinney",
|
|
799
|
-
args: [token, amountWei, exit]
|
|
947
|
+
args: [token, amountWei, exit, destinationGasLimit]
|
|
800
948
|
}) : (0, import_viem6.encodeFunctionData)({
|
|
801
949
|
abi: spokeAbi,
|
|
802
950
|
functionName: "bridgeToFinneyWithFee",
|
|
803
|
-
args: [token, amountWei, exit,
|
|
951
|
+
args: [token, amountWei, exit, destinationGasLimit, fee]
|
|
804
952
|
});
|
|
805
953
|
}
|
|
806
954
|
function resolveStakePulls(pulls, amountRao) {
|
|
@@ -887,7 +1035,7 @@ function encodeHubBridge(destSelector, token, recipient, taoAmount, stakedAlphaR
|
|
|
887
1035
|
args: [...args, fee]
|
|
888
1036
|
});
|
|
889
1037
|
}
|
|
890
|
-
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee) {
|
|
1038
|
+
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, destinationGasLimit, fee) {
|
|
891
1039
|
const maxBps = await provider.readContract({
|
|
892
1040
|
address: gateway,
|
|
893
1041
|
abi: spokeAbi,
|
|
@@ -898,7 +1046,7 @@ async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee)
|
|
|
898
1046
|
address: gateway,
|
|
899
1047
|
abi: spokeAbi,
|
|
900
1048
|
functionName: "quoteBridgeToFinneyWithFee",
|
|
901
|
-
args: [token, amountWei, exit,
|
|
1049
|
+
args: [token, amountWei, exit, destinationGasLimit, fee]
|
|
902
1050
|
});
|
|
903
1051
|
return networkFee;
|
|
904
1052
|
}
|
|
@@ -944,6 +1092,9 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
944
1092
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
945
1093
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
946
1094
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
1095
|
+
const destinationGasLimit = resolveDestinationGasLimit(
|
|
1096
|
+
input.destinationGasLimit
|
|
1097
|
+
);
|
|
947
1098
|
const destination = normalizeSS58(input.destination);
|
|
948
1099
|
const exit = {
|
|
949
1100
|
ss58: ss58ToPublicKey(destination),
|
|
@@ -987,6 +1138,7 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
987
1138
|
asset.evmToken,
|
|
988
1139
|
input.amountWei,
|
|
989
1140
|
exit,
|
|
1141
|
+
destinationGasLimit,
|
|
990
1142
|
partnerFee
|
|
991
1143
|
),
|
|
992
1144
|
value,
|
|
@@ -1102,6 +1254,9 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1102
1254
|
assertDelivery(input.delivery);
|
|
1103
1255
|
assertAssetMode(input.asset, input.delivery);
|
|
1104
1256
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
1257
|
+
const destinationGasLimit = resolveDestinationGasLimit(
|
|
1258
|
+
input.destinationGasLimit
|
|
1259
|
+
);
|
|
1105
1260
|
const destination = normalizeSS58(input.destination);
|
|
1106
1261
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
1107
1262
|
const exit = {
|
|
@@ -1126,13 +1281,19 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1126
1281
|
address: evm.contracts.gateway,
|
|
1127
1282
|
abi: spokeAbi,
|
|
1128
1283
|
functionName: "quoteBridgeToFinney",
|
|
1129
|
-
args: [
|
|
1284
|
+
args: [
|
|
1285
|
+
asset.evmToken,
|
|
1286
|
+
input.amountWei,
|
|
1287
|
+
exit,
|
|
1288
|
+
destinationGasLimit
|
|
1289
|
+
]
|
|
1130
1290
|
}) : quoteSpokeWithFee(
|
|
1131
1291
|
provider,
|
|
1132
1292
|
evm.contracts.gateway,
|
|
1133
1293
|
asset.evmToken,
|
|
1134
1294
|
input.amountWei,
|
|
1135
1295
|
exit,
|
|
1296
|
+
destinationGasLimit,
|
|
1136
1297
|
partnerFee
|
|
1137
1298
|
)
|
|
1138
1299
|
]);
|
|
@@ -1142,6 +1303,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1142
1303
|
asset.evmToken,
|
|
1143
1304
|
input.amountWei,
|
|
1144
1305
|
exit,
|
|
1306
|
+
destinationGasLimit,
|
|
1145
1307
|
partnerFee
|
|
1146
1308
|
);
|
|
1147
1309
|
estimatedBridgeGas = await provider.estimateGas({
|
|
@@ -1167,7 +1329,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1167
1329
|
function prepareBaseToSubtensor(provider, input) {
|
|
1168
1330
|
return prepareEvmToSubtensor(provider, { ...input, evmChain: "base" });
|
|
1169
1331
|
}
|
|
1170
|
-
async function
|
|
1332
|
+
async function prepareSubtensorToEvmExact(provider, input) {
|
|
1171
1333
|
const sender = normalizeEvmAddress(input.sender);
|
|
1172
1334
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
1173
1335
|
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
@@ -1249,6 +1411,53 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
1249
1411
|
plan
|
|
1250
1412
|
});
|
|
1251
1413
|
}
|
|
1414
|
+
async function prepareSubtensorToEvm(provider, input) {
|
|
1415
|
+
const options = input.stakeRounding;
|
|
1416
|
+
if (options != null && options !== false && typeof options.enabled !== "boolean") {
|
|
1417
|
+
throw new ForeverMoneyError(
|
|
1418
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1419
|
+
"stakeRounding.enabled must be a boolean."
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
if (!options || !options.enabled)
|
|
1423
|
+
return prepareSubtensorToEvmExact(provider, input);
|
|
1424
|
+
if (input.source !== "staked" || !input.stakePulls?.length) {
|
|
1425
|
+
throw new ForeverMoneyError(
|
|
1426
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1427
|
+
"Stake rounding requires a staked source with named stakePulls."
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
const minimum = minimumSubtensorOutput(input);
|
|
1431
|
+
const result = await estimateRoundedStake({
|
|
1432
|
+
amountWei: input.amountWei,
|
|
1433
|
+
minAmountOutWei: minimum,
|
|
1434
|
+
pulls: input.stakePulls,
|
|
1435
|
+
positions: options.positions,
|
|
1436
|
+
minStakeRao: options.minStakeRao ?? 0n,
|
|
1437
|
+
partnerFeeBps: input.partnerFee?.bps ?? 0,
|
|
1438
|
+
quoteAndEstimate: async (amountWei, stakePulls, minAmountOutWei) => ({
|
|
1439
|
+
prepared: await prepareSubtensorToEvmExact(provider, {
|
|
1440
|
+
...input,
|
|
1441
|
+
amountWei,
|
|
1442
|
+
stakePulls,
|
|
1443
|
+
minAmountOutWei,
|
|
1444
|
+
stakeRounding: false
|
|
1445
|
+
})
|
|
1446
|
+
})
|
|
1447
|
+
});
|
|
1448
|
+
return Object.freeze({
|
|
1449
|
+
...result.prepared,
|
|
1450
|
+
stakeRounding: Object.freeze({
|
|
1451
|
+
requestedAmountWei: input.amountWei,
|
|
1452
|
+
amountWei: result.amountWei,
|
|
1453
|
+
minAmountOutWei: result.minAmountOutWei,
|
|
1454
|
+
pulls: result.pulls,
|
|
1455
|
+
simulationComplete: !result.prepared.plan.steps.some(
|
|
1456
|
+
(step2) => step2.kind === "approval"
|
|
1457
|
+
)
|
|
1458
|
+
})
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1252
1461
|
function prepareSubtensorToBase(provider, input) {
|
|
1253
1462
|
return prepareSubtensorToEvm(provider, { ...input, evmChain: "base" });
|
|
1254
1463
|
}
|