@forevermoney/sdk 0.5.3 → 0.5.5

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,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.5 — 2026-09-21
4
+
5
+ - Discover destination off-ramps from the canonical CCIP router for the source
6
+ chain, retaining the configured legacy off-ramp for historical transfers.
7
+ Cache discovery for five minutes per provider and coalesce concurrent reads.
8
+ - Recognise both legacy and CCIP v2 execution events and select the latest
9
+ execution across off-ramps. This fixes completed Finney-to-Robinhood SN80
10
+ deliveries remaining in the waiting state. Verified against a real delivery.
11
+
12
+ ## 0.5.4 — 2026-09-21
13
+
14
+ - Added the canonical Robinhood SN80 token to deployment metadata and enabled
15
+ staked SN80 bridge preparation between Robinhood and Finney in both
16
+ directions. Deployment metadata version is 1.4.0.
17
+
3
18
  ## 0.5.3 — 2026-09-19
4
19
 
5
20
  - Increased EVM-to-Subtensor CCIP destination execution gas from the spoke
package/README.md CHANGED
@@ -134,13 +134,12 @@ The bridge does not deduct its fee from the destination amount. For both
134
134
  directions, the SDK encodes the bridge amount itself as the contract's minimum
135
135
  output; callers cannot weaken that invariant.
136
136
 
137
- ## Bridge SN80 on Base
137
+ ## Bridge SN80 on Base and Robinhood
138
138
 
139
- Pass `asset: 'sn80'` to bridge Base SN80 to a Finney subnet-80 staked position,
140
- or bridge existing subnet-80 stake back to Base SN80. The default asset is `tao`,
141
- so existing TAO integrations keep their behavior. SN80 is supported on the Base
142
- lane with staked input/delivery; liquid TAO conversion and the Robinhood SN80
143
- 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.
144
143
 
145
144
  ```ts
146
145
  const toFinney = await foreverMoney.bridge.prepareBaseToSubtensor({
@@ -175,10 +174,13 @@ any other SN80 validator cannot be bridged until it is moved to that hotkey.
175
174
  Canonical SN80 token addresses are exported as `contracts.wrappedSn80`:
176
175
 
177
176
  - Base: `0x6F63d869011f95274498023b4ABFC00b30c34378`
177
+ - Robinhood: `0x6F63d869011f95274498023b4ABFC00b30c34378`
178
178
  - Finney: `0xfD628dE75EF96f0A5C59659159C6cA81E0DC2222`
179
179
 
180
180
  The Base address `0x2292233d308188fcb3775f63a20f31dff6db02d9` is the SN80/TAO
181
- 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.
182
184
 
183
185
  ## Bridge stake held with several validators
184
186
 
package/dist/index.cjs CHANGED
@@ -95,7 +95,7 @@ var SUBTENSOR_CCIP_SELECTOR = 2135107236357186872n;
95
95
  var RAO_PER_TAO = 1000000000n;
96
96
  var EVM_WEI_PER_RAO = 1000000000n;
97
97
  var SN80_NETUID = 80n;
98
- var FOREVERMONEY_DEPLOYMENT_VERSION = "1.3.0";
98
+ var FOREVERMONEY_DEPLOYMENT_VERSION = "1.4.0";
99
99
  var foreverMoneyDeployment = Object.freeze({
100
100
  version: FOREVERMONEY_DEPLOYMENT_VERSION,
101
101
  base: Object.freeze({
@@ -148,6 +148,9 @@ var foreverMoneyDeployment = Object.freeze({
148
148
  wrappedTao: (0, import_viem.getAddress)(
149
149
  "0xf3081494B87e8D5fb7960f066E931D1D0e6E3d67"
150
150
  ),
151
+ wrappedSn80: (0, import_viem.getAddress)(
152
+ "0x6f63d869011f95274498023b4abfc00b30c34378"
153
+ ),
151
154
  ccipRouter: (0, import_viem.getAddress)(
152
155
  "0x06fC836cf9839B1cd891C440A0a45242DA6Ae1c9"
153
156
  ),
@@ -705,7 +708,8 @@ var CCIP_EXECUTION_ABI = Object.freeze([
705
708
  "event ExecutionStateChanged(uint64 indexed sourceChainSelector,uint64 indexed sequenceNumber,bytes32 indexed messageId,bytes32 messageHash,uint8 state,bytes returnData,uint256 gasUsed)"
706
709
  ]);
707
710
  var CCIP_ROUTER_ABI = Object.freeze([
708
- "function isOffRamp(uint64 sourceChainSelector,address offRamp) view returns (bool)"
711
+ "function isOffRamp(uint64 sourceChainSelector,address offRamp) view returns (bool)",
712
+ "function getOffRamps() view returns ((uint64 sourceChainSelector,address offRamp)[])"
709
713
  ]);
710
714
  var foreverMoneyAbis = Object.freeze({
711
715
  alphaGateway: ALPHA_GATEWAY_ABI,
@@ -814,15 +818,9 @@ function bridgeAsset(evmChain, asset = "tao") {
814
818
  'Asset must be "tao" or "sn80".'
815
819
  );
816
820
  }
817
- if (asset === "sn80" && evmChain !== "base") {
818
- throw new ForeverMoneyError(
819
- "INVALID_TRANSACTION_PLAN",
820
- "SN80 bridging is supported between Base and Subtensor."
821
- );
822
- }
823
821
  const evm = getForeverMoneyEvmDeployment(evmChain);
824
822
  return asset === "sn80" ? {
825
- evmToken: foreverMoneyDeployment.base.contracts.wrappedSn80,
823
+ evmToken: evm.contracts.wrappedSn80,
826
824
  subtensorToken: foreverMoneyDeployment.subtensor.contracts.wrappedSn80,
827
825
  label: "SN80",
828
826
  wrappedLabel: "SN80"
@@ -2075,6 +2073,41 @@ function bridgeMessageIdFromReceipt(direction, receipt) {
2075
2073
  // src/bridge/tracking.ts
2076
2074
  var ccipExecutionAbi = (0, import_viem11.parseAbi)(CCIP_EXECUTION_ABI);
2077
2075
  var alphaGatewayAbi2 = (0, import_viem11.parseAbi)(ALPHA_GATEWAY_ABI);
2076
+ var routerAbi = (0, import_viem11.parseAbi)(CCIP_ROUTER_ABI);
2077
+ var executionV2Abi = (0, import_viem11.parseAbi)([
2078
+ "event ExecutionStateChanged(uint64 indexed sourceChainSelector,uint64 indexed sequenceNumber,bytes32 indexed messageId,uint8 state,bytes returnData)"
2079
+ ]);
2080
+ var rampCache = /* @__PURE__ */ new WeakMap();
2081
+ function deliveryOffRamps(provider, client, router, selector, legacy) {
2082
+ let cache = rampCache.get(provider);
2083
+ if (!cache) {
2084
+ cache = /* @__PURE__ */ new Map();
2085
+ rampCache.set(provider, cache);
2086
+ }
2087
+ const key = `${router}:${selector}:${legacy}`;
2088
+ const cached = cache.get(key);
2089
+ if (cached && cached.expires > Date.now()) return cached.request;
2090
+ const request = client.readContract({
2091
+ address: router,
2092
+ abi: routerAbi,
2093
+ functionName: "getOffRamps"
2094
+ }).then((ramps) => {
2095
+ const addresses = /* @__PURE__ */ new Map([
2096
+ [legacy.toLowerCase(), legacy]
2097
+ ]);
2098
+ for (const ramp of ramps) {
2099
+ if (ramp.sourceChainSelector === selector)
2100
+ addresses.set(ramp.offRamp.toLowerCase(), ramp.offRamp);
2101
+ }
2102
+ return [...addresses.values()];
2103
+ });
2104
+ const entry = { expires: Date.now() + 3e5, request };
2105
+ cache.set(key, entry);
2106
+ void request.catch(() => {
2107
+ if (cache.get(key) === entry) cache.delete(key);
2108
+ });
2109
+ return request;
2110
+ }
2078
2111
  function destinationChainId(direction) {
2079
2112
  assertBridgeDirection(direction);
2080
2113
  const evm = getForeverMoneyEvmDeployment(
@@ -2188,13 +2221,29 @@ async function getCcipDeliveryStatus(provider, input) {
2188
2221
  const evmToSubtensor = isEvmToSubtensorDirection(input.direction);
2189
2222
  const sourceSelector = evmToSubtensor ? evm.ccipSelector : foreverMoneyDeployment.subtensor.ccipSelector;
2190
2223
  const offRamp = evmToSubtensor ? evmChain === "base" ? foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromBase : foreverMoneyDeployment.subtensor.contracts.ccipOffRampFromRobinhood : evm.contracts.ccipOffRampFromSubtensor;
2191
- const logs = await client.getLogs({
2192
- address: offRamp,
2193
- fromBlock: BigInt(input.fromBlock),
2194
- toBlock: "latest",
2195
- event: ccipExecutionAbi[0],
2196
- args: { sourceChainSelector: sourceSelector, messageId },
2197
- strict: true
2224
+ const offRamps = await deliveryOffRamps(
2225
+ provider,
2226
+ client,
2227
+ evmToSubtensor ? foreverMoneyDeployment.subtensor.contracts.ccipRouter : evm.contracts.ccipRouter,
2228
+ sourceSelector,
2229
+ offRamp
2230
+ );
2231
+ const batches = await Promise.all(
2232
+ [executionV2Abi[0], ccipExecutionAbi[0]].map(
2233
+ (event) => client.getLogs({
2234
+ address: offRamps.length === 1 ? offRamps[0] : [...offRamps],
2235
+ fromBlock: BigInt(input.fromBlock),
2236
+ toBlock: "latest",
2237
+ event,
2238
+ args: { sourceChainSelector: sourceSelector, messageId },
2239
+ strict: true
2240
+ })
2241
+ )
2242
+ );
2243
+ const logs = batches.flat().sort((a, b) => {
2244
+ const blockA = a.blockNumber ?? 0n;
2245
+ const blockB = b.blockNumber ?? 0n;
2246
+ return blockA === blockB ? (a.logIndex ?? 0) - (b.logIndex ?? 0) : blockA < blockB ? -1 : 1;
2198
2247
  });
2199
2248
  const latest = logs.at(-1);
2200
2249
  if (latest === void 0) return "waiting";