Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/THREAT_MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ What has monetary value, irreversibility, or trust significance in this system:
| Agent reputation records | On-chain, external ERC-8004 ReputationRegistry `0x8004BAa1…9b63` (Base mainnet) | Stars written by clients of the agents via `giveFeedback` (the `/assistent` rating widget; agentId 97599 always, 97600 when web tools ran). Third-party contract, permissionless by design (spec v1.1 removed pre-authorization): anyone can rate from any wallet, so the records are an **advisory** discovery signal — like the analytics counters, never an input to a security or financial decision (§5, reputation layer) |
| Agent wallet private key | scw_js secrets | Can trigger `requestImageUpdate()` and receive mintPrice per call. Same address as the x402 `payTo` (`NFT_WALLET`), bound as the `agentWallet` of the three ERC-8004 agents |
| Receiver-authorizer private key | scw_js secrets | Signs batch-settlement channel-config and refund authorizations (EIP-712) for the `/assistent` sellers (LLM, search, fetch) via `receiverAuthorizerSigner`. Never sends transactions, never needs funding |
| Facilitator wallet private key | x402_facilitator secrets | Receives settlement fees (USDC; EURC and EURe on Base) |
| Facilitator wallet private key | x402_facilitator secrets; post-cutover also a `bunny_js` script secret (write-once) — see [`x402_facilitator/bunny-port-implementation-plan.md`](../x402_facilitator/bunny-port-implementation-plan.md) | Receives settlement fees (USDC; EURC and EURe on Base). The edge move puts the settle/fee-relay hot key on bunny.net infrastructure — a deliberate custody change; the key never grants custody of escrowed funds, only the fee pull and transaction relay | |
| Scaleway secrets (SCW_SECRET_KEY) | Serverless secrets | Access to S3 image bucket, transactional email, and analytics counters |
| BFL / IONOS API keys | Serverless secrets | Image generation quota; no on-chain access |
| bunny.net account (incl. account-wide API key) | bunny.net dashboard; CLI profile at `~/.config/bunnynet.json` (mode 600) | Controls all three Edge Script twins (searchapi 97472, llm 97948, mcp 97949) — can read each script's plain env values, overwrite secrets, replace code, delete scripts. Broader than the per-script deploy keys below |
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/deploy-bunny-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,15 @@ jobs:
script_id: ${{ secrets.BUNNYMCP_SCRIPT_ID }}
deploy_key: ${{ secrets.BUNNYMCP_DEPLOY_KEY }}
file: "bunny_js/dist/mcp.js"

- name: Deploy facilitator script to Bunny Edge Scripting
uses: BunnyWay/actions/deploy-script@931a74b9c45ea9f9f01435fbbf1649830e67d3b3 # 2026-10-12 main; a mutable ref here is code-exec with the deploy keys — bump deliberately
with:
# The facilitator twin (payment core ported from x402_facilitator/). Publishing does
# NOT make it live: facilitator.fretchen.eu keeps serving the Scaleway function
# until the DNS flip (x402_facilitator/bunny-port-implementation-plan.md). Same
# rule as the other steps: set both secrets in GitHub before merging, or this step
# fails (and does not block the twins' deploys above).
script_id: ${{ secrets.BUNNYFACILITATOR_SCRIPT_ID }}
deploy_key: ${{ secrets.BUNNYFACILITATOR_DEPLOY_KEY }}
file: "bunny_js/dist/facilitator.js"
4 changes: 3 additions & 1 deletion bunny_js/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# bunny_js — agent rules

Bunny Edge Scripts: the x402 llm/search/mcp **sellers** (plus the scw twins' origin domains).
Bunny Edge Scripts: the x402 llm/search/mcp **sellers** plus the ported **facilitator**
(the payment core's verbatim twin from `x402_facilitator/`, waiting on its cutover —
see [`x402_facilitator/bunny-port-implementation-plan.md`](../x402_facilitator/bunny-port-implementation-plan.md)).
See [`README.md`](./README.md) for the architecture, commands, environment/secrets, known
limits and deploy paths, and [`SECURITY.md`](./SECURITY.md) for the security posture.

Expand Down
51 changes: 48 additions & 3 deletions bunny_js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ domains, and the Scaleway twins they replaced are removed from `scw_js`'s
`npm run deploy` (run it before merging to make the docs' "retired" claims literal).
Only `scw_js`'s `llmx402cron` remains, claiming the channels these twins charge.

The fourth script is the **facilitator** (`facilitator.ts`, `service="facilitator"`) — the x402
payment facilitator (`/verify`, `/settle`, `/supported`, `/openapi.json`) ported verbatim from
`x402_facilitator/` (payment core + its test suite) with Scaleway's envelope exchanged for
native `Request`/`Response` routing in `src/facilitator_routes.ts`. Its cutover plan is
[`x402_facilitator/bunny-port-implementation-plan.md`](../x402_facilitator/bunny-port-implementation-plan.md):
until its DNS flip the Scaleway facilitator remains the live one at `facilitator.fretchen.eu`.

## Architecture

- `src/routes.ts` — the **only new code**: the routing layer, natively against web-standard
Expand All @@ -29,17 +36,32 @@ Only `scw_js`'s `llmx402cron` remains, claiming the channels these twins charge.
envelope helpers are not ported — there is no Scaleway envelope here.
- `src/runtime-shims.ts` — pins the Node `Buffer`/`process` globals the ported code assumes,
imported FIRST by the entry so they exist before any ported module's top-level code runs.
- `src/facilitator/` — the facilitator's own corner of the tree (`src/` is otherwise the
twins'): the payment core ported **verbatim** from `x402_facilitator/` (that package keeps
its own copy alive for its Scaleway function until the teardown PR; only `walletreportcron`
stays on Scaleway long-term, because the edge has no cron) plus one new file — the routing.
`src/facilitator/facilitator_routes.ts` mirrors the Scaleway handler's routing/CORS/envelope
semantics line by line against web-standard `Request`/`Response`; everything else is
untouched payment behavior, including its test suite (`test/facilitator/` — the ported
core tests, `facilitator_routes.test.ts` for the native routing, and
`facilitator_alert_coverage.test.ts`, which pins the ported log phrases to
`observability/alerts/bunny.yaml`'s facilitator branch; the twins need no such test).
The entry is `src/facilitator.ts`, flat like the other three.

## Commands

- `npm run build` — tsdown bundle to a **single** `dist/searchapi.js` (bunny deploys one file).
- `npm run dev:searchapi` / `dev:llm` / `dev:mcp` — local serve of one twin via Deno on
- `npm run dev:searchapi` / `dev:llm` / `dev:mcp` / `dev:facilitator` — local serve of one
twin via Deno on
127.0.0.1 (from source, no build needed; `--sloppy-imports` covers the shared packages'
extensionless tsc output, and `--env-file=.env` is baked in — a missing `.env` only warns).
The paid path needs `NFT_WALLET_PUBLIC_KEY` + `RECEIVER_AUTHORIZER_PRIVATE_KEY` in `.env`;
extra args go after `--` (`npm run dev:mcp -- --port 8081`-style), because npm eats
unknown flags like `--env-file` itself.
- `npm test` — vitest (165 tests: 108 ported + the translated routes suite (incl. cache-control)).
- `npm test` — vitest (521 tests: the twins' ported suite + routes, the facilitator's ported
payment suite (`test/x402_*.test.ts`) + native routes + its alert-phrase guard;
`npm run test:integration` runs the live-RPC cases under `test/integration/`, inherited
from the facilitator package).
- `npm run check` — typecheck + lint + format + tests.
- `npm run deploy` — build, then `bunny scripts deploy dist/searchapi.js` (needs the bunny CLI,
installed and logged in).
Expand Down Expand Up @@ -73,6 +95,17 @@ it is inert without another credential, plain — and plain stays inspectable wh
| `SCW_COCKPIT_PUSH_TOKEN` | secret (`--secret`) | Cockpit push-only token — authenticates the Loki log flush to the observability data source (see the observability README/implementation plan) |
| `COCKPIT_LOKI_URL` | plain | push URL of the custom Cockpit logs data source — public and inert without the token |

**Facilitator script — its own env set** (`bunny scripts env set` against its script id; the
values mirror `x402_facilitator/serverless.yml`):

| Variable | bunny kind | Use |
| --------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------- |
| `FACILITATOR_FEE_AMOUNT` | plain | fee in the token's smallest unit (`10000` = 0.01 USDC/EURC) |
| `BATCH_SETTLEMENT_TEST_WALLETS` | plain | testnet-only allowance-check bypass list (public, testnet-fenced in code) |
| `FACILITATOR_WALLET_PRIVATE_KEY` | secret (`--secret`) | the facilitator hot wallet — signs settles and pulls fees; write-once on bunny's side |
| `RPC_URL_EIP155_*` | secret (`--secret`) | the four network RPC URLs — real values embed API keys; unset falls back to rate-limited public endpoints |
| `SCW_COCKPIT_PUSH_TOKEN` / `COCKPIT_LOKI_URL` | as above | the logger pair, same as the twins |

## Known limits / decisions

- **Single-file bundle, 1 MB limit.** tsdown emits one `dist/searchapi.js` with `codeSplitting:
Expand All @@ -93,7 +126,12 @@ false`; Node builtins stay external and MUST be imported through the `node:` pre
sync) + 1 upstream + facilitator calls. Well within the cap, but `getFacilitatorFeeConfig`
style fetch storms would not be.
- **No cron at the edge** — `llmx402cron` stays on Scaleway and claims the channels this twin
charges, unchanged, because the channel storage is the same bucket.
charges, unchanged, because the channel storage is the same bucket. For the same reason the
facilitator's `walletreportcron` (weekly wallet-report email) stays on Scaleway too — the
edge has no scheduler, and the report additionally rides Scaleway Transactional Email.
- **Facilitator bundle ≈ 573 KB minified** (`dist/facilitator.js`, single file, deps inlined —
viem + the @x402 SDK are the bulk), well under the 1 MB script limit. Its imports are
`node:buffer`/`node:process` only, same as the twins.

## Trial matrix (Task 6 — historical; superseded by the production switch)

Expand Down Expand Up @@ -123,6 +161,11 @@ these twins' channels) and `growthapi` — the old twins' entries are gone from
The original port plan (scaffold, bundle gate, SigV4 spike, verbatim stack + tests, native
routing + tests) is complete.

The facilitator twin is ported and green but **not yet live**: `facilitator.fretchen.eu` still
serves the Scaleway function until the port plan's operational cutover (trial-hostname probes
→ DNS flip), after which `x402_facilitator/` trims down to `walletreportcron` in the teardown
PR.

## Deploy paths (both can coexist)

Bunny publishes whichever deployment happened **last**, so a manual CLI deploy and the
Expand All @@ -143,6 +186,8 @@ next push to `main` overwrites it back to the committed state.
- `BUNNYJS_SCRIPT_ID` / `BUNNYJS_DEPLOY_KEY` — the searchapi script
- `BUNNYLLM_SCRIPT_ID` / `BUNNYLLM_DEPLOY_KEY` — the llm script
- `BUNNYMCP_SCRIPT_ID` / `BUNNYMCP_DEPLOY_KEY` — the mcp script
- `BUNNYFACILITATOR_SCRIPT_ID` / `BUNNYFACILITATOR_DEPLOY_KEY` — the facilitator script
(same pattern; serves `facilitator.fretchen.eu` after its DNS flip — see the port plan)

(Per-script deploy keys are deliberately chosen over the account-wide API key a
CLI-in-CI variant would need — narrower blast radius. A step whose secrets are missing
Expand Down
5 changes: 3 additions & 2 deletions bunny_js/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions bunny_js/notebooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# bunny_js notebooks

Front-end-free test notebooks for the Edge Scripts in this package. All use the **Deno
kernel**; each notebook resolves its npm dependencies via `npm:` specifiers and this
folder's own `deno.json`/`node_modules` (scoped here — see `deno.json`'s comment), so they
run without touching the parent `bunny_js` npm project — subfolders inherit that scope too.

- `facilitator/` — the facilitator test notebooks, one per payment scheme; the target host is
the `FACILITATOR_URL` env var. See [`facilitator/README.md`](./facilitator/README.md).
- `llm_x402_buyer.ipynb` / `llm_mcp_buyer.ipynb` / `search_x402_buyer.ipynb` — the buyer's
side of the deployed twins (`wrapFetchWithPayment` against the `*-agent.fretchen.eu`
endpoints). While trialing, point a notebook's `SERVER` at the trial hostnames the same
way — the documented pattern there is the origin of the facilitator folder's
`FACILITATOR_URL` knob.
33 changes: 33 additions & 0 deletions bunny_js/notebooks/facilitator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Facilitator test notebooks

The facilitator's four scheme notebooks — replicated from
[`x402_facilitator/notebooks/`](../../../x402_facilitator/notebooks/README.md) (the
authoritative per-scheme flow descriptions live there) — exercising the bunny twin
end-to-end over HTTP. Together they are the exact Task 5 probe matrix of the port plan
([`bunny-port-implementation-plan.md`](../../../x402_facilitator/bunny-port-implementation-plan.md)):

| Notebook | Kernel | Tests what (short form) |
| ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------ |
| `x402_exact.ipynb` | Deno/TS | `exact`: /supported → approve fee allowance → /verify → /settle → confirm the fee was pulled |
| `x402_exact_permit2.ipynb` | Deno/TS | `exact` via Permit2, paying EURe (no EIP-3009) on Base (`DRY_RUN=1` skips the on-chain writes) |
| `x402_batch_settlement.ipynb` | Deno/TS | `batch-settlement`: escrowed deposit → off-chain vouchers → one claim (runs on Base Sepolia) |
| `x402_batch_settlement_permit2.ipynb` | Deno/TS | `batch-settlement` with EURe: Permit2 deposit → two vouchers → one claim (`DRY_RUN=1` supported) |

## The target — `FACILITATOR_URL`

Env-driven in these copies (`Deno.env.get("FACILITATOR_URL") ?? "http://localhost:8080"`),
unlike the originals' hardcoded cell:

```bash
# unset → http://localhost:8080, the port `npm run dev:facilitator` serves on (verified)
# against the deployed trial script:
FACILITATOR_URL=https://fretchen-facilitator-rtsp5.bunny.run jupyter …
```

Prerequisites when targeting the deployed script (see the package README's env table and
the port plan's Task 5): `FACILITATOR_WALLET_PRIVATE_KEY` (the `facilitatorFees` recipient
the notebooks approve against), `BATCH_SETTLEMENT_TEST_WALLETS` (testnet fee-gate bypass for
the notebook wallets), ideally `RPC_URL_EIP155_84532`.

Dependencies stay self-contained: `npm:` specifiers resolve through the parent
`notebooks/deno.json` scope (Deno config applies to this subfolder too).
Loading
Loading