@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 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. Bridge principal
10
- is transferred 1:1 and the network fee is charged separately, so the SDK fixes
11
- the contract's minimum destination output to the bridged principal instead of
12
- exposing configurable slippage.
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 staked position,
135
- or bridge existing subnet-80 stake back to Base SN80. The default asset is `tao`,
136
- so existing TAO integrations keep their behavior. SN80 is supported on the Base
137
- lane with staked input/delivery; liquid TAO conversion and the Robinhood SN80
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; bridge calls use the token addresses above.
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.3.0";
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: foreverMoneyDeployment.base.contracts.wrappedSn80,
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, 0n, fee]
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, 0n, fee]
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: [asset.evmToken, input.amountWei, exit]
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({