@totalreclaw/totalreclaw 3.3.12-rc.21 → 3.3.12-rc.22

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/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: totalreclaw
3
3
  description: "End-to-end encrypted, decentralized memory for OpenClaw. A native kind:memory provider — recall is automatic via memory_search/memory_get, and facts are captured in the background. Trigger on 'install TotalReclaw', 'set up TotalReclaw', 'restore my recovery phrase', any recall request ('what do you remember about me', 'what's my X'), AND any explicit remember request ('remember X', 'save X')."
4
- version: 3.3.12-rc.21
4
+ version: 3.3.12-rc.22
5
5
  author: TotalReclaw Team
6
6
  license: MIT
7
7
  homepage: https://totalreclaw.xyz
package/billing-cache.ts CHANGED
@@ -14,6 +14,9 @@
14
14
  * - keeps the chain-id override in sync with the relay's authoritative
15
15
  * `chain_id` (after ops-1 both tiers are on Gnosis 100; the client consumes
16
16
  * the relay value verbatim — see `syncChainIdFromBilling`)
17
+ * - keeps the DataEdge-address override in sync with the relay's
18
+ * authoritative `data_edge_address` (staging vs prod contract; consumed
19
+ * verbatim — see `syncDataEdgeAddressFromBilling`, #460)
17
20
  * - does NOT import anything that performs outbound I/O
18
21
  *
19
22
  * Do NOT add any outbound-request call to this file — a single match for
@@ -24,7 +27,10 @@
24
27
 
25
28
  import fs from 'node:fs';
26
29
  import path from 'node:path';
27
- import { CONFIG, setChainIdOverride } from './config.js';
30
+ import { CONFIG, setChainIdOverride, setDataEdgeAddressOverride } from './config.js';
31
+
32
+ /** A plausible EVM address — anything else from billing is ignored (#460). */
33
+ const DATA_EDGE_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
28
34
 
29
35
  // ---------------------------------------------------------------------------
30
36
  // Constants
@@ -57,6 +63,13 @@ export interface BillingCache {
57
63
  * source of truth, so the client consumes this verbatim (#402).
58
64
  */
59
65
  chain_id?: number;
66
+ /**
67
+ * Authoritative DataEdge contract address from the relay
68
+ * `/v1/billing/status` response. Staging returns the isolated staging
69
+ * DataEdge, production the prod DataEdge; the client consumes this verbatim
70
+ * so writes and reads land on the same contract (#460).
71
+ */
72
+ data_edge_address?: string;
60
73
  checked_at: number;
61
74
  }
62
75
 
@@ -82,6 +95,34 @@ export function syncChainIdFromBilling(chainId: number | undefined): void {
82
95
  setChainIdOverride(typeof chainId === 'number' && Number.isFinite(chainId) ? chainId : 100);
83
96
  }
84
97
 
98
+ // ---------------------------------------------------------------------------
99
+ // DataEdge-address sync
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * Apply the relay's authoritative `data_edge_address` to the runtime DataEdge
104
+ * override. Mirrors `syncChainIdFromBilling`.
105
+ *
106
+ * The relay routes each environment to its own DataEdge (staging is on-chain
107
+ * isolated). If the client ignores this and uses the WASM-baked default (the
108
+ * PROD DataEdge), writes against the staging relay mine on the prod contract
109
+ * while reads come from the staging subgraph → empty recall + phantom
110
+ * "stored=N" success (#460).
111
+ *
112
+ * A missing / malformed `data_edge_address` (older relay, partial payload,
113
+ * junk) clears the override (`null`) so resolution falls through to the WASM
114
+ * default — never a stale value. Only a plausible address
115
+ * (`0x` + 40 hex) is honored. Called from `readBillingCache` and
116
+ * `writeBillingCache` so every cache read or write keeps the override in sync.
117
+ * Idempotent. The explicit env override (`TOTALRECLAW_DATA_EDGE_ADDRESS`)
118
+ * still wins — it is the first term in `getSubgraphConfig`.
119
+ */
120
+ export function syncDataEdgeAddressFromBilling(address: string | undefined): void {
121
+ setDataEdgeAddressOverride(
122
+ typeof address === 'string' && DATA_EDGE_ADDRESS_RE.test(address) ? address : null,
123
+ );
124
+ }
125
+
85
126
  // ---------------------------------------------------------------------------
86
127
  // Read / write
87
128
  // ---------------------------------------------------------------------------
@@ -99,9 +140,10 @@ export function readBillingCache(): BillingCache | null {
99
140
  if (!fs.existsSync(BILLING_CACHE_PATH)) return null;
100
141
  const raw = JSON.parse(fs.readFileSync(BILLING_CACHE_PATH, 'utf-8')) as BillingCache;
101
142
  if (!raw.checked_at || Date.now() - raw.checked_at > BILLING_CACHE_TTL) return null;
102
- // Keep chain override in sync with the persisted authoritative chain_id
103
- // across process restarts.
143
+ // Keep chain + DataEdge overrides in sync with the persisted authoritative
144
+ // values across process restarts.
104
145
  syncChainIdFromBilling(raw.chain_id);
146
+ syncDataEdgeAddressFromBilling(raw.data_edge_address);
105
147
  return raw;
106
148
  } catch {
107
149
  return null;
@@ -121,7 +163,9 @@ export function writeBillingCache(cache: BillingCache): void {
121
163
  } catch {
122
164
  // Best-effort — don't block on cache write failure.
123
165
  }
124
- // Sync chain override AFTER the write so in-process UserOp signing picks
125
- // up the correct chain immediately, even if the disk write failed.
166
+ // Sync chain + DataEdge overrides AFTER the write so in-process UserOp
167
+ // signing + subgraph reads pick up the correct chain and contract
168
+ // immediately, even if the disk write failed.
126
169
  syncChainIdFromBilling(cache.chain_id);
170
+ syncDataEdgeAddressFromBilling(cache.data_edge_address);
127
171
  }
package/config.ts CHANGED
@@ -122,6 +122,39 @@ export function __resetChainIdOverrideForTests(): void {
122
122
  _chainIdOverride = null;
123
123
  }
124
124
 
125
+ /**
126
+ * Runtime override for the DataEdge contract address, set after the relay
127
+ * billing response is read. The relay returns an authoritative
128
+ * `data_edge_address` in `/v1/billing/status` (staging routes to the isolated
129
+ * staging DataEdge, production to the prod DataEdge). Chain-aware clients MUST
130
+ * consume it verbatim — otherwise writes mine on the WASM-baked prod DataEdge
131
+ * while reads hit the relay's subgraph, so recall silently returns empty
132
+ * against staging (#460). Mirrors `_chainIdOverride`.
133
+ *
134
+ * `null` means "no billing override" → fall through to the WASM default. The
135
+ * explicit operator env override (`TOTALRECLAW_DATA_EDGE_ADDRESS`, exposed as
136
+ * `CONFIG.dataEdgeAddress`) always wins over this.
137
+ */
138
+ let _dataEdgeAddressOverride: string | null = null;
139
+
140
+ export function setDataEdgeAddressOverride(address: string | null): void {
141
+ _dataEdgeAddressOverride = address;
142
+ }
143
+
144
+ /**
145
+ * Read the billing-sourced DataEdge override (or `''` when unset). Used by
146
+ * `getSubgraphConfig` as the middle term of the env → billing → WASM-default
147
+ * resolution order (#460).
148
+ */
149
+ export function getDataEdgeAddressOverride(): string {
150
+ return _dataEdgeAddressOverride ?? '';
151
+ }
152
+
153
+ /** Reset the DataEdge override — used by tests. */
154
+ export function __resetDataEdgeAddressOverrideForTests(): void {
155
+ _dataEdgeAddressOverride = null;
156
+ }
157
+
125
158
  export const CONFIG = {
126
159
  // Core — recoveryPhrase reads from override first, then env var.
127
160
  // Use getRecoveryPhrase() for dynamic access; this property is for
@@ -235,7 +268,14 @@ export const CONFIG = {
235
268
  get chainId(): number {
236
269
  return _chainIdOverride ?? 100;
237
270
  },
238
- dataEdgeAddress: process.env.TOTALRECLAW_DATA_EDGE_ADDRESS || '',
271
+ // Explicit operator env override for the DataEdge contract. Stays env-only:
272
+ // it is the FIRST term of `getSubgraphConfig`'s env → billing → WASM-default
273
+ // resolution order, so it wins over the relay's authoritative
274
+ // `data_edge_address` (see `getDataEdgeAddressOverride` / #460). Getter, not
275
+ // a literal, so it reflects the live env rather than freezing at module load.
276
+ get dataEdgeAddress(): string {
277
+ return process.env.TOTALRECLAW_DATA_EDGE_ADDRESS || '';
278
+ },
239
279
  entryPointAddress: process.env.TOTALRECLAW_ENTRYPOINT_ADDRESS || '',
240
280
  rpcUrl: process.env.TOTALRECLAW_RPC_URL || '',
241
281
 
@@ -14,6 +14,9 @@
14
14
  * - keeps the chain-id override in sync with the relay's authoritative
15
15
  * `chain_id` (after ops-1 both tiers are on Gnosis 100; the client consumes
16
16
  * the relay value verbatim — see `syncChainIdFromBilling`)
17
+ * - keeps the DataEdge-address override in sync with the relay's
18
+ * authoritative `data_edge_address` (staging vs prod contract; consumed
19
+ * verbatim — see `syncDataEdgeAddressFromBilling`, #460)
17
20
  * - does NOT import anything that performs outbound I/O
18
21
  *
19
22
  * Do NOT add any outbound-request call to this file — a single match for
@@ -23,7 +26,9 @@
23
26
  */
24
27
  import fs from 'node:fs';
25
28
  import path from 'node:path';
26
- import { CONFIG, setChainIdOverride } from './config.js';
29
+ import { CONFIG, setChainIdOverride, setDataEdgeAddressOverride } from './config.js';
30
+ /** A plausible EVM address — anything else from billing is ignored (#460). */
31
+ const DATA_EDGE_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
27
32
  // ---------------------------------------------------------------------------
28
33
  // Constants
29
34
  // ---------------------------------------------------------------------------
@@ -51,6 +56,30 @@ export function syncChainIdFromBilling(chainId) {
51
56
  setChainIdOverride(typeof chainId === 'number' && Number.isFinite(chainId) ? chainId : 100);
52
57
  }
53
58
  // ---------------------------------------------------------------------------
59
+ // DataEdge-address sync
60
+ // ---------------------------------------------------------------------------
61
+ /**
62
+ * Apply the relay's authoritative `data_edge_address` to the runtime DataEdge
63
+ * override. Mirrors `syncChainIdFromBilling`.
64
+ *
65
+ * The relay routes each environment to its own DataEdge (staging is on-chain
66
+ * isolated). If the client ignores this and uses the WASM-baked default (the
67
+ * PROD DataEdge), writes against the staging relay mine on the prod contract
68
+ * while reads come from the staging subgraph → empty recall + phantom
69
+ * "stored=N" success (#460).
70
+ *
71
+ * A missing / malformed `data_edge_address` (older relay, partial payload,
72
+ * junk) clears the override (`null`) so resolution falls through to the WASM
73
+ * default — never a stale value. Only a plausible address
74
+ * (`0x` + 40 hex) is honored. Called from `readBillingCache` and
75
+ * `writeBillingCache` so every cache read or write keeps the override in sync.
76
+ * Idempotent. The explicit env override (`TOTALRECLAW_DATA_EDGE_ADDRESS`)
77
+ * still wins — it is the first term in `getSubgraphConfig`.
78
+ */
79
+ export function syncDataEdgeAddressFromBilling(address) {
80
+ setDataEdgeAddressOverride(typeof address === 'string' && DATA_EDGE_ADDRESS_RE.test(address) ? address : null);
81
+ }
82
+ // ---------------------------------------------------------------------------
54
83
  // Read / write
55
84
  // ---------------------------------------------------------------------------
56
85
  /**
@@ -68,9 +97,10 @@ export function readBillingCache() {
68
97
  const raw = JSON.parse(fs.readFileSync(BILLING_CACHE_PATH, 'utf-8'));
69
98
  if (!raw.checked_at || Date.now() - raw.checked_at > BILLING_CACHE_TTL)
70
99
  return null;
71
- // Keep chain override in sync with the persisted authoritative chain_id
72
- // across process restarts.
100
+ // Keep chain + DataEdge overrides in sync with the persisted authoritative
101
+ // values across process restarts.
73
102
  syncChainIdFromBilling(raw.chain_id);
103
+ syncDataEdgeAddressFromBilling(raw.data_edge_address);
74
104
  return raw;
75
105
  }
76
106
  catch {
@@ -92,7 +122,9 @@ export function writeBillingCache(cache) {
92
122
  catch {
93
123
  // Best-effort — don't block on cache write failure.
94
124
  }
95
- // Sync chain override AFTER the write so in-process UserOp signing picks
96
- // up the correct chain immediately, even if the disk write failed.
125
+ // Sync chain + DataEdge overrides AFTER the write so in-process UserOp
126
+ // signing + subgraph reads pick up the correct chain and contract
127
+ // immediately, even if the disk write failed.
97
128
  syncChainIdFromBilling(cache.chain_id);
129
+ syncDataEdgeAddressFromBilling(cache.data_edge_address);
98
130
  }
package/dist/config.js CHANGED
@@ -107,6 +107,35 @@ export function setChainIdOverride(chainId) {
107
107
  export function __resetChainIdOverrideForTests() {
108
108
  _chainIdOverride = null;
109
109
  }
110
+ /**
111
+ * Runtime override for the DataEdge contract address, set after the relay
112
+ * billing response is read. The relay returns an authoritative
113
+ * `data_edge_address` in `/v1/billing/status` (staging routes to the isolated
114
+ * staging DataEdge, production to the prod DataEdge). Chain-aware clients MUST
115
+ * consume it verbatim — otherwise writes mine on the WASM-baked prod DataEdge
116
+ * while reads hit the relay's subgraph, so recall silently returns empty
117
+ * against staging (#460). Mirrors `_chainIdOverride`.
118
+ *
119
+ * `null` means "no billing override" → fall through to the WASM default. The
120
+ * explicit operator env override (`TOTALRECLAW_DATA_EDGE_ADDRESS`, exposed as
121
+ * `CONFIG.dataEdgeAddress`) always wins over this.
122
+ */
123
+ let _dataEdgeAddressOverride = null;
124
+ export function setDataEdgeAddressOverride(address) {
125
+ _dataEdgeAddressOverride = address;
126
+ }
127
+ /**
128
+ * Read the billing-sourced DataEdge override (or `''` when unset). Used by
129
+ * `getSubgraphConfig` as the middle term of the env → billing → WASM-default
130
+ * resolution order (#460).
131
+ */
132
+ export function getDataEdgeAddressOverride() {
133
+ return _dataEdgeAddressOverride ?? '';
134
+ }
135
+ /** Reset the DataEdge override — used by tests. */
136
+ export function __resetDataEdgeAddressOverrideForTests() {
137
+ _dataEdgeAddressOverride = null;
138
+ }
110
139
  export const CONFIG = {
111
140
  // Core — recoveryPhrase reads from override first, then env var.
112
141
  // Use getRecoveryPhrase() for dynamic access; this property is for
@@ -216,7 +245,14 @@ export const CONFIG = {
216
245
  get chainId() {
217
246
  return _chainIdOverride ?? 100;
218
247
  },
219
- dataEdgeAddress: process.env.TOTALRECLAW_DATA_EDGE_ADDRESS || '',
248
+ // Explicit operator env override for the DataEdge contract. Stays env-only:
249
+ // it is the FIRST term of `getSubgraphConfig`'s env → billing → WASM-default
250
+ // resolution order, so it wins over the relay's authoritative
251
+ // `data_edge_address` (see `getDataEdgeAddressOverride` / #460). Getter, not
252
+ // a literal, so it reflects the live env rather than freezing at module load.
253
+ get dataEdgeAddress() {
254
+ return process.env.TOTALRECLAW_DATA_EDGE_ADDRESS || '';
255
+ },
220
256
  entryPointAddress: process.env.TOTALRECLAW_ENTRYPOINT_ADDRESS || '',
221
257
  rpcUrl: process.env.TOTALRECLAW_RPC_URL || '',
222
258
  // 3.3.3-rc.1 (issue #187 — ONNX decouple): kill switch for the
package/dist/index.js CHANGED
@@ -735,14 +735,15 @@ async function initialize(logger) {
735
735
  const tier = billingData.tier;
736
736
  const expiresAt = billingData.expires_at;
737
737
  // Populate billing cache for future use. Copy the relay's
738
- // authoritative chain_id so it lands on disk and drives the runtime
739
- // chain override verbatim (#402).
738
+ // authoritative chain_id + data_edge_address so they land on disk and
739
+ // drive the runtime chain + DataEdge overrides verbatim (#402, #460).
740
740
  writeBillingCache({
741
741
  tier: tier || 'free',
742
742
  free_writes_used: billingData.free_writes_used ?? 0,
743
743
  free_writes_limit: billingData.free_writes_limit ?? 0,
744
744
  features: billingData.features,
745
745
  chain_id: billingData.chain_id,
746
+ data_edge_address: billingData.data_edge_address,
746
747
  checked_at: Date.now(),
747
748
  });
748
749
  if (tier === 'pro' && expiresAt) {
@@ -4083,6 +4084,8 @@ const plugin = {
4083
4084
  features: billingData.features,
4084
4085
  // Relay's authoritative chain_id → drives the chain override verbatim (#402).
4085
4086
  chain_id: billingData.chain_id,
4087
+ // Relay's authoritative data_edge_address → drives the DataEdge override verbatim (#460).
4088
+ data_edge_address: billingData.data_edge_address,
4086
4089
  checked_at: Date.now(),
4087
4090
  };
4088
4091
  writeBillingCache(cache);
@@ -21,7 +21,7 @@ function getWasm() {
21
21
  _wasm = requireWasm('@totalreclaw/core');
22
22
  return _wasm;
23
23
  }
24
- import { CONFIG } from './config.js';
24
+ import { CONFIG, getDataEdgeAddressOverride } from './config.js';
25
25
  import { buildRelayHeaders } from './relay-headers.js';
26
26
  import { rpcRequest, rpcWithRetry } from './relay.js';
27
27
  import { signUserOp } from './vault-crypto.js';
@@ -712,7 +712,10 @@ export function isSubgraphMode() {
712
712
  * - TOTALRECLAW_SELF_HOSTED -- set "true" to use self-hosted server (default: managed service)
713
713
  *
714
714
  * Chain ID is no longer configurable via env — it is auto-detected from the
715
- * relay billing response (free = Base Sepolia, Pro = Gnosis mainnet).
715
+ * relay billing response (post-ops-1 both tiers are on Gnosis mainnet, chain
716
+ * 100). The DataEdge contract address is likewise consumed from the relay's
717
+ * authoritative `data_edge_address` (env override still wins, WASM default is
718
+ * the final fallback — see the `dataEdgeAddress` resolution below, #460).
716
719
  */
717
720
  export function getSubgraphConfig() {
718
721
  return {
@@ -721,7 +724,12 @@ export function getSubgraphConfig() {
721
724
  mnemonic: CONFIG.recoveryPhrase,
722
725
  cachePath: CONFIG.cachePath,
723
726
  chainId: CONFIG.chainId,
724
- dataEdgeAddress: CONFIG.dataEdgeAddress || getWasm().getDataEdgeAddress(),
727
+ // Resolution order (#460): explicit operator env override → relay billing
728
+ // `data_edge_address` (verbatim) → WASM-baked default. The relay routes
729
+ // each environment to its own DataEdge (staging is on-chain isolated), so
730
+ // consuming its value keeps writes + reads on the same contract; without
731
+ // the middle term staging writes mined on the prod default → empty recall.
732
+ dataEdgeAddress: CONFIG.dataEdgeAddress || getDataEdgeAddressOverride() || getWasm().getDataEdgeAddress(),
725
733
  entryPointAddress: CONFIG.entryPointAddress || getWasm().getEntryPointAddress(),
726
734
  rpcUrl: CONFIG.rpcUrl || undefined,
727
735
  };
package/dist/tr-cli.js CHANGED
@@ -51,7 +51,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
51
51
  // Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
52
52
  // Do not edit by hand — running tests will catch drift but the publish workflow
53
53
  // rewrites this constant at the start of every npm/ClawHub publish.
54
- const PLUGIN_VERSION = '3.3.12-rc.21';
54
+ const PLUGIN_VERSION = '3.3.12-rc.22';
55
55
  function die(msg, code = 1) {
56
56
  process.stderr.write(`tr: ${msg}\n`);
57
57
  process.exit(code);
package/index.ts CHANGED
@@ -1007,14 +1007,15 @@ async function initialize(logger: OpenClawPluginApi['logger']): Promise<void> {
1007
1007
  const tier = billingData.tier as string;
1008
1008
  const expiresAt = billingData.expires_at as string | undefined;
1009
1009
  // Populate billing cache for future use. Copy the relay's
1010
- // authoritative chain_id so it lands on disk and drives the runtime
1011
- // chain override verbatim (#402).
1010
+ // authoritative chain_id + data_edge_address so they land on disk and
1011
+ // drive the runtime chain + DataEdge overrides verbatim (#402, #460).
1012
1012
  writeBillingCache({
1013
1013
  tier: tier || 'free',
1014
1014
  free_writes_used: (billingData.free_writes_used as number) ?? 0,
1015
1015
  free_writes_limit: (billingData.free_writes_limit as number) ?? 0,
1016
1016
  features: billingData.features as BillingCache['features'] | undefined,
1017
1017
  chain_id: billingData.chain_id as number | undefined,
1018
+ data_edge_address: billingData.data_edge_address as string | undefined,
1018
1019
  checked_at: Date.now(),
1019
1020
  });
1020
1021
  if (tier === 'pro' && expiresAt) {
@@ -4785,6 +4786,8 @@ const plugin = {
4785
4786
  features: billingData.features as BillingCache['features'] | undefined,
4786
4787
  // Relay's authoritative chain_id → drives the chain override verbatim (#402).
4787
4788
  chain_id: billingData.chain_id as number | undefined,
4789
+ // Relay's authoritative data_edge_address → drives the DataEdge override verbatim (#460).
4790
+ data_edge_address: billingData.data_edge_address as string | undefined,
4788
4791
  checked_at: Date.now(),
4789
4792
  };
4790
4793
  writeBillingCache(cache);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@totalreclaw/totalreclaw",
3
- "version": "3.3.12-rc.21",
3
+ "version": "3.3.12-rc.22",
4
4
  "description": "End-to-end encrypted, agent-portable memory for OpenClaw and any LLM-agent runtime. XChaCha20-Poly1305 with protobuf v4 + on-chain Memory Taxonomy v1 (claim / preference / directive / commitment / episode / summary).",
5
5
  "type": "module",
6
6
  "keywords": [
package/skill.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "totalreclaw",
3
- "version": "3.3.12-rc.21",
3
+ "version": "3.3.12-rc.22",
4
4
  "description": "End-to-end encrypted memory for AI agents — portable, yours forever. XChaCha20-Poly1305 E2EE: server never sees plaintext.",
5
5
  "author": "TotalReclaw Team",
6
6
  "license": "MIT",
package/subgraph-store.ts CHANGED
@@ -21,7 +21,7 @@ function getWasm() {
21
21
  if (!_wasm) _wasm = requireWasm('@totalreclaw/core');
22
22
  return _wasm;
23
23
  }
24
- import { CONFIG } from './config.js';
24
+ import { CONFIG, getDataEdgeAddressOverride } from './config.js';
25
25
  import { buildRelayHeaders } from './relay-headers.js';
26
26
  import { rpcRequest, rpcWithRetry } from './relay.js';
27
27
  import { signUserOp } from './vault-crypto.js';
@@ -844,7 +844,10 @@ export function isSubgraphMode(): boolean {
844
844
  * - TOTALRECLAW_SELF_HOSTED -- set "true" to use self-hosted server (default: managed service)
845
845
  *
846
846
  * Chain ID is no longer configurable via env — it is auto-detected from the
847
- * relay billing response (free = Base Sepolia, Pro = Gnosis mainnet).
847
+ * relay billing response (post-ops-1 both tiers are on Gnosis mainnet, chain
848
+ * 100). The DataEdge contract address is likewise consumed from the relay's
849
+ * authoritative `data_edge_address` (env override still wins, WASM default is
850
+ * the final fallback — see the `dataEdgeAddress` resolution below, #460).
848
851
  */
849
852
  export function getSubgraphConfig(): SubgraphStoreConfig {
850
853
  return {
@@ -853,7 +856,12 @@ export function getSubgraphConfig(): SubgraphStoreConfig {
853
856
  mnemonic: CONFIG.recoveryPhrase,
854
857
  cachePath: CONFIG.cachePath,
855
858
  chainId: CONFIG.chainId,
856
- dataEdgeAddress: CONFIG.dataEdgeAddress || getWasm().getDataEdgeAddress(),
859
+ // Resolution order (#460): explicit operator env override → relay billing
860
+ // `data_edge_address` (verbatim) → WASM-baked default. The relay routes
861
+ // each environment to its own DataEdge (staging is on-chain isolated), so
862
+ // consuming its value keeps writes + reads on the same contract; without
863
+ // the middle term staging writes mined on the prod default → empty recall.
864
+ dataEdgeAddress: CONFIG.dataEdgeAddress || getDataEdgeAddressOverride() || getWasm().getDataEdgeAddress(),
857
865
  entryPointAddress: CONFIG.entryPointAddress || getWasm().getEntryPointAddress(),
858
866
  rpcUrl: CONFIG.rpcUrl || undefined,
859
867
  };
package/tr-cli.ts CHANGED
@@ -68,7 +68,7 @@ const STATE_PATH = CONFIG.onboardingStatePath;
68
68
  // Auto-synced by skill/scripts/sync-version.mjs from skill/plugin/package.json::version.
69
69
  // Do not edit by hand — running tests will catch drift but the publish workflow
70
70
  // rewrites this constant at the start of every npm/ClawHub publish.
71
- const PLUGIN_VERSION = '3.3.12-rc.21';
71
+ const PLUGIN_VERSION = '3.3.12-rc.22';
72
72
 
73
73
  function die(msg: string, code = 1): never {
74
74
  process.stderr.write(`tr: ${msg}\n`);