@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.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
// src/core/addresses.ts
|
|
2
|
-
import { isHex, u8aToHex } from "@polkadot/util";
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "@polkadot/
|
|
8
|
-
import {
|
|
3
|
+
AccountId,
|
|
4
|
+
Blake2256,
|
|
5
|
+
getSs58AddressInfo
|
|
6
|
+
} from "@polkadot-api/substrate-bindings";
|
|
7
|
+
import {
|
|
8
|
+
bytesToHex,
|
|
9
|
+
concat,
|
|
10
|
+
getAddress,
|
|
11
|
+
hexToBytes,
|
|
12
|
+
isAddress,
|
|
13
|
+
stringToBytes,
|
|
14
|
+
zeroAddress
|
|
15
|
+
} from "viem";
|
|
9
16
|
|
|
10
17
|
// src/core/errors.ts
|
|
11
18
|
var ForeverMoneyError = class extends Error {
|
|
@@ -22,22 +29,33 @@ var ForeverMoneyError = class extends Error {
|
|
|
22
29
|
// src/core/addresses.ts
|
|
23
30
|
var BITTENSOR_SS58_PREFIX = 42;
|
|
24
31
|
function normalizeEvmAddress(value) {
|
|
25
|
-
|
|
32
|
+
const candidate = typeof value === "string" && /^0x[0-9A-F]{40}$/.test(value) ? value.toLowerCase() : value;
|
|
33
|
+
if (!isAddress(candidate) || candidate === zeroAddress) {
|
|
26
34
|
throw new ForeverMoneyError(
|
|
27
35
|
"INVALID_ADDRESS",
|
|
28
36
|
"Expected a non-zero EVM address."
|
|
29
37
|
);
|
|
30
38
|
}
|
|
31
|
-
return getAddress(
|
|
39
|
+
return getAddress(candidate);
|
|
32
40
|
}
|
|
33
41
|
function evmToMirrorSS58(evmAddress) {
|
|
34
|
-
return
|
|
42
|
+
return AccountId(BITTENSOR_SS58_PREFIX).dec(
|
|
43
|
+
Blake2256(
|
|
44
|
+
concat([
|
|
45
|
+
stringToBytes("evm:"),
|
|
46
|
+
hexToBytes(normalizeEvmAddress(evmAddress))
|
|
47
|
+
])
|
|
48
|
+
)
|
|
49
|
+
);
|
|
35
50
|
}
|
|
36
51
|
function decodeBittensorAddress(value) {
|
|
37
52
|
try {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
if (typeof value !== "string" || !value || value.startsWith("0x"))
|
|
54
|
+
throw new Error("Raw hex is not SS58.");
|
|
55
|
+
const info = getSs58AddressInfo(value);
|
|
56
|
+
if (!info.isValid || info.ss58Format !== BITTENSOR_SS58_PREFIX || info.publicKey.length !== 32)
|
|
57
|
+
throw new Error("Invalid Bittensor address.");
|
|
58
|
+
const publicKey = info.publicKey;
|
|
41
59
|
return publicKey;
|
|
42
60
|
} catch {
|
|
43
61
|
throw new ForeverMoneyError(
|
|
@@ -47,7 +65,7 @@ function decodeBittensorAddress(value) {
|
|
|
47
65
|
}
|
|
48
66
|
}
|
|
49
67
|
function normalizeSS58(value) {
|
|
50
|
-
return
|
|
68
|
+
return AccountId(BITTENSOR_SS58_PREFIX).dec(decodeBittensorAddress(value));
|
|
51
69
|
}
|
|
52
70
|
function isBittensorSS58(value) {
|
|
53
71
|
try {
|
|
@@ -58,14 +76,14 @@ function isBittensorSS58(value) {
|
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
78
|
function ss58ToPublicKey(value) {
|
|
61
|
-
return
|
|
79
|
+
return bytesToHex(decodeBittensorAddress(value));
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
// src/core/amounts.ts
|
|
65
|
-
import {
|
|
83
|
+
import { maxUint256, formatUnits, parseUnits } from "viem";
|
|
66
84
|
|
|
67
85
|
// src/chains/deployment.ts
|
|
68
|
-
import { getAddress as getAddress2 } from "
|
|
86
|
+
import { getAddress as getAddress2 } from "viem";
|
|
69
87
|
var BASE_CHAIN_ID = 8453;
|
|
70
88
|
var ROBINHOOD_CHAIN_ID = 4663;
|
|
71
89
|
var SUBTENSOR_CHAIN_ID = 964;
|
|
@@ -74,7 +92,8 @@ var ROBINHOOD_CCIP_SELECTOR = 6180753054346818345n;
|
|
|
74
92
|
var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
75
93
|
var RAO_PER_TAO = 1000000000n;
|
|
76
94
|
var EVM_WEI_PER_RAO = 1000000000n;
|
|
77
|
-
var
|
|
95
|
+
var SN80_NETUID = 80n;
|
|
96
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.2.0";
|
|
78
97
|
var foreverMoneyDeployment = Object.freeze({
|
|
79
98
|
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
80
99
|
base: Object.freeze({
|
|
@@ -83,10 +102,16 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
83
102
|
chainId: BASE_CHAIN_ID,
|
|
84
103
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
85
104
|
contracts: Object.freeze({
|
|
86
|
-
gateway: getAddress2("
|
|
105
|
+
gateway: getAddress2("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
106
|
+
legacyGateway: getAddress2(
|
|
107
|
+
"0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C"
|
|
108
|
+
),
|
|
87
109
|
wrappedTao: getAddress2(
|
|
88
110
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
89
111
|
),
|
|
112
|
+
wrappedSn80: getAddress2(
|
|
113
|
+
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
114
|
+
),
|
|
90
115
|
ccipRouter: getAddress2(
|
|
91
116
|
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
92
117
|
),
|
|
@@ -112,7 +137,10 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
112
137
|
chainId: ROBINHOOD_CHAIN_ID,
|
|
113
138
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
114
139
|
contracts: Object.freeze({
|
|
115
|
-
gateway: getAddress2("
|
|
140
|
+
gateway: getAddress2("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
141
|
+
legacyGateway: getAddress2(
|
|
142
|
+
"0x53Dcc4FE04193e489BE537F722F65317DB1E65d8"
|
|
143
|
+
),
|
|
116
144
|
wrappedTao: getAddress2(
|
|
117
145
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
118
146
|
),
|
|
@@ -128,13 +156,19 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
128
156
|
chainId: SUBTENSOR_CHAIN_ID,
|
|
129
157
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
130
158
|
contracts: Object.freeze({
|
|
131
|
-
gateway: getAddress2("
|
|
159
|
+
gateway: getAddress2("0xcd0C6d98D0A126B1c113d15b4c28F38321437787"),
|
|
160
|
+
legacyGateway: getAddress2(
|
|
161
|
+
"0x998f20Fea90bF7792774dECc7f994716442B1705"
|
|
162
|
+
),
|
|
132
163
|
alphaVault: getAddress2(
|
|
133
164
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
134
165
|
),
|
|
135
166
|
wrappedTao: getAddress2(
|
|
136
167
|
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
137
168
|
),
|
|
169
|
+
wrappedSn80: getAddress2(
|
|
170
|
+
"0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222"
|
|
171
|
+
),
|
|
138
172
|
ccipRouter: getAddress2(
|
|
139
173
|
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
140
174
|
),
|
|
@@ -203,7 +237,7 @@ function assertWholeRao(amountWei) {
|
|
|
203
237
|
"The amount cannot be negative."
|
|
204
238
|
);
|
|
205
239
|
}
|
|
206
|
-
if (amountWei >
|
|
240
|
+
if (amountWei > maxUint256) {
|
|
207
241
|
throw new ForeverMoneyError(
|
|
208
242
|
"INVALID_TRANSACTION_PLAN",
|
|
209
243
|
"The amount exceeds uint256."
|
|
@@ -229,7 +263,7 @@ function formatTaoAmount(amountWei) {
|
|
|
229
263
|
"A TAO amount cannot be negative."
|
|
230
264
|
);
|
|
231
265
|
}
|
|
232
|
-
if (amountWei >
|
|
266
|
+
if (amountWei > maxUint256) {
|
|
233
267
|
throw new ForeverMoneyError(
|
|
234
268
|
"INVALID_TRANSACTION_PLAN",
|
|
235
269
|
"The TAO amount exceeds uint256."
|
|
@@ -241,7 +275,8 @@ function formatTaoAmount(amountWei) {
|
|
|
241
275
|
"TAO amounts must resolve to a whole RAO."
|
|
242
276
|
);
|
|
243
277
|
}
|
|
244
|
-
|
|
278
|
+
const formatted = formatUnits(amountWei, 18);
|
|
279
|
+
return formatted.includes(".") ? formatted : `${formatted}.0`;
|
|
245
280
|
}
|
|
246
281
|
function mulDivCeil(value, numerator, denominator) {
|
|
247
282
|
return (value * numerator + denominator - 1n) / denominator;
|
|
@@ -264,7 +299,7 @@ function feeWithBuffer(fee) {
|
|
|
264
299
|
BASIS_POINTS + NETWORK_FEE_BUFFER_BPS,
|
|
265
300
|
BASIS_POINTS
|
|
266
301
|
);
|
|
267
|
-
if (buffered >
|
|
302
|
+
if (buffered > maxUint256) {
|
|
268
303
|
throw new ForeverMoneyError(
|
|
269
304
|
"INVALID_TRANSACTION_PLAN",
|
|
270
305
|
"The buffered network fee exceeds uint256."
|
|
@@ -290,7 +325,7 @@ function gasLimitWithBuffer(gasLimit) {
|
|
|
290
325
|
BASIS_POINTS + GAS_LIMIT_BUFFER_BPS,
|
|
291
326
|
BASIS_POINTS
|
|
292
327
|
);
|
|
293
|
-
if (buffered >
|
|
328
|
+
if (buffered > maxUint256) {
|
|
294
329
|
throw new ForeverMoneyError(
|
|
295
330
|
"INVALID_TRANSACTION_PLAN",
|
|
296
331
|
"The buffered gas limit exceeds uint256."
|
|
@@ -314,23 +349,34 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
314
349
|
"function ROUTER() view returns (address)",
|
|
315
350
|
"function BITTENSOR_SELECTOR() view returns (uint64)",
|
|
316
351
|
"function SUBTENSOR_GATEWAY() view returns (address)",
|
|
352
|
+
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
353
|
+
"function bridgeFeeBps() view returns (uint16)",
|
|
317
354
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
318
355
|
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) payable returns (bytes32 messageId)",
|
|
356
|
+
"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)",
|
|
357
|
+
"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)",
|
|
319
358
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
320
359
|
]);
|
|
321
360
|
var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
322
361
|
"function ROUTER() view returns (address)",
|
|
323
362
|
"function allowedLane(uint64 selector) view returns (bool)",
|
|
363
|
+
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
364
|
+
"function bridgeFeeBps() view returns (uint16)",
|
|
365
|
+
"function integratorTaoTopUp(uint256 taoAmount,uint16 bps) pure returns (uint256)",
|
|
324
366
|
"function quoteBridgeOut(uint64 destSelector,address token,address recipient,uint256 mintedAmount) view returns (uint256 fee)",
|
|
325
367
|
"function bridgeOut(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
368
|
+
"function integratorCut(uint256 amount,uint16 bps) pure returns (uint256)",
|
|
369
|
+
"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)",
|
|
370
|
+
"function bridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
326
371
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
327
372
|
"function claimNative(address to)",
|
|
328
373
|
"function claimStaked(address token,bytes32 destColdkey,address to)",
|
|
329
374
|
"function claimToken(address token,address to)",
|
|
330
375
|
"function claimableNative(address account) view returns (uint256)",
|
|
331
|
-
"function claimableToken(address
|
|
376
|
+
"function claimableToken(address token,address account) view returns (uint256)",
|
|
332
377
|
"event BridgedOut(uint64 destChainSelector,address indexed token,address indexed sender,address indexed recipient,uint256 minted,bytes32 messageId)",
|
|
333
378
|
"event Claimable(address indexed token,address indexed user,uint256 native,uint256 wsn)",
|
|
379
|
+
"event NotDelivered(uint64 indexed sourceChainSelector,address indexed token,uint256 amount,uint8 reason)",
|
|
334
380
|
"event DeliveredLiquid(address indexed token,bytes32 indexed ss58,uint256 taoOut)",
|
|
335
381
|
"event DeliveredStaked(address indexed token,bytes32 indexed ss58,uint256 alphaRao)"
|
|
336
382
|
]);
|
|
@@ -374,10 +420,10 @@ var foreverMoneyAbis = Object.freeze({
|
|
|
374
420
|
});
|
|
375
421
|
|
|
376
422
|
// src/bridge/plans.ts
|
|
377
|
-
import {
|
|
423
|
+
import { encodeFunctionData, parseAbi } from "viem";
|
|
378
424
|
|
|
379
425
|
// src/core/plans.ts
|
|
380
|
-
import { keccak256,
|
|
426
|
+
import { keccak256, stringToBytes as stringToBytes2 } from "viem";
|
|
381
427
|
function createTransactionPlan(plan) {
|
|
382
428
|
const steps = plan.steps.map(
|
|
383
429
|
(step2) => Object.freeze({
|
|
@@ -394,12 +440,12 @@ function createTransactionPlan(plan) {
|
|
|
394
440
|
};
|
|
395
441
|
return Object.freeze({
|
|
396
442
|
...versionedPlan,
|
|
397
|
-
hash: keccak256(
|
|
443
|
+
hash: keccak256(stringToBytes2(JSON.stringify(versionedPlan)))
|
|
398
444
|
});
|
|
399
445
|
}
|
|
400
446
|
|
|
401
447
|
// src/core/validation.ts
|
|
402
|
-
import {
|
|
448
|
+
import { maxUint256 as maxUint2562, zeroHash, isHex } from "viem";
|
|
403
449
|
function assertBigInt(amount, label) {
|
|
404
450
|
if (typeof amount !== "bigint") {
|
|
405
451
|
throw new ForeverMoneyError(
|
|
@@ -422,7 +468,7 @@ function assertPositiveAmount(amount, label) {
|
|
|
422
468
|
`${label} cannot be negative.`
|
|
423
469
|
);
|
|
424
470
|
}
|
|
425
|
-
if (amount >
|
|
471
|
+
if (amount > maxUint2562) {
|
|
426
472
|
throw new ForeverMoneyError(
|
|
427
473
|
"INVALID_TRANSACTION_PLAN",
|
|
428
474
|
`${label} exceeds uint256.`
|
|
@@ -437,7 +483,7 @@ function assertNonNegativeAmount(amount, label) {
|
|
|
437
483
|
`${label} cannot be negative.`
|
|
438
484
|
);
|
|
439
485
|
}
|
|
440
|
-
if (amount >
|
|
486
|
+
if (amount > maxUint2562) {
|
|
441
487
|
throw new ForeverMoneyError(
|
|
442
488
|
"INVALID_TRANSACTION_PLAN",
|
|
443
489
|
`${label} exceeds uint256.`
|
|
@@ -469,7 +515,7 @@ function assertRecord(value, label) {
|
|
|
469
515
|
}
|
|
470
516
|
}
|
|
471
517
|
function normalizeBytes32(value, label) {
|
|
472
|
-
if (!
|
|
518
|
+
if (!isHex(value, { strict: true }) || value.length !== 66) {
|
|
473
519
|
throw new ForeverMoneyError(
|
|
474
520
|
"INVALID_BYTES32",
|
|
475
521
|
`${label} must be a 32-byte hex value.`
|
|
@@ -478,16 +524,111 @@ function normalizeBytes32(value, label) {
|
|
|
478
524
|
return value.toLowerCase();
|
|
479
525
|
}
|
|
480
526
|
function normalizeOptionalBytes32(value, label) {
|
|
481
|
-
return value === void 0 ?
|
|
527
|
+
return value === void 0 ? zeroHash : normalizeBytes32(value, label);
|
|
482
528
|
}
|
|
483
529
|
|
|
484
530
|
// src/bridge/plans.ts
|
|
485
|
-
var
|
|
486
|
-
var
|
|
487
|
-
var
|
|
488
|
-
var
|
|
531
|
+
var erc20Abi = parseAbi(ERC20_ABI);
|
|
532
|
+
var spokeAbi = parseAbi(SPOKE_GATEWAY_ABI);
|
|
533
|
+
var alphaAbi = parseAbi(ALPHA_GATEWAY_ABI);
|
|
534
|
+
var stakingAbi = parseAbi(STAKING_ABI);
|
|
489
535
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
490
536
|
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
537
|
+
var MIN_LIQUID_SUBTENSOR_TO_EVM_WEI = 2000000n * EVM_WEI_PER_RAO;
|
|
538
|
+
var MAX_PARTNER_FEE_BPS = 1e4;
|
|
539
|
+
var NO_PARTNER_FEE = {
|
|
540
|
+
recipient: "0x0000000000000000000000000000000000000000",
|
|
541
|
+
bps: 0
|
|
542
|
+
};
|
|
543
|
+
function resolvePartnerFee(fee, gateway) {
|
|
544
|
+
if (fee === void 0) return NO_PARTNER_FEE;
|
|
545
|
+
if (typeof fee.bps !== "number" || !Number.isInteger(fee.bps) || fee.bps < 0 || fee.bps > MAX_PARTNER_FEE_BPS) {
|
|
546
|
+
throw new ForeverMoneyError(
|
|
547
|
+
"INVALID_PARTNER_FEE",
|
|
548
|
+
`Partner fee bps must be an integer between 0 and ${MAX_PARTNER_FEE_BPS}.`,
|
|
549
|
+
{ bps: fee.bps }
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
if (fee.bps === 0) return NO_PARTNER_FEE;
|
|
553
|
+
const recipient = normalizeEvmAddress(fee.recipient);
|
|
554
|
+
if (recipient === NO_PARTNER_FEE.recipient || recipient.toLowerCase() === gateway.toLowerCase()) {
|
|
555
|
+
throw new ForeverMoneyError(
|
|
556
|
+
"INVALID_PARTNER_FEE",
|
|
557
|
+
"Partner fee recipient must not be the zero address or the gateway.",
|
|
558
|
+
{ recipient }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
return { recipient, bps: fee.bps };
|
|
562
|
+
}
|
|
563
|
+
function partnerFeeCut(amount, bps) {
|
|
564
|
+
return amount * BigInt(bps) / 10000n;
|
|
565
|
+
}
|
|
566
|
+
function partnerFeeTaoTopUp(taoAmount, bps) {
|
|
567
|
+
const raw = partnerFeeCut(taoAmount, bps);
|
|
568
|
+
if (raw === 0n) return 0n;
|
|
569
|
+
return (raw + EVM_WEI_PER_RAO - 1n) / EVM_WEI_PER_RAO * EVM_WEI_PER_RAO;
|
|
570
|
+
}
|
|
571
|
+
function assertPartnerFeeAllowed(fee, maxBps) {
|
|
572
|
+
if (fee.bps > maxBps) {
|
|
573
|
+
throw new ForeverMoneyError(
|
|
574
|
+
"INVALID_PARTNER_FEE",
|
|
575
|
+
`Partner fee of ${fee.bps} bps exceeds the gateway maximum of ${maxBps} bps.`,
|
|
576
|
+
{ bps: fee.bps, maxBps }
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function bridgeAsset(evmChain, asset = "tao") {
|
|
581
|
+
if (asset !== "tao" && asset !== "sn80") {
|
|
582
|
+
throw new ForeverMoneyError(
|
|
583
|
+
"INVALID_TRANSACTION_PLAN",
|
|
584
|
+
'Asset must be "tao" or "sn80".'
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
if (asset === "sn80" && evmChain !== "base") {
|
|
588
|
+
throw new ForeverMoneyError(
|
|
589
|
+
"INVALID_TRANSACTION_PLAN",
|
|
590
|
+
"SN80 bridging is supported between Base and Subtensor."
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const evm = getForeverMoneyEvmDeployment(evmChain);
|
|
594
|
+
return asset === "sn80" ? {
|
|
595
|
+
evmToken: foreverMoneyDeployment.base.contracts.wrappedSn80,
|
|
596
|
+
subtensorToken: foreverMoneyDeployment.subtensor.contracts.wrappedSn80,
|
|
597
|
+
label: "SN80",
|
|
598
|
+
wrappedLabel: "SN80"
|
|
599
|
+
} : {
|
|
600
|
+
evmToken: evm.contracts.wrappedTao,
|
|
601
|
+
subtensorToken: foreverMoneyDeployment.subtensor.contracts.wrappedTao,
|
|
602
|
+
label: "TAO",
|
|
603
|
+
wrappedLabel: "wrapped TAO"
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function assertAssetMode(asset, mode) {
|
|
607
|
+
if (asset === "sn80" && mode !== "staked") {
|
|
608
|
+
throw new ForeverMoneyError(
|
|
609
|
+
"INVALID_TRANSACTION_PLAN",
|
|
610
|
+
"SN80 bridging requires staked subnet 80 input or delivery; liquid TAO conversion is not supported."
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function sourceNetuid(input) {
|
|
615
|
+
if (input.source !== "staked") return void 0;
|
|
616
|
+
const netuid = input.netuid ?? (input.asset === "sn80" ? SN80_NETUID : void 0);
|
|
617
|
+
if (netuid === void 0) {
|
|
618
|
+
throw new ForeverMoneyError(
|
|
619
|
+
"INVALID_TRANSACTION_PLAN",
|
|
620
|
+
"A netuid is required when bridging staked TAO."
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
assertNonNegativeAmount(netuid, "netuid");
|
|
624
|
+
if (input.asset === "sn80" && netuid !== SN80_NETUID) {
|
|
625
|
+
throw new ForeverMoneyError(
|
|
626
|
+
"INVALID_TRANSACTION_PLAN",
|
|
627
|
+
"SN80 staking approvals must use netuid 80."
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return netuid;
|
|
631
|
+
}
|
|
491
632
|
function assertDelivery(value) {
|
|
492
633
|
if (value !== "liquid" && value !== "staked") {
|
|
493
634
|
throw new ForeverMoneyError(
|
|
@@ -517,6 +658,19 @@ function assertBaseToSubtensorAmount(amountWei, delivery) {
|
|
|
517
658
|
);
|
|
518
659
|
}
|
|
519
660
|
}
|
|
661
|
+
function assertSubtensorToEvmAmount(amountWei, source) {
|
|
662
|
+
assertWholeRao(amountWei);
|
|
663
|
+
if (source === "liquid" && amountWei < MIN_LIQUID_SUBTENSOR_TO_EVM_WEI) {
|
|
664
|
+
throw new ForeverMoneyError(
|
|
665
|
+
"AMOUNT_BELOW_MINIMUM",
|
|
666
|
+
"Bridging liquid TAO from Subtensor requires at least 0.002 TAO.",
|
|
667
|
+
{
|
|
668
|
+
amountWei: amountWei.toString(),
|
|
669
|
+
minimumAmountWei: MIN_LIQUID_SUBTENSOR_TO_EVM_WEI.toString()
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
520
674
|
function transactionStep(kind, label, chainId, from, to, data, value, gasLimit) {
|
|
521
675
|
return {
|
|
522
676
|
kind,
|
|
@@ -531,9 +685,94 @@ function transactionStep(kind, label, chainId, from, to, data, value, gasLimit)
|
|
|
531
685
|
}
|
|
532
686
|
};
|
|
533
687
|
}
|
|
688
|
+
function partnerFeeSummary(charged, unit, fee) {
|
|
689
|
+
if (fee.bps === 0) return "";
|
|
690
|
+
return ` Partner fee: ${charged} wei of ${unit} (${fee.bps} bps) on top, paid to ${fee.recipient}.`;
|
|
691
|
+
}
|
|
692
|
+
function encodeSpokeBridge(token, amountWei, exit, fee) {
|
|
693
|
+
return fee.bps === 0 ? encodeFunctionData({
|
|
694
|
+
abi: spokeAbi,
|
|
695
|
+
functionName: "bridgeToFinney",
|
|
696
|
+
args: [token, amountWei, exit]
|
|
697
|
+
}) : encodeFunctionData({
|
|
698
|
+
abi: spokeAbi,
|
|
699
|
+
functionName: "bridgeToFinneyWithFee",
|
|
700
|
+
args: [token, amountWei, exit, 0n, fee]
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
function encodeHubBridge(destSelector, token, recipient, taoAmount, stakedAlphaRao, minTokenOut, fee) {
|
|
704
|
+
const args = [
|
|
705
|
+
destSelector,
|
|
706
|
+
token,
|
|
707
|
+
recipient,
|
|
708
|
+
taoAmount,
|
|
709
|
+
stakedAlphaRao,
|
|
710
|
+
minTokenOut
|
|
711
|
+
];
|
|
712
|
+
return fee.bps === 0 ? encodeFunctionData({
|
|
713
|
+
abi: alphaAbi,
|
|
714
|
+
functionName: "bridgeOut",
|
|
715
|
+
args
|
|
716
|
+
}) : encodeFunctionData({
|
|
717
|
+
abi: alphaAbi,
|
|
718
|
+
functionName: "bridgeOutWithFee",
|
|
719
|
+
args: [...args, fee]
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee) {
|
|
723
|
+
const maxBps = await provider.readContract({
|
|
724
|
+
address: gateway,
|
|
725
|
+
abi: spokeAbi,
|
|
726
|
+
functionName: "maxIntegratorFeeBps"
|
|
727
|
+
});
|
|
728
|
+
assertPartnerFeeAllowed(fee, maxBps);
|
|
729
|
+
const [networkFee] = await provider.readContract({
|
|
730
|
+
address: gateway,
|
|
731
|
+
abi: spokeAbi,
|
|
732
|
+
functionName: "quoteBridgeToFinneyWithFee",
|
|
733
|
+
args: [token, amountWei, exit, 0n, fee]
|
|
734
|
+
});
|
|
735
|
+
return networkFee;
|
|
736
|
+
}
|
|
737
|
+
async function quoteHubWithFee(provider, gateway, destSelector, token, recipient, mintedAmount, taoAmount, stakedAlphaRao, fee) {
|
|
738
|
+
const maxBps = await provider.readContract({
|
|
739
|
+
address: gateway,
|
|
740
|
+
abi: alphaAbi,
|
|
741
|
+
functionName: "maxIntegratorFeeBps"
|
|
742
|
+
});
|
|
743
|
+
assertPartnerFeeAllowed(fee, maxBps);
|
|
744
|
+
const [networkFee, taoTopUp, alphaTopUpRao, crossing] = await provider.readContract({
|
|
745
|
+
address: gateway,
|
|
746
|
+
abi: alphaAbi,
|
|
747
|
+
functionName: "quoteBridgeOutWithFee",
|
|
748
|
+
args: [
|
|
749
|
+
destSelector,
|
|
750
|
+
token,
|
|
751
|
+
recipient,
|
|
752
|
+
mintedAmount,
|
|
753
|
+
taoAmount,
|
|
754
|
+
stakedAlphaRao,
|
|
755
|
+
fee
|
|
756
|
+
]
|
|
757
|
+
});
|
|
758
|
+
if (crossing !== mintedAmount || taoTopUp !== partnerFeeTaoTopUp(taoAmount, fee.bps) || alphaTopUpRao !== partnerFeeCut(stakedAlphaRao, fee.bps)) {
|
|
759
|
+
throw new ForeverMoneyError(
|
|
760
|
+
"INVALID_PROVIDER_RESPONSE",
|
|
761
|
+
"The gateway partner fee quote does not match the SDK fee calculation.",
|
|
762
|
+
{
|
|
763
|
+
crossing: crossing.toString(),
|
|
764
|
+
taoTopUp: taoTopUp.toString(),
|
|
765
|
+
alphaTopUpRao: alphaTopUpRao.toString()
|
|
766
|
+
}
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
return { networkFee, taoTopUp, alphaTopUpRao };
|
|
770
|
+
}
|
|
534
771
|
function buildEvmToSubtensorPlan(input) {
|
|
535
772
|
const sender = normalizeEvmAddress(input.sender);
|
|
773
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
536
774
|
assertDelivery(input.delivery);
|
|
775
|
+
assertAssetMode(input.asset, input.delivery);
|
|
537
776
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
538
777
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
539
778
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
@@ -545,19 +784,25 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
545
784
|
minTaoOut: input.amountWei
|
|
546
785
|
};
|
|
547
786
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
787
|
+
const partnerFee = resolvePartnerFee(
|
|
788
|
+
input.partnerFee,
|
|
789
|
+
evm.contracts.gateway
|
|
790
|
+
);
|
|
791
|
+
const cut = partnerFeeCut(input.amountWei, partnerFee.bps);
|
|
548
792
|
const steps = [];
|
|
549
|
-
if (input.allowanceWei < input.amountWei) {
|
|
793
|
+
if (input.allowanceWei < input.amountWei + cut) {
|
|
550
794
|
steps.push(
|
|
551
795
|
transactionStep(
|
|
552
796
|
"approval",
|
|
553
|
-
|
|
797
|
+
`Approve ${asset.wrappedLabel} for the ForeverMoney gateway`,
|
|
554
798
|
evm.chainId,
|
|
555
799
|
sender,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
800
|
+
asset.evmToken,
|
|
801
|
+
encodeFunctionData({
|
|
802
|
+
abi: erc20Abi,
|
|
803
|
+
functionName: "approve",
|
|
804
|
+
args: [evm.contracts.gateway, input.amountWei + cut]
|
|
805
|
+
}),
|
|
561
806
|
0n
|
|
562
807
|
)
|
|
563
808
|
);
|
|
@@ -566,13 +811,15 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
566
811
|
steps.push(
|
|
567
812
|
transactionStep(
|
|
568
813
|
"transaction",
|
|
569
|
-
`Bridge
|
|
814
|
+
`Bridge ${asset.wrappedLabel} from ${evm.name} to Subtensor (${input.delivery})`,
|
|
570
815
|
evm.chainId,
|
|
571
816
|
sender,
|
|
572
817
|
evm.contracts.gateway,
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
818
|
+
encodeSpokeBridge(
|
|
819
|
+
asset.evmToken,
|
|
820
|
+
input.amountWei,
|
|
821
|
+
exit,
|
|
822
|
+
partnerFee
|
|
576
823
|
),
|
|
577
824
|
value,
|
|
578
825
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
@@ -580,7 +827,7 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
580
827
|
);
|
|
581
828
|
return createTransactionPlan({
|
|
582
829
|
action: evm.key === "base" ? "bridge.base-to-subtensor" : "bridge.robinhood-to-subtensor",
|
|
583
|
-
summary: `Bridge ${input.amountWei} wei of
|
|
830
|
+
summary: `Bridge ${input.amountWei} wei of ${asset.wrappedLabel} from ${evm.name} to ${destination}.${partnerFeeSummary(cut, asset.wrappedLabel, partnerFee)}`,
|
|
584
831
|
steps
|
|
585
832
|
});
|
|
586
833
|
}
|
|
@@ -590,21 +837,23 @@ function buildBaseToSubtensorPlan(input) {
|
|
|
590
837
|
function buildSubtensorToEvmPlan(input) {
|
|
591
838
|
const sender = normalizeEvmAddress(input.sender);
|
|
592
839
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
840
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
593
841
|
assertSource(input.source);
|
|
594
|
-
|
|
842
|
+
assertAssetMode(input.asset, input.source);
|
|
843
|
+
const netuid = sourceNetuid(input);
|
|
844
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
595
845
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
596
846
|
const { subtensor } = foreverMoneyDeployment;
|
|
597
847
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
848
|
+
const partnerFee = resolvePartnerFee(
|
|
849
|
+
input.partnerFee,
|
|
850
|
+
subtensor.contracts.gateway
|
|
851
|
+
);
|
|
598
852
|
const amountRao = input.amountWei / EVM_WEI_PER_RAO;
|
|
853
|
+
const alphaTopUpRao = input.source === "staked" ? partnerFeeCut(amountRao, partnerFee.bps) : 0n;
|
|
854
|
+
const taoTopUp = input.source === "liquid" ? partnerFeeTaoTopUp(input.amountWei, partnerFee.bps) : 0n;
|
|
599
855
|
const steps = [];
|
|
600
856
|
if (input.source === "staked") {
|
|
601
|
-
if (input.netuid === void 0) {
|
|
602
|
-
throw new ForeverMoneyError(
|
|
603
|
-
"INVALID_TRANSACTION_PLAN",
|
|
604
|
-
"A netuid is required when bridging staked TAO."
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
assertNonNegativeAmount(input.netuid, "netuid");
|
|
608
857
|
if (input.stakingAllowanceRao === void 0) {
|
|
609
858
|
throw new ForeverMoneyError(
|
|
610
859
|
"INVALID_TRANSACTION_PLAN",
|
|
@@ -612,19 +861,23 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
612
861
|
);
|
|
613
862
|
}
|
|
614
863
|
assertNonNegativeAmount(input.stakingAllowanceRao, "Staking allowance");
|
|
615
|
-
if (input.stakingAllowanceRao < amountRao) {
|
|
864
|
+
if (input.stakingAllowanceRao < amountRao + alphaTopUpRao) {
|
|
616
865
|
steps.push(
|
|
617
866
|
transactionStep(
|
|
618
867
|
"approval",
|
|
619
|
-
|
|
868
|
+
`Approve staked ${asset.label} for the ForeverMoney gateway`,
|
|
620
869
|
subtensor.chainId,
|
|
621
870
|
sender,
|
|
622
871
|
subtensor.contracts.stakingPrecompile,
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
872
|
+
encodeFunctionData({
|
|
873
|
+
abi: stakingAbi,
|
|
874
|
+
functionName: "approve",
|
|
875
|
+
args: [
|
|
876
|
+
subtensor.contracts.gateway,
|
|
877
|
+
netuid,
|
|
878
|
+
amountRao + alphaTopUpRao
|
|
879
|
+
]
|
|
880
|
+
}),
|
|
628
881
|
0n
|
|
629
882
|
)
|
|
630
883
|
);
|
|
@@ -637,30 +890,35 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
637
890
|
}
|
|
638
891
|
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
639
892
|
const stakedAlphaRao = input.source === "staked" ? amountRao : 0n;
|
|
640
|
-
const value = taoAmount + feeWithBuffer(input.exactNetworkFeeWei);
|
|
893
|
+
const value = taoAmount + taoTopUp + feeWithBuffer(input.exactNetworkFeeWei);
|
|
641
894
|
assertNonNegativeAmount(value, "Transaction value");
|
|
642
895
|
steps.push(
|
|
643
896
|
transactionStep(
|
|
644
897
|
"transaction",
|
|
645
|
-
`Bridge ${input.source}
|
|
898
|
+
`Bridge ${input.source} ${asset.label} from Subtensor to ${evm.name}`,
|
|
646
899
|
subtensor.chainId,
|
|
647
900
|
sender,
|
|
648
901
|
subtensor.contracts.gateway,
|
|
649
|
-
|
|
902
|
+
encodeHubBridge(
|
|
650
903
|
evm.ccipSelector,
|
|
651
|
-
|
|
904
|
+
asset.subtensorToken,
|
|
652
905
|
recipient,
|
|
653
906
|
taoAmount,
|
|
654
907
|
stakedAlphaRao,
|
|
655
|
-
input.amountWei
|
|
656
|
-
|
|
908
|
+
input.amountWei,
|
|
909
|
+
partnerFee
|
|
910
|
+
),
|
|
657
911
|
value,
|
|
658
912
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
659
913
|
)
|
|
660
914
|
);
|
|
661
915
|
return createTransactionPlan({
|
|
662
916
|
action: evm.key === "base" ? "bridge.subtensor-to-base" : "bridge.subtensor-to-robinhood",
|
|
663
|
-
summary: `Bridge ${input.amountWei} wei of ${input.source}
|
|
917
|
+
summary: `Bridge ${input.amountWei} wei of ${input.source} ${asset.label} from Subtensor to ${recipient} on ${evm.name}.${partnerFeeSummary(
|
|
918
|
+
input.source === "liquid" ? taoTopUp : alphaTopUpRao * EVM_WEI_PER_RAO,
|
|
919
|
+
input.source === "liquid" ? "TAO" : `staked ${asset.label}`,
|
|
920
|
+
partnerFee
|
|
921
|
+
)}`,
|
|
664
922
|
steps
|
|
665
923
|
});
|
|
666
924
|
}
|
|
@@ -669,7 +927,9 @@ function buildSubtensorToBasePlan(input) {
|
|
|
669
927
|
}
|
|
670
928
|
async function prepareEvmToSubtensor(provider, input) {
|
|
671
929
|
const sender = normalizeEvmAddress(input.sender);
|
|
930
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
672
931
|
assertDelivery(input.delivery);
|
|
932
|
+
assertAssetMode(input.asset, input.delivery);
|
|
673
933
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
674
934
|
const destination = normalizeSS58(input.destination);
|
|
675
935
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
@@ -679,29 +939,42 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
679
939
|
wantLiquid: input.delivery === "liquid",
|
|
680
940
|
minTaoOut: input.amountWei
|
|
681
941
|
};
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
evm.contracts.gateway
|
|
685
|
-
SPOKE_GATEWAY_ABI,
|
|
686
|
-
provider
|
|
942
|
+
const partnerFee = resolvePartnerFee(
|
|
943
|
+
input.partnerFee,
|
|
944
|
+
evm.contracts.gateway
|
|
687
945
|
);
|
|
946
|
+
const cut = partnerFeeCut(input.amountWei, partnerFee.bps);
|
|
688
947
|
const [allowanceWei, exactNetworkFeeWei] = await Promise.all([
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
948
|
+
provider.readContract({
|
|
949
|
+
address: asset.evmToken,
|
|
950
|
+
abi: erc20Abi,
|
|
951
|
+
functionName: "allowance",
|
|
952
|
+
args: [sender, evm.contracts.gateway]
|
|
953
|
+
}),
|
|
954
|
+
partnerFee.bps === 0 ? provider.readContract({
|
|
955
|
+
address: evm.contracts.gateway,
|
|
956
|
+
abi: spokeAbi,
|
|
957
|
+
functionName: "quoteBridgeToFinney",
|
|
958
|
+
args: [asset.evmToken, input.amountWei, exit]
|
|
959
|
+
}) : quoteSpokeWithFee(
|
|
960
|
+
provider,
|
|
961
|
+
evm.contracts.gateway,
|
|
962
|
+
asset.evmToken,
|
|
963
|
+
input.amountWei,
|
|
964
|
+
exit,
|
|
965
|
+
partnerFee
|
|
966
|
+
)
|
|
696
967
|
]);
|
|
697
968
|
let estimatedBridgeGas;
|
|
698
|
-
if (allowanceWei >= input.amountWei) {
|
|
699
|
-
const data =
|
|
700
|
-
|
|
701
|
-
|
|
969
|
+
if (allowanceWei >= input.amountWei + cut) {
|
|
970
|
+
const data = encodeSpokeBridge(
|
|
971
|
+
asset.evmToken,
|
|
972
|
+
input.amountWei,
|
|
973
|
+
exit,
|
|
974
|
+
partnerFee
|
|
702
975
|
);
|
|
703
976
|
estimatedBridgeGas = await provider.estimateGas({
|
|
704
|
-
|
|
977
|
+
account: sender,
|
|
705
978
|
to: evm.contracts.gateway,
|
|
706
979
|
data,
|
|
707
980
|
value: feeWithBuffer(exactNetworkFeeWei)
|
|
@@ -716,6 +989,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
716
989
|
return Object.freeze({
|
|
717
990
|
exactNetworkFeeWei,
|
|
718
991
|
transactionValueWei: feeWithBuffer(exactNetworkFeeWei),
|
|
992
|
+
partnerFeeWei: cut,
|
|
719
993
|
plan
|
|
720
994
|
});
|
|
721
995
|
}
|
|
@@ -725,58 +999,67 @@ function prepareBaseToSubtensor(provider, input) {
|
|
|
725
999
|
async function prepareSubtensorToEvm(provider, input) {
|
|
726
1000
|
const sender = normalizeEvmAddress(input.sender);
|
|
727
1001
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
1002
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
728
1003
|
assertSource(input.source);
|
|
729
|
-
|
|
1004
|
+
assertAssetMode(input.asset, input.source);
|
|
1005
|
+
const netuid = sourceNetuid(input);
|
|
1006
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
730
1007
|
const { subtensor } = foreverMoneyDeployment;
|
|
731
1008
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
provider
|
|
1009
|
+
const partnerFee = resolvePartnerFee(
|
|
1010
|
+
input.partnerFee,
|
|
1011
|
+
subtensor.contracts.gateway
|
|
736
1012
|
);
|
|
737
|
-
const
|
|
1013
|
+
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
1014
|
+
const stakedAlphaRao = input.source === "staked" ? input.amountWei / EVM_WEI_PER_RAO : 0n;
|
|
1015
|
+
const exactNetworkFeeWei = partnerFee.bps === 0 ? await provider.readContract({
|
|
1016
|
+
address: subtensor.contracts.gateway,
|
|
1017
|
+
abi: alphaAbi,
|
|
1018
|
+
functionName: "quoteBridgeOut",
|
|
1019
|
+
args: [
|
|
1020
|
+
evm.ccipSelector,
|
|
1021
|
+
asset.subtensorToken,
|
|
1022
|
+
recipient,
|
|
1023
|
+
input.amountWei
|
|
1024
|
+
]
|
|
1025
|
+
}) : (await quoteHubWithFee(
|
|
1026
|
+
provider,
|
|
1027
|
+
subtensor.contracts.gateway,
|
|
738
1028
|
evm.ccipSelector,
|
|
739
|
-
|
|
1029
|
+
asset.subtensorToken,
|
|
740
1030
|
recipient,
|
|
741
|
-
input.amountWei
|
|
742
|
-
|
|
1031
|
+
input.amountWei,
|
|
1032
|
+
taoAmount,
|
|
1033
|
+
stakedAlphaRao,
|
|
1034
|
+
partnerFee
|
|
1035
|
+
)).networkFee;
|
|
743
1036
|
let stakingAllowanceRao;
|
|
744
1037
|
if (input.source === "staked") {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
assertNonNegativeAmount(input.netuid, "netuid");
|
|
752
|
-
const staking = new Contract(
|
|
753
|
-
subtensor.contracts.stakingPrecompile,
|
|
754
|
-
STAKING_ABI,
|
|
755
|
-
provider
|
|
756
|
-
);
|
|
757
|
-
stakingAllowanceRao = await staking.getFunction("allowance")(
|
|
758
|
-
sender,
|
|
759
|
-
subtensor.contracts.gateway,
|
|
760
|
-
input.netuid
|
|
761
|
-
);
|
|
1038
|
+
stakingAllowanceRao = await provider.readContract({
|
|
1039
|
+
address: subtensor.contracts.stakingPrecompile,
|
|
1040
|
+
abi: stakingAbi,
|
|
1041
|
+
functionName: "allowance",
|
|
1042
|
+
args: [sender, subtensor.contracts.gateway, netuid]
|
|
1043
|
+
});
|
|
762
1044
|
}
|
|
763
|
-
const
|
|
764
|
-
const
|
|
765
|
-
const value = taoAmount + feeWithBuffer(exactNetworkFeeWei);
|
|
1045
|
+
const alphaTopUpRao = partnerFeeCut(stakedAlphaRao, partnerFee.bps);
|
|
1046
|
+
const taoTopUp = partnerFeeTaoTopUp(taoAmount, partnerFee.bps);
|
|
1047
|
+
const value = taoAmount + taoTopUp + feeWithBuffer(exactNetworkFeeWei);
|
|
766
1048
|
assertNonNegativeAmount(value, "Transaction value");
|
|
767
1049
|
let estimatedBridgeGas;
|
|
768
|
-
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao) {
|
|
1050
|
+
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao + alphaTopUpRao) {
|
|
769
1051
|
estimatedBridgeGas = await provider.estimateGas({
|
|
770
|
-
|
|
1052
|
+
account: sender,
|
|
771
1053
|
to: subtensor.contracts.gateway,
|
|
772
|
-
data:
|
|
1054
|
+
data: encodeHubBridge(
|
|
773
1055
|
evm.ccipSelector,
|
|
774
|
-
|
|
1056
|
+
asset.subtensorToken,
|
|
775
1057
|
recipient,
|
|
776
1058
|
taoAmount,
|
|
777
1059
|
stakedAlphaRao,
|
|
778
|
-
input.amountWei
|
|
779
|
-
|
|
1060
|
+
input.amountWei,
|
|
1061
|
+
partnerFee
|
|
1062
|
+
),
|
|
780
1063
|
value
|
|
781
1064
|
});
|
|
782
1065
|
}
|
|
@@ -789,6 +1072,7 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
789
1072
|
return Object.freeze({
|
|
790
1073
|
exactNetworkFeeWei,
|
|
791
1074
|
transactionValueWei: value,
|
|
1075
|
+
partnerFeeWei: input.source === "liquid" ? taoTopUp : alphaTopUpRao * EVM_WEI_PER_RAO,
|
|
792
1076
|
plan
|
|
793
1077
|
});
|
|
794
1078
|
}
|
|
@@ -797,7 +1081,7 @@ function prepareSubtensorToBase(provider, input) {
|
|
|
797
1081
|
}
|
|
798
1082
|
|
|
799
1083
|
// src/core/transport.ts
|
|
800
|
-
import {
|
|
1084
|
+
import { createPublicClient, custom } from "viem";
|
|
801
1085
|
function isRecord(value) {
|
|
802
1086
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
803
1087
|
}
|
|
@@ -916,10 +1200,42 @@ function providerFromTransport(transport) {
|
|
|
916
1200
|
"An EIP-1193-compatible RPC transport is required."
|
|
917
1201
|
);
|
|
918
1202
|
}
|
|
919
|
-
return
|
|
1203
|
+
return createPublicClient({
|
|
1204
|
+
transport: custom(transport, { retryCount: 0 }),
|
|
1205
|
+
cacheTime: 0,
|
|
1206
|
+
batch: { multicall: false }
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
function trackingClient(provider) {
|
|
1210
|
+
if (provider && "getChainId" in provider && typeof provider.getChainId === "function")
|
|
1211
|
+
return provider;
|
|
1212
|
+
if (provider && "send" in provider && typeof provider.send === "function") {
|
|
1213
|
+
return providerFromTransport({
|
|
1214
|
+
request: ({ method, params }) => {
|
|
1215
|
+
if (params !== void 0 && !Array.isArray(params))
|
|
1216
|
+
throw new ForeverMoneyError(
|
|
1217
|
+
"RPC_ERROR",
|
|
1218
|
+
"Tracking RPC params must be an array."
|
|
1219
|
+
);
|
|
1220
|
+
return provider.send(
|
|
1221
|
+
method,
|
|
1222
|
+
params === void 0 ? [] : [...params]
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
throw new ForeverMoneyError(
|
|
1228
|
+
"MISSING_TRANSPORT",
|
|
1229
|
+
"A viem public client or JSON-RPC provider is required."
|
|
1230
|
+
);
|
|
920
1231
|
}
|
|
921
1232
|
|
|
922
1233
|
// src/core/provider-errors.ts
|
|
1234
|
+
import {
|
|
1235
|
+
BaseError,
|
|
1236
|
+
ContractFunctionRevertedError,
|
|
1237
|
+
ExecutionRevertedError
|
|
1238
|
+
} from "viem";
|
|
923
1239
|
function stringProperty(value, property) {
|
|
924
1240
|
if (typeof value !== "object" || value === null || !(property in value)) {
|
|
925
1241
|
return void 0;
|
|
@@ -932,9 +1248,13 @@ async function providerOperation(operation, action) {
|
|
|
932
1248
|
return await action();
|
|
933
1249
|
} catch (error) {
|
|
934
1250
|
if (error instanceof ForeverMoneyError) throw error;
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
1251
|
+
const revert = error instanceof BaseError ? error.walk(
|
|
1252
|
+
(cause) => cause instanceof ContractFunctionRevertedError || cause instanceof ExecutionRevertedError
|
|
1253
|
+
) : void 0;
|
|
1254
|
+
const viemRevert = revert instanceof ContractFunctionRevertedError || revert instanceof ExecutionRevertedError;
|
|
1255
|
+
const causeCode = viemRevert ? revert.name : stringProperty(error, "code");
|
|
1256
|
+
const reason = viemRevert ? revert instanceof ContractFunctionRevertedError ? revert.data?.errorName ?? revert.reason : void 0 : stringProperty(error, "reason");
|
|
1257
|
+
const reverted = viemRevert || causeCode === "CALL_EXCEPTION";
|
|
938
1258
|
throw new ForeverMoneyError(
|
|
939
1259
|
reverted ? "SIMULATION_REVERTED" : "RPC_ERROR",
|
|
940
1260
|
reverted ? `${operation} reverted.` : `${operation} failed.`,
|
|
@@ -947,10 +1267,14 @@ async function providerOperation(operation, action) {
|
|
|
947
1267
|
}
|
|
948
1268
|
|
|
949
1269
|
// src/vaults/plans.ts
|
|
950
|
-
import {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1270
|
+
import {
|
|
1271
|
+
encodeFunctionData as encodeFunctionData2,
|
|
1272
|
+
parseAbi as parseAbi2,
|
|
1273
|
+
zeroAddress as zeroAddress2
|
|
1274
|
+
} from "viem";
|
|
1275
|
+
var erc20Abi2 = parseAbi2(ERC20_ABI);
|
|
1276
|
+
var factoryAbi = parseAbi2(VAULT_FACTORY_ABI);
|
|
1277
|
+
var managerAbi = parseAbi2(VAULT_MANAGER_ABI);
|
|
954
1278
|
function step(kind, label, from, to, data, value = 0n) {
|
|
955
1279
|
return {
|
|
956
1280
|
kind,
|
|
@@ -1067,10 +1391,11 @@ function buildCreateVaultPlan(input) {
|
|
|
1067
1391
|
`Approve ${stash.token} for the vault factory`,
|
|
1068
1392
|
owner,
|
|
1069
1393
|
stash.token,
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1394
|
+
encodeFunctionData2({
|
|
1395
|
+
abi: erc20Abi2,
|
|
1396
|
+
functionName: "approve",
|
|
1397
|
+
args: [base.contracts.vaultFactory, stash.amount]
|
|
1398
|
+
})
|
|
1074
1399
|
)
|
|
1075
1400
|
);
|
|
1076
1401
|
}
|
|
@@ -1081,15 +1406,19 @@ function buildCreateVaultPlan(input) {
|
|
|
1081
1406
|
"Create ForeverMoney vault",
|
|
1082
1407
|
owner,
|
|
1083
1408
|
base.contracts.vaultFactory,
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1409
|
+
encodeFunctionData2({
|
|
1410
|
+
abi: factoryAbi,
|
|
1411
|
+
functionName: "create",
|
|
1412
|
+
args: [
|
|
1413
|
+
owner,
|
|
1414
|
+
associatedMiner,
|
|
1415
|
+
akAddress,
|
|
1416
|
+
poolManager,
|
|
1417
|
+
poolAddress,
|
|
1418
|
+
positionManagerImplementation,
|
|
1419
|
+
stashTokens
|
|
1420
|
+
]
|
|
1421
|
+
}),
|
|
1093
1422
|
value
|
|
1094
1423
|
)
|
|
1095
1424
|
);
|
|
@@ -1108,14 +1437,15 @@ async function prepareCreateVault(provider, input) {
|
|
|
1108
1437
|
const allowances = await Promise.all(
|
|
1109
1438
|
erc20Stash.map(async ({ token }) => ({
|
|
1110
1439
|
token,
|
|
1111
|
-
allowance: await
|
|
1112
|
-
token,
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1440
|
+
allowance: await provider.readContract({
|
|
1441
|
+
address: token,
|
|
1442
|
+
abi: erc20Abi2,
|
|
1443
|
+
functionName: "allowance",
|
|
1444
|
+
args: [
|
|
1445
|
+
owner,
|
|
1446
|
+
foreverMoneyDeployment.base.contracts.vaultFactory
|
|
1447
|
+
]
|
|
1448
|
+
})
|
|
1119
1449
|
}))
|
|
1120
1450
|
);
|
|
1121
1451
|
return buildCreateVaultPlan({ ...input, allowances });
|
|
@@ -1142,11 +1472,11 @@ function buildDepositVaultPlan(input) {
|
|
|
1142
1472
|
"Deposit native ETH into the vault as WETH",
|
|
1143
1473
|
owner,
|
|
1144
1474
|
manager,
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
deposit.amount
|
|
1149
|
-
|
|
1475
|
+
encodeFunctionData2({
|
|
1476
|
+
abi: managerAbi,
|
|
1477
|
+
functionName: "topUpAk",
|
|
1478
|
+
args: [akAddress, zeroAddress2, deposit.amount]
|
|
1479
|
+
}),
|
|
1150
1480
|
deposit.amount
|
|
1151
1481
|
)
|
|
1152
1482
|
);
|
|
@@ -1166,10 +1496,11 @@ function buildDepositVaultPlan(input) {
|
|
|
1166
1496
|
`Approve ${deposit.token} for the vault manager`,
|
|
1167
1497
|
owner,
|
|
1168
1498
|
deposit.token,
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1499
|
+
encodeFunctionData2({
|
|
1500
|
+
abi: erc20Abi2,
|
|
1501
|
+
functionName: "approve",
|
|
1502
|
+
args: [manager, deposit.amount]
|
|
1503
|
+
})
|
|
1173
1504
|
)
|
|
1174
1505
|
);
|
|
1175
1506
|
}
|
|
@@ -1179,11 +1510,11 @@ function buildDepositVaultPlan(input) {
|
|
|
1179
1510
|
`Deposit ${deposit.token} into the vault`,
|
|
1180
1511
|
owner,
|
|
1181
1512
|
manager,
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
deposit.amount
|
|
1186
|
-
|
|
1513
|
+
encodeFunctionData2({
|
|
1514
|
+
abi: managerAbi,
|
|
1515
|
+
functionName: "topUpAk",
|
|
1516
|
+
args: [akAddress, deposit.token, deposit.amount]
|
|
1517
|
+
})
|
|
1187
1518
|
)
|
|
1188
1519
|
);
|
|
1189
1520
|
}
|
|
@@ -1203,11 +1534,12 @@ async function prepareDepositVault(provider, input) {
|
|
|
1203
1534
|
const allowances = await Promise.all(
|
|
1204
1535
|
erc20Deposits.map(async ({ token }) => ({
|
|
1205
1536
|
token,
|
|
1206
|
-
allowance: await
|
|
1207
|
-
token,
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1537
|
+
allowance: await provider.readContract({
|
|
1538
|
+
address: token,
|
|
1539
|
+
abi: erc20Abi2,
|
|
1540
|
+
functionName: "allowance",
|
|
1541
|
+
args: [owner, manager]
|
|
1542
|
+
})
|
|
1211
1543
|
}))
|
|
1212
1544
|
);
|
|
1213
1545
|
return buildDepositVaultPlan({ ...input, allowances });
|
|
@@ -1238,16 +1570,17 @@ function buildWithdrawVaultPlan(input) {
|
|
|
1238
1570
|
"Withdraw assets from the vault",
|
|
1239
1571
|
owner,
|
|
1240
1572
|
manager,
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1573
|
+
encodeFunctionData2({
|
|
1574
|
+
abi: managerAbi,
|
|
1575
|
+
functionName: "withdrawFromAkAndPositions",
|
|
1576
|
+
args: [
|
|
1244
1577
|
akAddress,
|
|
1245
1578
|
input.amount0,
|
|
1246
1579
|
input.amount1,
|
|
1247
1580
|
input.decreaseTokenIds,
|
|
1248
1581
|
input.unwrapWeth
|
|
1249
1582
|
]
|
|
1250
|
-
)
|
|
1583
|
+
})
|
|
1251
1584
|
)
|
|
1252
1585
|
]
|
|
1253
1586
|
});
|
|
@@ -1265,9 +1598,11 @@ function buildClaimVaultFeesPlan(input) {
|
|
|
1265
1598
|
"Claim vault fees",
|
|
1266
1599
|
owner,
|
|
1267
1600
|
manager,
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1601
|
+
encodeFunctionData2({
|
|
1602
|
+
abi: managerAbi,
|
|
1603
|
+
functionName: "claimFees",
|
|
1604
|
+
args: [akAddress]
|
|
1605
|
+
})
|
|
1271
1606
|
)
|
|
1272
1607
|
]
|
|
1273
1608
|
});
|
|
@@ -1287,20 +1622,25 @@ function buildSetVaultStakingPlan(input) {
|
|
|
1287
1622
|
`${input.staking ? "Stake" : "Unstake"} the vault position`,
|
|
1288
1623
|
owner,
|
|
1289
1624
|
manager,
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1625
|
+
encodeFunctionData2({
|
|
1626
|
+
abi: managerAbi,
|
|
1627
|
+
functionName: action,
|
|
1628
|
+
args: [akAddress, "0x"]
|
|
1629
|
+
})
|
|
1294
1630
|
)
|
|
1295
1631
|
]
|
|
1296
1632
|
});
|
|
1297
1633
|
}
|
|
1298
1634
|
|
|
1299
1635
|
// src/bridge/tracking.ts
|
|
1300
|
-
import {
|
|
1636
|
+
import {
|
|
1637
|
+
decodeEventLog as decodeEventLog2,
|
|
1638
|
+
parseAbi as parseAbi4,
|
|
1639
|
+
TransactionReceiptNotFoundError
|
|
1640
|
+
} from "viem";
|
|
1301
1641
|
|
|
1302
1642
|
// src/bridge/receipts.ts
|
|
1303
|
-
import {
|
|
1643
|
+
import { decodeEventLog, isHex as isHex2, parseAbi as parseAbi3 } from "viem";
|
|
1304
1644
|
|
|
1305
1645
|
// src/core/receipts.ts
|
|
1306
1646
|
function isLogFrom(log, address) {
|
|
@@ -1308,8 +1648,8 @@ function isLogFrom(log, address) {
|
|
|
1308
1648
|
}
|
|
1309
1649
|
|
|
1310
1650
|
// src/bridge/receipts.ts
|
|
1311
|
-
var
|
|
1312
|
-
var
|
|
1651
|
+
var alphaGatewayAbi = parseAbi3(ALPHA_GATEWAY_ABI);
|
|
1652
|
+
var spokeGatewayAbi = parseAbi3(SPOKE_GATEWAY_ABI);
|
|
1313
1653
|
function assertBridgeDirection(value) {
|
|
1314
1654
|
if (value !== "base-to-subtensor" && value !== "robinhood-to-subtensor" && value !== "subtensor-to-base" && value !== "subtensor-to-robinhood") {
|
|
1315
1655
|
throw new ForeverMoneyError(
|
|
@@ -1332,20 +1672,28 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1332
1672
|
evmChainFromBridgeDirection(direction)
|
|
1333
1673
|
);
|
|
1334
1674
|
const evmToSubtensor = isEvmToSubtensorDirection(direction);
|
|
1335
|
-
const [
|
|
1336
|
-
|
|
1337
|
-
|
|
1675
|
+
const [addresses, contractAbi, eventName] = evmToSubtensor ? [
|
|
1676
|
+
[evm.contracts.legacyGateway, evm.contracts.gateway],
|
|
1677
|
+
spokeGatewayAbi,
|
|
1678
|
+
"BridgedToFinney"
|
|
1679
|
+
] : [
|
|
1680
|
+
[
|
|
1681
|
+
foreverMoneyDeployment.subtensor.contracts.legacyGateway,
|
|
1682
|
+
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1683
|
+
],
|
|
1684
|
+
alphaGatewayAbi,
|
|
1338
1685
|
"BridgedOut"
|
|
1339
1686
|
];
|
|
1340
1687
|
for (const log of receipt.logs) {
|
|
1341
|
-
if (!isLogFrom(log, address)) continue;
|
|
1688
|
+
if (!addresses.some((address) => isLogFrom(log, address))) continue;
|
|
1342
1689
|
try {
|
|
1343
|
-
const parsed =
|
|
1690
|
+
const parsed = decodeEventLog({
|
|
1691
|
+
abi: contractAbi,
|
|
1344
1692
|
data: log.data,
|
|
1345
1693
|
topics: [...log.topics]
|
|
1346
1694
|
});
|
|
1347
|
-
const messageId = parsed
|
|
1348
|
-
if (parsed
|
|
1695
|
+
const messageId = "messageId" in parsed.args ? parsed.args.messageId : void 0;
|
|
1696
|
+
if (parsed.eventName === eventName && typeof messageId === "string" && isHex2(messageId, { strict: true }) && messageId.length === 66 && (evmToSubtensor || "destChainSelector" in parsed.args && parsed.args.destChainSelector === evm.ccipSelector)) {
|
|
1349
1697
|
return messageId.toLowerCase();
|
|
1350
1698
|
}
|
|
1351
1699
|
} catch {
|
|
@@ -1355,8 +1703,8 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1355
1703
|
}
|
|
1356
1704
|
|
|
1357
1705
|
// src/bridge/tracking.ts
|
|
1358
|
-
var
|
|
1359
|
-
var
|
|
1706
|
+
var ccipExecutionAbi = parseAbi4(CCIP_EXECUTION_ABI);
|
|
1707
|
+
var alphaGatewayAbi2 = parseAbi4(ALPHA_GATEWAY_ABI);
|
|
1360
1708
|
function destinationChainId(direction) {
|
|
1361
1709
|
assertBridgeDirection(direction);
|
|
1362
1710
|
const evm = getForeverMoneyEvmDeployment(
|
|
@@ -1372,14 +1720,14 @@ function sourceChainId(direction) {
|
|
|
1372
1720
|
return isEvmToSubtensorDirection(direction) ? evm.chainId : foreverMoneyDeployment.subtensor.chainId;
|
|
1373
1721
|
}
|
|
1374
1722
|
async function assertProviderChain(provider, expectedChainId, label) {
|
|
1375
|
-
const
|
|
1376
|
-
if (
|
|
1723
|
+
const chainId = await provider.getChainId();
|
|
1724
|
+
if (chainId !== expectedChainId) {
|
|
1377
1725
|
throw new ForeverMoneyError(
|
|
1378
1726
|
"CHAIN_MISMATCH",
|
|
1379
|
-
`${label} transport reported chain ID ${
|
|
1727
|
+
`${label} transport reported chain ID ${chainId}; expected ${expectedChainId}.`,
|
|
1380
1728
|
{
|
|
1381
1729
|
expectedChainId,
|
|
1382
|
-
actualChainId:
|
|
1730
|
+
actualChainId: chainId.toString()
|
|
1383
1731
|
}
|
|
1384
1732
|
);
|
|
1385
1733
|
}
|
|
@@ -1390,13 +1738,14 @@ async function assertDestinationProvider(provider, direction) {
|
|
|
1390
1738
|
return expectedChainId;
|
|
1391
1739
|
}
|
|
1392
1740
|
async function getBridgeSourceStatus(provider, input) {
|
|
1741
|
+
const client = trackingClient(provider);
|
|
1393
1742
|
const expectedChainId = sourceChainId(input.direction);
|
|
1394
1743
|
const transactionHash = normalizeBytes32(
|
|
1395
1744
|
input.transactionHash,
|
|
1396
1745
|
"Transaction hash"
|
|
1397
1746
|
);
|
|
1398
|
-
await assertProviderChain(
|
|
1399
|
-
const receipt = await
|
|
1747
|
+
await assertProviderChain(client, expectedChainId, "Bridge source");
|
|
1748
|
+
const receipt = await receiptOrNull(client, transactionHash);
|
|
1400
1749
|
if (receipt === null) {
|
|
1401
1750
|
return Object.freeze({
|
|
1402
1751
|
direction: input.direction,
|
|
@@ -1406,7 +1755,7 @@ async function getBridgeSourceStatus(provider, input) {
|
|
|
1406
1755
|
messageId: null
|
|
1407
1756
|
});
|
|
1408
1757
|
}
|
|
1409
|
-
if (receipt.status ===
|
|
1758
|
+
if (receipt.status === "reverted") {
|
|
1410
1759
|
return Object.freeze({
|
|
1411
1760
|
direction: input.direction,
|
|
1412
1761
|
sourceChainId: expectedChainId,
|
|
@@ -1415,7 +1764,7 @@ async function getBridgeSourceStatus(provider, input) {
|
|
|
1415
1764
|
messageId: null
|
|
1416
1765
|
});
|
|
1417
1766
|
}
|
|
1418
|
-
if (receipt.status !==
|
|
1767
|
+
if (receipt.status !== "success") {
|
|
1419
1768
|
throw new ForeverMoneyError(
|
|
1420
1769
|
"INVALID_PROVIDER_RESPONSE",
|
|
1421
1770
|
"The bridge source receipt has an invalid status."
|
|
@@ -1437,8 +1786,10 @@ async function getBridgeSourceStatus(provider, input) {
|
|
|
1437
1786
|
});
|
|
1438
1787
|
}
|
|
1439
1788
|
async function getCcipDeliveryCheckpoint(provider, direction) {
|
|
1440
|
-
const
|
|
1441
|
-
const
|
|
1789
|
+
const client = trackingClient(provider);
|
|
1790
|
+
const expectedChainId = await assertDestinationProvider(client, direction);
|
|
1791
|
+
const blockNumber = await client.getBlockNumber({ cacheTime: 0 });
|
|
1792
|
+
const fromBlock = Number(blockNumber);
|
|
1442
1793
|
if (!Number.isSafeInteger(fromBlock) || fromBlock < 0) {
|
|
1443
1794
|
throw new ForeverMoneyError(
|
|
1444
1795
|
"INVALID_PROVIDER_RESPONSE",
|
|
@@ -1452,6 +1803,7 @@ async function getCcipDeliveryCheckpoint(provider, direction) {
|
|
|
1452
1803
|
});
|
|
1453
1804
|
}
|
|
1454
1805
|
async function getCcipDeliveryStatus(provider, input) {
|
|
1806
|
+
const client = trackingClient(provider);
|
|
1455
1807
|
const expectedChainId = destinationChainId(input.direction);
|
|
1456
1808
|
const messageId = normalizeBytes32(input.messageId, "CCIP message ID");
|
|
1457
1809
|
if (!Number.isSafeInteger(input.fromBlock) || input.fromBlock < 0) {
|
|
@@ -1460,42 +1812,50 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1460
1812
|
"CCIP fromBlock must be a non-negative safe integer."
|
|
1461
1813
|
);
|
|
1462
1814
|
}
|
|
1463
|
-
await assertProviderChain(
|
|
1464
|
-
const event = ccipExecutionInterface.getEvent("ExecutionStateChanged");
|
|
1465
|
-
if (event === null) {
|
|
1466
|
-
throw new Error("CCIP execution event is missing from the SDK ABI.");
|
|
1467
|
-
}
|
|
1815
|
+
await assertProviderChain(client, expectedChainId, "Bridge destination");
|
|
1468
1816
|
const evmChain = evmChainFromBridgeDirection(input.direction);
|
|
1469
1817
|
const evm = getForeverMoneyEvmDeployment(evmChain);
|
|
1470
1818
|
const evmToSubtensor = isEvmToSubtensorDirection(input.direction);
|
|
1471
1819
|
const sourceSelector = evmToSubtensor ? evm.ccipSelector : foreverMoneyDeployment.subtensor.ccipSelector;
|
|
1472
1820
|
const offRamp = evmToSubtensor ? evmChain === "base" ? foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromBase : foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromRobinhood : evm.contracts.ccipOffRampFromSubtensor;
|
|
1473
|
-
const logs = await
|
|
1821
|
+
const logs = await client.getLogs({
|
|
1474
1822
|
address: offRamp,
|
|
1475
|
-
fromBlock: input.fromBlock,
|
|
1823
|
+
fromBlock: BigInt(input.fromBlock),
|
|
1476
1824
|
toBlock: "latest",
|
|
1477
|
-
|
|
1825
|
+
event: ccipExecutionAbi[0],
|
|
1826
|
+
args: { sourceChainSelector: sourceSelector, messageId },
|
|
1827
|
+
strict: true
|
|
1478
1828
|
});
|
|
1479
1829
|
const latest = logs.at(-1);
|
|
1480
1830
|
if (latest === void 0) return "waiting";
|
|
1481
|
-
const
|
|
1482
|
-
const state = Number(parsed?.args.state);
|
|
1831
|
+
const state = latest.args.state;
|
|
1483
1832
|
if (state === 3) return "failure";
|
|
1484
1833
|
if (state !== 2) return "waiting";
|
|
1485
1834
|
if (!evmToSubtensor) return "success";
|
|
1486
|
-
const receipt = await
|
|
1835
|
+
const receipt = await receiptOrNull(client, latest.transactionHash);
|
|
1487
1836
|
if (receipt === null) {
|
|
1488
1837
|
throw new ForeverMoneyError(
|
|
1489
1838
|
"INVALID_PROVIDER_RESPONSE",
|
|
1490
1839
|
"The CCIP execution receipt is unavailable."
|
|
1491
1840
|
);
|
|
1492
1841
|
}
|
|
1842
|
+
const subtensorGateways = [
|
|
1843
|
+
foreverMoneyDeployment.subtensor.contracts.legacyGateway,
|
|
1844
|
+
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1845
|
+
];
|
|
1493
1846
|
for (const log of receipt.logs) {
|
|
1494
|
-
if (
|
|
1847
|
+
if (!subtensorGateways.some(
|
|
1848
|
+
(gateway) => log.address.toLowerCase() === gateway.toLowerCase()
|
|
1849
|
+
)) {
|
|
1495
1850
|
continue;
|
|
1496
1851
|
}
|
|
1497
1852
|
try {
|
|
1498
|
-
|
|
1853
|
+
const { eventName } = decodeEventLog2({
|
|
1854
|
+
abi: alphaGatewayAbi2,
|
|
1855
|
+
data: log.data,
|
|
1856
|
+
topics: log.topics
|
|
1857
|
+
});
|
|
1858
|
+
if (eventName === "Claimable" || eventName === "NotDelivered") {
|
|
1499
1859
|
return "recovery";
|
|
1500
1860
|
}
|
|
1501
1861
|
} catch {
|
|
@@ -1503,20 +1863,28 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1503
1863
|
}
|
|
1504
1864
|
return "success";
|
|
1505
1865
|
}
|
|
1866
|
+
async function receiptOrNull(provider, hash) {
|
|
1867
|
+
try {
|
|
1868
|
+
return await provider.getTransactionReceipt({ hash });
|
|
1869
|
+
} catch (error) {
|
|
1870
|
+
if (error instanceof TransactionReceiptNotFoundError) return null;
|
|
1871
|
+
throw error;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1506
1874
|
|
|
1507
1875
|
// src/client.ts
|
|
1508
1876
|
async function assertProviderChain2(provider, expectedChainId, name) {
|
|
1509
|
-
const
|
|
1877
|
+
const chainId = await providerOperation(
|
|
1510
1878
|
`Reading the ${name} chain ID`,
|
|
1511
|
-
() => provider.
|
|
1879
|
+
() => provider.getChainId()
|
|
1512
1880
|
);
|
|
1513
|
-
if (
|
|
1881
|
+
if (chainId !== expectedChainId) {
|
|
1514
1882
|
throw new ForeverMoneyError(
|
|
1515
1883
|
"CHAIN_MISMATCH",
|
|
1516
|
-
`${name} transport reported chain ID ${
|
|
1884
|
+
`${name} transport reported chain ID ${chainId}; expected ${expectedChainId}.`,
|
|
1517
1885
|
{
|
|
1518
1886
|
expectedChainId,
|
|
1519
|
-
actualChainId:
|
|
1887
|
+
actualChainId: chainId.toString()
|
|
1520
1888
|
}
|
|
1521
1889
|
);
|
|
1522
1890
|
}
|
|
@@ -1648,19 +2016,20 @@ function createForeverMoneyClient(options) {
|
|
|
1648
2016
|
}
|
|
1649
2017
|
|
|
1650
2018
|
// src/vaults/receipts.ts
|
|
1651
|
-
import {
|
|
1652
|
-
var
|
|
2019
|
+
import { decodeEventLog as decodeEventLog3, parseAbi as parseAbi5 } from "viem";
|
|
2020
|
+
var vaultFactoryAbi = parseAbi5(VAULT_FACTORY_ABI);
|
|
1653
2021
|
function vaultManagerFromCreationReceipt(receipt) {
|
|
1654
2022
|
for (const log of receipt.logs) {
|
|
1655
2023
|
if (!isLogFrom(log, foreverMoneyDeployment.base.contracts.vaultFactory)) {
|
|
1656
2024
|
continue;
|
|
1657
2025
|
}
|
|
1658
2026
|
try {
|
|
1659
|
-
const parsed =
|
|
2027
|
+
const parsed = decodeEventLog3({
|
|
2028
|
+
abi: vaultFactoryAbi,
|
|
1660
2029
|
data: log.data,
|
|
1661
2030
|
topics: [...log.topics]
|
|
1662
2031
|
});
|
|
1663
|
-
if (parsed
|
|
2032
|
+
if (parsed.eventName === "SnLiquidityManagerCreated") {
|
|
1664
2033
|
return normalizeEvmAddress(String(parsed.args.manager));
|
|
1665
2034
|
}
|
|
1666
2035
|
} catch {
|
|
@@ -1670,7 +2039,10 @@ function vaultManagerFromCreationReceipt(receipt) {
|
|
|
1670
2039
|
}
|
|
1671
2040
|
|
|
1672
2041
|
// src/core/transactions.ts
|
|
1673
|
-
import {
|
|
2042
|
+
import {
|
|
2043
|
+
defineChain,
|
|
2044
|
+
maxUint256 as maxUint2563
|
|
2045
|
+
} from "viem";
|
|
1674
2046
|
function decimalQuantity(value, label) {
|
|
1675
2047
|
if (!/^(0|[1-9][0-9]*)$/.test(value)) {
|
|
1676
2048
|
throw new ForeverMoneyError(
|
|
@@ -1679,7 +2051,7 @@ function decimalQuantity(value, label) {
|
|
|
1679
2051
|
);
|
|
1680
2052
|
}
|
|
1681
2053
|
const quantity = BigInt(value);
|
|
1682
|
-
if (quantity >
|
|
2054
|
+
if (quantity > maxUint2563) {
|
|
1683
2055
|
throw new ForeverMoneyError(
|
|
1684
2056
|
"INVALID_TRANSACTION_PLAN",
|
|
1685
2057
|
`${label} exceeds uint256.`
|
|
@@ -1715,6 +2087,52 @@ function toEthersTransaction(transaction) {
|
|
|
1715
2087
|
}
|
|
1716
2088
|
});
|
|
1717
2089
|
}
|
|
2090
|
+
var transactionChains = new Map(
|
|
2091
|
+
[
|
|
2092
|
+
[BASE_CHAIN_ID, "Base", "Ether", "ETH"],
|
|
2093
|
+
[ROBINHOOD_CHAIN_ID, "Robinhood", "Ether", "ETH"],
|
|
2094
|
+
[SUBTENSOR_CHAIN_ID, "Subtensor", "TAO", "TAO"]
|
|
2095
|
+
].map(([id, name, currency, symbol]) => [
|
|
2096
|
+
id,
|
|
2097
|
+
Object.freeze(
|
|
2098
|
+
defineChain({
|
|
2099
|
+
id,
|
|
2100
|
+
name,
|
|
2101
|
+
nativeCurrency: Object.freeze({
|
|
2102
|
+
name: currency,
|
|
2103
|
+
symbol,
|
|
2104
|
+
decimals: 18
|
|
2105
|
+
}),
|
|
2106
|
+
// Chain identity only. The caller retains ownership of the wallet transport.
|
|
2107
|
+
rpcUrls: Object.freeze({
|
|
2108
|
+
default: Object.freeze({ http: Object.freeze([]) })
|
|
2109
|
+
})
|
|
2110
|
+
})
|
|
2111
|
+
)
|
|
2112
|
+
])
|
|
2113
|
+
);
|
|
2114
|
+
function toViemTransaction(transaction) {
|
|
2115
|
+
const chain = transactionChains.get(transaction.chainId);
|
|
2116
|
+
if (!chain)
|
|
2117
|
+
throw new ForeverMoneyError(
|
|
2118
|
+
"INVALID_TRANSACTION_PLAN",
|
|
2119
|
+
"Unsupported transaction chain."
|
|
2120
|
+
);
|
|
2121
|
+
return Object.freeze({
|
|
2122
|
+
chain,
|
|
2123
|
+
account: transaction.from,
|
|
2124
|
+
chainId: transaction.chainId,
|
|
2125
|
+
to: transaction.to,
|
|
2126
|
+
data: transaction.data,
|
|
2127
|
+
value: decimalQuantity(transaction.value, "Transaction value"),
|
|
2128
|
+
...transaction.gasLimit === void 0 ? {} : {
|
|
2129
|
+
gas: decimalQuantity(
|
|
2130
|
+
transaction.gasLimit,
|
|
2131
|
+
"Transaction gas limit"
|
|
2132
|
+
)
|
|
2133
|
+
}
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
1718
2136
|
export {
|
|
1719
2137
|
BASE_CCIP_SELECTOR,
|
|
1720
2138
|
BASE_CHAIN_ID,
|
|
@@ -1723,12 +2141,15 @@ export {
|
|
|
1723
2141
|
FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
1724
2142
|
ForeverMoneyError,
|
|
1725
2143
|
GAS_LIMIT_BUFFER_BPS,
|
|
2144
|
+
MAX_PARTNER_FEE_BPS,
|
|
1726
2145
|
MIN_LIQUID_BASE_TO_SUBTENSOR_WEI,
|
|
1727
2146
|
MIN_LIQUID_EVM_TO_SUBTENSOR_WEI,
|
|
2147
|
+
MIN_LIQUID_SUBTENSOR_TO_EVM_WEI,
|
|
1728
2148
|
NETWORK_FEE_BUFFER_BPS,
|
|
1729
2149
|
RAO_PER_TAO,
|
|
1730
2150
|
ROBINHOOD_CCIP_SELECTOR,
|
|
1731
2151
|
ROBINHOOD_CHAIN_ID,
|
|
2152
|
+
SN80_NETUID,
|
|
1732
2153
|
SUBTENSOR_CCIP_SELECTOR,
|
|
1733
2154
|
SUBTENSOR_CHAIN_ID,
|
|
1734
2155
|
assertWholeRao,
|
|
@@ -1761,10 +2182,13 @@ export {
|
|
|
1761
2182
|
normalizeEvmAddress,
|
|
1762
2183
|
normalizeSS58,
|
|
1763
2184
|
parseTaoAmount,
|
|
2185
|
+
partnerFeeCut,
|
|
2186
|
+
partnerFeeTaoTopUp,
|
|
1764
2187
|
sourceChainId,
|
|
1765
2188
|
ss58ToPublicKey,
|
|
1766
2189
|
toEip1193Transaction,
|
|
1767
2190
|
toEthersTransaction,
|
|
2191
|
+
toViemTransaction,
|
|
1768
2192
|
vaultManagerFromCreationReceipt
|
|
1769
2193
|
};
|
|
1770
2194
|
//# sourceMappingURL=index.js.map
|