@forevermoney/sdk 0.5.2 → 0.5.4
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 +20 -0
- package/README.md +18 -11
- package/dist/index.cjs +40 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -5
- package/dist/index.d.ts +13 -5
- package/dist/index.js +40 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.4 — 2026-09-21
|
|
4
|
+
|
|
5
|
+
- Added the canonical Robinhood SN80 token to deployment metadata and enabled
|
|
6
|
+
staked SN80 bridge preparation between Robinhood and Finney in both
|
|
7
|
+
directions. Deployment metadata version is 1.4.0.
|
|
8
|
+
|
|
9
|
+
## 0.5.3 — 2026-09-19
|
|
10
|
+
|
|
11
|
+
- Increased EVM-to-Subtensor CCIP destination execution gas from the spoke
|
|
12
|
+
gateways' 300,000 default to an explicit 3,500,000. A 900,000 manual
|
|
13
|
+
execution reached the gateway but still fell back to a claimable booking;
|
|
14
|
+
the same live liquid claim path estimates near 2,560,000 gas. Quotes and
|
|
15
|
+
transaction calldata now use the same exported
|
|
16
|
+
`EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT` for zero-fee and partner-fee
|
|
17
|
+
bridges. Callers can override it per transaction with the optional
|
|
18
|
+
`destinationGasLimit` request field, without changing or republishing the
|
|
19
|
+
SDK.
|
|
20
|
+
- Added the explicit-gas `quoteBridgeToFinney` and `bridgeToFinney` overloads
|
|
21
|
+
to the public SpokeGateway ABI.
|
|
22
|
+
|
|
3
23
|
## 0.5.2 — 2026-09-16
|
|
4
24
|
|
|
5
25
|
- Added opt-in stake rounding adjustment to Subtensor bridge preparation, with an explicit disable option and adjusted amount/plan metadata. Searches only on `StrandedStake`, preserves minimum output, requotes candidates and checks source balances, minimum remainders and partner-fee allocations. Approval-required plans are explicitly marked as not yet simulated.
|
package/README.md
CHANGED
|
@@ -6,10 +6,10 @@ vault contracts.
|
|
|
6
6
|
The SDK owns the canonical production deployment: chain IDs, CCIP selectors,
|
|
7
7
|
contract addresses, ABIs, and protocol-specific amount conversion. An
|
|
8
8
|
integrator supplies only RPC transports and user input. The SDK never accepts a
|
|
9
|
-
private key, signs a transaction, or broadcasts a transaction.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
private key, signs a transaction, or broadcasts a transaction. Network fees are
|
|
10
|
+
charged separately. Subtensor-to-EVM requests accept an explicit minimum output
|
|
11
|
+
to account for native staking rounding; exact output remains the default.
|
|
12
|
+
SDK 0.5.2 also provides opt-in stake rounding preparation (see below).
|
|
13
13
|
|
|
14
14
|
Bridge plans use the current gateways in `contracts.gateway`. Retired gateways
|
|
15
15
|
remain under `contracts.legacyGateways` only so receipts and deliveries of
|
|
@@ -101,6 +101,11 @@ Liquid Base-to-Subtensor delivery requires at least `0.01 TAO` because the
|
|
|
101
101
|
destination vault must unstake the bridged position. The SDK rejects smaller
|
|
102
102
|
liquid deliveries with `AMOUNT_BELOW_MINIMUM` before quoting or planning them;
|
|
103
103
|
staked delivery does not use this liquid-unstaking minimum.
|
|
104
|
+
EVM-to-Subtensor plans quote and encode an explicit 3,500,000 destination gas
|
|
105
|
+
limit so the variable-cost Subtensor exit path can auto-execute through CCIP.
|
|
106
|
+
The policy is exported as `EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT`. Pass a
|
|
107
|
+
positive bigint as `destinationGasLimit` to override it for one preparation or
|
|
108
|
+
plan; the SDK uses that value for both the fee quote and transaction calldata.
|
|
104
109
|
The network-fee buffer is 2% and the estimated-gas buffer is 50%; both policies
|
|
105
110
|
are exported as bigint basis-point constants and covered by property tests.
|
|
106
111
|
|
|
@@ -129,13 +134,12 @@ The bridge does not deduct its fee from the destination amount. For both
|
|
|
129
134
|
directions, the SDK encodes the bridge amount itself as the contract's minimum
|
|
130
135
|
output; callers cannot weaken that invariant.
|
|
131
136
|
|
|
132
|
-
## Bridge SN80 on Base
|
|
137
|
+
## Bridge SN80 on Base and Robinhood
|
|
133
138
|
|
|
134
|
-
Pass `asset: 'sn80'` to bridge Base SN80 to a Finney subnet-80
|
|
135
|
-
or bridge existing subnet-80 stake back to
|
|
136
|
-
so existing TAO integrations keep their behavior.
|
|
137
|
-
|
|
138
|
-
lane are not supported by these methods.
|
|
139
|
+
Pass `asset: 'sn80'` to bridge Base or Robinhood SN80 to a Finney subnet-80
|
|
140
|
+
staked position, or bridge existing subnet-80 stake back to either EVM chain.
|
|
141
|
+
The default asset is `tao`, so existing TAO integrations keep their behavior.
|
|
142
|
+
SN80 uses staked input/delivery; liquid TAO conversion is not supported.
|
|
139
143
|
|
|
140
144
|
```ts
|
|
141
145
|
const toFinney = await foreverMoney.bridge.prepareBaseToSubtensor({
|
|
@@ -170,10 +174,13 @@ any other SN80 validator cannot be bridged until it is moved to that hotkey.
|
|
|
170
174
|
Canonical SN80 token addresses are exported as `contracts.wrappedSn80`:
|
|
171
175
|
|
|
172
176
|
- Base: `0x6F63d869011f95274498023b4ABFC00b30c34378`
|
|
177
|
+
- Robinhood: `0x6F63d869011f95274498023b4ABFC00b30c34378`
|
|
173
178
|
- Finney: `0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222`
|
|
174
179
|
|
|
175
180
|
The Base address `0x2292233d308188fcb3775f63a20f31dff6db02d9` is the SN80/TAO
|
|
176
|
-
liquidity pool
|
|
181
|
+
liquidity pool. The Robinhood SN80/TAO pool is
|
|
182
|
+
`0xBB17f4Db7831128239a34C2F7bd291622FC2Cc38`; bridge calls use the token
|
|
183
|
+
addresses above.
|
|
177
184
|
|
|
178
185
|
## Bridge stake held with several validators
|
|
179
186
|
|
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
BASE_CCIP_SELECTOR: () => BASE_CCIP_SELECTOR,
|
|
24
24
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
25
25
|
BASIS_POINTS: () => BASIS_POINTS,
|
|
26
|
+
EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT: () => EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT,
|
|
26
27
|
EVM_WEI_PER_RAO: () => EVM_WEI_PER_RAO,
|
|
27
28
|
FOREVERMONEY_DEPLOYMENT_VERSION: () => FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
28
29
|
ForeverMoneyError: () => ForeverMoneyError,
|
|
@@ -94,7 +95,7 @@ var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
|
|
|
94
95
|
var RAO_PER_TAO = 1000000000n;
|
|
95
96
|
var EVM_WEI_PER_RAO = 1000000000n;
|
|
96
97
|
var SN80_NETUID = 80n;
|
|
97
|
-
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.
|
|
98
|
+
var FOREVERMONEY_DEPLOYMENT_VERSION = "1.4.0";
|
|
98
99
|
var foreverMoneyDeployment = Object.freeze({
|
|
99
100
|
version: FOREVERMONEY_DEPLOYMENT_VERSION,
|
|
100
101
|
base: Object.freeze({
|
|
@@ -147,6 +148,9 @@ var foreverMoneyDeployment = Object.freeze({
|
|
|
147
148
|
wrappedTao: (0, import_viem.getAddress)(
|
|
148
149
|
"0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
|
|
149
150
|
),
|
|
151
|
+
wrappedSn80: (0, import_viem.getAddress)(
|
|
152
|
+
"0x6f63d869011f95274498023b4abfc00b30c34378"
|
|
153
|
+
),
|
|
150
154
|
ccipRouter: (0, import_viem.getAddress)(
|
|
151
155
|
"0x06fC836cf9839B1cd891C440A0a45242DA6Ae1c9"
|
|
152
156
|
),
|
|
@@ -645,7 +649,9 @@ var SPOKE_GATEWAY_ABI = Object.freeze([
|
|
|
645
649
|
"function maxIntegratorFeeBps() view returns (uint16)",
|
|
646
650
|
"function bridgeFeeBps() view returns (uint16)",
|
|
647
651
|
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) view returns (uint256 fee)",
|
|
652
|
+
"function quoteBridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit) view returns (uint256 fee)",
|
|
648
653
|
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit) payable returns (bytes32 messageId)",
|
|
654
|
+
"function bridgeToFinney(address token,uint256 amount,(bytes32 ss58,address evmFallback,bool wantLiquid,uint256 minTaoOut) exit,uint256 gasLimit) payable returns (bytes32 messageId)",
|
|
649
655
|
"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)",
|
|
650
656
|
"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)",
|
|
651
657
|
"event BridgedToFinney(address indexed token,address indexed sender,bytes32 indexed ss58,uint256 amount,bytes32 messageId)"
|
|
@@ -663,7 +669,6 @@ var ALPHA_GATEWAY_ABI = Object.freeze([
|
|
|
663
669
|
"function bridgeOutWithFee(uint64 destSelector,address token,address recipient,uint256 taoAmount,uint256 stakedAlphaRao,uint256 minTokenOut,(address recipient,uint16 bps) integrator) payable returns (bytes32 messageId)",
|
|
664
670
|
"function bridgeOutFromValidators(uint64 destSelector,address token,address recipient,uint256 taoAmount,(bytes32 validator,uint256 alphaRao)[] sources,uint256 minTokenOut) payable returns (bytes32 messageId)",
|
|
665
671
|
"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)",
|
|
666
|
-
"function minStakeRequired() view returns (uint256)",
|
|
667
672
|
"function MAX_STAKE_SOURCES() view returns (uint256)",
|
|
668
673
|
"function GATEWAY_COLDKEY() view returns (bytes32)",
|
|
669
674
|
"function claimLiquid(address token,uint256 minTaoOut,address to)",
|
|
@@ -750,6 +755,18 @@ var stakingAbi = (0, import_viem6.parseAbi)(STAKING_ABI);
|
|
|
750
755
|
var MIN_LIQUID_EVM_TO_SUBTENSOR_WEI = 10000000000000000n;
|
|
751
756
|
var MIN_LIQUID_BASE_TO_SUBTENSOR_WEI = MIN_LIQUID_EVM_TO_SUBTENSOR_WEI;
|
|
752
757
|
var MIN_LIQUID_SUBTENSOR_TO_EVM_WEI = 2000000n * EVM_WEI_PER_RAO;
|
|
758
|
+
var EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT = 3500000n;
|
|
759
|
+
function resolveDestinationGasLimit(value) {
|
|
760
|
+
const gasLimit = value ?? EVM_TO_SUBTENSOR_DESTINATION_GAS_LIMIT;
|
|
761
|
+
if (typeof gasLimit !== "bigint" || gasLimit <= 0n) {
|
|
762
|
+
throw new ForeverMoneyError(
|
|
763
|
+
"INVALID_TRANSACTION_PLAN",
|
|
764
|
+
"Destination gas limit must be a positive bigint.",
|
|
765
|
+
{ destinationGasLimit: String(gasLimit) }
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return gasLimit;
|
|
769
|
+
}
|
|
753
770
|
var MAX_PARTNER_FEE_BPS = 1e4;
|
|
754
771
|
var MAX_STAKE_PULLS = 16;
|
|
755
772
|
var NO_PARTNER_FEE = {
|
|
@@ -800,15 +817,9 @@ function bridgeAsset(evmChain, asset = "tao") {
|
|
|
800
817
|
'Asset must be "tao" or "sn80".'
|
|
801
818
|
);
|
|
802
819
|
}
|
|
803
|
-
if (asset === "sn80" && evmChain !== "base") {
|
|
804
|
-
throw new ForeverMoneyError(
|
|
805
|
-
"INVALID_TRANSACTION_PLAN",
|
|
806
|
-
"SN80 bridging is supported between Base and Subtensor."
|
|
807
|
-
);
|
|
808
|
-
}
|
|
809
820
|
const evm = getForeverMoneyEvmDeployment(evmChain);
|
|
810
821
|
return asset === "sn80" ? {
|
|
811
|
-
evmToken:
|
|
822
|
+
evmToken: evm.contracts.wrappedSn80,
|
|
812
823
|
subtensorToken: foreverMoneyDeployment.subtensor.contracts.wrappedSn80,
|
|
813
824
|
label: "SN80",
|
|
814
825
|
wrappedLabel: "SN80"
|
|
@@ -926,15 +937,15 @@ function partnerFeeSummary(charged, unit, fee) {
|
|
|
926
937
|
if (fee.bps === 0) return "";
|
|
927
938
|
return ` Partner fee: ${charged} wei of ${unit} (${fee.bps} bps) on top, paid to ${fee.recipient}.`;
|
|
928
939
|
}
|
|
929
|
-
function encodeSpokeBridge(token, amountWei, exit, fee) {
|
|
940
|
+
function encodeSpokeBridge(token, amountWei, exit, destinationGasLimit, fee) {
|
|
930
941
|
return fee.bps === 0 ? (0, import_viem6.encodeFunctionData)({
|
|
931
942
|
abi: spokeAbi,
|
|
932
943
|
functionName: "bridgeToFinney",
|
|
933
|
-
args: [token, amountWei, exit]
|
|
944
|
+
args: [token, amountWei, exit, destinationGasLimit]
|
|
934
945
|
}) : (0, import_viem6.encodeFunctionData)({
|
|
935
946
|
abi: spokeAbi,
|
|
936
947
|
functionName: "bridgeToFinneyWithFee",
|
|
937
|
-
args: [token, amountWei, exit,
|
|
948
|
+
args: [token, amountWei, exit, destinationGasLimit, fee]
|
|
938
949
|
});
|
|
939
950
|
}
|
|
940
951
|
function resolveStakePulls(pulls, amountRao) {
|
|
@@ -1021,7 +1032,7 @@ function encodeHubBridge(destSelector, token, recipient, taoAmount, stakedAlphaR
|
|
|
1021
1032
|
args: [...args, fee]
|
|
1022
1033
|
});
|
|
1023
1034
|
}
|
|
1024
|
-
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee) {
|
|
1035
|
+
async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, destinationGasLimit, fee) {
|
|
1025
1036
|
const maxBps = await provider.readContract({
|
|
1026
1037
|
address: gateway,
|
|
1027
1038
|
abi: spokeAbi,
|
|
@@ -1032,7 +1043,7 @@ async function quoteSpokeWithFee(provider, gateway, token, amountWei, exit, fee)
|
|
|
1032
1043
|
address: gateway,
|
|
1033
1044
|
abi: spokeAbi,
|
|
1034
1045
|
functionName: "quoteBridgeToFinneyWithFee",
|
|
1035
|
-
args: [token, amountWei, exit,
|
|
1046
|
+
args: [token, amountWei, exit, destinationGasLimit, fee]
|
|
1036
1047
|
});
|
|
1037
1048
|
return networkFee;
|
|
1038
1049
|
}
|
|
@@ -1078,6 +1089,9 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
1078
1089
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
1079
1090
|
assertNonNegativeAmount(input.allowanceWei, "Token allowance");
|
|
1080
1091
|
assertNonNegativeAmount(input.exactNetworkFeeWei, "Network fee");
|
|
1092
|
+
const destinationGasLimit = resolveDestinationGasLimit(
|
|
1093
|
+
input.destinationGasLimit
|
|
1094
|
+
);
|
|
1081
1095
|
const destination = normalizeSS58(input.destination);
|
|
1082
1096
|
const exit = {
|
|
1083
1097
|
ss58: ss58ToPublicKey(destination),
|
|
@@ -1121,6 +1135,7 @@ function buildEvmToSubtensorPlan(input) {
|
|
|
1121
1135
|
asset.evmToken,
|
|
1122
1136
|
input.amountWei,
|
|
1123
1137
|
exit,
|
|
1138
|
+
destinationGasLimit,
|
|
1124
1139
|
partnerFee
|
|
1125
1140
|
),
|
|
1126
1141
|
value,
|
|
@@ -1236,6 +1251,9 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1236
1251
|
assertDelivery(input.delivery);
|
|
1237
1252
|
assertAssetMode(input.asset, input.delivery);
|
|
1238
1253
|
assertBaseToSubtensorAmount(input.amountWei, input.delivery);
|
|
1254
|
+
const destinationGasLimit = resolveDestinationGasLimit(
|
|
1255
|
+
input.destinationGasLimit
|
|
1256
|
+
);
|
|
1239
1257
|
const destination = normalizeSS58(input.destination);
|
|
1240
1258
|
const evm = getForeverMoneyEvmDeployment(input.evmChain);
|
|
1241
1259
|
const exit = {
|
|
@@ -1260,13 +1278,19 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1260
1278
|
address: evm.contracts.gateway,
|
|
1261
1279
|
abi: spokeAbi,
|
|
1262
1280
|
functionName: "quoteBridgeToFinney",
|
|
1263
|
-
args: [
|
|
1281
|
+
args: [
|
|
1282
|
+
asset.evmToken,
|
|
1283
|
+
input.amountWei,
|
|
1284
|
+
exit,
|
|
1285
|
+
destinationGasLimit
|
|
1286
|
+
]
|
|
1264
1287
|
}) : quoteSpokeWithFee(
|
|
1265
1288
|
provider,
|
|
1266
1289
|
evm.contracts.gateway,
|
|
1267
1290
|
asset.evmToken,
|
|
1268
1291
|
input.amountWei,
|
|
1269
1292
|
exit,
|
|
1293
|
+
destinationGasLimit,
|
|
1270
1294
|
partnerFee
|
|
1271
1295
|
)
|
|
1272
1296
|
]);
|
|
@@ -1276,6 +1300,7 @@ async function prepareEvmToSubtensor(provider, input) {
|
|
|
1276
1300
|
asset.evmToken,
|
|
1277
1301
|
input.amountWei,
|
|
1278
1302
|
exit,
|
|
1303
|
+
destinationGasLimit,
|
|
1279
1304
|
partnerFee
|
|
1280
1305
|
);
|
|
1281
1306
|
estimatedBridgeGas = await provider.estimateGas({
|