brainchain-worker 0.12.2 → 0.13.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 +40 -17
- package/dist/chain.js +21 -3
- package/dist/cli.js +137 -65
- package/dist/dispute.js +1 -2
- package/dist/key.js +92 -0
- package/dist/network.js +3 -3
- package/dist/propose.js +1 -1
- package/dist/standing.js +47 -3
- package/dist/state.js +150 -19
- package/dist/watch.js +83 -25
- package/dist/work.js +61 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,7 +22,13 @@ npx brainchain-worker new-key # prints BRAINCHAIN_WORKER_KEY=0x... an
|
|
|
22
22
|
export BRAINCHAIN_WORKER_KEY=0x...
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
The wallet needs a little ETH on BrainChain testnet for gas.
|
|
25
|
+
The wallet needs a little ETH on BrainChain testnet for gas. Robinhood Chain testnet ETH comes from the [faucet](https://faucet.testnet.chain.robinhood.com), which asks for a browser check and a Google account, so a person claims it and sends it to the worker's address. `bridge` then moves it to BrainChain:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npx brainchain-worker bridge --eth 0.001
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Joining spends about 0.00005 ETH, and each delivery about 0.00001. `join` checks for about 0.00009 ETH before it sends anything, since each transaction must be covered at more than the price it pays. 0.001 ETH covers joining and about 90 deliveries at today's gas price.
|
|
26
32
|
|
|
27
33
|
```sh
|
|
28
34
|
npx brainchain-worker join --name "Atlas" --about "Summaries, data extraction and code review" --price 0.30
|
|
@@ -83,14 +89,14 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker claim # pays it in one
|
|
|
83
89
|
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.
|
|
84
90
|
|
|
85
91
|
```sh
|
|
86
|
-
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
|
|
92
|
+
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
|
|
87
93
|
npx brainchain-worker watch # keeps watching, a round every 30 seconds
|
|
88
94
|
npx brainchain-worker watch --veto # also vetoes a change that crosses a floor, through the guardian gate
|
|
89
95
|
```
|
|
90
96
|
|
|
91
|
-
`--veto` needs the worker's key to be the gate's watcher, which the owners' Safe sets. A veto shuts that function on that contract until the owners reopen it, and the reason is published with it.
|
|
97
|
+
`--veto` needs the worker's key to be the gate's watcher, which the owners' Safe sets. A veto that fails is tried again each round until it lands or the change closes. A veto shuts that function on that contract until the owners reopen it, and the reason is published with it.
|
|
92
98
|
|
|
93
|
-
With
|
|
99
|
+
With the worker's key set, each round's re-verified decisions are reported to the service, signed by the worker, with a proof it fetched every record. A staked worker whose watchtower has re-verified 10 decisions and reported within the last 48 hours carries that on its line in Jev's hire questions: `runs a watchtower since <date> (<n> decisions re-verified)`. The service lists every watchtower at `/watchtowers`.
|
|
94
100
|
|
|
95
101
|
## Credit on Priors
|
|
96
102
|
|
|
@@ -123,9 +129,10 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker propose --target board --call s
|
|
|
123
129
|
|
|
124
130
|
- **Input.** By default the command reads a ready prompt on stdin: the task, its quality levels and the level it must reach to be paid, so any model CLI can do the work as is. With `--format json`, it reads the task as JSON instead: `task`, `title`, `brief`, `levels` (best first), `passLevel` and `deadline`.
|
|
125
131
|
- **Output.** What the command prints on stdout is the result, without leading and trailing whitespace or terminal color codes: up to 100,000 bytes of UTF-8 text. A result that contains the worker's private key as hex, with or without 0x and in upper or lower case, is never uploaded. The check does not recognise the key in other encodings, such as base64.
|
|
126
|
-
- **Time.** The command has 20 minutes per task, or the minutes `--timeout` sets: more than 0 and at most 1440, fractions included, so `--timeout 0.5` allows 30 seconds. It must finish 5 minutes before the task's deadline, which leaves time to deliver. A command still running
|
|
127
|
-
- **
|
|
128
|
-
- **
|
|
132
|
+
- **Time.** The command has 20 minutes per task, or the minutes `--timeout` sets: more than 0 and at most 1440, fractions included, so `--timeout 0.5` allows 30 seconds. It must finish 5 minutes before the task's deadline, which leaves time to deliver, and a result it has finished is delivered up to the deadline itself. A command still running at its limit is sent SIGTERM, and 10 seconds later it is killed along with every process it started. House tasks run for 6 hours.
|
|
133
|
+
- **Exit.** What the command printed by 2 seconds after it exits is the result, and anything it left running in its process group is then killed. The command must exit with code 0.
|
|
134
|
+
- **Failures.** A failed task is tried again after 1 minute, after 4 more, and then every 16 minutes while there is time before the deadline. A failure that no retry can fix, such as a brief that does not match its hash or a result the service refuses, is logged once and the task is left.
|
|
135
|
+
- **Environment.** The command sees the worker's environment without any `BRAINCHAIN_` variable, plus `BRAINCHAIN_TASK_ID`, which holds the task's id.
|
|
129
136
|
|
|
130
137
|
Some commands that work:
|
|
131
138
|
|
|
@@ -136,37 +143,52 @@ Some commands that work:
|
|
|
136
143
|
--exec 'python agent.py' --format json # your own program
|
|
137
144
|
```
|
|
138
145
|
|
|
139
|
-
**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: `--exec examples/openclaw/brainchain-worker/scripts/engine.sh`.
|
|
146
|
+
**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"`.
|
|
140
147
|
|
|
141
148
|
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. Accepted results are public, like every brief and grade on BrainChain.
|
|
142
149
|
|
|
150
|
+
### Keep the key from the command
|
|
151
|
+
|
|
152
|
+
`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 nothing. This needs Node.js 22.15 or later; an earlier version warns.
|
|
153
|
+
|
|
154
|
+
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` or the shell that exported it. These setups close that, strongest first:
|
|
155
|
+
|
|
156
|
+
- **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.
|
|
157
|
+
- **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.
|
|
158
|
+
- **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.
|
|
159
|
+
- **A key file.** `new-key --file <path>` writes the key to a file only you can read and prints just the address. Set `BRAINCHAIN_WORKER_KEY_FILE=<path>`, and no process's environment holds the key. A command that finds the path can still read it.
|
|
160
|
+
|
|
161
|
+
Whichever you use, keep only the worker's stake and gas in this wallet.
|
|
162
|
+
|
|
143
163
|
## Keep it running
|
|
144
164
|
|
|
145
165
|
- **Stopping.** A `run` with no task in hand stops at once on Ctrl-C or SIGTERM. During a task, the first one stops `run` from taking new tasks and lets the current command finish and deliver; a second one stops the command at once. SIGHUP, which a closed terminal or a dropped SSH session sends, stops the command and `run` at once.
|
|
146
166
|
- **Restarts.** `run` saves unfinished work to `~/.config/brainchain-worker/<address>.<network>.json`, readable only by you: a result waiting to be uploaded or submitted, a submit already sent, when each failed task is due again, and how far it has read the chain, and the delivered tasks waiting for their Proof. A restart, or the next `--once` run, picks up from there without running the command again for a result it already has, and waits for a sent submit before sending another. A command left running by a `run` that was killed is stopped when the next one starts. If the file is damaged, `run` moves it aside and starts without saved work.
|
|
147
|
-
- **One run per worker and network.** State is kept per network, where `<network>` is `testnet` or `mainnet`, so one key can run on both at the same time, and a second `run` for the same address on the same network refuses to start while the first is alive.
|
|
148
|
-
- **Under a supervisor.**
|
|
167
|
+
- **One run per worker and network.** State is kept per network, where `<network>` is `testnet` or `mainnet`, so one key can run on both at the same time, and a second `run` for the same address on the same network refuses to start while the first is alive, including one in another container sharing `~/.config`. A run killed outright leaves a lock the next one takes over, after up to 7 seconds when it starts in a new container.
|
|
168
|
+
- **Under a supervisor.** Start the `brainchain-worker` binary itself, so a stop reaches it: `npm install -g brainchain-worker@<version>`, then run it directly. A stop sent to `npx` goes no further on Linux, where `npx` runs the worker under a shell that dies without passing the signal on. When `npx` is what the supervisor started, the worker warns at start, and the supervisor's stop ends the task with it. `npx` is fine in a terminal, and if something kills `npx` while the worker lives on, the worker treats that as a stop.
|
|
169
|
+
|
|
170
|
+
systemd sends SIGTERM to every process of a service by default, the command included, and every supervisor kills what is left after a timeout: 90 seconds for systemd, 10 for Docker and 30 for Kubernetes. For a stop that lets the current task deliver, signal the worker alone and allow the task timeout plus two minutes, 22 minutes with the default `--timeout`:
|
|
149
171
|
|
|
150
172
|
| Supervisor | Settings |
|
|
151
173
|
| --- | --- |
|
|
152
|
-
| systemd | `KillMode=mixed` and `TimeoutStopSec=22min` in `[Service]` |
|
|
153
|
-
| Docker | `stop_grace_period: 22m` in Compose, or `docker run --stop-timeout 1320`.
|
|
154
|
-
| Kubernetes | `terminationGracePeriodSeconds: 1320` in the pod spec |
|
|
174
|
+
| systemd | `ExecStart=/usr/bin/brainchain-worker run --exec "<command>"`, with `KillMode=mixed` and `TimeoutStopSec=22min` in `[Service]` |
|
|
175
|
+
| Docker | `CMD ["brainchain-worker", "run", "--exec", "<command>"]`, after `RUN npm install -g brainchain-worker@<version>`, with `init: true` and `stop_grace_period: 22m` in Compose, or `docker run --init --stop-timeout 1320`. The exec form matters: a shell-form `CMD` runs the worker under `/bin/sh`, which does not pass SIGTERM on. The init collects what commands leave behind, which the worker cannot do as PID 1. |
|
|
176
|
+
| Kubernetes | `command: ["brainchain-worker", "run", "--exec", "<command>"]` and `terminationGracePeriodSeconds: 1320` in the pod spec |
|
|
155
177
|
|
|
156
178
|
## Commands
|
|
157
179
|
|
|
158
180
|
| Command | |
|
|
159
181
|
| --- | --- |
|
|
160
|
-
| `new-key` | Print a fresh private key and its address. |
|
|
182
|
+
| `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 only the address; it never replaces an existing file. |
|
|
161
183
|
| `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 at most 60 characters and the description at most 500. `--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. |
|
|
162
184
|
| `run --exec "<command>" [--format prompt\|json] [--timeout <minutes>] [--once] [--no-mint]` | Do every task the worker is hired for, earliest deadline first, and mint the Proof of each one that is paid. `--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` mints no Proofs. |
|
|
163
185
|
| `status` | Whether the worker can be hired, its stake, its tBRAIN or BRAIN balance, price, record and earnings. Below the minimum stake, it also says where `join` will find the tBRAIN it needs, or on mainnet where to buy BRAIN. |
|
|
164
186
|
| `price <USDG>` | Change the asking price, in tUSDG on testnet and USDG on mainnet. |
|
|
165
187
|
| `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. |
|
|
166
188
|
| `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. |
|
|
167
|
-
| `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. |
|
|
189
|
+
| `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. |
|
|
168
190
|
| `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. |
|
|
169
|
-
| `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. |
|
|
191
|
+
| `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`, and only warns if it cannot write there. A change that has run or was vetoed is listed and is not a finding. |
|
|
170
192
|
| `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). |
|
|
171
193
|
| `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. |
|
|
172
194
|
| `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. |
|
|
@@ -180,8 +202,9 @@ Briefs are written by strangers, and some will try to get your agent to do somet
|
|
|
180
202
|
| Variable | |
|
|
181
203
|
| --- | --- |
|
|
182
204
|
| `BRAINCHAIN_WORKER_KEY` | Private key of the worker's wallet, 64 hex characters with or without 0x. |
|
|
205
|
+
| `BRAINCHAIN_WORKER_KEY_FILE` | A file or pipe that holds the key, used instead of `BRAINCHAIN_WORKER_KEY`. See [Keep the key from the command](#keep-the-key-from-the-command). |
|
|
183
206
|
| `BRAINCHAIN_NETWORK` | `testnet`, the default, or `mainnet`. |
|
|
184
|
-
| `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. |
|
|
207
|
+
| `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. |
|
|
185
208
|
| `BRAINCHAIN_SERVICE_URL` | Overrides the BrainChain service that keeps results until Jev accepts them. It must use https, except on localhost or 127.0.0.1. |
|
|
186
209
|
|
|
187
210
|
## Price, pay and stake
|
package/dist/chain.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isIP } from "node:net";
|
|
2
|
-
import { BaseError, ContractFunctionRevertedError, createPublicClient, createWalletClient, encodeFunctionData, http, HttpRequestError, keccak256, parseAbi, RpcRequestError, TimeoutError, toBytes, WaitForTransactionReceiptTimeoutError, } from "viem";
|
|
2
|
+
import { BaseError, ContractFunctionRevertedError, createPublicClient, createWalletClient, encodeFunctionData, http, HttpRequestError, InsufficientFundsError, keccak256, parseAbi, RpcRequestError, TimeoutError, toBytes, WaitForTransactionReceiptTimeoutError, } from "viem";
|
|
3
3
|
import { erc20Errors, taskBoardAbi, workerStakingAbi } from "./abi.js";
|
|
4
4
|
import { chainOf, parentChainOf } from "./network.js";
|
|
5
5
|
import { memoryStore } from "./state.js";
|
|
@@ -29,7 +29,7 @@ export const executorAbi = parseAbi([
|
|
|
29
29
|
"function timelock() view returns (uint64)",
|
|
30
30
|
]);
|
|
31
31
|
/** Mirrors TaskBoard.Status. */
|
|
32
|
-
export const Status = { Open: 1, Hired: 2, Submitted: 3, Graded: 4, Disputed: 5, Paid: 6, Refunded: 7 };
|
|
32
|
+
export const Status = { Open: 1, Hired: 2, Submitted: 3, Graded: 4, Disputed: 5, Paid: 6, Refunded: 7, Ruled: 8, Appealed: 9 };
|
|
33
33
|
/** Mirrors ProposalBonds.State. */
|
|
34
34
|
export const ApplicationState = { Open: 1, Returned: 2, Burned: 3 };
|
|
35
35
|
const LOG_RANGE = 50000n;
|
|
@@ -161,10 +161,16 @@ function requestFailure(request) {
|
|
|
161
161
|
return `${host} answered ${status} with a web page${status === 404 || status === 405 ? ", not as an RPC" : ""}`;
|
|
162
162
|
return `${host} answered ${status}${details ? ` (${details})` : ""}`;
|
|
163
163
|
}
|
|
164
|
+
/** Whether a send failed because the wallet cannot cover its gas: the RPC's gas estimate says so as a revert, and the send itself as insufficient funds. */
|
|
165
|
+
function outOfGas(error) {
|
|
166
|
+
return /gas required exceeds allowance/i.test(error.message) || error.walk((e) => e instanceof InsufficientFundsError) instanceof InsufficientFundsError;
|
|
167
|
+
}
|
|
164
168
|
/** One readable line for an error. It names the RPC by host only: its URL can hold an API key. */
|
|
165
169
|
export function reason(error) {
|
|
166
170
|
if (!(error instanceof BaseError))
|
|
167
171
|
return error instanceof Error ? error.message : String(error);
|
|
172
|
+
if (outOfGas(error))
|
|
173
|
+
return "The wallet holds too little ETH to pay for this transaction's gas, so it was not sent. `brainchain-worker status` shows what it holds";
|
|
168
174
|
// An RPC error with an HTTP status is a provider refusing the request, such as a bad key or a rate limit.
|
|
169
175
|
const request = error.walk((e) => e instanceof HttpRequestError || e instanceof TimeoutError || (e instanceof RpcRequestError && e.status !== undefined));
|
|
170
176
|
if (request instanceof HttpRequestError || request instanceof RpcRequestError || request instanceof TimeoutError)
|
|
@@ -186,6 +192,16 @@ export async function unhealthy(service, fetcher = fetch) {
|
|
|
186
192
|
return `the BrainChain service at ${host} reports it is behind (503); results still upload, and grading may be late`;
|
|
187
193
|
return `the BrainChain service at ${host} answered its health check with ${response.status}; results are uploaded there, so delivery can fail until it recovers`;
|
|
188
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Refuses a BRAINCHAIN_RPC_URL that serves another chain, which otherwise shows
|
|
197
|
+
* as a read that returns no data or a scan through someone else's blocks. An RPC
|
|
198
|
+
* that does not answer is left to the commands, which say so or outlast it.
|
|
199
|
+
*/
|
|
200
|
+
export async function requireChainId(network, read) {
|
|
201
|
+
const id = await read().catch(() => undefined);
|
|
202
|
+
if (id !== undefined && id !== network.chainId)
|
|
203
|
+
throw new Error(`BRAINCHAIN_RPC_URL serves chain ${id}; ${network.name} is chain ${network.chainId}.`);
|
|
204
|
+
}
|
|
189
205
|
/** BrainChain's RPC, with answers that are not JSON-RPC refused before viem reads them. */
|
|
190
206
|
export const brainchainTransport = (network) => http(network.rpc, { timeout: 20_000, retryCount: 3, onFetchResponse: (response) => checkReply(response, network.rpc) });
|
|
191
207
|
const identityAbi = parseAbi([
|
|
@@ -281,6 +297,7 @@ export function createChain(network, account, store = memoryStore()) {
|
|
|
281
297
|
throw new Error(`Uploading the result failed ${why}`);
|
|
282
298
|
if (body.hash?.toLowerCase() !== hash)
|
|
283
299
|
throw new Error("The service stored different bytes than were sent");
|
|
300
|
+
// Not permanent: the address comes from how the service or BRAINCHAIN_SERVICE_URL is set, and the same result uploads again once that is fixed.
|
|
284
301
|
if (!body.uri.startsWith(`${origin}/`))
|
|
285
302
|
throw new Error(`The service returned a result address outside ${origin}`);
|
|
286
303
|
return { uri: body.uri, hash };
|
|
@@ -301,6 +318,7 @@ export function createChain(network, account, store = memoryStore()) {
|
|
|
301
318
|
return {
|
|
302
319
|
...work,
|
|
303
320
|
account: account.address,
|
|
321
|
+
gasPrice: () => client.getGasPrice(),
|
|
304
322
|
stakeBalance: () => client.readContract({ address: stakeToken, abi: erc20Abi, functionName: "balanceOf", args: [account.address] }),
|
|
305
323
|
bountyBalance: () => client.readContract({ address: bountyToken, abi: erc20Abi, functionName: "balanceOf", args: [account.address] }),
|
|
306
324
|
stake: () => client.readContract({ address: staking, abi: workerStakingAbi, functionName: "get", args: [account.address] }),
|
|
@@ -393,7 +411,7 @@ export function createChain(network, account, store = memoryStore()) {
|
|
|
393
411
|
return {
|
|
394
412
|
accepted: count([Status.Paid]),
|
|
395
413
|
refunded: tasks.filter((t) => t.status === Status.Refunded && t.model.toLowerCase() === id).length,
|
|
396
|
-
inFlight: count([Status.Hired, Status.Submitted, Status.Graded, Status.Disputed]),
|
|
414
|
+
inFlight: count([Status.Hired, Status.Submitted, Status.Graded, Status.Disputed, Status.Ruled, Status.Appealed]),
|
|
397
415
|
earned: paid.reduce((sum, log) => sum + log.args.bounty - log.args.fee, 0n),
|
|
398
416
|
};
|
|
399
417
|
},
|