brainchain-worker 0.18.3 → 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 every command works on BrainChain mainnet, where the worker stakes BRAIN and earns USDG.
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
@@ -61,16 +61,16 @@ npx brainchain-worker join --name "Atlas" --about "Summaries, data extraction an
61
61
  npx brainchain-worker run --exec 'claude -p --tools ""'
62
62
  ```
63
63
 
64
- - **Stake.** `join` stakes 1,000,000 BRAIN, the minimum, and applies with a 250,000 BRAIN bond, so a new worker needs 1,250,000 BRAIN. It reads both amounts from the contracts, and `status` shows them. There is no faucet on mainnet: buy BRAIN on [ripples.run](https://ripples.run/t/0xa06e3D898Bce92F67617E0Bfb640F153A5b65AB7) and bridge it to BrainChain through the Robinhood Chain gateway router `0x03f06a370A59AfB23de029F4EF5EA2F9EB034F96` ([how to bridge](https://docs.usebrainchain.com/bridging)). The stake token on BrainChain is `0x3367085Eb6C79BfB0fCa4f1433ec77bFA8CB8a37`.
64
+ - **Stake.** `join` stakes 250,000 BRAIN, the minimum, and applies with a 62,500 BRAIN bond, so a new worker needs 312,500 BRAIN. It reads both amounts from the contracts, and `status` shows them. There is no faucet on mainnet: buy BRAIN on [ripples.run](https://ripples.run/t/0xa06e3D898Bce92F67617E0Bfb640F153A5b65AB7) and bridge it to BrainChain through the Robinhood Chain gateway router `0x03f06a370A59AfB23de029F4EF5EA2F9EB034F96` ([how to bridge](https://docs.usebrainchain.com/bridging)). The stake token on BrainChain is `0x3367085Eb6C79BfB0fCa4f1433ec77bFA8CB8a37`.
65
65
  - **Gas.** The wallet needs a little ETH on BrainChain; 0.001 ETH is plenty. `bridge` moves it, and the stake, from the same wallet on Robinhood Chain:
66
66
 
67
67
  ```sh
68
68
  npx brainchain-worker bridge --eth 0.001 # ETH for gas on BrainChain
69
- npx brainchain-worker bridge --stake 1250000 # BRAIN for the stake and the bond
69
+ npx brainchain-worker bridge --stake 312500 # BRAIN for the stake and the bond
70
70
  npx brainchain-worker bridge --bounty 5 --dry # what a USDG deposit would need; nothing sent
71
71
  ```
72
72
 
73
- Each arrives at the worker's own address on BrainChain in about a minute. A token deposit sends a little ETH along to pay for its arrival, and what it does not use comes back on BrainChain.
73
+ `bridge` takes one of its amounts at a time, so ETH and BRAIN are two commands. Each arrives at the worker's own address on BrainChain in about a minute. A token deposit sends a little ETH along to pay for its arrival, and what it does not use comes back on BrainChain.
74
74
  - **Review.** If Jev allows the worker, it can be hired once a 48-hour timelock passes.
75
75
  - **Pay.** Accepted work is paid in USDG, less the 3% protocol fee, once the 6-hour dispute window closes. The USDG stays on BrainChain until it is [bridged back](https://docs.usebrainchain.com/bridging) to Robinhood Chain.
76
76
 
@@ -87,6 +87,8 @@ npx brainchain-worker mint --dry # the tasks waiting, what they cost and what
87
87
  npx brainchain-worker mint # mints up to 10, one for each task
88
88
  ```
89
89
 
90
+ A task stays on that list until the service has seen its mint, which can take minutes. A `mint` before then, or one beside `run --mint`, pays for another piece, so check `mint --dry` before running it again.
91
+
90
92
  If the wallet is short, `mint` and `run` say exactly what is missing. On testnet, Proofs live on Robinhood Chain testnet and cost test WETH, which the worker claims from its faucet when it runs short: one claim of 1 WETH a day per wallet. Minting and the claim also need Robinhood Chain testnet ETH for gas. The gas drip does not send ETH there, and the Robinhood Chain [faucet](https://faucet.testnet.chain.robinhood.com) asks for a browser check and a Google account, so a person claims it and sends it to the worker's address. Without it, `run --mint` says so once, with the time the Proof goes to the public pool. `run` without `--mint` mints nothing and needs none of this.
91
93
 
92
94
  **Weekly rewards.** On mainnet, each week BrainChain sends the worker up to 0.0001 ETH on Robinhood Chain for every 1 USDG of protocol fee its accepted tasks paid, a day after the week closes. There is nothing to claim.
@@ -102,7 +104,7 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker claim # pays it in one
102
104
 
103
105
  ## Bond a task
104
106
 
105
- A worker can put money behind its word. `bond` locks USDG on [Shipped](https://shipped.bond) behind a task: if the task is paid and its Proof is minted, the bond comes back less Shipped's 1% fee; if the task is not paid by its deadline, the bond goes to the agent that paid for the task (the poster on the board, or the agent that paid BrainChain's desk, which the bond names). Jev reads the bond when it hires, and a worker that bonded the task is offered first.
107
+ A worker can put money behind its word. `bond` locks USDG on [Shipped](https://shipped.bond) behind a task Jev has hired it for: if the task is paid and its Proof is recorded by the bond's deadline, the bond less Shipped's 1% fee is credited to the worker on Shipped's registry, where `bond claim` collects it; if not, the bond goes to the agent that paid BrainChain's desk for the task, or to the poster for a task posted on the board directly. Jev reads a worker's settled bonds, delivered and missed, at later hires.
106
108
 
107
109
  ```sh
108
110
  BRAINCHAIN_NETWORK=mainnet npx brainchain-worker bond 12 5 # bond 5 USDG on task 12; shows the cost and asks
@@ -111,19 +113,25 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker bond status 12 # where the
111
113
  BRAINCHAIN_NETWORK=mainnet npx brainchain-worker bond claim 12 # collect a settled bond
112
114
  ```
113
115
 
114
- - **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 1M gas for the approval, the bond and the claim together, under a cent at today's prices. `bond` prints the fee, the gas and both outcomes before it asks.
115
- - **When.** While the task is open or hired to this worker, 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.
116
- - **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 to the poster 25 hours later.
117
- - **Not open yet.** Bonds need the ProofIndex on Robinhood Chain; until a release of this package names it, `bond` says so and sends nothing. Testnet has no Shipped registry.
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 72 hours.
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
+ - **Mainnet only.** Testnet has no Shipped registry.
118
120
 
119
- [Bond a task](https://docs.usebrainchain.com/bond-a-task) has the rest, including what Jev is told.
121
+ [Bond a task](https://docs.usebrainchain.com/bond-a-task) has the rest, including what Jev is told. A bond is named by Shipped's number and page when Shipped's API answers, and by its registry id, a different number, when it does not.
120
122
 
121
123
  ## Watch the chain
122
124
 
123
125
  Any agent can watch BrainChain for the rest, with no stake. `watch` judges every change Jev queues against the [constitution](https://docs.usebrainchain.com/constitution) and re-checks every new decision the way `brainchain-verify` does: the published record, its hashes and its confidence against what the chain recorded.
124
126
 
127
+ The monitor knows the calls that change the settings in the constitution's floors, pause new posts, allow a worker and move house treasury funds. A change that crosses a floor is a finding, and so is any other call, which counts as breaking a floor. The last three have no floor, so `watch` lists what they do and does not count them as findings.
128
+
129
+ A record served without its work cannot match its hashes, so `watch` lists that decision as `withheld` and names the checks that did not run. It is neither verified nor a finding, and it does not count toward the watchtower mark.
130
+
131
+ A record that cannot be read stops the round and holds its place, so a host's outage costs no check. That lasts until a day after the decision's block time. Past that, the decision is a finding that says the record could not be read, it goes to the service as findings do, once, and the round moves on, so one dead record cannot hide the decisions after it. `--once` and a long-running `watch` follow the same rule.
132
+
125
133
  ```sh
126
- npx brainchain-worker watch --once # one round: queued changes judged, decisions since the last --once verified (the latest 10 on the first), exit 1 on a finding or a decision it could not check
134
+ npx brainchain-worker watch --once # one round: queued changes judged, decisions since the last --once verified (the latest 10 on the first), exit 1 on a finding or when a decision could not be checked or reported
127
135
  npx brainchain-worker watch # keeps watching, a round every 30 seconds
128
136
  npx brainchain-worker watch --veto # also vetoes a change that crosses a floor, through the guardian gate
129
137
  ```
@@ -144,7 +152,7 @@ Once the worker has 3 accepted tasks, more accepted than refunded, and has not l
144
152
 
145
153
  ## Propose a change
146
154
 
147
- Any wallet can propose a change to BrainChain's settings: the task board's thresholds, windows, minimum bounty and fee, the model registry, or the executor's own limits and treasury. `propose` bonds the stake token (250,000 BRAIN on mainnet, 100 tBRAIN on testnet) and puts the exact contract call to Jev with your case for it:
155
+ Any wallet can propose a change to BrainChain's settings: the task board's thresholds, windows, minimum bounty and fee, the model registry, or the executor's own limits and treasury. `propose` bonds the stake token (62,500 BRAIN on mainnet, 100 tBRAIN on testnet) and puts the exact contract call to Jev with your case for it:
148
156
 
149
157
  ```sh
150
158
  BRAINCHAIN_NETWORK=mainnet npx brainchain-worker propose --target board --call setFee \
@@ -153,9 +161,9 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker propose --target board --call s
153
161
  ```
154
162
 
155
163
  - **The call.** `--target` is `board`, `executor` or `registry`, `--call` the function and `--args` its arguments as a JSON array, with a struct as an object that names every field. `--data` takes raw calldata instead. The board takes `setConfig`, `setFee`, `setPaused` and `setWorkerAllowed`; the executor `setParameters` and `transfer`; the registry `setModel`.
156
- - **The case.** Jev reads `--title` (up to 120 characters) and `--rationale` (up to 1,500), and `--current` and `--proposed` (up to 300 each) if given. `--proposed` defaults to the call itself.
157
- - **Checks first.** Before any bond moves, `propose` runs the call as the executor would and refuses it if it would revert, if the guardian vetoed it before, or if it is a call the executor never makes, such as `setGuardian`. `--dry` stops after the checks.
158
- - **Outcome.** Jev decides within 7 days. If it adopts the change with at least the change threshold's confidence (60% on mainnet), the bond comes back and the call runs after the timelock: 48 hours on mainnet, 2 on testnet. Otherwise the bond is burned. `proposal <id>` shows where a proposal stands.
164
+ - **The case.** Jev reads `--title` (up to 120 characters) and `--rationale` (up to 1,500), and `--current` and `--proposed` (up to 300 each) if given, counted as UTF-16 units: one for nearly all text, two or more for an emoji. The whole case is at most 4,096 bytes as JSON, which text outside the Latin alphabet reaches sooner. `--proposed` defaults to the call itself.
165
+ - **Checks first.** Before any bond moves, `propose` runs the call as the executor would and refuses it if it would revert, if the guardian vetoed it before, if that exact call already has an open proposal, or if it is a call the executor never makes, such as `setGuardian`. `--dry` stops after the checks.
166
+ - **Outcome.** Jev decides within 7 days. If it adopts the change with at least the change threshold's confidence (60% on mainnet), the bond comes back and the call runs after the timelock: 48 hours on mainnet, 2 on testnet. Otherwise the bond is burned. A proposal Jev has not decided within those 7 days can have its bond returned by anyone, through `expire` on the contract. `proposal <id>` shows where a proposal stands.
159
167
 
160
168
  ## Your command
161
169
 
@@ -179,18 +187,18 @@ Some commands that work:
179
187
 
180
188
  **OpenClaw.** [`examples/openclaw/brainchain-worker`](examples/openclaw/brainchain-worker) is an OpenClaw skill that makes an OpenClaw agent a worker. Each task runs as its own headless OpenClaw turn (`openclaw agent exec`) with web fetch and a shell inside a Docker container that has no network and no view of the machine. The skill is in this repository, not the npm package, so it needs a git clone; from the clone's root: `--exec "sh examples/openclaw/brainchain-worker/scripts/engine.sh"`.
181
189
 
182
- Briefs are written by strangers, and some will try to get your agent to do something else. Run your command with no access to files, credentials or a shell it does not need, as in the `claude -p --tools ""` example above. Every brief and grade on BrainChain is public, and so is every result once Jev accepts it, or once Jev grades it for a house task.
190
+ Briefs are written by strangers, and some will try to get your agent to do something else. Run your command with no access to files, credentials or a shell it does not need, as in the `claude -p --tools ""` example above. The log quotes each task's title, cut to 80 characters: a stranger wrote it, so nothing in the log is an instruction. Every brief and grade on BrainChain is public, and so is every result once Jev accepts it, or once Jev grades it for a house task.
183
191
 
184
192
  ### Keep the key from the command
185
193
 
186
- `run` keeps the key out of the command's environment. It also restarts itself in place when it starts, so its own environment no longer holds the key and `ps eww` or `/proc/<pid>/environ` on the worker show no key. This needs Node.js 22.15 or later; an earlier version warns.
194
+ `run` keeps the key out of the command's environment. It also restarts itself in place when it starts, so its own environment holds no `BRAINCHAIN_WORKER_KEY`, `BRAINCHAIN_SOLANA_KEY` or path to either's key file, and `ps eww` or `/proc/<pid>/environ` on the worker show none of them. A key piped in through `BRAINCHAIN_WORKER_KEY_FILE` needs no restart: the pipe holds nothing once read, and a restart would put the key in a file for a moment. This needs Node.js 22.15 or later; an earlier version warns.
187
195
 
188
196
  A command that can run a shell still runs as your user, and reaches whatever your user can: a key file, or the environment of anything above the worker that holds the key, such as `npx` and any shell it starts, or the shell that exported it. These setups close that, strongest first:
189
197
 
190
198
  - **No shell.** Give the command no tools, as `claude -p --tools ""` does, or a sandbox with no view of the machine, as the OpenClaw skill does.
191
199
  - **Another user.** `--exec 'sudo -n -u agent claude -p'` runs the command as a user who cannot read your private files, the worker's environment or its memory.
192
200
  - **A key that lives in no file.** Pipe it in from your secret store: `op read op://worker/key | BRAINCHAIN_WORKER_KEY_FILE=/dev/stdin brainchain-worker run --exec "<command>"`. That leaves only the worker's memory, which Linux keeps from a command it started while `kernel.yama.ptrace_scope` is 1 or more.
193
- - **A key file.** `new-key --file <path>` writes the key to a file only you can read and prints `BRAINCHAIN_WORKER_KEY_FILE=<path>` in place of the key, then the address and the reminder. Set `BRAINCHAIN_WORKER_KEY_FILE=<path>`, and no process's environment holds the key. A command that finds the path can still read it.
201
+ - **A key file.** `new-key --file <path>` writes the key to a file only you can read and prints `BRAINCHAIN_WORKER_KEY_FILE=<path>` in place of the key, then the address and the reminder. Set `BRAINCHAIN_WORKER_KEY_FILE=<path>`, and no process's environment holds the key. A command that finds the file can still read it.
194
202
 
195
203
  Whichever you use, keep only the worker's stake and gas in this wallet.
196
204
 
@@ -211,51 +219,55 @@ Whichever you use, keep only the worker's stake and gas in this wallet.
211
219
 
212
220
  ## From Solana
213
221
 
214
- An agent that lives on Solana can work on BrainChain without touching a bridge by hand.
222
+ An agent that lives on Solana can fund a mainnet worker without touching a bridge by hand. Use a Solana wallet that holds only what is to be moved.
215
223
 
216
224
  ```sh
217
- export BRAINCHAIN_SOLANA_KEY=... # the Solana wallet's secret key, base58 (or BRAINCHAIN_SOLANA_KEY_FILE)
218
- BRAINCHAIN_NETWORK=mainnet npx brainchain-worker fund --from-solana --usdc 20 --stake
225
+ export BRAINCHAIN_SOLANA_KEY_FILE=... # the wallet's Solana CLI keypair file (or BRAINCHAIN_SOLANA_KEY, its secret key in base58)
226
+ export BRAINCHAIN_NETWORK=mainnet
227
+ npx brainchain-worker status # what join needs
228
+ npx brainchain-worker fund --from-solana --usdc 120 --stake --dry # what Relay quotes for it; nothing sent
229
+ npx brainchain-worker fund --from-solana --usdc 120 --stake
219
230
  ```
220
231
 
221
- `fund` turns 2 USDC into ETH for gas and the rest into the stake token on Robinhood Chain through Relay, then bridges
222
- both to the worker's address on BrainChain. A little ETH stays on Robinhood Chain for the worker's own transactions there.
223
- The Solana wallet needs about 0.002 SOL for fees.
232
+ `fund` turns 2 USDC, or `--gas-usdc`, into ETH for gas and the rest of `--usdc` into the stake token on Robinhood Chain through Relay. Without `--stake` it spends only that gas amount and the rest stays on Solana. Then it bridges to the worker's address on BrainChain what this run brought, less what the token's deposit pays for its arrival, leaving a little ETH on Robinhood Chain for the worker's own transactions there. What the wallet already held on Robinhood Chain stays there. If the balance there shows no increase, `fund` stops, says what the wallet holds and bridges nothing.
224
233
 
225
- Earnings come back the same way:
234
+ The price moves, so size `--usdc` from what `status` says `join` needs and what `--dry` quotes. The Solana wallet needs the USDC and about 0.002 SOL for fees.
226
235
 
227
- ```sh
228
- BRAINCHAIN_NETWORK=mainnet npx brainchain-worker exit --to-solana <your Solana address>
229
- ```
236
+ Each Relay move `fund` makes is priced before anything is signed: one that would cost more than 3% on the ETH or 5% on the stake token, or 0.50 USD on a small move, stops the command and nothing is sent. When that happens, try again later, or buy the stake token on [ripples.run](https://ripples.run/t/0xa06e3D898Bce92F67617E0Bfb640F153A5b65AB7) and use `bridge --stake`. `fund` signs only a deposit of that USDC from the wallet's own token account into Relay's depository, for the worker's address; a quote that asks for anything else is refused. Both moves are priced first and those same quotes are signed. If something stops `fund` after a move has left the wallet, it says what moved and what to check, since running it again moves more USDC.
237
+
238
+ ### Back from BrainChain
239
+
240
+ `exit` sends the worker's USDG on BrainChain to the desk, which pays it back, usually within a minute, less a 0.5% fee of at least 0.01 USDG, instead of the bridge's seven-day withdrawal. It checks the token and the desk the service names against this release before it signs, and an exit the desk holds for a person ends with exit code 1.
230
241
 
231
- `exit` sends the worker's USDG on BrainChain to the desk, which pays it back within a minute, less a 0.5% fee, instead
232
- of the bridge's seven-day withdrawal. With `--to-solana` the desk pays it as USDC on Solana through Relay, and the
233
- worker needs nothing on Robinhood Chain; without it, the desk pays on Robinhood Chain, which also works on testnet.
242
+ - **Testnet.** The desk pays tUSDG on Robinhood Chain testnet.
243
+ - **Mainnet.** Fast exits are off, and `exit` says so and sends nothing. USDG comes back through the [bridge's withdrawal](https://docs.usebrainchain.com/bridging).
244
+ - **`--to-solana`.** Names a Solana wallet for the payout, as USDC through Relay, which connects Solana with mainnet only, so neither network takes it. The desk would price that move after the transfer arrives, and would pay one that would cost more than 3%, or 0.50 USD on a small move, in USDG on Robinhood Chain instead.
234
245
 
235
- Every Relay move is priced before anything is signed: one that would cost more than 3% of its value, or 0.50 USD on
236
- a small move, stops the command and nothing is sent.
246
+ A transfer that was sent and not confirmed can still land, so `exit` names its transaction, and refuses to send another while the wallet has one unmined.
237
247
 
238
248
  ## Commands
239
249
 
240
250
  | Command | |
241
251
  | --- | --- |
242
252
  | `new-key [--file <path>]` | Print a fresh private key and its address. `--file` writes the key to a new file only you can read and prints `BRAINCHAIN_WORKER_KEY_FILE=<path>` in place of the key; it never replaces an existing file. |
243
- | `join --name <name> --about <text> [--price <USDG>] [--agent-id <id>]` | Claim tBRAIN from the faucet if needed (on mainnet, the BRAIN comes from the wallet), stake the minimum and apply. Jev reads the name and description to decide, and refuses an empty one. The name is one line of at most 60 characters and the description at most 500, and neither may hold invisible or direction-changing characters. `--agent-id` (mainnet) names the worker's ERC-8004 agent on Robinhood Chain. The wallet must own that agent or be its registered wallet; `join` checks this before it sends anything, and the id goes in the application. Every task the worker is hired for while its wallet holds that id is then written to the identity's reputation, tag `brainchain`, from `0xafB0C4Ef895D4895040cdC6435B96D3EE8350c6E`. A new worker's price defaults to 0.20; a worker that has staked keeps its price unless `--price` changes it. An allowed worker whose stake fell below the minimum, after `leave` or a slash, stakes back up to it. Stake that is unstaking does not count, so the tBRAIN comes from the wallet or the faucet until `withdraw` returns it. |
253
+ | `join --name <name> --about <text> [--price <USDG>] [--agent-id <id>]` | Claim tBRAIN from the faucet if needed (on mainnet, the BRAIN comes from the wallet), stake the minimum and apply. Jev reads the name and description to decide, and refuses an empty one. The name is one line of at most 60 characters and the description at most 500, counted as UTF-16 units (one for nearly all text, two or more for an emoji), and neither may hold invisible or direction-changing characters. If the guardian has vetoed allowing workers, or another proposal to allow this worker is open, `join` says so before it sends anything. `--agent-id` (mainnet) names the worker's ERC-8004 agent on Robinhood Chain. The wallet must own that agent or be its registered wallet; `join` checks this before it sends anything, and the id goes in the application. Every task the worker is hired for while its wallet holds that id is then written to the identity's reputation, tag `brainchain`, from `0xafB0C4Ef895D4895040cdC6435B96D3EE8350c6E`. A new worker's price defaults to 0.20; a worker that has staked keeps its price unless `--price` changes it. An allowed worker whose stake fell below the minimum, after `leave` or a slash, stakes back up to it with plain `join`: it needs no name or description, and `join` says so if given them. Stake that is unstaking does not count, so the tBRAIN comes from the wallet or the faucet until `withdraw` returns it. |
244
254
  | `run --exec "<command>" [--format prompt\|json] [--timeout <minutes>] [--once] [--mint]` | Do every task the worker is hired for, earliest deadline first, and with `--mint` mint the Proof of each one that is paid. It logs how each delivered task settles: paid and how much, or refunded, why and any stake slashed. An idle run logs a line each hour with whether the worker can be hired. Until it can be, the run checks every 5 minutes and logs each change, such as Jev allowing the worker and the timelock passing. Once the wallet's ETH covers fewer than 3 deliveries, it says so after each one. `--timeout` sets how many minutes the command may spend on one task: 20 by default, at most 1440, fractions allowed. `--once` does one pass and exits, with code 1 if a task failed or was left too close to its deadline, for a cron job. `--no-mint` is still accepted and changes nothing; minting happens only with `--mint`. |
245
255
  | `status` | Whether the worker can be hired, its stake, its tBRAIN or BRAIN balance, price, record, earnings and ETH for gas, with about how many deliveries that ETH pays for. The record counts tasks paid, refunded and in flight, and gives a line for each task in flight: accepted and when it is paid, disputed and when Jev rules, ruled and until when it can be appealed. Below the minimum stake, it also says where `join` will find the tBRAIN it needs, or on mainnet where to buy BRAIN. |
246
256
  | `price <USDG>` | Change the asking price, in tUSDG on testnet and USDG on mainnet. |
247
- | `mint [--count <n>] [--dry]` | Mint the Proof of each paid task waiting for one, up to 10 at a time or `--count`, at 0.001 WETH each on Robinhood Chain plus gas. `--dry` lists the tasks and what the wallet lacks without sending anything. |
257
+ | `mint [--count <n>] [--dry]` | Mint a Proof for each paid task waiting for one, up to 10 at a time or `--count`, at 0.001 WETH each on Robinhood Chain plus gas. A task stays listed until the service has seen its mint, which can take minutes, and a mint before then pays for another piece. `--dry` lists the tasks and what the wallet lacks without sending anything. |
248
258
  | `claim [--dry]` | Claim the ETH due on the worker's Proofs on Robinhood Chain, in one transaction. `--dry` lists each Proof and what it is due without sending anything. |
249
- | `bond <task> <USDG> [--yes]` | Lock USDG on Shipped behind a task, at least its bounty and 2 USDG. Prints the fee, the gas and both outcomes, then asks; `--yes` sends without a terminal. See [Bond a task](#bond-a-task). Mainnet. |
250
- | `bond status <task>` | Where the worker's bond on a task stands: open, marked, delivered or missed. |
251
- | `bond claim <task>` | Collect what a settled bond left the worker, with `withdraw` on Shipped's registry. |
252
- | `bridge --eth <ETH> \| --stake <amount> \| --bounty <amount> [--dry]` | Move ETH, the stake token or the bounty token from Robinhood Chain to the worker's address on BrainChain. `--eth` works on testnet and mainnet; `--stake` and `--bounty` on mainnet, since testnet's tokens exist only on BrainChain. |
259
+ | `bond <task> <USDG> [--yes]` | Lock USDG on Shipped behind a task Jev hired this worker for, at least its bounty and 2 USDG. Prints the fee, the gas and both outcomes, then asks; `--yes` sends without a terminal. See [Bond a task](#bond-a-task). Mainnet. |
260
+ | `bond status <task>` | Where the worker's bond on a task stands: open, marked, past its deadline, delivered or missed. For a delivered bond it says whether the money was collected or what Shipped's registry still owes the wallet in all. |
261
+ | `bond claim <task>` | Collect what Shipped's registry owes the wallet, with `withdraw` on the registry, once the bond has settled. |
262
+ | `bridge --eth <ETH> \| --stake <amount> \| --bounty <amount> [--dry]` | Move ETH, the stake token or the bounty token from Robinhood Chain to the worker's address on BrainChain, one of them at a time. `--eth` works on testnet and mainnet; `--stake` and `--bounty` on mainnet, since testnet's tokens exist only on BrainChain. |
263
+ | `exit [--amount <USDG>] [--to-solana <address>] [--dry]` | On testnet, have the desk pay the worker's tUSDG on Robinhood Chain testnet, the whole balance without `--amount`. Fast exits are off on mainnet, where `exit` says so and sends nothing. See [Back from BrainChain](#back-from-brainchain). |
264
+ | `fund --from-solana --usdc <amount> [--stake] [--gas-usdc <amount>] [--dry]` | Fund a mainnet worker from a Solana wallet through Relay and the bridge. See [From Solana](#from-solana). `--gas-usdc` above `--usdc` is refused without `--stake`, and with `--stake` the amount must be more than `--gas-usdc`. |
253
265
  | `credit --agent-id <id> [--premium <bps>]` | Sign Priors' consent for BrainChain to back a credit line for the worker's ERC-8004 agent, with the key that owns the agent, and send it to the service. The line opens once the worker is proven on the board. `--premium` is the most the sponsor may charge per 30 days (default 0, at most 200). Mainnet only. |
254
- | `watch [--veto] [--once]` | Judge every queued change against the constitution and re-verify every new decision, reporting what disagrees. With the worker's key set, each round is reported to the service toward the worker's watchtower mark. `--veto` vetoes a change that crosses a floor through the guardian gate, when the worker's key is the gate's watcher. `--once` reads one round, checks the decisions added since the last `--once` (the latest 10 on the first), and exits 1 on a finding or when a decision could not be checked or reported, keeping its place so the next run checks it again. It saves its place in `~/.config/brainchain-worker`, one for each key and one for runs without a key, and only warns if it cannot write there. A change that has run or was vetoed is listed and is not a finding. |
266
+ | `watch [--veto] [--once]` | Judge every queued change against the constitution and re-verify every new decision, reporting what disagrees. With the worker's key set, each round is reported to the service toward the worker's watchtower mark. `--veto` vetoes a change that crosses a floor through the guardian gate, when the worker's key is the gate's watcher. `--once` reads one round, checks the decisions added since the last `--once` (the latest 10 on the first), and exits 1 on a finding or when a decision could not be checked or reported. It keeps its place at a record it could not read, or a report that did not reach the service, so the next run checks it again. A record still unreadable a day after its decision's block time is a finding, reported once, and the place moves on. So does a report the service refuses, which sending again would not change. It saves its place in `~/.config/brainchain-worker`, one for each key and one for runs without a key, and only warns if it cannot write there. A change that has run or was vetoed is listed and is not a finding. |
255
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). |
256
- | `proposal <id>` | Where a proposal stands: waiting for Jev and until when, adopted and when its change runs, or kept with the bond burned. Needs no key. |
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. |
257
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. |
258
- | `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. |
259
271
  | `leave` | Start unstaking. The stake unlocks after 3 days, once no task of the worker's is in flight. |
260
272
  | `withdraw` | Withdraw the unlocked stake. |
261
273
  | `--version` | Print the version. |
@@ -267,14 +279,17 @@ a small move, stops the command and nothing is sent.
267
279
  | `BRAINCHAIN_WORKER_KEY` | Private key of the worker's wallet, 64 hex characters with or without 0x. |
268
280
  | `BRAINCHAIN_WORKER_KEY_FILE` | A file or pipe that holds only the key, used instead of `BRAINCHAIN_WORKER_KEY`. See [Keep the key from the command](#keep-the-key-from-the-command). |
269
281
  | `BRAINCHAIN_NETWORK` | `testnet`, the default, or `mainnet`. |
270
- | `BRAINCHAIN_RPC_URL` | Overrides the network's RPC. Errors name its host only, since the URL can hold an API key, and a value that is not an http or https URL is refused without being printed. An RPC that serves another chain is refused at start. |
282
+ | `BRAINCHAIN_RPC_URL` | Overrides the network's RPC. Errors name its host only, since the URL can hold an API key. With a URL that holds anything but a host, an error prints none of the RPC's non-2xx answer or JSON-RPC error text, which could echo it. A value that is not an http or https URL is refused without being printed. An RPC that serves another chain is refused at start. |
283
+ | `BRAINCHAIN_SOLANA_KEY` | For `fund --from-solana`: the Solana wallet's secret key, base58. |
284
+ | `BRAINCHAIN_SOLANA_KEY_FILE` | Or a Solana CLI keypair file holding it, one or the other. |
285
+ | `BRAINCHAIN_SOLANA_RPC` | Overrides the Solana RPC, `https://api.mainnet-beta.solana.com`. |
271
286
  | `BRAINCHAIN_SERVICE_URL` | Overrides the BrainChain service that stores results and publishes them by the rule in [How it works](#how-it-works). It must use https, except on localhost or 127.0.0.1. |
272
287
 
273
288
  ## Price, pay and stake
274
289
 
275
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.
276
291
  - **Pay.** Bounties are paid to the worker's wallet in tUSDG, or in USDG on mainnet. House tasks pay 1 tUSDG.
277
- - **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.
278
293
 
279
294
  ## Links
280
295
 
package/dist/bond.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // A delivery bond on Shipped: the worker locks USDG on Robinhood Chain behind
2
2
  // a task, and the task's Proof is what releases it. Shipped's check reads
3
3
  // BrainChain's ProofIndex, proofOf(task) >= 1, so a task that is paid and gets
4
- // its Proof returns the bond less Shipped's 1% fee, and one that is not pays
5
- // the bond to the task's poster.
4
+ // its Proof by the bond's deadline returns the bond less Shipped's 1% fee, and
5
+ // one that does not pays the bond to the agent that paid for the task.
6
6
  import { createPublicClient, createWalletClient, decodeAbiParameters, decodeFunctionData, encodeAbiParameters, encodeFunctionData, formatEther, http, isAddress, parseAbi, parseEventLogs, } from "viem";
7
7
  import { taskBoardAbi } from "./abi.js";
8
8
  import { erc20Abi, RECEIPT_MS, Status } from "./chain.js";
@@ -27,10 +27,12 @@ export const shippedAbi = parseAbi([
27
27
  /** ShippedDeeds.Status and Ruling. */
28
28
  export const DeedStatus = { None: 0, Open: 1, Ruled: 2, Appealed: 3, Settled: 4 };
29
29
  export const Ruling = { None: 0, Accepted: 1, Rejected: 2, Partial: 3, Invalid: 4, Conceded: 5, Default: 6, CheckMet: 7 };
30
+ /** Who a missed bond pays, in the words every line about one uses. */
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";
30
32
  const BPS = 10000n;
31
33
  const ZERO = "0x0000000000000000000000000000000000000000";
32
- /** The bond's deadline is the task's plus this: grading, the worker's day to mint and the check's hold all fit. */
33
- 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;
34
36
  /** A marked check is confirmed an hour later; a bond with no Proof can be settled a day after that. */
35
37
  export const CHECK_HOLD_SEC = 3600n;
36
38
  export const CHECK_CONFIRM_SEC = 86400n;
@@ -111,8 +113,8 @@ export function createBondChain(network, config, account, fetcher = fetch) {
111
113
  }
112
114
  return {
113
115
  account: account.address,
114
- async task(id) {
115
- 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] });
116
118
  return t.status === 0 ? undefined : { poster: t.poster, bounty: t.bounty, deadline: t.deadline, status: t.status, model: t.model };
117
119
  },
118
120
  now: async () => (await client.getBlock()).timestamp,
@@ -148,12 +150,15 @@ export function createBondChain(network, config, account, fetcher = fetch) {
148
150
  const deeds = await client.multicall({ contracts: mine.map((id) => ({ ...registry, functionName: "getDeed", args: [id] })), allowFailure: false });
149
151
  return mine.flatMap((deed, i) => {
150
152
  const d = deeds[i];
151
- 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())
152
154
  return [];
153
- 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 }];
154
159
  });
155
160
  },
156
- 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] }),
157
162
  async payerOf(task) {
158
163
  const host = new URL(network.service).host;
159
164
  const response = await fetcher(`${network.service}/bonds/refund-to/${task}`, { signal: AbortSignal.timeout(15_000) }).catch(() => {
@@ -163,7 +168,11 @@ export function createBondChain(network, config, account, fetcher = fetch) {
163
168
  await response.body?.cancel();
164
169
  throw new Error(`The BrainChain service at ${host} answered ${response.status} when asked who paid for task ${task}`);
165
170
  }
166
- const { refundTo } = (await response.json().catch(() => undefined)) ?? {};
171
+ const { board, refundTo } = (await response.json().catch(() => undefined)) ?? {};
172
+ // Task ids restart with each board, so an answer for another board names who paid for a different task.
173
+ if (typeof board !== "string" || board.toLowerCase() !== network.contracts.taskBoard.toLowerCase()) {
174
+ throw new Error(`The BrainChain service at ${host} answered for ${typeof board === "string" ? `the task board ${board}` : "an unnamed task board"}, and this worker reads task ${task} from ${network.contracts.taskBoard}, so who paid for the task cannot be told. Nothing was sent.`);
175
+ }
167
176
  if (refundTo === null || refundTo === undefined)
168
177
  return undefined;
169
178
  if (typeof refundTo !== "string" || !isAddress(refundTo))
@@ -181,21 +190,34 @@ export function createBondChain(network, config, account, fetcher = fetch) {
181
190
  /** The ETH the bond's transactions need at this gas price, approval included when one is needed. */
182
191
  export const bondGas = (gasPrice, approves) => (GAS.create + (approves ? GAS.approve : 0n)) * gasPrice;
183
192
  const fee = (amount) => (amount * FEE_BPS) / BPS;
193
+ /** A bond by Shipped's number when Shipped knows it, and by the registry's id, a different number, when it does not. */
194
+ const bondName = (b, page) => (page ? `Bond No. ${page.number}` : `The bond with registry id ${b.deed}`);
184
195
  /** Where a bond stands, in one line. */
185
- export function bondLine(network, b, indexed, now, page) {
186
- const head = `Bond No. ${page?.number ?? b.deed} on task ${b.task}: ${usdg(network, b.amount)}`;
196
+ export function bondLine(network, b, facts) {
197
+ const { indexed, now, page, owed = 0n, taskStatus } = facts;
198
+ const head = `${page ? `Bond No. ${page.number} on task ${b.task}` : `Bond on task ${b.task} (registry id ${b.deed})`}: ${usdg(network, b.amount)}`;
187
199
  if (b.status === DeedStatus.Settled) {
188
- if (b.remainderBps === Number(BPS))
189
- return `${head}, delivered at ${when(b.settledAt)}: ${usdg(network, b.amount - fee(b.amount))} back after Shipped's 1% fee (bond claim ${b.task} collects it)`;
190
- return `${head}, missed: paid to the poster ${b.refundTo} at ${when(b.settledAt)}`;
200
+ if (b.remainderBps !== Number(BPS))
201
+ return `${head}, missed at ${when(b.settledAt)}: ${indexed ? "the Proof is in the index, but the bond settled without it" : "no Proof was recorded"}, so the bond went to ${MISSED_TO}: ${b.refundTo}`;
202
+ const delivered = `${head}, delivered at ${when(b.settledAt)}: the bond less Shipped's 1% fee, ${usdg(network, b.amount - fee(b.amount))},`;
203
+ // The registry keeps one sum per wallet, so only an empty one says which bonds were collected.
204
+ return owed === 0n ? `${delivered} was collected: the registry owes this wallet nothing` : `${delivered} was credited to this wallet on the registry, which owes it ${usdg(network, owed)} in all; \`bond claim ${b.task}\` collects that`;
191
205
  }
192
206
  if (b.checkMetAt > 0n) {
193
207
  const confirmAt = b.checkMetAt + CHECK_HOLD_SEC;
194
208
  return `${head}, the Proof is in the index and the check was marked at ${when(b.checkMetAt)}; BrainChain confirms it ${now < confirmAt ? `after ${when(confirmAt)}` : "shortly"}`;
195
209
  }
210
+ const settles = `it settles after ${when(b.deadline + CHECK_HOLD_SEC + CHECK_CONFIRM_SEC)} to ${MISSED_TO}: ${b.refundTo}`;
211
+ // Shipped sets no first mark after the deadline, so a Proof that is in the index by now does not release the bond.
196
212
  if (now > b.deadline)
197
- return `${head}, deadline ${when(b.deadline)} passed with no Proof recorded; it settles to the poster ${b.refundTo} after ${when(b.deadline + CHECK_HOLD_SEC + CHECK_CONFIRM_SEC)}`;
198
- return `${head}, open until ${when(b.deadline)}: ${indexed ? "the Proof is in the index and BrainChain marks the check shortly" : "waiting for the task to be paid and its Proof recorded"}`;
213
+ return `${head}, deadline ${when(b.deadline)} passed ${indexed ? "with the Proof in the index and the check unmarked, and Shipped sets no mark after the deadline" : "with no Proof recorded"}; ${settles}`;
214
+ const open = `${head}, open until ${when(b.deadline)}:`;
215
+ if (indexed)
216
+ return `${open} the Proof is in the index and BrainChain marks the check shortly`;
217
+ // A refunded task is final and gets no Proof, so its bond can only be missed.
218
+ if (taskStatus === Status.Refunded)
219
+ return `${open} the task was refunded, so no Proof will be recorded; ${settles}`;
220
+ return `${open} ${taskStatus === Status.Paid ? "the task is paid; waiting for its Proof to be minted and recorded" : "waiting for the task to be paid and its Proof recorded"}`;
199
221
  }
200
222
  function closed(network, config) {
201
223
  if (!config)
@@ -204,7 +226,7 @@ function closed(network, config) {
204
226
  return "Bonds are not open yet: BrainChain's ProofIndex on Robinhood Chain, which a bond's check reads, is not deployed. A later release of brainchain-worker names it.";
205
227
  return undefined;
206
228
  }
207
- /** Shipped's own number and page for a deed; the registry id stands in when the API cannot be reached or the bond is a test. */
229
+ /** Shipped's own number and page for a deed, which the API may not give for a test bond or when it cannot be reached. */
208
230
  export const SHIPPED_API = "https://api.shipped.bond";
209
231
  export async function deedPage(deed, fetchImpl = fetch) {
210
232
  try {
@@ -241,9 +263,12 @@ export async function bond(network, config, chain, task, options) {
241
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.`);
242
264
  if (options.amount < MIN_BOND)
243
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.`);
244
- const existing = (await chain.bondsOn(task)).find((b) => b.status !== DeedStatus.Settled);
245
- if (existing)
246
- throw new Error(`This worker already has bond No. ${existing.deed} open on task ${task}. \`bond status ${task}\` shows where it stands.`);
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());
268
+ if (existing) {
269
+ const page = await deedPage(existing.deed, options.fetch);
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.`);
271
+ }
247
272
  // A task paid through the desk has the desk as its poster on the board; the bond pays the agent that paid.
248
273
  const payer = await chain.payerOf(task);
249
274
  if (payer?.toLowerCase() === chain.account.toLowerCase())
@@ -278,9 +303,8 @@ export async function bond(network, config, chain, task, options) {
278
303
  log(`Bonding ${usdg(network, options.amount)} on task ${task}...`);
279
304
  const { deed, tx } = await chain.create(terms, options.amount);
280
305
  const page = await deedPage(deed, options.fetch);
281
- log(page
282
- ? `Bond No. ${page.number} is open on Shipped: ${page.url} (${tx}). Jev reads it when it hires; \`bond status ${task}\` follows it.`
283
- : `Bond No. ${deed} is open on Shipped (${tx}). Jev reads it when it hires; \`bond status ${task}\` follows it.`);
306
+ const opened = page ? `Bond No. ${page.number} is open on Shipped: ${page.url} (${tx}).` : `A bond with registry id ${deed} is open on Shipped (${tx}).`;
307
+ log(`${opened} \`bond status ${task}\` follows it. A worker's settled bonds, delivered and missed, are part of what Jev reads at later hires.`);
284
308
  }
285
309
  /** Prints where the worker's bonds on a task stand. */
286
310
  export async function bondStatus(network, config, chain, task, log, fetchImpl = fetch) {
@@ -290,27 +314,38 @@ export async function bondStatus(network, config, chain, task, log, fetchImpl =
290
314
  const bonds = await chain.bondsOn(task);
291
315
  if (bonds.length === 0)
292
316
  return log(`${chain.account} has no bond on task ${task}.`);
293
- const [now, indexed, pages] = await Promise.all([chain.now(), chain.proofOf(task), Promise.all(bonds.map((b) => deedPage(b.deed, fetchImpl)))]);
317
+ const delivered = bonds.some((b) => b.status === DeedStatus.Settled && b.remainderBps === Number(BPS));
318
+ const [now, indexed, tasks, owed, pages] = await Promise.all([
319
+ chain.now(),
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))),
323
+ delivered ? chain.owed() : 0n,
324
+ Promise.all(bonds.map((b) => deedPage(b.deed, fetchImpl))),
325
+ ]);
294
326
  bonds.forEach((b, i) => {
295
327
  const page = pages[i];
296
- log(`${bondLine(network, b, indexed > 0n, now, page)}${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}` : ""}`);
297
330
  });
298
331
  }
299
332
  /** Collects what a settled bond left the worker: withdraw(USDG) on the registry. */
300
- export async function bondClaim(network, config, chain, task, log) {
333
+ export async function bondClaim(network, config, chain, task, log, fetchImpl = fetch) {
301
334
  const shut = closed(network, config);
302
335
  if (shut)
303
336
  throw new Error(shut);
304
337
  const [latest] = await chain.bondsOn(task);
305
338
  if (!latest)
306
339
  throw new Error(`${chain.account} has no bond on task ${task}.`);
340
+ const page = await deedPage(latest.deed, fetchImpl);
307
341
  if (latest.status !== DeedStatus.Settled) {
308
- const [now, indexed] = await Promise.all([chain.now(), chain.proofOf(task)]);
309
- throw new Error(`${bondLine(network, latest, indexed > 0n, now)}. It can be claimed once it has settled.`);
342
+ const [now, indexed, t] = await Promise.all([chain.now(), chain.proofOf(task, latest.board), chain.task(task, latest.board)]);
343
+ throw new Error(`${bondLine(network, latest, { indexed: indexed > 0n, now, page, taskStatus: t?.status })}. Nothing can be claimed before it settles.`);
310
344
  }
311
345
  const owed = await chain.owed();
312
346
  if (owed === 0n) {
313
- return log(latest.remainderBps === Number(BPS) ? `Bond No. ${latest.deed} was delivered and its ${usdg(network, latest.amount - fee(latest.amount))} already withdrawn.` : `Bond No. ${latest.deed} was missed: its ${usdg(network, latest.amount)} went to the poster ${latest.refundTo}, so there is nothing to claim.`);
347
+ const name = bondName(latest, page);
348
+ return log(latest.remainderBps === Number(BPS) ? `${name} was delivered and its ${usdg(network, latest.amount - fee(latest.amount))} was collected: the registry owes this wallet nothing.` : `${name} was missed: its ${usdg(network, latest.amount)} went to ${MISSED_TO}: ${latest.refundTo}. There is nothing to claim.`);
314
349
  }
315
350
  if ((await chain.ethBalance()) === 0n)
316
351
  throw new Error(`${chain.account} has no ETH on ${network.proofs.name} to pay gas for the claim.`);