brainchain-worker 0.4.2 → 0.5.1
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 +17 -0
- package/dist/chain.js +3 -1
- package/dist/network.js +7 -7
- package/dist/propose.js +38 -11
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -70,6 +70,21 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker claim # pays it in one
|
|
|
70
70
|
|
|
71
71
|
`claim` asks the BrainChain service which Proofs the wallet holds, reads what each is due and claims every one with income due in a single transaction, up to 200 at a time. Income always goes to the Proof's current holder. The transaction is sent from the worker's key on Robinhood Chain, so the wallet needs a little ETH there for gas. Testnet Proofs earn no income.
|
|
72
72
|
|
|
73
|
+
## Propose a change
|
|
74
|
+
|
|
75
|
+
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:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
BRAINCHAIN_NETWORK=mainnet npx brainchain-worker propose --target board --call setFee \
|
|
79
|
+
--args '[200, "0xdFeFbdBF680568e8DC44abbbd657c0701964cC8c"]' \
|
|
80
|
+
--title "Lower the fee to 2%" --rationale "Why the change is better, in up to 1,500 characters." --dry
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- **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`.
|
|
84
|
+
- **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.
|
|
85
|
+
- **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.
|
|
86
|
+
- **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.
|
|
87
|
+
|
|
73
88
|
## Your command
|
|
74
89
|
|
|
75
90
|
`run --exec "<command>"` runs the command through the shell once per task.
|
|
@@ -115,6 +130,8 @@ Briefs are written by strangers, and some will try to get your agent to do somet
|
|
|
115
130
|
| `price <USDG>` | Change the asking price, in tUSDG on testnet and USDG on mainnet. |
|
|
116
131
|
| `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. |
|
|
117
132
|
| `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. |
|
|
133
|
+
| `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). |
|
|
134
|
+
| `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. |
|
|
118
135
|
| `leave` | Start unstaking. The stake unlocks after 3 days, once no task of the worker's is in flight. |
|
|
119
136
|
| `withdraw` | Withdraw the unlocked stake. |
|
|
120
137
|
| `--version` | Print the version. |
|
package/dist/chain.js
CHANGED
|
@@ -186,9 +186,11 @@ export async function unhealthy(service, fetcher = fetch) {
|
|
|
186
186
|
return `the BrainChain service at ${host} reports it is behind (503); results still upload, and grading may be late`;
|
|
187
187
|
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
188
|
}
|
|
189
|
+
/** BrainChain's RPC, with answers that are not JSON-RPC refused before viem reads them. */
|
|
190
|
+
export const brainchainTransport = (network) => http(network.rpc, { timeout: 20_000, retryCount: 3, onFetchResponse: (response) => checkReply(response, network.rpc) });
|
|
189
191
|
export function createChain(network, account, store = memoryStore()) {
|
|
190
192
|
const chain = chainOf(network);
|
|
191
|
-
const transport =
|
|
193
|
+
const transport = brainchainTransport(network);
|
|
192
194
|
// BrainChain mines a block per transaction, so receipts are polled every second.
|
|
193
195
|
const client = createPublicClient({ chain, transport, pollingInterval: 1_000 });
|
|
194
196
|
const wallet = createWalletClient({ account, chain, transport });
|
package/dist/network.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { defineChain, fallback, http, zeroAddress } from "viem";
|
|
2
|
-
/** BrainChain testnet, release 3.
|
|
2
|
+
/** BrainChain testnet, release 3.4.0. */
|
|
3
3
|
export const TESTNET = {
|
|
4
4
|
id: "testnet",
|
|
5
5
|
name: "BrainChain testnet",
|
|
6
6
|
chainId: 274630,
|
|
7
7
|
rpc: "https://rpc.testnet.usebrainchain.com",
|
|
8
8
|
service: "https://brain.testnet.usebrainchain.com",
|
|
9
|
-
fromBlock:
|
|
9
|
+
fromBlock: 1793n,
|
|
10
10
|
explorer: "https://usebrainchain.com/testnet",
|
|
11
11
|
stakeSymbol: "tBRAIN",
|
|
12
12
|
bountySymbol: "tUSDG",
|
|
@@ -22,11 +22,11 @@ export const TESTNET = {
|
|
|
22
22
|
gas: "Get Robinhood Chain testnet ETH for gas at https://faucet.testnet.chain.robinhood.com",
|
|
23
23
|
},
|
|
24
24
|
contracts: {
|
|
25
|
-
taskBoard: "
|
|
26
|
-
staking: "
|
|
27
|
-
bonds: "
|
|
28
|
-
executor: "
|
|
29
|
-
registry: "
|
|
25
|
+
taskBoard: "0x0beA77333E26a257983fCc59B8D857730b5B1D2B",
|
|
26
|
+
staking: "0x09468dE253517bD5d91b92115cce45798F300Cf0",
|
|
27
|
+
bonds: "0x34279448dc42b84B479daeF092749020017F001B",
|
|
28
|
+
executor: "0xFFf56B7B194C190152b17C086c36A2a79f050397",
|
|
29
|
+
registry: "0xcE777c32824fd5963a9C31C70e9B1c5D9C4ED16e",
|
|
30
30
|
stakeToken: "0xc717546497343911750768335b649b86d0a6eCcf",
|
|
31
31
|
bountyToken: "0xC4ec72794bF3Ce9ba1DE4B38085464c2eb0cDCf2",
|
|
32
32
|
},
|
package/dist/propose.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { BaseError, createPublicClient, createWalletClient, decodeFunctionData, encodeAbiParameters, encodeFunctionData,
|
|
2
|
-
import {
|
|
1
|
+
import { BaseError, createPublicClient, createWalletClient, decodeErrorResult, decodeFunctionData, encodeAbiParameters, encodeFunctionData, ExecutionRevertedError, getAddress, isAddress, isHex, keccak256, parseAbi, parseEventLogs, size, slice, } from "viem";
|
|
2
|
+
import { erc20Errors } from "./abi.js";
|
|
3
|
+
import { bondsAbi, brainchainTransport, erc20Abi, executorAbi, RECEIPT_MS } from "./chain.js";
|
|
3
4
|
import { chainOf } from "./network.js";
|
|
4
5
|
import { stake, when } from "./standing.js";
|
|
5
6
|
/** Every call the executor can be asked to make, by the contract it calls. */
|
|
@@ -25,9 +26,15 @@ const proposalsAbi = parseAbi([
|
|
|
25
26
|
"function decideWithin() view returns (uint64)",
|
|
26
27
|
]);
|
|
27
28
|
const executorReadsAbi = parseAbi([
|
|
29
|
+
"function vetoed(address target, bytes4 selector) view returns (bool)",
|
|
28
30
|
"function vetoedCall(bytes32) view returns (bool)",
|
|
29
31
|
"function changeThresholdBps() view returns (uint16)",
|
|
30
32
|
]);
|
|
33
|
+
// The reverts a governed call can hit, so a rehearsal names the rule it broke.
|
|
34
|
+
const callErrorsAbi = [
|
|
35
|
+
...parseAbi(["error BadConfig()", "error BadFee()", "error ZeroAddress()", "error NotOwner()", "error NotSelf()", "error NotGuardian()"]),
|
|
36
|
+
...erc20Errors,
|
|
37
|
+
];
|
|
31
38
|
/** Mirrors ProposalBonds.State. */
|
|
32
39
|
const State = { None: 0, Open: 1, Returned: 2, Burned: 3 };
|
|
33
40
|
export class ProposalError extends Error {
|
|
@@ -167,7 +174,7 @@ export function caseURI(call, input) {
|
|
|
167
174
|
const LOG_RANGE = 50000n;
|
|
168
175
|
export function createProposals(network, account) {
|
|
169
176
|
const chain = chainOf(network);
|
|
170
|
-
const transport =
|
|
177
|
+
const transport = brainchainTransport(network);
|
|
171
178
|
const client = createPublicClient({ chain, transport, pollingInterval: 1_000 });
|
|
172
179
|
const wallet = createWalletClient({ account, chain, transport });
|
|
173
180
|
const { bonds, executor, stakeToken } = network.contracts;
|
|
@@ -196,19 +203,39 @@ export function createProposals(network, account) {
|
|
|
196
203
|
decideWithin: () => client.readContract({ ...proposals, functionName: "decideWithin" }),
|
|
197
204
|
changeThresholdBps: async () => BigInt(await client.readContract({ address: executor, abi: executorReadsAbi, functionName: "changeThresholdBps" })),
|
|
198
205
|
timelock: () => client.readContract({ address: executor, abi: executorAbi, functionName: "timelock" }),
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
// A set 3.4.0 executor shuts a function (target and selector) until the admin reopens it; earlier ones block the exact call.
|
|
207
|
+
vetoed: async (call) => {
|
|
208
|
+
try {
|
|
209
|
+
return await client.readContract({ address: executor, abi: executorReadsAbi, functionName: "vetoed", args: [call.address, slice(call.data, 0, 4)] });
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return client.readContract({
|
|
213
|
+
address: executor,
|
|
214
|
+
abi: executorReadsAbi,
|
|
215
|
+
functionName: "vetoedCall",
|
|
216
|
+
args: [keccak256(encodeAbiParameters([{ type: "address" }, { type: "bytes" }], [call.address, call.data]))],
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
},
|
|
205
220
|
async rehearse(call) {
|
|
206
221
|
try {
|
|
207
222
|
await client.call({ account: executor, to: call.address, data: call.data });
|
|
208
223
|
return undefined;
|
|
209
224
|
}
|
|
210
225
|
catch (error) {
|
|
211
|
-
|
|
226
|
+
// Only a revert says the call would fail; an RPC that cannot be reached is a failure of its own.
|
|
227
|
+
const reverted = error instanceof BaseError ? error.walk((e) => e instanceof ExecutionRevertedError) : null;
|
|
228
|
+
if (!(error instanceof BaseError) || !reverted)
|
|
229
|
+
throw error;
|
|
230
|
+
const raw = error.walk((e) => typeof e.data === "string");
|
|
231
|
+
try {
|
|
232
|
+
if (raw?.data)
|
|
233
|
+
return `${decodeErrorResult({ abi: callErrorsAbi, data: raw.data }).errorName}, the contract's check on these values`;
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Not one of the contracts' own errors; the node's message says more.
|
|
237
|
+
}
|
|
238
|
+
return error.shortMessage;
|
|
212
239
|
}
|
|
213
240
|
},
|
|
214
241
|
async propose(call, uri, bond) {
|
|
@@ -259,7 +286,7 @@ export async function propose(network, chain, call, uri, options) {
|
|
|
259
286
|
chain.rehearse(call),
|
|
260
287
|
]);
|
|
261
288
|
if (vetoed)
|
|
262
|
-
throw new ProposalError(`The guardian vetoed ${call.text} on the ${call.target}
|
|
289
|
+
throw new ProposalError(`The guardian vetoed ${call.text} on the ${call.target}, so the executor does not queue it until the admin reopens that function`);
|
|
263
290
|
if (revert)
|
|
264
291
|
throw new ProposalError(`${call.text} would fail if the executor made it now (${revert}), so the bond would be burned for nothing`);
|
|
265
292
|
log(`Proposal on ${network.name}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainchain-worker",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Earn on BrainChain: stake, apply,
|
|
3
|
+
"version": "0.5.1",
|
|
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>",
|
|
7
7
|
"homepage": "https://docs.usebrainchain.com/run-a-worker",
|