@forevermoney/sdk 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +113 -5
- package/dist/index.cjs +646 -245
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -32
- package/dist/index.d.ts +87 -32
- package/dist/index.js +655 -231
- package/dist/index.js.map +1 -1
- package/examples/viem.ts +64 -0
- package/package.json +8 -9
package/dist/index.cjs
CHANGED
|
@@ -27,12 +27,15 @@ __export(index_exports, {
|
|
|
27
27
|
FOREVERMONEY_DEPLOYMENT_VERSION: () => FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
28
28
|
ForeverMoneyError: () => ForeverMoneyError,
|
|
29
29
|
GAS_LIMIT_BUFFER_BPS: () => GAS_LIMIT_BUFFER_BPS,
|
|
30
|
+
MAX_PARTNER_FEE_BPS: () => MAX_PARTNER_FEE_BPS,
|
|
30
31
|
MIN_LIQUID_BASE_TO_SUBTENSOR_WEI: () => MIN_LIQUID_BASE_TO_SUBTENSOR_WEI,
|
|
31
32
|
MIN_LIQUID_EVM_TO_SUBTENSOR_WEI: () => MIN_LIQUID_EVM_TO_SUBTENSOR_WEI,
|
|
33
|
+
MIN_LIQUID_SUBTENSOR_TO_EVM_WEI: () => MIN_LIQUID_SUBTENSOR_TO_EVM_WEI,
|
|
32
34
|
NETWORK_FEE_BUFFER_BPS: () => NETWORK_FEE_BUFFER_BPS,
|
|
33
35
|
RAO_PER_TAO: () => RAO_PER_TAO,
|
|
34
36
|
ROBINHOOD_CCIP_SELECTOR: () => ROBINHOOD_CCIP_SELECTOR,
|
|
35
37
|
ROBINHOOD_CHAIN_ID: () => ROBINHOOD_CHAIN_ID,
|
|
38
|
+
SN80_NETUID: () => SN80_NETUID,
|
|
36
39
|
SUBTENSOR_CCIP_SELECTOR: () => SUBTENSOR_CCIP_SELECTOR,
|
|
37
40
|
SUBTENSOR_CHAIN_ID: () => SUBTENSOR_CHAIN_ID,
|
|
38
41
|
assertWholeRao: () => assertWholeRao,
|
|
@@ -65,18 +68,20 @@ __export(index_exports, {
|
|
|
65
68
|
normalizeEvmAddress: () => normalizeEvmAddress,
|
|
66
69
|
normalizeSS58: () => normalizeSS58,
|
|
67
70
|
parseTaoAmount: () => parseTaoAmount,
|
|
71
|
+
partnerFeeCut: () => partnerFeeCut,
|
|
72
|
+
partnerFeeTaoTopUp: () => partnerFeeTaoTopUp,
|
|
68
73
|
sourceChainId: () => sourceChainId,
|
|
69
74
|
ss58ToPublicKey: () => ss58ToPublicKey,
|
|
70
75
|
toEip1193Transaction: () => toEip1193Transaction,
|
|
71
76
|
toEthersTransaction: () => toEthersTransaction,
|
|
77
|
+
toViemTransaction: () => toViemTransaction,
|
|
72
78
|
vaultManagerFromCreationReceipt: () => vaultManagerFromCreationReceipt
|
|
73
79
|
});
|
|
74
80
|
module.exports = __toCommonJS(index_exports);
|
|
75
81
|
|
|
76
82
|
// src/core/addresses.ts
|
|
77
|
-
var
|
|
78
|
-
var
|
|
79
|
-
var import_ethers = require("ethers");
|
|
83
|
+
var import_substrate_bindings = require("@polkadot-api/substrate-bindings");
|
|
84
|
+
var import_viem = require("viem");
|
|
80
85
|
|
|
81
86
|
// src/core/errors.ts
|
|
82
87
|
var ForeverMoneyError = class extends Error {
|
|
@@ -93,22 +98,33 @@ var ForeverMoneyError = class extends Error {
|
|
|
93
98
|
// src/core/addresses.ts
|
|
94
99
|
var BITTENSOR_SS58_PREFIX = 42;
|
|
95
100
|
function normalizeEvmAddress(value) {
|
|
96
|
-
|
|
101
|
+
const candidate = typeof value === "string" && /^0x[0-9A-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
102
|
+
if (!(0, import_viem.isAddress)(candidate) || candidate === import_viem.zeroAddress) {
|
|
97
103
|
throw new ForeverMoneyError(
|
|
98
104
|
"INVALID_ADDRESS",
|
|
99
105
|
"Expected a non-zero EVM address."
|
|
100
106
|
);
|
|
101
107
|
}
|
|
102
|
-
return (0,
|
|
108
|
+
return (0, import_viem.getAddress)(candidate);
|
|
103
109
|
}
|
|
104
110
|
function evmToMirrorSS58(evmAddress) {
|
|
105
|
-
return (0,
|
|
111
|
+
return (0, import_substrate_bindings.AccountId)(BITTENSOR_SS58_PREFIX).dec(
|
|
112
|
+
(0, import_substrate_bindings.Blake2256)(
|
|
113
|
+
(0, import_viem.concat)([
|
|
114
|
+
(0, import_viem.stringToBytes)("evm:"),
|
|
115
|
+
(0, import_viem.hexToBytes)(normalizeEvmAddress(evmAddress))
|
|
116
|
+
])
|
|
117
|
+
)
|
|
118
|
+
);
|
|
106
119
|
}
|
|
107
120
|
function decodeBittensorAddress(value) {
|
|
108
121
|
try {
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
if (typeof value !== "string" || !value || value.startsWith("0x"))
|
|
123
|
+
throw new Error("Raw hex is not SS58.");
|
|
124
|
+
const info = (0, import_substrate_bindings.getSs58AddressInfo)(value);
|
|
125
|
+
if (!info.isValid || info.ss58Format !== BITTENSOR_SS58_PREFIX || info.publicKey.length !== 32)
|
|
126
|
+
throw new Error("Invalid Bittensor address.");
|
|
127
|
+
const publicKey = info.publicKey;
|
|
112
128
|
return publicKey;
|
|
113
129
|
} catch {
|
|
114
130
|
throw new ForeverMoneyError(
|
|
@@ -118,7 +134,7 @@ function decodeBittensorAddress(value) {
|
|
|
118
134
|
}
|
|
119
135
|
}
|
|
120
136
|
function normalizeSS58(value) {
|
|
121
|
-
return (0,
|
|
137
|
+
return (0, import_substrate_bindings.AccountId)(BITTENSOR_SS58_PREFIX).dec(decodeBittensorAddress(value));
|
|
122
138
|
}
|
|
123
139
|
function isBittensorSS58(value) {
|
|
124
140
|
try {
|
|
@@ -129,14 +145,14 @@ function isBittensorSS58(value) {
|
|
|
129
145
|
}
|
|
130
146
|
}
|
|
131
147
|
function ss58ToPublicKey(value) {
|
|
132
|
-
return (0,
|
|
148
|
+
return (0, import_viem.bytesToHex)(decodeBittensorAddress(value));
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
// src/core/amounts.ts
|
|
136
|
-
var
|
|
152
|
+
var import_viem3 = require("viem");
|
|
137
153
|
|
|
138
154
|
// src/chains/deployment.ts
|
|
139
|
-
var
|
|
155
|
+
var import_viem2 = require("viem");
|
|
140
156
|
var BASE_CHAIN_ID = 8453;
|
|
141
157
|
var ROBINHOOD_CHAIN_ID = 4663;
|
|
142
158
|
var SUBTENSOR_CHAIN_ID = 964;
|
|
@@ -145,7 +161,8 @@ var ROBINHOOD_CCIP_SELECTOR = 6180753054346818345n;
|
|
|
145
161
|
var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
146
162
|
var RAO_PER_TAO = 1000000000n;
|
|
147
163
|
var EVM_WEI_PER_RAO = 1000000000n;
|
|
148
|
-
var
|
|
164
|
+
var SN80_NETUID = 80n;
|
|
165
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.2.0";
|
|
149
166
|
var foreverMoneyDeployment = Object.freeze({
|
|
150
167
|
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
151
168
|
base: Object.freeze({
|
|
@@ -154,26 +171,32 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
154
171
|
chainId: BASE_CHAIN_ID,
|
|
155
172
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
156
173
|
contracts: Object.freeze({
|
|
157
|
-
gateway: (0,
|
|
158
|
-
|
|
174
|
+
gateway: (0, import_viem2.getAddress)("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
175
|
+
legacyGateway: (0, import_viem2.getAddress)(
|
|
176
|
+
"0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C"
|
|
177
|
+
),
|
|
178
|
+
wrappedTao: (0, import_viem2.getAddress)(
|
|
159
179
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
160
180
|
),
|
|
161
|
-
|
|
181
|
+
wrappedSn80: (0, import_viem2.getAddress)(
|
|
182
|
+
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
183
|
+
),
|
|
184
|
+
ccipRouter: (0, import_viem2.getAddress)(
|
|
162
185
|
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
163
186
|
),
|
|
164
|
-
ccipOffRampFromSubtensor: (0,
|
|
187
|
+
ccipOffRampFromSubtensor: (0, import_viem2.getAddress)(
|
|
165
188
|
"0xf09AFe78d3c7d359b334d7cB88995751F7eC5E13"
|
|
166
189
|
),
|
|
167
|
-
vaultFactory: (0,
|
|
190
|
+
vaultFactory: (0, import_viem2.getAddress)(
|
|
168
191
|
"0x9b7F3c7aa335D8BF9f91741E541820466735F868"
|
|
169
192
|
),
|
|
170
|
-
vaultManagerImplementation: (0,
|
|
193
|
+
vaultManagerImplementation: (0, import_viem2.getAddress)(
|
|
171
194
|
"0x235Ad8654fe3a1619a9892eBCaD9361F416168da"
|
|
172
195
|
),
|
|
173
|
-
batchLiquidityManagerQuery: (0,
|
|
196
|
+
batchLiquidityManagerQuery: (0, import_viem2.getAddress)(
|
|
174
197
|
"0xd117176C5E6809b68500995F59589A8c081565A5"
|
|
175
198
|
),
|
|
176
|
-
weth: (0,
|
|
199
|
+
weth: (0, import_viem2.getAddress)("0x4200000000000000000000000000000000000006")
|
|
177
200
|
}),
|
|
178
201
|
deploymentBlock: 50098800
|
|
179
202
|
}),
|
|
@@ -183,14 +206,17 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
183
206
|
chainId: ROBINHOOD_CHAIN_ID,
|
|
184
207
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
185
208
|
contracts: Object.freeze({
|
|
186
|
-
gateway: (0,
|
|
187
|
-
|
|
209
|
+
gateway: (0, import_viem2.getAddress)("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
210
|
+
legacyGateway: (0, import_viem2.getAddress)(
|
|
211
|
+
"0x53Dcc4FE04193e489BE537F722F65317DB1E65d8"
|
|
212
|
+
),
|
|
213
|
+
wrappedTao: (0, import_viem2.getAddress)(
|
|
188
214
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
189
215
|
),
|
|
190
|
-
ccipRouter: (0,
|
|
216
|
+
ccipRouter: (0, import_viem2.getAddress)(
|
|
191
217
|
"0x06fC836cf9839B1cd891C440A0a45242DA6Ae1c9"
|
|
192
218
|
),
|
|
193
|
-
ccipOffRampFromSubtensor: (0,
|
|
219
|
+
ccipOffRampFromSubtensor: (0, import_viem2.getAddress)(
|
|
194
220
|
"0xcDca5D374e46A6DDDab50bD2D9acB8c796eC35C3"
|
|
195
221
|
)
|
|
196
222
|
})
|
|
@@ -199,23 +225,29 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
199
225
|
chainId: SUBTENSOR_CHAIN_ID,
|
|
200
226
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
201
227
|
contracts: Object.freeze({
|
|
202
|
-
gateway: (0,
|
|
203
|
-
|
|
228
|
+
gateway: (0, import_viem2.getAddress)("0xcd0C6d98D0A126B1c113d15b4c28F38321437787"),
|
|
229
|
+
legacyGateway: (0, import_viem2.getAddress)(
|
|
230
|
+
"0x998f20Fea90bF7792774dECc7f994716442B1705"
|
|
231
|
+
),
|
|
232
|
+
alphaVault: (0, import_viem2.getAddress)(
|
|
204
233
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
205
234
|
),
|
|
206
|
-
wrappedTao: (0,
|
|
235
|
+
wrappedTao: (0, import_viem2.getAddress)(
|
|
207
236
|
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
208
237
|
),
|
|
209
|
-
|
|
238
|
+
wrappedSn80: (0, import_viem2.getAddress)(
|
|
239
|
+
"0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222"
|
|
240
|
+
),
|
|
241
|
+
ccipRouter: (0, import_viem2.getAddress)(
|
|
210
242
|
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
211
243
|
),
|
|
212
|
-
ccipOffRampFromBase: (0,
|
|
244
|
+
ccipOffRampFromBase: (0, import_viem2.getAddress)(
|
|
213
245
|
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
214
246
|
),
|
|
215
|
-
ccipOffRampFromRobinhood: (0,
|
|
247
|
+
ccipOffRampFromRobinhood: (0, import_viem2.getAddress)(
|
|
216
248
|
"0x51a6150400ed9F0Ae240F5D1b15E3b45Fc4339C7"
|
|
217
249
|
),
|
|
218
|
-
stakingPrecompile: (0,
|
|
250
|
+
stakingPrecompile: (0, import_viem2.getAddress)(
|
|
219
251
|
"0x0000000000000000000000000000000000000805"
|
|
220
252
|
)
|
|
221
253
|
})
|
|
@@ -245,7 +277,7 @@ function parseTaoAmount(value) {
|
|
|
245
277
|
}
|
|
246
278
|
let amount;
|
|
247
279
|
try {
|
|
248
|
-
amount = (0,
|
|
280
|
+
amount = (0, import_viem3.parseUnits)(value, 18);
|
|
249
281
|
} catch {
|
|
250
282
|
throw new ForeverMoneyError(
|
|
251
283
|
"AMOUNT_NOT_WHOLE_RAO",
|
|
@@ -274,7 +306,7 @@ function assertWholeRao(amountWei) {
|
|
|
274
306
|
"The amount cannot be negative."
|
|
275
307
|
);
|
|
276
308
|
}
|
|
277
|
-
if (amountWei >
|
|
309
|
+
if (amountWei > import_viem3.maxUint256) {
|
|
278
310
|
throw new ForeverMoneyError(
|
|
279
311
|
"INVALID_TRANSACTION_PLAN",
|
|
280
312
|
"The amount exceeds uint256."
|
|
@@ -300,7 +332,7 @@ function formatTaoAmount(amountWei) {
|
|
|
300
332
|
"A TAO amount cannot be negative."
|
|
301
333
|
);
|
|
302
334
|
}
|
|
303
|
-
if (amountWei >
|
|
335
|
+
if (amountWei > import_viem3.maxUint256) {
|
|
304
336
|
throw new ForeverMoneyError(
|
|
305
337
|
"INVALID_TRANSACTION_PLAN",
|
|
306
338
|
"The TAO amount exceeds uint256."
|
|
@@ -312,7 +344,8 @@ function formatTaoAmount(amountWei) {
|
|
|
312
344
|
"TAO amounts must resolve to a whole RAO."
|
|
313
345
|
);
|
|
314
346
|
}
|
|
315
|
-
|
|
347
|
+
const formatted = (0, import_viem3.formatUnits)(amountWei, 18);
|
|
348
|
+
return formatted.includes(".") ? formatted : `${formatted}.0`;
|
|
316
349
|
}
|
|
317
350
|
function mulDivCeil(value, numerator, denominator) {
|
|
318
351
|
return (value * numerator + denominator - 1n) / denominator;
|
|
@@ -335,7 +368,7 @@ function feeWithBuffer(fee) {
|
|
|
335
368
|
BASIS_POINTS + NETWORK_FEE_BUFFER_BPS,
|
|
336
369
|
BASIS_POINTS
|
|
337
370
|
);
|
|
338
|
-
if (buffered >
|
|
371
|
+
if (buffered > import_viem3.maxUint256) {
|
|
339
372
|
throw new ForeverMoneyError(
|
|
340
373
|
"INVALID_TRANSACTION_PLAN",
|
|
341
374
|
"The buffered network fee exceeds uint256."
|
|
@@ -361,7 +394,7 @@ function gasLimitWithBuffer(gasLimit) {
|
|
|
361
394
|
BASIS_POINTS + GAS_LIMIT_BUFFER_BPS,
|
|
362
395
|
BASIS_POINTS
|
|
363
396
|
);
|
|
364
|
-
if (buffered >
|
|
397
|
+
if (buffered > import_viem3.maxUint256) {
|
|
365
398
|
throw new ForeverMoneyError(
|
|
366
399
|
"INVALID_TRANSACTION_PLAN",
|
|
367
400
|
"The buffered gas limit exceeds uint256."
|
|
@@ -385,23 +418,34 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
385
418
|
"function ROUTER() view returns (address)",
|
|
386
419
|
"function BITTENSOR_SELECTOR() view returns (uint64)",
|
|
387
420
|
"function SUBTENSOR_GATEWAY() view returns (address)",
|
|
421
|
+
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
422
|
+
"function bridgeFeeBps() view returns (uint16)",
|
|
388
423
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
389
424
|
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) payable returns (bytes32 messageId)",
|
|
425
|
+
"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)",
|
|
426
|
+
"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)",
|
|
390
427
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
391
428
|
]);
|
|
392
429
|
var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
393
430
|
"function ROUTER() view returns (address)",
|
|
394
431
|
"function allowedLane(uint64 selector) view returns (bool)",
|
|
432
|
+
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
433
|
+
"function bridgeFeeBps() view returns (uint16)",
|
|
434
|
+
"function integratorTaoTopUp(uint256 taoAmount,uint16 bps) pure returns (uint256)",
|
|
395
435
|
"function quoteBridgeOut(uint64 destSelector,address token,address recipient,uint256 mintedAmount) view returns (uint256 fee)",
|
|
396
436
|
"function bridgeOut(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
437
|
+
"function integratorCut(uint256 amount,uint16 bps) pure returns (uint256)",
|
|
438
|
+
"function quoteBridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 mintedAmount,uint256 taoAmount,uint256 stakedAlphaRao,(address recipient,uint16 bps) integrator) view returns (uint256 fee,uint256 nativeTopUp,uint256 alphaTopUp,uint256 amountCrossing)",
|
|
439
|
+
"function bridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
397
440
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
398
441
|
"function claimNative(address to)",
|
|
399
442
|
"function claimStaked(address token,bytes32 destColdkey,address to)",
|
|
400
443
|
"function claimToken(address token,address to)",
|
|
401
444
|
"function claimableNative(address account) view returns (uint256)",
|
|
402
|
-
"function claimableToken(address
|
|
445
|
+
"function claimableToken(address token,address account) view returns (uint256)",
|
|
403
446
|
"event BridgedOut(uint64 destChainSelector,address indexed token,address indexed sender,address indexed recipient,uint256 minted,bytes32 messageId)",
|
|
404
447
|
"event Claimable(address indexed token,address indexed user,uint256 native,uint256 wsn)",
|
|
448
|
+
"event NotDelivered(uint64 indexed sourceChainSelector,address indexed token,uint256 amount,uint8 reason)",
|
|
405
449
|
"event DeliveredLiquid(address indexed token,bytes32 indexed ss58,uint256 taoOut)",
|
|
406
450
|
"event DeliveredStaked(address indexed token,bytes32 indexed ss58,uint256 alphaRao)"
|
|
407
451
|
]);
|
|
@@ -445,10 +489,10 @@ var foreverMoneyAbis = Object.freeze({
|
|
|
445
489
|
});
|
|
446
490
|
|
|
447
491
|
// src/bridge/plans.ts
|
|
448
|
-
var
|
|
492
|
+
var import_viem6 = require("viem");
|
|
449
493
|
|
|
450
494
|
// src/core/plans.ts
|
|
451
|
-
var
|
|
495
|
+
var import_viem4 = require("viem");
|
|
452
496
|
function createTransactionPlan(plan) {
|
|
453
497
|
const steps = plan.steps.map(
|
|
454
498
|
(step2) => Object.freeze({
|
|
@@ -465,12 +509,12 @@ function createTransactionPlan(plan) {
|
|
|
465
509
|
};
|
|
466
510
|
return Object.freeze({
|
|
467
511
|
...versionedPlan,
|
|
468
|
-
hash: (0,
|
|
512
|
+
hash: (0, import_viem4.keccak256)((0, import_viem4.stringToBytes)(JSON.stringify(versionedPlan)))
|
|
469
513
|
});
|
|
470
514
|
}
|
|
471
515
|
|
|
472
516
|
// src/core/validation.ts
|
|
473
|
-
var
|
|
517
|
+
var import_viem5 = require("viem");
|
|
474
518
|
function assertBigInt(amount, label) {
|
|
475
519
|
if (typeof amount !== "bigint") {
|
|
476
520
|
throw new ForeverMoneyError(
|
|
@@ -493,7 +537,7 @@ function assertPositiveAmount(amount, label) {
|
|
|
493
537
|
`${label} cannot be negative.`
|
|
494
538
|
);
|
|
495
539
|
}
|
|
496
|
-
if (amount >
|
|
540
|
+
if (amount > import_viem5.maxUint256) {
|
|
497
541
|
throw new ForeverMoneyError(
|
|
498
542
|
"INVALID_TRANSACTION_PLAN",
|
|
499
543
|
`${label} exceeds uint256.`
|
|
@@ -508,7 +552,7 @@ function assertNonNegativeAmount(amount, label) {
|
|
|
508
552
|
`${label} cannot be negative.`
|
|
509
553
|
);
|
|
510
554
|
}
|
|
511
|
-
if (amount >
|
|
555
|
+
if (amount > import_viem5.maxUint256) {
|
|
512
556
|
throw new ForeverMoneyError(
|
|
513
557
|
"INVALID_TRANSACTION_PLAN",
|
|
514
558
|
`${label} exceeds uint256.`
|
|
@@ -540,7 +584,7 @@ function assertRecord(value, label) {
|
|
|
540
584
|
}
|
|
541
585
|
}
|
|
542
586
|
function normalizeBytes32(value, label) {
|
|
543
|
-
if (!(0,
|
|
587
|
+
if (!(0, import_viem5.isHex)(value, { strict: true }) || value.length !== 66) {
|
|
544
588
|
throw new ForeverMoneyError(
|
|
545
589
|
"INVALID_BYTES32",
|
|
546
590
|
`${label} must be a 32-byte hex value.`
|
|
@@ -549,16 +593,111 @@ function normalizeBytes32(value, label) {
|
|
|
549
593
|
return value.toLowerCase();
|
|
550
594
|
}
|
|
551
595
|
function normalizeOptionalBytes32(value, label) {
|
|
552
|
-
return value === void 0 ?
|
|
596
|
+
return value === void 0 ? import_viem5.zeroHash : normalizeBytes32(value, label);
|
|
553
597
|
}
|
|
554
598
|
|
|
555
599
|
// src/bridge/plans.ts
|
|
556
|
-
var
|
|
557
|
-
var
|
|
558
|
-
var
|
|
559
|
-
var
|
|
600
|
+
var erc20Abi = (0, import_viem6.parseAbi)(ERC20_ABI);
|
|
601
|
+
var spokeAbi = (0, import_viem6.parseAbi)(SPOKE_GATEWAY_ABI);
|
|
602
|
+
var alphaAbi = (0, import_viem6.parseAbi)(ALPHA_GATEWAY_ABI);
|
|
603
|
+
var stakingAbi = (0, import_viem6.parseAbi)(STAKING_ABI);
|
|
560
604
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
561
605
|
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
606
|
+
var MIN_LIQUID_SUBTENSOR_TO_EVM_WEI = 2000000n * EVM_WEI_PER_RAO;
|
|
607
|
+
var MAX_PARTNER_FEE_BPS = 1e4;
|
|
608
|
+
var NO_PARTNER_FEE = {
|
|
609
|
+
recipient: "0x0000000000000000000000000000000000000000",
|
|
610
|
+
bps: 0
|
|
611
|
+
};
|
|
612
|
+
function resolvePartnerFee(fee, gateway) {
|
|
613
|
+
if (fee === void 0) return NO_PARTNER_FEE;
|
|
614
|
+
if (typeof fee.bps !== "number" || !Number.isInteger(fee.bps) || fee.bps < 0 || fee.bps > MAX_PARTNER_FEE_BPS) {
|
|
615
|
+
throw new ForeverMoneyError(
|
|
616
|
+
"INVALID_PARTNER_FEE",
|
|
617
|
+
`Partner fee bps must be an integer between 0 and ${MAX_PARTNER_FEE_BPS}.`,
|
|
618
|
+
{ bps: fee.bps }
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
if (fee.bps === 0) return NO_PARTNER_FEE;
|
|
622
|
+
const recipient = normalizeEvmAddress(fee.recipient);
|
|
623
|
+
if (recipient === NO_PARTNER_FEE.recipient || recipient.toLowerCase() === gateway.toLowerCase()) {
|
|
624
|
+
throw new ForeverMoneyError(
|
|
625
|
+
"INVALID_PARTNER_FEE",
|
|
626
|
+
"Partner fee recipient must not be the zero address or the gateway.",
|
|
627
|
+
{ recipient }
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return { recipient, bps: fee.bps };
|
|
631
|
+
}
|
|
632
|
+
function partnerFeeCut(amount, bps) {
|
|
633
|
+
return amount * BigInt(bps) / 10000n;
|
|
634
|
+
}
|
|
635
|
+
function partnerFeeTaoTopUp(taoAmount, bps) {
|
|
636
|
+
const raw = partnerFeeCut(taoAmount, bps);
|
|
637
|
+
if (raw === 0n) return 0n;
|
|
638
|
+
return (raw + EVM_WEI_PER_RAO - 1n) / EVM_WEI_PER_RAO * EVM_WEI_PER_RAO;
|
|
639
|
+
}
|
|
640
|
+
function assertPartnerFeeAllowed(fee, maxBps) {
|
|
641
|
+
if (fee.bps > maxBps) {
|
|
642
|
+
throw new ForeverMoneyError(
|
|
643
|
+
"INVALID_PARTNER_FEE",
|
|
644
|
+
`Partner fee of ${fee.bps} bps exceeds the gateway maximum of ${maxBps} bps.`,
|
|
645
|
+
{ bps: fee.bps, maxBps }
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function bridgeAsset(evmChain, asset = "tao") {
|
|
650
|
+
if (asset !== "tao" && asset !== "sn80") {
|
|
651
|
+
throw new ForeverMoneyError(
|
|
652
|
+
"INVALID_TRANSACTION_PLAN",
|
|
653
|
+
'Asset must be "tao" or "sn80".'
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
if (asset === "sn80" && evmChain !== "base") {
|
|
657
|
+
throw new ForeverMoneyError(
|
|
658
|
+
"INVALID_TRANSACTION_PLAN",
|
|
659
|
+
"SN80 bridging is supported between Base and Subtensor."
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
const evm = getForeverMoneyEvmDeployment(evmChain);
|
|
663
|
+
return asset === "sn80" ? {
|
|
664
|
+
evmToken: foreverMoneyDeployment.base.contracts.wrappedSn80,
|
|
665
|
+
subtensorToken: foreverMoneyDeployment.subtensor.contracts.wrappedSn80,
|
|
666
|
+
label: "SN80",
|
|
667
|
+
wrappedLabel: "SN80"
|
|
668
|
+
} : {
|
|
669
|
+
evmToken: evm.contracts.wrappedTao,
|
|
670
|
+
subtensorToken: foreverMoneyDeployment.subtensor.contracts.wrappedTao,
|
|
671
|
+
label: "TAO",
|
|
672
|
+
wrappedLabel: "wrapped TAO"
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
function assertAssetMode(asset, mode) {
|
|
676
|
+
if (asset === "sn80" && mode !== "staked") {
|
|
677
|
+
throw new ForeverMoneyError(
|
|
678
|
+
"INVALID_TRANSACTION_PLAN",
|
|
679
|
+
"SN80 bridging requires staked subnet 80 input or delivery; liquid TAO conversion is not supported."
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function sourceNetuid(input) {
|
|
684
|
+
if (input.source !== "staked") return void 0;
|
|
685
|
+
const netuid = input.netuid ?? (input.asset === "sn80" ? SN80_NETUID : void 0);
|
|
686
|
+
if (netuid === void 0) {
|
|
687
|
+
throw new ForeverMoneyError(
|
|
688
|
+
"INVALID_TRANSACTION_PLAN",
|
|
689
|
+
"A netuid is required when bridging staked TAO."
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
assertNonNegativeAmount(netuid, "netuid");
|
|
693
|
+
if (input.asset === "sn80" && netuid !== SN80_NETUID) {
|
|
694
|
+
throw new ForeverMoneyError(
|
|
695
|
+
"INVALID_TRANSACTION_PLAN",
|
|
696
|
+
"SN80 staking approvals must use netuid 80."
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
return netuid;
|
|
700
|
+
}
|
|
562
701
|
function assertDelivery(value) {
|
|
563
702
|
if (value !== "liquid" && value !== "staked") {
|
|
564
703
|
throw new ForeverMoneyError(
|
|
@@ -588,6 +727,19 @@ function assertBaseToSubtensorAmount(amountWei, delivery) {
|
|
|
588
727
|
);
|
|
589
728
|
}
|
|
590
729
|
}
|
|
730
|
+
function assertSubtensorToEvmAmount(amountWei, source) {
|
|
731
|
+
assertWholeRao(amountWei);
|
|
732
|
+
if (source === "liquid" && amountWei < MIN_LIQUID_SUBTENSOR_TO_EVM_WEI) {
|
|
733
|
+
throw new ForeverMoneyError(
|
|
734
|
+
"AMOUNT_BELOW_MINIMUM",
|
|
735
|
+
"Bridging liquid TAO from Subtensor requires at least 0.002 TAO.",
|
|
736
|
+
{
|
|
737
|
+
amountWei: amountWei.toString(),
|
|
738
|
+
minimumAmountWei: MIN_LIQUID_SUBTENSOR_TO_EVM_WEI.toString()
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
591
743
|
function transactionStep(kind, label, chainId, from, to, data, value, gasLimit) {
|
|
592
744
|
return {
|
|
593
745
|
kind,
|
|
@@ -602,9 +754,94 @@ function transactionStep(kind, label, chainId, from, to, data, value, gasLimit)
|
|
|
602
754
|
}
|
|
603
755
|
};
|
|
604
756
|
}
|
|
757
|
+
function partnerFeeSummary(charged, unit, fee) {
|
|
758
|
+
if (fee.bps === 0) return "";
|
|
759
|
+
return ` Partner fee: ${charged} wei of ${unit} (${fee.bps} bps) on top, paid to ${fee.recipient}.`;
|
|
760
|
+
}
|
|
761
|
+
function encodeSpokeBridge(token, amountWei, exit, fee) {
|
|
762
|
+
return fee.bps === 0 ? (0, import_viem6.encodeFunctionData)({
|
|
763
|
+
abi: spokeAbi,
|
|
764
|
+
functionName: "bridgeToFinney",
|
|
765
|
+
args: [token, amountWei, exit]
|
|
766
|
+
}) : (0, import_viem6.encodeFunctionData)({
|
|
767
|
+
abi: spokeAbi,
|
|
768
|
+
functionName: "bridgeToFinneyWithFee",
|
|
769
|
+
args: [token, amountWei, exit, 0n, fee]
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
function encodeHubBridge(destSelector, token, recipient, taoAmount, stakedAlphaRao, minTokenOut, fee) {
|
|
773
|
+
const args = [
|
|
774
|
+
destSelector,
|
|
775
|
+
token,
|
|
776
|
+
recipient,
|
|
777
|
+
taoAmount,
|
|
778
|
+
stakedAlphaRao,
|
|
779
|
+
minTokenOut
|
|
780
|
+
];
|
|
781
|
+
return fee.bps === 0 ? (0, import_viem6.encodeFunctionData)({
|
|
782
|
+
abi: alphaAbi,
|
|
783
|
+
functionName: "bridgeOut",
|
|
784
|
+
args
|
|
785
|
+
}) : (0, import_viem6.encodeFunctionData)({
|
|
786
|
+
abi: alphaAbi,
|
|
787
|
+
functionName: "bridgeOutWithFee",
|
|
788
|
+
args: [...args, fee]
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee) {
|
|
792
|
+
const maxBps = await provider.readContract({
|
|
793
|
+
address: gateway,
|
|
794
|
+
abi: spokeAbi,
|
|
795
|
+
functionName: "maxIntegratorFeeBps"
|
|
796
|
+
});
|
|
797
|
+
assertPartnerFeeAllowed(fee, maxBps);
|
|
798
|
+
const [networkFee] = await provider.readContract({
|
|
799
|
+
address: gateway,
|
|
800
|
+
abi: spokeAbi,
|
|
801
|
+
functionName: "quoteBridgeToFinneyWithFee",
|
|
802
|
+
args: [token, amountWei, exit, 0n, fee]
|
|
803
|
+
});
|
|
804
|
+
return networkFee;
|
|
805
|
+
}
|
|
806
|
+
async function quoteHubWithFee(provider, gateway, destSelector, token, recipient, mintedAmount, taoAmount, stakedAlphaRao, fee) {
|
|
807
|
+
const maxBps = await provider.readContract({
|
|
808
|
+
address: gateway,
|
|
809
|
+
abi: alphaAbi,
|
|
810
|
+
functionName: "maxIntegratorFeeBps"
|
|
811
|
+
});
|
|
812
|
+
assertPartnerFeeAllowed(fee, maxBps);
|
|
813
|
+
const [networkFee, taoTopUp, alphaTopUpRao, crossing] = await provider.readContract({
|
|
814
|
+
address: gateway,
|
|
815
|
+
abi: alphaAbi,
|
|
816
|
+
functionName: "quoteBridgeOutWithFee",
|
|
817
|
+
args: [
|
|
818
|
+
destSelector,
|
|
819
|
+
token,
|
|
820
|
+
recipient,
|
|
821
|
+
mintedAmount,
|
|
822
|
+
taoAmount,
|
|
823
|
+
stakedAlphaRao,
|
|
824
|
+
fee
|
|
825
|
+
]
|
|
826
|
+
});
|
|
827
|
+
if (crossing !== mintedAmount || taoTopUp !== partnerFeeTaoTopUp(taoAmount, fee.bps) || alphaTopUpRao !== partnerFeeCut(stakedAlphaRao, fee.bps)) {
|
|
828
|
+
throw new ForeverMoneyError(
|
|
829
|
+
"INVALID_PROVIDER_RESPONSE",
|
|
830
|
+
"The gateway partner fee quote does not match the SDK fee calculation.",
|
|
831
|
+
{
|
|
832
|
+
crossing: crossing.toString(),
|
|
833
|
+
taoTopUp: taoTopUp.toString(),
|
|
834
|
+
alphaTopUpRao: alphaTopUpRao.toString()
|
|
835
|
+
}
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
return { networkFee, taoTopUp, alphaTopUpRao };
|
|
839
|
+
}
|
|
605
840
|
function buildEvmToSubtensorPlan(input) {
|
|
606
841
|
const sender = normalizeEvmAddress(input.sender);
|
|
842
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
607
843
|
assertDelivery(input.delivery);
|
|
844
|
+
assertAssetMode(input.asset, input.delivery);
|
|
608
845
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
609
846
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
610
847
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
@@ -616,19 +853,25 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
616
853
|
minTaoOut: input.amountWei
|
|
617
854
|
};
|
|
618
855
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
856
|
+
const partnerFee = resolvePartnerFee(
|
|
857
|
+
input.partnerFee,
|
|
858
|
+
evm.contracts.gateway
|
|
859
|
+
);
|
|
860
|
+
const cut = partnerFeeCut(input.amountWei, partnerFee.bps);
|
|
619
861
|
const steps = [];
|
|
620
|
-
if (input.allowanceWei < input.amountWei) {
|
|
862
|
+
if (input.allowanceWei < input.amountWei + cut) {
|
|
621
863
|
steps.push(
|
|
622
864
|
transactionStep(
|
|
623
865
|
"approval",
|
|
624
|
-
|
|
866
|
+
`Approve ${asset.wrappedLabel} for the ForeverMoney gateway`,
|
|
625
867
|
evm.chainId,
|
|
626
868
|
sender,
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
869
|
+
asset.evmToken,
|
|
870
|
+
(0, import_viem6.encodeFunctionData)({
|
|
871
|
+
abi: erc20Abi,
|
|
872
|
+
functionName: "approve",
|
|
873
|
+
args: [evm.contracts.gateway, input.amountWei + cut]
|
|
874
|
+
}),
|
|
632
875
|
0n
|
|
633
876
|
)
|
|
634
877
|
);
|
|
@@ -637,13 +880,15 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
637
880
|
steps.push(
|
|
638
881
|
transactionStep(
|
|
639
882
|
"transaction",
|
|
640
|
-
`Bridge
|
|
883
|
+
`Bridge ${asset.wrappedLabel} from ${evm.name} to Subtensor (${input.delivery})`,
|
|
641
884
|
evm.chainId,
|
|
642
885
|
sender,
|
|
643
886
|
evm.contracts.gateway,
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
887
|
+
encodeSpokeBridge(
|
|
888
|
+
asset.evmToken,
|
|
889
|
+
input.amountWei,
|
|
890
|
+
exit,
|
|
891
|
+
partnerFee
|
|
647
892
|
),
|
|
648
893
|
value,
|
|
649
894
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
@@ -651,7 +896,7 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
651
896
|
);
|
|
652
897
|
return createTransactionPlan({
|
|
653
898
|
action: evm.key === "base" ? "bridge.base-to-subtensor" : "bridge.robinhood-to-subtensor",
|
|
654
|
-
summary: `Bridge ${input.amountWei} wei of
|
|
899
|
+
summary: `Bridge ${input.amountWei} wei of ${asset.wrappedLabel} from ${evm.name} to ${destination}.${partnerFeeSummary(cut, asset.wrappedLabel, partnerFee)}`,
|
|
655
900
|
steps
|
|
656
901
|
});
|
|
657
902
|
}
|
|
@@ -661,21 +906,23 @@ function buildBaseToSubtensorPlan(input) {
|
|
|
661
906
|
function buildSubtensorToEvmPlan(input) {
|
|
662
907
|
const sender = normalizeEvmAddress(input.sender);
|
|
663
908
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
909
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
664
910
|
assertSource(input.source);
|
|
665
|
-
|
|
911
|
+
assertAssetMode(input.asset, input.source);
|
|
912
|
+
const netuid = sourceNetuid(input);
|
|
913
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
666
914
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
667
915
|
const { subtensor } = foreverMoneyDeployment;
|
|
668
916
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
917
|
+
const partnerFee = resolvePartnerFee(
|
|
918
|
+
input.partnerFee,
|
|
919
|
+
subtensor.contracts.gateway
|
|
920
|
+
);
|
|
669
921
|
const amountRao = input.amountWei / EVM_WEI_PER_RAO;
|
|
922
|
+
const alphaTopUpRao = input.source === "staked" ? partnerFeeCut(amountRao, partnerFee.bps) : 0n;
|
|
923
|
+
const taoTopUp = input.source === "liquid" ? partnerFeeTaoTopUp(input.amountWei, partnerFee.bps) : 0n;
|
|
670
924
|
const steps = [];
|
|
671
925
|
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
926
|
if (input.stakingAllowanceRao === void 0) {
|
|
680
927
|
throw new ForeverMoneyError(
|
|
681
928
|
"INVALID_TRANSACTION_PLAN",
|
|
@@ -683,19 +930,23 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
683
930
|
);
|
|
684
931
|
}
|
|
685
932
|
assertNonNegativeAmount(input.stakingAllowanceRao, "Staking allowance");
|
|
686
|
-
if (input.stakingAllowanceRao < amountRao) {
|
|
933
|
+
if (input.stakingAllowanceRao < amountRao + alphaTopUpRao) {
|
|
687
934
|
steps.push(
|
|
688
935
|
transactionStep(
|
|
689
936
|
"approval",
|
|
690
|
-
|
|
937
|
+
`Approve staked ${asset.label} for the ForeverMoney gateway`,
|
|
691
938
|
subtensor.chainId,
|
|
692
939
|
sender,
|
|
693
940
|
subtensor.contracts.stakingPrecompile,
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
941
|
+
(0, import_viem6.encodeFunctionData)({
|
|
942
|
+
abi: stakingAbi,
|
|
943
|
+
functionName: "approve",
|
|
944
|
+
args: [
|
|
945
|
+
subtensor.contracts.gateway,
|
|
946
|
+
netuid,
|
|
947
|
+
amountRao + alphaTopUpRao
|
|
948
|
+
]
|
|
949
|
+
}),
|
|
699
950
|
0n
|
|
700
951
|
)
|
|
701
952
|
);
|
|
@@ -708,30 +959,35 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
708
959
|
}
|
|
709
960
|
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
710
961
|
const stakedAlphaRao = input.source === "staked" ? amountRao : 0n;
|
|
711
|
-
const value = taoAmount + feeWithBuffer(input.exactNetworkFeeWei);
|
|
962
|
+
const value = taoAmount + taoTopUp + feeWithBuffer(input.exactNetworkFeeWei);
|
|
712
963
|
assertNonNegativeAmount(value, "Transaction value");
|
|
713
964
|
steps.push(
|
|
714
965
|
transactionStep(
|
|
715
966
|
"transaction",
|
|
716
|
-
`Bridge ${input.source}
|
|
967
|
+
`Bridge ${input.source} ${asset.label} from Subtensor to ${evm.name}`,
|
|
717
968
|
subtensor.chainId,
|
|
718
969
|
sender,
|
|
719
970
|
subtensor.contracts.gateway,
|
|
720
|
-
|
|
971
|
+
encodeHubBridge(
|
|
721
972
|
evm.ccipSelector,
|
|
722
|
-
|
|
973
|
+
asset.subtensorToken,
|
|
723
974
|
recipient,
|
|
724
975
|
taoAmount,
|
|
725
976
|
stakedAlphaRao,
|
|
726
|
-
input.amountWei
|
|
727
|
-
|
|
977
|
+
input.amountWei,
|
|
978
|
+
partnerFee
|
|
979
|
+
),
|
|
728
980
|
value,
|
|
729
981
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
730
982
|
)
|
|
731
983
|
);
|
|
732
984
|
return createTransactionPlan({
|
|
733
985
|
action: evm.key === "base" ? "bridge.subtensor-to-base" : "bridge.subtensor-to-robinhood",
|
|
734
|
-
summary: `Bridge ${input.amountWei} wei of ${input.source}
|
|
986
|
+
summary: `Bridge ${input.amountWei} wei of ${input.source} ${asset.label} from Subtensor to ${recipient} on ${evm.name}.${partnerFeeSummary(
|
|
987
|
+
input.source === "liquid" ? taoTopUp : alphaTopUpRao * EVM_WEI_PER_RAO,
|
|
988
|
+
input.source === "liquid" ? "TAO" : `staked ${asset.label}`,
|
|
989
|
+
partnerFee
|
|
990
|
+
)}`,
|
|
735
991
|
steps
|
|
736
992
|
});
|
|
737
993
|
}
|
|
@@ -740,7 +996,9 @@ function buildSubtensorToBasePlan(input) {
|
|
|
740
996
|
}
|
|
741
997
|
async function prepareEvmToSubtensor(provider, input) {
|
|
742
998
|
const sender = normalizeEvmAddress(input.sender);
|
|
999
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
743
1000
|
assertDelivery(input.delivery);
|
|
1001
|
+
assertAssetMode(input.asset, input.delivery);
|
|
744
1002
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
745
1003
|
const destination = normalizeSS58(input.destination);
|
|
746
1004
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
@@ -750,29 +1008,42 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
750
1008
|
wantLiquid: input.delivery === "liquid",
|
|
751
1009
|
minTaoOut: input.amountWei
|
|
752
1010
|
};
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
evm.contracts.gateway
|
|
756
|
-
SPOKE_GATEWAY_ABI,
|
|
757
|
-
provider
|
|
1011
|
+
const partnerFee = resolvePartnerFee(
|
|
1012
|
+
input.partnerFee,
|
|
1013
|
+
evm.contracts.gateway
|
|
758
1014
|
);
|
|
1015
|
+
const cut = partnerFeeCut(input.amountWei, partnerFee.bps);
|
|
759
1016
|
const [allowanceWei, exactNetworkFeeWei] = await Promise.all([
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1017
|
+
provider.readContract({
|
|
1018
|
+
address: asset.evmToken,
|
|
1019
|
+
abi: erc20Abi,
|
|
1020
|
+
functionName: "allowance",
|
|
1021
|
+
args: [sender, evm.contracts.gateway]
|
|
1022
|
+
}),
|
|
1023
|
+
partnerFee.bps === 0 ? provider.readContract({
|
|
1024
|
+
address: evm.contracts.gateway,
|
|
1025
|
+
abi: spokeAbi,
|
|
1026
|
+
functionName: "quoteBridgeToFinney",
|
|
1027
|
+
args: [asset.evmToken, input.amountWei, exit]
|
|
1028
|
+
}) : quoteSpokeWithFee(
|
|
1029
|
+
provider,
|
|
1030
|
+
evm.contracts.gateway,
|
|
1031
|
+
asset.evmToken,
|
|
1032
|
+
input.amountWei,
|
|
1033
|
+
exit,
|
|
1034
|
+
partnerFee
|
|
1035
|
+
)
|
|
767
1036
|
]);
|
|
768
1037
|
let estimatedBridgeGas;
|
|
769
|
-
if (allowanceWei >= input.amountWei) {
|
|
770
|
-
const data =
|
|
771
|
-
|
|
772
|
-
|
|
1038
|
+
if (allowanceWei >= input.amountWei + cut) {
|
|
1039
|
+
const data = encodeSpokeBridge(
|
|
1040
|
+
asset.evmToken,
|
|
1041
|
+
input.amountWei,
|
|
1042
|
+
exit,
|
|
1043
|
+
partnerFee
|
|
773
1044
|
);
|
|
774
1045
|
estimatedBridgeGas = await provider.estimateGas({
|
|
775
|
-
|
|
1046
|
+
account: sender,
|
|
776
1047
|
to: evm.contracts.gateway,
|
|
777
1048
|
data,
|
|
778
1049
|
value: feeWithBuffer(exactNetworkFeeWei)
|
|
@@ -787,6 +1058,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
787
1058
|
return Object.freeze({
|
|
788
1059
|
exactNetworkFeeWei,
|
|
789
1060
|
transactionValueWei: feeWithBuffer(exactNetworkFeeWei),
|
|
1061
|
+
partnerFeeWei: cut,
|
|
790
1062
|
plan
|
|
791
1063
|
});
|
|
792
1064
|
}
|
|
@@ -796,58 +1068,67 @@ function prepareBaseToSubtensor(provider, input) {
|
|
|
796
1068
|
async function prepareSubtensorToEvm(provider, input) {
|
|
797
1069
|
const sender = normalizeEvmAddress(input.sender);
|
|
798
1070
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
1071
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
799
1072
|
assertSource(input.source);
|
|
800
|
-
|
|
1073
|
+
assertAssetMode(input.asset, input.source);
|
|
1074
|
+
const netuid = sourceNetuid(input);
|
|
1075
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
801
1076
|
const { subtensor } = foreverMoneyDeployment;
|
|
802
1077
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
provider
|
|
1078
|
+
const partnerFee = resolvePartnerFee(
|
|
1079
|
+
input.partnerFee,
|
|
1080
|
+
subtensor.contracts.gateway
|
|
807
1081
|
);
|
|
808
|
-
const
|
|
1082
|
+
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
1083
|
+
const stakedAlphaRao = input.source === "staked" ? input.amountWei / EVM_WEI_PER_RAO : 0n;
|
|
1084
|
+
const exactNetworkFeeWei = partnerFee.bps === 0 ? await provider.readContract({
|
|
1085
|
+
address: subtensor.contracts.gateway,
|
|
1086
|
+
abi: alphaAbi,
|
|
1087
|
+
functionName: "quoteBridgeOut",
|
|
1088
|
+
args: [
|
|
1089
|
+
evm.ccipSelector,
|
|
1090
|
+
asset.subtensorToken,
|
|
1091
|
+
recipient,
|
|
1092
|
+
input.amountWei
|
|
1093
|
+
]
|
|
1094
|
+
}) : (await quoteHubWithFee(
|
|
1095
|
+
provider,
|
|
1096
|
+
subtensor.contracts.gateway,
|
|
809
1097
|
evm.ccipSelector,
|
|
810
|
-
|
|
1098
|
+
asset.subtensorToken,
|
|
811
1099
|
recipient,
|
|
812
|
-
input.amountWei
|
|
813
|
-
|
|
1100
|
+
input.amountWei,
|
|
1101
|
+
taoAmount,
|
|
1102
|
+
stakedAlphaRao,
|
|
1103
|
+
partnerFee
|
|
1104
|
+
)).networkFee;
|
|
814
1105
|
let stakingAllowanceRao;
|
|
815
1106
|
if (input.source === "staked") {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
-
);
|
|
1107
|
+
stakingAllowanceRao = await provider.readContract({
|
|
1108
|
+
address: subtensor.contracts.stakingPrecompile,
|
|
1109
|
+
abi: stakingAbi,
|
|
1110
|
+
functionName: "allowance",
|
|
1111
|
+
args: [sender, subtensor.contracts.gateway, netuid]
|
|
1112
|
+
});
|
|
833
1113
|
}
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
const value = taoAmount + feeWithBuffer(exactNetworkFeeWei);
|
|
1114
|
+
const alphaTopUpRao = partnerFeeCut(stakedAlphaRao, partnerFee.bps);
|
|
1115
|
+
const taoTopUp = partnerFeeTaoTopUp(taoAmount, partnerFee.bps);
|
|
1116
|
+
const value = taoAmount + taoTopUp + feeWithBuffer(exactNetworkFeeWei);
|
|
837
1117
|
assertNonNegativeAmount(value, "Transaction value");
|
|
838
1118
|
let estimatedBridgeGas;
|
|
839
|
-
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao) {
|
|
1119
|
+
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao + alphaTopUpRao) {
|
|
840
1120
|
estimatedBridgeGas = await provider.estimateGas({
|
|
841
|
-
|
|
1121
|
+
account: sender,
|
|
842
1122
|
to: subtensor.contracts.gateway,
|
|
843
|
-
data:
|
|
1123
|
+
data: encodeHubBridge(
|
|
844
1124
|
evm.ccipSelector,
|
|
845
|
-
|
|
1125
|
+
asset.subtensorToken,
|
|
846
1126
|
recipient,
|
|
847
1127
|
taoAmount,
|
|
848
1128
|
stakedAlphaRao,
|
|
849
|
-
input.amountWei
|
|
850
|
-
|
|
1129
|
+
input.amountWei,
|
|
1130
|
+
partnerFee
|
|
1131
|
+
),
|
|
851
1132
|
value
|
|
852
1133
|
});
|
|
853
1134
|
}
|
|
@@ -860,6 +1141,7 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
860
1141
|
return Object.freeze({
|
|
861
1142
|
exactNetworkFeeWei,
|
|
862
1143
|
transactionValueWei: value,
|
|
1144
|
+
partnerFeeWei: input.source === "liquid" ? taoTopUp : alphaTopUpRao * EVM_WEI_PER_RAO,
|
|
863
1145
|
plan
|
|
864
1146
|
});
|
|
865
1147
|
}
|
|
@@ -868,7 +1150,7 @@ function prepareSubtensorToBase(provider, input) {
|
|
|
868
1150
|
}
|
|
869
1151
|
|
|
870
1152
|
// src/core/transport.ts
|
|
871
|
-
var
|
|
1153
|
+
var import_viem7 = require("viem");
|
|
872
1154
|
function isRecord(value) {
|
|
873
1155
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
874
1156
|
}
|
|
@@ -987,10 +1269,38 @@ function providerFromTransport(transport) {
|
|
|
987
1269
|
"An EIP-1193-compatible RPC transport is required."
|
|
988
1270
|
);
|
|
989
1271
|
}
|
|
990
|
-
return
|
|
1272
|
+
return (0, import_viem7.createPublicClient)({
|
|
1273
|
+
transport: (0, import_viem7.custom)(transport, { retryCount: 0 }),
|
|
1274
|
+
cacheTime: 0,
|
|
1275
|
+
batch: { multicall: false }
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
function trackingClient(provider) {
|
|
1279
|
+
if (provider && "getChainId" in provider && typeof provider.getChainId === "function")
|
|
1280
|
+
return provider;
|
|
1281
|
+
if (provider && "send" in provider && typeof provider.send === "function") {
|
|
1282
|
+
return providerFromTransport({
|
|
1283
|
+
request: ({ method, params }) => {
|
|
1284
|
+
if (params !== void 0 && !Array.isArray(params))
|
|
1285
|
+
throw new ForeverMoneyError(
|
|
1286
|
+
"RPC_ERROR",
|
|
1287
|
+
"Tracking RPC params must be an array."
|
|
1288
|
+
);
|
|
1289
|
+
return provider.send(
|
|
1290
|
+
method,
|
|
1291
|
+
params === void 0 ? [] : [...params]
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
throw new ForeverMoneyError(
|
|
1297
|
+
"MISSING_TRANSPORT",
|
|
1298
|
+
"A viem public client or JSON-RPC provider is required."
|
|
1299
|
+
);
|
|
991
1300
|
}
|
|
992
1301
|
|
|
993
1302
|
// src/core/provider-errors.ts
|
|
1303
|
+
var import_viem8 = require("viem");
|
|
994
1304
|
function stringProperty(value, property) {
|
|
995
1305
|
if (typeof value !== "object" || value === null || !(property in value)) {
|
|
996
1306
|
return void 0;
|
|
@@ -1003,9 +1313,13 @@ async function providerOperation(operation, action) {
|
|
|
1003
1313
|
return await action();
|
|
1004
1314
|
} catch (error) {
|
|
1005
1315
|
if (error instanceof ForeverMoneyError) throw error;
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1316
|
+
const revert = error instanceof import_viem8.BaseError ? error.walk(
|
|
1317
|
+
(cause) => cause instanceof import_viem8.ContractFunctionRevertedError || cause instanceof import_viem8.ExecutionRevertedError
|
|
1318
|
+
) : void 0;
|
|
1319
|
+
const viemRevert = revert instanceof import_viem8.ContractFunctionRevertedError || revert instanceof import_viem8.ExecutionRevertedError;
|
|
1320
|
+
const causeCode = viemRevert ? revert.name : stringProperty(error, "code");
|
|
1321
|
+
const reason = viemRevert ? revert instanceof import_viem8.ContractFunctionRevertedError ? revert.data?.errorName ?? revert.reason : void 0 : stringProperty(error, "reason");
|
|
1322
|
+
const reverted = viemRevert || causeCode === "CALL_EXCEPTION";
|
|
1009
1323
|
throw new ForeverMoneyError(
|
|
1010
1324
|
reverted ? "SIMULATION_REVERTED" : "RPC_ERROR",
|
|
1011
1325
|
reverted ? `${operation} reverted.` : `${operation} failed.`,
|
|
@@ -1018,10 +1332,10 @@ async function providerOperation(operation, action) {
|
|
|
1018
1332
|
}
|
|
1019
1333
|
|
|
1020
1334
|
// src/vaults/plans.ts
|
|
1021
|
-
var
|
|
1022
|
-
var
|
|
1023
|
-
var
|
|
1024
|
-
var
|
|
1335
|
+
var import_viem9 = require("viem");
|
|
1336
|
+
var erc20Abi2 = (0, import_viem9.parseAbi)(ERC20_ABI);
|
|
1337
|
+
var factoryAbi = (0, import_viem9.parseAbi)(VAULT_FACTORY_ABI);
|
|
1338
|
+
var managerAbi = (0, import_viem9.parseAbi)(VAULT_MANAGER_ABI);
|
|
1025
1339
|
function step(kind, label, from, to, data, value = 0n) {
|
|
1026
1340
|
return {
|
|
1027
1341
|
kind,
|
|
@@ -1138,10 +1452,11 @@ function buildCreateVaultPlan(input) {
|
|
|
1138
1452
|
`Approve ${stash.token} for the vault factory`,
|
|
1139
1453
|
owner,
|
|
1140
1454
|
stash.token,
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1455
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1456
|
+
abi: erc20Abi2,
|
|
1457
|
+
functionName: "approve",
|
|
1458
|
+
args: [base.contracts.vaultFactory, stash.amount]
|
|
1459
|
+
})
|
|
1145
1460
|
)
|
|
1146
1461
|
);
|
|
1147
1462
|
}
|
|
@@ -1152,15 +1467,19 @@ function buildCreateVaultPlan(input) {
|
|
|
1152
1467
|
"Create ForeverMoney vault",
|
|
1153
1468
|
owner,
|
|
1154
1469
|
base.contracts.vaultFactory,
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1470
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1471
|
+
abi: factoryAbi,
|
|
1472
|
+
functionName: "create",
|
|
1473
|
+
args: [
|
|
1474
|
+
owner,
|
|
1475
|
+
associatedMiner,
|
|
1476
|
+
akAddress,
|
|
1477
|
+
poolManager,
|
|
1478
|
+
poolAddress,
|
|
1479
|
+
positionManagerImplementation,
|
|
1480
|
+
stashTokens
|
|
1481
|
+
]
|
|
1482
|
+
}),
|
|
1164
1483
|
value
|
|
1165
1484
|
)
|
|
1166
1485
|
);
|
|
@@ -1179,14 +1498,15 @@ async function prepareCreateVault(provider, input) {
|
|
|
1179
1498
|
const allowances = await Promise.all(
|
|
1180
1499
|
erc20Stash.map(async ({ token }) => ({
|
|
1181
1500
|
token,
|
|
1182
|
-
allowance: await
|
|
1183
|
-
token,
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1501
|
+
allowance: await provider.readContract({
|
|
1502
|
+
address: token,
|
|
1503
|
+
abi: erc20Abi2,
|
|
1504
|
+
functionName: "allowance",
|
|
1505
|
+
args: [
|
|
1506
|
+
owner,
|
|
1507
|
+
foreverMoneyDeployment.base.contracts.vaultFactory
|
|
1508
|
+
]
|
|
1509
|
+
})
|
|
1190
1510
|
}))
|
|
1191
1511
|
);
|
|
1192
1512
|
return buildCreateVaultPlan({ ...input, allowances });
|
|
@@ -1213,11 +1533,11 @@ function buildDepositVaultPlan(input) {
|
|
|
1213
1533
|
"Deposit native ETH into the vault as WETH",
|
|
1214
1534
|
owner,
|
|
1215
1535
|
manager,
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
deposit.amount
|
|
1220
|
-
|
|
1536
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1537
|
+
abi: managerAbi,
|
|
1538
|
+
functionName: "topUpAk",
|
|
1539
|
+
args: [akAddress, import_viem9.zeroAddress, deposit.amount]
|
|
1540
|
+
}),
|
|
1221
1541
|
deposit.amount
|
|
1222
1542
|
)
|
|
1223
1543
|
);
|
|
@@ -1237,10 +1557,11 @@ function buildDepositVaultPlan(input) {
|
|
|
1237
1557
|
`Approve ${deposit.token} for the vault manager`,
|
|
1238
1558
|
owner,
|
|
1239
1559
|
deposit.token,
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1560
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1561
|
+
abi: erc20Abi2,
|
|
1562
|
+
functionName: "approve",
|
|
1563
|
+
args: [manager, deposit.amount]
|
|
1564
|
+
})
|
|
1244
1565
|
)
|
|
1245
1566
|
);
|
|
1246
1567
|
}
|
|
@@ -1250,11 +1571,11 @@ function buildDepositVaultPlan(input) {
|
|
|
1250
1571
|
`Deposit ${deposit.token} into the vault`,
|
|
1251
1572
|
owner,
|
|
1252
1573
|
manager,
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
deposit.amount
|
|
1257
|
-
|
|
1574
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1575
|
+
abi: managerAbi,
|
|
1576
|
+
functionName: "topUpAk",
|
|
1577
|
+
args: [akAddress, deposit.token, deposit.amount]
|
|
1578
|
+
})
|
|
1258
1579
|
)
|
|
1259
1580
|
);
|
|
1260
1581
|
}
|
|
@@ -1274,11 +1595,12 @@ async function prepareDepositVault(provider, input) {
|
|
|
1274
1595
|
const allowances = await Promise.all(
|
|
1275
1596
|
erc20Deposits.map(async ({ token }) => ({
|
|
1276
1597
|
token,
|
|
1277
|
-
allowance: await
|
|
1278
|
-
token,
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1598
|
+
allowance: await provider.readContract({
|
|
1599
|
+
address: token,
|
|
1600
|
+
abi: erc20Abi2,
|
|
1601
|
+
functionName: "allowance",
|
|
1602
|
+
args: [owner, manager]
|
|
1603
|
+
})
|
|
1282
1604
|
}))
|
|
1283
1605
|
);
|
|
1284
1606
|
return buildDepositVaultPlan({ ...input, allowances });
|
|
@@ -1309,16 +1631,17 @@ function buildWithdrawVaultPlan(input) {
|
|
|
1309
1631
|
"Withdraw assets from the vault",
|
|
1310
1632
|
owner,
|
|
1311
1633
|
manager,
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1634
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1635
|
+
abi: managerAbi,
|
|
1636
|
+
functionName: "withdrawFromAkAndPositions",
|
|
1637
|
+
args: [
|
|
1315
1638
|
akAddress,
|
|
1316
1639
|
input.amount0,
|
|
1317
1640
|
input.amount1,
|
|
1318
1641
|
input.decreaseTokenIds,
|
|
1319
1642
|
input.unwrapWeth
|
|
1320
1643
|
]
|
|
1321
|
-
)
|
|
1644
|
+
})
|
|
1322
1645
|
)
|
|
1323
1646
|
]
|
|
1324
1647
|
});
|
|
@@ -1336,9 +1659,11 @@ function buildClaimVaultFeesPlan(input) {
|
|
|
1336
1659
|
"Claim vault fees",
|
|
1337
1660
|
owner,
|
|
1338
1661
|
manager,
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1662
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1663
|
+
abi: managerAbi,
|
|
1664
|
+
functionName: "claimFees",
|
|
1665
|
+
args: [akAddress]
|
|
1666
|
+
})
|
|
1342
1667
|
)
|
|
1343
1668
|
]
|
|
1344
1669
|
});
|
|
@@ -1358,20 +1683,21 @@ function buildSetVaultStakingPlan(input) {
|
|
|
1358
1683
|
`${input.staking ? "Stake" : "Unstake"} the vault position`,
|
|
1359
1684
|
owner,
|
|
1360
1685
|
manager,
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1686
|
+
(0, import_viem9.encodeFunctionData)({
|
|
1687
|
+
abi: managerAbi,
|
|
1688
|
+
functionName: action,
|
|
1689
|
+
args: [akAddress, "0x"]
|
|
1690
|
+
})
|
|
1365
1691
|
)
|
|
1366
1692
|
]
|
|
1367
1693
|
});
|
|
1368
1694
|
}
|
|
1369
1695
|
|
|
1370
1696
|
// src/bridge/tracking.ts
|
|
1371
|
-
var
|
|
1697
|
+
var import_viem11 = require("viem");
|
|
1372
1698
|
|
|
1373
1699
|
// src/bridge/receipts.ts
|
|
1374
|
-
var
|
|
1700
|
+
var import_viem10 = require("viem");
|
|
1375
1701
|
|
|
1376
1702
|
// src/core/receipts.ts
|
|
1377
1703
|
function isLogFrom(log, address) {
|
|
@@ -1379,8 +1705,8 @@ function isLogFrom(log, address) {
|
|
|
1379
1705
|
}
|
|
1380
1706
|
|
|
1381
1707
|
// src/bridge/receipts.ts
|
|
1382
|
-
var
|
|
1383
|
-
var
|
|
1708
|
+
var alphaGatewayAbi = (0, import_viem10.parseAbi)(ALPHA_GATEWAY_ABI);
|
|
1709
|
+
var spokeGatewayAbi = (0, import_viem10.parseAbi)(SPOKE_GATEWAY_ABI);
|
|
1384
1710
|
function assertBridgeDirection(value) {
|
|
1385
1711
|
if (value !== "base-to-subtensor" && value !== "robinhood-to-subtensor" && value !== "subtensor-to-base" && value !== "subtensor-to-robinhood") {
|
|
1386
1712
|
throw new ForeverMoneyError(
|
|
@@ -1403,20 +1729,28 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1403
1729
|
evmChainFromBridgeDirection(direction)
|
|
1404
1730
|
);
|
|
1405
1731
|
const evmToSubtensor = isEvmToSubtensorDirection(direction);
|
|
1406
|
-
const [
|
|
1407
|
-
|
|
1408
|
-
|
|
1732
|
+
const [addresses, contractAbi, eventName] = evmToSubtensor ? [
|
|
1733
|
+
[evm.contracts.legacyGateway, evm.contracts.gateway],
|
|
1734
|
+
spokeGatewayAbi,
|
|
1735
|
+
"BridgedToFinney"
|
|
1736
|
+
] : [
|
|
1737
|
+
[
|
|
1738
|
+
foreverMoneyDeployment.subtensor.contracts.legacyGateway,
|
|
1739
|
+
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1740
|
+
],
|
|
1741
|
+
alphaGatewayAbi,
|
|
1409
1742
|
"BridgedOut"
|
|
1410
1743
|
];
|
|
1411
1744
|
for (const log of receipt.logs) {
|
|
1412
|
-
if (!isLogFrom(log, address)) continue;
|
|
1745
|
+
if (!addresses.some((address) => isLogFrom(log, address))) continue;
|
|
1413
1746
|
try {
|
|
1414
|
-
const parsed =
|
|
1747
|
+
const parsed = (0, import_viem10.decodeEventLog)({
|
|
1748
|
+
abi: contractAbi,
|
|
1415
1749
|
data: log.data,
|
|
1416
1750
|
topics: [...log.topics]
|
|
1417
1751
|
});
|
|
1418
|
-
const messageId = parsed
|
|
1419
|
-
if (parsed
|
|
1752
|
+
const messageId = "messageId" in parsed.args ? parsed.args.messageId : void 0;
|
|
1753
|
+
if (parsed.eventName === eventName && typeof messageId === "string" && (0, import_viem10.isHex)(messageId, { strict: true }) && messageId.length === 66 && (evmToSubtensor || "destChainSelector" in parsed.args && parsed.args.destChainSelector === evm.ccipSelector)) {
|
|
1420
1754
|
return messageId.toLowerCase();
|
|
1421
1755
|
}
|
|
1422
1756
|
} catch {
|
|
@@ -1426,8 +1760,8 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1426
1760
|
}
|
|
1427
1761
|
|
|
1428
1762
|
// src/bridge/tracking.ts
|
|
1429
|
-
var
|
|
1430
|
-
var
|
|
1763
|
+
var ccipExecutionAbi = (0, import_viem11.parseAbi)(CCIP_EXECUTION_ABI);
|
|
1764
|
+
var alphaGatewayAbi2 = (0, import_viem11.parseAbi)(ALPHA_GATEWAY_ABI);
|
|
1431
1765
|
function destinationChainId(direction) {
|
|
1432
1766
|
assertBridgeDirection(direction);
|
|
1433
1767
|
const evm = getForeverMoneyEvmDeployment(
|
|
@@ -1443,14 +1777,14 @@ function sourceChainId(direction) {
|
|
|
1443
1777
|
return isEvmToSubtensorDirection(direction) ? evm.chainId : foreverMoneyDeployment.subtensor.chainId;
|
|
1444
1778
|
}
|
|
1445
1779
|
async function assertProviderChain(provider, expectedChainId, label) {
|
|
1446
|
-
const
|
|
1447
|
-
if (
|
|
1780
|
+
const chainId = await provider.getChainId();
|
|
1781
|
+
if (chainId !== expectedChainId) {
|
|
1448
1782
|
throw new ForeverMoneyError(
|
|
1449
1783
|
"CHAIN_MISMATCH",
|
|
1450
|
-
`${label} transport reported chain ID ${
|
|
1784
|
+
`${label} transport reported chain ID ${chainId}; expected ${expectedChainId}.`,
|
|
1451
1785
|
{
|
|
1452
1786
|
expectedChainId,
|
|
1453
|
-
actualChainId:
|
|
1787
|
+
actualChainId: chainId.toString()
|
|
1454
1788
|
}
|
|
1455
1789
|
);
|
|
1456
1790
|
}
|
|
@@ -1461,13 +1795,14 @@ async function assertDestinationProvider(provider, direction) {
|
|
|
1461
1795
|
return expectedChainId;
|
|
1462
1796
|
}
|
|
1463
1797
|
async function getBridgeSourceStatus(provider, input) {
|
|
1798
|
+
const client = trackingClient(provider);
|
|
1464
1799
|
const expectedChainId = sourceChainId(input.direction);
|
|
1465
1800
|
const transactionHash = normalizeBytes32(
|
|
1466
1801
|
input.transactionHash,
|
|
1467
1802
|
"Transaction hash"
|
|
1468
1803
|
);
|
|
1469
|
-
await assertProviderChain(
|
|
1470
|
-
const receipt = await
|
|
1804
|
+
await assertProviderChain(client, expectedChainId, "Bridge source");
|
|
1805
|
+
const receipt = await receiptOrNull(client, transactionHash);
|
|
1471
1806
|
if (receipt === null) {
|
|
1472
1807
|
return Object.freeze({
|
|
1473
1808
|
direction: input.direction,
|
|
@@ -1477,7 +1812,7 @@ async function getBridgeSourceStatus(provider, input) {
|
|
|
1477
1812
|
messageId: null
|
|
1478
1813
|
});
|
|
1479
1814
|
}
|
|
1480
|
-
if (receipt.status ===
|
|
1815
|
+
if (receipt.status === "reverted") {
|
|
1481
1816
|
return Object.freeze({
|
|
1482
1817
|
direction: input.direction,
|
|
1483
1818
|
sourceChainId: expectedChainId,
|
|
@@ -1486,7 +1821,7 @@ async function getBridgeSourceStatus(provider, input) {
|
|
|
1486
1821
|
messageId: null
|
|
1487
1822
|
});
|
|
1488
1823
|
}
|
|
1489
|
-
if (receipt.status !==
|
|
1824
|
+
if (receipt.status !== "success") {
|
|
1490
1825
|
throw new ForeverMoneyError(
|
|
1491
1826
|
"INVALID_PROVIDER_RESPONSE",
|
|
1492
1827
|
"The bridge source receipt has an invalid status."
|
|
@@ -1508,8 +1843,10 @@ async function getBridgeSourceStatus(provider, input) {
|
|
|
1508
1843
|
});
|
|
1509
1844
|
}
|
|
1510
1845
|
async function getCcipDeliveryCheckpoint(provider, direction) {
|
|
1511
|
-
const
|
|
1512
|
-
const
|
|
1846
|
+
const client = trackingClient(provider);
|
|
1847
|
+
const expectedChainId = await assertDestinationProvider(client, direction);
|
|
1848
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
1849
|
+
const fromBlock = Number(blockNumber);
|
|
1513
1850
|
if (!Number.isSafeInteger(fromBlock) || fromBlock < 0) {
|
|
1514
1851
|
throw new ForeverMoneyError(
|
|
1515
1852
|
"INVALID_PROVIDER_RESPONSE",
|
|
@@ -1523,6 +1860,7 @@ async function getCcipDeliveryCheckpoint(provider, direction) {
|
|
|
1523
1860
|
});
|
|
1524
1861
|
}
|
|
1525
1862
|
async function getCcipDeliveryStatus(provider, input) {
|
|
1863
|
+
const client = trackingClient(provider);
|
|
1526
1864
|
const expectedChainId = destinationChainId(input.direction);
|
|
1527
1865
|
const messageId = normalizeBytes32(input.messageId, "CCIP message ID");
|
|
1528
1866
|
if (!Number.isSafeInteger(input.fromBlock) || input.fromBlock < 0) {
|
|
@@ -1531,42 +1869,50 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1531
1869
|
"CCIP fromBlock must be a non-negative safe integer."
|
|
1532
1870
|
);
|
|
1533
1871
|
}
|
|
1534
|
-
await assertProviderChain(
|
|
1535
|
-
const event = ccipExecutionInterface.getEvent("ExecutionStateChanged");
|
|
1536
|
-
if (event === null) {
|
|
1537
|
-
throw new Error("CCIP execution event is missing from the SDK ABI.");
|
|
1538
|
-
}
|
|
1872
|
+
await assertProviderChain(client, expectedChainId, "Bridge destination");
|
|
1539
1873
|
const evmChain = evmChainFromBridgeDirection(input.direction);
|
|
1540
1874
|
const evm = getForeverMoneyEvmDeployment(evmChain);
|
|
1541
1875
|
const evmToSubtensor = isEvmToSubtensorDirection(input.direction);
|
|
1542
1876
|
const sourceSelector = evmToSubtensor ? evm.ccipSelector : foreverMoneyDeployment.subtensor.ccipSelector;
|
|
1543
1877
|
const offRamp = evmToSubtensor ? evmChain === "base" ? foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromBase : foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromRobinhood : evm.contracts.ccipOffRampFromSubtensor;
|
|
1544
|
-
const logs = await
|
|
1878
|
+
const logs = await client.getLogs({
|
|
1545
1879
|
address: offRamp,
|
|
1546
|
-
fromBlock: input.fromBlock,
|
|
1880
|
+
fromBlock: BigInt(input.fromBlock),
|
|
1547
1881
|
toBlock: "latest",
|
|
1548
|
-
|
|
1882
|
+
event: ccipExecutionAbi[0],
|
|
1883
|
+
args: { sourceChainSelector: sourceSelector, messageId },
|
|
1884
|
+
strict: true
|
|
1549
1885
|
});
|
|
1550
1886
|
const latest = logs.at(-1);
|
|
1551
1887
|
if (latest === void 0) return "waiting";
|
|
1552
|
-
const
|
|
1553
|
-
const state = Number(parsed?.args.state);
|
|
1888
|
+
const state = latest.args.state;
|
|
1554
1889
|
if (state === 3) return "failure";
|
|
1555
1890
|
if (state !== 2) return "waiting";
|
|
1556
1891
|
if (!evmToSubtensor) return "success";
|
|
1557
|
-
const receipt = await
|
|
1892
|
+
const receipt = await receiptOrNull(client, latest.transactionHash);
|
|
1558
1893
|
if (receipt === null) {
|
|
1559
1894
|
throw new ForeverMoneyError(
|
|
1560
1895
|
"INVALID_PROVIDER_RESPONSE",
|
|
1561
1896
|
"The CCIP execution receipt is unavailable."
|
|
1562
1897
|
);
|
|
1563
1898
|
}
|
|
1899
|
+
const subtensorGateways = [
|
|
1900
|
+
foreverMoneyDeployment.subtensor.contracts.legacyGateway,
|
|
1901
|
+
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1902
|
+
];
|
|
1564
1903
|
for (const log of receipt.logs) {
|
|
1565
|
-
if (
|
|
1904
|
+
if (!subtensorGateways.some(
|
|
1905
|
+
(gateway) => log.address.toLowerCase() === gateway.toLowerCase()
|
|
1906
|
+
)) {
|
|
1566
1907
|
continue;
|
|
1567
1908
|
}
|
|
1568
1909
|
try {
|
|
1569
|
-
|
|
1910
|
+
const { eventName } = (0, import_viem11.decodeEventLog)({
|
|
1911
|
+
abi: alphaGatewayAbi2,
|
|
1912
|
+
data: log.data,
|
|
1913
|
+
topics: log.topics
|
|
1914
|
+
});
|
|
1915
|
+
if (eventName === "Claimable" || eventName === "NotDelivered") {
|
|
1570
1916
|
return "recovery";
|
|
1571
1917
|
}
|
|
1572
1918
|
} catch {
|
|
@@ -1574,20 +1920,28 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1574
1920
|
}
|
|
1575
1921
|
return "success";
|
|
1576
1922
|
}
|
|
1923
|
+
async function receiptOrNull(provider, hash) {
|
|
1924
|
+
try {
|
|
1925
|
+
return await provider.getTransactionReceipt({ hash });
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
if (error instanceof import_viem11.TransactionReceiptNotFoundError) return null;
|
|
1928
|
+
throw error;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1577
1931
|
|
|
1578
1932
|
// src/client.ts
|
|
1579
1933
|
async function assertProviderChain2(provider, expectedChainId, name) {
|
|
1580
|
-
const
|
|
1934
|
+
const chainId = await providerOperation(
|
|
1581
1935
|
`Reading the ${name} chain ID`,
|
|
1582
|
-
() => provider.
|
|
1936
|
+
() => provider.getChainId()
|
|
1583
1937
|
);
|
|
1584
|
-
if (
|
|
1938
|
+
if (chainId !== expectedChainId) {
|
|
1585
1939
|
throw new ForeverMoneyError(
|
|
1586
1940
|
"CHAIN_MISMATCH",
|
|
1587
|
-
`${name} transport reported chain ID ${
|
|
1941
|
+
`${name} transport reported chain ID ${chainId}; expected ${expectedChainId}.`,
|
|
1588
1942
|
{
|
|
1589
1943
|
expectedChainId,
|
|
1590
|
-
actualChainId:
|
|
1944
|
+
actualChainId: chainId.toString()
|
|
1591
1945
|
}
|
|
1592
1946
|
);
|
|
1593
1947
|
}
|
|
@@ -1719,19 +2073,20 @@ function createForeverMoneyClient(options) {
|
|
|
1719
2073
|
}
|
|
1720
2074
|
|
|
1721
2075
|
// src/vaults/receipts.ts
|
|
1722
|
-
var
|
|
1723
|
-
var
|
|
2076
|
+
var import_viem12 = require("viem");
|
|
2077
|
+
var vaultFactoryAbi = (0, import_viem12.parseAbi)(VAULT_FACTORY_ABI);
|
|
1724
2078
|
function vaultManagerFromCreationReceipt(receipt) {
|
|
1725
2079
|
for (const log of receipt.logs) {
|
|
1726
2080
|
if (!isLogFrom(log, foreverMoneyDeployment.base.contracts.vaultFactory)) {
|
|
1727
2081
|
continue;
|
|
1728
2082
|
}
|
|
1729
2083
|
try {
|
|
1730
|
-
const parsed =
|
|
2084
|
+
const parsed = (0, import_viem12.decodeEventLog)({
|
|
2085
|
+
abi: vaultFactoryAbi,
|
|
1731
2086
|
data: log.data,
|
|
1732
2087
|
topics: [...log.topics]
|
|
1733
2088
|
});
|
|
1734
|
-
if (parsed
|
|
2089
|
+
if (parsed.eventName === "SnLiquidityManagerCreated") {
|
|
1735
2090
|
return normalizeEvmAddress(String(parsed.args.manager));
|
|
1736
2091
|
}
|
|
1737
2092
|
} catch {
|
|
@@ -1741,7 +2096,7 @@ function vaultManagerFromCreationReceipt(receipt) {
|
|
|
1741
2096
|
}
|
|
1742
2097
|
|
|
1743
2098
|
// src/core/transactions.ts
|
|
1744
|
-
var
|
|
2099
|
+
var import_viem13 = require("viem");
|
|
1745
2100
|
function decimalQuantity(value, label) {
|
|
1746
2101
|
if (!/^(0|[1-9][0-9]*)$/.test(value)) {
|
|
1747
2102
|
throw new ForeverMoneyError(
|
|
@@ -1750,7 +2105,7 @@ function decimalQuantity(value, label) {
|
|
|
1750
2105
|
);
|
|
1751
2106
|
}
|
|
1752
2107
|
const quantity = BigInt(value);
|
|
1753
|
-
if (quantity >
|
|
2108
|
+
if (quantity > import_viem13.maxUint256) {
|
|
1754
2109
|
throw new ForeverMoneyError(
|
|
1755
2110
|
"INVALID_TRANSACTION_PLAN",
|
|
1756
2111
|
`${label} exceeds uint256.`
|
|
@@ -1786,4 +2141,50 @@ function toEthersTransaction(transaction) {
|
|
|
1786
2141
|
}
|
|
1787
2142
|
});
|
|
1788
2143
|
}
|
|
2144
|
+
var transactionChains = new Map(
|
|
2145
|
+
[
|
|
2146
|
+
[BASE_CHAIN_ID, "Base", "Ether", "ETH"],
|
|
2147
|
+
[ROBINHOOD_CHAIN_ID, "Robinhood", "Ether", "ETH"],
|
|
2148
|
+
[SUBTENSOR_CHAIN_ID, "Subtensor", "TAO", "TAO"]
|
|
2149
|
+
].map(([id, name, currency, symbol]) => [
|
|
2150
|
+
id,
|
|
2151
|
+
Object.freeze(
|
|
2152
|
+
(0, import_viem13.defineChain)({
|
|
2153
|
+
id,
|
|
2154
|
+
name,
|
|
2155
|
+
nativeCurrency: Object.freeze({
|
|
2156
|
+
name: currency,
|
|
2157
|
+
symbol,
|
|
2158
|
+
decimals: 18
|
|
2159
|
+
}),
|
|
2160
|
+
// Chain identity only. The caller retains ownership of the wallet transport.
|
|
2161
|
+
rpcUrls: Object.freeze({
|
|
2162
|
+
default: Object.freeze({ http: Object.freeze([]) })
|
|
2163
|
+
})
|
|
2164
|
+
})
|
|
2165
|
+
)
|
|
2166
|
+
])
|
|
2167
|
+
);
|
|
2168
|
+
function toViemTransaction(transaction) {
|
|
2169
|
+
const chain = transactionChains.get(transaction.chainId);
|
|
2170
|
+
if (!chain)
|
|
2171
|
+
throw new ForeverMoneyError(
|
|
2172
|
+
"INVALID_TRANSACTION_PLAN",
|
|
2173
|
+
"Unsupported transaction chain."
|
|
2174
|
+
);
|
|
2175
|
+
return Object.freeze({
|
|
2176
|
+
chain,
|
|
2177
|
+
account: transaction.from,
|
|
2178
|
+
chainId: transaction.chainId,
|
|
2179
|
+
to: transaction.to,
|
|
2180
|
+
data: transaction.data,
|
|
2181
|
+
value: decimalQuantity(transaction.value, "Transaction value"),
|
|
2182
|
+
...transaction.gasLimit === void 0 ? {} : {
|
|
2183
|
+
gas: decimalQuantity(
|
|
2184
|
+
transaction.gasLimit,
|
|
2185
|
+
"Transaction gas limit"
|
|
2186
|
+
)
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
1789
2190
|
//# sourceMappingURL=index.cjs.map
|