@forevermoney/sdk 0.4.0 → 0.5.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 +18 -0
- package/README.md +36 -3
- package/dist/index.cjs +108 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -7
- package/dist/index.d.ts +27 -7
- package/dist/index.js +108 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-09-16
|
|
4
|
+
|
|
5
|
+
- Staked bridges from Subtensor can pull from several validators. Pass
|
|
6
|
+
`stakePulls: [{ hotkey, amountRao }]` (1–16 entries, unique hotkeys, summing
|
|
7
|
+
to the bridged amount) and the plan calls the V5.1 gateway's
|
|
8
|
+
`bridgeOutFromValidators` (or `…WithFee`), which re-delegates every pull to
|
|
9
|
+
the token's canonical validator before depositing. Without `stakePulls` the
|
|
10
|
+
plan keeps the single-validator `bridgeOut` call. With a partner fee the cut
|
|
11
|
+
is charged on top and spread across the pulls in proportion to their
|
|
12
|
+
amounts, so each position needs headroom for its share. Added `StakePull`, `MAX_STAKE_PULLS`, and the
|
|
13
|
+
`bridgeOutFromValidators*`, `minStakeRequired`, `MAX_STAKE_SOURCES` and
|
|
14
|
+
`GATEWAY_COLDKEY` ABI entries.
|
|
15
|
+
- Moved the Subtensor gateway to the V5.1 deployment at
|
|
16
|
+
`0xd5Fa238aa4177f6c1341491969d9cBeec94EEd69`. `contracts.legacyGateway` is
|
|
17
|
+
now `contracts.legacyGateways`, a list of retired hubs (oldest first) used for
|
|
18
|
+
receipt and delivery tracking; the V5 hub stays listed because the spoke
|
|
19
|
+
gateways still deliver through it. Deployment metadata version is 1.3.0.
|
|
20
|
+
|
|
3
21
|
## 0.4.0 — 2026-09-16
|
|
4
22
|
|
|
5
23
|
- Added `asset: 'sn80'` to the bridge builders and preparation methods for Base
|
package/README.md
CHANGED
|
@@ -11,9 +11,12 @@ is transferred 1:1 and the network fee is charged separately, so the SDK fixes
|
|
|
11
11
|
the contract's minimum destination output to the bridged principal instead of
|
|
12
12
|
exposing configurable slippage.
|
|
13
13
|
|
|
14
|
-
Bridge plans use the
|
|
15
|
-
remain under `contracts.
|
|
16
|
-
bridges sent through them can still be tracked.
|
|
14
|
+
Bridge plans use the current gateways in `contracts.gateway`. Retired gateways
|
|
15
|
+
remain under `contracts.legacyGateways` only so receipts and deliveries of
|
|
16
|
+
bridges sent through them can still be tracked. A spoke gateway's Subtensor
|
|
17
|
+
hub pointer is immutable, so after a hub-only redeploy EVM-to-Subtensor
|
|
18
|
+
deliveries still arrive through the previous hub; the SDK watches every known
|
|
19
|
+
hub, so tracking and recovery detection are unaffected. Plans call the zero-fee V5
|
|
17
20
|
entrypoints unless a `partnerFee` is passed (see "Charge a partner fee").
|
|
18
21
|
|
|
19
22
|
## Install
|
|
@@ -172,6 +175,36 @@ Canonical SN80 token addresses are exported as `contracts.wrappedSn80`:
|
|
|
172
175
|
The Base address `0x2292233d308188fcb3775f63a20f31dff6db02d9` is the SN80/TAO
|
|
173
176
|
liquidity pool; bridge calls use the token addresses above.
|
|
174
177
|
|
|
178
|
+
## Bridge stake held with several validators
|
|
179
|
+
|
|
180
|
+
Staked alpha on Finney is keyed by validator hotkey, and a bridge can only pull
|
|
181
|
+
from the positions you name. Pass `stakePulls` to draw from more than one
|
|
182
|
+
validator; the gateway re-delegates each pull to the token's canonical
|
|
183
|
+
validator and deposits the total, so stake with any validator can be bridged.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const prepared = await foreverMoney.bridge.prepareSubtensorToBase({
|
|
187
|
+
sender: '0x...',
|
|
188
|
+
recipient: '0x...',
|
|
189
|
+
amountWei: 140n * 10n ** 18n,
|
|
190
|
+
source: 'staked',
|
|
191
|
+
netuid: 0n,
|
|
192
|
+
stakePulls: [
|
|
193
|
+
{ hotkey: '0x…validatorA', amountRao: 100_000_000_000n },
|
|
194
|
+
{ hotkey: '0x…validatorB', amountRao: 40_000_000_000n },
|
|
195
|
+
],
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Rules the SDK checks before building a plan: 1 to `MAX_STAKE_PULLS` (16)
|
|
200
|
+
entries, unique non-zero hotkeys, positive amounts, and a sum equal to the
|
|
201
|
+
bridged amount in RAO. One staking approval on the netuid covers every pull.
|
|
202
|
+
Nothing on-chain enforces the runtime's minimum stake on what you leave behind
|
|
203
|
+
(`minStakeRequired()`, 0.02 TAO-equivalent), so size each pull to either drain
|
|
204
|
+
the position or leave at least that much. With a partner fee the cut is charged
|
|
205
|
+
on top and spread across the pulls in proportion to their amounts, so each
|
|
206
|
+
position needs headroom for its share.
|
|
207
|
+
|
|
175
208
|
## Charge a partner fee
|
|
176
209
|
|
|
177
210
|
Integrators can take a fee on each bridge by passing `partnerFee` to any bridge
|
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
ForeverMoneyError: () => ForeverMoneyError,
|
|
29
29
|
GAS_LIMIT_BUFFER_BPS: () => GAS_LIMIT_BUFFER_BPS,
|
|
30
30
|
MAX_PARTNER_FEE_BPS: () => MAX_PARTNER_FEE_BPS,
|
|
31
|
+
MAX_STAKE_PULLS: () => MAX_STAKE_PULLS,
|
|
31
32
|
MIN_LIQUID_BASE_TO_SUBTENSOR_WEI: () => MIN_LIQUID_BASE_TO_SUBTENSOR_WEI,
|
|
32
33
|
MIN_LIQUID_EVM_TO_SUBTENSOR_WEI: () => MIN_LIQUID_EVM_TO_SUBTENSOR_WEI,
|
|
33
34
|
MIN_LIQUID_SUBTENSOR_TO_EVM_WEI: () => MIN_LIQUID_SUBTENSOR_TO_EVM_WEI,
|
|
@@ -162,7 +163,7 @@ var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
|
162
163
|
var RAO_PER_TAO = 1000000000n;
|
|
163
164
|
var EVM_WEI_PER_RAO = 1000000000n;
|
|
164
165
|
var SN80_NETUID = 80n;
|
|
165
|
-
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.
|
|
166
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.3.0";
|
|
166
167
|
var foreverMoneyDeployment = Object.freeze({
|
|
167
168
|
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
168
169
|
base: Object.freeze({
|
|
@@ -172,9 +173,11 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
172
173
|
ccipSelector: BASE_CCIP_SELECTOR,
|
|
173
174
|
contracts: Object.freeze({
|
|
174
175
|
gateway: (0, import_viem2.getAddress)("0x1da2415229b614C787e145D1D7346eb496319C52"),
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// Retired gateways, oldest first; kept only to track transfers sent
|
|
177
|
+
// through them.
|
|
178
|
+
legacyGateways: [
|
|
179
|
+
(0, import_viem2.getAddress)("0x5EF3d7D19e4b233a1A169DA0d5CB02ec6b160a2C")
|
|
180
|
+
],
|
|
178
181
|
wrappedTao: (0, import_viem2.getAddress)(
|
|
179
182
|
"0xf3081494b87e8d5fb7960f066e931d1d0e6e3d67"
|
|
180
183
|
),
|
|
@@ -207,9 +210,9 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
207
210
|
ccipSelector: ROBINHOOD_CCIP_SELECTOR,
|
|
208
211
|
contracts: Object.freeze({
|
|
209
212
|
gateway: (0, import_viem2.getAddress)("0xf27fdA637131E25B2A1b4865ED9597d881980c7E"),
|
|
210
|
-
|
|
211
|
-
"0x53Dcc4FE04193e489BE537F722F65317DB1E65d8"
|
|
212
|
-
|
|
213
|
+
legacyGateways: [
|
|
214
|
+
(0, import_viem2.getAddress)("0x53Dcc4FE04193e489BE537F722F65317DB1E65d8")
|
|
215
|
+
],
|
|
213
216
|
wrappedTao: (0, import_viem2.getAddress)(
|
|
214
217
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
215
218
|
),
|
|
@@ -225,10 +228,14 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
225
228
|
chainId: SUBTENSOR_CHAIN_ID,
|
|
226
229
|
ccipSelector: SUBTENSOR_CCIP_SELECTOR,
|
|
227
230
|
contracts: Object.freeze({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
),
|
|
231
|
+
// V5.1 AlphaGateway: multi-validator staked input (tao-bridge 485c898).
|
|
232
|
+
gateway: (0, import_viem2.getAddress)("0xd5Fa238aa4177f6c1341491969d9cBeec94EEd69"),
|
|
233
|
+
// V4 and V5 hubs. The spokes still deliver to the V5 hub (their hub
|
|
234
|
+
// pointer is immutable), so it stays here for delivery tracking.
|
|
235
|
+
legacyGateways: [
|
|
236
|
+
(0, import_viem2.getAddress)("0x998f20Fea90bF7792774dECc7f994716442B1705"),
|
|
237
|
+
(0, import_viem2.getAddress)("0xcd0C6d98D0A126B1c113d15b4c28F38321437787")
|
|
238
|
+
],
|
|
232
239
|
alphaVault: (0, import_viem2.getAddress)(
|
|
233
240
|
"0x11837459896D96F821a8D88eC93a3C8D152033D4"
|
|
234
241
|
),
|
|
@@ -437,6 +444,11 @@ var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
|
437
444
|
"function integratorCut(uint256 amount,uint16 bps) pure returns (uint256)",
|
|
438
445
|
"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
446
|
"function bridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
447
|
+
"function bridgeOutFromValidators(uint64 destSelector,address token,address recipient,uint256 taoAmount,(bytes32 validator,uint256 alphaRao)[] sources,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
448
|
+
"function bridgeOutFromValidatorsWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,(bytes32 validator,uint256 alphaRao)[] sources,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
449
|
+
"function minStakeRequired() view returns (uint256)",
|
|
450
|
+
"function MAX_STAKE_SOURCES() view returns (uint256)",
|
|
451
|
+
"function GATEWAY_COLDKEY() view returns (bytes32)",
|
|
440
452
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
441
453
|
"function claimNative(address to)",
|
|
442
454
|
"function claimStaked(address token,bytes32 destColdkey,address to)",
|
|
@@ -605,6 +617,7 @@ var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
|
605
617
|
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
606
618
|
var MIN_LIQUID_SUBTENSOR_TO_EVM_WEI = 2000000n * EVM_WEI_PER_RAO;
|
|
607
619
|
var MAX_PARTNER_FEE_BPS = 1e4;
|
|
620
|
+
var MAX_STAKE_PULLS = 16;
|
|
608
621
|
var NO_PARTNER_FEE = {
|
|
609
622
|
recipient: "0x0000000000000000000000000000000000000000",
|
|
610
623
|
bps: 0
|
|
@@ -698,6 +711,16 @@ function sourceNetuid(input) {
|
|
|
698
711
|
}
|
|
699
712
|
return netuid;
|
|
700
713
|
}
|
|
714
|
+
function stakePullsFor(input, amountRao) {
|
|
715
|
+
if (input.stakePulls === void 0) return void 0;
|
|
716
|
+
if (input.source !== "staked") {
|
|
717
|
+
throw new ForeverMoneyError(
|
|
718
|
+
"INVALID_TRANSACTION_PLAN",
|
|
719
|
+
"Stake pulls are only valid for a staked source."
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
return resolveStakePulls(input.stakePulls, amountRao);
|
|
723
|
+
}
|
|
701
724
|
function assertDelivery(value) {
|
|
702
725
|
if (value !== "liquid" && value !== "staked") {
|
|
703
726
|
throw new ForeverMoneyError(
|
|
@@ -769,7 +792,72 @@ function encodeSpokeBridge(token, amountWei, exit, fee) {
|
|
|
769
792
|
args: [token, amountWei, exit, 0n, fee]
|
|
770
793
|
});
|
|
771
794
|
}
|
|
772
|
-
function
|
|
795
|
+
function resolveStakePulls(pulls, amountRao) {
|
|
796
|
+
if (pulls.length === 0 || pulls.length > MAX_STAKE_PULLS) {
|
|
797
|
+
throw new ForeverMoneyError(
|
|
798
|
+
"INVALID_TRANSACTION_PLAN",
|
|
799
|
+
`Between 1 and ${MAX_STAKE_PULLS} stake pulls are required.`,
|
|
800
|
+
{ count: pulls.length }
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
const seen = /* @__PURE__ */ new Set();
|
|
804
|
+
let total = 0n;
|
|
805
|
+
const resolved = pulls.map((pull) => {
|
|
806
|
+
const validator = normalizeBytes32(pull.hotkey, "Stake pull hotkey");
|
|
807
|
+
if (validator === `0x${"0".repeat(64)}`) {
|
|
808
|
+
throw new ForeverMoneyError(
|
|
809
|
+
"INVALID_TRANSACTION_PLAN",
|
|
810
|
+
"Stake pull hotkey must not be zero."
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
const key = validator.toLowerCase();
|
|
814
|
+
if (seen.has(key)) {
|
|
815
|
+
throw new ForeverMoneyError(
|
|
816
|
+
"INVALID_TRANSACTION_PLAN",
|
|
817
|
+
"Stake pulls must not repeat a hotkey.",
|
|
818
|
+
{ hotkey: validator }
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
seen.add(key);
|
|
822
|
+
if (typeof pull.amountRao !== "bigint" || pull.amountRao <= 0n) {
|
|
823
|
+
throw new ForeverMoneyError(
|
|
824
|
+
"INVALID_TRANSACTION_PLAN",
|
|
825
|
+
"Each stake pull must take a positive amount of RAO.",
|
|
826
|
+
{ hotkey: validator }
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
total += pull.amountRao;
|
|
830
|
+
return { validator, alphaRao: pull.amountRao };
|
|
831
|
+
});
|
|
832
|
+
if (total !== amountRao) {
|
|
833
|
+
throw new ForeverMoneyError(
|
|
834
|
+
"INVALID_TRANSACTION_PLAN",
|
|
835
|
+
"Stake pulls must sum to the bridged amount.",
|
|
836
|
+
{ pulledRao: total.toString(), amountRao: amountRao.toString() }
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
return resolved;
|
|
840
|
+
}
|
|
841
|
+
function encodeHubBridge(destSelector, token, recipient, taoAmount, stakedAlphaRao, minTokenOut, fee, pulls) {
|
|
842
|
+
if (pulls !== void 0) {
|
|
843
|
+
const args2 = [
|
|
844
|
+
destSelector,
|
|
845
|
+
token,
|
|
846
|
+
recipient,
|
|
847
|
+
taoAmount,
|
|
848
|
+
pulls,
|
|
849
|
+
minTokenOut
|
|
850
|
+
];
|
|
851
|
+
return fee.bps === 0 ? (0, import_viem6.encodeFunctionData)({
|
|
852
|
+
abi: alphaAbi,
|
|
853
|
+
functionName: "bridgeOutFromValidators",
|
|
854
|
+
args: args2
|
|
855
|
+
}) : (0, import_viem6.encodeFunctionData)({
|
|
856
|
+
abi: alphaAbi,
|
|
857
|
+
functionName: "bridgeOutFromValidatorsWithFee",
|
|
858
|
+
args: [...args2, fee]
|
|
859
|
+
});
|
|
860
|
+
}
|
|
773
861
|
const args = [
|
|
774
862
|
destSelector,
|
|
775
863
|
token,
|
|
@@ -959,6 +1047,7 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
959
1047
|
}
|
|
960
1048
|
const taoAmount = input.source === "liquid" ? input.amountWei : 0n;
|
|
961
1049
|
const stakedAlphaRao = input.source === "staked" ? amountRao : 0n;
|
|
1050
|
+
const pulls = stakePullsFor(input, amountRao);
|
|
962
1051
|
const value = taoAmount + taoTopUp + feeWithBuffer(input.exactNetworkFeeWei);
|
|
963
1052
|
assertNonNegativeAmount(value, "Transaction value");
|
|
964
1053
|
steps.push(
|
|
@@ -975,7 +1064,8 @@ function buildSubtensorToEvmPlan(input) {
|
|
|
975
1064
|
taoAmount,
|
|
976
1065
|
stakedAlphaRao,
|
|
977
1066
|
input.amountWei,
|
|
978
|
-
partnerFee
|
|
1067
|
+
partnerFee,
|
|
1068
|
+
pulls
|
|
979
1069
|
),
|
|
980
1070
|
value,
|
|
981
1071
|
input.estimatedBridgeGas === void 0 ? void 0 : gasLimitWithBuffer(input.estimatedBridgeGas)
|
|
@@ -1127,7 +1217,8 @@ async function prepareSubtensorToEvm(provider, input) {
|
|
|
1127
1217
|
taoAmount,
|
|
1128
1218
|
stakedAlphaRao,
|
|
1129
1219
|
input.amountWei,
|
|
1130
|
-
partnerFee
|
|
1220
|
+
partnerFee,
|
|
1221
|
+
stakePullsFor(input, stakedAlphaRao)
|
|
1131
1222
|
),
|
|
1132
1223
|
value
|
|
1133
1224
|
});
|
|
@@ -1730,12 +1821,12 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
|
|
|
1730
1821
|
);
|
|
1731
1822
|
const evmToSubtensor = isEvmToSubtensorDirection(direction);
|
|
1732
1823
|
const [addresses, contractAbi, eventName] = evmToSubtensor ? [
|
|
1733
|
-
[evm.contracts.
|
|
1824
|
+
[...evm.contracts.legacyGateways, evm.contracts.gateway],
|
|
1734
1825
|
spokeGatewayAbi,
|
|
1735
1826
|
"BridgedToFinney"
|
|
1736
1827
|
] : [
|
|
1737
1828
|
[
|
|
1738
|
-
foreverMoneyDeployment.subtensor.contracts.
|
|
1829
|
+
...foreverMoneyDeployment.subtensor.contracts.legacyGateways,
|
|
1739
1830
|
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1740
1831
|
],
|
|
1741
1832
|
alphaGatewayAbi,
|
|
@@ -1897,7 +1988,7 @@ async function getCcipDeliveryStatus(provider, input) {
|
|
|
1897
1988
|
);
|
|
1898
1989
|
}
|
|
1899
1990
|
const subtensorGateways = [
|
|
1900
|
-
foreverMoneyDeployment.subtensor.contracts.
|
|
1991
|
+
...foreverMoneyDeployment.subtensor.contracts.legacyGateways,
|
|
1901
1992
|
foreverMoneyDeployment.subtensor.contracts.gateway
|
|
1902
1993
|
];
|
|
1903
1994
|
for (const log of receipt.logs) {
|