@forevermoney/sdk 0.2.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 +43 -0
- package/README.md +86 -0
- package/dist/index.cjs +361 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -6
- package/dist/index.d.ts +39 -6
- package/dist/index.js +361 -87
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
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,6 +68,8 @@ __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,
|
|
@@ -156,7 +161,8 @@ var ROBINHOOD_CCIP_SELECTOR = 6180753054346818345n;
|
|
|
156
161
|
var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
157
162
|
var RAO_PER_TAO = 1000000000n;
|
|
158
163
|
var EVM_WEI_PER_RAO = 1000000000n;
|
|
159
|
-
var
|
|
164
|
+
var SN80_NETUID = 80n;
|
|
165
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.2.0";
|
|
160
166
|
var foreverMoneyDeployment = Object.freeze({
|
|
161
167
|
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
162
168
|
base: Object.freeze({
|
|
@@ -165,10 +171,16 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
165
171
|
chainId: BASE_CHAIN_ID,
|
|
166
172
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
167
173
|
contracts: Object.freeze({
|
|
168
|
-
gateway: (0, import_viem2.getAddress)("
|
|
174
|
+
gateway: (0, import_viem2.getAddress)("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
175
|
+
legacyGateway: (0, import_viem2.getAddress)(
|
|
176
|
+
"0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C"
|
|
177
|
+
),
|
|
169
178
|
wrappedTao: (0, import_viem2.getAddress)(
|
|
170
179
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
171
180
|
),
|
|
181
|
+
wrappedSn80: (0, import_viem2.getAddress)(
|
|
182
|
+
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
183
|
+
),
|
|
172
184
|
ccipRouter: (0, import_viem2.getAddress)(
|
|
173
185
|
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
174
186
|
),
|
|
@@ -194,7 +206,10 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
194
206
|
chainId: ROBINHOOD_CHAIN_ID,
|
|
195
207
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
196
208
|
contracts: Object.freeze({
|
|
197
|
-
gateway: (0, import_viem2.getAddress)("
|
|
209
|
+
gateway: (0, import_viem2.getAddress)("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
210
|
+
legacyGateway: (0, import_viem2.getAddress)(
|
|
211
|
+
"0x53Dcc4FE04193e489BE537F722F65317DB1E65d8"
|
|
212
|
+
),
|
|
198
213
|
wrappedTao: (0, import_viem2.getAddress)(
|
|
199
214
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
200
215
|
),
|
|
@@ -210,13 +225,19 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
210
225
|
chainId: SUBTENSOR_CHAIN_ID,
|
|
211
226
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
212
227
|
contracts: Object.freeze({
|
|
213
|
-
gateway: (0, import_viem2.getAddress)("
|
|
228
|
+
gateway: (0, import_viem2.getAddress)("0xcd0C6d98D0A126B1c113d15b4c28F38321437787"),
|
|
229
|
+
legacyGateway: (0, import_viem2.getAddress)(
|
|
230
|
+
"0x998f20Fea90bF7792774dECc7f994716442B1705"
|
|
231
|
+
),
|
|
214
232
|
alphaVault: (0, import_viem2.getAddress)(
|
|
215
233
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
216
234
|
),
|
|
217
235
|
wrappedTao: (0, import_viem2.getAddress)(
|
|
218
236
|
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
219
237
|
),
|
|
238
|
+
wrappedSn80: (0, import_viem2.getAddress)(
|
|
239
|
+
"0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222"
|
|
240
|
+
),
|
|
220
241
|
ccipRouter: (0, import_viem2.getAddress)(
|
|
221
242
|
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
222
243
|
),
|
|
@@ -397,23 +418,34 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
397
418
|
"function ROUTER() view returns (address)",
|
|
398
419
|
"function BITTENSOR_SELECTOR() view returns (uint64)",
|
|
399
420
|
"function SUBTENSOR_GATEWAY() view returns (address)",
|
|
421
|
+
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
422
|
+
"function bridgeFeeBps() view returns (uint16)",
|
|
400
423
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
401
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)",
|
|
402
427
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
403
428
|
]);
|
|
404
429
|
var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
405
430
|
"function ROUTER() view returns (address)",
|
|
406
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)",
|
|
407
435
|
"function quoteBridgeOut(uint64 destSelector,address token,address recipient,uint256 mintedAmount) view returns (uint256 fee)",
|
|
408
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)",
|
|
409
440
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
410
441
|
"function claimNative(address to)",
|
|
411
442
|
"function claimStaked(address token,bytes32 destColdkey,address to)",
|
|
412
443
|
"function claimToken(address token,address to)",
|
|
413
444
|
"function claimableNative(address account) view returns (uint256)",
|
|
414
|
-
"function claimableToken(address
|
|
445
|
+
"function claimableToken(address token,address account) view returns (uint256)",
|
|
415
446
|
"event BridgedOut(uint64 destChainSelector,address indexed token,address indexed sender,address indexed recipient,uint256 minted,bytes32 messageId)",
|
|
416
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)",
|
|
417
449
|
"event DeliveredLiquid(address indexed token,bytes32 indexed ss58,uint256 taoOut)",
|
|
418
450
|
"event DeliveredStaked(address indexed token,bytes32 indexed ss58,uint256 alphaRao)"
|
|
419
451
|
]);
|
|
@@ -571,6 +603,101 @@ var alphaAbi = (0, import_viem6.parseAbi)(ALPHA_GATEWAY_ABI);
|
|
|
571
603
|
var stakingAbi = (0, import_viem6.parseAbi)(STAKING_ABI);
|
|
572
604
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
573
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
|
+
}
|
|
574
701
|
function assertDelivery(value) {
|
|
575
702
|
if (value !== "liquid" && value !== "staked") {
|
|
576
703
|
throw new ForeverMoneyError(
|
|
@@ -600,6 +727,19 @@ function assertBaseToSubtensorAmount(amountWei, delivery) {
|
|
|
600
727
|
);
|
|
601
728
|
}
|
|
602
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
|
+
}
|
|
603
743
|
function transactionStep(kind, label, chainId, from, to, data, value, gasLimit) {
|
|
604
744
|
return {
|
|
605
745
|
kind,
|
|
@@ -614,9 +754,94 @@ function transactionStep(kind, label, chainId, from, to, data, value, gasLimit)
|
|
|
614
754
|
}
|
|
615
755
|
};
|
|
616
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
|
+
}
|
|
617
840
|
function buildEvmToSubtensorPlan(input) {
|
|
618
841
|
const sender = normalizeEvmAddress(input.sender);
|
|
842
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
619
843
|
assertDelivery(input.delivery);
|
|
844
|
+
assertAssetMode(input.asset, input.delivery);
|
|
620
845
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
621
846
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
622
847
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
@@ -628,19 +853,24 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
628
853
|
minTaoOut: input.amountWei
|
|
629
854
|
};
|
|
630
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);
|
|
631
861
|
const steps = [];
|
|
632
|
-
if (input.allowanceWei < input.amountWei) {
|
|
862
|
+
if (input.allowanceWei < input.amountWei + cut) {
|
|
633
863
|
steps.push(
|
|
634
864
|
transactionStep(
|
|
635
865
|
"approval",
|
|
636
|
-
|
|
866
|
+
`Approve ${asset.wrappedLabel} for the ForeverMoney gateway`,
|
|
637
867
|
evm.chainId,
|
|
638
868
|
sender,
|
|
639
|
-
|
|
869
|
+
asset.evmToken,
|
|
640
870
|
(0, import_viem6.encodeFunctionData)({
|
|
641
871
|
abi: erc20Abi,
|
|
642
872
|
functionName: "approve",
|
|
643
|
-
args: [evm.contracts.gateway, input.amountWei]
|
|
873
|
+
args: [evm.contracts.gateway, input.amountWei + cut]
|
|
644
874
|
}),
|
|
645
875
|
0n
|
|
646
876
|
)
|
|
@@ -650,22 +880,23 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
650
880
|
steps.push(
|
|
651
881
|
transactionStep(
|
|
652
882
|
"transaction",
|
|
653
|
-
`Bridge
|
|
883
|
+
`Bridge ${asset.wrappedLabel} from ${evm.name} to Subtensor (${input.delivery})`,
|
|
654
884
|
evm.chainId,
|
|
655
885
|
sender,
|
|
656
886
|
evm.contracts.gateway,
|
|
657
|
-
(
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
887
|
+
encodeSpokeBridge(
|
|
888
|
+
asset.evmToken,
|
|
889
|
+
input.amountWei,
|
|
890
|
+
exit,
|
|
891
|
+
partnerFee
|
|
892
|
+
),
|
|
662
893
|
value,
|
|
663
894
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
664
895
|
)
|
|
665
896
|
);
|
|
666
897
|
return createTransactionPlan({
|
|
667
898
|
action: evm.key === "base" ? "bridge.base-to-subtensor" : "bridge.robinhood-to-subtensor",
|
|
668
|
-
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)}`,
|
|
669
900
|
steps
|
|
670
901
|
});
|
|
671
902
|
}
|
|
@@ -675,21 +906,23 @@ function buildBaseToSubtensorPlan(input) {
|
|
|
675
906
|
function buildSubtensorToEvmPlan(input) {
|
|
676
907
|
const sender = normalizeEvmAddress(input.sender);
|
|
677
908
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
909
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
678
910
|
assertSource(input.source);
|
|
679
|
-
|
|
911
|
+
assertAssetMode(input.asset, input.source);
|
|
912
|
+
const netuid = sourceNetuid(input);
|
|
913
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
680
914
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
681
915
|
const { subtensor } = foreverMoneyDeployment;
|
|
682
916
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
917
|
+
const partnerFee = resolvePartnerFee(
|
|
918
|
+
input.partnerFee,
|
|
919
|
+
subtensor.contracts.gateway
|
|
920
|
+
);
|
|
683
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;
|
|
684
924
|
const steps = [];
|
|
685
925
|
if (input.source === "staked") {
|
|
686
|
-
if (input.netuid === void 0) {
|
|
687
|
-
throw new ForeverMoneyError(
|
|
688
|
-
"INVALID_TRANSACTION_PLAN",
|
|
689
|
-
"A netuid is required when bridging staked TAO."
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
assertNonNegativeAmount(input.netuid, "netuid");
|
|
693
926
|
if (input.stakingAllowanceRao === void 0) {
|
|
694
927
|
throw new ForeverMoneyError(
|
|
695
928
|
"INVALID_TRANSACTION_PLAN",
|
|
@@ -697,11 +930,11 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
697
930
|
);
|
|
698
931
|
}
|
|
699
932
|
assertNonNegativeAmount(input.stakingAllowanceRao, "Staking allowance");
|
|
700
|
-
if (input.stakingAllowanceRao < amountRao) {
|
|
933
|
+
if (input.stakingAllowanceRao < amountRao + alphaTopUpRao) {
|
|
701
934
|
steps.push(
|
|
702
935
|
transactionStep(
|
|
703
936
|
"approval",
|
|
704
|
-
|
|
937
|
+
`Approve staked ${asset.label} for the ForeverMoney gateway`,
|
|
705
938
|
subtensor.chainId,
|
|
706
939
|
sender,
|
|
707
940
|
subtensor.contracts.stakingPrecompile,
|
|
@@ -710,8 +943,8 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
710
943
|
functionName: "approve",
|
|
711
944
|
args: [
|
|
712
945
|
subtensor.contracts.gateway,
|
|
713
|
-
|
|
714
|
-
amountRao
|
|
946
|
+
netuid,
|
|
947
|
+
amountRao + alphaTopUpRao
|
|
715
948
|
]
|
|
716
949
|
}),
|
|
717
950
|
0n
|
|
@@ -726,34 +959,35 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
726
959
|
}
|
|
727
960
|
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
728
961
|
const stakedAlphaRao = input.source === "staked" ? amountRao : 0n;
|
|
729
|
-
const value = taoAmount + feeWithBuffer(input.exactNetworkFeeWei);
|
|
962
|
+
const value = taoAmount + taoTopUp + feeWithBuffer(input.exactNetworkFeeWei);
|
|
730
963
|
assertNonNegativeAmount(value, "Transaction value");
|
|
731
964
|
steps.push(
|
|
732
965
|
transactionStep(
|
|
733
966
|
"transaction",
|
|
734
|
-
`Bridge ${input.source}
|
|
967
|
+
`Bridge ${input.source} ${asset.label} from Subtensor to ${evm.name}`,
|
|
735
968
|
subtensor.chainId,
|
|
736
969
|
sender,
|
|
737
970
|
subtensor.contracts.gateway,
|
|
738
|
-
(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
input.amountWei
|
|
748
|
-
]
|
|
749
|
-
}),
|
|
971
|
+
encodeHubBridge(
|
|
972
|
+
evm.ccipSelector,
|
|
973
|
+
asset.subtensorToken,
|
|
974
|
+
recipient,
|
|
975
|
+
taoAmount,
|
|
976
|
+
stakedAlphaRao,
|
|
977
|
+
input.amountWei,
|
|
978
|
+
partnerFee
|
|
979
|
+
),
|
|
750
980
|
value,
|
|
751
981
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
752
982
|
)
|
|
753
983
|
);
|
|
754
984
|
return createTransactionPlan({
|
|
755
985
|
action: evm.key === "base" ? "bridge.subtensor-to-base" : "bridge.subtensor-to-robinhood",
|
|
756
|
-
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
|
+
)}`,
|
|
757
991
|
steps
|
|
758
992
|
});
|
|
759
993
|
}
|
|
@@ -762,7 +996,9 @@ function buildSubtensorToBasePlan(input) {
|
|
|
762
996
|
}
|
|
763
997
|
async function prepareEvmToSubtensor(provider, input) {
|
|
764
998
|
const sender = normalizeEvmAddress(input.sender);
|
|
999
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
765
1000
|
assertDelivery(input.delivery);
|
|
1001
|
+
assertAssetMode(input.asset, input.delivery);
|
|
766
1002
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
767
1003
|
const destination = normalizeSS58(input.destination);
|
|
768
1004
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
@@ -772,27 +1008,40 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
772
1008
|
wantLiquid: input.delivery === "liquid",
|
|
773
1009
|
minTaoOut: input.amountWei
|
|
774
1010
|
};
|
|
1011
|
+
const partnerFee = resolvePartnerFee(
|
|
1012
|
+
input.partnerFee,
|
|
1013
|
+
evm.contracts.gateway
|
|
1014
|
+
);
|
|
1015
|
+
const cut = partnerFeeCut(input.amountWei, partnerFee.bps);
|
|
775
1016
|
const [allowanceWei, exactNetworkFeeWei] = await Promise.all([
|
|
776
1017
|
provider.readContract({
|
|
777
|
-
address:
|
|
1018
|
+
address: asset.evmToken,
|
|
778
1019
|
abi: erc20Abi,
|
|
779
1020
|
functionName: "allowance",
|
|
780
1021
|
args: [sender, evm.contracts.gateway]
|
|
781
1022
|
}),
|
|
782
|
-
provider.readContract({
|
|
1023
|
+
partnerFee.bps === 0 ? provider.readContract({
|
|
783
1024
|
address: evm.contracts.gateway,
|
|
784
1025
|
abi: spokeAbi,
|
|
785
1026
|
functionName: "quoteBridgeToFinney",
|
|
786
|
-
args: [
|
|
787
|
-
})
|
|
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
|
+
)
|
|
788
1036
|
]);
|
|
789
1037
|
let estimatedBridgeGas;
|
|
790
|
-
if (allowanceWei >= input.amountWei) {
|
|
791
|
-
const data = (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1038
|
+
if (allowanceWei >= input.amountWei + cut) {
|
|
1039
|
+
const data = encodeSpokeBridge(
|
|
1040
|
+
asset.evmToken,
|
|
1041
|
+
input.amountWei,
|
|
1042
|
+
exit,
|
|
1043
|
+
partnerFee
|
|
1044
|
+
);
|
|
796
1045
|
estimatedBridgeGas = await provider.estimateGas({
|
|
797
1046
|
account: sender,
|
|
798
1047
|
to: evm.contracts.gateway,
|
|
@@ -809,6 +1058,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
809
1058
|
return Object.freeze({
|
|
810
1059
|
exactNetworkFeeWei,
|
|
811
1060
|
transactionValueWei: feeWithBuffer(exactNetworkFeeWei),
|
|
1061
|
+
partnerFeeWei: cut,
|
|
812
1062
|
plan
|
|
813
1063
|
});
|
|
814
1064
|
}
|
|
@@ -818,58 +1068,67 @@ function prepareBaseToSubtensor(provider, input) {
|
|
|
818
1068
|
async function prepareSubtensorToEvm(provider, input) {
|
|
819
1069
|
const sender = normalizeEvmAddress(input.sender);
|
|
820
1070
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
1071
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
821
1072
|
assertSource(input.source);
|
|
822
|
-
|
|
1073
|
+
assertAssetMode(input.asset, input.source);
|
|
1074
|
+
const netuid = sourceNetuid(input);
|
|
1075
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
823
1076
|
const { subtensor } = foreverMoneyDeployment;
|
|
824
1077
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
825
|
-
const
|
|
1078
|
+
const partnerFee = resolvePartnerFee(
|
|
1079
|
+
input.partnerFee,
|
|
1080
|
+
subtensor.contracts.gateway
|
|
1081
|
+
);
|
|
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({
|
|
826
1085
|
address: subtensor.contracts.gateway,
|
|
827
1086
|
abi: alphaAbi,
|
|
828
1087
|
functionName: "quoteBridgeOut",
|
|
829
1088
|
args: [
|
|
830
1089
|
evm.ccipSelector,
|
|
831
|
-
|
|
1090
|
+
asset.subtensorToken,
|
|
832
1091
|
recipient,
|
|
833
1092
|
input.amountWei
|
|
834
1093
|
]
|
|
835
|
-
})
|
|
1094
|
+
}) : (await quoteHubWithFee(
|
|
1095
|
+
provider,
|
|
1096
|
+
subtensor.contracts.gateway,
|
|
1097
|
+
evm.ccipSelector,
|
|
1098
|
+
asset.subtensorToken,
|
|
1099
|
+
recipient,
|
|
1100
|
+
input.amountWei,
|
|
1101
|
+
taoAmount,
|
|
1102
|
+
stakedAlphaRao,
|
|
1103
|
+
partnerFee
|
|
1104
|
+
)).networkFee;
|
|
836
1105
|
let stakingAllowanceRao;
|
|
837
1106
|
if (input.source === "staked") {
|
|
838
|
-
if (input.netuid === void 0) {
|
|
839
|
-
throw new ForeverMoneyError(
|
|
840
|
-
"INVALID_TRANSACTION_PLAN",
|
|
841
|
-
"A netuid is required when bridging staked TAO."
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
assertNonNegativeAmount(input.netuid, "netuid");
|
|
845
1107
|
stakingAllowanceRao = await provider.readContract({
|
|
846
1108
|
address: subtensor.contracts.stakingPrecompile,
|
|
847
1109
|
abi: stakingAbi,
|
|
848
1110
|
functionName: "allowance",
|
|
849
|
-
args: [sender, subtensor.contracts.gateway,
|
|
1111
|
+
args: [sender, subtensor.contracts.gateway, netuid]
|
|
850
1112
|
});
|
|
851
1113
|
}
|
|
852
|
-
const
|
|
853
|
-
const
|
|
854
|
-
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);
|
|
855
1117
|
assertNonNegativeAmount(value, "Transaction value");
|
|
856
1118
|
let estimatedBridgeGas;
|
|
857
|
-
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao) {
|
|
1119
|
+
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao + alphaTopUpRao) {
|
|
858
1120
|
estimatedBridgeGas = await provider.estimateGas({
|
|
859
1121
|
account: sender,
|
|
860
1122
|
to: subtensor.contracts.gateway,
|
|
861
|
-
data: (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
input.amountWei
|
|
871
|
-
]
|
|
872
|
-
}),
|
|
1123
|
+
data: encodeHubBridge(
|
|
1124
|
+
evm.ccipSelector,
|
|
1125
|
+
asset.subtensorToken,
|
|
1126
|
+
recipient,
|
|
1127
|
+
taoAmount,
|
|
1128
|
+
stakedAlphaRao,
|
|
1129
|
+
input.amountWei,
|
|
1130
|
+
partnerFee
|
|
1131
|
+
),
|
|
873
1132
|
value
|
|
874
1133
|
});
|
|
875
1134
|
}
|
|
@@ -882,6 +1141,7 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
882
1141
|
return Object.freeze({
|
|
883
1142
|
exactNetworkFeeWei,
|
|
884
1143
|
transactionValueWei: value,
|
|
1144
|
+
partnerFeeWei: input.source === "liquid" ? taoTopUp : alphaTopUpRao * EVM_WEI_PER_RAO,
|
|
885
1145
|
plan
|
|
886
1146
|
});
|
|
887
1147
|
}
|
|
@@ -1469,13 +1729,20 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1469
1729
|
evmChainFromBridgeDirection(direction)
|
|
1470
1730
|
);
|
|
1471
1731
|
const evmToSubtensor = isEvmToSubtensorDirection(direction);
|
|
1472
|
-
const [
|
|
1473
|
-
|
|
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
|
+
],
|
|
1474
1741
|
alphaGatewayAbi,
|
|
1475
1742
|
"BridgedOut"
|
|
1476
1743
|
];
|
|
1477
1744
|
for (const log of receipt.logs) {
|
|
1478
|
-
if (!isLogFrom(log, address)) continue;
|
|
1745
|
+
if (!addresses.some((address) => isLogFrom(log, address))) continue;
|
|
1479
1746
|
try {
|
|
1480
1747
|
const parsed = (0, import_viem10.decodeEventLog)({
|
|
1481
1748
|
abi: contractAbi,
|
|
@@ -1629,16 +1896,23 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1629
1896
|
"The CCIP execution receipt is unavailable."
|
|
1630
1897
|
);
|
|
1631
1898
|
}
|
|
1899
|
+
const subtensorGateways = [
|
|
1900
|
+
foreverMoneyDeployment.subtensor.contracts.legacyGateway,
|
|
1901
|
+
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1902
|
+
];
|
|
1632
1903
|
for (const log of receipt.logs) {
|
|
1633
|
-
if (
|
|
1904
|
+
if (!subtensorGateways.some(
|
|
1905
|
+
(gateway) => log.address.toLowerCase() === gateway.toLowerCase()
|
|
1906
|
+
)) {
|
|
1634
1907
|
continue;
|
|
1635
1908
|
}
|
|
1636
1909
|
try {
|
|
1637
|
-
|
|
1910
|
+
const { eventName } = (0, import_viem11.decodeEventLog)({
|
|
1638
1911
|
abi: alphaGatewayAbi2,
|
|
1639
1912
|
data: log.data,
|
|
1640
1913
|
topics: log.topics
|
|
1641
|
-
})
|
|
1914
|
+
});
|
|
1915
|
+
if (eventName === "Claimable" || eventName === "NotDelivered") {
|
|
1642
1916
|
return "recovery";
|
|
1643
1917
|
}
|
|
1644
1918
|
} catch {
|