Base Contracts
Required for AmetystCreditToken. This address is baked into the
constructor and becomes the only account allowed to mint. The prefilled value is
just a default — overwrite it whenever the minter key has been rotated.
It must be the address that actually calls mint, which is the
ZeroDev kernel address — not the EOA. Both admin-api and credential-api load
MINTER_PRIVATE_KEY into a ZeroDev kernel (entryPoint 0.7, KERNEL_V3_3,
ECDSA validator, default index), so the on-chain caller is that kernel's smart-account
address. Paste the EOA here by mistake and every mint reverts with
“ACT: caller is not the minter”. There is no way to fix that in place: you would have
to redeploy the token, swap base_contracts_references, and migrate every
merchant's payment preference again.
Required for FacilitatorsRegistry. This address becomes the
registry's admin, and setFacilitatorFor /
removeFacilitatorFor — the two calls this app makes on behalf of a
merchant that cannot sign for itself — are onlyAdmin. So it must be the
address admin-app signs with, or the Set Facilitator button reverts
NotAdmin on every merchant.
admin is set in the CONSTRUCTOR. Afterwards it can only
be changed by the current admin calling proposeAdmin followed by
acceptAdmin from the new one. Get it wrong and there is no in-place fix:
you have to redeploy the registry, swap base_contracts_references, and
re-do every merchant's facilitator assignment.
Heads-up on this app's signer. Every on-chain action here builds a
fresh, randomly generated EOA and wraps it in a new ZeroDev kernel
(src/chain/kernel.ts), so admin-app has no stable signing address today.
Until that changes, put an address you control and can sign with here — not an
address this app produced.
Test Accounts
Policies
Deploy Policy Contract
Rules
Policies
Facilitator Registry
Credits & Design Partners
Every Allowed email gets access + free credits and is marked a design partner by default, so Allowed ⊆ Design partners: an allowed partner is always a design partner, but a design partner is not necessarily allowed.
Allowed
Access allowlist
Allowed emails
| Status | Design partner | Actions | |
|---|---|---|---|
| No allowed emails yet | |||
Waitlist
| Design partner | Actions | |
|---|---|---|
| No waitlisted emails | ||
Design partners
Partners
Design Partners
| Partner ID | Credits | ||
|---|---|---|---|
| No design partners yet | |||
Task Proposals
Partner
Proposals history
No proposals have been made to this partner yet.
| Date | Kind | Task | What | By | Status | Decided at |
|---|
Tasks
| Slug | Description | Files | |
|---|---|---|---|
| Load a partner's tasks to begin | |||
Author Proposal
Memory
Records, docs and blobs written into the partner's workspace when the proposal is accepted, in row order. Profile is the namespace: shared (all profiles) or one of the partner's seats; custom… takes an apikey:<id> seat the list did not show.
Docs are materialized into every fire of their seat (shared docs for every seat) as <key>.md; the memory manifest above decides whether a key is shared by the workspace or owned per member. Records are read/appended with the task memory tools; blobs are the archive.
Summary
Stateful Integrations (Connections)
Registering a connector writes four artifacts in one transaction: the capability row, the connector spec, the composition-instructions card and one skill row per operation. A connector with 0 skills or no instructions is invisible to getAllowlist however correct its spec is — both are shown in the table below. A connector with no source descriptor (none in the Source column) is a third way to be invisible: cli 0.2.101+ drops a sourceless entry from the user's catalog, so the provider looks healthy here and does not exist for the client. To edit a registered connector, click its Edit button: the whole row loads into the register form below, and Register re-registers it in place (the endpoint is a whole-row upsert). A row registered before the card brief was stored comes back with the five brief boxes blank — restate them once.
Registered
| Slug | Name | Base URL | Skills | Instructions | Source | Edit |
|---|---|---|---|---|---|---|
| Refresh to load registered connectors | ||||||
Register Connector
Intake
Request shape
Source
Card brief
Transactions
One partner's spend: settled rows, pending consumes and projected credit mints, in one union. Amounts are base units — the decimal shown is exact (BigInt), and the raw integer is on the cell's tooltip. The provider filter takes a bucket key (provider:<name>, bucket:legacy, bucket:top-up), which is why its options come from the totals grid rather than from typed-in names.
Partner
Transactions
| Date (UTC) | Provider | Type | Product | Amount | Status | Source |
|---|---|---|---|---|---|---|
| Pick a partner and load their transactions | ||||||
Totals — day × provider
| Day (UTC) | Provider | Amount | Rows |
|---|---|---|---|
| Pick a partner and load their transactions | |||
Optimizer Pricing
These prices are not read by the optimizer yet. Saving here
changes this table and changes no quote. Quotes are still computed by
buyer-api's computeOptimizerQuote from environment variables
(OPTIMIZER_BEHAVIORS_BASE_RATE, OPTIMIZER_PER_SKILL_RATE,
OPTIMIZER_MIN_PER_RUN); the leg that will read these rows instead has
not been written. Until it lands, this tab is where the prices are
authored, not where they take effect.
The optimizer's price list, in EUR.
Values are exact decimals, not rounded. Type the digits you mean
(0.25, 7, 20); up to 30 decimal places are
stored, and anything longer is refused rather than silently shortened.
The server accepts exactly these four keys. The list is closed, so
a misspelled key is rejected instead of becoming a row. The four are not a
one-to-one map onto the three parameters the quoter uses today — only
min_per_run lines up — so the mapping is settled by whoever writes
buyer-api's leg, not inferred from these names.
Free runs
Give one workspace N free optimizer runs, for a stated reason. They are consumed before any paid run, and after the workspace's own one-off first run — a partner holding both has 1 + N. Each grant is spent only against runs of the type it names. A calibration is grantable here: the automatic one-per-workspace calibration comp has been retired, and a comped calibration must now name the grant it redeemed — so this table is the only way one is given. That retirement is about calibrations only; the workspace's free first optimizer run is a separate entitlement and still exists.
Workspace
Remaining, honoured by the biller: —
| Granted | Used | Left | Status | Reason | By | When |
|---|
Grant free runs
The run type decides what the grant can pay for — a grant is spent only against runs of its own kind, so a behaviour grant will not fund a maintenance run. It defaults to maintenance, which is what every grant made before this field existed was. The reason is required — this table's value over a counter is that it remembers why. Who granted it is taken from your admin session, never from this form.
Automatic design partner grants
What a design partner is handed on enrolment and at every renewal: a credit amount and a number of free runs per type, for a partnership period of N months. Saving here changes nothing retroactively — it arms every future grant, which is why the save is confirmed on prod even though it writes no chain.
The package
Due for renewal
| Partner | Period | Period started | Overdue | Last package | Outcome |
|---|
Granting is idempotent: pressing the button twice reports already_minted rather than minting twice. A 200 is not "the batch succeeded" — every row reports its own two halves, and skipped_no_kernel means the partner has no wallet yet, so the package is owed and they stay on this list.
Optimizer settings
What one workspace's optimizer has done so far, what is due now and what is scheduled — per kind of run. Everything shown is computed by the server; pick a workspace to read its plan.
Workspace
Done so far
| Date | Member | Run | Amount |
|---|
Nothing has run yet.
Due now
| Member | Cadence | Due |
|---|
Nothing is due.
Scheduled
| Member | Cadence | Next run |
|---|
Nothing scheduled.
Done so far
| Date | Run | Amount |
|---|
Nothing has run yet.
Due now
| Task | Cadence | Due |
|---|
Nothing is due.
Scheduled
| Task | Cadence | Next run |
|---|
Nothing scheduled.
Done so far
| Date | Task | Status | Amount |
|---|
Nothing has run yet.
Due now
| Task | Status | Requested at |
|---|
Nothing is due.
Scheduled
Calibrations are not scheduled ahead — they run once when launched.
Endpoint Pricing (Merchant Economics)
Per endpoint: the price we charge, the cost we pay upstream, the markup between them, and what the last 30 days of calls were worth.
Every amount is base units (×106). The decimal shown is exact (BigInt); the raw integer is on the cell's tooltip. Editing the markup shows the price that would be written — ceil(cost × (10000 + bps) / 10000) — before anything is saved or applied.
A dash is not a zero. — means the value has never been recorded (or could not be read); 0.000000 and 0% mean it was recorded and it is zero. Most of the pass-through fleet really does run at exactly 0% markup — that is the finding this table exists to make visible, and it must not look like missing data.
Recording and applying are two acts. Save writes the cost and markup and moves no price. Apply derives the price from the saved row and writes products.price and the bazaar row in one transaction, with an audit entry. Verify asks the live staging merchant what it actually quotes. Writes are staging-only server-side.
Provider
The picker counts each provider's endpoints running at 0% markup, code-pinned, in bazaar-drift and uncosted, so the providers worth opening are visible before opening one. Each line is named; its commerceInfoId is on the line's tooltip. Typing above narrows the list by substring across the name, the slug and the id — so a pasted id lands too. Filtering never changes what is selected: the provider currently loaded stays in the list even when it does not match, because a picker that silently re-pointed at another merchant would put one merchant's prices under another's name.
Suggested allowance
A buyer's deferred allowance is drained one endpoint price at a time, so it has to be a multiple of the LCM of every price this merchant charges — or the buyer ends with a remainder no call can spend. The markup you just saved may have moved that LCM. Below is the server's re-aligned proposal; Apply on any row writes the price and this allowance in one transaction. You may overwrite the number — a value that does not align is flagged in red and still allowed, because only you know whether it is meant.
Endpoints
| Endpoint | Current price | Upstream cost | Markup % | New price | 30d calls | 30d revenue | Est. margin | Status | Actions | |
|---|---|---|---|---|---|---|---|---|---|---|
| Pick a provider and load its endpoints | ||||||||||
Est. margin is (price − recorded cost) × calls. Estimated is load-bearing: no measured per-call upstream cost is persisted anywhere yet, so for a pass-through endpoint this is 0 because cost was recorded as equal to price — which is the real finding, not a missing number. It is — when no cost is recorded.
Price changes
| Applied | Endpoint | Old price | New price | Markup | By | Verification | Actions |
|---|---|---|---|---|---|---|---|
| Pick a provider and load its endpoints | |||||||
A revert does not rewrite the row it undoes: it writes a new row pointing at the original and stamps the original's revertedAt. Verification has three states — verified, checked and failed, and never checked. A failed check stamps its result and leaves the timestamp empty, so a change whose live 402 quoted the wrong amount never reads as merely un-examined.
Merchant Integration (x402)
How to onboard a stateless x402 merchant — a pay-per-call provider with no user account or stored state on our side. This is the manual runbook; every step below happens in the tools it names, not in this console.
- Scaffold the proxy. Add the provider to
merchant-fixturesfollowing the existing per-provider pattern (one proxy service wrapping the upstream API, prices declared per endpoint). - Deploy it on Railway. The FIRST deploy of a new merchant is manual (create the service, set its env); after that, every merge to main redeploys it automatically.
- Route it. Add the merchant's route in
merchants-routerso its slug resolves to the deployed proxy. - Register it in the supplier app. Sign up / sign in on the supplier app with the merchant's own account (name, email, password), then register the commerce info and every endpoint. Composition instructions MUST list the endpoints under an
## Endpointsheading — atoms outside it are invisible togetAllowlist. - Products & pricing. Declare each product's price and (if deferred) the suggested allowance — base units, ×106.
- Whitelist on-chain — LAST. The
payTowhitelist entry is always the final step, only after everything above is verified. Never whitelist first. - Smoke test. From a staging MCP session:
getAllowlistmust surface the provider, and one realspendper payment shape (exact and deferred) must succeed end to end.
Full runbook with copy-paste values: eng-onboard-merchant (operator skill) and the loop-merchants queue for autonomous intake.
Treasury
The treasury account's money, for invoicing: current balance, every on-chain transfer in and out, and who the counterparty was. Every amount is base units (×106), shown as an exact decimal — the CSV carries both forms. A counterparty label is operator-curated: merchants' payTo addresses are not recorded off-chain anywhere, so name them here (click Label on a row) and the name sticks for every future transfer.
Press Refresh to load.
Older transfers exist that are not shown — the page budget ran out. Narrow the period, or export what is loaded and note the cut.
| Time | Dir | Counterparty | Label | Amount | Tx |
|---|