@forevermoney/sdk 0.1.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 +25 -0
- package/LICENSE +21 -0
- package/README.md +299 -0
- package/dist/index.cjs +1789 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +311 -0
- package/dist/index.d.ts +311 -0
- package/dist/index.js +1770 -0
- package/dist/index.js.map +1 -0
- package/examples/node.ts +28 -0
- package/examples/talisman.ts +192 -0
- package/package.json +74 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1789 @@
|
|
|
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
|
+
BASE_CCIP_SELECTOR: () => BASE_CCIP_SELECTOR,
|
|
24
|
+
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
25
|
+
BASIS_POINTS: () => BASIS_POINTS,
|
|
26
|
+
EVM_WEI_PER_RAO: () => EVM_WEI_PER_RAO,
|
|
27
|
+
FOREVERMONEY_DEPLOYMENT_VERSION: () => FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
28
|
+
ForeverMoneyError: () => ForeverMoneyError,
|
|
29
|
+
GAS_LIMIT_BUFFER_BPS: () => GAS_LIMIT_BUFFER_BPS,
|
|
30
|
+
MIN_LIQUID_BASE_TO_SUBTENSOR_WEI: () => MIN_LIQUID_BASE_TO_SUBTENSOR_WEI,
|
|
31
|
+
MIN_LIQUID_EVM_TO_SUBTENSOR_WEI: () => MIN_LIQUID_EVM_TO_SUBTENSOR_WEI,
|
|
32
|
+
NETWORK_FEE_BUFFER_BPS: () => NETWORK_FEE_BUFFER_BPS,
|
|
33
|
+
RAO_PER_TAO: () => RAO_PER_TAO,
|
|
34
|
+
ROBINHOOD_CCIP_SELECTOR: () => ROBINHOOD_CCIP_SELECTOR,
|
|
35
|
+
ROBINHOOD_CHAIN_ID: () => ROBINHOOD_CHAIN_ID,
|
|
36
|
+
SUBTENSOR_CCIP_SELECTOR: () => SUBTENSOR_CCIP_SELECTOR,
|
|
37
|
+
SUBTENSOR_CHAIN_ID: () => SUBTENSOR_CHAIN_ID,
|
|
38
|
+
assertWholeRao: () => assertWholeRao,
|
|
39
|
+
bridgeMessageIdFromReceipt: () => bridgeMessageIdFromReceipt,
|
|
40
|
+
buildBaseToSubtensorPlan: () => buildBaseToSubtensorPlan,
|
|
41
|
+
buildClaimVaultFeesPlan: () => buildClaimVaultFeesPlan,
|
|
42
|
+
buildCreateVaultPlan: () => buildCreateVaultPlan,
|
|
43
|
+
buildDepositVaultPlan: () => buildDepositVaultPlan,
|
|
44
|
+
buildEvmToSubtensorPlan: () => buildEvmToSubtensorPlan,
|
|
45
|
+
buildSetVaultStakingPlan: () => buildSetVaultStakingPlan,
|
|
46
|
+
buildSubtensorToBasePlan: () => buildSubtensorToBasePlan,
|
|
47
|
+
buildSubtensorToEvmPlan: () => buildSubtensorToEvmPlan,
|
|
48
|
+
buildWithdrawVaultPlan: () => buildWithdrawVaultPlan,
|
|
49
|
+
createForeverMoneyClient: () => createForeverMoneyClient,
|
|
50
|
+
destinationChainId: () => destinationChainId,
|
|
51
|
+
evmChainFromBridgeDirection: () => evmChainFromBridgeDirection,
|
|
52
|
+
evmToMirrorSS58: () => evmToMirrorSS58,
|
|
53
|
+
feeWithBuffer: () => feeWithBuffer,
|
|
54
|
+
foreverMoneyAbis: () => foreverMoneyAbis,
|
|
55
|
+
foreverMoneyDeployment: () => foreverMoneyDeployment,
|
|
56
|
+
formatTaoAmount: () => formatTaoAmount,
|
|
57
|
+
gasLimitWithBuffer: () => gasLimitWithBuffer,
|
|
58
|
+
getBridgeSourceStatus: () => getBridgeSourceStatus,
|
|
59
|
+
getCcipDeliveryCheckpoint: () => getCcipDeliveryCheckpoint,
|
|
60
|
+
getCcipDeliveryStatus: () => getCcipDeliveryStatus,
|
|
61
|
+
getForeverMoneyEvmDeployment: () => getForeverMoneyEvmDeployment,
|
|
62
|
+
http: () => http,
|
|
63
|
+
isBittensorSS58: () => isBittensorSS58,
|
|
64
|
+
isEvmToSubtensorDirection: () => isEvmToSubtensorDirection,
|
|
65
|
+
normalizeEvmAddress: () => normalizeEvmAddress,
|
|
66
|
+
normalizeSS58: () => normalizeSS58,
|
|
67
|
+
parseTaoAmount: () => parseTaoAmount,
|
|
68
|
+
sourceChainId: () => sourceChainId,
|
|
69
|
+
ss58ToPublicKey: () => ss58ToPublicKey,
|
|
70
|
+
toEip1193Transaction: () => toEip1193Transaction,
|
|
71
|
+
toEthersTransaction: () => toEthersTransaction,
|
|
72
|
+
vaultManagerFromCreationReceipt: () => vaultManagerFromCreationReceipt
|
|
73
|
+
});
|
|
74
|
+
module.exports = __toCommonJS(index_exports);
|
|
75
|
+
|
|
76
|
+
// src/core/addresses.ts
|
|
77
|
+
var import_util = require("@polkadot/util");
|
|
78
|
+
var import_util_crypto = require("@polkadot/util-crypto");
|
|
79
|
+
var import_ethers = require("ethers");
|
|
80
|
+
|
|
81
|
+
// src/core/errors.ts
|
|
82
|
+
var ForeverMoneyError = class extends Error {
|
|
83
|
+
code;
|
|
84
|
+
details;
|
|
85
|
+
constructor(code, message, details) {
|
|
86
|
+
super(message);
|
|
87
|
+
this.name = "ForeverMoneyError";
|
|
88
|
+
this.code = code;
|
|
89
|
+
this.details = details === void 0 ? void 0 : Object.freeze({ ...details });
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/core/addresses.ts
|
|
94
|
+
var BITTENSOR_SS58_PREFIX = 42;
|
|
95
|
+
function normalizeEvmAddress(value) {
|
|
96
|
+
if (!(0, import_ethers.isAddress)(value) || value === import_ethers.ZeroAddress) {
|
|
97
|
+
throw new ForeverMoneyError(
|
|
98
|
+
"INVALID_ADDRESS",
|
|
99
|
+
"Expected a non-zero EVM address."
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return (0, import_ethers.getAddress)(value);
|
|
103
|
+
}
|
|
104
|
+
function evmToMirrorSS58(evmAddress) {
|
|
105
|
+
return (0, import_util_crypto.evmToAddress)(normalizeEvmAddress(evmAddress), BITTENSOR_SS58_PREFIX);
|
|
106
|
+
}
|
|
107
|
+
function decodeBittensorAddress(value) {
|
|
108
|
+
try {
|
|
109
|
+
if (!value || (0, import_util.isHex)(value)) throw new Error("Raw hex is not SS58.");
|
|
110
|
+
const publicKey = (0, import_util_crypto.decodeAddress)(value, false, BITTENSOR_SS58_PREFIX);
|
|
111
|
+
if (publicKey.length !== 32) throw new Error("Invalid key length.");
|
|
112
|
+
return publicKey;
|
|
113
|
+
} catch {
|
|
114
|
+
throw new ForeverMoneyError(
|
|
115
|
+
"INVALID_SS58",
|
|
116
|
+
"Expected a Bittensor SS58 address with prefix 42."
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function normalizeSS58(value) {
|
|
121
|
+
return (0, import_util_crypto.encodeAddress)(decodeBittensorAddress(value), BITTENSOR_SS58_PREFIX);
|
|
122
|
+
}
|
|
123
|
+
function isBittensorSS58(value) {
|
|
124
|
+
try {
|
|
125
|
+
decodeBittensorAddress(value);
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function ss58ToPublicKey(value) {
|
|
132
|
+
return (0, import_util.u8aToHex)(decodeBittensorAddress(value));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/core/amounts.ts
|
|
136
|
+
var import_ethers3 = require("ethers");
|
|
137
|
+
|
|
138
|
+
// src/chains/deployment.ts
|
|
139
|
+
var import_ethers2 = require("ethers");
|
|
140
|
+
var BASE_CHAIN_ID = 8453;
|
|
141
|
+
var ROBINHOOD_CHAIN_ID = 4663;
|
|
142
|
+
var SUBTENSOR_CHAIN_ID = 964;
|
|
143
|
+
var BASE_CCIP_SELECTOR = 15971525489660198786n;
|
|
144
|
+
var ROBINHOOD_CCIP_SELECTOR = 6180753054346818345n;
|
|
145
|
+
var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
146
|
+
var RAO_PER_TAO = 1000000000n;
|
|
147
|
+
var EVM_WEI_PER_RAO = 1000000000n;
|
|
148
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.1.0";
|
|
149
|
+
var foreverMoneyDeployment = Object.freeze({
|
|
150
|
+
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
151
|
+
base: Object.freeze({
|
|
152
|
+
key: "base",
|
|
153
|
+
name: "Base",
|
|
154
|
+
chainId: BASE_CHAIN_ID,
|
|
155
|
+
ccipSelector: BASE_CCIP_SELECTOR,
|
|
156
|
+
contracts: Object.freeze({
|
|
157
|
+
gateway: (0, import_ethers2.getAddress)("0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C"),
|
|
158
|
+
wrappedTao: (0, import_ethers2.getAddress)(
|
|
159
|
+
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
160
|
+
),
|
|
161
|
+
ccipRouter: (0, import_ethers2.getAddress)(
|
|
162
|
+
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
163
|
+
),
|
|
164
|
+
ccipOffRampFromSubtensor: (0, import_ethers2.getAddress)(
|
|
165
|
+
"0xf09AFe78d3c7d359b334d7cB88995751F7eC5E13"
|
|
166
|
+
),
|
|
167
|
+
vaultFactory: (0, import_ethers2.getAddress)(
|
|
168
|
+
"0x9b7F3c7aa335D8BF9f91741E541820466735F868"
|
|
169
|
+
),
|
|
170
|
+
vaultManagerImplementation: (0, import_ethers2.getAddress)(
|
|
171
|
+
"0x235Ad8654fe3a1619a9892eBCaD9361F416168da"
|
|
172
|
+
),
|
|
173
|
+
batchLiquidityManagerQuery: (0, import_ethers2.getAddress)(
|
|
174
|
+
"0xd117176C5E6809b68500995F59589A8c081565A5"
|
|
175
|
+
),
|
|
176
|
+
weth: (0, import_ethers2.getAddress)("0x4200000000000000000000000000000000000006")
|
|
177
|
+
}),
|
|
178
|
+
deploymentBlock: 50098800
|
|
179
|
+
}),
|
|
180
|
+
robinhood: Object.freeze({
|
|
181
|
+
key: "robinhood",
|
|
182
|
+
name: "Robinhood",
|
|
183
|
+
chainId: ROBINHOOD_CHAIN_ID,
|
|
184
|
+
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
185
|
+
contracts: Object.freeze({
|
|
186
|
+
gateway: (0, import_ethers2.getAddress)("0x53Dcc4FE04193e489BE537F722F65317DB1E65d8"),
|
|
187
|
+
wrappedTao: (0, import_ethers2.getAddress)(
|
|
188
|
+
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
189
|
+
),
|
|
190
|
+
ccipRouter: (0, import_ethers2.getAddress)(
|
|
191
|
+
"0x06fC836cf9839B1cd891C440A0a45242DA6Ae1c9"
|
|
192
|
+
),
|
|
193
|
+
ccipOffRampFromSubtensor: (0, import_ethers2.getAddress)(
|
|
194
|
+
"0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3"
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
}),
|
|
198
|
+
subtensor: Object.freeze({
|
|
199
|
+
chainId: SUBTENSOR_CHAIN_ID,
|
|
200
|
+
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
201
|
+
contracts: Object.freeze({
|
|
202
|
+
gateway: (0, import_ethers2.getAddress)("0x998f20Fea90bF7792774dECc7f994716442B1705"),
|
|
203
|
+
alphaVault: (0, import_ethers2.getAddress)(
|
|
204
|
+
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
205
|
+
),
|
|
206
|
+
wrappedTao: (0, import_ethers2.getAddress)(
|
|
207
|
+
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
208
|
+
),
|
|
209
|
+
ccipRouter: (0, import_ethers2.getAddress)(
|
|
210
|
+
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
211
|
+
),
|
|
212
|
+
ccipOffRampFromBase: (0, import_ethers2.getAddress)(
|
|
213
|
+
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
214
|
+
),
|
|
215
|
+
ccipOffRampFromRobinhood: (0, import_ethers2.getAddress)(
|
|
216
|
+
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
217
|
+
),
|
|
218
|
+
stakingPrecompile: (0, import_ethers2.getAddress)(
|
|
219
|
+
"0x0000000000000000000000000000000000000805"
|
|
220
|
+
)
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
});
|
|
224
|
+
function getForeverMoneyEvmDeployment(chain = "base") {
|
|
225
|
+
return foreverMoneyDeployment[chain];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/core/amounts.ts
|
|
229
|
+
var BASIS_POINTS = 10000n;
|
|
230
|
+
var NETWORK_FEE_BUFFER_BPS = 200n;
|
|
231
|
+
var GAS_LIMIT_BUFFER_BPS = 5000n;
|
|
232
|
+
function parseTaoAmount(value) {
|
|
233
|
+
if (typeof value !== "string") {
|
|
234
|
+
throw new ForeverMoneyError(
|
|
235
|
+
"AMOUNT_NOT_WHOLE_RAO",
|
|
236
|
+
"TAO amounts must be decimal strings."
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const decimalPlaces = value.includes(".") ? value.split(".")[1]?.length ?? 0 : 0;
|
|
240
|
+
if (decimalPlaces > 9) {
|
|
241
|
+
throw new ForeverMoneyError(
|
|
242
|
+
"AMOUNT_NOT_WHOLE_RAO",
|
|
243
|
+
"TAO amounts must be positive decimal strings with at most nine decimal places."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
let amount;
|
|
247
|
+
try {
|
|
248
|
+
amount = (0, import_ethers3.parseUnits)(value, 18);
|
|
249
|
+
} catch {
|
|
250
|
+
throw new ForeverMoneyError(
|
|
251
|
+
"AMOUNT_NOT_WHOLE_RAO",
|
|
252
|
+
"TAO amounts must be positive decimal strings with at most nine decimal places."
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
assertWholeRao(amount);
|
|
256
|
+
return amount;
|
|
257
|
+
}
|
|
258
|
+
function assertWholeRao(amountWei) {
|
|
259
|
+
if (typeof amountWei !== "bigint") {
|
|
260
|
+
throw new ForeverMoneyError(
|
|
261
|
+
"INVALID_TRANSACTION_PLAN",
|
|
262
|
+
"The amount must be a bigint."
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (amountWei === 0n) {
|
|
266
|
+
throw new ForeverMoneyError(
|
|
267
|
+
"AMOUNT_ZERO",
|
|
268
|
+
"The amount must be greater than zero."
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (amountWei < 0n) {
|
|
272
|
+
throw new ForeverMoneyError(
|
|
273
|
+
"INVALID_TRANSACTION_PLAN",
|
|
274
|
+
"The amount cannot be negative."
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (amountWei > import_ethers3.MaxUint256) {
|
|
278
|
+
throw new ForeverMoneyError(
|
|
279
|
+
"INVALID_TRANSACTION_PLAN",
|
|
280
|
+
"The amount exceeds uint256."
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
if (amountWei % EVM_WEI_PER_RAO !== 0n) {
|
|
284
|
+
throw new ForeverMoneyError(
|
|
285
|
+
"AMOUNT_NOT_WHOLE_RAO",
|
|
286
|
+
"TAO amounts must resolve to a whole RAO."
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function formatTaoAmount(amountWei) {
|
|
291
|
+
if (typeof amountWei !== "bigint") {
|
|
292
|
+
throw new ForeverMoneyError(
|
|
293
|
+
"INVALID_TRANSACTION_PLAN",
|
|
294
|
+
"The TAO amount must be a bigint."
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
if (amountWei < 0n) {
|
|
298
|
+
throw new ForeverMoneyError(
|
|
299
|
+
"INVALID_TRANSACTION_PLAN",
|
|
300
|
+
"A TAO amount cannot be negative."
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
if (amountWei > import_ethers3.MaxUint256) {
|
|
304
|
+
throw new ForeverMoneyError(
|
|
305
|
+
"INVALID_TRANSACTION_PLAN",
|
|
306
|
+
"The TAO amount exceeds uint256."
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (amountWei % EVM_WEI_PER_RAO !== 0n) {
|
|
310
|
+
throw new ForeverMoneyError(
|
|
311
|
+
"AMOUNT_NOT_WHOLE_RAO",
|
|
312
|
+
"TAO amounts must resolve to a whole RAO."
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return (0, import_ethers3.formatUnits)(amountWei, 18);
|
|
316
|
+
}
|
|
317
|
+
function mulDivCeil(value, numerator, denominator) {
|
|
318
|
+
return (value * numerator + denominator - 1n) / denominator;
|
|
319
|
+
}
|
|
320
|
+
function feeWithBuffer(fee) {
|
|
321
|
+
if (typeof fee !== "bigint") {
|
|
322
|
+
throw new ForeverMoneyError(
|
|
323
|
+
"INVALID_TRANSACTION_PLAN",
|
|
324
|
+
"The network fee must be a bigint."
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
if (fee < 0n) {
|
|
328
|
+
throw new ForeverMoneyError(
|
|
329
|
+
"INVALID_TRANSACTION_PLAN",
|
|
330
|
+
"A network fee cannot be negative."
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const buffered = mulDivCeil(
|
|
334
|
+
fee,
|
|
335
|
+
BASIS_POINTS + NETWORK_FEE_BUFFER_BPS,
|
|
336
|
+
BASIS_POINTS
|
|
337
|
+
);
|
|
338
|
+
if (buffered > import_ethers3.MaxUint256) {
|
|
339
|
+
throw new ForeverMoneyError(
|
|
340
|
+
"INVALID_TRANSACTION_PLAN",
|
|
341
|
+
"The buffered network fee exceeds uint256."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
return buffered;
|
|
345
|
+
}
|
|
346
|
+
function gasLimitWithBuffer(gasLimit) {
|
|
347
|
+
if (typeof gasLimit !== "bigint") {
|
|
348
|
+
throw new ForeverMoneyError(
|
|
349
|
+
"INVALID_TRANSACTION_PLAN",
|
|
350
|
+
"The gas limit must be a bigint."
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (gasLimit <= 0n) {
|
|
354
|
+
throw new ForeverMoneyError(
|
|
355
|
+
"INVALID_TRANSACTION_PLAN",
|
|
356
|
+
"A gas limit must be greater than zero."
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const buffered = mulDivCeil(
|
|
360
|
+
gasLimit,
|
|
361
|
+
BASIS_POINTS + GAS_LIMIT_BUFFER_BPS,
|
|
362
|
+
BASIS_POINTS
|
|
363
|
+
);
|
|
364
|
+
if (buffered > import_ethers3.MaxUint256) {
|
|
365
|
+
throw new ForeverMoneyError(
|
|
366
|
+
"INVALID_TRANSACTION_PLAN",
|
|
367
|
+
"The buffered gas limit exceeds uint256."
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return buffered;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/abis/index.ts
|
|
374
|
+
var ERC20_ABI = Object.freeze([
|
|
375
|
+
"function allowance(address owner,address spender) view returns (uint256)",
|
|
376
|
+
"function approve(address spender,uint256 amount) returns (bool)",
|
|
377
|
+
"function balanceOf(address account) view returns (uint256)"
|
|
378
|
+
]);
|
|
379
|
+
var STAKING_ABI = Object.freeze([
|
|
380
|
+
"function allowance(address sourceAddress,address spenderAddress,uint256 netuid) view returns (uint256)",
|
|
381
|
+
"function approve(address spenderAddress,uint256 netuid,uint256 absoluteAmount)",
|
|
382
|
+
"function getStake(bytes32 hotkey,bytes32 coldkey,uint256 netuid) view returns (uint256)"
|
|
383
|
+
]);
|
|
384
|
+
var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
385
|
+
"function ROUTER() view returns (address)",
|
|
386
|
+
"function BITTENSOR_SELECTOR() view returns (uint64)",
|
|
387
|
+
"function SUBTENSOR_GATEWAY() view returns (address)",
|
|
388
|
+
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
389
|
+
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) payable returns (bytes32 messageId)",
|
|
390
|
+
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
391
|
+
]);
|
|
392
|
+
var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
393
|
+
"function ROUTER() view returns (address)",
|
|
394
|
+
"function allowedLane(uint64 selector) view returns (bool)",
|
|
395
|
+
"function quoteBridgeOut(uint64 destSelector,address token,address recipient,uint256 mintedAmount) view returns (uint256 fee)",
|
|
396
|
+
"function bridgeOut(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
397
|
+
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
398
|
+
"function claimNative(address to)",
|
|
399
|
+
"function claimStaked(address token,bytes32 destColdkey,address to)",
|
|
400
|
+
"function claimToken(address token,address to)",
|
|
401
|
+
"function claimableNative(address account) view returns (uint256)",
|
|
402
|
+
"function claimableToken(address account,address token) view returns (uint256)",
|
|
403
|
+
"event BridgedOut(uint64 destChainSelector,address indexed token,address indexed sender,address indexed recipient,uint256 minted,bytes32 messageId)",
|
|
404
|
+
"event Claimable(address indexed token,address indexed user,uint256 native,uint256 wsn)",
|
|
405
|
+
"event DeliveredLiquid(address indexed token,bytes32 indexed ss58,uint256 taoOut)",
|
|
406
|
+
"event DeliveredStaked(address indexed token,bytes32 indexed ss58,uint256 alphaRao)"
|
|
407
|
+
]);
|
|
408
|
+
var ALPHA_VAULT_ABI = Object.freeze([
|
|
409
|
+
"function isPaused() view returns (bool)",
|
|
410
|
+
"function pausedUntil() view returns (uint256)",
|
|
411
|
+
"function isListed(address token) view returns (bool)",
|
|
412
|
+
"function backing(address token) view returns (uint256 stakedAlpha)",
|
|
413
|
+
"function positionOf(address token) view returns (bytes32 validator,uint256 netuid)"
|
|
414
|
+
]);
|
|
415
|
+
var VAULT_FACTORY_ABI = Object.freeze([
|
|
416
|
+
"function create(address owner,bytes32 associatedMiner,address akAddress,address poolManager,address poolAddress,address positionManagerImpl,(address token,uint256 amount)[] stashTokens) payable returns (address manager)",
|
|
417
|
+
"event SnLiquidityManagerCreated(address indexed manager,address indexed owner,bytes32 associatedMiner,address akAddress,address poolAddress,address poolManager,address positionManager)"
|
|
418
|
+
]);
|
|
419
|
+
var VAULT_MANAGER_ABI = Object.freeze([
|
|
420
|
+
"function claimFees(address akAddress)",
|
|
421
|
+
"function topUpAk(address akAddress,address token,uint256 amount) payable",
|
|
422
|
+
"function withdrawFromAkAndPositions(address akAddress,uint256 amount0,uint256 amount1,uint256[] decreaseTokenIds,bool unwrapETH)",
|
|
423
|
+
"function stake(address akAddress,bytes data)",
|
|
424
|
+
"function unstake(address akAddress,bytes data)",
|
|
425
|
+
"function owner() view returns (address)",
|
|
426
|
+
"event AKStashTopUp(address akAddress,address token,uint256 amount)",
|
|
427
|
+
"event AKStashWithdraw(address akAddress,address token,uint256 amount)"
|
|
428
|
+
]);
|
|
429
|
+
var CCIP_EXECUTION_ABI = Object.freeze([
|
|
430
|
+
"event ExecutionStateChanged(uint64 indexed sourceChainSelector,uint64 indexed sequenceNumber,bytes32 indexed messageId,bytes32 messageHash,uint8 state,bytes returnData,uint256 gasUsed)"
|
|
431
|
+
]);
|
|
432
|
+
var CCIP_ROUTER_ABI = Object.freeze([
|
|
433
|
+
"function isOffRamp(uint64 sourceChainSelector,address offRamp) view returns (bool)"
|
|
434
|
+
]);
|
|
435
|
+
var foreverMoneyAbis = Object.freeze({
|
|
436
|
+
alphaGateway: ALPHA_GATEWAY_ABI,
|
|
437
|
+
alphaVault: ALPHA_VAULT_ABI,
|
|
438
|
+
ccipExecution: CCIP_EXECUTION_ABI,
|
|
439
|
+
ccipRouter: CCIP_ROUTER_ABI,
|
|
440
|
+
erc20: ERC20_ABI,
|
|
441
|
+
spokeGateway: SPOKE_GATEWAY_ABI,
|
|
442
|
+
staking: STAKING_ABI,
|
|
443
|
+
vaultFactory: VAULT_FACTORY_ABI,
|
|
444
|
+
vaultManager: VAULT_MANAGER_ABI
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// src/bridge/plans.ts
|
|
448
|
+
var import_ethers6 = require("ethers");
|
|
449
|
+
|
|
450
|
+
// src/core/plans.ts
|
|
451
|
+
var import_ethers4 = require("ethers");
|
|
452
|
+
function createTransactionPlan(plan) {
|
|
453
|
+
const steps = plan.steps.map(
|
|
454
|
+
(step2) => Object.freeze({
|
|
455
|
+
...step2,
|
|
456
|
+
transaction: Object.freeze({ ...step2.transaction })
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
const versionedPlan = {
|
|
460
|
+
version: "1",
|
|
461
|
+
deploymentVersion: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
462
|
+
action: plan.action,
|
|
463
|
+
summary: plan.summary,
|
|
464
|
+
steps: Object.freeze(steps)
|
|
465
|
+
};
|
|
466
|
+
return Object.freeze({
|
|
467
|
+
...versionedPlan,
|
|
468
|
+
hash: (0, import_ethers4.keccak256)((0, import_ethers4.toUtf8Bytes)(JSON.stringify(versionedPlan)))
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/core/validation.ts
|
|
473
|
+
var import_ethers5 = require("ethers");
|
|
474
|
+
function assertBigInt(amount, label) {
|
|
475
|
+
if (typeof amount !== "bigint") {
|
|
476
|
+
throw new ForeverMoneyError(
|
|
477
|
+
"INVALID_TRANSACTION_PLAN",
|
|
478
|
+
`${label} must be a bigint.`
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function assertPositiveAmount(amount, label) {
|
|
483
|
+
assertBigInt(amount, label);
|
|
484
|
+
if (amount === 0n) {
|
|
485
|
+
throw new ForeverMoneyError(
|
|
486
|
+
"AMOUNT_ZERO",
|
|
487
|
+
`${label} must be greater than zero.`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (amount < 0n) {
|
|
491
|
+
throw new ForeverMoneyError(
|
|
492
|
+
"INVALID_TRANSACTION_PLAN",
|
|
493
|
+
`${label} cannot be negative.`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
if (amount > import_ethers5.MaxUint256) {
|
|
497
|
+
throw new ForeverMoneyError(
|
|
498
|
+
"INVALID_TRANSACTION_PLAN",
|
|
499
|
+
`${label} exceeds uint256.`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function assertNonNegativeAmount(amount, label) {
|
|
504
|
+
assertBigInt(amount, label);
|
|
505
|
+
if (amount < 0n) {
|
|
506
|
+
throw new ForeverMoneyError(
|
|
507
|
+
"INVALID_TRANSACTION_PLAN",
|
|
508
|
+
`${label} cannot be negative.`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
if (amount > import_ethers5.MaxUint256) {
|
|
512
|
+
throw new ForeverMoneyError(
|
|
513
|
+
"INVALID_TRANSACTION_PLAN",
|
|
514
|
+
`${label} exceeds uint256.`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function assertBoolean(value, label) {
|
|
519
|
+
if (typeof value !== "boolean") {
|
|
520
|
+
throw new ForeverMoneyError(
|
|
521
|
+
"INVALID_TRANSACTION_PLAN",
|
|
522
|
+
`${label} must be a boolean.`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function assertArray(value, label) {
|
|
527
|
+
if (!Array.isArray(value)) {
|
|
528
|
+
throw new ForeverMoneyError(
|
|
529
|
+
"INVALID_TRANSACTION_PLAN",
|
|
530
|
+
`${label} must be an array.`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function assertRecord(value, label) {
|
|
535
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
536
|
+
throw new ForeverMoneyError(
|
|
537
|
+
"INVALID_TRANSACTION_PLAN",
|
|
538
|
+
`${label} must be an object.`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function normalizeBytes32(value, label) {
|
|
543
|
+
if (!(0, import_ethers5.isHexString)(value, 32)) {
|
|
544
|
+
throw new ForeverMoneyError(
|
|
545
|
+
"INVALID_BYTES32",
|
|
546
|
+
`${label} must be a 32-byte hex value.`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
return value.toLowerCase();
|
|
550
|
+
}
|
|
551
|
+
function normalizeOptionalBytes32(value, label) {
|
|
552
|
+
return value === void 0 ? import_ethers5.ZeroHash : normalizeBytes32(value, label);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/bridge/plans.ts
|
|
556
|
+
var erc20Interface = new import_ethers6.Interface(ERC20_ABI);
|
|
557
|
+
var spokeInterface = new import_ethers6.Interface(SPOKE_GATEWAY_ABI);
|
|
558
|
+
var alphaInterface = new import_ethers6.Interface(ALPHA_GATEWAY_ABI);
|
|
559
|
+
var stakingInterface = new import_ethers6.Interface(STAKING_ABI);
|
|
560
|
+
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
561
|
+
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
562
|
+
function assertDelivery(value) {
|
|
563
|
+
if (value !== "liquid" && value !== "staked") {
|
|
564
|
+
throw new ForeverMoneyError(
|
|
565
|
+
"INVALID_TRANSACTION_PLAN",
|
|
566
|
+
'Delivery must be "liquid" or "staked".'
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function assertSource(value) {
|
|
571
|
+
if (value !== "liquid" && value !== "staked") {
|
|
572
|
+
throw new ForeverMoneyError(
|
|
573
|
+
"INVALID_TRANSACTION_PLAN",
|
|
574
|
+
'Source must be "liquid" or "staked".'
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function assertBaseToSubtensorAmount(amountWei, delivery) {
|
|
579
|
+
assertWholeRao(amountWei);
|
|
580
|
+
if (delivery === "liquid" && amountWei < MIN_LIQUID_EVM_TO_SUBTENSOR_WEI) {
|
|
581
|
+
throw new ForeverMoneyError(
|
|
582
|
+
"AMOUNT_BELOW_MINIMUM",
|
|
583
|
+
"Liquid delivery from an EVM chain to Subtensor requires at least 0.01 TAO.",
|
|
584
|
+
{
|
|
585
|
+
amountWei: amountWei.toString(),
|
|
586
|
+
minimumAmountWei: MIN_LIQUID_EVM_TO_SUBTENSOR_WEI.toString()
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
function transactionStep(kind, label, chainId, from, to, data, value, gasLimit) {
|
|
592
|
+
return {
|
|
593
|
+
kind,
|
|
594
|
+
label,
|
|
595
|
+
transaction: {
|
|
596
|
+
chainId,
|
|
597
|
+
from,
|
|
598
|
+
to,
|
|
599
|
+
data,
|
|
600
|
+
value: value.toString(),
|
|
601
|
+
...gasLimit === void 0 ? {} : { gasLimit: gasLimit.toString() }
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function buildEvmToSubtensorPlan(input) {
|
|
606
|
+
const sender = normalizeEvmAddress(input.sender);
|
|
607
|
+
assertDelivery(input.delivery);
|
|
608
|
+
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
609
|
+
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
610
|
+
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
611
|
+
const destination = normalizeSS58(input.destination);
|
|
612
|
+
const exit = {
|
|
613
|
+
ss58: ss58ToPublicKey(destination),
|
|
614
|
+
evmFallback: sender,
|
|
615
|
+
wantLiquid: input.delivery === "liquid",
|
|
616
|
+
minTaoOut: input.amountWei
|
|
617
|
+
};
|
|
618
|
+
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
619
|
+
const steps = [];
|
|
620
|
+
if (input.allowanceWei < input.amountWei) {
|
|
621
|
+
steps.push(
|
|
622
|
+
transactionStep(
|
|
623
|
+
"approval",
|
|
624
|
+
"Approve wrapped TAO for the ForeverMoney gateway",
|
|
625
|
+
evm.chainId,
|
|
626
|
+
sender,
|
|
627
|
+
evm.contracts.wrappedTao,
|
|
628
|
+
erc20Interface.encodeFunctionData("approve", [
|
|
629
|
+
evm.contracts.gateway,
|
|
630
|
+
input.amountWei
|
|
631
|
+
]),
|
|
632
|
+
0n
|
|
633
|
+
)
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
const value = feeWithBuffer(input.exactNetworkFeeWei);
|
|
637
|
+
steps.push(
|
|
638
|
+
transactionStep(
|
|
639
|
+
"transaction",
|
|
640
|
+
`Bridge wrapped TAO from ${evm.name} to Subtensor (${input.delivery})`,
|
|
641
|
+
evm.chainId,
|
|
642
|
+
sender,
|
|
643
|
+
evm.contracts.gateway,
|
|
644
|
+
spokeInterface.encodeFunctionData(
|
|
645
|
+
"bridgeToFinney(address,uint256,(bytes32,address,bool,uint256))",
|
|
646
|
+
[evm.contracts.wrappedTao, input.amountWei, exit]
|
|
647
|
+
),
|
|
648
|
+
value,
|
|
649
|
+
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
650
|
+
)
|
|
651
|
+
);
|
|
652
|
+
return createTransactionPlan({
|
|
653
|
+
action: evm.key === "base" ? "bridge.base-to-subtensor" : "bridge.robinhood-to-subtensor",
|
|
654
|
+
summary: `Bridge ${input.amountWei} wei of wrapped TAO from ${evm.name} to ${destination}.`,
|
|
655
|
+
steps
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
function buildBaseToSubtensorPlan(input) {
|
|
659
|
+
return buildEvmToSubtensorPlan({ ...input, evmChain: "base" });
|
|
660
|
+
}
|
|
661
|
+
function buildSubtensorToEvmPlan(input) {
|
|
662
|
+
const sender = normalizeEvmAddress(input.sender);
|
|
663
|
+
const recipient = normalizeEvmAddress(input.recipient);
|
|
664
|
+
assertSource(input.source);
|
|
665
|
+
assertWholeRao(input.amountWei);
|
|
666
|
+
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
667
|
+
const { subtensor } = foreverMoneyDeployment;
|
|
668
|
+
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
669
|
+
const amountRao = input.amountWei / EVM_WEI_PER_RAO;
|
|
670
|
+
const steps = [];
|
|
671
|
+
if (input.source === "staked") {
|
|
672
|
+
if (input.netuid === void 0) {
|
|
673
|
+
throw new ForeverMoneyError(
|
|
674
|
+
"INVALID_TRANSACTION_PLAN",
|
|
675
|
+
"A netuid is required when bridging staked TAO."
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
assertNonNegativeAmount(input.netuid, "netuid");
|
|
679
|
+
if (input.stakingAllowanceRao === void 0) {
|
|
680
|
+
throw new ForeverMoneyError(
|
|
681
|
+
"INVALID_TRANSACTION_PLAN",
|
|
682
|
+
"The staking allowance is required when bridging staked TAO."
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
assertNonNegativeAmount(input.stakingAllowanceRao, "Staking allowance");
|
|
686
|
+
if (input.stakingAllowanceRao < amountRao) {
|
|
687
|
+
steps.push(
|
|
688
|
+
transactionStep(
|
|
689
|
+
"approval",
|
|
690
|
+
"Approve staked TAO for the ForeverMoney gateway",
|
|
691
|
+
subtensor.chainId,
|
|
692
|
+
sender,
|
|
693
|
+
subtensor.contracts.stakingPrecompile,
|
|
694
|
+
stakingInterface.encodeFunctionData("approve", [
|
|
695
|
+
subtensor.contracts.gateway,
|
|
696
|
+
input.netuid,
|
|
697
|
+
amountRao
|
|
698
|
+
]),
|
|
699
|
+
0n
|
|
700
|
+
)
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
} else if (input.netuid !== void 0 || input.stakingAllowanceRao !== void 0) {
|
|
704
|
+
throw new ForeverMoneyError(
|
|
705
|
+
"INVALID_TRANSACTION_PLAN",
|
|
706
|
+
"netuid and staking allowance are only valid for staked TAO."
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
710
|
+
const stakedAlphaRao = input.source === "staked" ? amountRao : 0n;
|
|
711
|
+
const value = taoAmount + feeWithBuffer(input.exactNetworkFeeWei);
|
|
712
|
+
assertNonNegativeAmount(value, "Transaction value");
|
|
713
|
+
steps.push(
|
|
714
|
+
transactionStep(
|
|
715
|
+
"transaction",
|
|
716
|
+
`Bridge ${input.source} TAO from Subtensor to ${evm.name}`,
|
|
717
|
+
subtensor.chainId,
|
|
718
|
+
sender,
|
|
719
|
+
subtensor.contracts.gateway,
|
|
720
|
+
alphaInterface.encodeFunctionData("bridgeOut", [
|
|
721
|
+
evm.ccipSelector,
|
|
722
|
+
subtensor.contracts.wrappedTao,
|
|
723
|
+
recipient,
|
|
724
|
+
taoAmount,
|
|
725
|
+
stakedAlphaRao,
|
|
726
|
+
input.amountWei
|
|
727
|
+
]),
|
|
728
|
+
value,
|
|
729
|
+
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
730
|
+
)
|
|
731
|
+
);
|
|
732
|
+
return createTransactionPlan({
|
|
733
|
+
action: evm.key === "base" ? "bridge.subtensor-to-base" : "bridge.subtensor-to-robinhood",
|
|
734
|
+
summary: `Bridge ${input.amountWei} wei of ${input.source} TAO from Subtensor to ${recipient} on ${evm.name}.`,
|
|
735
|
+
steps
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
function buildSubtensorToBasePlan(input) {
|
|
739
|
+
return buildSubtensorToEvmPlan({ ...input, evmChain: "base" });
|
|
740
|
+
}
|
|
741
|
+
async function prepareEvmToSubtensor(provider, input) {
|
|
742
|
+
const sender = normalizeEvmAddress(input.sender);
|
|
743
|
+
assertDelivery(input.delivery);
|
|
744
|
+
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
745
|
+
const destination = normalizeSS58(input.destination);
|
|
746
|
+
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
747
|
+
const exit = {
|
|
748
|
+
ss58: ss58ToPublicKey(destination),
|
|
749
|
+
evmFallback: sender,
|
|
750
|
+
wantLiquid: input.delivery === "liquid",
|
|
751
|
+
minTaoOut: input.amountWei
|
|
752
|
+
};
|
|
753
|
+
const token = new import_ethers6.Contract(evm.contracts.wrappedTao, ERC20_ABI, provider);
|
|
754
|
+
const gateway = new import_ethers6.Contract(
|
|
755
|
+
evm.contracts.gateway,
|
|
756
|
+
SPOKE_GATEWAY_ABI,
|
|
757
|
+
provider
|
|
758
|
+
);
|
|
759
|
+
const [allowanceWei, exactNetworkFeeWei] = await Promise.all([
|
|
760
|
+
token.getFunction("allowance")(
|
|
761
|
+
sender,
|
|
762
|
+
evm.contracts.gateway
|
|
763
|
+
),
|
|
764
|
+
gateway.getFunction(
|
|
765
|
+
"quoteBridgeToFinney(address,uint256,(bytes32,address,bool,uint256))"
|
|
766
|
+
)(evm.contracts.wrappedTao, input.amountWei, exit)
|
|
767
|
+
]);
|
|
768
|
+
let estimatedBridgeGas;
|
|
769
|
+
if (allowanceWei >= input.amountWei) {
|
|
770
|
+
const data = spokeInterface.encodeFunctionData(
|
|
771
|
+
"bridgeToFinney(address,uint256,(bytes32,address,bool,uint256))",
|
|
772
|
+
[evm.contracts.wrappedTao, input.amountWei, exit]
|
|
773
|
+
);
|
|
774
|
+
estimatedBridgeGas = await provider.estimateGas({
|
|
775
|
+
from: sender,
|
|
776
|
+
to: evm.contracts.gateway,
|
|
777
|
+
data,
|
|
778
|
+
value: feeWithBuffer(exactNetworkFeeWei)
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
const plan = buildEvmToSubtensorPlan({
|
|
782
|
+
...input,
|
|
783
|
+
allowanceWei,
|
|
784
|
+
exactNetworkFeeWei,
|
|
785
|
+
...estimatedBridgeGas === void 0 ? {} : { estimatedBridgeGas }
|
|
786
|
+
});
|
|
787
|
+
return Object.freeze({
|
|
788
|
+
exactNetworkFeeWei,
|
|
789
|
+
transactionValueWei: feeWithBuffer(exactNetworkFeeWei),
|
|
790
|
+
plan
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function prepareBaseToSubtensor(provider, input) {
|
|
794
|
+
return prepareEvmToSubtensor(provider, { ...input, evmChain: "base" });
|
|
795
|
+
}
|
|
796
|
+
async function prepareSubtensorToEvm(provider, input) {
|
|
797
|
+
const sender = normalizeEvmAddress(input.sender);
|
|
798
|
+
const recipient = normalizeEvmAddress(input.recipient);
|
|
799
|
+
assertSource(input.source);
|
|
800
|
+
assertWholeRao(input.amountWei);
|
|
801
|
+
const { subtensor } = foreverMoneyDeployment;
|
|
802
|
+
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
803
|
+
const gateway = new import_ethers6.Contract(
|
|
804
|
+
subtensor.contracts.gateway,
|
|
805
|
+
ALPHA_GATEWAY_ABI,
|
|
806
|
+
provider
|
|
807
|
+
);
|
|
808
|
+
const exactNetworkFeeWei = await gateway.getFunction("quoteBridgeOut")(
|
|
809
|
+
evm.ccipSelector,
|
|
810
|
+
subtensor.contracts.wrappedTao,
|
|
811
|
+
recipient,
|
|
812
|
+
input.amountWei
|
|
813
|
+
);
|
|
814
|
+
let stakingAllowanceRao;
|
|
815
|
+
if (input.source === "staked") {
|
|
816
|
+
if (input.netuid === void 0) {
|
|
817
|
+
throw new ForeverMoneyError(
|
|
818
|
+
"INVALID_TRANSACTION_PLAN",
|
|
819
|
+
"A netuid is required when bridging staked TAO."
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
assertNonNegativeAmount(input.netuid, "netuid");
|
|
823
|
+
const staking = new import_ethers6.Contract(
|
|
824
|
+
subtensor.contracts.stakingPrecompile,
|
|
825
|
+
STAKING_ABI,
|
|
826
|
+
provider
|
|
827
|
+
);
|
|
828
|
+
stakingAllowanceRao = await staking.getFunction("allowance")(
|
|
829
|
+
sender,
|
|
830
|
+
subtensor.contracts.gateway,
|
|
831
|
+
input.netuid
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
835
|
+
const stakedAlphaRao = input.source === "staked" ? input.amountWei / EVM_WEI_PER_RAO : 0n;
|
|
836
|
+
const value = taoAmount + feeWithBuffer(exactNetworkFeeWei);
|
|
837
|
+
assertNonNegativeAmount(value, "Transaction value");
|
|
838
|
+
let estimatedBridgeGas;
|
|
839
|
+
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao) {
|
|
840
|
+
estimatedBridgeGas = await provider.estimateGas({
|
|
841
|
+
from: sender,
|
|
842
|
+
to: subtensor.contracts.gateway,
|
|
843
|
+
data: alphaInterface.encodeFunctionData("bridgeOut", [
|
|
844
|
+
evm.ccipSelector,
|
|
845
|
+
subtensor.contracts.wrappedTao,
|
|
846
|
+
recipient,
|
|
847
|
+
taoAmount,
|
|
848
|
+
stakedAlphaRao,
|
|
849
|
+
input.amountWei
|
|
850
|
+
]),
|
|
851
|
+
value
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
const plan = buildSubtensorToEvmPlan({
|
|
855
|
+
...input,
|
|
856
|
+
exactNetworkFeeWei,
|
|
857
|
+
...stakingAllowanceRao === void 0 ? {} : { stakingAllowanceRao },
|
|
858
|
+
...estimatedBridgeGas === void 0 ? {} : { estimatedBridgeGas }
|
|
859
|
+
});
|
|
860
|
+
return Object.freeze({
|
|
861
|
+
exactNetworkFeeWei,
|
|
862
|
+
transactionValueWei: value,
|
|
863
|
+
plan
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
function prepareSubtensorToBase(provider, input) {
|
|
867
|
+
return prepareSubtensorToEvm(provider, { ...input, evmChain: "base" });
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/core/transport.ts
|
|
871
|
+
var import_ethers7 = require("ethers");
|
|
872
|
+
function isRecord(value) {
|
|
873
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
874
|
+
}
|
|
875
|
+
function invalidJsonRpcResponse() {
|
|
876
|
+
return new ForeverMoneyError(
|
|
877
|
+
"RPC_ERROR",
|
|
878
|
+
"RPC returned an invalid JSON-RPC response."
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
function isLoopback(endpoint) {
|
|
882
|
+
return endpoint.hostname === "localhost" || endpoint.hostname === "127.0.0.1" || endpoint.hostname === "[::1]";
|
|
883
|
+
}
|
|
884
|
+
function jsonRpcResult(body, expectedId) {
|
|
885
|
+
if (!isRecord(body) || body.jsonrpc !== "2.0" || body.id !== expectedId) {
|
|
886
|
+
throw invalidJsonRpcResponse();
|
|
887
|
+
}
|
|
888
|
+
if (body.error !== void 0) {
|
|
889
|
+
if (!isRecord(body.error) || !Number.isInteger(body.error.code) || typeof body.error.message !== "string") {
|
|
890
|
+
throw invalidJsonRpcResponse();
|
|
891
|
+
}
|
|
892
|
+
throw new ForeverMoneyError("RPC_ERROR", body.error.message, {
|
|
893
|
+
rpcCode: body.error.code,
|
|
894
|
+
...body.error.data === void 0 ? {} : { rpcData: body.error.data }
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
if (!Object.prototype.hasOwnProperty.call(body, "result")) {
|
|
898
|
+
throw invalidJsonRpcResponse();
|
|
899
|
+
}
|
|
900
|
+
return body.result;
|
|
901
|
+
}
|
|
902
|
+
function http(url, options = {}) {
|
|
903
|
+
let endpoint;
|
|
904
|
+
try {
|
|
905
|
+
endpoint = new URL(url);
|
|
906
|
+
} catch {
|
|
907
|
+
throw new ForeverMoneyError("RPC_ERROR", "RPC URL is invalid.");
|
|
908
|
+
}
|
|
909
|
+
if (endpoint.protocol !== "https:" && endpoint.protocol !== "http:") {
|
|
910
|
+
throw new ForeverMoneyError(
|
|
911
|
+
"RPC_ERROR",
|
|
912
|
+
"RPC URL must use HTTP or HTTPS."
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
if (endpoint.protocol === "http:" && !isLoopback(endpoint)) {
|
|
916
|
+
throw new ForeverMoneyError(
|
|
917
|
+
"RPC_ERROR",
|
|
918
|
+
"Non-local RPC URLs must use HTTPS."
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
922
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
|
|
923
|
+
throw new ForeverMoneyError(
|
|
924
|
+
"RPC_ERROR",
|
|
925
|
+
"RPC timeout must be a positive integer."
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
929
|
+
if (typeof fetchImplementation !== "function") {
|
|
930
|
+
throw new ForeverMoneyError(
|
|
931
|
+
"RPC_ERROR",
|
|
932
|
+
"This runtime does not provide fetch. Pass an explicit fetch implementation."
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
let requestId = 0;
|
|
936
|
+
return Object.freeze({
|
|
937
|
+
async request(request) {
|
|
938
|
+
if (!isRecord(request) || typeof request.method !== "string" || request.method.length === 0) {
|
|
939
|
+
throw new ForeverMoneyError(
|
|
940
|
+
"RPC_ERROR",
|
|
941
|
+
"RPC method must be a non-empty string."
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
if (request.params !== void 0 && !Array.isArray(request.params) && !isRecord(request.params)) {
|
|
945
|
+
throw new ForeverMoneyError(
|
|
946
|
+
"RPC_ERROR",
|
|
947
|
+
"RPC params must be an array or object."
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
const controller = new AbortController();
|
|
951
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
952
|
+
const id = ++requestId;
|
|
953
|
+
try {
|
|
954
|
+
const response = await fetchImplementation(endpoint, {
|
|
955
|
+
method: "POST",
|
|
956
|
+
headers: { "content-type": "application/json" },
|
|
957
|
+
body: JSON.stringify({
|
|
958
|
+
jsonrpc: "2.0",
|
|
959
|
+
id,
|
|
960
|
+
method: request.method,
|
|
961
|
+
params: request.params ?? []
|
|
962
|
+
}),
|
|
963
|
+
signal: controller.signal
|
|
964
|
+
});
|
|
965
|
+
if (!response.ok) {
|
|
966
|
+
throw new ForeverMoneyError(
|
|
967
|
+
"RPC_ERROR",
|
|
968
|
+
`RPC returned HTTP ${response.status}.`,
|
|
969
|
+
{ status: response.status }
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
return jsonRpcResult(await response.json(), id);
|
|
973
|
+
} catch (error) {
|
|
974
|
+
if (error instanceof ForeverMoneyError) throw error;
|
|
975
|
+
const message = error instanceof Error && error.name === "AbortError" ? `RPC request timed out after ${timeoutMs}ms.` : "RPC request failed.";
|
|
976
|
+
throw new ForeverMoneyError("RPC_ERROR", message);
|
|
977
|
+
} finally {
|
|
978
|
+
clearTimeout(timer);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
function providerFromTransport(transport) {
|
|
984
|
+
if (!transport || typeof transport.request !== "function") {
|
|
985
|
+
throw new ForeverMoneyError(
|
|
986
|
+
"MISSING_TRANSPORT",
|
|
987
|
+
"An EIP-1193-compatible RPC transport is required."
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
return new import_ethers7.BrowserProvider(transport);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/core/provider-errors.ts
|
|
994
|
+
function stringProperty(value, property) {
|
|
995
|
+
if (typeof value !== "object" || value === null || !(property in value)) {
|
|
996
|
+
return void 0;
|
|
997
|
+
}
|
|
998
|
+
const entry = value[property];
|
|
999
|
+
return typeof entry === "string" ? entry : void 0;
|
|
1000
|
+
}
|
|
1001
|
+
async function providerOperation(operation, action) {
|
|
1002
|
+
try {
|
|
1003
|
+
return await action();
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
if (error instanceof ForeverMoneyError) throw error;
|
|
1006
|
+
const causeCode = stringProperty(error, "code");
|
|
1007
|
+
const reason = stringProperty(error, "reason");
|
|
1008
|
+
const reverted = causeCode === "CALL_EXCEPTION";
|
|
1009
|
+
throw new ForeverMoneyError(
|
|
1010
|
+
reverted ? "SIMULATION_REVERTED" : "RPC_ERROR",
|
|
1011
|
+
reverted ? `${operation} reverted.` : `${operation} failed.`,
|
|
1012
|
+
{
|
|
1013
|
+
...causeCode === void 0 ? {} : { causeCode },
|
|
1014
|
+
...reason === void 0 ? {} : { reason }
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/vaults/plans.ts
|
|
1021
|
+
var import_ethers8 = require("ethers");
|
|
1022
|
+
var erc20Interface2 = new import_ethers8.Interface(ERC20_ABI);
|
|
1023
|
+
var factoryInterface = new import_ethers8.Interface(VAULT_FACTORY_ABI);
|
|
1024
|
+
var managerInterface = new import_ethers8.Interface(VAULT_MANAGER_ABI);
|
|
1025
|
+
function step(kind, label, from, to, data, value = 0n) {
|
|
1026
|
+
return {
|
|
1027
|
+
kind,
|
|
1028
|
+
label,
|
|
1029
|
+
transaction: {
|
|
1030
|
+
chainId: foreverMoneyDeployment.base.chainId,
|
|
1031
|
+
from,
|
|
1032
|
+
to,
|
|
1033
|
+
data,
|
|
1034
|
+
value: value.toString()
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function normalizeStashTokens(stashTokens) {
|
|
1039
|
+
assertArray(stashTokens, "Stash tokens");
|
|
1040
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1041
|
+
return stashTokens.map((entry) => {
|
|
1042
|
+
assertRecord(entry, "Stash token");
|
|
1043
|
+
if (typeof entry.token !== "string") {
|
|
1044
|
+
throw new ForeverMoneyError(
|
|
1045
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1046
|
+
"Stash token address must be a string."
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
const normalizedToken = normalizeEvmAddress(entry.token);
|
|
1050
|
+
assertPositiveAmount(entry.amount, "Stash amount");
|
|
1051
|
+
const key = normalizedToken.toLowerCase();
|
|
1052
|
+
if (seen.has(key)) {
|
|
1053
|
+
throw new ForeverMoneyError(
|
|
1054
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1055
|
+
`Duplicate stash token: ${normalizedToken}.`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
seen.add(key);
|
|
1059
|
+
return Object.freeze({
|
|
1060
|
+
token: normalizedToken,
|
|
1061
|
+
amount: entry.amount
|
|
1062
|
+
});
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
function allowanceMap(allowances) {
|
|
1066
|
+
assertArray(allowances, "Token allowances");
|
|
1067
|
+
const result = /* @__PURE__ */ new Map();
|
|
1068
|
+
for (const entry of allowances) {
|
|
1069
|
+
assertRecord(entry, "Token allowance");
|
|
1070
|
+
if (typeof entry.token !== "string") {
|
|
1071
|
+
throw new ForeverMoneyError(
|
|
1072
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1073
|
+
"Allowance token address must be a string."
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
const token = normalizeEvmAddress(entry.token).toLowerCase();
|
|
1077
|
+
assertNonNegativeAmount(entry.allowance, "Token allowance");
|
|
1078
|
+
if (result.has(token)) {
|
|
1079
|
+
throw new ForeverMoneyError(
|
|
1080
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1081
|
+
`Duplicate allowance for token ${entry.token}.`
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
result.set(token, entry.allowance);
|
|
1085
|
+
}
|
|
1086
|
+
return result;
|
|
1087
|
+
}
|
|
1088
|
+
function assertAllowanceSet(allowances, tokens) {
|
|
1089
|
+
const expected = new Set(
|
|
1090
|
+
tokens.filter(
|
|
1091
|
+
({ token }) => token !== foreverMoneyDeployment.base.contracts.weth
|
|
1092
|
+
).map(({ token }) => token.toLowerCase())
|
|
1093
|
+
);
|
|
1094
|
+
for (const token of allowances.keys()) {
|
|
1095
|
+
if (!expected.has(token)) {
|
|
1096
|
+
throw new ForeverMoneyError(
|
|
1097
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1098
|
+
`Unexpected allowance for token ${token}.`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function buildCreateVaultPlan(input) {
|
|
1104
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1105
|
+
const akAddress = normalizeEvmAddress(input.akAddress);
|
|
1106
|
+
const poolManager = normalizeEvmAddress(input.poolManager);
|
|
1107
|
+
const poolAddress = normalizeEvmAddress(input.poolAddress);
|
|
1108
|
+
const positionManagerImplementation = normalizeEvmAddress(
|
|
1109
|
+
input.positionManagerImplementation
|
|
1110
|
+
);
|
|
1111
|
+
const associatedMiner = normalizeOptionalBytes32(
|
|
1112
|
+
input.associatedMiner,
|
|
1113
|
+
"Associated miner"
|
|
1114
|
+
);
|
|
1115
|
+
const stashTokens = normalizeStashTokens(input.stashTokens);
|
|
1116
|
+
const allowances = allowanceMap(input.allowances);
|
|
1117
|
+
assertAllowanceSet(allowances, stashTokens);
|
|
1118
|
+
const { base } = foreverMoneyDeployment;
|
|
1119
|
+
const steps = [];
|
|
1120
|
+
let value = 0n;
|
|
1121
|
+
for (const stash of stashTokens) {
|
|
1122
|
+
if (stash.token === base.contracts.weth) {
|
|
1123
|
+
value += stash.amount;
|
|
1124
|
+
assertNonNegativeAmount(value, "Vault creation value");
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
const allowance = allowances.get(stash.token.toLowerCase());
|
|
1128
|
+
if (allowance === void 0) {
|
|
1129
|
+
throw new ForeverMoneyError(
|
|
1130
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1131
|
+
`Missing allowance for stash token ${stash.token}.`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
if (allowance < stash.amount) {
|
|
1135
|
+
steps.push(
|
|
1136
|
+
step(
|
|
1137
|
+
"approval",
|
|
1138
|
+
`Approve ${stash.token} for the vault factory`,
|
|
1139
|
+
owner,
|
|
1140
|
+
stash.token,
|
|
1141
|
+
erc20Interface2.encodeFunctionData("approve", [
|
|
1142
|
+
base.contracts.vaultFactory,
|
|
1143
|
+
stash.amount
|
|
1144
|
+
])
|
|
1145
|
+
)
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
steps.push(
|
|
1150
|
+
step(
|
|
1151
|
+
"transaction",
|
|
1152
|
+
"Create ForeverMoney vault",
|
|
1153
|
+
owner,
|
|
1154
|
+
base.contracts.vaultFactory,
|
|
1155
|
+
factoryInterface.encodeFunctionData("create", [
|
|
1156
|
+
owner,
|
|
1157
|
+
associatedMiner,
|
|
1158
|
+
akAddress,
|
|
1159
|
+
poolManager,
|
|
1160
|
+
poolAddress,
|
|
1161
|
+
positionManagerImplementation,
|
|
1162
|
+
stashTokens
|
|
1163
|
+
]),
|
|
1164
|
+
value
|
|
1165
|
+
)
|
|
1166
|
+
);
|
|
1167
|
+
return createTransactionPlan({
|
|
1168
|
+
action: "vault.create",
|
|
1169
|
+
summary: `Create a vault owned by ${owner} with ${stashTokens.length} initial stash token(s).`,
|
|
1170
|
+
steps
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
async function prepareCreateVault(provider, input) {
|
|
1174
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1175
|
+
const stashTokens = normalizeStashTokens(input.stashTokens);
|
|
1176
|
+
const erc20Stash = stashTokens.filter(
|
|
1177
|
+
({ token }) => token !== foreverMoneyDeployment.base.contracts.weth
|
|
1178
|
+
);
|
|
1179
|
+
const allowances = await Promise.all(
|
|
1180
|
+
erc20Stash.map(async ({ token }) => ({
|
|
1181
|
+
token,
|
|
1182
|
+
allowance: await new import_ethers8.Contract(
|
|
1183
|
+
token,
|
|
1184
|
+
ERC20_ABI,
|
|
1185
|
+
provider
|
|
1186
|
+
).getFunction("allowance")(
|
|
1187
|
+
owner,
|
|
1188
|
+
foreverMoneyDeployment.base.contracts.vaultFactory
|
|
1189
|
+
)
|
|
1190
|
+
}))
|
|
1191
|
+
);
|
|
1192
|
+
return buildCreateVaultPlan({ ...input, allowances });
|
|
1193
|
+
}
|
|
1194
|
+
function buildDepositVaultPlan(input) {
|
|
1195
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1196
|
+
const manager = normalizeEvmAddress(input.manager);
|
|
1197
|
+
const akAddress = normalizeEvmAddress(input.akAddress);
|
|
1198
|
+
const deposits = normalizeStashTokens(input.deposits);
|
|
1199
|
+
if (deposits.length === 0) {
|
|
1200
|
+
throw new ForeverMoneyError(
|
|
1201
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1202
|
+
"At least one vault deposit is required."
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
const allowances = allowanceMap(input.allowances);
|
|
1206
|
+
assertAllowanceSet(allowances, deposits);
|
|
1207
|
+
const steps = [];
|
|
1208
|
+
for (const deposit of deposits) {
|
|
1209
|
+
if (deposit.token === foreverMoneyDeployment.base.contracts.weth) {
|
|
1210
|
+
steps.push(
|
|
1211
|
+
step(
|
|
1212
|
+
"transaction",
|
|
1213
|
+
"Deposit native ETH into the vault as WETH",
|
|
1214
|
+
owner,
|
|
1215
|
+
manager,
|
|
1216
|
+
managerInterface.encodeFunctionData("topUpAk", [
|
|
1217
|
+
akAddress,
|
|
1218
|
+
import_ethers8.ZeroAddress,
|
|
1219
|
+
deposit.amount
|
|
1220
|
+
]),
|
|
1221
|
+
deposit.amount
|
|
1222
|
+
)
|
|
1223
|
+
);
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
const allowance = allowances.get(deposit.token.toLowerCase());
|
|
1227
|
+
if (allowance === void 0) {
|
|
1228
|
+
throw new ForeverMoneyError(
|
|
1229
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1230
|
+
`Missing allowance for deposit token ${deposit.token}.`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
if (allowance < deposit.amount) {
|
|
1234
|
+
steps.push(
|
|
1235
|
+
step(
|
|
1236
|
+
"approval",
|
|
1237
|
+
`Approve ${deposit.token} for the vault manager`,
|
|
1238
|
+
owner,
|
|
1239
|
+
deposit.token,
|
|
1240
|
+
erc20Interface2.encodeFunctionData("approve", [
|
|
1241
|
+
manager,
|
|
1242
|
+
deposit.amount
|
|
1243
|
+
])
|
|
1244
|
+
)
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
steps.push(
|
|
1248
|
+
step(
|
|
1249
|
+
"transaction",
|
|
1250
|
+
`Deposit ${deposit.token} into the vault`,
|
|
1251
|
+
owner,
|
|
1252
|
+
manager,
|
|
1253
|
+
managerInterface.encodeFunctionData("topUpAk", [
|
|
1254
|
+
akAddress,
|
|
1255
|
+
deposit.token,
|
|
1256
|
+
deposit.amount
|
|
1257
|
+
])
|
|
1258
|
+
)
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
return createTransactionPlan({
|
|
1262
|
+
action: "vault.deposit",
|
|
1263
|
+
summary: `Deposit ${deposits.length} token amount(s) into vault ${manager}.`,
|
|
1264
|
+
steps
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
async function prepareDepositVault(provider, input) {
|
|
1268
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1269
|
+
const manager = normalizeEvmAddress(input.manager);
|
|
1270
|
+
const deposits = normalizeStashTokens(input.deposits);
|
|
1271
|
+
const erc20Deposits = deposits.filter(
|
|
1272
|
+
({ token }) => token !== foreverMoneyDeployment.base.contracts.weth
|
|
1273
|
+
);
|
|
1274
|
+
const allowances = await Promise.all(
|
|
1275
|
+
erc20Deposits.map(async ({ token }) => ({
|
|
1276
|
+
token,
|
|
1277
|
+
allowance: await new import_ethers8.Contract(
|
|
1278
|
+
token,
|
|
1279
|
+
ERC20_ABI,
|
|
1280
|
+
provider
|
|
1281
|
+
).getFunction("allowance")(owner, manager)
|
|
1282
|
+
}))
|
|
1283
|
+
);
|
|
1284
|
+
return buildDepositVaultPlan({ ...input, allowances });
|
|
1285
|
+
}
|
|
1286
|
+
function buildWithdrawVaultPlan(input) {
|
|
1287
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1288
|
+
const manager = normalizeEvmAddress(input.manager);
|
|
1289
|
+
const akAddress = normalizeEvmAddress(input.akAddress);
|
|
1290
|
+
assertArray(input.decreaseTokenIds, "Position token IDs");
|
|
1291
|
+
assertBoolean(input.unwrapWeth, "unwrapWeth");
|
|
1292
|
+
assertNonNegativeAmount(input.amount0, "Token 0 withdrawal");
|
|
1293
|
+
assertNonNegativeAmount(input.amount1, "Token 1 withdrawal");
|
|
1294
|
+
if (input.amount0 === 0n && input.amount1 === 0n) {
|
|
1295
|
+
throw new ForeverMoneyError(
|
|
1296
|
+
"AMOUNT_ZERO",
|
|
1297
|
+
"At least one withdrawal amount must be greater than zero."
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
for (const tokenId of input.decreaseTokenIds) {
|
|
1301
|
+
assertNonNegativeAmount(tokenId, "Position token ID");
|
|
1302
|
+
}
|
|
1303
|
+
return createTransactionPlan({
|
|
1304
|
+
action: "vault.withdraw",
|
|
1305
|
+
summary: `Withdraw token amounts from vault ${manager}.`,
|
|
1306
|
+
steps: [
|
|
1307
|
+
step(
|
|
1308
|
+
"transaction",
|
|
1309
|
+
"Withdraw assets from the vault",
|
|
1310
|
+
owner,
|
|
1311
|
+
manager,
|
|
1312
|
+
managerInterface.encodeFunctionData(
|
|
1313
|
+
"withdrawFromAkAndPositions",
|
|
1314
|
+
[
|
|
1315
|
+
akAddress,
|
|
1316
|
+
input.amount0,
|
|
1317
|
+
input.amount1,
|
|
1318
|
+
input.decreaseTokenIds,
|
|
1319
|
+
input.unwrapWeth
|
|
1320
|
+
]
|
|
1321
|
+
)
|
|
1322
|
+
)
|
|
1323
|
+
]
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
function buildClaimVaultFeesPlan(input) {
|
|
1327
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1328
|
+
const manager = normalizeEvmAddress(input.manager);
|
|
1329
|
+
const akAddress = normalizeEvmAddress(input.akAddress);
|
|
1330
|
+
return createTransactionPlan({
|
|
1331
|
+
action: "vault.claim-fees",
|
|
1332
|
+
summary: `Claim all fees for ${akAddress} from vault ${manager}.`,
|
|
1333
|
+
steps: [
|
|
1334
|
+
step(
|
|
1335
|
+
"transaction",
|
|
1336
|
+
"Claim vault fees",
|
|
1337
|
+
owner,
|
|
1338
|
+
manager,
|
|
1339
|
+
managerInterface.encodeFunctionData("claimFees(address)", [
|
|
1340
|
+
akAddress
|
|
1341
|
+
])
|
|
1342
|
+
)
|
|
1343
|
+
]
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
function buildSetVaultStakingPlan(input) {
|
|
1347
|
+
const owner = normalizeEvmAddress(input.owner);
|
|
1348
|
+
const manager = normalizeEvmAddress(input.manager);
|
|
1349
|
+
const akAddress = normalizeEvmAddress(input.akAddress);
|
|
1350
|
+
assertBoolean(input.staking, "staking");
|
|
1351
|
+
const action = input.staking ? "stake" : "unstake";
|
|
1352
|
+
return createTransactionPlan({
|
|
1353
|
+
action: `vault.${action}`,
|
|
1354
|
+
summary: `${input.staking ? "Stake" : "Unstake"} the active position in vault ${manager}.`,
|
|
1355
|
+
steps: [
|
|
1356
|
+
step(
|
|
1357
|
+
"transaction",
|
|
1358
|
+
`${input.staking ? "Stake" : "Unstake"} the vault position`,
|
|
1359
|
+
owner,
|
|
1360
|
+
manager,
|
|
1361
|
+
managerInterface.encodeFunctionData(
|
|
1362
|
+
`${action}(address,bytes)`,
|
|
1363
|
+
[akAddress, "0x"]
|
|
1364
|
+
)
|
|
1365
|
+
)
|
|
1366
|
+
]
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// src/bridge/tracking.ts
|
|
1371
|
+
var import_ethers10 = require("ethers");
|
|
1372
|
+
|
|
1373
|
+
// src/bridge/receipts.ts
|
|
1374
|
+
var import_ethers9 = require("ethers");
|
|
1375
|
+
|
|
1376
|
+
// src/core/receipts.ts
|
|
1377
|
+
function isLogFrom(log, address) {
|
|
1378
|
+
return log.address.toLowerCase() === address.toLowerCase();
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// src/bridge/receipts.ts
|
|
1382
|
+
var alphaGatewayInterface = new import_ethers9.Interface(ALPHA_GATEWAY_ABI);
|
|
1383
|
+
var spokeGatewayInterface = new import_ethers9.Interface(SPOKE_GATEWAY_ABI);
|
|
1384
|
+
function assertBridgeDirection(value) {
|
|
1385
|
+
if (value !== "base-to-subtensor" && value !== "robinhood-to-subtensor" && value !== "subtensor-to-base" && value !== "subtensor-to-robinhood") {
|
|
1386
|
+
throw new ForeverMoneyError(
|
|
1387
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1388
|
+
"Bridge direction must identify a canonical Base or Robinhood lane."
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
function evmChainFromBridgeDirection(direction) {
|
|
1393
|
+
assertBridgeDirection(direction);
|
|
1394
|
+
return direction.includes("robinhood") ? "robinhood" : "base";
|
|
1395
|
+
}
|
|
1396
|
+
function isEvmToSubtensorDirection(direction) {
|
|
1397
|
+
assertBridgeDirection(direction);
|
|
1398
|
+
return direction.endsWith("-to-subtensor");
|
|
1399
|
+
}
|
|
1400
|
+
function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
1401
|
+
assertBridgeDirection(direction);
|
|
1402
|
+
const evm = getForeverMoneyEvmDeployment(
|
|
1403
|
+
evmChainFromBridgeDirection(direction)
|
|
1404
|
+
);
|
|
1405
|
+
const evmToSubtensor = isEvmToSubtensorDirection(direction);
|
|
1406
|
+
const [address, contractInterface, eventName] = evmToSubtensor ? [evm.contracts.gateway, spokeGatewayInterface, "BridgedToFinney"] : [
|
|
1407
|
+
foreverMoneyDeployment.subtensor.contracts.gateway,
|
|
1408
|
+
alphaGatewayInterface,
|
|
1409
|
+
"BridgedOut"
|
|
1410
|
+
];
|
|
1411
|
+
for (const log of receipt.logs) {
|
|
1412
|
+
if (!isLogFrom(log, address)) continue;
|
|
1413
|
+
try {
|
|
1414
|
+
const parsed = contractInterface.parseLog({
|
|
1415
|
+
data: log.data,
|
|
1416
|
+
topics: [...log.topics]
|
|
1417
|
+
});
|
|
1418
|
+
const messageId = parsed?.args.messageId;
|
|
1419
|
+
if (parsed?.name === eventName && typeof messageId === "string" && (0, import_ethers9.isHexString)(messageId, 32) && (evmToSubtensor || parsed.args.destChainSelector === evm.ccipSelector)) {
|
|
1420
|
+
return messageId.toLowerCase();
|
|
1421
|
+
}
|
|
1422
|
+
} catch {
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/bridge/tracking.ts
|
|
1429
|
+
var ccipExecutionInterface = new import_ethers10.Interface(CCIP_EXECUTION_ABI);
|
|
1430
|
+
var alphaGatewayInterface2 = new import_ethers10.Interface(ALPHA_GATEWAY_ABI);
|
|
1431
|
+
function destinationChainId(direction) {
|
|
1432
|
+
assertBridgeDirection(direction);
|
|
1433
|
+
const evm = getForeverMoneyEvmDeployment(
|
|
1434
|
+
evmChainFromBridgeDirection(direction)
|
|
1435
|
+
);
|
|
1436
|
+
return isEvmToSubtensorDirection(direction) ? foreverMoneyDeployment.subtensor.chainId : evm.chainId;
|
|
1437
|
+
}
|
|
1438
|
+
function sourceChainId(direction) {
|
|
1439
|
+
assertBridgeDirection(direction);
|
|
1440
|
+
const evm = getForeverMoneyEvmDeployment(
|
|
1441
|
+
evmChainFromBridgeDirection(direction)
|
|
1442
|
+
);
|
|
1443
|
+
return isEvmToSubtensorDirection(direction) ? evm.chainId : foreverMoneyDeployment.subtensor.chainId;
|
|
1444
|
+
}
|
|
1445
|
+
async function assertProviderChain(provider, expectedChainId, label) {
|
|
1446
|
+
const network = await provider.getNetwork();
|
|
1447
|
+
if (network.chainId !== BigInt(expectedChainId)) {
|
|
1448
|
+
throw new ForeverMoneyError(
|
|
1449
|
+
"CHAIN_MISMATCH",
|
|
1450
|
+
`${label} transport reported chain ID ${network.chainId}; expected ${expectedChainId}.`,
|
|
1451
|
+
{
|
|
1452
|
+
expectedChainId,
|
|
1453
|
+
actualChainId: network.chainId.toString()
|
|
1454
|
+
}
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
async function assertDestinationProvider(provider, direction) {
|
|
1459
|
+
const expectedChainId = destinationChainId(direction);
|
|
1460
|
+
await assertProviderChain(provider, expectedChainId, "Bridge destination");
|
|
1461
|
+
return expectedChainId;
|
|
1462
|
+
}
|
|
1463
|
+
async function getBridgeSourceStatus(provider, input) {
|
|
1464
|
+
const expectedChainId = sourceChainId(input.direction);
|
|
1465
|
+
const transactionHash = normalizeBytes32(
|
|
1466
|
+
input.transactionHash,
|
|
1467
|
+
"Transaction hash"
|
|
1468
|
+
);
|
|
1469
|
+
await assertProviderChain(provider, expectedChainId, "Bridge source");
|
|
1470
|
+
const receipt = await provider.getTransactionReceipt(transactionHash);
|
|
1471
|
+
if (receipt === null) {
|
|
1472
|
+
return Object.freeze({
|
|
1473
|
+
direction: input.direction,
|
|
1474
|
+
sourceChainId: expectedChainId,
|
|
1475
|
+
transactionHash,
|
|
1476
|
+
status: "pending",
|
|
1477
|
+
messageId: null
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
if (receipt.status === 0) {
|
|
1481
|
+
return Object.freeze({
|
|
1482
|
+
direction: input.direction,
|
|
1483
|
+
sourceChainId: expectedChainId,
|
|
1484
|
+
transactionHash,
|
|
1485
|
+
status: "failed",
|
|
1486
|
+
messageId: null
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
if (receipt.status !== 1) {
|
|
1490
|
+
throw new ForeverMoneyError(
|
|
1491
|
+
"INVALID_PROVIDER_RESPONSE",
|
|
1492
|
+
"The bridge source receipt has an invalid status."
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
const messageId = bridgeMessageIdFromReceipt(input.direction, receipt);
|
|
1496
|
+
if (messageId === null) {
|
|
1497
|
+
throw new ForeverMoneyError(
|
|
1498
|
+
"INVALID_PROVIDER_RESPONSE",
|
|
1499
|
+
"The confirmed bridge source receipt does not contain its canonical gateway event."
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
return Object.freeze({
|
|
1503
|
+
direction: input.direction,
|
|
1504
|
+
sourceChainId: expectedChainId,
|
|
1505
|
+
transactionHash,
|
|
1506
|
+
status: "confirmed",
|
|
1507
|
+
messageId
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
async function getCcipDeliveryCheckpoint(provider, direction) {
|
|
1511
|
+
const expectedChainId = await assertDestinationProvider(provider, direction);
|
|
1512
|
+
const fromBlock = await provider.getBlockNumber();
|
|
1513
|
+
if (!Number.isSafeInteger(fromBlock) || fromBlock < 0) {
|
|
1514
|
+
throw new ForeverMoneyError(
|
|
1515
|
+
"INVALID_PROVIDER_RESPONSE",
|
|
1516
|
+
"Bridge destination transport returned an invalid block number."
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
return Object.freeze({
|
|
1520
|
+
direction,
|
|
1521
|
+
destinationChainId: expectedChainId,
|
|
1522
|
+
fromBlock
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
async function getCcipDeliveryStatus(provider, input) {
|
|
1526
|
+
const expectedChainId = destinationChainId(input.direction);
|
|
1527
|
+
const messageId = normalizeBytes32(input.messageId, "CCIP message ID");
|
|
1528
|
+
if (!Number.isSafeInteger(input.fromBlock) || input.fromBlock < 0) {
|
|
1529
|
+
throw new ForeverMoneyError(
|
|
1530
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1531
|
+
"CCIP fromBlock must be a non-negative safe integer."
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
await assertProviderChain(provider, expectedChainId, "Bridge destination");
|
|
1535
|
+
const event = ccipExecutionInterface.getEvent("ExecutionStateChanged");
|
|
1536
|
+
if (event === null) {
|
|
1537
|
+
throw new Error("CCIP execution event is missing from the SDK ABI.");
|
|
1538
|
+
}
|
|
1539
|
+
const evmChain = evmChainFromBridgeDirection(input.direction);
|
|
1540
|
+
const evm = getForeverMoneyEvmDeployment(evmChain);
|
|
1541
|
+
const evmToSubtensor = isEvmToSubtensorDirection(input.direction);
|
|
1542
|
+
const sourceSelector = evmToSubtensor ? evm.ccipSelector : foreverMoneyDeployment.subtensor.ccipSelector;
|
|
1543
|
+
const offRamp = evmToSubtensor ? evmChain === "base" ? foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromBase : foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromRobinhood : evm.contracts.ccipOffRampFromSubtensor;
|
|
1544
|
+
const logs = await provider.getLogs({
|
|
1545
|
+
address: offRamp,
|
|
1546
|
+
fromBlock: input.fromBlock,
|
|
1547
|
+
toBlock: "latest",
|
|
1548
|
+
topics: [event.topicHash, (0, import_ethers10.toBeHex)(sourceSelector, 32), null, messageId]
|
|
1549
|
+
});
|
|
1550
|
+
const latest = logs.at(-1);
|
|
1551
|
+
if (latest === void 0) return "waiting";
|
|
1552
|
+
const parsed = ccipExecutionInterface.parseLog(latest);
|
|
1553
|
+
const state = Number(parsed?.args.state);
|
|
1554
|
+
if (state === 3) return "failure";
|
|
1555
|
+
if (state !== 2) return "waiting";
|
|
1556
|
+
if (!evmToSubtensor) return "success";
|
|
1557
|
+
const receipt = await provider.getTransactionReceipt(latest.transactionHash);
|
|
1558
|
+
if (receipt === null) {
|
|
1559
|
+
throw new ForeverMoneyError(
|
|
1560
|
+
"INVALID_PROVIDER_RESPONSE",
|
|
1561
|
+
"The CCIP execution receipt is unavailable."
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
for (const log of receipt.logs) {
|
|
1565
|
+
if (log.address.toLowerCase() !== foreverMoneyDeployment.subtensor.contracts.gateway.toLowerCase()) {
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
try {
|
|
1569
|
+
if (alphaGatewayInterface2.parseLog(log)?.name === "Claimable") {
|
|
1570
|
+
return "recovery";
|
|
1571
|
+
}
|
|
1572
|
+
} catch {
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return "success";
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/client.ts
|
|
1579
|
+
async function assertProviderChain2(provider, expectedChainId, name) {
|
|
1580
|
+
const network = await providerOperation(
|
|
1581
|
+
`Reading the ${name} chain ID`,
|
|
1582
|
+
() => provider.getNetwork()
|
|
1583
|
+
);
|
|
1584
|
+
if (network.chainId !== BigInt(expectedChainId)) {
|
|
1585
|
+
throw new ForeverMoneyError(
|
|
1586
|
+
"CHAIN_MISMATCH",
|
|
1587
|
+
`${name} transport reported chain ID ${network.chainId}; expected ${expectedChainId}.`,
|
|
1588
|
+
{
|
|
1589
|
+
expectedChainId,
|
|
1590
|
+
actualChainId: network.chainId.toString()
|
|
1591
|
+
}
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
function createForeverMoneyClient(options) {
|
|
1596
|
+
if (!options?.transports?.base || !options.transports.subtensor) {
|
|
1597
|
+
throw new ForeverMoneyError(
|
|
1598
|
+
"MISSING_TRANSPORT",
|
|
1599
|
+
"Base and Subtensor transports are required."
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
const baseProvider = providerFromTransport(options.transports.base);
|
|
1603
|
+
const subtensorProvider = providerFromTransport(
|
|
1604
|
+
options.transports.subtensor
|
|
1605
|
+
);
|
|
1606
|
+
const robinhoodProvider = options.transports.robinhood ? providerFromTransport(options.transports.robinhood) : void 0;
|
|
1607
|
+
const verifyBaseConnection = async () => {
|
|
1608
|
+
await assertProviderChain2(baseProvider, BASE_CHAIN_ID, "Base");
|
|
1609
|
+
};
|
|
1610
|
+
const verifySubtensorConnection = async () => {
|
|
1611
|
+
await assertProviderChain2(
|
|
1612
|
+
subtensorProvider,
|
|
1613
|
+
SUBTENSOR_CHAIN_ID,
|
|
1614
|
+
"Subtensor"
|
|
1615
|
+
);
|
|
1616
|
+
};
|
|
1617
|
+
const verifyRobinhoodConnection = async () => {
|
|
1618
|
+
if (robinhoodProvider === void 0) return;
|
|
1619
|
+
await assertProviderChain2(
|
|
1620
|
+
robinhoodProvider,
|
|
1621
|
+
ROBINHOOD_CHAIN_ID,
|
|
1622
|
+
"Robinhood"
|
|
1623
|
+
);
|
|
1624
|
+
};
|
|
1625
|
+
const verifyConnections = async () => {
|
|
1626
|
+
await Promise.all([
|
|
1627
|
+
verifyBaseConnection(),
|
|
1628
|
+
verifyRobinhoodConnection(),
|
|
1629
|
+
verifySubtensorConnection()
|
|
1630
|
+
]);
|
|
1631
|
+
};
|
|
1632
|
+
const providerForChain = (chainId) => {
|
|
1633
|
+
if (chainId === BASE_CHAIN_ID) return baseProvider;
|
|
1634
|
+
if (chainId === SUBTENSOR_CHAIN_ID) return subtensorProvider;
|
|
1635
|
+
if (chainId === ROBINHOOD_CHAIN_ID && robinhoodProvider !== void 0) {
|
|
1636
|
+
return robinhoodProvider;
|
|
1637
|
+
}
|
|
1638
|
+
throw new ForeverMoneyError(
|
|
1639
|
+
"MISSING_TRANSPORT",
|
|
1640
|
+
`A transport for chain ID ${chainId} is required for this operation.`
|
|
1641
|
+
);
|
|
1642
|
+
};
|
|
1643
|
+
return Object.freeze({
|
|
1644
|
+
bridge: Object.freeze({
|
|
1645
|
+
async prepareEvmToSubtensor(input) {
|
|
1646
|
+
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
1647
|
+
const provider = providerForChain(evm.chainId);
|
|
1648
|
+
await assertProviderChain2(provider, evm.chainId, evm.name);
|
|
1649
|
+
return providerOperation(
|
|
1650
|
+
`Preparing the ${evm.name} to Subtensor bridge`,
|
|
1651
|
+
() => prepareEvmToSubtensor(provider, input)
|
|
1652
|
+
);
|
|
1653
|
+
},
|
|
1654
|
+
async prepareBaseToSubtensor(input) {
|
|
1655
|
+
await verifyBaseConnection();
|
|
1656
|
+
return providerOperation(
|
|
1657
|
+
"Preparing the Base to Subtensor bridge",
|
|
1658
|
+
() => prepareBaseToSubtensor(baseProvider, input)
|
|
1659
|
+
);
|
|
1660
|
+
},
|
|
1661
|
+
async prepareSubtensorToEvm(input) {
|
|
1662
|
+
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
1663
|
+
await verifySubtensorConnection();
|
|
1664
|
+
return providerOperation(
|
|
1665
|
+
`Preparing the Subtensor to ${evm.name} bridge`,
|
|
1666
|
+
() => prepareSubtensorToEvm(subtensorProvider, input)
|
|
1667
|
+
);
|
|
1668
|
+
},
|
|
1669
|
+
async prepareSubtensorToBase(input) {
|
|
1670
|
+
await verifySubtensorConnection();
|
|
1671
|
+
return providerOperation(
|
|
1672
|
+
"Preparing the Subtensor to Base bridge",
|
|
1673
|
+
() => prepareSubtensorToBase(subtensorProvider, input)
|
|
1674
|
+
);
|
|
1675
|
+
},
|
|
1676
|
+
async getDeliveryCheckpoint(direction) {
|
|
1677
|
+
const expectedChainId = destinationChainId(direction);
|
|
1678
|
+
const provider = providerForChain(expectedChainId);
|
|
1679
|
+
return providerOperation(
|
|
1680
|
+
"Reading the bridge delivery checkpoint",
|
|
1681
|
+
() => getCcipDeliveryCheckpoint(provider, direction)
|
|
1682
|
+
);
|
|
1683
|
+
},
|
|
1684
|
+
async getDeliveryStatus(input) {
|
|
1685
|
+
const expectedChainId = destinationChainId(input.direction);
|
|
1686
|
+
const provider = providerForChain(expectedChainId);
|
|
1687
|
+
return providerOperation(
|
|
1688
|
+
"Reading CCIP delivery status",
|
|
1689
|
+
() => getCcipDeliveryStatus(provider, input)
|
|
1690
|
+
);
|
|
1691
|
+
},
|
|
1692
|
+
async getSourceStatus(input) {
|
|
1693
|
+
const expectedChainId = sourceChainId(input.direction);
|
|
1694
|
+
const provider = providerForChain(expectedChainId);
|
|
1695
|
+
return providerOperation(
|
|
1696
|
+
"Reading the bridge source transaction",
|
|
1697
|
+
() => getBridgeSourceStatus(provider, input)
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
}),
|
|
1701
|
+
vaults: Object.freeze({
|
|
1702
|
+
async prepareCreate(input) {
|
|
1703
|
+
await verifyBaseConnection();
|
|
1704
|
+
return providerOperation(
|
|
1705
|
+
"Preparing vault creation",
|
|
1706
|
+
() => prepareCreateVault(baseProvider, input)
|
|
1707
|
+
);
|
|
1708
|
+
},
|
|
1709
|
+
async prepareDeposit(input) {
|
|
1710
|
+
await verifyBaseConnection();
|
|
1711
|
+
return providerOperation(
|
|
1712
|
+
"Preparing the vault deposit",
|
|
1713
|
+
() => prepareDepositVault(baseProvider, input)
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
}),
|
|
1717
|
+
verifyConnections
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/vaults/receipts.ts
|
|
1722
|
+
var import_ethers11 = require("ethers");
|
|
1723
|
+
var vaultFactoryInterface = new import_ethers11.Interface(VAULT_FACTORY_ABI);
|
|
1724
|
+
function vaultManagerFromCreationReceipt(receipt) {
|
|
1725
|
+
for (const log of receipt.logs) {
|
|
1726
|
+
if (!isLogFrom(log, foreverMoneyDeployment.base.contracts.vaultFactory)) {
|
|
1727
|
+
continue;
|
|
1728
|
+
}
|
|
1729
|
+
try {
|
|
1730
|
+
const parsed = vaultFactoryInterface.parseLog({
|
|
1731
|
+
data: log.data,
|
|
1732
|
+
topics: [...log.topics]
|
|
1733
|
+
});
|
|
1734
|
+
if (parsed?.name === "SnLiquidityManagerCreated") {
|
|
1735
|
+
return normalizeEvmAddress(String(parsed.args.manager));
|
|
1736
|
+
}
|
|
1737
|
+
} catch {
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// src/core/transactions.ts
|
|
1744
|
+
var import_ethers12 = require("ethers");
|
|
1745
|
+
function decimalQuantity(value, label) {
|
|
1746
|
+
if (!/^(0|[1-9][0-9]*)$/.test(value)) {
|
|
1747
|
+
throw new ForeverMoneyError(
|
|
1748
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1749
|
+
`${label} must be an unsigned decimal integer.`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
const quantity = BigInt(value);
|
|
1753
|
+
if (quantity > import_ethers12.MaxUint256) {
|
|
1754
|
+
throw new ForeverMoneyError(
|
|
1755
|
+
"INVALID_TRANSACTION_PLAN",
|
|
1756
|
+
`${label} exceeds uint256.`
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
return quantity;
|
|
1760
|
+
}
|
|
1761
|
+
function hexQuantity(value) {
|
|
1762
|
+
return `0x${value.toString(16)}`;
|
|
1763
|
+
}
|
|
1764
|
+
function toEip1193Transaction(transaction) {
|
|
1765
|
+
const value = decimalQuantity(transaction.value, "Transaction value");
|
|
1766
|
+
const gasLimit = transaction.gasLimit === void 0 ? void 0 : decimalQuantity(transaction.gasLimit, "Transaction gas limit");
|
|
1767
|
+
return Object.freeze({
|
|
1768
|
+
from: transaction.from,
|
|
1769
|
+
to: transaction.to,
|
|
1770
|
+
data: transaction.data,
|
|
1771
|
+
value: hexQuantity(value),
|
|
1772
|
+
...gasLimit === void 0 ? {} : { gas: hexQuantity(gasLimit) }
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
function toEthersTransaction(transaction) {
|
|
1776
|
+
return Object.freeze({
|
|
1777
|
+
chainId: transaction.chainId,
|
|
1778
|
+
to: transaction.to,
|
|
1779
|
+
data: transaction.data,
|
|
1780
|
+
value: decimalQuantity(transaction.value, "Transaction value"),
|
|
1781
|
+
...transaction.gasLimit === void 0 ? {} : {
|
|
1782
|
+
gasLimit: decimalQuantity(
|
|
1783
|
+
transaction.gasLimit,
|
|
1784
|
+
"Transaction gas limit"
|
|
1785
|
+
)
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
//# sourceMappingURL=index.cjs.map
|