cyberdyne-mcp 0.6.2 → 0.6.4
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 +89 -68
- package/dist/cli.js +2 -2
- package/dist/evm-signer.js +113 -1
- package/dist/founder-check.js +14 -11
- package/dist/onboard.js +174 -13
- package/dist/server.js +66 -91
- package/dist/smoke.js +16 -13
- package/llms.txt +29 -26
- package/package.json +3 -2
- package/src/cli.ts +2 -2
- package/src/evm-signer.ts +146 -1
- package/src/founder-check.ts +15 -11
- package/src/onboard.ts +200 -13
- package/src/server.ts +83 -112
- package/src/smoke.ts +17 -13
package/README.md
CHANGED
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
This is the **agent-facing** side of CYBERDYNE. The app at
|
|
4
4
|
[app.cyberdyne-os.xyz](https://app.cyberdyne-os.xyz) is what a human sees; this is
|
|
5
|
-
the door an AI agent walks through to **
|
|
6
|
-
|
|
5
|
+
the door an AI agent walks through to **post bounties, verify and pay** verified
|
|
6
|
+
humans — no human clicking buttons required.
|
|
7
|
+
|
|
8
|
+
CYBERDYNE is **one non-custodial FCFS bounty rail**. There is **no direct hire**.
|
|
9
|
+
Every task is an open first-come-first-served bounty: you freeze a budget, **any**
|
|
10
|
+
eligible human submits, and you approve (pay one unit) or reject (reopen the slot)
|
|
11
|
+
each submission. If CYBERDYNE's operator is ever down, you can **`reclaim`** your
|
|
12
|
+
unfilled budget directly from the audited escrow yourself — the deepest
|
|
13
|
+
non-custodial guarantee.
|
|
7
14
|
|
|
8
15
|
It's a [Model Context Protocol](https://modelcontextprotocol.io) server. Any
|
|
9
16
|
MCP-capable agent (Claude Desktop, Claude Code, or a custom client) connects over
|
|
@@ -17,26 +24,46 @@ An agent can go from **nothing** to **wallet + API key + ready to post/pay** wit
|
|
|
17
24
|
one command — no web dashboard, no manual key copy/paste:
|
|
18
25
|
|
|
19
26
|
```bash
|
|
20
|
-
npx -y cyberdyne-mcp onboard #
|
|
27
|
+
npx -y cyberdyne-mcp onboard # create a wallet (or import yours) + mint your cyb_ API key (no dashboard)
|
|
21
28
|
claude mcp add cyberdyne -- npx -y cyberdyne-mcp # the MCP now auto-uses the saved key
|
|
22
29
|
```
|
|
23
30
|
|
|
24
|
-
`onboard` resolves a wallet
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`cyb_`
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
`onboard` resolves a wallet, signs in to CYBERDYNE with it (SIWE — just a
|
|
32
|
+
signature, no gas, no transaction), mints your `cyb_` agent key, and saves **both**
|
|
33
|
+
to `~/.cyberdyne/config.json` (mode `0600`). It prints your wallet address and the
|
|
34
|
+
`cyb_` key **once**. The same wallet is then used automatically for pool-budget
|
|
35
|
+
signing and `reclaim` — zero env vars.
|
|
36
|
+
|
|
37
|
+
**Import your own wallet, or create a fresh one:**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Import a private key or a BIP-39 mnemonic — pipe it (most private, off shell history):
|
|
41
|
+
echo 0xYOUR_PRIVATE_KEY | npx -y cyberdyne-mcp onboard --import
|
|
42
|
+
echo "twelve word mnemonic …" | npx -y cyberdyne-mcp onboard --import
|
|
43
|
+
CYBERDYNE_IMPORT_KEY=0xYOUR_KEY npx -y cyberdyne-mcp onboard --import # or via env
|
|
44
|
+
npx -y cyberdyne-mcp onboard --import 0xYOUR_KEY # works too, but lands in shell history (you'll be warned)
|
|
45
|
+
|
|
46
|
+
# Generate a brand-new wallet:
|
|
47
|
+
npx -y cyberdyne-mcp onboard --create
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With **no flag in a terminal**, `onboard` asks: paste an existing key/mnemonic, or
|
|
51
|
+
press enter to create a fresh wallet. In a non-interactive/CI shell with no flag or
|
|
52
|
+
`CYBERDYNE_IMPORT_KEY`, it defaults to **create**. A mnemonic derives account index
|
|
53
|
+
0 (`m/44'/60'/0'/0/0`). An imported key is validated (0x + 64 hex, or a valid BIP-39
|
|
54
|
+
mnemonic) before any network call. `CYBERDYNE_EVM_PRIVATE_KEY` (env) still works as a
|
|
55
|
+
no-flag default and overrides the saved wallet.
|
|
30
56
|
|
|
31
57
|
An agent already running inside an LLM with this MCP connected can **self-onboard
|
|
32
58
|
with zero web interaction** by calling the `onboard` tool — it's the one tool that
|
|
33
|
-
works without an existing key and bootstraps everything else.
|
|
34
|
-
|
|
35
|
-
`
|
|
59
|
+
works without an existing key and bootstraps everything else. (The `onboard` tool
|
|
60
|
+
generates/reuses a wallet; to **import** your own, use the `--import` CLI.) After
|
|
61
|
+
that it can fund (`get_deposit_address` → send USDC on Base → `deposit`) and
|
|
62
|
+
`post_task` — and `withdraw_treasury` to recover unspent treasury to your wallet.
|
|
36
63
|
|
|
37
64
|
> Mirrors the Bankr CLI UX (`bankr login` → wallet + API key in one shot). The
|
|
38
65
|
> human **submit-proof** step is intentionally still in the app (human-only); every
|
|
39
|
-
> agent-side action — onboard, fund, post,
|
|
66
|
+
> agent-side action — onboard, fund, post, authorize, review, close, reclaim — is
|
|
40
67
|
> headless.
|
|
41
68
|
|
|
42
69
|
## CLI
|
|
@@ -80,43 +107,41 @@ No key is hardcoded anywhere. `list_categories` and `onboard` work without a tok
|
|
|
80
107
|
(`onboard` mints one); every other tool returns a clear error until a key is set —
|
|
81
108
|
via `CYBERDYNE_IDENTITY_TOKEN`, a saved `onboard`/`login`, or the `onboard` tool.
|
|
82
109
|
|
|
83
|
-
## The
|
|
110
|
+
## The flow
|
|
84
111
|
|
|
85
112
|
An agent cannot submit proof on a human's behalf — the **submit-proof step is
|
|
86
|
-
human-only and happens in the app/UI**.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
There are two settlement flows:
|
|
113
|
+
human-only and happens in the app/UI**. On the **live** rail fund with real USDC —
|
|
114
|
+
`get_deposit_address` returns where to send, then `deposit` credits your treasury
|
|
115
|
+
from the tx hash. (`fund_treasury` is a **testnet/demo** top-up and is disabled when
|
|
116
|
+
the platform is live.)
|
|
92
117
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
118
|
+
There is **one** settlement model: the **non-custodial FCFS pool bounty**. You
|
|
119
|
+
freeze a budget once; **any** eligible human submits first-come-first-served; you
|
|
120
|
+
approve each unit (pay one) or reject it (the slot reopens). `post_task` returns an
|
|
121
|
+
`authIntent` (the whole-budget authorization to sign) plus a separate `deployFee`
|
|
122
|
+
(a non-refundable 2.5% USDC / 5% other-token fee tx).
|
|
97
123
|
|
|
98
124
|
```
|
|
99
|
-
get_deposit_address → send USDC → deposit (fund the treasury)
|
|
100
|
-
→ post_task
|
|
101
|
-
→ authorize_task
|
|
102
|
-
→
|
|
103
|
-
→
|
|
125
|
+
get_deposit_address → send USDC → deposit (optional, fund the treasury)
|
|
126
|
+
→ post_task ({ …, quantity }) → { task, authIntent, deployFee }
|
|
127
|
+
→ authorize_task ({ task_id, auth_intent, deploy_fee }) (sign the budget + pay the deploy fee; freeze the whole budget on the audited escrow)
|
|
128
|
+
→ humans submit FCFS → poll get_task
|
|
129
|
+
→ review_submission per pending submission (approve → capture one unit, full reward in-token; reject → slot reopens)
|
|
130
|
+
→ close_task (operator voids the unfilled budget back to you; the deploy fee is non-refundable)
|
|
104
131
|
```
|
|
105
132
|
|
|
106
|
-
###
|
|
133
|
+
### Trustless backstop — `reclaim`
|
|
107
134
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
`close_task` asks CYBERDYNE's operator to void the unfilled budget. If the operator
|
|
136
|
+
is ever **down**, you don't need it: after the on-chain **authorization deadline**,
|
|
137
|
+
your own wallet (the budget's `payer`) calls the audited escrow's payer-only
|
|
138
|
+
`reclaim(paymentInfo)` **directly**, with zero platform involvement, and recovers
|
|
139
|
+
the unfilled budget itself. This is the deepest non-custodial guarantee.
|
|
113
140
|
|
|
114
141
|
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
→ review_submission per pending submission (approve → capture one unit; reject → slot reopens)
|
|
119
|
-
→ close_task (refund unfilled units; the deploy fee is non-refundable)
|
|
142
|
+
reclaim ({ task_id }) → your MCP wallet reads escrow_payment_info, reconstructs the exact
|
|
143
|
+
PaymentInfo struct, and calls reclaim() on the audited escrow on Base.
|
|
144
|
+
Errors clearly if it's too early, already settled, or you're not the payer.
|
|
120
145
|
```
|
|
121
146
|
|
|
122
147
|
## Tools → live endpoints
|
|
@@ -131,20 +156,20 @@ post_task (quantity > 1) → { task, authIntent, depl
|
|
|
131
156
|
| `get_deposit_address` | `GET /api/treasury/deposit` | Where to send real USDC to fund the treasury (live rail). |
|
|
132
157
|
| `deposit` | `POST /api/treasury/deposit` | Credit the treasury from a real on-chain USDC deposit (tx hash). |
|
|
133
158
|
| `withdraw_treasury` | `POST /api/treasury/withdraw` | Recover unspent treasury to your wallet — pull USDC back to your own verified deposit wallet on Base (live rail; no destination param, so funds can only go to you). |
|
|
134
|
-
| `post_task` | `POST /api/tasks` | Open
|
|
135
|
-
| `
|
|
136
|
-
| `authorize_task` | `POST /api/tasks/[id]/authorize` | Open the escrow hold. Custodial/manual: just `task_id`. On-chain direct hire: `auth_intent`/`signed_payment`. Pool/FCFS: also `deploy_fee`/`fee_tx_hash`. |
|
|
159
|
+
| `post_task` | `POST /api/tasks` | Open an FCFS pool bounty. `reward_usd` is the total budget; `quantity` units; not charged until authorize. Response carries `authIntent` + `deployFee`. |
|
|
160
|
+
| `authorize_task` | `POST /api/tasks/[id]/authorize` | Freeze the whole budget on the audited escrow. With a signing wallet: pass `auth_intent` + `deploy_fee` (the MCP signs + pays the fee); or pre-made `signed_payment` + `fee_tx_hash`. |
|
|
137
161
|
| `get_task` | `GET /api/tasks/[id]` | Task + the submissions/claims the poster may see. Poll for a `pending` submission. |
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
|
|
142
|
-
The
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
|
|
146
|
-
is
|
|
147
|
-
|
|
162
|
+
| `review_submission` | `POST /api/submissions/[id]/review` | Settle one submission: `approve:true` → capture one unit (full reward in-token); `approve:false` → reject (slot reopens). This is how you pay humans. |
|
|
163
|
+
| `close_task` | `POST /api/tasks/[id]/close` | Close the bounty; the operator voids the unfilled remainder back to you. The deploy fee is non-refundable. |
|
|
164
|
+
| `reclaim` | on-chain `reclaim(paymentInfo)` | **Trustless backstop.** After the authorization deadline, your wallet (the payer) recovers the unfilled budget directly from the audited escrow — no CYBERDYNE operator. Returns `{ ok, tx_hash, reclaimed }`. |
|
|
165
|
+
|
|
166
|
+
The settlement model is the **non-custodial FCFS pool escrow** (freeze-at-deploy on
|
|
167
|
+
the audited base/commerce-payments `AuthCaptureEscrow`): at `authorize_task` the whole
|
|
168
|
+
budget is frozen; `review_submission` captures one unit to the human (full reward,
|
|
169
|
+
in-token); `close_task` voids the unfilled remainder via the operator, and `reclaim`
|
|
170
|
+
is your own payer-only on-chain recovery if the operator is ever unavailable.
|
|
171
|
+
`search_humans` (discovery only — there is no direct hire) goes through the a2a
|
|
172
|
+
JSON-RPC gateway because the REST `GET /api/humans` is session-only.
|
|
148
173
|
|
|
149
174
|
## Run it
|
|
150
175
|
|
|
@@ -207,28 +232,24 @@ Bundles the MCP gateway **and** the usage skill. Once connected, run
|
|
|
207
232
|
|
|
208
233
|
Then ask the agent, e.g.:
|
|
209
234
|
|
|
210
|
-
> *
|
|
211
|
-
>
|
|
235
|
+
> *Post a $3.50 FCFS bounty for a human to record 10 phrases, freeze the budget,
|
|
236
|
+
> then verify the first valid submission and pay it.*
|
|
212
237
|
|
|
213
|
-
The agent chains `
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
238
|
+
The agent chains `post_task → authorize_task → get_task → review_submission →
|
|
239
|
+
close_task` on its own. If CYBERDYNE's operator is ever down, it can `reclaim` the
|
|
240
|
+
unfilled budget directly from the escrow after the authorization deadline. There is
|
|
241
|
+
**no direct hire** — every task is an open FCFS pool bounty.
|
|
217
242
|
|
|
218
243
|
## Honesty / accuracy
|
|
219
244
|
|
|
220
245
|
State only what is independently verifiable. This repository, its code, and the
|
|
221
246
|
fact that the tools run and call the documented endpoints are verifiable. The
|
|
222
|
-
backend is **pre-launch**. The
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
metrics, any token/airdrop, named individuals, partnerships, or compliance status
|
|
228
|
-
— none are established.
|
|
247
|
+
backend is **pre-launch**. The settlement model is the **non-custodial FCFS pool
|
|
248
|
+
escrow** (freeze-at-deploy on the audited base/commerce-payments `AuthCaptureEscrow`,
|
|
249
|
+
with the agent's own payer-only `reclaim` backstop). Do **not** assert funding,
|
|
250
|
+
valuation, investors, revenue or user metrics, any token/airdrop, named individuals,
|
|
251
|
+
partnerships, or compliance status — none are established.
|
|
229
252
|
|
|
230
253
|
## Follow-ups (not in this server)
|
|
231
254
|
|
|
232
|
-
- The **paid `hire` path** (x402 402→pay→200 over `POST /api/a2a`) is implemented
|
|
233
|
-
on the platform but not surfaced here — it needs an x402 signing client.
|
|
234
255
|
- A **remote/HTTP MCP** variant (vs. stdio) for hosted agents.
|
package/dist/cli.js
CHANGED
|
@@ -160,9 +160,9 @@ export async function runPost(argv) {
|
|
|
160
160
|
const res = await c.rest("POST", "/api/tasks", { body });
|
|
161
161
|
const taskId = res.task?.id;
|
|
162
162
|
console.error(` ✓ posted — task ${taskId}`);
|
|
163
|
-
// Custodial rail (no authIntent): nothing else to do; the hold opens later.
|
|
163
|
+
// Custodial/testnet rail (no authIntent): nothing else to do; the hold opens later.
|
|
164
164
|
if (!res.authIntent || !res.deployFee) {
|
|
165
|
-
console.error(`\n✓ Task ${taskId} is open
|
|
165
|
+
console.error(`\n✓ Task ${taskId} is open. Humans submit FCFS; review each submission to capture a unit (review_submission), then close_task.`);
|
|
166
166
|
process.exit(0);
|
|
167
167
|
}
|
|
168
168
|
// POOL rail — the autonomous `bankr launch` path. Sign the budget with the saved
|
package/dist/evm-signer.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Nothing here moves funds or pays gas; it only produces a signature.
|
|
14
14
|
*/
|
|
15
15
|
import { privateKeyToAccount } from "viem/accounts";
|
|
16
|
-
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
|
|
16
|
+
import { createPublicClient, createWalletClient, getAddress, http, parseUnits } from "viem";
|
|
17
17
|
import { base, baseSepolia } from "viem/chains";
|
|
18
18
|
import { AuthCaptureEvmScheme, toClientEvmSigner } from "@x402/evm";
|
|
19
19
|
import { readSavedWalletKey } from "./client.js";
|
|
@@ -99,3 +99,115 @@ export async function signAuthCapture(requirements) {
|
|
|
99
99
|
const result = await scheme().createPaymentPayload(2, requirements);
|
|
100
100
|
return Buffer.from(JSON.stringify(result)).toString("base64");
|
|
101
101
|
}
|
|
102
|
+
// ── Trustless self-recovery: reclaim ────────────────────────────────────────
|
|
103
|
+
// The deepest non-custodial guarantee. The AGENT (payer) calls the AUDITED
|
|
104
|
+
// AuthCaptureEscrow `reclaim(paymentInfo)` DIRECTLY — no CYBERDYNE operator. The
|
|
105
|
+
// contract requires `msg.sender == paymentInfo.payer` and `block.timestamp >=
|
|
106
|
+
// authorizationExpiry`, then returns the uncaptured remainder to the payer. This
|
|
107
|
+
// is the same PaymentInfo tuple shape used by authorize/capture/void.
|
|
108
|
+
/** The canonical audited base/commerce-payments AuthCaptureEscrow on Base. */
|
|
109
|
+
export const AUTH_CAPTURE_ESCROW_ADDRESS = "0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff";
|
|
110
|
+
/** PaymentInfo tuple components — EXACTLY the order the escrow hashes/expects. */
|
|
111
|
+
const PAYMENT_INFO_COMPONENTS = [
|
|
112
|
+
{ name: "operator", type: "address" },
|
|
113
|
+
{ name: "payer", type: "address" },
|
|
114
|
+
{ name: "receiver", type: "address" },
|
|
115
|
+
{ name: "token", type: "address" },
|
|
116
|
+
{ name: "maxAmount", type: "uint120" },
|
|
117
|
+
{ name: "preApprovalExpiry", type: "uint48" },
|
|
118
|
+
{ name: "authorizationExpiry", type: "uint48" },
|
|
119
|
+
{ name: "refundExpiry", type: "uint48" },
|
|
120
|
+
{ name: "minFeeBps", type: "uint16" },
|
|
121
|
+
{ name: "maxFeeBps", type: "uint16" },
|
|
122
|
+
{ name: "feeReceiver", type: "address" },
|
|
123
|
+
{ name: "salt", type: "uint256" },
|
|
124
|
+
];
|
|
125
|
+
const paymentInfoArg = { name: "paymentInfo", type: "tuple", components: PAYMENT_INFO_COMPONENTS };
|
|
126
|
+
/** Minimal ABI: just `reclaim(PaymentInfo)` — payer-callable after authorizationExpiry. */
|
|
127
|
+
export const reclaimAbi = [
|
|
128
|
+
{ type: "function", name: "reclaim", stateMutability: "nonpayable", inputs: [paymentInfoArg], outputs: [] },
|
|
129
|
+
];
|
|
130
|
+
/** Decode the base64 signed payment → { payer (authorization.from), validBefore, salt }. */
|
|
131
|
+
function decodeSignedPayment(signedPayment) {
|
|
132
|
+
let payload;
|
|
133
|
+
try {
|
|
134
|
+
const decoded = JSON.parse(Buffer.from(signedPayment, "base64").toString("utf8"));
|
|
135
|
+
payload = (decoded.payload ?? decoded);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
throw new Error("malformed_signed_payment");
|
|
139
|
+
}
|
|
140
|
+
const auth = payload.authorization;
|
|
141
|
+
const permit = payload.permit2Authorization;
|
|
142
|
+
const from = auth?.from ?? permit?.from;
|
|
143
|
+
const validBefore = auth?.validBefore ?? permit?.deadline;
|
|
144
|
+
const salt = payload.salt;
|
|
145
|
+
if (!from || !validBefore || !salt)
|
|
146
|
+
throw new Error("incomplete_signed_payment (need authorization.from + validBefore + salt)");
|
|
147
|
+
return { payer: from, preApprovalExpiry: Number(validBefore), salt, value: auth?.value };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Reconstruct the on-chain PaymentInfo struct EXACTLY like the platform's
|
|
151
|
+
* `structFor` (lib/payments/escrow-pool.ts) — the contract recomputes the hash, so
|
|
152
|
+
* every field must match the one signed at deploy or `reclaim` reverts.
|
|
153
|
+
*/
|
|
154
|
+
function structFor(info, payer, preApprovalExpiry, salt, value) {
|
|
155
|
+
const x = info.extra;
|
|
156
|
+
return {
|
|
157
|
+
operator: getAddress(x.captureAuthorizer),
|
|
158
|
+
payer: getAddress(payer),
|
|
159
|
+
receiver: getAddress(info.payTo),
|
|
160
|
+
token: getAddress(info.asset),
|
|
161
|
+
maxAmount: BigInt(value ?? info.amount),
|
|
162
|
+
preApprovalExpiry,
|
|
163
|
+
authorizationExpiry: x.captureDeadline,
|
|
164
|
+
refundExpiry: x.refundDeadline,
|
|
165
|
+
minFeeBps: x.minFeeBps ?? 0,
|
|
166
|
+
maxFeeBps: x.maxFeeBps ?? 0,
|
|
167
|
+
feeReceiver: getAddress(x.feeRecipient),
|
|
168
|
+
salt: BigInt(salt),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* TRUSTLESS SELF-RECOVERY. The agent (payer) calls `reclaim(paymentInfo)` on the
|
|
173
|
+
* audited escrow directly to recover its OWN unfilled budget — no operator. Asserts
|
|
174
|
+
* the MCP wallet == payer (reclaim is payer-only on-chain) and that the authorization
|
|
175
|
+
* deadline has passed, then signs+sends from the agent wallet and waits for the receipt.
|
|
176
|
+
* Returns { ok, tx_hash, reclaimed } (reclaimed = the human-readable atomic maxAmount).
|
|
177
|
+
*/
|
|
178
|
+
export async function reclaimBudget(info) {
|
|
179
|
+
const signed = info.signedPayment;
|
|
180
|
+
if (!signed)
|
|
181
|
+
throw new Error("no signedPayment on this task — it was never frozen on the escrow (nothing to reclaim)");
|
|
182
|
+
const { payer, preApprovalExpiry, salt, value } = decodeSignedPayment(signed);
|
|
183
|
+
// reclaim is payer-only on-chain — assert this wallet IS the payer before sending.
|
|
184
|
+
const me = evmAddress();
|
|
185
|
+
if (me.toLowerCase() !== payer.toLowerCase()) {
|
|
186
|
+
throw new Error(`wallet ${me} is not the payer (${payer}) of this budget — reclaim is payer-only on-chain. ` +
|
|
187
|
+
"Use the same wallet that authorized/froze the budget.");
|
|
188
|
+
}
|
|
189
|
+
const paymentInfo = structFor(info, payer, preApprovalExpiry, salt, value);
|
|
190
|
+
// reclaim requires block.timestamp >= authorizationExpiry — check first for a clear error.
|
|
191
|
+
const now = Math.floor(Date.now() / 1000);
|
|
192
|
+
if (now < paymentInfo.authorizationExpiry) {
|
|
193
|
+
const when = new Date(paymentInfo.authorizationExpiry * 1000).toISOString();
|
|
194
|
+
throw new Error(`too_early: the authorization deadline has not passed yet (reclaim opens at ${when}, ` +
|
|
195
|
+
`in ~${Math.ceil((paymentInfo.authorizationExpiry - now) / 60)} min). ` +
|
|
196
|
+
"Until then, close_task asks CYBERDYNE's operator to void the unfilled budget back to you.");
|
|
197
|
+
}
|
|
198
|
+
const wallet = createWalletClient({ account: account(), chain: chain(), transport: http(process.env.CYBERDYNE_RPC_URL) });
|
|
199
|
+
const pub = createPublicClient({ chain: chain(), transport: http(process.env.CYBERDYNE_RPC_URL) });
|
|
200
|
+
const hash = await wallet.writeContract({
|
|
201
|
+
address: AUTH_CAPTURE_ESCROW_ADDRESS,
|
|
202
|
+
abi: reclaimAbi,
|
|
203
|
+
functionName: "reclaim",
|
|
204
|
+
args: [paymentInfo],
|
|
205
|
+
chain: chain(),
|
|
206
|
+
});
|
|
207
|
+
const receipt = await pub.waitForTransactionReceipt({ hash });
|
|
208
|
+
if (receipt.status !== "success") {
|
|
209
|
+
throw new Error("reclaim reverted on-chain — the budget may already be fully captured/settled, already reclaimed, " +
|
|
210
|
+
"or the deadline window is wrong. Nothing was recovered.");
|
|
211
|
+
}
|
|
212
|
+
return { ok: true, tx_hash: hash, reclaimed: String(paymentInfo.maxAmount) };
|
|
213
|
+
}
|
package/dist/founder-check.js
CHANGED
|
@@ -65,24 +65,27 @@ const posted = await call("post_task", {
|
|
|
65
65
|
});
|
|
66
66
|
const taskId = posted.task.id;
|
|
67
67
|
console.log(`post_task -> ${taskId} reward $${posted.task.reward_usd}`);
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
68
|
+
// (search_humans is discovery only — there is NO direct hire. The task is an open
|
|
69
|
+
// FCFS pool bounty: ANY eligible human submits first-come-first-served.)
|
|
70
|
+
void human;
|
|
71
|
+
// 3. Authorize — freeze the budget (POOL: authIntent + deployFee; manual: just task_id).
|
|
72
|
+
const authd = await call("authorize_task", {
|
|
73
|
+
task_id: taskId,
|
|
74
|
+
...(posted.authIntent ? { auth_intent: posted.authIntent } : {}),
|
|
75
|
+
...(posted.deployFee ? { deploy_fee: posted.deployFee } : {}),
|
|
76
|
+
});
|
|
77
|
+
console.log(`authorize_task -> escrow ${authd.task?.escrow_status}`);
|
|
75
78
|
// 4. Poll until the human's proof is in (they submit in the app — human-only).
|
|
76
79
|
const got = await call("get_task", { task_id: taskId });
|
|
77
80
|
console.log(`get_task -> ${got.task.status} submissions ${got.submissions.length}`);
|
|
78
|
-
// 5. If the proof is in, verify and
|
|
81
|
+
// 5. If the proof is in, verify it and capture one unit to the human.
|
|
79
82
|
const pending = (got.submissions ?? []).find((s) => s.status === "pending");
|
|
80
83
|
if (pending) {
|
|
81
|
-
const settled = await call("
|
|
82
|
-
console.log(`
|
|
84
|
+
const settled = await call("review_submission", { submission_id: pending.id, approve: true, score: 5 });
|
|
85
|
+
console.log(`review_submission -> captured ${JSON.stringify(settled).slice(0, 80)}`);
|
|
83
86
|
console.log("\n[agent] has a human verification it could not produce itself, and the contributor is paid.");
|
|
84
87
|
}
|
|
85
88
|
else {
|
|
86
|
-
console.log("\n[agent]
|
|
89
|
+
console.log("\n[agent] bounty is live and the budget is frozen; humans submit proof in the app FCFS, then the agent reviews each.");
|
|
87
90
|
}
|
|
88
91
|
await client.close();
|