@moleculeagora/cli
v5.1.1
Published
Command-line client for Agora, a decentralized bounty marketplace for science. Lets Posters, Solvers, and Guardians interact with Agora's public API and on-chain contracts.
Downloads
5,297
Readme
agora-cli
A command-line client for Agora, a decentralized bounty marketplace for science. It lets a Poster, Solver, or Guardian carry out their part of a bounty — publish, submit, decrypt, settle, or claim — from their own machine. Solver submission can hand each API approval and the final transaction to the Solver's external wallet; claim can prepare a transaction for an external wallet from its public address. A Guardian may process-inject its own Safe owner signer to authenticate its published report and submit its own native settlement confirmation; Agora operations never receive that signer.
This CLI is primarily meant to accompany Agora's agent skills. If you're looking for role-specific workflow guidance, use the public Poster, Solver, or Guardian bundle.
Install
npm install -g @moleculeagora/cli
agora-cli --helpOr run it without installing:
npx @moleculeagora/cli --helpCommands
poster:publish-fund— publish an approvedbounty_challenge.mdand fund escrowcontent-commitment— derive a challenge Markdown commitment offlinesolver:submit— seal and submit a solutionguardian:judgeable— pull the bounties ready to judge, each with its settlement timeoutguardian:open— decrypt a solver's submissionguardian:submit— publish a Guardian verdict and, when configured, its native Safe-owner receipt off-chainguardian:confirm-settlement— independently verify the current registered settlement and submit only this Guardian's native Safe confirmationsettlement:candidates— list every bounty whose Guardian reports are ready to settlesettlement:prepare— publish one bounty's consensus record and print its exactAgoraHub.settlecalldatasettlement:register— register that transaction on the Guardian Safe with zero confirmationssettlement:execute— execute the one current threshold-approved Safe transactionsettlement:reject— release a Safe nonce held by a settlement that can no longer completesettlement:heartbeat— run the five steps above in order, as the hosted job doessettlement:verify— reproduce the registered settlement calldata without signing or writingtimeout:overdue— prepare or submit permissionless timeout settlements for overdue bountiesclaim— claim a queued payoutverification-record— fetch or create the advisory receipt automatically derived from a bounty's finalized outcome
Run agora-cli help <command> for a command's exact usage and required
environment variables.
The settlement: commands are the settlement coordinator's own steps. Each
does one thing, prints one JSON line, and is safe to run by hand during an
incident; settlement:heartbeat composes them in the order an operator would.
They discover authenticated Guardian report receipts from the Safe service,
reconstruct each committed roster, and deterministically select the next
quorum-ready bounty. They register and broadcast with a Safe delegate key
that can never approve, and never load a Safe owner key.
Supporting Guardians use guardian:confirm-settlement, which discovers that
Guardian's authenticated Safe-owner report receipt and exact published report
bytes, reconstructs the exact current Safe transaction, and submits only that
Guardian's native confirmation. It also recognises a Safe rejection, and
re-proves independently that the settlement being released really can no longer
complete. Repeating it after success is safe; it never proposes, executes, or
relays.
Environment variables
Solver and claim commands never need Agora infrastructure credentials. Solver
submission accepts a public address and client-provided signatures. Claim can
likewise use a public address and print one claim.transaction_prepared JSON
record for an external wallet to sign and broadcast.
| Role | Recommended wallet input | Optional local manual test |
|---|---|---|
| Poster | AGORA_POSTER_PRIVATE_KEY | — |
| Solver | --solver-address <address> | AGORA_SOLVER_PRIVATE_KEY |
| Claimant | --claimant-address <address> | AGORA_CLAIMANT_PRIVATE_KEY |
guardian:open separately requires AGORA_GUARDIAN_PRIVATE_KEYS_JSON to
decrypt eligible submissions. Those HPKE keys are not wallet signing keys and
must remain in Guardian secret storage.
The CLI keeps only the older narrow live Base Sepolia defaults used by Solver
submit, claim, timeout, and the variables explicitly listed as defaulted by
each command's help. Poster, Guardian, and settlement commands require the
command-specific public deployment values listed by agora-cli help <command>
in addition to their role-owned identity or secret material. The public role
bundles publish the coherent supported live configuration. Do not combine
values from different deployments.
On managed chains, guardian:submit publishes a native Safe message receipt
when AGORA_GUARDIAN_ID, AGORA_SAFE_API_KEY, and
AGORA_GUARDIAN_SAFE_OWNER_PRIVATE_KEY are present. The Guardian ID must match
a key_id in the bounty's committed recipient set. The same Safe API key and
owner key, plus the shared Safe address in
AGORA_GUARDIAN_SETTLEMENT_SIGNER, are consumed by the zero-argument
guardian:confirm-settlement command. The live Safe address is published in the
supported public Guardian bundle and must be supplied to the CLI explicitly.
The command binds the signer to its authenticated receipt and exact published
report before submitting one confirmation. The owner key remains in Guardian
custody and is injected only for those command processes; Agora operations
never receive it.
guardian:submit, guardian:confirm-settlement, and every settlement:
command require AGORA_RPC_URL, AGORA_CHAIN_ID, and AGORA_HUB_ADDRESS. The
settlement write steps additionally need AGORA_SAFE_COORDINATOR_PRIVATE_KEY.
AGORA_API_BASE_URL defaults to the live deployment for commands that use the
public API. Run agora-cli help <command> for the exact contract of every
command.
solver:submit --solver-address <address> <bounty_id> <artifact_dir> fetches
and verifies the bounty's published Guardian recipient set automatically before
encrypting the submission. Before each authenticated API write it prints one
newline-delimited JSON record with event submit.approval_required. Sign that
record's exact typed_data value as EIP-712 with the declared wallet, then
write only the returned 0x-prefixed signature plus a newline to the command's
stdin. A retry emits fresh typed data and requires a fresh signature. EOA,
hardware, custodial, and contract-wallet clients can all keep custody of their
own signing material.
After preparation, the command prints submit.transaction_prepared with
locally rebuilt AgoraHub.submit calldata for the same wallet to sign and
broadcast. AGORA_SOLVER_PRIVATE_KEY remains an optional convenience: when
--solver-address is omitted, the CLI signs the same approvals and broadcasts
the same locally rebuilt transaction itself. Never send a raw private key to
Agora or place one in submitted artifacts.
Authentication
Authenticated routes require one EIP-712 signature per request — no login, session, or stored credential. Sign it with your own wallet key and send it as a single header:
Authorization: Agora-Approval base64(address~signature~blockNumber~blockHash)The browser Review action, poster:publish-fund, and solver:submit build this
header with buildAuthorizationHeader. Publication preparation also requires
one poster_address query parameter. That query is part of the signed request,
and the route binds the verified signer to the declared Poster. Solver upload
and preparation bind the verified signer to the Solver address in the route.
The declared actor and a valid approval are always required.
Example
Signing POST /api/bounty-challenge-specs/prepare-publication?poster_address=0x8fee...500
with the exact body # Bounty\n against agora-v0.vercel.app, chain 84532,
Hub 0x1111…1111:
{
"domain": {
"name": "Agora",
"version": "1",
"chainId": 84532,
"verifyingContract": "0x1111111111111111111111111111111111111111"
},
"types": {
"ApiRequestApproval": [
{ "name": "endpointId", "type": "string" },
{ "name": "audience", "type": "string" },
{ "name": "bodyHash", "type": "bytes32" },
{ "name": "query", "type": "string" },
{ "name": "blockNumber", "type": "uint256" },
{ "name": "blockHash", "type": "bytes32" }
]
},
"primaryType": "ApiRequestApproval",
"message": {
"endpointId": "POST /api/bounty-challenge-specs/prepare-publication",
"audience": "agora-v0.vercel.app",
"bodyHash": "0xe4cd779cdf5536670d65f436425387c91cbc01ec355048824bdf65d0dbcf7f1d",
"query": "poster_address=0x8fee...500",
"blockNumber": "44783011",
"blockHash": "0xfbc4ce4806ce928d77c12d56d85959ed6c597c702a3a393ae4f52d75ec178456"
}
}Sign that with eth_signTypedData_v4, then send:
Authorization: Agora-Approval base64(0x8fee...500~0x1111...111~44783011~0xfbc4...456)bodyHash is keccak256("# Bounty\n"). Only
address, signature, blockNumber, blockHash go in the header; the
server recomputes endpointId/audience/bodyHash/query itself from the
request it received and checks the signature against those, so anything
mismatched (wrong path, tampered query, stale block, ...) just fails to verify.
Fields
endpointId—"<METHOD> <path>"(pathname only, no query).audience— the lowercased host you're addressing, binding the signature to one deployment.bodyHash—keccak256of the exact bytes sent on the wire (UTF-8 for strings). Hash and send the same bytes — don't hash one representation and transmit another.query— the query string canonicalized viaURLSearchParams;""if absent.blockNumber/blockHash— a recent block on the target chain; this is the freshness/replay bound.
The CLI implements this signing flow for its authenticated commands, so normal CLI users do not construct the header themselves.
