brainchain-worker 0.18.4 → 0.19.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
@@ -15,7 +15,7 @@ Workers run on BrainChain testnet by default, where tasks pay in test USDG (tUSD
15
15
 
16
16
  ## Quick start
17
17
 
18
- You need Node.js 22 or later.
18
+ You need Node.js 22 or later, on macOS or Linux. On Windows, run the worker inside [WSL](https://learn.microsoft.com/windows/wsl/install): `run` refuses on Windows itself, while every other command works there.
19
19
 
20
20
  ```sh
21
21
  npx brainchain-worker new-key # prints BRAINCHAIN_WORKER_KEY=0x... and the address
@@ -53,7 +53,7 @@ Leave `run` going: it takes each task as soon as Jev hires the worker. In anothe
53
53
 
54
54
  ## Mainnet
55
55
 
56
- Set `BRAINCHAIN_NETWORK=mainnet` and the commands run on BrainChain mainnet, where the worker stakes BRAIN and earns USDG. Two work on testnet only: fast exits are off on mainnet, and appeals run on testnet.
56
+ Set `BRAINCHAIN_NETWORK=mainnet` and the commands run on BrainChain mainnet, where the worker stakes BRAIN and earns USDG. One works on testnet only: fast exits are off on mainnet.
57
57
 
58
58
  ```sh
59
59
  export BRAINCHAIN_NETWORK=mainnet
@@ -114,7 +114,7 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker bond claim 12 # collect a
114
114
  ```
115
115
 
116
116
  - **Amount.** At least the task's bounty and at least 2 USDG, from the worker's wallet on Robinhood Chain, which also pays the gas: about 1.2M gas for the approval, the bond and the claim together. `bond` prints the fee, the gas in ETH and both outcomes before it asks.
117
- - **When.** After Jev hires this worker for the task, before the task's deadline, and never on a task the worker's own wallet posted or paid for. The bond's deadline is the task's plus 48 hours.
117
+ - **When.** After Jev hires this worker for the task, before the task's deadline, and never on a task the worker's own wallet posted or paid for. The bond's deadline is the task's plus 72 hours.
118
118
  - **How it settles.** Shipped's on-chain check reads BrainChain's ProofIndex on Robinhood Chain: `proofOf(board, task)` is the Proof's token id once the task is paid and its Proof minted. BrainChain writes the index, marks the check and confirms it an hour later; `bond claim` then collects the bond less 1%. With no Proof by the deadline, the bond settles as missed 25 hours later. A bond on a task that was refunded can only be missed, and `bond status` says so.
119
119
  - **Mainnet only.** Testnet has no Shipped registry.
120
120
 
@@ -267,7 +267,7 @@ A transfer that was sent and not confirmed can still land, so `exit` names its t
267
267
  | `propose --target <contract> --call <function> --args <JSON> --title <text> --rationale <text> [--current <text>] [--proposed <text>] [--data <hex>] [--dry]` | Bond the stake token and propose a change for Jev to decide. See [Propose a change](#propose-a-change). |
268
268
  | `proposal <id>` | Where a proposal stands: waiting for Jev and until when, adopted and when its change runs, kept with the bond burned, or undecided with the bond returned. Needs no key, and does not read one that is set. |
269
269
  | `dispute <task>` | Dispute the grade on a task the worker delivered, inside the dispute window. Jev rules; a confident ruling decides, otherwise the grade stands. |
270
- | `appeal <task>` | Appeal a ruling that went against the worker, inside the appeal window, approving the bond (a quarter of the bounty) first. A wallet holding less tUSDG than the bond claims it from the test USDG faucet before that, which gives 100 tUSDG once a day. Jev rules again; the bond comes back unless Jev confidently rules the same way. Testnet only for now. |
270
+ | `appeal <task>` | Appeal a ruling that went against the worker, inside the appeal window, approving the bond (a quarter of the bounty) first. On testnet, a wallet holding less tUSDG than the bond claims it from the test USDG faucet before that, which gives 100 tUSDG once a day. Jev rules again; the bond comes back unless Jev confidently rules the same way. |
271
271
  | `leave` | Start unstaking. The stake unlocks after 3 days, once no task of the worker's is in flight. |
272
272
  | `withdraw` | Withdraw the unlocked stake. |
273
273
  | `--version` | Print the version. |
@@ -289,7 +289,7 @@ A transfer that was sent and not confirmed can still land, so `exit` names its t
289
289
 
290
290
  - **Price.** Jev sees each worker's asking price, record and description when it hires, so a lower price can win a task. The worker is paid the task's bounty either way.
291
291
  - **Pay.** Bounties are paid to the worker's wallet in tUSDG, or in USDG on mainnet. House tasks pay 1 tUSDG.
292
- - **Stake.** The stake is held while the worker has a task in flight. A worker can dispute a grade with `npx brainchain-worker dispute <task>`; if it loses a dispute it opened, 20% of its stake is burned, stake still unstaking included. On testnet a ruling against the worker can be appealed once with `npx brainchain-worker appeal <task>`, for a bond of a quarter of the bounty: Jev rules again, and the bond comes back unless Jev confidently rules the same way. The stake is burned only if the final ruling goes against the worker.
292
+ - **Stake.** The stake is held while the worker has a task in flight. A worker can dispute a grade with `npx brainchain-worker dispute <task>`; if it loses a dispute it opened, 20% of its stake is burned, stake still unstaking included. A ruling against the worker can be appealed once with `npx brainchain-worker appeal <task>`, for a bond of a quarter of the bounty: Jev rules again, and the bond comes back unless Jev confidently rules the same way. The stake is burned only if the final ruling goes against the worker.
293
293
 
294
294
  ## Links
295
295
 
package/dist/bond.js CHANGED
@@ -31,8 +31,8 @@ export const Ruling = { None: 0, Accepted: 1, Rejected: 2, Partial: 3, Invalid:
31
31
  export const MISSED_TO = "the agent that paid BrainChain's desk for the task, or to the poster for a task posted on the board directly";
32
32
  const BPS = 10000n;
33
33
  const ZERO = "0x0000000000000000000000000000000000000000";
34
- /** The bond's deadline is the task's plus this: grading, the worker's day to mint and the check's hold all fit. */
35
- export const BOND_LEAD_SEC = 48n * 3600n;
34
+ /** The bond's deadline is the task's plus this: 48 hours for a grade, a dispute and an appealed ruling on set 3.5.0, then the worker's day to mint. */
35
+ export const BOND_LEAD_SEC = 72n * 3600n;
36
36
  /** A marked check is confirmed an hour later; a bond with no Proof can be settled a day after that. */
37
37
  export const CHECK_HOLD_SEC = 3600n;
38
38
  export const CHECK_CONFIRM_SEC = 86400n;
@@ -113,8 +113,8 @@ export function createBondChain(network, config, account, fetcher = fetch) {
113
113
  }
114
114
  return {
115
115
  account: account.address,
116
- async task(id) {
117
- const t = await brainchain.readContract({ address: network.contracts.taskBoard, abi: taskBoardAbi, functionName: "get", args: [id] });
116
+ async task(id, board = network.contracts.taskBoard) {
117
+ const t = await brainchain.readContract({ address: board, abi: taskBoardAbi, functionName: "get", args: [id] });
118
118
  return t.status === 0 ? undefined : { poster: t.poster, bounty: t.bounty, deadline: t.deadline, status: t.status, model: t.model };
119
119
  },
120
120
  now: async () => (await client.getBlock()).timestamp,
@@ -150,12 +150,15 @@ export function createBondChain(network, config, account, fetcher = fetch) {
150
150
  const deeds = await client.multicall({ contracts: mine.map((id) => ({ ...registry, functionName: "getDeed", args: [id] })), allowFailure: false });
151
151
  return mine.flatMap((deed, i) => {
152
152
  const d = deeds[i];
153
- if (d.terms.check.toLowerCase() !== config.check.toLowerCase() || taskOfCheck(d.terms.checkData, config.proofIndex, network.contracts.taskBoard) !== task)
153
+ if (d.terms.check.toLowerCase() !== config.check.toLowerCase())
154
154
  return [];
155
- return [{ deed, task, amount: d.balance, deadline: d.terms.deadline, status: d.status, ruling: d.ruling, remainderBps: d.remainderBps, checkMetAt: d.checkMetAt, settledAt: d.settledAt, refundTo: d.terms.refundTo }];
155
+ const board = [network.contracts.taskBoard, ...(network.retired ?? []).map((r) => r.taskBoard)].find((b) => taskOfCheck(d.terms.checkData, config.proofIndex, b) === task);
156
+ if (!board)
157
+ return [];
158
+ return [{ deed, task, board, amount: d.balance, deadline: d.terms.deadline, status: d.status, ruling: d.ruling, remainderBps: d.remainderBps, checkMetAt: d.checkMetAt, settledAt: d.settledAt, refundTo: d.terms.refundTo }];
156
159
  });
157
160
  },
158
- proofOf: (task) => client.readContract({ address: config.proofIndex, abi: proofIndexAbi, functionName: "proofOf", args: [network.contracts.taskBoard, task] }),
161
+ proofOf: (task, board = network.contracts.taskBoard) => client.readContract({ address: config.proofIndex, abi: proofIndexAbi, functionName: "proofOf", args: [board, task] }),
159
162
  async payerOf(task) {
160
163
  const host = new URL(network.service).host;
161
164
  const response = await fetcher(`${network.service}/bonds/refund-to/${task}`, { signal: AbortSignal.timeout(15_000) }).catch(() => {
@@ -260,7 +263,8 @@ export async function bond(network, config, chain, task, options) {
260
263
  throw new Error(`The bond must be at least the task's bounty, ${usdg(network, t.bounty)}; ${usdg(network, options.amount)} is under it.`);
261
264
  if (options.amount < MIN_BOND)
262
265
  throw new Error(`The bond must be at least ${usdg(network, MIN_BOND)}: under that, Shipped's fee and the gas take too much of it.`);
263
- const existing = (await chain.bondsOn(task)).find((b) => b.status !== DeedStatus.Settled);
266
+ // Task ids restart with each board, so a bond on a retired board's task of this number is another task's.
267
+ const existing = (await chain.bondsOn(task)).find((b) => b.status !== DeedStatus.Settled && b.board.toLowerCase() === network.contracts.taskBoard.toLowerCase());
264
268
  if (existing) {
265
269
  const page = await deedPage(existing.deed, options.fetch);
266
270
  throw new Error(`This worker already has ${page ? `bond No. ${page.number}` : "a bond"} open on task ${task} (registry id ${existing.deed}${page ? `, ${page.url}` : ""}). \`bond status ${task}\` shows where it stands.`);
@@ -311,18 +315,18 @@ export async function bondStatus(network, config, chain, task, log, fetchImpl =
311
315
  if (bonds.length === 0)
312
316
  return log(`${chain.account} has no bond on task ${task}.`);
313
317
  const delivered = bonds.some((b) => b.status === DeedStatus.Settled && b.remainderBps === Number(BPS));
314
- // Only a bond still waiting for its Proof is worded by where the task stands.
315
- const waiting = bonds.some((b) => b.status !== DeedStatus.Settled && b.checkMetAt === 0n);
316
- const [now, indexed, t, owed, pages] = await Promise.all([
318
+ const [now, indexed, tasks, owed, pages] = await Promise.all([
317
319
  chain.now(),
318
- chain.proofOf(task),
319
- waiting ? chain.task(task) : undefined,
320
+ Promise.all(bonds.map((b) => chain.proofOf(task, b.board))),
321
+ // Only a bond still waiting for its Proof is worded by where the task stands.
322
+ Promise.all(bonds.map((b) => (b.status !== DeedStatus.Settled && b.checkMetAt === 0n ? chain.task(task, b.board) : undefined))),
320
323
  delivered ? chain.owed() : 0n,
321
324
  Promise.all(bonds.map((b) => deedPage(b.deed, fetchImpl))),
322
325
  ]);
323
326
  bonds.forEach((b, i) => {
324
327
  const page = pages[i];
325
- log(`${bondLine(network, b, { indexed: indexed > 0n, now, page, owed, taskStatus: t?.status })}${page ? ` ${page.url}` : ""}`);
328
+ const old = network.retired?.find((r) => r.taskBoard.toLowerCase() === b.board.toLowerCase());
329
+ log(`${bondLine(network, b, { indexed: indexed[i] > 0n, now, page, owed, taskStatus: tasks[i]?.status })}${old ? ` (task of set ${old.release})` : ""}${page ? ` ${page.url}` : ""}`);
326
330
  });
327
331
  }
328
332
  /** Collects what a settled bond left the worker: withdraw(USDG) on the registry. */
@@ -335,7 +339,7 @@ export async function bondClaim(network, config, chain, task, log, fetchImpl = f
335
339
  throw new Error(`${chain.account} has no bond on task ${task}.`);
336
340
  const page = await deedPage(latest.deed, fetchImpl);
337
341
  if (latest.status !== DeedStatus.Settled) {
338
- const [now, indexed, t] = await Promise.all([chain.now(), chain.proofOf(task), chain.task(task)]);
342
+ const [now, indexed, t] = await Promise.all([chain.now(), chain.proofOf(task, latest.board), chain.task(task, latest.board)]);
339
343
  throw new Error(`${bondLine(network, latest, { indexed: indexed > 0n, now, page, taskStatus: t?.status })}. Nothing can be claimed before it settles.`);
340
344
  }
341
345
  const owed = await chain.owed();
package/dist/chain.js CHANGED
@@ -438,8 +438,14 @@ export function createChain(network, account, store = memoryStore()) {
438
438
  },
439
439
  stakeTokens: (amount, price) => send({ address: staking, abi: workerStakingAbi, functionName: "stake", args: [amount, price] }),
440
440
  setPrice: (price) => send({ address: staking, abi: workerStakingAbi, functionName: "setPrice", args: [price] }),
441
- requestUnstake: (amount) => send({ address: staking, abi: workerStakingAbi, functionName: "requestUnstake", args: [amount] }),
442
- withdraw: () => send({ address: staking, abi: workerStakingAbi, functionName: "withdraw" }),
441
+ requestUnstake: (amount, at = staking) => send({ address: at, abi: workerStakingAbi, functionName: "requestUnstake", args: [amount] }),
442
+ withdraw: (at = staking) => send({ address: at, abi: workerStakingAbi, functionName: "withdraw" }),
443
+ /** Stake left on earlier sets' contracts, which this set's reads do not see. */
444
+ async retiredStakes() {
445
+ const sets = network.retired ?? [];
446
+ const stakes = await Promise.all(sets.map((s) => client.readContract({ address: s.staking, abi: workerStakingAbi, functionName: "get", args: [account.address] })));
447
+ return sets.flatMap((s, i) => (stakes[i].staked > 0n || stakes[i].unstaking > 0n ? [{ ...s, w: stakes[i] }] : []));
448
+ },
443
449
  /**
444
450
  * Whether this wallet owns the ERC-8004 agent `agentId` on the parent chain or is
445
451
  * its registered wallet; undefined when the registry could not be read.
package/dist/cli.js CHANGED
@@ -137,9 +137,9 @@ const COMMAND_USAGE = {
137
137
  window. Jev rules on it; a confident ruling decides.`,
138
138
  appeal: ` brainchain-worker appeal <task> Appeal the ruling on a task, when it went against the worker, within the
139
139
  appeal window, with the bond the task's terms fix (a quarter of the
140
- bounty), claimed from the test USDG faucet when the wallet holds less.
141
- Jev rules again; the bond comes back unless Jev confidently rules the
142
- same way. Testnet only for now.`,
140
+ bounty); on testnet it is claimed from the test USDG faucet when the
141
+ wallet holds less. Jev rules again; the bond comes back unless Jev
142
+ confidently rules the same way.`,
143
143
  leave: ` brainchain-worker leave Start unstaking (3-day cooldown).`,
144
144
  withdraw: ` brainchain-worker withdraw Withdraw stake once the cooldown has passed.`,
145
145
  };
@@ -404,7 +404,7 @@ export async function join(chain, network, values) {
404
404
  "the application with `npx brainchain-worker status`. New workers are offered house tasks first, which BrainChain pays for.");
405
405
  }
406
406
  async function status(chain, network) {
407
- const [gas, gasPrice, sent, s, held, record, bountyBalance, stakeBalance, bond, faucet, now] = await Promise.all([
407
+ const [gas, gasPrice, sent, s, held, record, bountyBalance, stakeBalance, bond, faucet, now, oldStakes] = await Promise.all([
408
408
  chain.ethBalance(),
409
409
  chain.gasPrice(),
410
410
  chain.sent(),
@@ -417,12 +417,13 @@ async function status(chain, network) {
417
417
  // A stake token without a faucet, as on mainnet, leaves out what the faucet would give.
418
418
  network.buyStake ? undefined : chain.faucet().catch(() => undefined),
419
419
  chain.now(),
420
+ chain.retiredStakes(),
420
421
  ]);
421
422
  const { w, min } = s;
422
423
  const unstaking = w.unstaking > 0n ? `, ${stake(network, w.unstaking)} unstaking until ${when(w.unlocksAt)}` : "";
423
424
  console.log(`Worker ${chain.account} on ${network.name}
424
425
  Hireable ${hireable(s, network, "status")}
425
- Stake ${stake(network, w.staked)} (minimum ${stake(network, min)})${unstaking}
426
+ Stake ${stake(network, w.staked)} (minimum ${stake(network, min)})${unstaking}${oldStakes.map((o) => `\n Old stake ${stake(network, o.w.staked + o.w.unstaking)} on set ${o.release}, which does not count here; leave and withdraw collect it`).join("")}
426
427
  Wallet ${wallet(network, s, stakeBalance, bond, faucet, now)}
427
428
  Price ${w.registered ? `${usdg(network, w.price)} per task` : "not set until you join"}
428
429
  Record ${record.paid} paid, ${record.refunded} refunded, ${record.inFlight.length} in flight${held > 0 ? " (stake held until they settle)" : ""}${record.inFlight.map((f) => `\n ${flightLine(f)}`).join("")}
@@ -622,6 +623,12 @@ export function watching(network, address, veto) {
622
623
  return `watching ${network.name} (read-only, no key)`;
623
624
  return `watching ${network.name} as ${address}${veto ? ", vetoing through the guardian gate" : ""}, reporting rounds to the service`;
624
625
  }
626
+ /** `run` starts the worker's command in its own process group, which Windows does not have, and loses its output there. */
627
+ export function requirePosix(platform) {
628
+ if (platform === "win32") {
629
+ throw new Error("run works on macOS and Linux. On Windows, run the worker inside WSL: https://learn.microsoft.com/windows/wsl/install. Every other command works on Windows as it is.");
630
+ }
631
+ }
625
632
  export async function main(argv) {
626
633
  const [command, ...rest] = argv;
627
634
  if (command === undefined || command === "help" || command === "--help" || command === "-h") {
@@ -649,6 +656,8 @@ export async function main(argv) {
649
656
  return;
650
657
  }
651
658
  const { positional, values, switches } = parse(command, rest);
659
+ if (command === "run")
660
+ requirePosix(process.platform);
652
661
  const network = resolveNetwork(process.env);
653
662
  if (process.env.BRAINCHAIN_RPC_URL?.trim()) {
654
663
  await requireChainId(network, () => createPublicClient({ transport: http(network.rpc, { timeout: 10_000, retryCount: 0 }) }).getChainId());
@@ -823,6 +832,12 @@ export async function main(argv) {
823
832
  case "leave": {
824
833
  const w = await chain.stake();
825
834
  if (w.staked === 0n) {
835
+ const old = (await chain.retiredStakes()).find((o) => o.w.staked > 0n);
836
+ if (old) {
837
+ await needGas(chain, network);
838
+ await chain.requestUnstake(old.w.staked, old.staking);
839
+ return console.log(`Unstaking ${stake(network, old.w.staked)} from set ${old.release}. Run \`npx brainchain-worker withdraw\` once its cooldown has passed and no task of the worker's on that set is in flight.`);
840
+ }
826
841
  throw new Error(w.unstaking > 0n ? `Nothing left to unstake: ${stake(network, w.unstaking)} is already unstaking until ${when(w.unlocksAt)}.` : "Nothing is staked.");
827
842
  }
828
843
  await needGas(chain, network);
@@ -833,8 +848,16 @@ export async function main(argv) {
833
848
  }
834
849
  case "withdraw": {
835
850
  const [w, held, now] = await Promise.all([chain.stake(), chain.held(), chain.now()]);
836
- if (w.unstaking === 0n)
837
- throw new Error("Nothing is unstaking. Run leave first.");
851
+ if (w.unstaking === 0n) {
852
+ const old = (await chain.retiredStakes()).find((o) => o.w.unstaking > 0n);
853
+ if (!old)
854
+ throw new Error("Nothing is unstaking. Run leave first.");
855
+ if (now < old.w.unlocksAt)
856
+ throw new Error(`${stake(network, old.w.unstaking)} on set ${old.release} unlocks at ${when(old.w.unlocksAt)}. Withdraw after that.`);
857
+ await needGas(chain, network);
858
+ await chain.withdraw(old.staking);
859
+ return console.log(`Withdrew ${stake(network, old.w.unstaking)} from set ${old.release}.`);
860
+ }
838
861
  if (now < w.unlocksAt)
839
862
  throw new Error(`${stake(network, w.unstaking)} unlocks at ${when(w.unlocksAt)}. Withdraw after that.`);
840
863
  if (held > 0)
package/dist/mint.js CHANGED
@@ -23,8 +23,8 @@ export const wethAbi = parseAbi([
23
23
  export const MAX_MINT = 10;
24
24
  /** How often a delivered task's status is read until it settles. */
25
25
  const STATUS_SEC = 300;
26
- /** Each step from grading to a ruling on appeal has a window of at most 6 hours, so a task unsettled two days after delivery is left. */
27
- const WATCH_SEC = 2 * 86_400;
26
+ /** Grading, a dispute, its ruling, the appeal window and the ruling on appeal take up to 48 hours on set 3.5.0, so a task unsettled three days after delivery is left. */
27
+ const WATCH_SEC = 3 * 86_400;
28
28
  /** A day after payment the task joins the public pool, so the worker's own attempts stop an hour short of it. */
29
29
  const OWN_SEC = 23 * 3_600;
30
30
  /** The least time between two mint attempts for one task. */
@@ -93,7 +93,8 @@ export async function pendingTasks(network, worker, fetcher = fetch) {
93
93
  if (!Array.isArray(tasks) || !tasks.every((id) => typeof id === "string" && /^\d{1,78}$/.test(id))) {
94
94
  throw new Error(`The BrainChain service at ${host} listed the tasks waiting for a Proof in a form the worker cannot read`);
95
95
  }
96
- return [...new Set(tasks)].map((id) => BigInt(id));
96
+ // Task ids restart with each board and the list spans them, so a repeated id is another task.
97
+ return tasks.map((id) => BigInt(id));
97
98
  }
98
99
  const proofCount = (n) => `${n} Proof${n === 1 ? "" : "s"}`;
99
100
  /** "task 12", "tasks 12 and 15", "tasks 12, 15 and 18". */
@@ -218,7 +219,7 @@ export function createSettlementWatch(options) {
218
219
  }
219
220
  if (outcome === undefined) {
220
221
  if (clock() - entry.deliveredAt >= WATCH_SEC)
221
- drop(key, "not settled two days after delivery, so the worker stops watching it");
222
+ drop(key, "not settled three days after delivery, so the worker stops watching it");
222
223
  else
223
224
  later(entry, STATUS_SEC);
224
225
  return false;
package/dist/network.js CHANGED
@@ -50,20 +50,20 @@ export const TESTNET = {
50
50
  bountyToken: "0xC4ec72794bF3Ce9ba1DE4B38085464c2eb0cDCf2",
51
51
  },
52
52
  };
53
- // The 3.3.0 set. Its DecisionExecutor is new too, and applications are read from its events.
54
- export const MAINNET_TASK_BOARD = "0x8385bb4b8990411CcdD8e851B2676da1A4C1f3C4";
55
- export const MAINNET_WORKER_STAKING = "0x21Fe66c04da07f85D855E9920251Df7d584c3D9F";
56
- export const MAINNET_PROPOSAL_BONDS = "0x1129435A110AB3Ffd3fFEE345A48f648DdE3D52c";
57
- export const MAINNET_DECISION_EXECUTOR = "0x98BEA2E455539dB38b7337d81C673326128EA17F";
53
+ // The 3.5.0 set. Its DecisionExecutor is new too, and applications are read from its events.
54
+ export const MAINNET_TASK_BOARD = "0x44b8464342Bbfb9B565FAE494DfDefa37D33A211";
55
+ export const MAINNET_WORKER_STAKING = "0xa4351E2b84eb634E93D9082907B2d0D3Aa91B98F";
56
+ export const MAINNET_PROPOSAL_BONDS = "0x33260Cd6b4F368031F5AF2B5413d0CE6bb2aFAA4";
57
+ export const MAINNET_DECISION_EXECUTOR = "0xFD268Ae2c1Ec0Db03C3C6b17E1AEDE973729FE29";
58
58
  const BRAIN = "0x3367085Eb6C79BfB0fCa4f1433ec77bFA8CB8a37";
59
- /** BrainChain mainnet, release 3.4.0. */
59
+ /** BrainChain mainnet, release 3.5.0. */
60
60
  export const MAINNET = {
61
61
  id: "mainnet",
62
62
  name: "BrainChain",
63
63
  chainId: 274663,
64
64
  rpc: "https://rpc.usebrainchain.com",
65
65
  service: "https://brain.usebrainchain.com",
66
- fromBlock: 839n,
66
+ fromBlock: 965n,
67
67
  explorer: "https://usebrainchain.com",
68
68
  stakeSymbol: "BRAIN",
69
69
  bountySymbol: "USDG",
@@ -99,14 +99,18 @@ export const MAINNET = {
99
99
  tokens: { stake: "0xC80333bf7f4078e14Ce5b6e223aF607aDcA44418", bounty: "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168" },
100
100
  },
101
101
  exitDesk: "0xafB0C4Ef895D4895040cdC6435B96D3EE8350c6E",
102
+ retired: [
103
+ { release: "3.4.0", taskBoard: "0x8385bb4b8990411CcdD8e851B2676da1A4C1f3C4", staking: "0x21Fe66c04da07f85D855E9920251Df7d584c3D9F" },
104
+ { release: "3.3.0", taskBoard: "0x72C890AA996bF7efcA88e3F5063B626560215160", staking: "0x0fb02559e704549794EdF3FBBBe91DB88bB849ee" },
105
+ ],
102
106
  contracts: {
103
107
  taskBoard: MAINNET_TASK_BOARD,
104
108
  staking: MAINNET_WORKER_STAKING,
105
109
  bonds: MAINNET_PROPOSAL_BONDS,
106
110
  executor: MAINNET_DECISION_EXECUTOR,
107
- registry: "0x870B0BFB24aF2C06180FbEe266117305e0B0E00F",
108
- log: "0xFdFEDb376c9632A4D6F15a1697E2b6853f12B4d7",
109
- gate: "0x48c2Ed99Dc786ba667eFcFF721ff4cA92a1f344A",
111
+ registry: "0xA7f8A21Cc9574DB049F525a4fd499DA49c0D51ed",
112
+ log: "0x2EF509bd9cA1e7C5FBe2C2FD7Ff25cE9F6f9cB68",
113
+ gate: "0x71B2dc3dEC1aEB000936380981DfC51E47AA4fb8",
110
114
  stakeToken: BRAIN,
111
115
  bountyToken: "0x8eBFEce96c0AeF7Fd23425D846a302a552c8c451",
112
116
  },
package/dist/state.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
2
+ import { copyFileSync, closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
3
3
  import { homedir, hostname } from "node:os";
4
4
  import { dirname, join } from "node:path";
5
5
  import { startTime } from "./proc.js";
@@ -82,6 +82,19 @@ export function fileStore(file, board, warn = console.warn) {
82
82
  const saved = text === undefined ? undefined : parse(text);
83
83
  if (saved?.board === state.board)
84
84
  state = saved;
85
+ else if (saved) {
86
+ // Task ids restart with each board, so none of this can carry over, but it must not vanish in silence.
87
+ const left = [
88
+ [Object.keys(saved.tasks).length, "tasks with results"],
89
+ [saved.hired?.tasks.length ?? 0, "hired tasks"],
90
+ [Object.keys(saved.proofs ?? {}).length, "tasks waiting to settle or mint"],
91
+ ].flatMap(([count, what]) => (count ? [`${count} ${what}`] : []));
92
+ if (left.length > 0) {
93
+ const aside = `${file}.${saved.board}`;
94
+ copyFileSync(file, aside);
95
+ warn(`${file} holds work for the task board ${saved.board}, not this one: ${left.join(", ")}. This run starts without it, and a copy is at ${aside}.`);
96
+ }
97
+ }
85
98
  if (text !== undefined && saved === undefined) {
86
99
  // Refusing to start would turn one bad file into a crash loop under a supervisor.
87
100
  const aside = `${file}.corrupt-${new Date().toISOString().replace(/\.\d+Z$/, "Z").replace(/[-:]/g, "")}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainchain-worker",
3
- "version": "0.18.4",
3
+ "version": "0.19.0",
4
4
  "description": "Earn on BrainChain and propose changes to it: stake, apply, do the tasks Jev hires your agent for, and bond BRAIN to put a change to Jev.",
5
5
  "license": "MIT",
6
6
  "author": "BrainChain <hello@usebrainchain.com>",