@blindmarket/mcp-server 0.3.6 → 0.4.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/README.md CHANGED
@@ -26,11 +26,11 @@ Environment:
26
26
  | `BLINDMARKET_PRIVATE_KEY` | 0G: yes · Base: for private briefs | On **0G** this wallet funds escrow in native 0G, pays gas, and signs `submitEvidence` for `complete_task`. On **Base** nothing is *signed* locally — the relay does that — but this key is still the executor's **decryption identity**: `fetch_brief` unwraps a private brief with it, so the pubkey you pass to `register_as_executor` must be the one `wallet_status` reports as `executorPublicKey`. Omit for read-only use or Base tasks that are all public. |
27
27
  | `BLINDMARKET_API_BASE` | no | Default `https://api.blindmarket.xyz` |
28
28
  | `BLINDMARKET_RPC_URL` | no | 0G RPC for the local wallet. Default `https://evmrpc.0g.ai` |
29
- | `BLINDMARKET_SETTLEMENT` | no | Force `0g` or `base`. Default: ask the backend (`GET /health/bridge`) which chain escrow settles on — `base` whenever it has a Base escrow and a Base marketplace signer configured. `base` fails loudly if the backend is not actually in Base mode. |
30
- | `BLINDMARKET_BASE_ESCROW_ADDRESS` | with forced `base` | The escrow the backend builds against, when `/health/bridge` cannot confirm it. Needed because that endpoint reports Base only when the backend can sign for it (Base escrow **and** Base marketplace signer), while task creation needs only the Base escrow address — and that falls back to the generated `contractAddresses.ts`, so a backend with an empty Base `.env` still builds Base transactions. Only read when `BLINDMARKET_SETTLEMENT=base`. |
29
+ | `BLINDMARKET_SETTLEMENT` | no | A chain key to require (`0g`, `base`, …). Default: ask the backend (`GET /health/bridge`). A backend that names its posting chain (`postingChain`) is followed: new tasks are escrowed there, and that chain's settlement token picks how you pay — an ERC-20 the relay serves (USDC on Base) through the relay, native 0G from the local wallet, anything else refused with `UNSUPPORTED_SETTLEMENT`. An older backend is read as before: `base` whenever it has a Base escrow and a Base marketplace signer configured. `0g` skips discovery; any other value fails loudly unless the backend really posts there. |
30
+ | `BLINDMARKET_BASE_ESCROW_ADDRESS` | with forced `base`, older backends | The escrow the backend builds against, when an older backend's `/health/bridge` cannot confirm it. Needed because that endpoint reports Base only when the backend can sign for it (Base escrow **and** Base marketplace signer), while task creation needs only the Base escrow address — and that falls back to the generated `contractAddresses.ts`, so a backend with an empty Base `.env` still builds Base transactions. Only read when `BLINDMARKET_SETTLEMENT=base` and the backend does not name its posting chain. |
31
31
  | `BLINDMARKET_BASE_CHAIN_ID` | no | Chain for that override. Default `84532` (Base Sepolia). |
32
- | `BLINDMARKET_BASE_RPC_URL` | no | Read-only Base RPC for allowance/balance checks and receipt polling. Default by chain: `https://sepolia.base.org` (84532) / `https://mainnet.base.org` (8453). |
33
- | `BLINDMARKET_USDC_ADDRESS` | no | Override the USDC address if the backend reports a Base chain not listed in `settlement.ts`. |
32
+ | `BLINDMARKET_BASE_RPC_URL` | no | Read-only Base RPC for allowance/balance checks and receipt polling. Default by chain: `https://sepolia.base.org` (84532) / `https://mainnet.base.org` (8453). Another relay chain `<key>` reads `BLINDMARKET_<KEY>_RPC_URL` and has no default. |
33
+ | `BLINDMARKET_USDC_ADDRESS` | no | Older backends: override the USDC address if the backend reports a Base chain not listed in `settlement.ts`. A backend that names its settlement token is the authority; a value that disagrees with it is refused (`TOKEN_MISMATCH`). |
34
34
 
35
35
  How a spend is paid, by settlement mode (`wallet_status` shows which you are in):
36
36
 
@@ -44,7 +44,11 @@ How a spend is paid, by settlement mode (`wallet_status` shows which you are in)
44
44
 
45
45
  **What an `sk_` key can do on Base — read this before putting one in a config file.** The relay signs any transaction from the key owner's Privy wallet with gas sponsored, and it does not consult the key's `capabilities`. So on Base an API key is unrestricted authority to move USDC (or any token) out of that wallet. Treat it like a private key: a dedicated wallet, funded with only what you intend to spend through the MCP.
46
46
 
47
- Discovery is a hint, not a proof: `/health/bridge` reports Base only when the backend has a Base marketplace signer, while task creation routes on `BASE_ESCROW_ADDRESS` alone. Every send therefore checks the unsigned tx targets the escrow the current mode expects and refuses with `ESCROW_MISMATCH` otherwise — that check is what prevents native 0G value being sent to a Base address.
47
+ Discovery is a hint, not a proof. On an older backend `/health/bridge` reports Base only when the backend has a Base marketplace signer, while task creation routes on `BASE_ESCROW_ADDRESS` alone; a backend that names its posting chain removes that gap, but not every way a task can live on another chain. Every send therefore checks the unsigned tx targets the escrow the current mode expects and refuses with `ESCROW_MISMATCH` otherwise — that check is what prevents native 0G value being sent to a Base address.
48
+
49
+ `register_as_executor` and `create_agent` declare exactly one chain in `supportedChains`: the one this process settles on (`wallet_status` shows it), because `complete_task` delivers only there. A backend that filters by it then offers you only tasks you can complete, and refuses bids and `/accept` (409 `CHAIN_UNSUPPORTED`) elsewhere — including tasks indexed before chains were recorded, which need both `0g` and `base` declared. If the chain cannot be learned, registration is refused rather than sent without one.
50
+
51
+ `BLINDMARKET_SETTLEMENT` may name any chain the backend has an escrow on, not only the one it posts on: that is how you deliver, cancel or reclaim a task left on a chain the backend has since stopped posting on. `post_task` and `rent_service` refuse there (`NOT_POSTING_CHAIN`), because new escrow is funded only on the posting chain. On 0G the local wallet must be on the backend's 0G chain id (`CHAIN_MISMATCH` otherwise) — checked only when the backend names its chains and discovery runs: `BLINDMARKET_SETTLEMENT=0g` skips discovery, and an older backend does not say.
48
52
 
49
53
  ### Base: what the wallet and key must be
50
54
 
@@ -69,7 +73,7 @@ claude mcp add blindmarket \
69
73
  -- node /path/to/BlindBounty/mcp/dist/index.js
70
74
  ```
71
75
 
72
- (`BLINDMARKET_BASE_ESCROW_ADDRESS` is only needed while `/health/bridge` cannot confirm Base — see the env table. Use the mainnet escrow once deployed.)
76
+ (`BLINDMARKET_SETTLEMENT` and `BLINDMARKET_BASE_ESCROW_ADDRESS` are only needed while an older backend's `/health/bridge` cannot confirm Base — see the env table. Use the mainnet escrow once deployed.)
73
77
 
74
78
  **Claude Code — 0G (legacy, local wallet)**
75
79
 
@@ -133,8 +137,9 @@ live one. It signs `submitEvidence` **locally** (no relay): it needs
133
137
  `BLINDMARKET_PRIVATE_KEY` to be the wallet that owns `BLINDMARKET_API_KEY`, and
134
138
  an RPC for the settlement chain (`BLINDMARKET_RPC_URL` for 0G,
135
139
  `BLINDMARKET_BASE_RPC_URL` for Base — there is no default for Base, and tasks
136
- on a chain without an RPC are skipped by the runtime itself: the backend stores
137
- the declared `supportedChains` but does not filter offers or `/accept` by it).
140
+ on a chain without an RPC are skipped by the runtime itself: older backends
141
+ store the declared `supportedChains` without filtering offers or `/accept` by
142
+ it).
138
143
  Without `BLINDMARKET_PRIVATE_KEY` the runtime refuses to start (SDK 0.6.0) —
139
144
  it no longer registers a throwaway wallet. `BLINDMARKET_EXPERIMENTAL_RUNTIME=true`
140
145
  enables it. The maintained way to EARN is still a platform agent deployed in
package/dist/index.js CHANGED
@@ -35,11 +35,15 @@ const server = new McpServer({
35
35
  // rent/post through the CURRENT encrypted flow (see rent.ts). Registered even
36
36
  // without a wallet so tools/list is stable; spends fail cleanly with NO_WALLET.
37
37
  const walletCtx = loadWallet();
38
- // Register all marketplace tools
39
- registerMarketTools(server, bb, walletCtx);
38
+ // Register all marketplace tools. They register first (tools/list order is
39
+ // unchanged), but register_as_executor/create_agent need rent.ts's settlement
40
+ // resolver, which is created just below — hence the late-bound getter.
41
+ let resolveSettlement = null;
42
+ registerMarketTools(server, bb, walletCtx, () => resolveSettlement());
40
43
  // rent.ts owns settlement discovery (which chain escrow settles on, and so
41
44
  // whether the local wallet or the Privy relay pays); wallet_status reports it.
42
45
  const { settlement } = registerRentTools(server, cfg, walletCtx);
46
+ resolveSettlement = settlement;
43
47
  registerWalletTools(server, walletCtx, settlement);
44
48
  // Executor runtime tools (runtime_start/stop/…) are GATED OFF by default.
45
49
  // The three bugs this gate used to cite (wrappedKey parsed as a record, blob
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rent.d.ts","sourceRoot":"","sources":["../src/rent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAiD,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAmDjG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG;IAAE,UAAU,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,CAgqC3I"}
1
+ {"version":3,"file":"rent.d.ts","sourceRoot":"","sources":["../src/rent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EAAkD,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAmDlG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG;IAAE,UAAU,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,CAwuC3I"}
package/dist/rent.js CHANGED
@@ -95,8 +95,9 @@ export function registerRentTools(server, cfg, walletCtx) {
95
95
  }
96
96
  const settlement = createSettlementResolver({ apiBase: cfg.apiBase ?? 'https://api.blindmarket.xyz', api });
97
97
  /** How this process pays. On 0G that is the local wallet, which must exist.
98
- * On Base nothing signs locally — the relay signs from the API key's owner
99
- * wallet — so a missing BLINDMARKET_PRIVATE_KEY is not an error there. */
98
+ * On a relay chain (Base) nothing signs locally — the relay signs from the
99
+ * API key's owner wallet — so a missing BLINDMARKET_PRIVATE_KEY is not an
100
+ * error there. */
100
101
  async function requireFunding() {
101
102
  let s;
102
103
  try {
@@ -105,26 +106,44 @@ export function registerRentTools(server, cfg, walletCtx) {
105
106
  catch (err) {
106
107
  return { error: fail(err.code ?? 'SETTLEMENT_UNKNOWN', err.message) };
107
108
  }
108
- if (s.mode === 'base')
109
+ if (s.payment === 'relay-erc20')
109
110
  return { s, payFrom: s.payFrom };
110
111
  if (!walletCtx) {
111
112
  return { error: fail('NO_WALLET', 'Spending on 0G needs a local funding wallet — set BLINDMARKET_PRIVATE_KEY (see wallet_status)') };
112
113
  }
114
+ // The escrow address is compared before every send, but not the chain:
115
+ // a testnet backend's escrow address, paid on the mainnet RPC, is some
116
+ // other account there. A backend that names its 0G chain id settles it.
117
+ if (s.chainId !== undefined && walletCtx.chainId !== s.chainId) {
118
+ return {
119
+ error: fail('CHAIN_MISMATCH', `The backend settles 0G on chain ${s.chainId}, but BLINDMARKET_PRIVATE_KEY signs on chain ${walletCtx.chainId} (BLINDMARKET_RPC_URL / BLINDMARKET_CHAIN_ID). Native value sent there would not reach this backend's escrow. Point both at chain ${s.chainId}.`),
120
+ };
121
+ }
113
122
  return { s, payFrom: walletCtx.wallet.address };
114
123
  }
124
+ /** A new escrow is funded where POST /api/v1/tasks builds: the backend's
125
+ * posting chain. A process forced onto another chain (to finish or refund
126
+ * tasks already there) cannot post. Unknown on an older backend. */
127
+ function notPostingChain(s) {
128
+ if (s.postingChain === undefined || s.mode === s.postingChain)
129
+ return null;
130
+ const e = new Error(`This process settles on ${s.mode} (BLINDMARKET_SETTLEMENT), but the backend posts new tasks on ${s.postingChain}, so a new escrow can only be funded there. Unset BLINDMARKET_SETTLEMENT (or set it to ${s.postingChain}) to post; ${s.mode} stays usable for tasks already on it.`);
131
+ e.code = 'NOT_POSTING_CHAIN';
132
+ return e;
133
+ }
115
134
  const ERC20 = new Interface([
116
135
  'function allowance(address owner, address spender) view returns (uint256)',
117
136
  'function approve(address spender, uint256 amount) returns (bool)',
118
137
  'function balanceOf(address owner) view returns (uint256)',
119
138
  ]);
120
- function usdc(s) {
121
- return new Contract(s.usdcAddress, ERC20, s.provider);
139
+ function settlementToken(s) {
140
+ return new Contract(s.token.address, ERC20, s.provider);
122
141
  }
123
142
  /** Spendable balance of whoever pays, in the settlement token's units. */
124
143
  async function payFromBalance(s, payFrom) {
125
144
  try {
126
- const raw = s.mode === 'base'
127
- ? await usdc(s).balanceOf(payFrom)
145
+ const raw = s.payment === 'relay-erc20'
146
+ ? await settlementToken(s).balanceOf(payFrom)
128
147
  : await walletCtx.provider.getBalance(payFrom);
129
148
  return formatUnits(raw, s.decimals);
130
149
  }
@@ -136,19 +155,22 @@ export function registerRentTools(server, cfg, walletCtx) {
136
155
  * the task, and the tx carries no chainId. Discovery is only a hint about
137
156
  * that chain (see settlement.ts). So before broadcasting, check the tx
138
157
  * targets the escrow THIS mode expects — otherwise native value goes to a
139
- * Base address on the 0G RPC, or a 0G refund is relayed onto Base. A
140
- * mismatch also drops the cached mode so the next call re-asks. */
158
+ * relay chain's address on the 0G RPC, or a 0G refund is relayed onto
159
+ * another chain. A mismatch also drops the cached mode so the next call
160
+ * re-asks. */
141
161
  async function verifyTarget(s, to, what) {
142
162
  const target = String(to).toLowerCase();
143
- const expected = s.mode === 'base' ? s.escrowAddress : s.escrowAddress;
163
+ const expected = s.escrowAddress;
144
164
  if (expected) {
145
165
  if (target === expected.toLowerCase())
146
166
  return;
147
167
  settlement.invalidate();
148
168
  const e = new Error(`backend built ${what} for ${to} but this process is in ${s.mode} mode expecting escrow ${expected}. ` +
149
- (s.mode === 'base'
150
- ? 'This task is escrowed on 0G — handle it with BLINDMARKET_SETTLEMENT=0g and a local key, or from the web app.'
151
- : 'The backend is building Base transactions — re-run and discovery will re-check, or set BLINDMARKET_SETTLEMENT=base.'));
169
+ (s.escrowChains
170
+ ? `This task is escrowed on another chain — set BLINDMARKET_SETTLEMENT to the chain that holds it (this backend has escrows on ${s.escrowChains.join(', ')}; 0G needs a local key), or use the web app.`
171
+ : s.payment === 'relay-erc20'
172
+ ? 'This task is escrowed on 0G — handle it with BLINDMARKET_SETTLEMENT=0g and a local key, or from the web app.'
173
+ : 'The backend is building Base transactions — re-run and discovery will re-check, or set BLINDMARKET_SETTLEMENT=base.'));
152
174
  e.code = 'ESCROW_MISMATCH';
153
175
  throw e;
154
176
  }
@@ -214,17 +236,17 @@ export function registerRentTools(server, cfg, walletCtx) {
214
236
  e.code = 'TX_PENDING';
215
237
  throw e;
216
238
  }
217
- /** Base only: createTask pulls USDC via transferFrom, so the escrow needs an
218
- * allowance first. Confirmed by re-reading allowance() rather than by
219
- * receipt, which is what makes the user-op case decidable. */
239
+ /** Relay chains only: createTask pulls the ERC-20 via transferFrom, so the
240
+ * escrow needs an allowance first. Confirmed by re-reading allowance()
241
+ * rather than by receipt, which is what makes the user-op case decidable. */
220
242
  async function ensureAllowance(s, record) {
221
243
  const need = BigInt(record.amountWei);
222
- const token = usdc(s);
244
+ const token = settlementToken(s);
223
245
  if ((await token.allowance(s.payFrom, s.escrowAddress)) >= need)
224
246
  return;
225
247
  const approve = async () => {
226
248
  const data = ERC20.encodeFunctionData('approve', [s.escrowAddress, need]);
227
- const { hash } = await relaySend(s, { to: s.usdcAddress, data });
249
+ const { hash } = await relaySend(s, { to: s.token.address, data });
228
250
  // Persist BEFORE waiting: a crash here must resume into the poll below.
229
251
  updateSpend(record.idempotencyKey, { stage: 'approved', approveTxHash: hash });
230
252
  record.stage = 'approved';
@@ -252,19 +274,19 @@ export function registerRentTools(server, cfg, walletCtx) {
252
274
  await approve();
253
275
  if (await settled())
254
276
  return;
255
- const e = new Error(`USDC allowance still below ${formatUnits(need, s.decimals)} after two approves (last ${record.approveTxHash}) — check the relay wallet's USDC balance and retry with the same idempotencyKey`);
277
+ const e = new Error(`${s.symbol} allowance still below ${formatUnits(need, s.decimals)} after two approves (last ${record.approveTxHash}) — check the relay wallet's ${s.symbol} balance and retry with the same idempotencyKey`);
256
278
  e.code = 'APPROVE_PENDING';
257
279
  throw e;
258
280
  }
259
281
  /** Fund escrow + index — the shared tail of rent_service and post_task.
260
282
  * Resumable at every stage via the spend ledger.
261
283
  *
262
- * Two funding paths, chosen by settlement mode:
263
- * 0g — native value from the local wallet, signed and sent here.
264
- * base — USDC via transferFrom: approve first (ensureAllowance), then the
265
- * createTask the backend built, both through the Privy relay with
266
- * no local signing at all. The backend picks the escrow; we only
267
- * check it is the one we approved. */
284
+ * Two funding paths, chosen by `payment`:
285
+ * local-native — native 0G from the local wallet, signed and sent here.
286
+ * relay-erc20 — the ERC-20 (USDC on Base) via transferFrom: approve
287
+ * first (ensureAllowance), then the createTask the backend built,
288
+ * both through the Privy relay with no local signing at all. The
289
+ * backend picks the escrow; we only check it is the one we approved. */
268
290
  async function fundAndIndex(record) {
269
291
  const s = await settlement();
270
292
  let { txHash } = record;
@@ -277,11 +299,14 @@ export function registerRentTools(server, cfg, walletCtx) {
277
299
  throw e;
278
300
  }
279
301
  if (record.stage === 'created' || record.stage === 'approved') {
280
- if (s.mode === 'base')
302
+ const refused = notPostingChain(s);
303
+ if (refused)
304
+ throw refused;
305
+ if (s.payment === 'relay-erc20')
281
306
  await ensureAllowance(s, record);
282
- const { unsignedTx } = await api('POST', '/api/v1/tasks', {
307
+ const { unsignedTx, chain: builtChain, chainId: builtChainId } = await api('POST', '/api/v1/tasks', {
283
308
  taskHash: record.taskHash,
284
- token: s.mode === 'base' ? s.usdcAddress : ZERO_TOKEN,
309
+ token: s.payment === 'relay-erc20' ? s.token.address : ZERO_TOKEN,
285
310
  amount: record.amountWei,
286
311
  locationZone: 'global',
287
312
  duration: String(record.durationSecs ?? 3600),
@@ -292,10 +317,19 @@ export function registerRentTools(server, cfg, walletCtx) {
292
317
  rootHash: record.rootHash,
293
318
  wrappedKeys: record.privacy === 'public' ? undefined : record.wrappedKeys,
294
319
  });
295
- // Either branch: the tx must target the escrow this mode expects. On
296
- // Base that is also the escrow the allowance above was granted to.
320
+ // Either branch: the tx must target the escrow this mode expects. On a
321
+ // relay chain that is also the escrow the allowance above was granted to.
322
+ // A chain-aware backend also names the chain it built for (older ones
323
+ // return only the tx, and are checked by address alone, as before).
324
+ if (s.postingChain !== undefined && ((builtChain !== undefined && builtChain !== s.mode) ||
325
+ (builtChainId !== undefined && s.chainId !== undefined && Number(builtChainId) !== s.chainId))) {
326
+ settlement.invalidate();
327
+ const e = new Error(`backend built createTask for ${builtChain ?? '?'} (chain ${builtChainId ?? '?'}) but this process settles on ${s.mode} (chain ${s.chainId ?? '?'}) — re-run and discovery will re-check`);
328
+ e.code = 'ESCROW_MISMATCH';
329
+ throw e;
330
+ }
297
331
  await verifyTarget(s, unsignedTx.to, 'createTask');
298
- if (s.mode === 'base') {
332
+ if (s.payment === 'relay-erc20') {
299
333
  const { hash, isUserOp, gas } = await relaySend(s, { to: unsignedTx.to, data: unsignedTx.data });
300
334
  // Persist BEFORE waiting, same reasoning as the 0G branch below.
301
335
  updateSpend(record.idempotencyKey, { stage: 'funded', txHash: hash, isUserOp, gas });
@@ -310,6 +344,8 @@ export function registerRentTools(server, cfg, walletCtx) {
310
344
  data: unsignedTx.data,
311
345
  value: BigInt(record.amountWei),
312
346
  gasLimit: GAS_LIMIT,
347
+ // ethers refuses to send when its provider is on another chain.
348
+ ...(s.chainId !== undefined ? { chainId: s.chainId } : {}),
313
349
  });
314
350
  // Persist the tx hash BEFORE waiting: if we crash mid-confirmation the
315
351
  // resume path re-runs /tasks/index with this hash instead of re-funding.
@@ -393,6 +429,9 @@ export function registerRentTools(server, cfg, walletCtx) {
393
429
  return fail(err.code ?? 'RESUME_FAILED', err.message);
394
430
  }
395
431
  }
432
+ const offPosting = notPostingChain(s);
433
+ if (offPosting)
434
+ return fail(offPosting.code, offPosting.message);
396
435
  const service = await api('GET', `/api/v1/marketplace/services/${serviceId}`);
397
436
  const isPublic = privacy === 'public';
398
437
  if (!isPublic && !service.agent_public_key) {
@@ -405,10 +444,10 @@ export function registerRentTools(server, cfg, walletCtx) {
405
444
  // A service priced in 18-decimal units on a 6-decimal chain would quote
406
445
  // as a trillion USDC and relay an approve for it before createTask
407
446
  // failed. The backend converts such prices since migration 31, but an
408
- // older backend may still serve one. 1,000,000 USDC per call is far
447
+ // older backend may still serve one. 1,000,000 tokens per call is far
409
448
  // above any real listing — refuse.
410
- if (s.mode === 'base' && priceRaw > 1000000n * 10n ** 6n) {
411
- return fail('PRICE_UNITS_SUSPECT', `service ${serviceId} lists price_raw=${priceRaw} which is ${formatUnits(priceRaw, 6)} USDC — this looks like an 18-decimal 0G price on a USDC chain. Not sending. Re-list the service in USDC base units.`);
449
+ if (s.decimals < 18 && priceRaw > 1000000n * 10n ** BigInt(s.decimals)) {
450
+ return fail('PRICE_UNITS_SUSPECT', `service ${serviceId} lists price_raw=${priceRaw} which is ${formatUnits(priceRaw, s.decimals)} ${s.symbol} — this looks like an 18-decimal 0G price on a ${s.symbol} chain. Not sending. Re-list the service in ${s.symbol} base units.`);
412
451
  }
413
452
  const price = formatUnits(priceRaw, s.decimals);
414
453
  if (!confirm) {
@@ -467,7 +506,7 @@ export function registerRentTools(server, cfg, walletCtx) {
467
506
  requiredCapabilities: [],
468
507
  amountWei: String(service.price_raw),
469
508
  settlement: s.mode,
470
- token: s.mode === 'base' ? s.usdcAddress : ZERO_TOKEN,
509
+ token: s.payment === 'relay-erc20' ? s.token.address : ZERO_TOKEN,
471
510
  durationSecs: 3600,
472
511
  createdAt: new Date().toISOString(),
473
512
  updatedAt: new Date().toISOString(),
@@ -518,6 +557,9 @@ export function registerRentTools(server, cfg, walletCtx) {
518
557
  return fail(err.code ?? 'RESUME_FAILED', err.message);
519
558
  }
520
559
  }
560
+ const offPosting = notPostingChain(s);
561
+ if (offPosting)
562
+ return fail(offPosting.code, offPosting.message);
521
563
  const isPublic = privacy === 'public';
522
564
  const amountStr = amount ?? amount0G;
523
565
  if (!amountStr) {
@@ -595,7 +637,7 @@ export function registerRentTools(server, cfg, walletCtx) {
595
637
  requiredCapabilities: capabilities ?? [],
596
638
  amountWei: amountWei.toString(),
597
639
  settlement: s.mode,
598
- token: s.mode === 'base' ? s.usdcAddress : ZERO_TOKEN,
640
+ token: s.payment === 'relay-erc20' ? s.token.address : ZERO_TOKEN,
599
641
  durationSecs: durationSeconds ?? 86400,
600
642
  createdAt: new Date().toISOString(),
601
643
  updatedAt: new Date().toISOString(),
@@ -643,7 +685,8 @@ export function registerRentTools(server, cfg, walletCtx) {
643
685
  //
644
686
  // Chain note: the backend resolves which chain holds the task
645
687
  // (resolveTaskChainById) and builds the unsigned tx for it; this side only
646
- // decides who signs — the local wallet on 0G, the Privy relay on Base — and
688
+ // decides who signs — the local wallet on 0G, the Privy relay on a relay
689
+ // chain (Base) — and
647
690
  // verifies the tx targets the escrow that mode expects before sending
648
691
  // (verifyTarget). Task state is read from the chain the mode names, not
649
692
  // from GET /tasks/:id, which is bound to the 0G escrow.
@@ -653,32 +696,55 @@ export function registerRentTools(server, cfg, walletCtx) {
653
696
  }
654
697
  /** Resolve a task id-or-hash to its live on-chain state.
655
698
  *
656
- * GET /api/v1/tasks/:id accepts either form — but it reads the 0G escrow
657
- * only (escrowService.getTask is bound to the 0G contract), so on Base its
658
- * struct is for whatever 0G task happens to share the id. In Base mode we
659
- * use it just to turn a hash into an id, then read the struct from the
660
- * Base escrow ourselves over the read-only provider. */
699
+ * GET /api/v1/tasks/:id accepts either form — but older backends read the
700
+ * 0G escrow only (escrowService.getTask was bound to the 0G contract), so
701
+ * on a relay chain its struct is for whatever 0G task happens to share the
702
+ * id. On a relay chain we use it just to turn a hash into an id, then read
703
+ * the struct from that chain's escrow ourselves over the read-only
704
+ * provider. */
661
705
  async function loadTask(s, task) {
662
- if (s.mode !== 'base') {
663
- return api('GET', `/api/v1/tasks/${encodeURIComponent(task)}`);
706
+ /** The backend said the task is on another chain: its id means a different task here. */
707
+ const onOtherChain = (taskId, chain) => {
708
+ const label = s.chain === 'base' ? 'Base' : s.chain;
709
+ const e = new Error(`task ${taskId} is a ${chain} task, and this process settles on ${label} — handle it with BLINDMARKET_SETTLEMENT=${chain}`);
710
+ // The code names the chain; on Base it is the TASK_NOT_ON_BASE it always was.
711
+ e.code = `TASK_NOT_ON_${s.chain.toUpperCase().replace(/-/g, '_')}`;
712
+ return e;
713
+ };
714
+ if (s.payment !== 'relay-erc20') {
715
+ const detail = await api('GET', `/api/v1/tasks/${encodeURIComponent(task)}`);
716
+ if (detail.chain && detail.chain !== s.chain)
717
+ throw onOtherChain(detail.taskId, detail.chain);
718
+ return detail;
664
719
  }
665
- // Base: the escrow is the authority, and we can read it directly. Only ask
720
+ // Relay chain: the escrow is the authority, and we can read it directly. Only ask
666
721
  // the backend when we need a hash resolved to an id — that endpoint makes
667
722
  // several Redis round trips and is the slowest thing in this path, so a
668
723
  // numeric id must not pay for it.
669
724
  let taskId;
725
+ let backendChain;
670
726
  if (/^\d+$/.test(task)) {
671
727
  taskId = task;
672
728
  }
673
729
  else {
674
730
  const viaBackend = await api('GET', `/api/v1/tasks/${encodeURIComponent(task)}`);
675
731
  taskId = viaBackend.taskId;
732
+ backendChain = viaBackend.chain;
733
+ // The same id can exist on this chain's escrow too (another task of the
734
+ // same poster): reading it would act on the wrong task.
735
+ if (backendChain && backendChain !== s.chain)
736
+ throw onOtherChain(taskId, backendChain);
676
737
  }
677
738
  const escrow = new Contract(s.escrowAddress, ESCROW_READ_ABI, s.provider);
678
739
  const t = await escrow.getTask(BigInt(taskId));
679
740
  if (String(t.agent).toLowerCase() === ZERO_TOKEN) {
680
- const e = new Error(`task ${taskId} does not exist on the Base escrow ${s.escrowAddress} — it is probably a 0G task; handle it with BLINDMARKET_SETTLEMENT=0g`);
681
- e.code = 'TASK_NOT_ON_BASE';
741
+ const label = s.chain === 'base' ? 'Base' : s.chain;
742
+ const hint = s.escrowChains
743
+ ? `it is on another chain; set BLINDMARKET_SETTLEMENT to the one that holds it (this backend has escrows on ${s.escrowChains.join(', ')})`
744
+ : 'it is probably a 0G task; handle it with BLINDMARKET_SETTLEMENT=0g';
745
+ const e = new Error(`task ${taskId} does not exist on the ${label} escrow ${s.escrowAddress} — ${hint}`);
746
+ // The code names the chain; on Base it is the TASK_NOT_ON_BASE it always was.
747
+ e.code = `TASK_NOT_ON_${s.chain.toUpperCase().replace(/-/g, '_')}`;
682
748
  throw e;
683
749
  }
684
750
  return {
@@ -692,8 +758,8 @@ export function registerRentTools(server, cfg, walletCtx) {
692
758
  decimals: s.decimals,
693
759
  };
694
760
  }
695
- /** Amount formatted against the task's OWN decimals — a Base task settles in
696
- * USDC (6), not the wallet's native 18. */
761
+ /** Amount formatted against the task's OWN decimals — a relay-chain task
762
+ * settles in its ERC-20 (USDC: 6), not the wallet's native 18. */
697
763
  function refundAmount(detail) {
698
764
  return formatUnits(BigInt(detail.amount), detail.decimals ?? 18);
699
765
  }
@@ -714,7 +780,7 @@ export function registerRentTools(server, cfg, walletCtx) {
714
780
  }
715
781
  /** Broadcast the refund and wait for it — the shared tail of both tools.
716
782
  * Resumable: a record past 'created' waits on the tx it already saved.
717
- * Same two paths as fundAndIndex: local wallet on 0G, Privy relay on Base.
783
+ * Same two paths as fundAndIndex: local wallet on 0G, Privy relay on a relay chain.
718
784
  * The backend resolves which chain holds the task and builds the tx for
719
785
  * it; this only decides who signs. */
720
786
  async function sendRefund(record) {
@@ -731,10 +797,10 @@ export function registerRentTools(server, cfg, walletCtx) {
731
797
  const { unsignedTx } = await api('POST', `/api/v1/tasks/${taskId}/${route}`);
732
798
  // The backend resolves the chain that holds the task and builds for it;
733
799
  // the tx carries no chainId. If that chain is not the one this mode
734
- // broadcasts on, stop here — relaying a 0G refund onto Base lands on an
735
- // address with no escrow and burns the gas.
800
+ // broadcasts on, stop here — relaying a 0G refund onto another chain
801
+ // lands on an address with no escrow and burns the gas.
736
802
  await verifyTarget(s, unsignedTx.to, `${route}Task`);
737
- if (s.mode === 'base') {
803
+ if (s.payment === 'relay-erc20') {
738
804
  const { hash, isUserOp, gas } = await relaySend(s, { to: unsignedTx.to, data: unsignedTx.data });
739
805
  // Persist BEFORE waiting, same reasoning as the 0G branch below.
740
806
  updateSpend(record.idempotencyKey, { stage: 'sent', txHash: hash, isUserOp, gas });
@@ -748,6 +814,7 @@ export function registerRentTools(server, cfg, walletCtx) {
748
814
  to: unsignedTx.to,
749
815
  data: unsignedTx.data,
750
816
  gasLimit: GAS_LIMIT,
817
+ ...(s.chainId !== undefined ? { chainId: s.chainId } : {}),
751
818
  });
752
819
  // Persist the hash BEFORE waiting, same reasoning as fundAndIndex: a
753
820
  // crash mid-confirmation must resume onto THIS tx, not broadcast another.
@@ -757,7 +824,7 @@ export function registerRentTools(server, cfg, walletCtx) {
757
824
  }
758
825
  }
759
826
  else if (txHash) {
760
- if (s.mode === 'base')
827
+ if (s.payment === 'relay-erc20')
761
828
  await waitRelayed(s, txHash, record.isUserOp ?? false);
762
829
  else
763
830
  await walletCtx.provider.waitForTransaction(txHash);
@@ -1029,7 +1096,7 @@ export function registerRentTools(server, cfg, walletCtx) {
1029
1096
  return f.error;
1030
1097
  const { s, payFrom } = f;
1031
1098
  // requireFunding returns an error on 0G without a wallet, so walletCtx is
1032
- // non-null whenever s.mode is '0g' from here on.
1099
+ // non-null whenever s.payment is 'local-native' from here on.
1033
1100
  let detail;
1034
1101
  try {
1035
1102
  detail = await loadTask(s, task);
@@ -1085,7 +1152,7 @@ export function registerRentTools(server, cfg, walletCtx) {
1085
1152
  }
1086
1153
  const tx = sub.unsignedSubmitEvidence;
1087
1154
  await verifyTarget(s, tx.to, 'submitEvidence');
1088
- if (s.mode === 'base') {
1155
+ if (s.payment === 'relay-erc20') {
1089
1156
  const sent = await relaySend(s, { to: tx.to, data: tx.data });
1090
1157
  submitTxHash = sent.hash;
1091
1158
  gas = sent.gas;
@@ -1130,7 +1197,7 @@ export function registerRentTools(server, cfg, walletCtx) {
1130
1197
  paidTo: done ? payFrom : undefined,
1131
1198
  submissionAttempts: after.submissionAttempts,
1132
1199
  hint: done
1133
- ? `Escrow released: ${formatUnits(BigInt(after.amount), after.decimals ?? 6)} ${s.mode === 'base' ? 'USDC' : '0G'} minus the marketplace fee is now in ${payFrom}.`
1200
+ ? `Escrow released: ${formatUnits(BigInt(after.amount), after.decimals ?? s.decimals)} ${s.symbol} minus the marketplace fee is now in ${payFrom}.`
1134
1201
  : fin.awaitingPosterApproval
1135
1202
  ? 'Manual-verification task: the poster must approve via verify_task before the escrow releases.'
1136
1203
  : `Verification did not pass (${(fin.verificationResult?.reasons ?? []).join('; ') || 'no reasons given'}). ` +