@zadq/x402 0.0.0-stage → 0.2.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.
@@ -0,0 +1,182 @@
1
+ // x402 `PaymentRequired` identity-policy extension helpers, following the
2
+ // bIP-0002 x402 transport profile (draft MintID MINT-190, cited by name): the
3
+ // identity policy rides a `PaymentRequired` extension; the identity proof
4
+ // rides a companion header alongside `X-PAYMENT` (see header.ts).
5
+ //
6
+ // Graceful degradation, by design: the extension is strictly additive.
7
+ // Counterparties that do not understand it see a normal 402 response and pay
8
+ // exactly as before; counterparties that do understand it can check the
9
+ // endpoint's verification signal — from the inline signed verdict, locally
10
+ // and with no round-trip, or later via the free verification path. Absence of
11
+ // the extension, of a verdict, or of any verification response changes
12
+ // nothing in either party's flow.
13
+ /** Extension key defined by the bIP-0002 profile for the identity policy. */
14
+ export const IDENTITY_POLICY_EXTENSION = 'identityPolicy';
15
+ /** The vendor-owned extension key the trust-perimeter invitation rides under. */
16
+ export const PERIMETER_EXTENSION = 'trustPerimeter';
17
+ /**
18
+ * Seller side: returns a copy of the 402 body with the identity policy
19
+ * attached under `extensions.identityPolicy`. Existing extensions are
20
+ * preserved. Only the canonical fields are emitted, so the object always
21
+ * validates against the closed v0 seller schema whatever else the caller's
22
+ * policy value carried.
23
+ */
24
+ export function withIdentityPolicy(body, policy) {
25
+ const canonical = {
26
+ claim: policy.claim,
27
+ assuranceAtLeast: policy.assuranceAtLeast,
28
+ verifyUrl: policy.verifyUrl,
29
+ };
30
+ if (policy.verdict !== undefined)
31
+ canonical.verdict = policy.verdict;
32
+ return {
33
+ ...body,
34
+ extensions: { ...body.extensions, [IDENTITY_POLICY_EXTENSION]: canonical },
35
+ };
36
+ }
37
+ /**
38
+ * Counterparty side: reads the identity policy from a 402 body, if present
39
+ * and well-formed. Returns `undefined` otherwise — an absent or malformed
40
+ * policy is a normal state, and the payment flow proceeds unchanged. Fields
41
+ * outside the canonical object are dropped, never fatal.
42
+ */
43
+ export function readIdentityPolicy(body) {
44
+ const raw = body.extensions?.[IDENTITY_POLICY_EXTENSION];
45
+ if (typeof raw !== 'object' || raw === null) {
46
+ return undefined;
47
+ }
48
+ const policy = raw;
49
+ if (policy['claim'] !== 'seller-endpoint-accountability' ||
50
+ typeof policy['assuranceAtLeast'] !== 'string' ||
51
+ typeof policy['verifyUrl'] !== 'string') {
52
+ return undefined;
53
+ }
54
+ const read = {
55
+ claim: 'seller-endpoint-accountability',
56
+ assuranceAtLeast: policy['assuranceAtLeast'],
57
+ verifyUrl: policy['verifyUrl'],
58
+ };
59
+ const verdict = policy['verdict'];
60
+ if (typeof verdict === 'object' && verdict !== null) {
61
+ read.verdict = verdict;
62
+ }
63
+ return read;
64
+ }
65
+ /**
66
+ * Vendor side: returns a copy of the 402 body with the trust-perimeter
67
+ * invitation attached under its own key. `identityPolicy` is untouched.
68
+ */
69
+ export function withPerimeterInvitation(body, invitation) {
70
+ const canonical = {
71
+ attestation: invitation.attestation,
72
+ attestUrl: invitation.attestUrl,
73
+ };
74
+ return {
75
+ ...body,
76
+ extensions: { ...body.extensions, [PERIMETER_EXTENSION]: canonical },
77
+ };
78
+ }
79
+ /**
80
+ * Agent side: reads the vendor's invitation, if present and well-formed.
81
+ * Anything else — absent, malformed, or a value this line does not offer
82
+ * (such as "required") — reads as `undefined`, and the payment proceeds
83
+ * exactly as before.
84
+ */
85
+ export function readPerimeterInvitation(body) {
86
+ const raw = body.extensions?.[PERIMETER_EXTENSION];
87
+ if (typeof raw !== 'object' || raw === null)
88
+ return undefined;
89
+ const v = raw;
90
+ const attestation = v['attestation'];
91
+ const attestUrl = v['attestUrl'];
92
+ if ((attestation !== 'welcome' && attestation !== 'preferred') ||
93
+ typeof attestUrl !== 'string' ||
94
+ !attestUrl.startsWith('https://')) {
95
+ return undefined;
96
+ }
97
+ return { attestation, attestUrl };
98
+ }
99
+ /** Extension key defined by the bIP-0002 profile for the complete v1 object. */
100
+ export const MINTID_IDENTITY_EXTENSION = 'mintid_identity';
101
+ /**
102
+ * Returns a copy of the 402 body with the canonical v1 object attached under
103
+ * `extensions.mintid_identity`. Only the canonical fields are emitted (each
104
+ * claim entry reduced to `{claim, operator?, value}`), so the object always
105
+ * validates against the closed v1 schema whatever else the caller's value
106
+ * carried. `identityPolicy` and every other key are untouched.
107
+ */
108
+ export function withMintidIdentity(body, policy) {
109
+ return {
110
+ ...body,
111
+ extensions: { ...body.extensions, [MINTID_IDENTITY_EXTENSION]: canonicalV1(policy) },
112
+ };
113
+ }
114
+ /**
115
+ * Counterparty side: reads the v1 object from a 402 body, if present and
116
+ * well-formed for this profile. Anything else — absent, another profile,
117
+ * a non-https challenge endpoint, an empty policy — reads as `undefined`,
118
+ * and the payment proceeds exactly as before.
119
+ */
120
+ export function readMintidIdentity(body) {
121
+ const raw = body.extensions?.[MINTID_IDENTITY_EXTENSION];
122
+ if (typeof raw !== 'object' || raw === null)
123
+ return undefined;
124
+ const v = raw;
125
+ const entries = v['claim_policy'];
126
+ if (v['profile'] !== 'bip-0002/1' ||
127
+ typeof v['challenge_endpoint'] !== 'string' ||
128
+ !v['challenge_endpoint'].startsWith('https://') ||
129
+ v['session_mode'] !== 'token_exchange' ||
130
+ !Array.isArray(entries) ||
131
+ entries.length === 0 ||
132
+ !entries.every(isClaimEntry)) {
133
+ return undefined;
134
+ }
135
+ return canonicalV1(v);
136
+ }
137
+ /**
138
+ * Seller side: one call for the coexistence window. Returns a copy of the
139
+ * 402 body with `identityPolicy` attached and, when the credential is held
140
+ * and a v1 policy is declared, `mintid_identity` beside it. Neither object
141
+ * ever absorbs a field of the other; both stay canonical and closed.
142
+ */
143
+ export function withSellerIdentity(body, identity) {
144
+ const withV0 = withIdentityPolicy(body, identity.policy);
145
+ if (identity.credential === 'held' && identity.v1 !== undefined) {
146
+ return withMintidIdentity(withV0, identity.v1);
147
+ }
148
+ return withV0;
149
+ }
150
+ function isClaimEntry(entry) {
151
+ if (typeof entry !== 'object' || entry === null)
152
+ return false;
153
+ const e = entry;
154
+ const value = e['value'];
155
+ return (typeof e['claim'] === 'string' &&
156
+ (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') &&
157
+ (e['operator'] === undefined || e['operator'] === '>='));
158
+ }
159
+ function canonicalV1(policy) {
160
+ const canonical = {
161
+ profile: 'bip-0002/1',
162
+ challenge_endpoint: policy.challenge_endpoint,
163
+ claim_policy: policy.claim_policy.map((e) => {
164
+ const entry = { claim: e.claim, value: e.value };
165
+ if (e.operator !== undefined)
166
+ entry.operator = e.operator;
167
+ return entry;
168
+ }),
169
+ session_mode: 'token_exchange',
170
+ };
171
+ if (policy.issuer_policy !== undefined) {
172
+ const ip = {};
173
+ if (policy.issuer_policy.accepted_issuer_ids !== undefined) {
174
+ ip.accepted_issuer_ids = [...policy.issuer_policy.accepted_issuer_ids];
175
+ }
176
+ if (policy.issuer_policy.issuer_state_minimum !== undefined) {
177
+ ip.issuer_state_minimum = policy.issuer_policy.issuer_state_minimum;
178
+ }
179
+ canonical.issuer_policy = ip;
180
+ }
181
+ return canonical;
182
+ }
@@ -0,0 +1,30 @@
1
+ /** The standard x402 payment header (payload untouched by this snippet). */
2
+ export declare const X_PAYMENT_HEADER = "X-PAYMENT";
3
+ /** Companion header defined by the bIP-0002 profile for the identity proof. */
4
+ export declare const X_PAYMENT_IDENTITY_HEADER = "X-PAYMENT-IDENTITY";
5
+ /**
6
+ * Envelope carried by the companion header (base64url-encoded JSON). The
7
+ * payload stays opaque to the transport: the v0 seller rail carries
8
+ * issuer-signed attestation references; v1 presentations ride the same
9
+ * envelope under their own format identifier.
10
+ */
11
+ export type IdentityProof = {
12
+ version: 1;
13
+ /** Proof format identifier, e.g. "attestation-ref/v0". */
14
+ format: string;
15
+ /** Opaque proof payload, base64url. Never identity data itself. */
16
+ payload: string;
17
+ };
18
+ type HeaderRecord = Record<string, string>;
19
+ /**
20
+ * Sender side: attaches the identity proof next to `X-PAYMENT`. Returns the
21
+ * same headers object/record for chaining.
22
+ */
23
+ export declare function attachIdentityProof<T extends Headers | HeaderRecord>(headers: T, proof: IdentityProof): T;
24
+ /**
25
+ * Receiver side: reads the identity proof, if present and well-formed.
26
+ * Returns `undefined` otherwise — an absent or malformed proof is a normal
27
+ * state; handle the request exactly as you would without the header.
28
+ */
29
+ export declare function readIdentityProof(headers: Headers | HeaderRecord): IdentityProof | undefined;
30
+ export {};
package/dist/header.js ADDED
@@ -0,0 +1,84 @@
1
+ // X-PAYMENT companion header helpers — proof transport of the bIP-0002 profile
2
+ // (draft MintID MINT-190, cited by name): the identity proof travels in a
3
+ // companion header next to `X-PAYMENT`, so the paying request stays a single
4
+ // HTTP round-trip and the x402 payment payload itself is never modified.
5
+ //
6
+ // Zero runtime dependencies; works with both `Headers`
7
+ // objects and plain header records.
8
+ //
9
+ // Graceful degradation, by design: the proof header is optional in both
10
+ // directions. A request without it is a normal x402 request and the sale
11
+ // completes as usual; a receiver that does not understand the header ignores
12
+ // it. Whether and how a receiver weighs an absent proof is its own published
13
+ // policy, applied out of band — never a hard gate wired into this snippet.
14
+ /** The standard x402 payment header (payload untouched by this snippet). */
15
+ export const X_PAYMENT_HEADER = 'X-PAYMENT';
16
+ /** Companion header defined by the bIP-0002 profile for the identity proof. */
17
+ export const X_PAYMENT_IDENTITY_HEADER = 'X-PAYMENT-IDENTITY';
18
+ /**
19
+ * Sender side: attaches the identity proof next to `X-PAYMENT`. Returns the
20
+ * same headers object/record for chaining.
21
+ */
22
+ export function attachIdentityProof(headers, proof) {
23
+ const value = base64UrlEncode(JSON.stringify(proof));
24
+ if (headers instanceof Headers) {
25
+ headers.set(X_PAYMENT_IDENTITY_HEADER, value);
26
+ }
27
+ else {
28
+ headers[X_PAYMENT_IDENTITY_HEADER] = value;
29
+ }
30
+ return headers;
31
+ }
32
+ /**
33
+ * Receiver side: reads the identity proof, if present and well-formed.
34
+ * Returns `undefined` otherwise — an absent or malformed proof is a normal
35
+ * state; handle the request exactly as you would without the header.
36
+ */
37
+ export function readIdentityProof(headers) {
38
+ const value = headers instanceof Headers
39
+ ? headers.get(X_PAYMENT_IDENTITY_HEADER)
40
+ : findHeader(headers, X_PAYMENT_IDENTITY_HEADER);
41
+ if (value === null || value === undefined || value === '') {
42
+ return undefined;
43
+ }
44
+ try {
45
+ const parsed = JSON.parse(base64UrlDecode(value));
46
+ if (parsed['version'] !== 1 ||
47
+ typeof parsed['format'] !== 'string' ||
48
+ typeof parsed['payload'] !== 'string') {
49
+ return undefined;
50
+ }
51
+ return parsed;
52
+ }
53
+ catch {
54
+ return undefined;
55
+ }
56
+ }
57
+ // Plain records may carry any header-name casing; HTTP header names are
58
+ // case-insensitive.
59
+ function findHeader(headers, name) {
60
+ const lower = name.toLowerCase();
61
+ for (const key of Object.keys(headers)) {
62
+ if (key.toLowerCase() === lower) {
63
+ return headers[key];
64
+ }
65
+ }
66
+ return undefined;
67
+ }
68
+ function base64UrlEncode(value) {
69
+ const bytes = new TextEncoder().encode(value);
70
+ let binary = '';
71
+ for (const b of bytes) {
72
+ binary += String.fromCharCode(b);
73
+ }
74
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
75
+ }
76
+ function base64UrlDecode(value) {
77
+ const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
78
+ const binary = atob(base64);
79
+ const bytes = new Uint8Array(binary.length);
80
+ for (let i = 0; i < binary.length; i++) {
81
+ bytes[i] = binary.charCodeAt(i);
82
+ }
83
+ return new TextDecoder().decode(bytes);
84
+ }
@@ -0,0 +1,28 @@
1
+ /** The chain query path of the issuer-scoped read. */
2
+ export declare const RECORDED_NULLIFIERS_QUERY = "/identity.v1.selfrevoke.Query/RecordedNullifiers";
3
+ /** A recorded self-revocation's state on the chain, by revocation value (hex). */
4
+ export type RecordedState = 'pending' | 'folded';
5
+ export type ChainReadOptions = {
6
+ fetch?: typeof fetch;
7
+ timeoutMs?: number;
8
+ };
9
+ /** Protobuf length-delimited field (wire type 2). */
10
+ export declare function bytesField(field: number, value: Uint8Array): Uint8Array;
11
+ /** Protobuf varint field (wire type 0). */
12
+ export declare function varintField(field: number, value: number): Uint8Array;
13
+ export declare function hexToBytes(hex: string): Uint8Array;
14
+ export declare function bytesToHex(bytes: Uint8Array): string;
15
+ /** `QueryRecordedNullifiersRequest{issuer_id = 1}` — names no revocation value. */
16
+ export declare function encodeIssuerQuery(issuerIdHex: string): Uint8Array;
17
+ /**
18
+ * `QueryRecordedNullifiersResponse{repeated RecordedNullifier records = 1}`
19
+ * with `RecordedNullifier{version = 1, nullifier = 2, folded_in_epoch = 3}`
20
+ * → `{<value hex>: 'pending' | 'folded'}`. Other fields are skipped.
21
+ */
22
+ export declare function decodeRecordedNullifiers(raw: Uint8Array): Map<string, RecordedState>;
23
+ /**
24
+ * Reads the issuer's recorded self-revocations from a node (`rpcUrl` is
25
+ * the node's JSON-RPC endpoint). `HolderStepError('chain', …)` when the node
26
+ * does not answer or refuses the query — never a partial answer.
27
+ */
28
+ export declare function readRecordedNullifiers(rpcUrl: string, issuerIdHex: string, options?: ChainReadOptions): Promise<Map<string, RecordedState>>;
@@ -0,0 +1,167 @@
1
+ // The holder's one chain read: the issuer-scoped set of recorded
2
+ // self-revocations, read through a node's JSON-RPC `abci_query`, restated
3
+ // from the chain's self-revocation query contract (cited by name). The
4
+ // request names the issuer only; the holder finds its own revocation value
5
+ // in the answer locally, so the node — and any edge in front of it — never
6
+ // learns which one the caller holds. What it can pair with the caller's
7
+ // address is "someone holds a credential of this issuer", nothing more.
8
+ //
9
+ // A relay's answer is never the receipt: before a self-revocation is
10
+ // reported recorded, this read must show it. Zero runtime dependencies; the
11
+ // two protobuf shapes involved are small enough to encode and decode by
12
+ // hand (varints and length-delimited fields only).
13
+ //
14
+ // CONTRACT POINT: the query path and the field numbers below
15
+ // (request: issuer id = field 1; each record: version = 1, value = 2,
16
+ // folded-in epoch = 3) are restated from MintID's chain query contract and
17
+ // are to be confirmed against MintID's self-contained attachment on the
18
+ // card that tracks this integration, before the first live run; they are
19
+ // the only place to change.
20
+ import { HolderStepError } from "./errors.js";
21
+ /** The chain query path of the issuer-scoped read. */
22
+ export const RECORDED_NULLIFIERS_QUERY = '/identity.v1.selfrevoke.Query/RecordedNullifiers';
23
+ function varint(value) {
24
+ const out = [];
25
+ let v = value;
26
+ while (v >= 0x80) {
27
+ out.push((v & 0x7f) | 0x80);
28
+ v = Math.floor(v / 128);
29
+ }
30
+ out.push(v);
31
+ return out;
32
+ }
33
+ /** Protobuf length-delimited field (wire type 2). */
34
+ export function bytesField(field, value) {
35
+ return Uint8Array.from([...varint((field << 3) | 2), ...varint(value.length), ...value]);
36
+ }
37
+ /** Protobuf varint field (wire type 0). */
38
+ export function varintField(field, value) {
39
+ return Uint8Array.from([...varint(field << 3), ...varint(value)]);
40
+ }
41
+ export function hexToBytes(hex) {
42
+ if (!/^([0-9a-f]{2})*$/.test(hex))
43
+ throw new HolderStepError('chain', 0, { error: 'not hex' });
44
+ return Uint8Array.from(hex.match(/../g) ?? [], (b) => parseInt(b, 16));
45
+ }
46
+ export function bytesToHex(bytes) {
47
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
48
+ }
49
+ /** `QueryRecordedNullifiersRequest{issuer_id = 1}` — names no revocation value. */
50
+ export function encodeIssuerQuery(issuerIdHex) {
51
+ return bytesField(1, hexToBytes(issuerIdHex));
52
+ }
53
+ function readVarint(data, pos) {
54
+ let result = 0;
55
+ let shift = 1;
56
+ let p = pos;
57
+ for (;;) {
58
+ const byte = data[p];
59
+ if (byte === undefined)
60
+ throw new HolderStepError('chain', 0, { error: 'truncated varint' });
61
+ p += 1;
62
+ result += (byte & 0x7f) * shift;
63
+ if ((byte & 0x80) === 0)
64
+ return [result, p];
65
+ shift *= 128;
66
+ }
67
+ }
68
+ function* fields(data) {
69
+ let pos = 0;
70
+ while (pos < data.length) {
71
+ const [tag, next] = readVarint(data, pos);
72
+ const field = Math.floor(tag / 8);
73
+ const wire = tag % 8;
74
+ if (wire === 0) {
75
+ const [value, after] = readVarint(data, next);
76
+ pos = after;
77
+ yield { field, varint: value };
78
+ }
79
+ else if (wire === 2) {
80
+ const [length, start] = readVarint(data, next);
81
+ const end = start + length;
82
+ if (end > data.length)
83
+ throw new HolderStepError('chain', 0, { error: 'truncated field' });
84
+ pos = end;
85
+ yield { field, bytes: data.subarray(start, end) };
86
+ }
87
+ else {
88
+ throw new HolderStepError('chain', 0, { error: `unsupported wire type ${wire}` });
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * `QueryRecordedNullifiersResponse{repeated RecordedNullifier records = 1}`
94
+ * with `RecordedNullifier{version = 1, nullifier = 2, folded_in_epoch = 3}`
95
+ * → `{<value hex>: 'pending' | 'folded'}`. Other fields are skipped.
96
+ */
97
+ export function decodeRecordedNullifiers(raw) {
98
+ const out = new Map();
99
+ for (const f of fields(raw)) {
100
+ if (f.field !== 1 || !f.bytes)
101
+ continue;
102
+ let value = '';
103
+ let folded = 0;
104
+ for (const inner of fields(f.bytes)) {
105
+ if (inner.field === 2 && inner.bytes)
106
+ value = bytesToHex(inner.bytes);
107
+ else if (inner.field === 3 && inner.varint !== undefined)
108
+ folded = inner.varint;
109
+ }
110
+ if (value)
111
+ out.set(value, folded ? 'folded' : 'pending');
112
+ }
113
+ return out;
114
+ }
115
+ /**
116
+ * Reads the issuer's recorded self-revocations from a node (`rpcUrl` is
117
+ * the node's JSON-RPC endpoint). `HolderStepError('chain', …)` when the node
118
+ * does not answer or refuses the query — never a partial answer.
119
+ */
120
+ export async function readRecordedNullifiers(rpcUrl, issuerIdHex, options = {}) {
121
+ const fetchImpl = options.fetch ?? fetch;
122
+ const request = {
123
+ jsonrpc: '2.0',
124
+ id: 1,
125
+ method: 'abci_query',
126
+ params: {
127
+ path: RECORDED_NULLIFIERS_QUERY,
128
+ data: bytesToHex(encodeIssuerQuery(issuerIdHex)),
129
+ prove: false,
130
+ },
131
+ };
132
+ let response;
133
+ try {
134
+ response = await fetchImpl(rpcUrl, {
135
+ method: 'POST',
136
+ headers: { 'content-type': 'application/json' },
137
+ body: JSON.stringify(request),
138
+ signal: AbortSignal.timeout(options.timeoutMs ?? 5_000),
139
+ });
140
+ }
141
+ catch (cause) {
142
+ throw new HolderStepError('chain', 0, { error: String(cause) });
143
+ }
144
+ let answer;
145
+ try {
146
+ answer = (await response.json());
147
+ }
148
+ catch {
149
+ throw new HolderStepError('chain', response.status, { error: 'non_json_response' });
150
+ }
151
+ if (response.status !== 200 ||
152
+ typeof answer !== 'object' ||
153
+ answer === null ||
154
+ 'error' in answer) {
155
+ throw new HolderStepError('chain', response.status, { error: 'node refused the query' });
156
+ }
157
+ const result = (answer['result'] ?? {});
158
+ const inner = (result['response'] ?? {});
159
+ if (Number(inner['code'] ?? 0) !== 0) {
160
+ throw new HolderStepError('chain', response.status, {
161
+ error: 'recorded revocations query refused',
162
+ });
163
+ }
164
+ const value = inner['value'];
165
+ const raw = typeof value === 'string' && value !== '' ? Buffer.from(value, 'base64') : Buffer.alloc(0);
166
+ return decodeRecordedNullifiers(new Uint8Array(raw));
167
+ }
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ import { type CustodyStore } from './custody.js';
3
+ import { SellerHolder } from './holder.js';
4
+ import { type KillSwitchOptions, type KillSwitchReport } from './killswitch.js';
5
+ import { type HolderRpc } from './process.js';
6
+ export declare const CUSTODY_FILE_ENV = "CREDENTIAL_CUSTODY_FILE";
7
+ export declare const CUSTODY_KEY_ENV = "CREDENTIAL_CUSTODY_KEY";
8
+ export declare const DASHBOARD_URL_ENV = "VERIFICATION_BASE_URL";
9
+ export declare const DASHBOARD_SELLER_ENV = "VERIFICATION_SELLER_ID";
10
+ export declare const DASHBOARD_SESSION_ENV = "VERIFICATION_SESSION";
11
+ /** The credential state the seller's record takes at the dashboard service. */
12
+ export declare const DASHBOARD_REVOKED_STATE = "self_revoked";
13
+ export type KillSwitchEnv = Record<string, string | undefined>;
14
+ export type KillSwitchIO = {
15
+ out: (line: string) => void;
16
+ err: (line: string) => void;
17
+ };
18
+ /** Seams for tests: the holder process, the transports, the operation itself. */
19
+ export type KillSwitchDeps = {
20
+ rpc?: HolderRpc;
21
+ custody?: CustodyStore;
22
+ fetch?: typeof fetch;
23
+ revoke?: (holder: SellerHolder, options: KillSwitchOptions) => Promise<KillSwitchReport>;
24
+ };
25
+ export type CliReport = KillSwitchReport & {
26
+ dashboard: 'reported' | 'not_reported' | 'report_failed';
27
+ next?: string;
28
+ };
29
+ /**
30
+ * Runs the kill switch; resolves to the process exit code. `0`: the
31
+ * credential is reported `revoked` or `pending`; `2`: `unconfirmed` (the
32
+ * holder refuses to present already; check again or resubmit — both are
33
+ * safe); `1`: refused before anything was submitted, or misuse.
34
+ */
35
+ export declare function runKillSwitch(argv: string[], env: KillSwitchEnv, io: KillSwitchIO, deps?: KillSwitchDeps): Promise<number>;
36
+ /**
37
+ * Tells the seller's own record at the dashboard service that the
38
+ * credential was revoked — the state and nothing else: no value, no
39
+ * transaction, no time of the seller's choosing. The service keeps the
40
+ * state change and writes nothing next to it.
41
+ */
42
+ export declare function reportToDashboard(fetchImpl: typeof fetch, baseUrl: string, sellerId: string, session: string): Promise<boolean>;