@stellar-agent-services/dev-mcp 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +50 -2
- package/dist/docs.d.ts +35 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +92 -0
- package/dist/docs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +125 -0
- package/dist/server.js.map +1 -0
- package/package.json +40 -4
- package/repository/AGENTS.md +162 -0
- package/repository/CONTRIBUTING.md +95 -0
- package/repository/IMPLEMENTATION_PLAN.md +560 -0
- package/repository/README.md +80 -0
- package/repository/SECURITY.md +22 -0
- package/repository/apps/wallet/README.md +78 -0
- package/repository/docs/client-integration/README-AI.md +495 -0
- package/repository/docs/client-integration/README.md +138 -0
- package/repository/docs/dx-improvements.md +670 -0
- package/repository/docs/erc-compatibility.md +163 -0
- package/repository/docs/job-recovery.md +201 -0
- package/repository/docs/payment-client.md +290 -0
- package/repository/docs/provider-setup-guide.md +348 -0
- package/repository/docs/provider-standard.md +728 -0
- package/repository/docs/recovery/README-AI.md +288 -0
- package/repository/docs/recovery/README.md +119 -0
- package/repository/docs/sow-closeout.md +67 -0
- package/repository/docs/standard/README-AI.md +614 -0
- package/repository/docs/standard/README.md +108 -0
- package/repository/docs/verification.md +504 -0
- package/repository/examples/paid-service/README.md +33 -0
- package/repository/examples/python-upstream/README.md +44 -0
- package/repository/llms.txt +65 -0
- package/repository/packages/create-stellar-service/README.md +143 -0
- package/repository/packages/dev-mcp/README.md +51 -0
- package/repository/packages/payment-client/README.md +98 -0
- package/repository/packages/protocol/README.md +15 -0
- package/repository/packages/protocol/schemas/manifest.schema.json +272 -0
- package/repository/packages/protocol/test-vectors/README.md +45 -0
- package/repository/packages/provider-kit/README.md +509 -0
- package/repository/packages/provider-kit/schemas/gateway-config.schema.json +140 -0
- package/repository/packages/verifier/README.md +131 -0
- package/repository/plugins/stellar-agent-services/README.md +31 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Provider Standard v1, in short
|
|
2
|
+
|
|
3
|
+
A shared way for AI services on Stellar to say what they do, get paid in USDC, and deliver results to people and
|
|
4
|
+
agents. It works with any programming language.
|
|
5
|
+
|
|
6
|
+
This page is the short version. The full rules are in [provider-standard.md](../provider-standard.md). If you are
|
|
7
|
+
an AI coding agent, read [README-AI.md](README-AI.md) instead.
|
|
8
|
+
|
|
9
|
+
## Why it exists
|
|
10
|
+
|
|
11
|
+
AI agents are starting to hire other AI services. Before an agent uses a service, it needs to know what the
|
|
12
|
+
service does, what it costs and how to get the result. The person paying needs to know their money is safe.
|
|
13
|
+
|
|
14
|
+
The standard gives every service the same answers:
|
|
15
|
+
|
|
16
|
+
- **Service developers** integrate once. Their service can then be found in Stellar Agent Search and paid by any
|
|
17
|
+
agent or wallet that speaks x402.
|
|
18
|
+
- **People and agents** see the exact price before paying, and get the promises below.
|
|
19
|
+
|
|
20
|
+
## How a call works
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
1. Find Stellar Agent Search finds the service. Its Stellar 8004 registration points to its manifest.
|
|
24
|
+
2. Ask Create a job. The service answers "402: pay this much".
|
|
25
|
+
3. Pay Sign one USDC payment and send the same request again.
|
|
26
|
+
4. Get The result comes back now (200), or later (202: "still working, check back").
|
|
27
|
+
5. Recover Lost the connection? Ask for the same job again. You never pay twice.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What a service publishes
|
|
31
|
+
|
|
32
|
+
One JSON file, the **manifest**, usually at `/.well-known/stellar-agent-services.json`:
|
|
33
|
+
|
|
34
|
+
```jsonc
|
|
35
|
+
{
|
|
36
|
+
"standard": "stellar-agent-services/provider@1",
|
|
37
|
+
"provider": { "name": "Example Text Tools" },
|
|
38
|
+
"baseUrl": "https://api.example.com/agent",
|
|
39
|
+
"payment": { /* USDC on Stellar, and the address that gets paid */ },
|
|
40
|
+
"services": [
|
|
41
|
+
{
|
|
42
|
+
"name": "text-stats",
|
|
43
|
+
"description": "Counts words, sentences and characters in English text.",
|
|
44
|
+
"price": { "amount": "100000" }, // 0.01 USDC (USDC has 7 decimals)
|
|
45
|
+
"charge": "on-success", // pay only if it works
|
|
46
|
+
"expectedDurationSeconds": 2,
|
|
47
|
+
"inputSchema": { "type": "object", "properties": { "text": { "type": "string" } }, "required": ["text"] },
|
|
48
|
+
"outputSchema": { "type": "object", "properties": { "words": { "type": "integer" } }, "required": ["words"] },
|
|
49
|
+
"capacity": { "concurrency": 4, "maxQueueWaitSeconds": 60 }
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Each service is also a ready-made MCP tool, with the same name, description and schemas.
|
|
56
|
+
|
|
57
|
+
## The promises
|
|
58
|
+
|
|
59
|
+
| Promise | How |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| You see the price first | The price, recipient and network come from the manifest. The wallet checks the payment request against it. |
|
|
62
|
+
| You never pay twice | One payment buys one job. Asking again, retrying or recovering never charges again. |
|
|
63
|
+
| You never pay for bad input | The service checks the input before it asks for money. |
|
|
64
|
+
| You never pay for work that can't start | A busy service says "try again in N seconds" before it asks for money. |
|
|
65
|
+
| Failed work can be free | With `on-success`, you pay only when the service returns a valid result. |
|
|
66
|
+
| Results don't get lost | Every job has a fixed address. If the connection drops, fetch the job again. |
|
|
67
|
+
| No secret keys on servers | The service only knows the address it gets paid to. |
|
|
68
|
+
|
|
69
|
+
## Two ways to charge
|
|
70
|
+
|
|
71
|
+
| `charge` | When the user pays | Good for |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `on-success` | After the work, only if it succeeded | Quick work, well under a minute |
|
|
74
|
+
| `upfront` | Before the work starts | Long work: video, reports, research |
|
|
75
|
+
|
|
76
|
+
## When many agents call at once
|
|
77
|
+
|
|
78
|
+
A service runs a set number of jobs at a time and queues the rest. A queued job shows its place in line and when it
|
|
79
|
+
should start. When the queue is full, the service answers `503` with "try again in N seconds" before any payment.
|
|
80
|
+
Agents wait and send the same request again. Nothing breaks, and nobody is charged twice.
|
|
81
|
+
|
|
82
|
+
## How to support it
|
|
83
|
+
|
|
84
|
+
| Path | For | What you write |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| TypeScript library | New services on Node, Workers, Deno or Bun | `defineService(...)` and a handler function |
|
|
87
|
+
| Gateway | An existing API in any language | A config file |
|
|
88
|
+
| Native | Any language | Your own implementation, checked against the JSON Schema and test vectors |
|
|
89
|
+
|
|
90
|
+
The quickest start is the [example service](../../examples/paid-service), built with the
|
|
91
|
+
[Provider Kit](../../packages/provider-kit).
|
|
92
|
+
|
|
93
|
+
## Get verified
|
|
94
|
+
|
|
95
|
+
A verifier tests a service and records the result on Stellar, in the 8004 Validation Registry. The badges are
|
|
96
|
+
**Standard v1**, **Payment verified** and **Recovery verified**, plus **8004 validated** and **Load-safe**.
|
|
97
|
+
|
|
98
|
+
Test your own service first, with no payment and no key: `verifier check <your manifest URL>`. Then ask a verifier
|
|
99
|
+
to check it: `provider-kit request-validation` records your request on chain. See
|
|
100
|
+
[verification.md](../verification.md).
|
|
101
|
+
|
|
102
|
+
## Learn more
|
|
103
|
+
|
|
104
|
+
- [Full standard](../provider-standard.md)
|
|
105
|
+
- [AI-readable version](README-AI.md)
|
|
106
|
+
- [Provider Kit](../../packages/provider-kit/README.md)
|
|
107
|
+
- [Payment client and wallet](../../packages/payment-client/README.md)
|
|
108
|
+
- [Example service](../../examples/paid-service/README.md)
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
# Verification, receipts, badges and Validation Registry
|
|
2
|
+
|
|
3
|
+
How a provider is checked against [Provider Standard v1](provider-standard.md), and how the result becomes
|
|
4
|
+
evidence anyone can check: a verification receipt, badges, and a record in the Stellar 8004 Validation Registry.
|
|
5
|
+
The verifier is [`packages/verifier`](../packages/verifier).
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
1. [Commands](#commands)
|
|
10
|
+
2. [Conformance levels](#conformance-levels)
|
|
11
|
+
3. [How the verifier works](#how-the-verifier-works)
|
|
12
|
+
4. [Checks](#checks)
|
|
13
|
+
5. [The test payer](#the-test-payer)
|
|
14
|
+
6. [Verification receipt](#verification-receipt)
|
|
15
|
+
7. [Badges](#badges)
|
|
16
|
+
8. [Stellar 8004 Validation Registry](#stellar-8004-validation-registry)
|
|
17
|
+
9. [Self-serve verification: verifier watch](#self-serve-verification-verifier-watch)
|
|
18
|
+
10. [Security considerations](#security-considerations)
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
Install the `verifier` command, or run each one as `npx @stellar-agent-services/verifier <command>`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @stellar-agent-services/verifier
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Command | What it does | Keys |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `verifier check <manifest-url>` | Runs every check that needs no payment: C1, C2, C3, C6 and C7's burst. `--dev` also accepts a provider on `http://localhost`. | none |
|
|
31
|
+
| `verifier verify <manifest-url> --pay` | Runs every check, paying for C4, C5 and C7's paid part. With `--validator <G…> --out <dir>`, it writes the receipt. | test payer |
|
|
32
|
+
| `verifier badge <receipt.json>` | Writes the SVG badges the receipt earns. With `--request-hash`, also "8004 validated", once the record is on chain. | none |
|
|
33
|
+
| `verifier publish <receipt.json>` | Answers a validation request with the receipt (`validation_response`). | validator, through the Stellar CLI keychain |
|
|
34
|
+
| `verifier watch` | Answers validation requests by itself: finds them on chain, runs every check, serves the receipt and badges, and publishes the answer. | test payer, and the validator through the Stellar CLI keychain |
|
|
35
|
+
|
|
36
|
+
A provider tests itself before going live:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
verifier check http://localhost:8787/.well-known/stellar-agent-services.json --dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A verifier answers validation requests by itself. It runs `verifier watch` on a server, with its validator key in the
|
|
43
|
+
Stellar CLI keychain and HTTPS in front of it:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
export VERIFIER_PAYER_SECRET=S... # the dedicated testnet test payer (see below)
|
|
47
|
+
verifier watch --source verifier --public-url https://verifier.example.com --data /var/lib/verifier
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
It finds every request that names the `verifier` key's address, checks the provider and answers on chain
|
|
51
|
+
([Self-serve verification](#self-serve-verification-verifier-watch)). The same steps by hand, for one request:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
verifier verify https://api.example.com/.well-known/stellar-agent-services.json --pay \
|
|
55
|
+
--validator $(stellar keys address verifier) --out evidence/receipts
|
|
56
|
+
# Publish evidence/receipts/<sha256>.json at an HTTPS URL, exactly as written, then:
|
|
57
|
+
verifier publish evidence/receipts/<sha256>.json --request-hash <hex> \
|
|
58
|
+
--response-uri https://example.com/receipts/<sha256>.json --source verifier
|
|
59
|
+
verifier badge evidence/receipts/<sha256>.json --request-hash <hex>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Flag | Meaning |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `--service <name>`, `--input <json>` | The service to pay for and load, and its input. Default: the cheapest service with an example, and that example. |
|
|
65
|
+
| `--max-price <usdc>`, `--max-spend <usdc>` | The most the test payer pays for one job (default 0.10) and in one run (default 1.00). |
|
|
66
|
+
| `--allow-pubnet` | Pay a provider on pubnet, which is real money. |
|
|
67
|
+
| `--rpc <url>` | The Soroban RPC. Default: the public testnet RPC, or on pubnet the one the 8004 SDK uses. |
|
|
68
|
+
| `--json` | The report, or the receipt, as JSON on standard output; progress goes to standard error. |
|
|
69
|
+
| `--public-url <https url>` | watch: where its receipts are served. Each answer on chain links there. |
|
|
70
|
+
| `--data <dir>` | watch: its state and its receipts. Default `./verifier-data`. |
|
|
71
|
+
| `--port <n>`, `--interval <s>` | watch: the HTTP port (default `$PORT`, or 8080), and how often it reads the registry (default 60 s). |
|
|
72
|
+
| `--retry-for <hours>` | watch: how long a provider that is not reachable yet is checked again (default 24). |
|
|
73
|
+
| `--network testnet\|mainnet` | watch: the network whose registry it reads, as the Stellar CLI names it. Mainnet also needs `--allow-pubnet`. |
|
|
74
|
+
|
|
75
|
+
**Exit codes:** `0` every check that ran passed, `1` a check or the command failed, `2` a usage error. `watch` runs
|
|
76
|
+
until SIGINT or SIGTERM, then exits `0`.
|
|
77
|
+
|
|
78
|
+
## Conformance levels
|
|
79
|
+
|
|
80
|
+
| Level | Name | Checks | Score | Badge |
|
|
81
|
+
| --- | --- | --- | --- | --- |
|
|
82
|
+
| 0 | – | Level 1 not reached | 0 | none |
|
|
83
|
+
| 1 | Described | C1 manifest, C6 hygiene | 33 | Standard v1 |
|
|
84
|
+
| 2 | Payable | Level 1 + C3 challenge, C4 paid call | 67 | Payment verified |
|
|
85
|
+
| 3 | Recoverable | Level 2 + C5 recovery | 100 | Recovery verified |
|
|
86
|
+
| – | Registered | C2 identity, plus a Validation Registry record of the receipt | – | 8004 validated |
|
|
87
|
+
| – | Load-safe | C7 load | – | Load-safe |
|
|
88
|
+
|
|
89
|
+
A level counts only checks that **passed**. A skipped check earns nothing, so `verifier check`, which pays for
|
|
90
|
+
nothing, reaches level 1 at most.
|
|
91
|
+
|
|
92
|
+
## How the verifier works
|
|
93
|
+
|
|
94
|
+
- **From the standard, not from the kit.** The checks follow [provider-standard.md](provider-standard.md) and cite
|
|
95
|
+
its rule ids. They don't reuse the Provider Kit, so a fault the kit shares with its providers can't pass unnoticed.
|
|
96
|
+
The verifier's tests run them against the kit, as a provider that must pass, and against kit providers made to
|
|
97
|
+
break one rule each.
|
|
98
|
+
- **MUST fails, SHOULD warns.** Each finding names a rule: an id such as `P4`, or a section such as `§5.5` for a
|
|
99
|
+
requirement the standard states without an id. A broken MUST rule fails the check. An unmet SHOULD rule is a
|
|
100
|
+
warning and never fails it. C7 is the exception: Load-safe certifies the SHOULD rules of §5.8, so they fail it.
|
|
101
|
+
- **Failed, or skipped.** A check fails only on what the provider did. When the verifier can't finish a check for
|
|
102
|
+
its own reasons, such as an RPC outage or a provider that stayed busy, the check is skipped with a reason, and
|
|
103
|
+
earns nothing.
|
|
104
|
+
- **A careful client.** The verifier sends what a conforming client sends: a fresh job id and token for each job
|
|
105
|
+
[J1], the same request again after a `429` or `503` [Q8], polls no faster than `Retry-After` [Q9].
|
|
106
|
+
- **It pays only where a client would.** No payment unless C1 passed and the challenge matches the manifest [P7],
|
|
107
|
+
within the verifier's own limits.
|
|
108
|
+
- **The chain decides.** A receipt is a claim [J16]. The verifier matches payments on chain by the auth-entry nonce
|
|
109
|
+
it signed, so another job's payment can never pass as this one's. "Not charged" is proven too: once a payment's
|
|
110
|
+
signature has expired, the chain must show no transfer for it.
|
|
111
|
+
|
|
112
|
+
Order of a run: C1, C2, C3, C4, C5, C6's probes, C7, then C6 over every exchange. The load test runs late so a
|
|
113
|
+
provider's rate limit can't turn away the other checks' requests.
|
|
114
|
+
|
|
115
|
+
## Checks
|
|
116
|
+
|
|
117
|
+
### C1 Manifest (§2)
|
|
118
|
+
|
|
119
|
+
The three steps of §2.4, and more.
|
|
120
|
+
|
|
121
|
+
| What | Rule |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| HTTPS (or loopback `http://` with `--dev`), `200`, `Content-Type: application/json` | M1 |
|
|
124
|
+
| No redirect, at most 256 KiB, I-JSON: UTF-8, no lone surrogates, no duplicate keys | M3 |
|
|
125
|
+
| `Cache-Control: max-age` of 300 s or less | M4 (warning) |
|
|
126
|
+
| The manifest URL has no user info | M6 |
|
|
127
|
+
| `standard` of another major version is rejected | M8 |
|
|
128
|
+
| The manifest JSON Schema, which also encodes M5, M6, M8, P1, S4 and S5; each error names its JSON Pointer | M9 |
|
|
129
|
+
| `checkManifestRules()`: same origin, unique names, schema sizes, examples, `payTo` checksum | M5, M7, S6, S9, P11 |
|
|
130
|
+
| Every `inputSchema` and `outputSchema` is a JSON Schema 2020-12 that compiles | §3.3 |
|
|
131
|
+
| `on-success` work expected to take at most half of `maxTimeoutSeconds` | S2 (warning) |
|
|
132
|
+
| No `if`/`then`/`else` or `patternProperties`, no `oneOf` or `allOf` at the root | S7 (warning) |
|
|
133
|
+
| Reverse-DNS extension keys | X1 (warning) |
|
|
134
|
+
|
|
135
|
+
Duplicate keys matter: `JSON.parse` keeps the last of two `payTo` members, while another parser may keep the
|
|
136
|
+
first, so two clients could show different payees.
|
|
137
|
+
|
|
138
|
+
C1 also decides what the run may do. The provider is **probed** only when the manifest passed the JSON Schema and
|
|
139
|
+
its `baseUrl` is on its own origin. It is **paid** only when C1 passed.
|
|
140
|
+
|
|
141
|
+
### C2 Identity (§7)
|
|
142
|
+
|
|
143
|
+
Reads only: the 8004 identity registry and the registration file.
|
|
144
|
+
|
|
145
|
+
| What | Rule |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| The agent exists | I2 |
|
|
148
|
+
| The registration file, from a `data:` or `https:` agent URI, lists `{ "name": "stellar-agent-services", "endpoint": <manifest URL>, "version": "1" }` | I1 |
|
|
149
|
+
| It also lists `{ "name": "web", "endpoint": <baseUrl> }` and sets `x402: true` | I1 (warning) |
|
|
150
|
+
| The agent's wallet, `get_agent_wallet(agentId)`, is the manifest's `payTo` | I3 |
|
|
151
|
+
|
|
152
|
+
Skipped without `provider.agentId`, in development mode, and when the registry or an `ipfs://` registration
|
|
153
|
+
can't be read. An `http://` agent URI fails: HTTPS is what proves the file's origin.
|
|
154
|
+
|
|
155
|
+
### C3 Challenge (§4)
|
|
156
|
+
|
|
157
|
+
With no payment, for every service the verifier has an input for: its first example that matches `inputSchema`,
|
|
158
|
+
the `--input` given for it, or an input built from the schema that validates against it.
|
|
159
|
+
|
|
160
|
+
| What | Rule |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| An input that fails `inputSchema` gets `400 invalid_input`, never a `402` | P8 |
|
|
163
|
+
| A valid input gets `402` with the x402 v2 `PAYMENT-REQUIRED` header | P3 |
|
|
164
|
+
| The challenge's one `accepts` entry equals the manifest's payment option plus the price | P4 |
|
|
165
|
+
| `resource.url` is the public create-job URL | P5 |
|
|
166
|
+
| The challenge carries the Bazaar extension | P6 (warning) |
|
|
167
|
+
| A `G…` `payTo` exists and holds an authorized USDC trustline | P2 |
|
|
168
|
+
|
|
169
|
+
### C4 Paid call (§4, §5)
|
|
170
|
+
|
|
171
|
+
The test payer pays for one job of the target service (`--service`, or the cheapest one with an example).
|
|
172
|
+
|
|
173
|
+
1. Unpaid create-job with a fresh job id and token: a `402` whose challenge matches the manifest. Otherwise
|
|
174
|
+
nothing is signed [P7].
|
|
175
|
+
2. The payment is signed once and sent with the identical request. A busy answer is waited out with the same
|
|
176
|
+
payment while it is valid [Q8], a `409` is followed with get-result [J7], and a lost response is recovered with
|
|
177
|
+
get-result, never paid again.
|
|
178
|
+
3. Every job view is checked: the job id is the `Idempotency-Key` (§5.2), `charge` is the manifest's (§3.2),
|
|
179
|
+
`charged` fits the state (§5.6), `200` once finished and otherwise `202` with `Retry-After`, plus `Location` on
|
|
180
|
+
create-job [J8], output only when it may be shown [J14], and a receipt whose terms are the payment's (§5.7).
|
|
181
|
+
A `payment_uncertain` job is polled within the same bounded follow period while the provider reconciles it
|
|
182
|
+
from the chain [J13]. Polling only reads the original job [J11]; unresolved uncertainty earns no paid check.
|
|
183
|
+
4. The job must succeed (§5.6), charged, with output that matches `outputSchema` [S1].
|
|
184
|
+
5. The receipt's transaction must have succeeded, carry the payment the verifier signed, and move exactly the
|
|
185
|
+
price from the payer to `payTo` [J16]. The response that settled SHOULD carry `PAYMENT-RESPONSE` [P10].
|
|
186
|
+
|
|
187
|
+
### C5 Recovery (§5.3–§5.5, [job-recovery.md](job-recovery.md))
|
|
188
|
+
|
|
189
|
+
It recovers C4's job, as a client that lost the create-job response would.
|
|
190
|
+
|
|
191
|
+
| Request | Expected | Rule |
|
|
192
|
+
| --- | --- | --- |
|
|
193
|
+
| create-job again: same id, token and input, no payment | `200` and the same job, no `402` | J5 |
|
|
194
|
+
| The same, with the same `PAYMENT-SIGNATURE` | the same job; the receipt still names the same transaction | J5, J16 |
|
|
195
|
+
| Same id, another input | `422 idempotency_key_reused` | J6 |
|
|
196
|
+
| Same id and input, another token | `422 idempotency_key_reused` | J6 |
|
|
197
|
+
| check-status and get-result with the token | `200` and the job, get-result with its output | J8, J15 |
|
|
198
|
+
| No token, a wrong token, an unknown job, on both | `404 job_not_found`, the same answer each time | J10 |
|
|
199
|
+
|
|
200
|
+
### C6 Hygiene
|
|
201
|
+
|
|
202
|
+
Two probes, then every exchange of the run.
|
|
203
|
+
|
|
204
|
+
| What | Rule |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| A body of `maxInputBytes` + 1 bytes is refused with `413 payload_too_large` | §5.4 |
|
|
207
|
+
| The job endpoints never redirect | §5.5 |
|
|
208
|
+
| Every error but `402` is `application/problem+json`, with a known `code` that goes with the HTTP status | E1 |
|
|
209
|
+
| `409` carries `Retry-After`; so do `429` and `503` | J7, §6 |
|
|
210
|
+
| No response contains a job token or a signed payment the verifier sent | E2 |
|
|
211
|
+
| No response contains a Stellar secret key | §10 |
|
|
212
|
+
| CORS on the manifest and the job endpoints, and a preflight that allows them | J17 (warning) |
|
|
213
|
+
|
|
214
|
+
### C7 Load (§5.8)
|
|
215
|
+
|
|
216
|
+
| What | Rule |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| 20 unpaid create-job requests at once: each answered within 30 s with `402`, or `429`/`503` with `Retry-After` | Q1 |
|
|
219
|
+
| With the test payer, once C4 passed: `capacity.concurrency` + 1 jobs paid at once (5 without `capacity`, at most 10) | |
|
|
220
|
+
| Each one accepted (`200`, or `202` with `queue` while queued) or refused with `429`/`503` and `Retry-After` | Q3, Q5 |
|
|
221
|
+
| Every accepted job finishes, and proves its own payment | Q4, J16 |
|
|
222
|
+
| A refused payment never lands: once its signature expired, the chain shows no transfer for it | Q3 |
|
|
223
|
+
| check-status and get-result keep answering at capacity | Q7 (warning) |
|
|
224
|
+
|
|
225
|
+
## The test payer
|
|
226
|
+
|
|
227
|
+
The paid checks are paid by a **dedicated** testnet account: create one for the verifier, fund it with XLM and
|
|
228
|
+
USDC, and keep nothing else in it. Its secret key is read from `VERIFIER_PAYER_SECRET` and stays in memory.
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
stellar keys generate verifier-payer --network testnet --fund
|
|
232
|
+
stellar tx new change-trust --source-account verifier-payer --network testnet \
|
|
233
|
+
--line USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5
|
|
234
|
+
# Testnet USDC: https://faucet.circle.com, or the testnet DEX (`stellar-pay wallet fund` does this for its own key).
|
|
235
|
+
export VERIFIER_PAYER_SECRET=$(stellar keys secret verifier-payer) # `stellar keys show` in older versions
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
A full run against a service that costs 0.01 USDC signs for 0.06 USDC: one C4 job and five C7 jobs.
|
|
239
|
+
|
|
240
|
+
The verifier's own limits apply before anything is signed:
|
|
241
|
+
- one job costs at most `--max-price` (default 0.10 USDC), and a run signs for at most `--max-spend` (default 1.00
|
|
242
|
+
USDC). A payment counts once signed, whether or not it settles;
|
|
243
|
+
- pubnet providers are paid only with `--allow-pubnet`;
|
|
244
|
+
- each signed payment is decoded before it is sent, and must move exactly the approved asset and amount, from the
|
|
245
|
+
payer, to `payTo`.
|
|
246
|
+
|
|
247
|
+
## Verification receipt
|
|
248
|
+
|
|
249
|
+
The verifier issues one receipt per run (`VerificationReceipt` in
|
|
250
|
+
[packages/protocol](../packages/protocol/src/receipts.ts), JSON Schema
|
|
251
|
+
[verification-receipt.schema.json](../packages/protocol/schemas/verification-receipt.schema.json)). It is published as
|
|
252
|
+
RFC 8785 canonical JSON.
|
|
253
|
+
|
|
254
|
+
| Field | Meaning |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
| `standard` | `stellar-agent-services/provider@1` |
|
|
257
|
+
| `agentId` | The provider's Stellar 8004 agent id. Required to publish to the Validation Registry. |
|
|
258
|
+
| `network` | `stellar:testnet` or `stellar:pubnet` |
|
|
259
|
+
| `validator` | The verifier's `G…` address, which signs `validation_response` |
|
|
260
|
+
| `startedAt`, `checkedAt` | RFC 3339 |
|
|
261
|
+
| `level` | The conformance level reached, 0–3 (0: not even Described) |
|
|
262
|
+
| `score`, `scoreDirection` | 0, 33, 67 or 100 for levels 0–3; `higher-is-better`. `score` is also the on-chain `response`. |
|
|
263
|
+
| `manifest` | `{ url, sha256 }`, where `sha256` is the JCS hash of the parsed manifest. No `sha256` when the manifest could not be read as JSON. |
|
|
264
|
+
| `results[]` | One per check, in order C1–C7: `check`, `status` (`passed`, `failed`, `skipped`), `failedRules`, `warnings?`, `reason?` (why it was skipped), `findings?` (each rule, `fail` or `warn`, and what was observed), `evidence?` |
|
|
265
|
+
| `payment` | The C4 payment receipt, after the verifier confirmed it on chain |
|
|
266
|
+
| `tool` | The software that issued it: `{ name, version }` |
|
|
267
|
+
|
|
268
|
+
`standard`, `agentId`, `network`, `validator`, `checkedAt`, `score`, `scoreDirection` and `results` form the
|
|
269
|
+
envelope shared with Stellar ERC-8126 records ([erc-compatibility.md](erc-compatibility.md), T2).
|
|
270
|
+
|
|
271
|
+
**Rules beyond the JSON Schema:** `level` is the highest level whose checks all passed, and `score` is that level's.
|
|
272
|
+
`verifier badge` and `verifier publish` refuse a receipt that breaks either.
|
|
273
|
+
|
|
274
|
+
**One file, one hash.** The receipt is written to `<dir>/<sha256>.json`, holding exactly its canonical bytes with
|
|
275
|
+
no trailing newline. The name, the SHA-256 of the file and the on-chain `response_hash` are the same value, so
|
|
276
|
+
anyone can fetch the `response_uri`, hash it, and compare it with the chain. Upload the file as it is: reformatting
|
|
277
|
+
it changes the hash.
|
|
278
|
+
|
|
279
|
+
**What a receipt never holds:** secrets, job tokens, payment payloads, or text a provider chose. Messages quote a
|
|
280
|
+
provider's value only when it is a short plain token, such as an id or a status.
|
|
281
|
+
|
|
282
|
+
Receipts are issued only for providers served over HTTPS, never in development mode.
|
|
283
|
+
|
|
284
|
+
## Badges
|
|
285
|
+
|
|
286
|
+
SVG images derived **only** from a receipt, never from what a provider says about itself.
|
|
287
|
+
|
|
288
|
+
| Badge | Earned by |
|
|
289
|
+
| --- | --- |
|
|
290
|
+
| Standard v1 | Level 1 |
|
|
291
|
+
| Payment verified | Level 2 |
|
|
292
|
+
| Recovery verified | Level 3 |
|
|
293
|
+
| Load-safe | C7 passed |
|
|
294
|
+
| 8004 validated | C2 passed, and the Validation Registry anchors this receipt |
|
|
295
|
+
|
|
296
|
+
- **Each badge names the network**, "testnet" or "mainnet", so testnet evidence never passes for mainnet.
|
|
297
|
+
- **"8004 validated" needs the chain.** `verifier badge --request-hash <hex>` reads the request's record. The record
|
|
298
|
+
must hold a response by the receipt's validator, for its agent, with its hash, its score and the tag
|
|
299
|
+
`provider-standard-v1`.
|
|
300
|
+
- **The badge links back to its evidence.** Its description carries the receipt's SHA-256. Where a badge is shown,
|
|
301
|
+
link it to the receipt.
|
|
302
|
+
- **Safe to embed anywhere.** The SVG is self-contained (no fonts, scripts or links), accessible (`role="img"` with a
|
|
303
|
+
title), and the same receipt always gives the same bytes.
|
|
304
|
+
|
|
305
|
+
Files are written next to the receipt as `<sha256>.<badge>.svg`, for example `<sha256>.payment-verified.svg`.
|
|
306
|
+
|
|
307
|
+
## Stellar 8004 Validation Registry
|
|
308
|
+
|
|
309
|
+
The registry ([contract](https://github.com/trionlabs/stellar-8004/tree/main/contracts/validation-registry), testnet
|
|
310
|
+
`CC5USZRO26MOIAVNYTTJDS63C2OBBLREOAOET4CPF2EZWO3YFKLMO3SL`) keeps one record per request. Only the validator a
|
|
311
|
+
request names may answer it, and may update its answer later.
|
|
312
|
+
|
|
313
|
+
### 1. The provider's owner asks
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
validation_request(caller = owner, validator_address = <verifier G…>, agent_id, request_uri, request_hash)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The request document names what is to be verified (`ValidationRequest` in
|
|
320
|
+
[packages/protocol](../packages/protocol/src/validation-request.ts), JSON Schema
|
|
321
|
+
[validation-request.schema.json](../packages/protocol/schemas/validation-request.schema.json)):
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"standard": "stellar-agent-services/provider@1",
|
|
326
|
+
"type": "validation-request",
|
|
327
|
+
"agentId": 42,
|
|
328
|
+
"network": "stellar:testnet",
|
|
329
|
+
"manifest": { "url": "https://api.example.com/.well-known/stellar-agent-services.json" },
|
|
330
|
+
"validator": "GBZSBASBFMV5RGKPRIPCB5QQVAJ35IXKMTF2A2766MOA4ZHQ7MLSIPXU",
|
|
331
|
+
"requestedAt": "2026-10-26T10:00:00Z"
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
| Field | Meaning |
|
|
336
|
+
| --- | --- |
|
|
337
|
+
| `standard`, `type` | `stellar-agent-services/provider@1`, `validation-request` |
|
|
338
|
+
| `agentId`, `network` | The provider's Stellar 8004 agent id, and `stellar:testnet` or `stellar:pubnet` |
|
|
339
|
+
| `manifest` | `{ url }`: the HTTPS manifest URL the agent's registration names [I1] |
|
|
340
|
+
| `validator` | The verifier's `G…` address: the only account that may answer |
|
|
341
|
+
| `requestedAt` | RFC 3339 |
|
|
342
|
+
|
|
343
|
+
- **One document, one hash.** `request_hash` is the SHA-256 of the document's RFC 8785 canonical JSON, the
|
|
344
|
+
`contentHash` mode of `generateRequestNonce`. `request_uri` holds exactly those bytes: a
|
|
345
|
+
`data:application/json;base64,` URI, or an HTTPS URL serving them. Anyone can hash what the URI holds and compare it
|
|
346
|
+
with the chain. [validation-requests.json](../packages/protocol/test-vectors/validation-requests.json) pins both.
|
|
347
|
+
- **Every request is new.** `requestedAt` makes each document unique, and the registry refuses a hash it already has.
|
|
348
|
+
- **Who may ask.** The registry takes a request only from the agent's owner, the address approved for the agent, or an
|
|
349
|
+
operator of the owner's.
|
|
350
|
+
- **Never the provider itself.** The validator is not the agent's owner or its wallet. The registry would accept such a
|
|
351
|
+
request, but its answer would prove nothing.
|
|
352
|
+
- **Ready to verify.** The provider is served over HTTPS and its registration passes [I1]–[I3]. Otherwise C2 fails, and
|
|
353
|
+
the answer can never earn "8004 validated". The request may go out before the deploy is live: `verifier watch` checks
|
|
354
|
+
a provider that is not reachable yet again for a day.
|
|
355
|
+
- **Readable on chain.** The registry keeps `request_uri` in the `validation_request` event only, and RPC nodes keep
|
|
356
|
+
events for a limited window. A `data:` URI keeps the document there with nothing to host. Once the event is gone, a
|
|
357
|
+
verifier takes the manifest URL from the agent's registration instead [I1].
|
|
358
|
+
|
|
359
|
+
`provider-kit launch` sends the request on the same approval page as the registration, once the new agent id is
|
|
360
|
+
known. `provider-kit request-validation` sends it on its own. Both check all of this before anything is signed, put the
|
|
361
|
+
document on chain as a `data:` URI and read the registry back. The owner approves the request in their own wallet, on a page the command
|
|
362
|
+
opens, and the account the wallet connects is checked as the one asking. With `--source <key>`, the Stellar CLI
|
|
363
|
+
keychain signs instead, for scripts and CI:
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
provider-kit request-validation --config provider.config.json --validator <verifier G…> --out request.json
|
|
367
|
+
provider-kit request-validation --config provider.config.json --validator <verifier G…> --source owner --dry-run
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
It prints the request hash. Nothing has to be sent to the verifier besides: a verifier running `verifier watch` finds
|
|
371
|
+
the request on chain. `--manifest <url>` instead of `--config` works for any provider, library or native. Without the
|
|
372
|
+
kit, the owner sends the same request with the Stellar CLI:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
stellar contract invoke --id CC5USZRO26MOIAVNYTTJDS63C2OBBLREOAOET4CPF2EZWO3YFKLMO3SL --source-account owner \
|
|
376
|
+
--network testnet -- validation_request --caller $(stellar keys address owner) \
|
|
377
|
+
--validator_address <verifier G…> --agent_id 42 --request_uri <uri> --request_hash <hex>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 2. The verifier answers
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
validation_response(caller = validator, request_hash, response = score, response_uri = receipt URL,
|
|
384
|
+
response_hash = sha256(receipt), tag = "provider-standard-v1")
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
`verifier watch` sends it by itself ([below](#self-serve-verification-verifier-watch)); `verifier publish` sends it by
|
|
388
|
+
hand. Both check everything before the Stellar CLI signs, and refuse when anything is off:
|
|
389
|
+
- the receipt is valid, and recent: checked within `--max-age` hours, default 24;
|
|
390
|
+
- the request exists, names this validator and the receipt's agent, on the receipt's network;
|
|
391
|
+
- `response_uri` is HTTPS and serves exactly the receipt's bytes;
|
|
392
|
+
- the `--source` key is the receipt's validator.
|
|
393
|
+
|
|
394
|
+
The validator key stays in the Stellar CLI keychain; the verifier never reads it. After sending, both read the record
|
|
395
|
+
back to confirm it.
|
|
396
|
+
|
|
397
|
+
### 3. Anyone reads it
|
|
398
|
+
|
|
399
|
+
`get_validation_status(request_hash)` returns the validator, agent, score, `response_hash` and tag;
|
|
400
|
+
`get_agent_validations_paginated(agent_id)` lists an agent's requests. Fetch the `response_uri`, check its SHA-256
|
|
401
|
+
against `response_hash`, and read the receipt. `get_summary(agent_id, validators, "provider-standard-v1")` averages
|
|
402
|
+
this standard's records without mixing in other kinds ([erc-compatibility.md](erc-compatibility.md), T2).
|
|
403
|
+
|
|
404
|
+
## Self-serve verification: verifier watch
|
|
405
|
+
|
|
406
|
+
`verifier watch` answers validation requests with no hand-off. The provider's owner sends `validation_request`, naming
|
|
407
|
+
the verifier, and nothing else. The watcher runs on a server until it is stopped:
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
export VERIFIER_PAYER_SECRET=S...
|
|
411
|
+
verifier watch --source verifier --public-url https://verifier.example.com --data /var/lib/verifier
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Each round
|
|
415
|
+
|
|
416
|
+
Every `--interval` seconds (default 60):
|
|
417
|
+
|
|
418
|
+
1. It reads the Validation Registry's list of the requests that name the validator, the `--source` key's address
|
|
419
|
+
(`get_validator_requests_paginated`). The list only grows, so each round reads only what is new. A request that
|
|
420
|
+
has a response already is left alone.
|
|
421
|
+
2. It works on each request that is due, one at a time, oldest first:
|
|
422
|
+
- it finds the manifest to check (below);
|
|
423
|
+
- it runs every check, C1–C7, with the test payer, within the limits of `verifier verify --pay`;
|
|
424
|
+
- it writes the receipt to `<data>/receipts/<sha256>.json`, with its badges;
|
|
425
|
+
- it publishes `validation_response`, with `response_uri` set to `<public-url>/receipts/<sha256>.json`, as
|
|
426
|
+
`verifier publish` does: the same checks, then the Stellar CLI signs. Once the record shows the answer, it adds
|
|
427
|
+
the "8004 validated" badge if the receipt earns it.
|
|
428
|
+
|
|
429
|
+
### Which manifest it checks
|
|
430
|
+
|
|
431
|
+
- **The request document first.** The registry keeps no `request_uri`: the watcher reads it from the request's
|
|
432
|
+
`validation_request` event, at the ledger of the request (its `last_update`, while it has no response). The
|
|
433
|
+
document must hash to `request_hash`. One that does, but is not a Provider Standard v1 request for this agent, this
|
|
434
|
+
network and this validator, is refused.
|
|
435
|
+
- **Else the registration.** RPC nodes keep events for a limited window, 7 days by default. For an older request, or a
|
|
436
|
+
document that can't be read, the manifest URL is the `stellar-agent-services` endpoint of the agent's registration
|
|
437
|
+
file [I1], from a `data:` or `https:` agent URI. A registration that lists several needs the document to choose.
|
|
438
|
+
- **Never a URL the registration doesn't list.** The manifest URL the request names must be the registration's
|
|
439
|
+
endpoint, character for character, or the watcher doesn't check it. So a request can't point the test payer and the
|
|
440
|
+
load test at someone else's server.
|
|
441
|
+
- Only an `https://` manifest URL without user info is checked [M1], [M6].
|
|
442
|
+
|
|
443
|
+
### Retries
|
|
444
|
+
|
|
445
|
+
| What happens | What the watcher does |
|
|
446
|
+
| --- | --- |
|
|
447
|
+
| The manifest URL gives no answer, or `404`, `408`, `425`, `429` or `5xx` | The provider is not reachable yet. It checks again after 1, 2, 4… minutes, at most an hour apart, for `--retry-for` hours (default 24) after it found the request. Then the failing result is the answer. |
|
|
448
|
+
| A run fails no check but skips one, such as during an RPC outage | It runs again, three runs in all, then answers with the last. |
|
|
449
|
+
| No manifest URL: the registration can't be read, or doesn't list the URL the request names | It waits as long, then refuses the request: no check, and no answer. |
|
|
450
|
+
| The request document asks for something else, or the manifest names another agent or network | It refuses the request at once. |
|
|
451
|
+
| The test payer holds less than `--max-spend` | Requests wait, and `/healthz` says so. A payer that can't pay never fails a provider. |
|
|
452
|
+
| The registry, the Stellar CLI or `<public-url>` fails | It tries again later. |
|
|
453
|
+
|
|
454
|
+
### Never twice
|
|
455
|
+
|
|
456
|
+
- The registry is read before every step, and the checks before signing refuse a request that has a response. A
|
|
457
|
+
response sent with `verifier publish`, or by another watcher, is never overwritten.
|
|
458
|
+
- The state records a response as sent before the Stellar CLI runs. After a crash, the response gets two minutes to
|
|
459
|
+
show in the registry. Only then is it sent again, with the same receipt.
|
|
460
|
+
- `<data>/watch.lock` keeps a second watcher off the same data directory. Run one watcher per validator key.
|
|
461
|
+
|
|
462
|
+
### What it serves
|
|
463
|
+
|
|
464
|
+
| Path | What |
|
|
465
|
+
| --- | --- |
|
|
466
|
+
| `/receipts/<sha256>.json` | The receipt, exactly as written: the `response_uri` on chain |
|
|
467
|
+
| `/receipts/<sha256>.<badge>.svg` | Its badges, such as `<sha256>.payment-verified.svg` |
|
|
468
|
+
| `/requests/<request hash>.json` | Where a request stands: `waiting`, `publishing`, `answered` or `refused`, the next try, why it is not answered yet, and the receipt |
|
|
469
|
+
| `/healthz` | `ok`, or `degraded` with the verifier's own problems, such as a test payer to fund |
|
|
470
|
+
|
|
471
|
+
Receipts and badges are named by their content and never change. Everything is public, with CORS open.
|
|
472
|
+
|
|
473
|
+
### Running it
|
|
474
|
+
|
|
475
|
+
- A small server with Node.js 22.13 or later, the Stellar CLI and a persistent volume is enough. Create the validator
|
|
476
|
+
key there, with XLM for fees: `stellar keys generate verifier --network testnet --fund`.
|
|
477
|
+
- Put HTTPS in front of `--port` at `--public-url`, with a reverse proxy or the platform's TLS. Before each answer,
|
|
478
|
+
the watcher checks that the receipt is served there.
|
|
479
|
+
- Keep `--data` on the volume and back it up: the chain links to the receipts in it. Keep `--public-url` stable for
|
|
480
|
+
the same reason.
|
|
481
|
+
- Keep at least `--max-spend` (default 1.00 USDC) in the test payer. A run with a 0.01 USDC service signs for 0.06.
|
|
482
|
+
- On mainnet: `--network mainnet --allow-pubnet`. The test payer then pays real money.
|
|
483
|
+
|
|
484
|
+
## Security considerations
|
|
485
|
+
|
|
486
|
+
- **Keys.** The test payer holds testnet USDC only, within the verifier's limits. The validator key signs through
|
|
487
|
+
the Stellar CLI keychain and never enters the verifier. Neither is shared with providers, discovery or the payment
|
|
488
|
+
client.
|
|
489
|
+
- **The provider is untrusted.** Every request has a deadline and a size limit on its answer, and no redirect is
|
|
490
|
+
followed. Once the manifest names its origin, the verifier sends requests to that origin only, so a manifest can't
|
|
491
|
+
point the load test at someone else's server.
|
|
492
|
+
- **Provider schemas are untrusted.** Each compiles in its own Ajv instance, after a check against the JSON Schema
|
|
493
|
+
2020-12 meta-schema; nothing remote is fetched [S5]. A provider can still slow its own run down with a costly
|
|
494
|
+
regular expression in a `pattern`. A verifier run as a shared service should run each verification in its own
|
|
495
|
+
process with a time limit.
|
|
496
|
+
- **Address checks.** The verifier does not resolve host names to refuse private addresses. A verifier run as a
|
|
497
|
+
shared service should run where it can reach only the public internet.
|
|
498
|
+
- **The watcher is a shared service.** Any agent owner can name it, and it then fetches URLs the owner chose: the
|
|
499
|
+
manifest, an `https:` agent URI or `request_uri`. Run it where it can reach only the public internet. It checks one
|
|
500
|
+
provider at a time, and a run signs for at most `--max-spend`; keep only what you are willing to spend in the test
|
|
501
|
+
payer. `/requests/` and `/healthz` show no key. They quote the checks' reasons, as receipts do, and give the
|
|
502
|
+
verifier's own errors as one plain line; the details go to its log.
|
|
503
|
+
- **No secrets in evidence.** Job tokens and signed payments are kept in memory to check that no response echoes
|
|
504
|
+
them [E2], and never written to a receipt or a report.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Example paid service
|
|
2
|
+
|
|
3
|
+
A sample provider built only with `@stellar-agent-services/provider-kit`. Pilot providers can copy it as
|
|
4
|
+
their template.
|
|
5
|
+
|
|
6
|
+
| Service | Charge | Price | Purpose |
|
|
7
|
+
| --- | --- | --- | --- |
|
|
8
|
+
| `text-stats` | on-success | 0.01 USDC | The basic paid call: finishes at once, failed work is never charged |
|
|
9
|
+
| `delayed-report` | upfront | 0.02 USDC | The Job Recovery demo: answers `202`, then check-status and get-result. Also the capacity demo: 2 at a time, up to 8 queued, then `503` before payment. |
|
|
10
|
+
|
|
11
|
+
Jobs are kept in SQLite (`JOB_DB_PATH`, default `./jobs.sqlite`), so a paid job survives a restart: stop the
|
|
12
|
+
server while a report is queued, start it again, and the report runs.
|
|
13
|
+
|
|
14
|
+
Set `HOST=127.0.0.1` when a reverse proxy on the same server supplies HTTPS. The default is `0.0.0.0`, which also
|
|
15
|
+
works inside a container. [The Oracle testnet deployment](../../deploy/d2/oracle/README.md) uses loopback and
|
|
16
|
+
a separate persistent job store beside an existing mainnet service.
|
|
17
|
+
|
|
18
|
+
`text-stats` admits one on-success job at a time and refuses excess work before payment [Q2], [Q3]. This is
|
|
19
|
+
deliberately conservative for the shared testnet facilitator. A live verification run at the former default of
|
|
20
|
+
four concurrent jobs encountered settlement errors; its receipt is retained in
|
|
21
|
+
[the verification evidence](../../evidence/receipts/README.md).
|
|
22
|
+
|
|
23
|
+
Run it with `npx provider-kit dev -- npm start` in this directory, after `npm install && npm run build` at the
|
|
24
|
+
repository root: with `PAY_TO` empty, `dev` makes a throwaway testnet account to be paid to, so you need no wallet yet
|
|
25
|
+
(or run `PAY_TO=G... npm start` with an account of yours). `.env.example` lists the settings; they are public values
|
|
26
|
+
only. Then `npx provider-kit try text-stats`, from
|
|
27
|
+
another terminal, pays for one job on testnet from a throwaway account and shows the result (`try delayed-report`
|
|
28
|
+
shows a job that answers 202 and is polled). See
|
|
29
|
+
[packages/payment-client](../../packages/payment-client/README.md) for a full testnet walkthrough, and the
|
|
30
|
+
[setup guide](../../docs/provider-setup-guide.md) for building your own provider from this template.
|
|
31
|
+
|
|
32
|
+
Providers with an existing API in another language don't need this template. They can use the kit's gateway
|
|
33
|
+
mode instead: see [examples/python-upstream](../python-upstream).
|