@quartermaster-sh/sdk 0.1.0

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 ADDED
@@ -0,0 +1,290 @@
1
+ # @quartermaster-sh/sdk
2
+
3
+ **Check before your agent pays.** Quartermaster is a policy gateway and
4
+ attestation ledger for AI-agent spending. Before your agent makes an x402
5
+ payment, ask Quartermaster: *is this allowed?* — and get back a signed,
6
+ tamper-evident record of the decision.
7
+
8
+ This SDK hides all the crypto. You bring an API key; it handles Ed25519 request
9
+ signing, x402 message construction, and chain verification for you.
10
+
11
+ ```ts
12
+ import { Quartermaster, usdc } from '@quartermaster-sh/sdk';
13
+
14
+ const qm = new Quartermaster({ apiKey: process.env.QM_API_KEY! }); // points at the sandbox by default
15
+
16
+ const check = await qm.check({ agent: 'research-bot', to: 'openai', amount: usdc(5) });
17
+ if (!check.approved) throw new Error(`Payment blocked: ${check.reasons.join(', ')}`);
18
+ console.log('✓ checked & logged — proof:', check.receipt!.hash); // your signed, tamper-evident proof
19
+ ```
20
+
21
+ That's the whole thing. One credential, one call. Your agent's spend just got
22
+ checked against its policy and written to an append-only ledger.
23
+
24
+ And when a payment is **not** allowed, you get a clear reason instead of a
25
+ surprise. On the sandbox, `research-bot` caps at **$5** and only pays a small
26
+ allowlist of friendly payees (`openai`, `anthropic`, …), so each rule denies on
27
+ its own:
28
+
29
+ ```ts
30
+ // Over the per-transaction cap, to an allowed payee:
31
+ const overCap = await qm.check({ agent: 'research-bot', to: 'openai', amount: usdc(500) });
32
+ // overCap.approved === false
33
+ // overCap.reasons === ['per_tx_cap_exceeded']
34
+
35
+ // Within the cap, but a payee that isn't on the sandbox allowlist:
36
+ const badPayee = await qm.check({ agent: 'research-bot', to: 'sketchy-vendor', amount: usdc(5) });
37
+ // badPayee.approved === false
38
+ // badPayee.reasons === ['payee_not_allowed']
39
+ ```
40
+
41
+ > `check()` reports **every** rule a payment breaks, so a request that is both
42
+ > over-cap *and* to a disallowed payee comes back with both reasons.
43
+
44
+ ---
45
+
46
+ ## Install
47
+
48
+ ```sh
49
+ npm i @quartermaster-sh/sdk
50
+ ```
51
+
52
+ Node ≥ 18. ESM-only. Zero heavy dependencies — just audited `@noble` crypto
53
+ primitives. Runs on Node, edge runtimes, and modern browsers.
54
+
55
+ ---
56
+
57
+ ## The 30-second mental model
58
+
59
+ - **Your API key is a *tenant* credential.** It identifies your organization —
60
+ not any single agent. Keep it server-side.
61
+ - **An *agent* is a spender you refer to by name** (`'research-bot'`). Each agent
62
+ has its own Ed25519 signing key; the SDK signs every request with it so
63
+ Quartermaster knows *who* is asking. On the sandbox, demo agents are built in,
64
+ so you don't provide keys until you go to production.
65
+ - **A *receipt* is an *attestation*** — a signed, hash-chained ledger record of
66
+ the decision. `check.receipt.hash` is your proof it happened and hasn't been
67
+ altered.
68
+ - **Deny by default.** No matching policy, an ambiguous rule, or a malformed
69
+ request all result in DENY. Quartermaster never approves on an error path.
70
+ - **No money moves here.** v1 *authorizes and records*; settlement is handled by
71
+ an upstream x402 facilitator. `check()` is the authorization + receipt step.
72
+
73
+ ---
74
+
75
+ ## `check()`
76
+
77
+ ```ts
78
+ qm.check(input: CheckInput): Promise<CheckResult>
79
+ ```
80
+
81
+ | `CheckInput` field | | |
82
+ |---|---|---|
83
+ | `to` | required | Payee. An address, or a sandbox payee name like `'openai'`. |
84
+ | `amount` | required | Atomic units as a string — use `usdc()`. Never a float. |
85
+ | `agent` | optional | Agent name. Defaults to your configured default / the sandbox demo agent. |
86
+ | `network` | optional | CAIP-2 network. Default `'eip155:8453'` (Base). |
87
+ | `asset` | optional | Asset contract/code. Default USDC on Base. |
88
+ | `nonce` | optional | 32-byte hex EIP-3009 nonce. Default: a fresh random one. |
89
+ | `validForSeconds` | optional | Authorization validity window. Default `600`. |
90
+
91
+ ```ts
92
+ interface CheckResult {
93
+ approved: boolean; // true iff the decision was APPROVE
94
+ decision: 'APPROVE' | 'DENY';
95
+ reasons: DenyReason[]; // [] when approved
96
+ receipt?: Receipt; // the signed record — present on APPROVE and chained denials
97
+ denialEventId?: string; // present for unchained denials (see below)
98
+ raw: unknown; // the full server response, for fields the SDK doesn't model
99
+ }
100
+ ```
101
+
102
+ **A DENY is not an error** — it's a normal result with `approved: false`. Only
103
+ transport failures and HTTP 4xx/5xx throw (see [Errors](#errors)).
104
+
105
+ ### Deny reasons
106
+
107
+ | reason | meaning |
108
+ |---|---|
109
+ | `per_tx_cap_exceeded` | amount is over the agent's per-transaction cap |
110
+ | `window_cap_exceeded` | this would push the agent over its rolling window budget |
111
+ | `payee_not_allowed` | `to` isn't on the policy's payee allowlist |
112
+ | `network_not_allowed` / `asset_not_allowed` | rail not permitted by policy |
113
+ | `agent_suspended` | the agent is registered but suspended |
114
+ | `agent_unknown` | no such agent in your org |
115
+ | `wallet_mismatch` | the paying wallet doesn't match the agent's bound wallet |
116
+ | `stale_authorization` | the payment authorization window is expired or not yet valid |
117
+ | `replay` | this nonce was already used |
118
+ | `no_policy` | the agent has no policy assigned |
119
+ | `malformed` | the payment couldn't be parsed/evaluated |
120
+
121
+ > **Chained vs. unchained denials.** Most decisions (all APPROVEs and policy
122
+ > denials) are written to the hash chain and carry a `receipt`. Three denial
123
+ > classes can't be chained — `agent_unknown`, `malformed`, and `replay` — and
124
+ > instead come back with a `denialEventId` (recorded in a separate audit table).
125
+
126
+ ---
127
+
128
+ ## `verify()` — trust, don't ask
129
+
130
+ The ledger is a SHA-256 hash chain: each record commits to the one before it and
131
+ is Ed25519-signed by your org's Quartermaster key. `verify()` confirms that chain
132
+ is intact.
133
+
134
+ ```ts
135
+ // Zero-config: ask the server to check its own chain.
136
+ const r = await qm.verify();
137
+ // → { valid: true, length: 42, problems: [], anchored: false }
138
+ ```
139
+
140
+ But the strong version doesn't take the server's word for it. Give `verify()`
141
+ your org's **pinned public key** and the SDK pulls the raw ledger rows and
142
+ **re-derives the entire chain locally** — recomputing every hash and checking
143
+ every signature against your key:
144
+
145
+ ```ts
146
+ const r = await qm.verify({ publicKey: process.env.QM_ORG_PUBKEY! });
147
+ if (!r.valid) console.error('ledger integrity FAILED', r.problems);
148
+ ```
149
+
150
+ This is the guarantee that matters: a database-level attacker who rewrote the
151
+ ledger *and* re-signed it with a fresh key would pass a server-side check, but
152
+ can't forge signatures under a key you pinned out-of-band. You can also confirm a
153
+ specific receipt is part of the verified chain:
154
+
155
+ ```ts
156
+ const r = await qm.verify({ publicKey, hash: check.receipt!.hash });
157
+ // r.entryFound === true
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Production setup
163
+
164
+ The sandbox ships public demo agents so the quickstart just works. In production
165
+ you point at your deployment and bring your **own** agents — each with its own
166
+ Ed25519 signing key (generate one per agent; keep it in your secrets manager):
167
+
168
+ ```ts
169
+ const qm = new Quartermaster({
170
+ apiKey: process.env.QM_API_KEY!,
171
+ baseUrl: 'https://quartermaster.your-company.com',
172
+ agents: {
173
+ 'research-bot': { id: 'agt_research', signingKey: process.env.RESEARCH_KEY!, wallet: '0xResearchWallet' },
174
+ 'procurement-bot': { id: 'agt_procurement', signingKey: process.env.PROCUREMENT_KEY!, wallet: '0xProcurementWallet' },
175
+ },
176
+ agent: 'research-bot', // the default for qm.check() when no agent is named
177
+ });
178
+
179
+ await qm.check({ agent: 'procurement-bot', to: '0xVendor', amount: usdc(120) });
180
+ ```
181
+
182
+ The call site never changes — `check({ agent: 'research-bot', … })` is the same
183
+ line; it's just backed by your real key now.
184
+
185
+ For a process that drives one agent per worker, grab a bound handle:
186
+
187
+ ```ts
188
+ const research = qm.agent('research-bot');
189
+ await research.check({ to: 'openai', amount: usdc(5) });
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Amounts
195
+
196
+ Amounts are **strings in atomic units**, end to end — Quartermaster never uses
197
+ floats (money and rounding don't mix). `usdc()` is the one sugar:
198
+
199
+ ```ts
200
+ usdc(40) // → '40000000' (6 decimals)
201
+ usdc('1.50') // → '1500000'
202
+ usdc('0.000001')// → '1'
203
+ usdc('1.2345678') // throws — more precision than USDC has
204
+ ```
205
+
206
+ For other assets, pass atomic units directly as a string.
207
+
208
+ ---
209
+
210
+ ## Reading the ledger
211
+
212
+ ```ts
213
+ const { items, nextCursor } = await qm.ledger({ limit: 50 });
214
+ for (const receipt of items) console.log(receipt.seq, receipt.decision, receipt.amount);
215
+
216
+ // Paginate with the cursor:
217
+ if (nextCursor) await qm.ledger({ limit: 50, after: nextCursor });
218
+ ```
219
+
220
+ Read-only and scoped to your org.
221
+
222
+ ---
223
+
224
+ ## What `check()` does and doesn't do
225
+
226
+ - **It does:** evaluate the payment against the agent's policy, sign an
227
+ attestation with your org's Quartermaster key, and append it to the ledger.
228
+ The receipt travels inside standard x402 traffic under the
229
+ `com.quartermaster.attestation` extension key.
230
+ - **It does not:** produce a settleable on-chain payment. `check()` uses a
231
+ placeholder for the EIP-3009/EIP-712 authorization signature, because
232
+ Quartermaster authorizes and records — it doesn't move funds. When you settle
233
+ through the gateway, your wallet supplies the real signed authorization; the
234
+ matching receipt is what lets settlement proceed.
235
+
236
+ ---
237
+
238
+ ## Configuration
239
+
240
+ ```ts
241
+ new Quartermaster({
242
+ apiKey: string, // required — tenant credential
243
+ baseUrl?: string, // default: the hosted sandbox
244
+ agents?: Record<string, AgentConfig>, // production agents (name → identity)
245
+ agent?: string, // default agent name for check()
246
+ fetch?: typeof fetch, // inject a custom transport (tests, proxies)
247
+ timeoutMs?: number, // per-request timeout, default 10_000
248
+ });
249
+ ```
250
+
251
+ `AgentConfig = { id: string; signingKey: string; wallet: string }` — the
252
+ registered agent id, its Ed25519 private key (hex), and its EIP-3009 `from`
253
+ wallet.
254
+
255
+ ---
256
+
257
+ ## Errors
258
+
259
+ ```ts
260
+ import { QuartermasterError } from '@quartermaster-sh/sdk';
261
+
262
+ try {
263
+ await qm.check({ agent: 'research-bot', to: 'openai', amount: usdc(5) });
264
+ } catch (err) {
265
+ if (err instanceof QuartermasterError) {
266
+ console.error(err.status, err.body); // e.g. 401 { error: 'invalid_api_key' }
267
+ }
268
+ }
269
+ ```
270
+
271
+ `QuartermasterError` carries `status` (HTTP status, or `0` for a network error)
272
+ and the parsed `body`. Again: a policy **DENY does not throw** — check
273
+ `result.approved`.
274
+
275
+ ---
276
+
277
+ ## API reference
278
+
279
+ | | |
280
+ |---|---|
281
+ | `new Quartermaster(config)` | Create a client. |
282
+ | `qm.check(input)` | Check + record a payment. Returns `CheckResult`. |
283
+ | `qm.agent(name \| config)` | A handle bound to one agent, with `.check()` and `.id`. |
284
+ | `qm.checkAs(config, input)` | `check()` against an inline signing identity. |
285
+ | `qm.verify(opts?)` | Verify the ledger chain (locally with `publicKey`, else server-side). |
286
+ | `qm.ledger(opts?)` | Read a page of the ledger. |
287
+ | `usdc(value)` | Decimal → 6-decimal atomic-unit string. |
288
+ | `randomNonce()` | A fresh 32-byte hex nonce. |
289
+ | `verifyChainLocally(pubKey, rows)` | The pure local chain verifier, if you have rows already. |
290
+ | `BASE_MAINNET`, `USDC_BASE` | Handy constants. |
@@ -0,0 +1,9 @@
1
+ /** Base mainnet, CAIP-2. */
2
+ export declare const BASE_MAINNET = "eip155:8453";
3
+ /** USDC on Base (6 decimals). */
4
+ export declare const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
5
+ /** USDC decimal amount → 6-decimal atomic units string. `usdc(40)` → "40000000". */
6
+ export declare function usdc(value: string | number): string;
7
+ /** A fresh EIP-3009 nonce: 32 random bytes, lowercase hex, no 0x prefix. */
8
+ export declare function randomNonce(): string;
9
+ //# sourceMappingURL=amounts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amounts.d.ts","sourceRoot":"","sources":["../src/amounts.ts"],"names":[],"mappings":"AAKA,4BAA4B;AAC5B,eAAO,MAAM,YAAY,gBAAgB,CAAC;AAC1C,iCAAiC;AACjC,eAAO,MAAM,SAAS,+CAA+C,CAAC;AAsCtE,oFAAoF;AACpF,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEnD;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,MAAM,CAIpC"}
@@ -0,0 +1,48 @@
1
+ // Amount + nonce helpers and network constants. Amounts are strings in atomic
2
+ // units end to end (Quartermaster rule 6 — never float, BigInt for math). The
3
+ // only sugar the SDK offers is usdc(), and it returns a string.
4
+ import { bytesToHex } from '@noble/hashes/utils.js';
5
+ /** Base mainnet, CAIP-2. */
6
+ export const BASE_MAINNET = 'eip155:8453';
7
+ /** USDC on Base (6 decimals). */
8
+ export const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
9
+ const USDC_DECIMALS = 6;
10
+ // Convert a decimal amount (e.g. "40", 5, "1.50") into atomic units as a
11
+ // digits-only string. Integer/string math throughout: a value is never parsed
12
+ // into a float, so nothing rounds. Rejects negatives, scientific notation, and
13
+ // more fractional digits than the asset supports.
14
+ function toAtomic(value, decimals) {
15
+ let text;
16
+ if (typeof value === 'number') {
17
+ if (!Number.isFinite(value)) {
18
+ throw new TypeError(`amount must be a finite number, got ${value}`);
19
+ }
20
+ text = String(value);
21
+ if (/[eE]/.test(text)) {
22
+ throw new TypeError(`amount ${value} is in scientific notation and cannot be converted precisely — pass it as a string`);
23
+ }
24
+ }
25
+ else {
26
+ text = value.trim();
27
+ }
28
+ if (!/^\d+(\.\d+)?$/.test(text)) {
29
+ throw new TypeError(`amount "${value}" must be a non-negative decimal string (no sign, no exponent)`);
30
+ }
31
+ const [whole, fraction = ''] = text.split('.');
32
+ if (fraction.length > decimals) {
33
+ throw new TypeError(`amount "${value}" has ${fraction.length} fractional digits; the asset supports at most ${decimals}`);
34
+ }
35
+ const atomic = `${whole}${fraction.padEnd(decimals, '0')}`.replace(/^0+(?=\d)/, '');
36
+ return atomic;
37
+ }
38
+ /** USDC decimal amount → 6-decimal atomic units string. `usdc(40)` → "40000000". */
39
+ export function usdc(value) {
40
+ return toAtomic(value, USDC_DECIMALS);
41
+ }
42
+ /** A fresh EIP-3009 nonce: 32 random bytes, lowercase hex, no 0x prefix. */
43
+ export function randomNonce() {
44
+ const bytes = new Uint8Array(32);
45
+ crypto.getRandomValues(bytes);
46
+ return bytesToHex(bytes);
47
+ }
48
+ //# sourceMappingURL=amounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amounts.js","sourceRoot":"","sources":["../src/amounts.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,gEAAgE;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,4BAA4B;AAC5B,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC;AAC1C,iCAAiC;AACjC,MAAM,CAAC,MAAM,SAAS,GAAG,4CAA4C,CAAC;AAEtE,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,yEAAyE;AACzE,8EAA8E;AAC9E,+EAA+E;AAC/E,kDAAkD;AAClD,SAAS,QAAQ,CAAC,KAAsB,EAAE,QAAgB;IACxD,IAAI,IAAY,CAAC;IACjB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,SAAS,CAAC,uCAAuC,KAAK,EAAE,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,SAAS,CACjB,UAAU,KAAK,oFAAoF,CACpG,CAAC;QACJ,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CAAC,WAAW,KAAK,gEAAgE,CAAC,CAAC;IACxG,CAAC;IAED,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,SAAS,CACjB,WAAW,KAAK,SAAS,QAAQ,CAAC,MAAM,kDAAkD,QAAQ,EAAE,CACrG,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACpF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,IAAI,CAAC,KAAsB;IACzC,OAAO,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;AACxC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function canonicalJson(value: unknown): string;
2
+ //# sourceMappingURL=canonical-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-json.d.ts","sourceRoot":"","sources":["../src/canonical-json.ts"],"names":[],"mappings":"AASA,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAsCpD"}
@@ -0,0 +1,47 @@
1
+ // Canonical JSON serialization: sorted keys, no whitespace. This is a
2
+ // BYTE-IDENTICAL port of the server's src/ledger/canonical-json.ts. The agent
3
+ // request signature is Ed25519 over canonicalJson({ agentId, payload,
4
+ // requirements }); if this diverges from the server by a single byte, every
5
+ // signature the SDK produces is rejected 401. It is also the hash preimage the
6
+ // local chain verifier (verify.ts) recomputes, so it must match the exact
7
+ // serialization the ledger hashed. Do not "improve" this without changing the
8
+ // server in lockstep — the byte-equality unit test guards it.
9
+ export function canonicalJson(value) {
10
+ if (value === null)
11
+ return 'null';
12
+ switch (typeof value) {
13
+ case 'string':
14
+ return JSON.stringify(value);
15
+ case 'boolean':
16
+ return value ? 'true' : 'false';
17
+ case 'number':
18
+ if (!Number.isSafeInteger(value)) {
19
+ throw new TypeError(`canonicalJson: only safe integers are serializable, got ${value} — represent amounts and large values as strings`);
20
+ }
21
+ return String(value);
22
+ case 'bigint':
23
+ throw new TypeError('canonicalJson: convert BigInt to a string before serializing');
24
+ case 'undefined':
25
+ throw new TypeError('canonicalJson: undefined is not serializable — use null');
26
+ default:
27
+ break;
28
+ }
29
+ if (Array.isArray(value)) {
30
+ return `[${value.map((v) => canonicalJson(v)).join(',')}]`;
31
+ }
32
+ if (typeof value === 'object') {
33
+ const record = value;
34
+ const keys = Object.keys(record).sort();
35
+ const parts = [];
36
+ for (const key of keys) {
37
+ const v = record[key];
38
+ if (v === undefined) {
39
+ throw new TypeError(`canonicalJson: key "${key}" is undefined — use null or omit the key`);
40
+ }
41
+ parts.push(`${JSON.stringify(key)}:${canonicalJson(v)}`);
42
+ }
43
+ return `{${parts.join(',')}}`;
44
+ }
45
+ throw new TypeError(`canonicalJson: cannot serialize a ${typeof value}`);
46
+ }
47
+ //# sourceMappingURL=canonical-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-json.js","sourceRoot":"","sources":["../src/canonical-json.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,8EAA8E;AAC9E,sEAAsE;AACtE,4EAA4E;AAC5E,+EAA+E;AAC/E,0EAA0E;AAC1E,8EAA8E;AAC9E,8DAA8D;AAE9D,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC/B,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAClC,KAAK,QAAQ;YACX,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,SAAS,CACjB,2DAA2D,KAAK,kDAAkD,CACnH,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,KAAK,QAAQ;YACX,MAAM,IAAI,SAAS,CAAC,8DAA8D,CAAC,CAAC;QACtF,KAAK,WAAW;YACd,MAAM,IAAI,SAAS,CAAC,yDAAyD,CAAC,CAAC;QACjF;YACE,MAAM;IACV,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC7D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;gBACpB,MAAM,IAAI,SAAS,CAAC,uBAAuB,GAAG,2CAA2C,CAAC,CAAC;YAC7F,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,CAAC;IACD,MAAM,IAAI,SAAS,CAAC,qCAAqC,OAAO,KAAK,EAAE,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { AgentConfig, CheckInput, CheckResult, LedgerOptions, LedgerPage, QuartermasterConfig, VerifyOptions, VerifyResult } from './types.js';
2
+ export declare class Quartermaster {
3
+ readonly baseUrl: string;
4
+ private readonly apiKey;
5
+ private readonly fetchImpl;
6
+ private readonly timeoutMs;
7
+ private readonly agents;
8
+ private readonly defaultAgentName;
9
+ constructor(config: QuartermasterConfig);
10
+ /** Resolve a friendly agent name to its signing identity, or throw clearly. */
11
+ private resolveAgent;
12
+ private request;
13
+ /**
14
+ * Check a payment against the agent's policy before it happens. Signs the
15
+ * request with the agent's Ed25519 key and records the decision on the
16
+ * append-only ledger. Returns a typed decision; a DENY is not an error.
17
+ */
18
+ check(input: CheckInput): Promise<CheckResult>;
19
+ /**
20
+ * The single build → sign → attest path, against an explicit signing
21
+ * identity. `qm.check` resolves a name to a config and calls this; an Agent
22
+ * handle calls it directly. Advanced callers may pass an inline AgentConfig.
23
+ */
24
+ checkAs(agent: AgentConfig, input: Omit<CheckInput, 'agent'>): Promise<CheckResult>;
25
+ /** A handle bound to one agent — for processes that drive several agents. */
26
+ agent(nameOrConfig: string | AgentConfig): Agent;
27
+ /** One page of the org-scoped, read-only ledger. */
28
+ ledger(opts?: LedgerOptions): Promise<LedgerPage>;
29
+ private fetchAllRows;
30
+ /**
31
+ * Confirm the ledger's hash-chain integrity. With `publicKey`, re-derives the
32
+ * entire chain LOCALLY from raw rows and checks every signature against that
33
+ * pinned key — the guarantee that does not trust our API. Without it, asks the
34
+ * server's /v1/ledger/verify.
35
+ */
36
+ verify(opts?: VerifyOptions): Promise<VerifyResult>;
37
+ }
38
+ /** A per-agent handle returned by `qm.agent(...)`. */
39
+ export declare class Agent {
40
+ private readonly client;
41
+ private readonly config;
42
+ constructor(client: Quartermaster, config: AgentConfig);
43
+ get id(): string;
44
+ /** Same as client.check(), pinned to this agent's signing identity. */
45
+ check(input: Omit<CheckInput, 'agent'>): Promise<CheckResult>;
46
+ }
47
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,WAAW,EAEX,aAAa,EACb,UAAU,EACV,mBAAmB,EAEnB,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAcpB,qBAAa,aAAa;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;gBAE1C,MAAM,EAAE,mBAAmB;IAqBvC,+EAA+E;IAC/E,OAAO,CAAC,YAAY;YAiBN,OAAO;IAqCrB;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAI9C;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;IAsBzF,6EAA6E;IAC7E,KAAK,CAAC,YAAY,EAAE,MAAM,GAAG,WAAW,GAAG,KAAK;IAKhD,oDAAoD;IAC9C,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,UAAU,CAAC;YAO7C,YAAY;IAY1B;;;;;OAKG;IACG,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;CA6B9D;AAED,sDAAsD;AACtD,qBAAa,KAAK;IAEd,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,WAAW;IAGtC,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,uEAAuE;IACvE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC;CAG9D"}
package/dist/client.js ADDED
@@ -0,0 +1,186 @@
1
+ // The Quartermaster client. Two public verbs the developer cares about:
2
+ // check() — sign + POST /v1/attest, return a clean typed decision.
3
+ // verify() — confirm the ledger's hash chain (locally, if given a pinned key).
4
+ // Everything crypto (Ed25519 request signing, x402 message construction, chain
5
+ // re-derivation) is hidden. A policy DENY is a normal result, not an error;
6
+ // only transport / 4xx-5xx throw QuartermasterError.
7
+ import { signRequestBody } from './sign.js';
8
+ import { buildX402Pair } from './x402.js';
9
+ import { verifyChainLocally } from './verify.js';
10
+ import { QuartermasterError } from './errors.js';
11
+ import { SANDBOX_AGENTS, SANDBOX_BASE_URL, SANDBOX_DEFAULT_AGENT } from './sandbox.js';
12
+ const DEFAULT_TIMEOUT_MS = 10_000;
13
+ const DEFAULT_PAGE_SIZE = 200;
14
+ const MAX_LEDGER_PAGES = 10_000; // generous guard against a broken cursor loop
15
+ export class Quartermaster {
16
+ baseUrl;
17
+ apiKey;
18
+ fetchImpl;
19
+ timeoutMs;
20
+ agents;
21
+ defaultAgentName;
22
+ constructor(config) {
23
+ if (!config.apiKey)
24
+ throw new TypeError('Quartermaster: apiKey is required');
25
+ this.apiKey = config.apiKey;
26
+ this.baseUrl = (config.baseUrl ?? SANDBOX_BASE_URL).replace(/\/+$/, '');
27
+ this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
28
+ const injected = config.fetch ?? globalThis.fetch;
29
+ if (typeof injected !== 'function') {
30
+ throw new TypeError('Quartermaster: no fetch available — pass config.fetch on this runtime');
31
+ }
32
+ // Bind to avoid "Illegal invocation" when using the global fetch.
33
+ this.fetchImpl = injected.bind(globalThis);
34
+ // Bundled sandbox demo agents apply only when pointed at the sandbox; a
35
+ // custom baseUrl gets only the caller's own agents (referencing a demo name
36
+ // there would just 404 as agent_unknown).
37
+ const onSandbox = this.baseUrl === SANDBOX_BASE_URL.replace(/\/+$/, '');
38
+ this.agents = { ...(onSandbox ? SANDBOX_AGENTS : {}), ...(config.agents ?? {}) };
39
+ this.defaultAgentName = config.agent ?? (onSandbox ? SANDBOX_DEFAULT_AGENT : undefined);
40
+ }
41
+ /** Resolve a friendly agent name to its signing identity, or throw clearly. */
42
+ resolveAgent(name) {
43
+ const resolved = name ?? this.defaultAgentName;
44
+ if (resolved === undefined) {
45
+ throw new TypeError('Quartermaster: no agent specified and no default configured — pass check({ agent }) or set config.agent');
46
+ }
47
+ const agent = this.agents[resolved];
48
+ if (agent === undefined) {
49
+ const known = Object.keys(this.agents);
50
+ throw new TypeError(`Quartermaster: unknown agent "${resolved}"${known.length ? ` — known agents: ${known.join(', ')}` : ' — no agents configured'}`);
51
+ }
52
+ return agent;
53
+ }
54
+ async request(opts) {
55
+ const url = new URL(this.baseUrl + opts.path);
56
+ for (const [k, v] of Object.entries(opts.query ?? {}))
57
+ url.searchParams.set(k, v);
58
+ const headers = { authorization: `Bearer ${this.apiKey}` };
59
+ if (opts.body !== undefined)
60
+ headers['content-type'] = 'application/json';
61
+ if (opts.signature !== undefined)
62
+ headers['x-qm-signature'] = opts.signature;
63
+ let res;
64
+ try {
65
+ res = await this.fetchImpl(url.toString(), {
66
+ method: opts.method,
67
+ headers,
68
+ body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
69
+ signal: AbortSignal.timeout(this.timeoutMs),
70
+ });
71
+ }
72
+ catch (err) {
73
+ throw new QuartermasterError(`request to ${opts.method} ${opts.path} failed: ${err instanceof Error ? err.message : String(err)}`, 0, undefined);
74
+ }
75
+ const text = await res.text();
76
+ let json;
77
+ try {
78
+ json = text === '' ? null : JSON.parse(text);
79
+ }
80
+ catch {
81
+ json = text;
82
+ }
83
+ if (!res.ok) {
84
+ throw new QuartermasterError(`${opts.method} ${opts.path} → ${res.status}`, res.status, json);
85
+ }
86
+ return json;
87
+ }
88
+ /**
89
+ * Check a payment against the agent's policy before it happens. Signs the
90
+ * request with the agent's Ed25519 key and records the decision on the
91
+ * append-only ledger. Returns a typed decision; a DENY is not an error.
92
+ */
93
+ check(input) {
94
+ return this.checkAs(this.resolveAgent(input.agent), input);
95
+ }
96
+ /**
97
+ * The single build → sign → attest path, against an explicit signing
98
+ * identity. `qm.check` resolves a name to a config and calls this; an Agent
99
+ * handle calls it directly. Advanced callers may pass an inline AgentConfig.
100
+ */
101
+ async checkAs(agent, input) {
102
+ const { payload, requirements } = buildX402Pair({ ...input, agent: agent.id }, agent);
103
+ const body = { agentId: agent.id, payload, requirements };
104
+ const signature = signRequestBody(body, agent.signingKey);
105
+ const json = await this.request({ method: 'POST', path: '/v1/attest', body, signature });
106
+ return {
107
+ approved: json.decision === 'APPROVE',
108
+ decision: json.decision,
109
+ reasons: (json.denyReasons ?? []),
110
+ receipt: json.attestation,
111
+ denialEventId: json.denialEventId,
112
+ raw: json,
113
+ };
114
+ }
115
+ /** A handle bound to one agent — for processes that drive several agents. */
116
+ agent(nameOrConfig) {
117
+ const config = typeof nameOrConfig === 'string' ? this.resolveAgent(nameOrConfig) : nameOrConfig;
118
+ return new Agent(this, config);
119
+ }
120
+ /** One page of the org-scoped, read-only ledger. */
121
+ async ledger(opts = {}) {
122
+ const query = {};
123
+ if (opts.limit !== undefined)
124
+ query.limit = String(opts.limit);
125
+ if (opts.after !== undefined)
126
+ query.after = opts.after;
127
+ return this.request({ method: 'GET', path: '/v1/ledger', query });
128
+ }
129
+ async fetchAllRows(pageSize) {
130
+ const rows = [];
131
+ let after;
132
+ for (let page = 0; page < MAX_LEDGER_PAGES; page++) {
133
+ const { items, nextCursor } = await this.ledger({ limit: pageSize, after });
134
+ rows.push(...items);
135
+ if (nextCursor === null || items.length === 0)
136
+ return rows;
137
+ after = nextCursor;
138
+ }
139
+ throw new QuartermasterError('ledger pagination did not terminate', 0, undefined);
140
+ }
141
+ /**
142
+ * Confirm the ledger's hash-chain integrity. With `publicKey`, re-derives the
143
+ * entire chain LOCALLY from raw rows and checks every signature against that
144
+ * pinned key — the guarantee that does not trust our API. Without it, asks the
145
+ * server's /v1/ledger/verify.
146
+ */
147
+ async verify(opts = {}) {
148
+ if (opts.publicKey !== undefined) {
149
+ const rows = await this.fetchAllRows(opts.pageSize ?? DEFAULT_PAGE_SIZE);
150
+ const result = verifyChainLocally(opts.publicKey, rows);
151
+ if (opts.hash !== undefined) {
152
+ result.entryFound = rows.some((r) => r.hash === opts.hash);
153
+ }
154
+ return result;
155
+ }
156
+ const server = await this.request({ method: 'GET', path: '/v1/ledger/verify' });
157
+ const result = {
158
+ valid: server.valid,
159
+ length: server.length,
160
+ problems: server.problems,
161
+ anchored: server.anchored,
162
+ };
163
+ if (opts.hash !== undefined) {
164
+ const rows = await this.fetchAllRows(opts.pageSize ?? DEFAULT_PAGE_SIZE);
165
+ result.entryFound = rows.some((r) => r.hash === opts.hash);
166
+ }
167
+ return result;
168
+ }
169
+ }
170
+ /** A per-agent handle returned by `qm.agent(...)`. */
171
+ export class Agent {
172
+ client;
173
+ config;
174
+ constructor(client, config) {
175
+ this.client = client;
176
+ this.config = config;
177
+ }
178
+ get id() {
179
+ return this.config.id;
180
+ }
181
+ /** Same as client.check(), pinned to this agent's signing identity. */
182
+ check(input) {
183
+ return this.client.checkAs(this.config, input);
184
+ }
185
+ }
186
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare class QuartermasterError extends Error {
2
+ /** HTTP status, or 0 when the request never got a response (network error). */
3
+ readonly status: number;
4
+ /** Parsed response body when available (often { error, detail }). */
5
+ readonly body: unknown;
6
+ constructor(message: string, status: number, body: unknown);
7
+ }
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAKA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D"}
package/dist/errors.js ADDED
@@ -0,0 +1,18 @@
1
+ // The only error type the SDK throws for HTTP/transport problems. A policy
2
+ // DENY is NOT an error — it is a normal CheckResult with approved:false.
3
+ // QuartermasterError is reserved for the things that genuinely went wrong:
4
+ // a bad/missing API key, a malformed request the server rejected (4xx), an
5
+ // upstream/server fault (5xx), or the network being unreachable.
6
+ export class QuartermasterError extends Error {
7
+ /** HTTP status, or 0 when the request never got a response (network error). */
8
+ status;
9
+ /** Parsed response body when available (often { error, detail }). */
10
+ body;
11
+ constructor(message, status, body) {
12
+ super(message);
13
+ this.name = 'QuartermasterError';
14
+ this.status = status;
15
+ this.body = body;
16
+ }
17
+ }
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,iEAAiE;AACjE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,+EAA+E;IACtE,MAAM,CAAS;IACxB,qEAAqE;IAC5D,IAAI,CAAU;IAEvB,YAAY,OAAe,EAAE,MAAc,EAAE,IAAa;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ export { Quartermaster, Agent } from './client.js';
2
+ export { QuartermasterError } from './errors.js';
3
+ export { usdc, randomNonce, BASE_MAINNET, USDC_BASE } from './amounts.js';
4
+ export { verifyChainLocally, GENESIS_PREV_HASH } from './verify.js';
5
+ export { canonicalJson } from './canonical-json.js';
6
+ export { SANDBOX_BASE_URL, SANDBOX_AGENTS, SANDBOX_DEFAULT_AGENT, demoAgentPublicKey, } from './sandbox.js';
7
+ export type { AgentConfig, QuartermasterConfig, CheckInput, CheckResult, Decision, DenyReason, Receipt, VerifyOptions, VerifyResult, ChainProblem, LedgerOptions, LedgerPage, } from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,UAAU,EACV,OAAO,EACP,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // @quartermaster-sh/sdk — check before your agent pays.
2
+ export { Quartermaster, Agent } from './client.js';
3
+ export { QuartermasterError } from './errors.js';
4
+ export { usdc, randomNonce, BASE_MAINNET, USDC_BASE } from './amounts.js';
5
+ export { verifyChainLocally, GENESIS_PREV_HASH } from './verify.js';
6
+ export { canonicalJson } from './canonical-json.js';
7
+ export { SANDBOX_BASE_URL, SANDBOX_AGENTS, SANDBOX_DEFAULT_AGENT, demoAgentPublicKey, } from './sandbox.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { AgentConfig } from './types.js';
2
+ export declare const SANDBOX_BASE_URL = "https://quartermaster-sandbox.vercel.app";
3
+ /** Ed25519 public key (hex) for a demo agent — for the sandbox seed, not runtime. */
4
+ export declare function demoAgentPublicKey(agent: AgentConfig): string;
5
+ export declare const SANDBOX_AGENTS: Record<string, AgentConfig>;
6
+ /** The agent qm.check() uses by default on the sandbox when none is named. */
7
+ export declare const SANDBOX_DEFAULT_AGENT = "research-bot";
8
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C,eAAO,MAAM,gBAAgB,6CAA6C,CAAC;AAW3E,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE7D;AAMD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAGtD,CAAC;AAEF,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,iBAAiB,CAAC"}
@@ -0,0 +1,41 @@
1
+ // The hosted sandbox: default base URL + the bundled public demo agents that
2
+ // make the one-credential quickstart work. These demo identities are
3
+ // DELIBERATELY PUBLIC — on the sandbox they can mint APPROVE attestations and
4
+ // consume window budget, but they can never settle (no real EIP-712 key), so
5
+ // shipping their signing keys is safe. In production you pass your own `agents`
6
+ // map and none of this is used.
7
+ //
8
+ // The keys/ids/wallets below are derived deterministically from a fixed
9
+ // namespace. When the sandbox org is provisioned, seed its agents to EXACTLY
10
+ // these ids, public keys, and wallets (same derivation) so the quickstart runs
11
+ // as written. Until then, `baseUrl` is configurable and production users are
12
+ // unaffected.
13
+ import * as ed from '@noble/ed25519';
14
+ import { sha256, sha512 } from '@noble/hashes/sha2.js';
15
+ import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils.js';
16
+ ed.hashes.sha512 = sha512;
17
+ // The hosted sandbox deployment. The SDK defaults here so the quickstart
18
+ // needs no `baseUrl`.
19
+ export const SANDBOX_BASE_URL = 'https://quartermaster-sandbox.vercel.app';
20
+ const NS = 'quartermaster:sandbox:v1:';
21
+ /** Derivation the sandbox seed must reuse verbatim to match the bundled agents. */
22
+ function demoAgent(name) {
23
+ const signingKey = bytesToHex(sha256(utf8ToBytes(`${NS}agent:${name}`)));
24
+ const wallet = `0x${bytesToHex(sha256(utf8ToBytes(`${NS}wallet:${name}`))).slice(0, 40)}`;
25
+ return { id: name, signingKey, wallet };
26
+ }
27
+ /** Ed25519 public key (hex) for a demo agent — for the sandbox seed, not runtime. */
28
+ export function demoAgentPublicKey(agent) {
29
+ return bytesToHex(ed.getPublicKey(hexToBytes(agent.signingKey)));
30
+ }
31
+ // Two demo spenders with different caps so both APPROVE and DENY are legible
32
+ // from the README. The sandbox policy for `research-bot` caps at $5 (so the
33
+ // usdc(500) example denies per_tx_cap_exceeded) and allowlists friendly payees
34
+ // like 'openai' (so usdc(5) → 'openai' approves; 'sketchy-vendor' denies).
35
+ export const SANDBOX_AGENTS = {
36
+ 'research-bot': demoAgent('research-bot'),
37
+ 'procurement-bot': demoAgent('procurement-bot'),
38
+ };
39
+ /** The agent qm.check() uses by default on the sandbox when none is named. */
40
+ export const SANDBOX_DEFAULT_AGENT = 'research-bot';
41
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,qEAAqE;AACrE,8EAA8E;AAC9E,6EAA6E;AAC7E,gFAAgF;AAChF,gCAAgC;AAChC,EAAE;AACF,wEAAwE;AACxE,6EAA6E;AAC7E,+EAA+E;AAC/E,6EAA6E;AAC7E,cAAc;AACd,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG7E,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;AAE1B,yEAAyE;AACzE,sBAAsB;AACtB,MAAM,CAAC,MAAM,gBAAgB,GAAG,0CAA0C,CAAC;AAE3E,MAAM,EAAE,GAAG,2BAA2B,CAAC;AAEvC,mFAAmF;AACnF,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,KAAK,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC1F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAAC,KAAkB;IACnD,OAAO,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,6EAA6E;AAC7E,4EAA4E;AAC5E,+EAA+E;AAC/E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,cAAc,GAAgC;IACzD,cAAc,EAAE,SAAS,CAAC,cAAc,CAAC;IACzC,iBAAiB,EAAE,SAAS,CAAC,iBAAiB,CAAC;CAChD,CAAC;AAEF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,cAAc,CAAC"}
package/dist/sign.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** Ed25519 signature (128-char hex) over the canonical JSON of `body`. */
2
+ export declare function signRequestBody(body: unknown, signingKeyHex: string): string;
3
+ //# sourceMappingURL=sign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAYA,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAE5E"}
package/dist/sign.js ADDED
@@ -0,0 +1,15 @@
1
+ // Agent request signing. The server verifies ed25519(canonicalJson({ agentId,
2
+ // payload, requirements })) against the agent's registered public key (see the
3
+ // server's src/http/attest-common.ts verifyAgentSignature). We sign the exact
4
+ // same preimage here so the developer never touches crypto.
5
+ import * as ed from '@noble/ed25519';
6
+ import { sha512 } from '@noble/hashes/sha2.js';
7
+ import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils.js';
8
+ import { canonicalJson } from './canonical-json.js';
9
+ // @noble/ed25519 needs a sha512 implementation wired in (same as the server).
10
+ ed.hashes.sha512 = sha512;
11
+ /** Ed25519 signature (128-char hex) over the canonical JSON of `body`. */
12
+ export function signRequestBody(body, signingKeyHex) {
13
+ return bytesToHex(ed.sign(utf8ToBytes(canonicalJson(body)), hexToBytes(signingKeyHex)));
14
+ }
15
+ //# sourceMappingURL=sign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.js","sourceRoot":"","sources":["../src/sign.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,+EAA+E;AAC/E,8EAA8E;AAC9E,4DAA4D;AAC5D,OAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,8EAA8E;AAC9E,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;AAE1B,0EAA0E;AAC1E,MAAM,UAAU,eAAe,CAAC,IAAa,EAAE,aAAqB;IAClE,OAAO,UAAU,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,114 @@
1
+ export interface AgentConfig {
2
+ /** The registered agent id (x-qm-agent-id / body.agentId). */
3
+ id: string;
4
+ /** Ed25519 private key (32-byte hex). Used to sign every request; never sent. */
5
+ signingKey: string;
6
+ /** The agent's EIP-3009 `from` wallet. Also satisfies on-chain wallet binding. */
7
+ wallet: string;
8
+ }
9
+ export interface QuartermasterConfig {
10
+ /** Tenant API key → Authorization: Bearer. Identifies the org, not the agent. */
11
+ apiKey: string;
12
+ /** Base URL of the Quartermaster deployment. Defaults to the hosted sandbox. */
13
+ baseUrl?: string;
14
+ /** Production: friendly name → signing identity. Merged over the sandbox demo set. */
15
+ agents?: Record<string, AgentConfig>;
16
+ /** Default agent name used when check() is called without one. */
17
+ agent?: string;
18
+ /** Injectable fetch (tests / custom transport). Defaults to globalThis.fetch. */
19
+ fetch?: typeof fetch;
20
+ /** Per-request timeout in milliseconds (default 10_000). */
21
+ timeoutMs?: number;
22
+ }
23
+ export interface CheckInput {
24
+ /** Payee (payTo). An opaque string — an address, or a sandbox payee name. */
25
+ to: string;
26
+ /** Amount in atomic units (6-decimal for USDC). Use usdc() — never a float. */
27
+ amount: string;
28
+ /** Agent name; defaults to the client's default agent / the sandbox demo agent. */
29
+ agent?: string;
30
+ /** CAIP-2 network. Default 'eip155:8453' (Base mainnet). */
31
+ network?: string;
32
+ /** Asset contract / code. Default USDC on Base. */
33
+ asset?: string;
34
+ /** EIP-3009 nonce (32-byte hex, 0x optional). Default: fresh random. */
35
+ nonce?: string;
36
+ /** Authorization validity window in seconds from now (default 600). */
37
+ validForSeconds?: number;
38
+ }
39
+ export type DenyReason = 'agent_suspended' | 'agent_unknown' | 'wallet_mismatch' | 'per_tx_cap_exceeded' | 'window_cap_exceeded' | 'network_not_allowed' | 'asset_not_allowed' | 'payee_not_allowed' | 'replay' | 'stale_authorization' | 'no_policy' | 'malformed';
40
+ export type Decision = 'APPROVE' | 'DENY';
41
+ export interface Receipt {
42
+ id: string;
43
+ organizationId: string;
44
+ seq: string;
45
+ agentId: string;
46
+ policyId: string | null;
47
+ decision: Decision;
48
+ denyReasons: string[];
49
+ network: string;
50
+ asset: string;
51
+ payTo: string;
52
+ amount: string;
53
+ eip3009Nonce: string;
54
+ payloadSha256: string;
55
+ requirements: unknown;
56
+ policySnapshot: unknown;
57
+ prevHash: string;
58
+ hash: string;
59
+ signature: string;
60
+ createdAt: string;
61
+ }
62
+ export interface CheckResult {
63
+ /** true exactly when the policy decision was APPROVE. */
64
+ approved: boolean;
65
+ decision: Decision;
66
+ /** Deny reasons ([] when approved). Strings from the engine vocabulary. */
67
+ reasons: DenyReason[];
68
+ /**
69
+ * The signed receipt. Present whenever the request was recorded on the hash
70
+ * chain — always on APPROVE, and on chainable denials. Absent only for
71
+ * unchained denials (agent_unknown / malformed / replay), which carry
72
+ * denialEventId instead.
73
+ */
74
+ receipt?: Receipt;
75
+ /** Present for unchained denials recorded as DenialEvents. */
76
+ denialEventId?: string;
77
+ /** The raw server JSON, for callers that need fields the SDK does not model. */
78
+ raw: unknown;
79
+ }
80
+ export interface VerifyOptions {
81
+ /**
82
+ * The org's Ed25519 signing public key (hex). When provided, verify()
83
+ * re-derives the whole chain LOCALLY from raw ledger rows and checks every
84
+ * signature against this pinned key — the strong guarantee that does not
85
+ * trust the server's own verdict.
86
+ */
87
+ publicKey?: string;
88
+ /** When set, also assert this attestation hash appears in the verified chain. */
89
+ hash?: string;
90
+ /** Max rows to pull per ledger page during local recompute (default 200). */
91
+ pageSize?: number;
92
+ }
93
+ export interface ChainProblem {
94
+ seq: string;
95
+ problem: string;
96
+ }
97
+ export interface VerifyResult {
98
+ valid: boolean;
99
+ length: number;
100
+ problems: ChainProblem[];
101
+ /** Only from the server path: whether the org's key is pinned server-side. */
102
+ anchored?: boolean;
103
+ /** Only when opts.hash was given: whether that entry is in the verified chain. */
104
+ entryFound?: boolean;
105
+ }
106
+ export interface LedgerOptions {
107
+ limit?: number;
108
+ after?: string;
109
+ }
110
+ export interface LedgerPage {
111
+ items: Receipt[];
112
+ nextCursor: string | null;
113
+ }
114
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ // Public types for @quartermaster-sh/sdk. x402 message shapes follow spec v2
2
+ // §5.1.2 / §5.2.2 and mirror the server's src/engine/types.ts — see the
3
+ // x402-protocol skill before changing them.
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wEAAwE;AACxE,4CAA4C"}
@@ -0,0 +1,8 @@
1
+ import type { Receipt, VerifyResult } from './types.js';
2
+ export declare const GENESIS_PREV_HASH = "GENESIS";
3
+ /**
4
+ * Re-derive and verify a whole org chain locally against a pinned signing key.
5
+ * `rows` must be the complete ledger in ascending seq order, starting at seq 1.
6
+ */
7
+ export declare function verifyChainLocally(signingPubKeyHex: string, rows: readonly Receipt[]): VerifyResult;
8
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAgB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAItE,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAyD3C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CA4CnG"}
package/dist/verify.js ADDED
@@ -0,0 +1,94 @@
1
+ // Local chain re-derivation — the trust demonstration. Given the raw ledger
2
+ // rows and the org's PINNED Ed25519 public key, this recomputes the entire hash
3
+ // chain from row 1 exactly as the server does, so a caller never has to trust
4
+ // the /v1/ledger/verify endpoint's own word. Ports the pure logic from the
5
+ // server's src/ledger/chain.ts + src/ledger/verify.ts, byte-for-byte.
6
+ //
7
+ // Why pinned key: verification against a key the server also stores proves
8
+ // nothing to a database-level attacker who can rewrite the chain, re-sign with
9
+ // a fresh key, and update the stored key in one breath. The pubkey must come
10
+ // from outside the API (the org's published anchor).
11
+ import * as ed from '@noble/ed25519';
12
+ import { sha256, sha512 } from '@noble/hashes/sha2.js';
13
+ import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils.js';
14
+ import { canonicalJson } from './canonical-json.js';
15
+ ed.hashes.sha512 = sha512;
16
+ export const GENESIS_PREV_HASH = 'GENESIS';
17
+ function hashInputFromRow(row) {
18
+ return {
19
+ id: row.id,
20
+ organizationId: row.organizationId,
21
+ seq: row.seq,
22
+ agentId: row.agentId,
23
+ policyId: row.policyId,
24
+ decision: row.decision,
25
+ denyReasons: row.denyReasons,
26
+ network: row.network,
27
+ asset: row.asset,
28
+ payTo: row.payTo,
29
+ amount: row.amount,
30
+ eip3009Nonce: row.eip3009Nonce,
31
+ payloadSha256: row.payloadSha256,
32
+ requirements: row.requirements,
33
+ policySnapshot: row.policySnapshot,
34
+ prevHash: row.prevHash,
35
+ createdAt: row.createdAt,
36
+ };
37
+ }
38
+ function computeAttestationHash(input) {
39
+ return bytesToHex(sha256(utf8ToBytes(canonicalJson(input))));
40
+ }
41
+ function verifyAttestationSignature(hashHex, signatureHex, publicKeyHex) {
42
+ try {
43
+ return ed.verify(hexToBytes(signatureHex), hexToBytes(hashHex), hexToBytes(publicKeyHex));
44
+ }
45
+ catch {
46
+ return false; // malformed hex never verifies
47
+ }
48
+ }
49
+ /**
50
+ * Re-derive and verify a whole org chain locally against a pinned signing key.
51
+ * `rows` must be the complete ledger in ascending seq order, starting at seq 1.
52
+ */
53
+ export function verifyChainLocally(signingPubKeyHex, rows) {
54
+ const problems = [];
55
+ if (rows.length === 0) {
56
+ return { valid: true, length: 0, problems };
57
+ }
58
+ const organizationId = rows[0].organizationId;
59
+ let expectedPrevHash = GENESIS_PREV_HASH;
60
+ rows.forEach((row, i) => {
61
+ const expectedSeq = BigInt(i + 1);
62
+ if (row.organizationId !== organizationId) {
63
+ problems.push({ seq: row.seq, problem: `row belongs to org ${row.organizationId}` });
64
+ }
65
+ if (BigInt(row.seq) !== expectedSeq) {
66
+ problems.push({ seq: row.seq, problem: `sequence gap: expected seq ${expectedSeq}, found ${row.seq}` });
67
+ }
68
+ if (row.prevHash !== expectedPrevHash) {
69
+ problems.push({
70
+ seq: row.seq,
71
+ problem: `broken link: prevHash ${row.prevHash} != prior hash ${expectedPrevHash}`,
72
+ });
73
+ }
74
+ let recomputed = null;
75
+ try {
76
+ recomputed = computeAttestationHash(hashInputFromRow(row));
77
+ }
78
+ catch (err) {
79
+ problems.push({ seq: row.seq, problem: `hash input not canonicalizable: ${String(err)}` });
80
+ }
81
+ if (recomputed !== null && recomputed !== row.hash) {
82
+ problems.push({
83
+ seq: row.seq,
84
+ problem: `hash mismatch: stored ${row.hash}, recomputed ${recomputed} — row content was altered`,
85
+ });
86
+ }
87
+ if (!verifyAttestationSignature(row.hash, row.signature, signingPubKeyHex)) {
88
+ problems.push({ seq: row.seq, problem: 'signature does not verify against org signing key' });
89
+ }
90
+ expectedPrevHash = row.hash;
91
+ });
92
+ return { valid: problems.length === 0, length: rows.length, problems };
93
+ }
94
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
package/dist/x402.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { AgentConfig, CheckInput } from './types.js';
2
+ export interface PaymentRequirements {
3
+ scheme: 'exact';
4
+ network: string;
5
+ amount: string;
6
+ asset: string;
7
+ payTo: string;
8
+ maxTimeoutSeconds: number;
9
+ }
10
+ export interface PaymentPayload {
11
+ x402Version: 2;
12
+ accepted: PaymentRequirements;
13
+ payload: {
14
+ signature: string;
15
+ authorization: {
16
+ from: string;
17
+ to: string;
18
+ value: string;
19
+ validAfter: string;
20
+ validBefore: string;
21
+ nonce: string;
22
+ };
23
+ };
24
+ }
25
+ export interface X402Pair {
26
+ requirements: PaymentRequirements;
27
+ payload: PaymentPayload;
28
+ }
29
+ export declare function buildX402Pair(input: CheckInput, agent: AgentConfig, nowMs?: number): X402Pair;
30
+ //# sourceMappingURL=x402.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../src/x402.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAY1D,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,CAAC,CAAC;IACf,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE;YACb,IAAI,EAAE,MAAM,CAAC;YACb,EAAE,EAAE,MAAM,CAAC;YACX,KAAK,EAAE,MAAM,CAAC;YACd,UAAU,EAAE,MAAM,CAAC;YACnB,WAAW,EAAE,MAAM,CAAC;YACpB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,mBAAmB,CAAC;IAClC,OAAO,EAAE,cAAc,CAAC;CACzB;AAgBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,GAAE,MAAmB,GAAG,QAAQ,CA8BzG"}
package/dist/x402.js ADDED
@@ -0,0 +1,50 @@
1
+ import { BASE_MAINNET, USDC_BASE, randomNonce } from './amounts.js';
2
+ // The EIP-712 authorization signature is verified UPSTREAM at settlement (spec
3
+ // §6.1.2), never by the policy engine. check() produces an attestation, not a
4
+ // settleable on-chain payment, so a placeholder is correct and honest here —
5
+ // documented in the README's "what check() does and does not do".
6
+ const PLACEHOLDER_EIP712_SIGNATURE = `0x${'00'.repeat(65)}`;
7
+ const DEFAULT_MAX_TIMEOUT_SECONDS = 60;
8
+ const DEFAULT_VALID_FOR_SECONDS = 600;
9
+ // Normalize a caller-supplied nonce (or mint one) to the `0x`-prefixed 32-byte
10
+ // hex the authorization carries. The engine/ledger normalize it again to their
11
+ // canonical form, but we validate here so a bad nonce fails in the SDK, loudly,
12
+ // before signing.
13
+ function toAuthorizationNonce(nonce) {
14
+ const hex = (nonce ?? randomNonce()).toLowerCase().replace(/^0x/, '');
15
+ if (!/^[0-9a-f]{64}$/.test(hex)) {
16
+ throw new TypeError(`nonce must be 32-byte hex (0x optional); got "${nonce}"`);
17
+ }
18
+ return `0x${hex}`;
19
+ }
20
+ // `nowMs` is injectable purely so tests are deterministic; production passes the
21
+ // real wall clock (the server evaluates the validity window at receive time).
22
+ export function buildX402Pair(input, agent, nowMs = Date.now()) {
23
+ const nowSeconds = Math.floor(nowMs / 1000);
24
+ const validForSeconds = input.validForSeconds ?? DEFAULT_VALID_FOR_SECONDS;
25
+ const requirements = {
26
+ scheme: 'exact',
27
+ network: input.network ?? BASE_MAINNET,
28
+ amount: input.amount,
29
+ asset: input.asset ?? USDC_BASE,
30
+ payTo: input.to,
31
+ maxTimeoutSeconds: DEFAULT_MAX_TIMEOUT_SECONDS,
32
+ };
33
+ const payload = {
34
+ x402Version: 2,
35
+ accepted: { ...requirements },
36
+ payload: {
37
+ signature: PLACEHOLDER_EIP712_SIGNATURE,
38
+ authorization: {
39
+ from: agent.wallet,
40
+ to: input.to,
41
+ value: input.amount,
42
+ validAfter: String(nowSeconds - 60),
43
+ validBefore: String(nowSeconds + validForSeconds),
44
+ nonce: toAuthorizationNonce(input.nonce),
45
+ },
46
+ },
47
+ };
48
+ return { requirements, payload };
49
+ }
50
+ //# sourceMappingURL=x402.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"x402.js","sourceRoot":"","sources":["../src/x402.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEpE,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,kEAAkE;AAClE,MAAM,4BAA4B,GAAG,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;AAE5D,MAAM,2BAA2B,GAAG,EAAE,CAAC;AACvC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAgCtC,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,kBAAkB;AAClB,SAAS,oBAAoB,CAAC,KAAyB;IACrD,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CAAC,iDAAiD,KAAK,GAAG,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,KAAK,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,iFAAiF;AACjF,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,KAAiB,EAAE,KAAkB,EAAE,QAAgB,IAAI,CAAC,GAAG,EAAE;IAC7F,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC5C,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe,IAAI,yBAAyB,CAAC;IAE3E,MAAM,YAAY,GAAwB;QACxC,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,YAAY;QACtC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,SAAS;QAC/B,KAAK,EAAE,KAAK,CAAC,EAAE;QACf,iBAAiB,EAAE,2BAA2B;KAC/C,CAAC;IAEF,MAAM,OAAO,GAAmB;QAC9B,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,EAAE,GAAG,YAAY,EAAE;QAC7B,OAAO,EAAE;YACP,SAAS,EAAE,4BAA4B;YACvC,aAAa,EAAE;gBACb,IAAI,EAAE,KAAK,CAAC,MAAM;gBAClB,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,KAAK,EAAE,KAAK,CAAC,MAAM;gBACnB,UAAU,EAAE,MAAM,CAAC,UAAU,GAAG,EAAE,CAAC;gBACnC,WAAW,EAAE,MAAM,CAAC,UAAU,GAAG,eAAe,CAAC;gBACjD,KAAK,EAAE,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC;aACzC;SACF;KACF,CAAC;IAEF,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;AACnC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@quartermaster-sh/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Check before your agent pays. The TypeScript client for Quartermaster — policy checks + signed, hash-chained receipts for x402 agent spending.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": ["dist", "README.md"],
16
+ "sideEffects": false,
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "dependencies": {
25
+ "@noble/ed25519": "^3.1.0",
26
+ "@noble/hashes": "^2.2.0"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^6.0.3"
30
+ },
31
+ "keywords": ["x402", "agent", "payments", "policy", "attestation", "quartermaster"],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/src4511-ops/quartermaster.git",
35
+ "directory": "packages/sdk"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/src4511-ops/quartermaster/issues"
39
+ },
40
+ "homepage": "https://github.com/src4511-ops/quartermaster/tree/main/packages/sdk#readme",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "license": "MIT"
45
+ }