@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.js
CHANGED
|
@@ -92,7 +92,8 @@ var ROBINHOOD_CCIP_SELECTOR = 6180753054346818345n;
|
|
|
92
92
|
var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
93
93
|
var RAO_PER_TAO = 1000000000n;
|
|
94
94
|
var EVM_WEI_PER_RAO = 1000000000n;
|
|
95
|
-
var
|
|
95
|
+
var SN80_NETUID = 80n;
|
|
96
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.2.0";
|
|
96
97
|
var foreverMoneyDeployment = Object.freeze({
|
|
97
98
|
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
98
99
|
base: Object.freeze({
|
|
@@ -101,10 +102,16 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
101
102
|
chainId: BASE_CHAIN_ID,
|
|
102
103
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
103
104
|
contracts: Object.freeze({
|
|
104
|
-
gateway: getAddress2("
|
|
105
|
+
gateway: getAddress2("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
106
|
+
legacyGateway: getAddress2(
|
|
107
|
+
"0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C"
|
|
108
|
+
),
|
|
105
109
|
wrappedTao: getAddress2(
|
|
106
110
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
107
111
|
),
|
|
112
|
+
wrappedSn80: getAddress2(
|
|
113
|
+
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
114
|
+
),
|
|
108
115
|
ccipRouter: getAddress2(
|
|
109
116
|
"0x881e3A65B4d4a04dD529061dd0071cf975F58bCD"
|
|
110
117
|
),
|
|
@@ -130,7 +137,10 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
130
137
|
chainId: ROBINHOOD_CHAIN_ID,
|
|
131
138
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
132
139
|
contracts: Object.freeze({
|
|
133
|
-
gateway: getAddress2("
|
|
140
|
+
gateway: getAddress2("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
141
|
+
legacyGateway: getAddress2(
|
|
142
|
+
"0x53Dcc4FE04193e489BE537F722F65317DB1E65d8"
|
|
143
|
+
),
|
|
134
144
|
wrappedTao: getAddress2(
|
|
135
145
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
136
146
|
),
|
|
@@ -146,13 +156,19 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
146
156
|
chainId: SUBTENSOR_CHAIN_ID,
|
|
147
157
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
148
158
|
contracts: Object.freeze({
|
|
149
|
-
gateway: getAddress2("
|
|
159
|
+
gateway: getAddress2("0xcd0C6d98D0A126B1c113d15b4c28F38321437787"),
|
|
160
|
+
legacyGateway: getAddress2(
|
|
161
|
+
"0x998f20Fea90bF7792774dECc7f994716442B1705"
|
|
162
|
+
),
|
|
150
163
|
alphaVault: getAddress2(
|
|
151
164
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
152
165
|
),
|
|
153
166
|
wrappedTao: getAddress2(
|
|
154
167
|
"0xC5b6C1632d34901239396F5E1BDe54B342900256"
|
|
155
168
|
),
|
|
169
|
+
wrappedSn80: getAddress2(
|
|
170
|
+
"0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222"
|
|
171
|
+
),
|
|
156
172
|
ccipRouter: getAddress2(
|
|
157
173
|
"0xD941fBEcD2b971d0F54b4C34286C95faB52B60B8"
|
|
158
174
|
),
|
|
@@ -333,23 +349,34 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
333
349
|
"function ROUTER() view returns (address)",
|
|
334
350
|
"function BITTENSOR_SELECTOR() view returns (uint64)",
|
|
335
351
|
"function SUBTENSOR_GATEWAY() view returns (address)",
|
|
352
|
+
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
353
|
+
"function bridgeFeeBps() view returns (uint16)",
|
|
336
354
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
337
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)",
|
|
338
358
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
339
359
|
]);
|
|
340
360
|
var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
341
361
|
"function ROUTER() view returns (address)",
|
|
342
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)",
|
|
343
366
|
"function quoteBridgeOut(uint64 destSelector,address token,address recipient,uint256 mintedAmount) view returns (uint256 fee)",
|
|
344
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)",
|
|
345
371
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
346
372
|
"function claimNative(address to)",
|
|
347
373
|
"function claimStaked(address token,bytes32 destColdkey,address to)",
|
|
348
374
|
"function claimToken(address token,address to)",
|
|
349
375
|
"function claimableNative(address account) view returns (uint256)",
|
|
350
|
-
"function claimableToken(address
|
|
376
|
+
"function claimableToken(address token,address account) view returns (uint256)",
|
|
351
377
|
"event BridgedOut(uint64 destChainSelector,address indexed token,address indexed sender,address indexed recipient,uint256 minted,bytes32 messageId)",
|
|
352
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)",
|
|
353
380
|
"event DeliveredLiquid(address indexed token,bytes32 indexed ss58,uint256 taoOut)",
|
|
354
381
|
"event DeliveredStaked(address indexed token,bytes32 indexed ss58,uint256 alphaRao)"
|
|
355
382
|
]);
|
|
@@ -507,6 +534,101 @@ var alphaAbi = parseAbi(ALPHA_GATEWAY_ABI);
|
|
|
507
534
|
var stakingAbi = parseAbi(STAKING_ABI);
|
|
508
535
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
509
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
|
+
}
|
|
510
632
|
function assertDelivery(value) {
|
|
511
633
|
if (value !== "liquid" && value !== "staked") {
|
|
512
634
|
throw new ForeverMoneyError(
|
|
@@ -536,6 +658,19 @@ function assertBaseToSubtensorAmount(amountWei, delivery) {
|
|
|
536
658
|
);
|
|
537
659
|
}
|
|
538
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
|
+
}
|
|
539
674
|
function transactionStep(kind, label, chainId, from, to, data, value, gasLimit) {
|
|
540
675
|
return {
|
|
541
676
|
kind,
|
|
@@ -550,9 +685,94 @@ function transactionStep(kind, label, chainId, from, to, data, value, gasLimit)
|
|
|
550
685
|
}
|
|
551
686
|
};
|
|
552
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
|
+
}
|
|
553
771
|
function buildEvmToSubtensorPlan(input) {
|
|
554
772
|
const sender = normalizeEvmAddress(input.sender);
|
|
773
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
555
774
|
assertDelivery(input.delivery);
|
|
775
|
+
assertAssetMode(input.asset, input.delivery);
|
|
556
776
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
557
777
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
558
778
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
@@ -564,19 +784,24 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
564
784
|
minTaoOut: input.amountWei
|
|
565
785
|
};
|
|
566
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);
|
|
567
792
|
const steps = [];
|
|
568
|
-
if (input.allowanceWei < input.amountWei) {
|
|
793
|
+
if (input.allowanceWei < input.amountWei + cut) {
|
|
569
794
|
steps.push(
|
|
570
795
|
transactionStep(
|
|
571
796
|
"approval",
|
|
572
|
-
|
|
797
|
+
`Approve ${asset.wrappedLabel} for the ForeverMoney gateway`,
|
|
573
798
|
evm.chainId,
|
|
574
799
|
sender,
|
|
575
|
-
|
|
800
|
+
asset.evmToken,
|
|
576
801
|
encodeFunctionData({
|
|
577
802
|
abi: erc20Abi,
|
|
578
803
|
functionName: "approve",
|
|
579
|
-
args: [evm.contracts.gateway, input.amountWei]
|
|
804
|
+
args: [evm.contracts.gateway, input.amountWei + cut]
|
|
580
805
|
}),
|
|
581
806
|
0n
|
|
582
807
|
)
|
|
@@ -586,22 +811,23 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
586
811
|
steps.push(
|
|
587
812
|
transactionStep(
|
|
588
813
|
"transaction",
|
|
589
|
-
`Bridge
|
|
814
|
+
`Bridge ${asset.wrappedLabel} from ${evm.name} to Subtensor (${input.delivery})`,
|
|
590
815
|
evm.chainId,
|
|
591
816
|
sender,
|
|
592
817
|
evm.contracts.gateway,
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
818
|
+
encodeSpokeBridge(
|
|
819
|
+
asset.evmToken,
|
|
820
|
+
input.amountWei,
|
|
821
|
+
exit,
|
|
822
|
+
partnerFee
|
|
823
|
+
),
|
|
598
824
|
value,
|
|
599
825
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
600
826
|
)
|
|
601
827
|
);
|
|
602
828
|
return createTransactionPlan({
|
|
603
829
|
action: evm.key === "base" ? "bridge.base-to-subtensor" : "bridge.robinhood-to-subtensor",
|
|
604
|
-
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)}`,
|
|
605
831
|
steps
|
|
606
832
|
});
|
|
607
833
|
}
|
|
@@ -611,21 +837,23 @@ function buildBaseToSubtensorPlan(input) {
|
|
|
611
837
|
function buildSubtensorToEvmPlan(input) {
|
|
612
838
|
const sender = normalizeEvmAddress(input.sender);
|
|
613
839
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
840
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
614
841
|
assertSource(input.source);
|
|
615
|
-
|
|
842
|
+
assertAssetMode(input.asset, input.source);
|
|
843
|
+
const netuid = sourceNetuid(input);
|
|
844
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
616
845
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
617
846
|
const { subtensor } = foreverMoneyDeployment;
|
|
618
847
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
848
|
+
const partnerFee = resolvePartnerFee(
|
|
849
|
+
input.partnerFee,
|
|
850
|
+
subtensor.contracts.gateway
|
|
851
|
+
);
|
|
619
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;
|
|
620
855
|
const steps = [];
|
|
621
856
|
if (input.source === "staked") {
|
|
622
|
-
if (input.netuid === void 0) {
|
|
623
|
-
throw new ForeverMoneyError(
|
|
624
|
-
"INVALID_TRANSACTION_PLAN",
|
|
625
|
-
"A netuid is required when bridging staked TAO."
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
assertNonNegativeAmount(input.netuid, "netuid");
|
|
629
857
|
if (input.stakingAllowanceRao === void 0) {
|
|
630
858
|
throw new ForeverMoneyError(
|
|
631
859
|
"INVALID_TRANSACTION_PLAN",
|
|
@@ -633,11 +861,11 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
633
861
|
);
|
|
634
862
|
}
|
|
635
863
|
assertNonNegativeAmount(input.stakingAllowanceRao, "Staking allowance");
|
|
636
|
-
if (input.stakingAllowanceRao < amountRao) {
|
|
864
|
+
if (input.stakingAllowanceRao < amountRao + alphaTopUpRao) {
|
|
637
865
|
steps.push(
|
|
638
866
|
transactionStep(
|
|
639
867
|
"approval",
|
|
640
|
-
|
|
868
|
+
`Approve staked ${asset.label} for the ForeverMoney gateway`,
|
|
641
869
|
subtensor.chainId,
|
|
642
870
|
sender,
|
|
643
871
|
subtensor.contracts.stakingPrecompile,
|
|
@@ -646,8 +874,8 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
646
874
|
functionName: "approve",
|
|
647
875
|
args: [
|
|
648
876
|
subtensor.contracts.gateway,
|
|
649
|
-
|
|
650
|
-
amountRao
|
|
877
|
+
netuid,
|
|
878
|
+
amountRao + alphaTopUpRao
|
|
651
879
|
]
|
|
652
880
|
}),
|
|
653
881
|
0n
|
|
@@ -662,34 +890,35 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
662
890
|
}
|
|
663
891
|
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
664
892
|
const stakedAlphaRao = input.source === "staked" ? amountRao : 0n;
|
|
665
|
-
const value = taoAmount + feeWithBuffer(input.exactNetworkFeeWei);
|
|
893
|
+
const value = taoAmount + taoTopUp + feeWithBuffer(input.exactNetworkFeeWei);
|
|
666
894
|
assertNonNegativeAmount(value, "Transaction value");
|
|
667
895
|
steps.push(
|
|
668
896
|
transactionStep(
|
|
669
897
|
"transaction",
|
|
670
|
-
`Bridge ${input.source}
|
|
898
|
+
`Bridge ${input.source} ${asset.label} from Subtensor to ${evm.name}`,
|
|
671
899
|
subtensor.chainId,
|
|
672
900
|
sender,
|
|
673
901
|
subtensor.contracts.gateway,
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
input.amountWei
|
|
684
|
-
]
|
|
685
|
-
}),
|
|
902
|
+
encodeHubBridge(
|
|
903
|
+
evm.ccipSelector,
|
|
904
|
+
asset.subtensorToken,
|
|
905
|
+
recipient,
|
|
906
|
+
taoAmount,
|
|
907
|
+
stakedAlphaRao,
|
|
908
|
+
input.amountWei,
|
|
909
|
+
partnerFee
|
|
910
|
+
),
|
|
686
911
|
value,
|
|
687
912
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
688
913
|
)
|
|
689
914
|
);
|
|
690
915
|
return createTransactionPlan({
|
|
691
916
|
action: evm.key === "base" ? "bridge.subtensor-to-base" : "bridge.subtensor-to-robinhood",
|
|
692
|
-
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
|
+
)}`,
|
|
693
922
|
steps
|
|
694
923
|
});
|
|
695
924
|
}
|
|
@@ -698,7 +927,9 @@ function buildSubtensorToBasePlan(input) {
|
|
|
698
927
|
}
|
|
699
928
|
async function prepareEvmToSubtensor(provider, input) {
|
|
700
929
|
const sender = normalizeEvmAddress(input.sender);
|
|
930
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
701
931
|
assertDelivery(input.delivery);
|
|
932
|
+
assertAssetMode(input.asset, input.delivery);
|
|
702
933
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
703
934
|
const destination = normalizeSS58(input.destination);
|
|
704
935
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
@@ -708,27 +939,40 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
708
939
|
wantLiquid: input.delivery === "liquid",
|
|
709
940
|
minTaoOut: input.amountWei
|
|
710
941
|
};
|
|
942
|
+
const partnerFee = resolvePartnerFee(
|
|
943
|
+
input.partnerFee,
|
|
944
|
+
evm.contracts.gateway
|
|
945
|
+
);
|
|
946
|
+
const cut = partnerFeeCut(input.amountWei, partnerFee.bps);
|
|
711
947
|
const [allowanceWei, exactNetworkFeeWei] = await Promise.all([
|
|
712
948
|
provider.readContract({
|
|
713
|
-
address:
|
|
949
|
+
address: asset.evmToken,
|
|
714
950
|
abi: erc20Abi,
|
|
715
951
|
functionName: "allowance",
|
|
716
952
|
args: [sender, evm.contracts.gateway]
|
|
717
953
|
}),
|
|
718
|
-
provider.readContract({
|
|
954
|
+
partnerFee.bps === 0 ? provider.readContract({
|
|
719
955
|
address: evm.contracts.gateway,
|
|
720
956
|
abi: spokeAbi,
|
|
721
957
|
functionName: "quoteBridgeToFinney",
|
|
722
|
-
args: [
|
|
723
|
-
})
|
|
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
|
+
)
|
|
724
967
|
]);
|
|
725
968
|
let estimatedBridgeGas;
|
|
726
|
-
if (allowanceWei >= input.amountWei) {
|
|
727
|
-
const data =
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
969
|
+
if (allowanceWei >= input.amountWei + cut) {
|
|
970
|
+
const data = encodeSpokeBridge(
|
|
971
|
+
asset.evmToken,
|
|
972
|
+
input.amountWei,
|
|
973
|
+
exit,
|
|
974
|
+
partnerFee
|
|
975
|
+
);
|
|
732
976
|
estimatedBridgeGas = await provider.estimateGas({
|
|
733
977
|
account: sender,
|
|
734
978
|
to: evm.contracts.gateway,
|
|
@@ -745,6 +989,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
745
989
|
return Object.freeze({
|
|
746
990
|
exactNetworkFeeWei,
|
|
747
991
|
transactionValueWei: feeWithBuffer(exactNetworkFeeWei),
|
|
992
|
+
partnerFeeWei: cut,
|
|
748
993
|
plan
|
|
749
994
|
});
|
|
750
995
|
}
|
|
@@ -754,58 +999,67 @@ function prepareBaseToSubtensor(provider, input) {
|
|
|
754
999
|
async function prepareSubtensorToEvm(provider, input) {
|
|
755
1000
|
const sender = normalizeEvmAddress(input.sender);
|
|
756
1001
|
const recipient = normalizeEvmAddress(input.recipient);
|
|
1002
|
+
const asset = bridgeAsset(input.evmChain, input.asset);
|
|
757
1003
|
assertSource(input.source);
|
|
758
|
-
|
|
1004
|
+
assertAssetMode(input.asset, input.source);
|
|
1005
|
+
const netuid = sourceNetuid(input);
|
|
1006
|
+
assertSubtensorToEvmAmount(input.amountWei, input.source);
|
|
759
1007
|
const { subtensor } = foreverMoneyDeployment;
|
|
760
1008
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
761
|
-
const
|
|
1009
|
+
const partnerFee = resolvePartnerFee(
|
|
1010
|
+
input.partnerFee,
|
|
1011
|
+
subtensor.contracts.gateway
|
|
1012
|
+
);
|
|
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({
|
|
762
1016
|
address: subtensor.contracts.gateway,
|
|
763
1017
|
abi: alphaAbi,
|
|
764
1018
|
functionName: "quoteBridgeOut",
|
|
765
1019
|
args: [
|
|
766
1020
|
evm.ccipSelector,
|
|
767
|
-
|
|
1021
|
+
asset.subtensorToken,
|
|
768
1022
|
recipient,
|
|
769
1023
|
input.amountWei
|
|
770
1024
|
]
|
|
771
|
-
})
|
|
1025
|
+
}) : (await quoteHubWithFee(
|
|
1026
|
+
provider,
|
|
1027
|
+
subtensor.contracts.gateway,
|
|
1028
|
+
evm.ccipSelector,
|
|
1029
|
+
asset.subtensorToken,
|
|
1030
|
+
recipient,
|
|
1031
|
+
input.amountWei,
|
|
1032
|
+
taoAmount,
|
|
1033
|
+
stakedAlphaRao,
|
|
1034
|
+
partnerFee
|
|
1035
|
+
)).networkFee;
|
|
772
1036
|
let stakingAllowanceRao;
|
|
773
1037
|
if (input.source === "staked") {
|
|
774
|
-
if (input.netuid === void 0) {
|
|
775
|
-
throw new ForeverMoneyError(
|
|
776
|
-
"INVALID_TRANSACTION_PLAN",
|
|
777
|
-
"A netuid is required when bridging staked TAO."
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
assertNonNegativeAmount(input.netuid, "netuid");
|
|
781
1038
|
stakingAllowanceRao = await provider.readContract({
|
|
782
1039
|
address: subtensor.contracts.stakingPrecompile,
|
|
783
1040
|
abi: stakingAbi,
|
|
784
1041
|
functionName: "allowance",
|
|
785
|
-
args: [sender, subtensor.contracts.gateway,
|
|
1042
|
+
args: [sender, subtensor.contracts.gateway, netuid]
|
|
786
1043
|
});
|
|
787
1044
|
}
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
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);
|
|
791
1048
|
assertNonNegativeAmount(value, "Transaction value");
|
|
792
1049
|
let estimatedBridgeGas;
|
|
793
|
-
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao) {
|
|
1050
|
+
if (input.source === "liquid" || stakingAllowanceRao !== void 0 && stakingAllowanceRao >= stakedAlphaRao + alphaTopUpRao) {
|
|
794
1051
|
estimatedBridgeGas = await provider.estimateGas({
|
|
795
1052
|
account: sender,
|
|
796
1053
|
to: subtensor.contracts.gateway,
|
|
797
|
-
data:
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
input.amountWei
|
|
807
|
-
]
|
|
808
|
-
}),
|
|
1054
|
+
data: encodeHubBridge(
|
|
1055
|
+
evm.ccipSelector,
|
|
1056
|
+
asset.subtensorToken,
|
|
1057
|
+
recipient,
|
|
1058
|
+
taoAmount,
|
|
1059
|
+
stakedAlphaRao,
|
|
1060
|
+
input.amountWei,
|
|
1061
|
+
partnerFee
|
|
1062
|
+
),
|
|
809
1063
|
value
|
|
810
1064
|
});
|
|
811
1065
|
}
|
|
@@ -818,6 +1072,7 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
818
1072
|
return Object.freeze({
|
|
819
1073
|
exactNetworkFeeWei,
|
|
820
1074
|
transactionValueWei: value,
|
|
1075
|
+
partnerFeeWei: input.source === "liquid" ? taoTopUp : alphaTopUpRao * EVM_WEI_PER_RAO,
|
|
821
1076
|
plan
|
|
822
1077
|
});
|
|
823
1078
|
}
|
|
@@ -1417,13 +1672,20 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1417
1672
|
evmChainFromBridgeDirection(direction)
|
|
1418
1673
|
);
|
|
1419
1674
|
const evmToSubtensor = isEvmToSubtensorDirection(direction);
|
|
1420
|
-
const [
|
|
1421
|
-
|
|
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
|
+
],
|
|
1422
1684
|
alphaGatewayAbi,
|
|
1423
1685
|
"BridgedOut"
|
|
1424
1686
|
];
|
|
1425
1687
|
for (const log of receipt.logs) {
|
|
1426
|
-
if (!isLogFrom(log, address)) continue;
|
|
1688
|
+
if (!addresses.some((address) => isLogFrom(log, address))) continue;
|
|
1427
1689
|
try {
|
|
1428
1690
|
const parsed = decodeEventLog({
|
|
1429
1691
|
abi: contractAbi,
|
|
@@ -1577,16 +1839,23 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1577
1839
|
"The CCIP execution receipt is unavailable."
|
|
1578
1840
|
);
|
|
1579
1841
|
}
|
|
1842
|
+
const subtensorGateways = [
|
|
1843
|
+
foreverMoneyDeployment.subtensor.contracts.legacyGateway,
|
|
1844
|
+
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1845
|
+
];
|
|
1580
1846
|
for (const log of receipt.logs) {
|
|
1581
|
-
if (
|
|
1847
|
+
if (!subtensorGateways.some(
|
|
1848
|
+
(gateway) => log.address.toLowerCase() === gateway.toLowerCase()
|
|
1849
|
+
)) {
|
|
1582
1850
|
continue;
|
|
1583
1851
|
}
|
|
1584
1852
|
try {
|
|
1585
|
-
|
|
1853
|
+
const { eventName } = decodeEventLog2({
|
|
1586
1854
|
abi: alphaGatewayAbi2,
|
|
1587
1855
|
data: log.data,
|
|
1588
1856
|
topics: log.topics
|
|
1589
|
-
})
|
|
1857
|
+
});
|
|
1858
|
+
if (eventName === "Claimable" || eventName === "NotDelivered") {
|
|
1590
1859
|
return "recovery";
|
|
1591
1860
|
}
|
|
1592
1861
|
} catch {
|
|
@@ -1872,12 +2141,15 @@ export {
|
|
|
1872
2141
|
FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
1873
2142
|
ForeverMoneyError,
|
|
1874
2143
|
GAS_LIMIT_BUFFER_BPS,
|
|
2144
|
+
MAX_PARTNER_FEE_BPS,
|
|
1875
2145
|
MIN_LIQUID_BASE_TO_SUBTENSOR_WEI,
|
|
1876
2146
|
MIN_LIQUID_EVM_TO_SUBTENSOR_WEI,
|
|
2147
|
+
MIN_LIQUID_SUBTENSOR_TO_EVM_WEI,
|
|
1877
2148
|
NETWORK_FEE_BUFFER_BPS,
|
|
1878
2149
|
RAO_PER_TAO,
|
|
1879
2150
|
ROBINHOOD_CCIP_SELECTOR,
|
|
1880
2151
|
ROBINHOOD_CHAIN_ID,
|
|
2152
|
+
SN80_NETUID,
|
|
1881
2153
|
SUBTENSOR_CCIP_SELECTOR,
|
|
1882
2154
|
SUBTENSOR_CHAIN_ID,
|
|
1883
2155
|
assertWholeRao,
|
|
@@ -1910,6 +2182,8 @@ export {
|
|
|
1910
2182
|
normalizeEvmAddress,
|
|
1911
2183
|
normalizeSS58,
|
|
1912
2184
|
parseTaoAmount,
|
|
2185
|
+
partnerFeeCut,
|
|
2186
|
+
partnerFeeTaoTopUp,
|
|
1913
2187
|
sourceChainId,
|
|
1914
2188
|
ss58ToPublicKey,
|
|
1915
2189
|
toEip1193Transaction,
|