@tabai/sdk 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.
- package/LICENSE +23 -0
- package/README.md +401 -0
- package/bin/tab.mjs +23 -0
- package/dist/_shared/abi.d.ts +150 -0
- package/dist/_shared/abi.d.ts.map +1 -0
- package/dist/_shared/abi.js +197 -0
- package/dist/_shared/abi.js.map +1 -0
- package/dist/_shared/chains.d.ts +118 -0
- package/dist/_shared/chains.d.ts.map +1 -0
- package/dist/_shared/chains.js +89 -0
- package/dist/_shared/chains.js.map +1 -0
- package/dist/_shared/hex.d.ts +35 -0
- package/dist/_shared/hex.d.ts.map +1 -0
- package/dist/_shared/hex.js +40 -0
- package/dist/_shared/hex.js.map +1 -0
- package/dist/_shared/index.d.ts +14 -0
- package/dist/_shared/index.d.ts.map +1 -0
- package/dist/_shared/index.js +14 -0
- package/dist/_shared/index.js.map +1 -0
- package/dist/_shared/keccak256.d.ts +29 -0
- package/dist/_shared/keccak256.d.ts.map +1 -0
- package/dist/_shared/keccak256.js +145 -0
- package/dist/_shared/keccak256.js.map +1 -0
- package/dist/_shared/result.d.ts +78 -0
- package/dist/_shared/result.d.ts.map +1 -0
- package/dist/_shared/result.js +61 -0
- package/dist/_shared/result.js.map +1 -0
- package/dist/cli/client-config.d.ts +155 -0
- package/dist/cli/client-config.d.ts.map +1 -0
- package/dist/cli/client-config.js +382 -0
- package/dist/cli/client-config.js.map +1 -0
- package/dist/cli/connect.d.ts +76 -0
- package/dist/cli/connect.d.ts.map +1 -0
- package/dist/cli/connect.js +158 -0
- package/dist/cli/connect.js.map +1 -0
- package/dist/cli/doctor.d.ts +57 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +253 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +45 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +371 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/http/client-402.d.ts +243 -0
- package/dist/http/client-402.d.ts.map +1 -0
- package/dist/http/client-402.js +515 -0
- package/dist/http/client-402.js.map +1 -0
- package/dist/http/headers.d.ts +173 -0
- package/dist/http/headers.d.ts.map +1 -0
- package/dist/http/headers.js +284 -0
- package/dist/http/headers.js.map +1 -0
- package/dist/http/index.d.ts +15 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +15 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/metering-claim.d.ts +82 -0
- package/dist/http/metering-claim.d.ts.map +1 -0
- package/dist/http/metering-claim.js +99 -0
- package/dist/http/metering-claim.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +40 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +50 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/assets.d.ts +31 -0
- package/dist/mcp/assets.d.ts.map +1 -0
- package/dist/mcp/assets.js +78 -0
- package/dist/mcp/assets.js.map +1 -0
- package/dist/mcp/index.d.ts +19 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/json-schema.d.ts +86 -0
- package/dist/mcp/json-schema.d.ts.map +1 -0
- package/dist/mcp/json-schema.js +215 -0
- package/dist/mcp/json-schema.js.map +1 -0
- package/dist/mcp/json.d.ts +43 -0
- package/dist/mcp/json.d.ts.map +1 -0
- package/dist/mcp/json.js +69 -0
- package/dist/mcp/json.js.map +1 -0
- package/dist/mcp/registry-client.d.ts +88 -0
- package/dist/mcp/registry-client.d.ts.map +1 -0
- package/dist/mcp/registry-client.js +158 -0
- package/dist/mcp/registry-client.js.map +1 -0
- package/dist/mcp/schemas.d.ts +82 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +493 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/server.d.ts +97 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +285 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/settings.d.ts +90 -0
- package/dist/mcp/settings.d.ts.map +1 -0
- package/dist/mcp/settings.js +160 -0
- package/dist/mcp/settings.js.map +1 -0
- package/dist/mcp/toolset.d.ts +231 -0
- package/dist/mcp/toolset.d.ts.map +1 -0
- package/dist/mcp/toolset.js +760 -0
- package/dist/mcp/toolset.js.map +1 -0
- package/dist/payments/abi.d.ts +9 -0
- package/dist/payments/abi.d.ts.map +1 -0
- package/dist/payments/abi.js +17 -0
- package/dist/payments/abi.js.map +1 -0
- package/dist/payments/config.d.ts +199 -0
- package/dist/payments/config.d.ts.map +1 -0
- package/dist/payments/config.js +259 -0
- package/dist/payments/config.js.map +1 -0
- package/dist/payments/index.d.ts +13 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +13 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/payments/kuru.d.ts +191 -0
- package/dist/payments/kuru.d.ts.map +1 -0
- package/dist/payments/kuru.js +377 -0
- package/dist/payments/kuru.js.map +1 -0
- package/dist/payments/monad.d.ts +69 -0
- package/dist/payments/monad.d.ts.map +1 -0
- package/dist/payments/monad.js +306 -0
- package/dist/payments/monad.js.map +1 -0
- package/dist/payments/permit2.d.ts +118 -0
- package/dist/payments/permit2.d.ts.map +1 -0
- package/dist/payments/permit2.js +366 -0
- package/dist/payments/permit2.js.map +1 -0
- package/dist/payments/registry.d.ts +119 -0
- package/dist/payments/registry.d.ts.map +1 -0
- package/dist/payments/registry.js +199 -0
- package/dist/payments/registry.js.map +1 -0
- package/dist/payments/strategy.d.ts +80 -0
- package/dist/payments/strategy.d.ts.map +1 -0
- package/dist/payments/strategy.js +103 -0
- package/dist/payments/strategy.js.map +1 -0
- package/dist/proxy/hooks.d.ts +90 -0
- package/dist/proxy/hooks.d.ts.map +1 -0
- package/dist/proxy/hooks.js +35 -0
- package/dist/proxy/hooks.js.map +1 -0
- package/dist/proxy/index.d.ts +9 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +9 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/proxy/proxy.d.ts +156 -0
- package/dist/proxy/proxy.d.ts.map +1 -0
- package/dist/proxy/proxy.js +366 -0
- package/dist/proxy/proxy.js.map +1 -0
- package/dist/server/adapters/express.d.ts +89 -0
- package/dist/server/adapters/express.d.ts.map +1 -0
- package/dist/server/adapters/express.js +215 -0
- package/dist/server/adapters/express.js.map +1 -0
- package/dist/server/adapters/hono.d.ts +52 -0
- package/dist/server/adapters/hono.d.ts.map +1 -0
- package/dist/server/adapters/hono.js +61 -0
- package/dist/server/adapters/hono.js.map +1 -0
- package/dist/server/adapters/next.d.ts +52 -0
- package/dist/server/adapters/next.d.ts.map +1 -0
- package/dist/server/adapters/next.js +56 -0
- package/dist/server/adapters/next.js.map +1 -0
- package/dist/server/index.d.ts +30 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +30 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/metering.d.ts +209 -0
- package/dist/server/metering.d.ts.map +1 -0
- package/dist/server/metering.js +365 -0
- package/dist/server/metering.js.map +1 -0
- package/dist/server/post-paid.d.ts +355 -0
- package/dist/server/post-paid.d.ts.map +1 -0
- package/dist/server/post-paid.js +512 -0
- package/dist/server/post-paid.js.map +1 -0
- package/dist/x402/client.d.ts +203 -0
- package/dist/x402/client.d.ts.map +1 -0
- package/dist/x402/client.js +337 -0
- package/dist/x402/client.js.map +1 -0
- package/dist/x402/hub.d.ts +79 -0
- package/dist/x402/hub.d.ts.map +1 -0
- package/dist/x402/hub.js +164 -0
- package/dist/x402/hub.js.map +1 -0
- package/dist/x402/index.d.ts +27 -0
- package/dist/x402/index.d.ts.map +1 -0
- package/dist/x402/index.js +27 -0
- package/dist/x402/index.js.map +1 -0
- package/dist/x402/proxy.d.ts +162 -0
- package/dist/x402/proxy.d.ts.map +1 -0
- package/dist/x402/proxy.js +198 -0
- package/dist/x402/proxy.js.map +1 -0
- package/dist/x402/server.d.ts +162 -0
- package/dist/x402/server.d.ts.map +1 -0
- package/dist/x402/server.js +306 -0
- package/dist/x402/server.js.map +1 -0
- package/dist/x402/wire.d.ts +104 -0
- package/dist/x402/wire.d.ts.map +1 -0
- package/dist/x402/wire.js +265 -0
- package/dist/x402/wire.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The x402 client: sign an EIP-3009 authorization for an `exact` requirement
|
|
3
|
+
* and repeat the request with it.
|
|
4
|
+
*
|
|
5
|
+
* ## Where this sits in Tab
|
|
6
|
+
*
|
|
7
|
+
* Tab never asks an Agent to prepay. This client exists for the two moments a
|
|
8
|
+
* prepayment is the right answer anyway: an Agent with no headroom that would
|
|
9
|
+
* rather pay for one call than settle a tab first, and a Service fronting an
|
|
10
|
+
* x402 upstream that has to pay the upstream before it can meter the Agent.
|
|
11
|
+
* In both, the payment is one call's worth, signed by whoever holds the key,
|
|
12
|
+
* and nothing about the Open Tab changes.
|
|
13
|
+
*
|
|
14
|
+
* ## The signing is hand-rolled, and here is the check on it
|
|
15
|
+
*
|
|
16
|
+
* `@x402/evm` is the reference client for this scheme and it is built on viem.
|
|
17
|
+
* This package signs with ethers, so pulling it in would ship a second EVM
|
|
18
|
+
* library to sign one struct. The struct is small enough to state:
|
|
19
|
+
*
|
|
20
|
+
* - EIP-712 domain `{ name, version, chainId, verifyingContract }`, with `name`
|
|
21
|
+
* and `version` from `requirements.extra`, `chainId` from the CAIP-2 network,
|
|
22
|
+
* and `verifyingContract` the token (`scheme_exact_evm.md`, section 1);
|
|
23
|
+
* - primary type `TransferWithAuthorization(address from, address to, uint256
|
|
24
|
+
* value, uint256 validAfter, uint256 validBefore, bytes32 nonce)` (EIP-3009);
|
|
25
|
+
* - `validAfter` `0`, `validBefore` now plus `maxTimeoutSeconds`, and a random
|
|
26
|
+
* 32-byte `nonce`, exactly as the reference `createEIP3009Payload` builds them.
|
|
27
|
+
*
|
|
28
|
+
* The test suite recovers the signer from every signature this file produces,
|
|
29
|
+
* and the reference client was run over the same authorization while this was
|
|
30
|
+
* written: the two signatures are byte for byte the same.
|
|
31
|
+
*
|
|
32
|
+
* Specification: scheme_exact_evm.md section 1, x402-specification-v2.md section 5.2.
|
|
33
|
+
*/
|
|
34
|
+
import { type Signer } from "ethers";
|
|
35
|
+
import { type Address, type Hex, type Result } from "../_shared/index.js";
|
|
36
|
+
import { type HeaderReader, type HeaderRecord } from "../http/headers.js";
|
|
37
|
+
import { type Logger } from "../logger.js";
|
|
38
|
+
import { type PaymentPayload, type PaymentRequired, type PaymentRequirements, type RequirementFilter, type SettleResponse } from "./wire.js";
|
|
39
|
+
/**
|
|
40
|
+
* What signs an x402 payment.
|
|
41
|
+
*
|
|
42
|
+
* Structural rather than the `ethers` class, so a test double or any wallet
|
|
43
|
+
* that can sign typed data satisfies it. An `ethers.Signer` does as it stands;
|
|
44
|
+
* the assertion below fails to compile if that ever stops being true.
|
|
45
|
+
*/
|
|
46
|
+
export interface X402Signer {
|
|
47
|
+
getAddress(): Promise<string>;
|
|
48
|
+
signTypedData(domain: {
|
|
49
|
+
name: string;
|
|
50
|
+
version: string;
|
|
51
|
+
chainId: bigint | number;
|
|
52
|
+
verifyingContract: string;
|
|
53
|
+
}, types: Record<string, readonly {
|
|
54
|
+
name: string;
|
|
55
|
+
type: string;
|
|
56
|
+
}[]>, value: Record<string, unknown>): Promise<string>;
|
|
57
|
+
}
|
|
58
|
+
type Assert<T extends true> = T;
|
|
59
|
+
type SignerIsAccepted = Assert<Signer extends X402Signer ? true : false>;
|
|
60
|
+
export type X402SignerAcceptsEthersSigner = SignerIsAccepted;
|
|
61
|
+
/**
|
|
62
|
+
* A signer built only when a payment is about to be signed.
|
|
63
|
+
*
|
|
64
|
+
* Returning `undefined` means "not available here", the same answer a payment
|
|
65
|
+
* strategy factory may give: the offer is declined and the `402` stands as the
|
|
66
|
+
* credit decision it was. That is what keeps every read keyless: a config file
|
|
67
|
+
* declares the factory, and nothing calls it until a prepayment is the answer.
|
|
68
|
+
*/
|
|
69
|
+
export type X402SignerFactory = () => X402Signer | undefined | Promise<X402Signer | undefined>;
|
|
70
|
+
/** A signer, or a factory that may produce one. */
|
|
71
|
+
export type X402SignerSource = X402Signer | X402SignerFactory;
|
|
72
|
+
/** Resolves a source. `ok(undefined)` is a factory declining; `err` is a factory that threw. */
|
|
73
|
+
export declare function resolveX402Signer(source: X402SignerSource | undefined): Promise<Result<X402Signer | undefined>>;
|
|
74
|
+
/** The EIP-712 type of the struct EIP-3009 `transferWithAuthorization` verifies. */
|
|
75
|
+
export declare const TRANSFER_WITH_AUTHORIZATION_TYPES: {
|
|
76
|
+
readonly TransferWithAuthorization: readonly [{
|
|
77
|
+
readonly name: "from";
|
|
78
|
+
readonly type: "address";
|
|
79
|
+
}, {
|
|
80
|
+
readonly name: "to";
|
|
81
|
+
readonly type: "address";
|
|
82
|
+
}, {
|
|
83
|
+
readonly name: "value";
|
|
84
|
+
readonly type: "uint256";
|
|
85
|
+
}, {
|
|
86
|
+
readonly name: "validAfter";
|
|
87
|
+
readonly type: "uint256";
|
|
88
|
+
}, {
|
|
89
|
+
readonly name: "validBefore";
|
|
90
|
+
readonly type: "uint256";
|
|
91
|
+
}, {
|
|
92
|
+
readonly name: "nonce";
|
|
93
|
+
readonly type: "bytes32";
|
|
94
|
+
}];
|
|
95
|
+
};
|
|
96
|
+
/** The authorization as it goes on the wire: every number a decimal string. */
|
|
97
|
+
export interface Eip3009Authorization {
|
|
98
|
+
readonly from: string;
|
|
99
|
+
readonly to: string;
|
|
100
|
+
readonly value: string;
|
|
101
|
+
readonly validAfter: string;
|
|
102
|
+
readonly validBefore: string;
|
|
103
|
+
readonly nonce: Hex;
|
|
104
|
+
}
|
|
105
|
+
export interface SignExactPaymentOptions {
|
|
106
|
+
readonly signer: X402Signer;
|
|
107
|
+
/** The server's `PaymentRequired`, echoed into the payload as `resource` and `extensions`. */
|
|
108
|
+
readonly required: PaymentRequired;
|
|
109
|
+
/** The entry of `required.accepts` being paid. */
|
|
110
|
+
readonly accepted: PaymentRequirements;
|
|
111
|
+
/** Seconds since the epoch. Defaults to the wall clock. */
|
|
112
|
+
readonly now?: () => number;
|
|
113
|
+
/** A 32-byte nonce. Defaults to random. A test passes a fixed one. */
|
|
114
|
+
readonly nonce?: () => Hex;
|
|
115
|
+
}
|
|
116
|
+
/** What a signature is for, kept beside the payload so a receipt can be built without re-reading it. */
|
|
117
|
+
export interface SignedExactPayment {
|
|
118
|
+
readonly payload: PaymentPayload;
|
|
119
|
+
readonly authorization: Eip3009Authorization;
|
|
120
|
+
readonly chainId: bigint;
|
|
121
|
+
readonly payer: Address;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Signs one `exact` requirement.
|
|
125
|
+
*
|
|
126
|
+
* The domain and the struct are exactly what the reference client signs, so
|
|
127
|
+
* the facilitator's recovery lands on `authorization.from`. The requirement is
|
|
128
|
+
* re-validated here rather than trusted, because it arrived over the network.
|
|
129
|
+
*/
|
|
130
|
+
export declare function signExactPayment(options: SignExactPaymentOptions): Promise<Result<SignedExactPayment>>;
|
|
131
|
+
/** One payment this client made, as the facilitator reported it back. */
|
|
132
|
+
export interface X402PaymentReceipt {
|
|
133
|
+
/** The settlement transaction hash. Empty when the server reported success without one. */
|
|
134
|
+
readonly txHash: string;
|
|
135
|
+
readonly network: string;
|
|
136
|
+
readonly chainId: bigint;
|
|
137
|
+
/** Atomic units of `asset`, as the requirement priced it. */
|
|
138
|
+
readonly amount: bigint;
|
|
139
|
+
readonly asset: Address;
|
|
140
|
+
readonly payTo: Address;
|
|
141
|
+
readonly payer: Address;
|
|
142
|
+
/** The absolute URL the payment bought. */
|
|
143
|
+
readonly url: string;
|
|
144
|
+
readonly requirement: PaymentRequirements;
|
|
145
|
+
readonly settlement: SettleResponse;
|
|
146
|
+
readonly observedAt: number;
|
|
147
|
+
}
|
|
148
|
+
/** The little of a response this client reads. Structural, as in `client-402.ts`. */
|
|
149
|
+
export interface X402Response {
|
|
150
|
+
readonly status: number;
|
|
151
|
+
readonly headers: HeaderReader | HeaderRecord;
|
|
152
|
+
readonly body?: unknown;
|
|
153
|
+
readonly bodyUsed?: unknown;
|
|
154
|
+
}
|
|
155
|
+
export interface X402FetchInit {
|
|
156
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
157
|
+
readonly [option: string]: unknown;
|
|
158
|
+
}
|
|
159
|
+
export type X402Fetch<Res extends X402Response> = (url: string, init: X402FetchInit) => Promise<Res>;
|
|
160
|
+
export interface X402ClientOptions<Res extends X402Response = X402Response> extends RequirementFilter {
|
|
161
|
+
readonly signer: X402Signer;
|
|
162
|
+
readonly fetchImpl?: X402Fetch<Res>;
|
|
163
|
+
/**
|
|
164
|
+
* Consulted after the server's `402` and before anything is signed. A refusal
|
|
165
|
+
* stops the payment and is returned as the error. This is where a Service
|
|
166
|
+
* fronting an upstream checks that the Agent it will meter has the headroom
|
|
167
|
+
* before the Service's own funds move.
|
|
168
|
+
*/
|
|
169
|
+
readonly authorise?: (requirement: PaymentRequirements, required: PaymentRequired) => Promise<Result<void>> | Result<void>;
|
|
170
|
+
readonly onPayment?: (receipt: X402PaymentReceipt) => void;
|
|
171
|
+
readonly logger?: Logger;
|
|
172
|
+
/** Milliseconds since the epoch, for `observedAt`. Defaults to `Date.now`. */
|
|
173
|
+
readonly now?: () => number;
|
|
174
|
+
/** Seconds since the epoch, for `validBefore`. Defaults to the wall clock. */
|
|
175
|
+
readonly nowSeconds?: () => number;
|
|
176
|
+
readonly nonce?: () => Hex;
|
|
177
|
+
}
|
|
178
|
+
/** What one call did: the response, and the payment that bought it if one did. */
|
|
179
|
+
export interface X402FetchResult<Res extends X402Response = X402Response> {
|
|
180
|
+
readonly response: Res;
|
|
181
|
+
readonly payment?: X402PaymentReceipt;
|
|
182
|
+
}
|
|
183
|
+
export interface X402Client<Res extends X402Response = X402Response> {
|
|
184
|
+
/**
|
|
185
|
+
* Sends the request; on a `402` carrying `PAYMENT-REQUIRED`, signs and sends
|
|
186
|
+
* it once more with `PAYMENT-SIGNATURE`. Exactly once: a second `402` means
|
|
187
|
+
* the payment was refused, and that is returned as an error naming why.
|
|
188
|
+
*/
|
|
189
|
+
fetch(url: string, init?: X402FetchInit): Promise<Result<X402FetchResult<Res>>>;
|
|
190
|
+
/**
|
|
191
|
+
* Pays a `402` the caller already holds: the same second step, for a caller
|
|
192
|
+
* that made the first request itself.
|
|
193
|
+
*/
|
|
194
|
+
pay(url: string, init: X402FetchInit, required: PaymentRequired): Promise<Result<X402FetchResult<Res>>>;
|
|
195
|
+
/** Every payment made, oldest first. */
|
|
196
|
+
payments(): readonly X402PaymentReceipt[];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Builds the client. Construction is total; every failure belongs to a call.
|
|
200
|
+
*/
|
|
201
|
+
export declare function createX402Client<Res extends X402Response = X402Response>(options: X402ClientOptions<Res>): X402Client<Res>;
|
|
202
|
+
export {};
|
|
203
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/x402/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The x402 client: sign an EIP-3009 authorization for an `exact` requirement
|
|
3
|
+
* and repeat the request with it.
|
|
4
|
+
*
|
|
5
|
+
* ## Where this sits in Tab
|
|
6
|
+
*
|
|
7
|
+
* Tab never asks an Agent to prepay. This client exists for the two moments a
|
|
8
|
+
* prepayment is the right answer anyway: an Agent with no headroom that would
|
|
9
|
+
* rather pay for one call than settle a tab first, and a Service fronting an
|
|
10
|
+
* x402 upstream that has to pay the upstream before it can meter the Agent.
|
|
11
|
+
* In both, the payment is one call's worth, signed by whoever holds the key,
|
|
12
|
+
* and nothing about the Open Tab changes.
|
|
13
|
+
*
|
|
14
|
+
* ## The signing is hand-rolled, and here is the check on it
|
|
15
|
+
*
|
|
16
|
+
* `@x402/evm` is the reference client for this scheme and it is built on viem.
|
|
17
|
+
* This package signs with ethers, so pulling it in would ship a second EVM
|
|
18
|
+
* library to sign one struct. The struct is small enough to state:
|
|
19
|
+
*
|
|
20
|
+
* - EIP-712 domain `{ name, version, chainId, verifyingContract }`, with `name`
|
|
21
|
+
* and `version` from `requirements.extra`, `chainId` from the CAIP-2 network,
|
|
22
|
+
* and `verifyingContract` the token (`scheme_exact_evm.md`, section 1);
|
|
23
|
+
* - primary type `TransferWithAuthorization(address from, address to, uint256
|
|
24
|
+
* value, uint256 validAfter, uint256 validBefore, bytes32 nonce)` (EIP-3009);
|
|
25
|
+
* - `validAfter` `0`, `validBefore` now plus `maxTimeoutSeconds`, and a random
|
|
26
|
+
* 32-byte `nonce`, exactly as the reference `createEIP3009Payload` builds them.
|
|
27
|
+
*
|
|
28
|
+
* The test suite recovers the signer from every signature this file produces,
|
|
29
|
+
* and the reference client was run over the same authorization while this was
|
|
30
|
+
* written: the two signatures are byte for byte the same.
|
|
31
|
+
*
|
|
32
|
+
* Specification: scheme_exact_evm.md section 1, x402-specification-v2.md section 5.2.
|
|
33
|
+
*/
|
|
34
|
+
import { getAddress, hexlify, randomBytes } from "ethers";
|
|
35
|
+
import { causeOf, ok, wrap } from "../_shared/index.js";
|
|
36
|
+
import { fail, upstreamError, validationError } from "../errors.js";
|
|
37
|
+
import { headerReaderOf } from "../http/headers.js";
|
|
38
|
+
import { defaultLogger } from "../logger.js";
|
|
39
|
+
import { X402_HEADER, X402_VERSION, chainIdOfNetwork, encodePaymentSignature, readPaymentRequired, readPaymentResponse, selectExactRequirement, } from "./wire.js";
|
|
40
|
+
/** Resolves a source. `ok(undefined)` is a factory declining; `err` is a factory that threw. */
|
|
41
|
+
export async function resolveX402Signer(source) {
|
|
42
|
+
if (source === undefined)
|
|
43
|
+
return ok(undefined);
|
|
44
|
+
if (typeof source !== "function")
|
|
45
|
+
return ok(source);
|
|
46
|
+
const produced = await wrap(async () => (await source()), (error) => ({
|
|
47
|
+
category: "UPSTREAM",
|
|
48
|
+
code: "X402_SIGNER_FACTORY_FAILED",
|
|
49
|
+
message: "the x402 signer factory threw",
|
|
50
|
+
retryable: false,
|
|
51
|
+
cause: causeOf(error),
|
|
52
|
+
}));
|
|
53
|
+
if (!produced.ok)
|
|
54
|
+
return produced;
|
|
55
|
+
if (produced.value === undefined || produced.value === null)
|
|
56
|
+
return ok(undefined);
|
|
57
|
+
const candidate = produced.value;
|
|
58
|
+
if (typeof candidate.getAddress !== "function" || typeof candidate.signTypedData !== "function") {
|
|
59
|
+
return validationError("X402_SIGNER_INVALID", "the x402 signer factory returned something without getAddress() and signTypedData()");
|
|
60
|
+
}
|
|
61
|
+
return ok(candidate);
|
|
62
|
+
}
|
|
63
|
+
/** The EIP-712 type of the struct EIP-3009 `transferWithAuthorization` verifies. */
|
|
64
|
+
export const TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
65
|
+
TransferWithAuthorization: [
|
|
66
|
+
{ name: "from", type: "address" },
|
|
67
|
+
{ name: "to", type: "address" },
|
|
68
|
+
{ name: "value", type: "uint256" },
|
|
69
|
+
{ name: "validAfter", type: "uint256" },
|
|
70
|
+
{ name: "validBefore", type: "uint256" },
|
|
71
|
+
{ name: "nonce", type: "bytes32" },
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Signs one `exact` requirement.
|
|
76
|
+
*
|
|
77
|
+
* The domain and the struct are exactly what the reference client signs, so
|
|
78
|
+
* the facilitator's recovery lands on `authorization.from`. The requirement is
|
|
79
|
+
* re-validated here rather than trusted, because it arrived over the network.
|
|
80
|
+
*/
|
|
81
|
+
export async function signExactPayment(options) {
|
|
82
|
+
const { accepted } = options;
|
|
83
|
+
const chainId = chainIdOfNetwork(accepted.network);
|
|
84
|
+
if (!chainId.ok)
|
|
85
|
+
return chainId;
|
|
86
|
+
const name = accepted.extra?.["name"];
|
|
87
|
+
const version = accepted.extra?.["version"];
|
|
88
|
+
if (typeof name !== "string" || typeof version !== "string") {
|
|
89
|
+
return validationError("X402_DOMAIN_MISSING", `the requirement for ${accepted.asset} carries no EIP-712 domain name and version in extra, so an EIP-3009 authorization cannot be signed for it`, { details: { asset: accepted.asset, network: accepted.network } });
|
|
90
|
+
}
|
|
91
|
+
if (!/^[0-9]+$/.test(accepted.amount)) {
|
|
92
|
+
return validationError("X402_REQUIREMENTS_INVALID", `amount \`${accepted.amount}\` is not a decimal integer of atomic units`);
|
|
93
|
+
}
|
|
94
|
+
const from = await wrap(async () => options.signer.getAddress(), (error) => ({
|
|
95
|
+
category: "UPSTREAM",
|
|
96
|
+
code: "X402_SIGNER_UNAVAILABLE",
|
|
97
|
+
message: "the x402 signer could not report its address",
|
|
98
|
+
retryable: false,
|
|
99
|
+
cause: causeOf(error),
|
|
100
|
+
}));
|
|
101
|
+
if (!from.ok)
|
|
102
|
+
return from;
|
|
103
|
+
const checksummed = wrapAddresses(from.value, accepted.payTo, accepted.asset);
|
|
104
|
+
if (!checksummed.ok)
|
|
105
|
+
return checksummed;
|
|
106
|
+
const [payer, payTo, asset] = checksummed.value;
|
|
107
|
+
const nowSeconds = options.now === undefined ? Math.floor(Date.now() / 1000) : options.now();
|
|
108
|
+
const authorization = {
|
|
109
|
+
from: payer,
|
|
110
|
+
to: payTo,
|
|
111
|
+
value: accepted.amount,
|
|
112
|
+
validAfter: "0",
|
|
113
|
+
validBefore: (nowSeconds + Math.floor(accepted.maxTimeoutSeconds)).toString(10),
|
|
114
|
+
nonce: options.nonce === undefined ? hexlify(randomBytes(32)) : options.nonce(),
|
|
115
|
+
};
|
|
116
|
+
const signature = await wrap(async () => options.signer.signTypedData({ name, version, chainId: chainId.value, verifyingContract: asset }, TRANSFER_WITH_AUTHORIZATION_TYPES, {
|
|
117
|
+
from: authorization.from,
|
|
118
|
+
to: authorization.to,
|
|
119
|
+
value: BigInt(authorization.value),
|
|
120
|
+
validAfter: BigInt(authorization.validAfter),
|
|
121
|
+
validBefore: BigInt(authorization.validBefore),
|
|
122
|
+
nonce: authorization.nonce,
|
|
123
|
+
}), (error) => ({
|
|
124
|
+
category: "UPSTREAM",
|
|
125
|
+
code: "X402_SIGNING_FAILED",
|
|
126
|
+
message: "the x402 signer refused to sign the EIP-3009 authorization",
|
|
127
|
+
retryable: false,
|
|
128
|
+
cause: causeOf(error),
|
|
129
|
+
}));
|
|
130
|
+
if (!signature.ok)
|
|
131
|
+
return signature;
|
|
132
|
+
const payload = {
|
|
133
|
+
x402Version: X402_VERSION,
|
|
134
|
+
resource: options.required.resource,
|
|
135
|
+
accepted,
|
|
136
|
+
payload: { signature: signature.value, authorization },
|
|
137
|
+
// Echoed unchanged, as the reference client does: the server advertised
|
|
138
|
+
// them and validates that what comes back carries at least what it sent.
|
|
139
|
+
...(options.required.extensions === undefined ? {} : { extensions: options.required.extensions }),
|
|
140
|
+
};
|
|
141
|
+
return ok({ payload, authorization, chainId: chainId.value, payer: payer.toLowerCase() });
|
|
142
|
+
}
|
|
143
|
+
/** Checksums the three addresses a payload carries, or names the one that is not an address. */
|
|
144
|
+
function wrapAddresses(from, payTo, asset) {
|
|
145
|
+
const checked = [];
|
|
146
|
+
for (const [label, value] of [
|
|
147
|
+
["signer address", from],
|
|
148
|
+
["payTo", payTo],
|
|
149
|
+
["asset", asset],
|
|
150
|
+
]) {
|
|
151
|
+
try {
|
|
152
|
+
checked.push(getAddress(value));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return validationError("X402_REQUIREMENTS_INVALID", `${label} \`${value}\` is not a 20-byte address`, {
|
|
156
|
+
details: { field: label, value },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return ok(checked);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Builds the client. Construction is total; every failure belongs to a call.
|
|
164
|
+
*/
|
|
165
|
+
export function createX402Client(options) {
|
|
166
|
+
const logger = options.logger ?? defaultLogger;
|
|
167
|
+
const now = options.now ?? (() => Date.now());
|
|
168
|
+
const history = [];
|
|
169
|
+
const filter = {
|
|
170
|
+
...(options.chainId === undefined ? {} : { chainId: options.chainId }),
|
|
171
|
+
...(options.asset === undefined ? {} : { asset: options.asset }),
|
|
172
|
+
...(options.maxAmount === undefined ? {} : { maxAmount: options.maxAmount }),
|
|
173
|
+
};
|
|
174
|
+
const send = async (url, init, attempt) => {
|
|
175
|
+
const impl = options.fetchImpl ?? hostFetch();
|
|
176
|
+
if (impl === undefined) {
|
|
177
|
+
return upstreamError("FETCH_UNAVAILABLE", "this host has no global fetch; supply fetchImpl, or run on Node 20.10 or later");
|
|
178
|
+
}
|
|
179
|
+
const sent = await wrap(async () => impl(url, init), (error) => ({
|
|
180
|
+
category: "UPSTREAM",
|
|
181
|
+
code: "FETCH_FAILED",
|
|
182
|
+
message: `the request to ${url} did not complete`,
|
|
183
|
+
retryable: true,
|
|
184
|
+
details: { url, attempt },
|
|
185
|
+
cause: causeOf(error),
|
|
186
|
+
}));
|
|
187
|
+
if (!sent.ok)
|
|
188
|
+
return sent;
|
|
189
|
+
if (!looksLikeResponse(sent.value)) {
|
|
190
|
+
return upstreamError("FETCH_RESPONSE_INVALID", `the fetch given to this client returned something that is not a response for ${url}`, {
|
|
191
|
+
details: { url, attempt },
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return ok(sent.value);
|
|
195
|
+
};
|
|
196
|
+
const pay = async (url, init, required) => {
|
|
197
|
+
const accepted = selectExactRequirement(required, filter);
|
|
198
|
+
if (!accepted.ok)
|
|
199
|
+
return accepted;
|
|
200
|
+
if (options.authorise !== undefined) {
|
|
201
|
+
const allowed = await wrap(async () => options.authorise?.(accepted.value, required), (error) => ({
|
|
202
|
+
category: "INTERNAL",
|
|
203
|
+
code: "X402_AUTHORISE_THREW",
|
|
204
|
+
message: "the authorise hook threw, so nothing was signed",
|
|
205
|
+
retryable: false,
|
|
206
|
+
cause: causeOf(error),
|
|
207
|
+
}));
|
|
208
|
+
if (!allowed.ok)
|
|
209
|
+
return allowed;
|
|
210
|
+
if (allowed.value !== undefined && !allowed.value.ok)
|
|
211
|
+
return allowed.value;
|
|
212
|
+
}
|
|
213
|
+
if (!isReplayable(init["body"])) {
|
|
214
|
+
return validationError("REQUEST_BODY_NOT_REPLAYABLE", `${url} asks for payment and the request body is a stream, which cannot be sent a second time; buffer the body before calling`, { details: { url } });
|
|
215
|
+
}
|
|
216
|
+
const signed = await signExactPayment({
|
|
217
|
+
signer: options.signer,
|
|
218
|
+
required,
|
|
219
|
+
accepted: accepted.value,
|
|
220
|
+
...(options.nowSeconds === undefined ? {} : { now: options.nowSeconds }),
|
|
221
|
+
...(options.nonce === undefined ? {} : { nonce: options.nonce }),
|
|
222
|
+
});
|
|
223
|
+
if (!signed.ok)
|
|
224
|
+
return signed;
|
|
225
|
+
const header = encodePaymentSignature(signed.value.payload);
|
|
226
|
+
if (!header.ok)
|
|
227
|
+
return header;
|
|
228
|
+
const paid = await send(url, { ...init, headers: { ...init.headers, [X402_HEADER.paymentSignature]: header.value } }, 2);
|
|
229
|
+
if (!paid.ok)
|
|
230
|
+
return paid;
|
|
231
|
+
const response = paid.value;
|
|
232
|
+
const settlement = readPaymentResponse(headerReaderOf(response.headers));
|
|
233
|
+
if (!settlement.ok) {
|
|
234
|
+
discard(response, logger);
|
|
235
|
+
return settlement;
|
|
236
|
+
}
|
|
237
|
+
if (response.status === 402) {
|
|
238
|
+
discard(response, logger);
|
|
239
|
+
const reason = settlement.value?.errorReason ?? readError(response.headers) ?? "the server did not say why";
|
|
240
|
+
return fail("LIMIT", "X402_PAYMENT_REJECTED", `${url} refused the signed payment: ${reason}`, {
|
|
241
|
+
retryable: false,
|
|
242
|
+
details: {
|
|
243
|
+
url,
|
|
244
|
+
reason,
|
|
245
|
+
amount: accepted.value.amount,
|
|
246
|
+
asset: accepted.value.asset.toLowerCase(),
|
|
247
|
+
network: accepted.value.network,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
const receipt = {
|
|
252
|
+
txHash: settlement.value?.transaction ?? "",
|
|
253
|
+
network: accepted.value.network,
|
|
254
|
+
chainId: signed.value.chainId,
|
|
255
|
+
amount: BigInt(accepted.value.amount),
|
|
256
|
+
asset: accepted.value.asset.toLowerCase(),
|
|
257
|
+
payTo: accepted.value.payTo.toLowerCase(),
|
|
258
|
+
payer: signed.value.payer,
|
|
259
|
+
url,
|
|
260
|
+
requirement: accepted.value,
|
|
261
|
+
settlement: settlement.value ?? { success: response.status < 400, transaction: "", network: accepted.value.network },
|
|
262
|
+
observedAt: now(),
|
|
263
|
+
};
|
|
264
|
+
history.push(receipt);
|
|
265
|
+
if (options.onPayment !== undefined) {
|
|
266
|
+
try {
|
|
267
|
+
options.onPayment(receipt);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
logger.warn("onPayment threw and the payment was recorded anyway", { url, error: causeOf(error).message });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return ok({ response, payment: receipt });
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
async fetch(url, init = { headers: {} }) {
|
|
277
|
+
const first = await send(url, init, 1);
|
|
278
|
+
if (!first.ok)
|
|
279
|
+
return first;
|
|
280
|
+
if (first.value.status !== 402)
|
|
281
|
+
return ok({ response: first.value });
|
|
282
|
+
const required = readPaymentRequired(headerReaderOf(first.value.headers));
|
|
283
|
+
discard(first.value, logger);
|
|
284
|
+
if (!required.ok)
|
|
285
|
+
return required;
|
|
286
|
+
if (required.value === undefined) {
|
|
287
|
+
return fail("LIMIT", "X402_PAYMENT_REQUIRED_MISSING", `${url} answered 402 without a ${X402_HEADER.paymentRequired} header, so there is nothing to pay`, {
|
|
288
|
+
retryable: false,
|
|
289
|
+
details: { url },
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return pay(url, init, required.value);
|
|
293
|
+
},
|
|
294
|
+
pay,
|
|
295
|
+
payments: () => [...history],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** The `error` field of a `PAYMENT-REQUIRED` a refusal may carry, when it carries one. */
|
|
299
|
+
function readError(headers) {
|
|
300
|
+
const required = readPaymentRequired(headers);
|
|
301
|
+
return required.ok ? required.value?.error : undefined;
|
|
302
|
+
}
|
|
303
|
+
function looksLikeResponse(value) {
|
|
304
|
+
if (typeof value !== "object" || value === null)
|
|
305
|
+
return false;
|
|
306
|
+
const candidate = value;
|
|
307
|
+
return typeof candidate.status === "number" && typeof candidate.headers === "object" && candidate.headers !== null;
|
|
308
|
+
}
|
|
309
|
+
/** A stream cannot be sent twice; everything else can. */
|
|
310
|
+
function isReplayable(body) {
|
|
311
|
+
if (body === undefined || body === null || typeof body !== "object")
|
|
312
|
+
return true;
|
|
313
|
+
const candidate = body;
|
|
314
|
+
return typeof candidate.getReader !== "function" && typeof candidate[Symbol.asyncIterator] !== "function";
|
|
315
|
+
}
|
|
316
|
+
/** Releases a response that is not going out, so its connection does not wait for the collector. */
|
|
317
|
+
function discard(response, logger) {
|
|
318
|
+
const body = response.body;
|
|
319
|
+
if (body === null || body === undefined || typeof body.cancel !== "function" || response.bodyUsed === true)
|
|
320
|
+
return;
|
|
321
|
+
try {
|
|
322
|
+
const cancelled = body.cancel();
|
|
323
|
+
if (typeof cancelled?.catch === "function") {
|
|
324
|
+
cancelled.catch((error) => {
|
|
325
|
+
logger.debug("discarded response body could not be cancelled", causeOf(error));
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
logger.debug("discarded response body could not be cancelled", causeOf(error));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function hostFetch() {
|
|
334
|
+
const candidate = globalThis.fetch;
|
|
335
|
+
return typeof candidate === "function" ? candidate : undefined;
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/x402/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAe,MAAM,QAAQ,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAuC,MAAM,eAAe,CAAC;AAEvF,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,cAAc,EAAwC,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAe,MAAM,cAAc,CAAC;AAC1D,OAAO,EACL,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,GAMvB,MAAM,WAAW,CAAC;AAmCnB,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAoC;IAC1E,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,OAAO,MAAM,KAAK,UAAU;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,MAAM,IAAI,CACzB,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,MAAM,EAAE,CAAY,EACvC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACV,QAAQ,EAAE,UAAmB;QAC7B,IAAI,EAAE,4BAA4B;QAClC,OAAO,EAAE,+BAA+B;QACxC,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;KACtB,CAAC,CACH,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,QAAQ,CAAC;IAClC,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC,SAAS,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,QAAQ,CAAC,KAA4B,CAAC;IACxD,IAAI,OAAO,SAAS,CAAC,UAAU,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;QAChG,OAAO,eAAe,CAAC,qBAAqB,EAAE,qFAAqF,CAAC,CAAC;IACvI,CAAC;IACD,OAAO,EAAE,CAAC,SAAuB,CAAC,CAAC;AACrC,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,iCAAiC,GAAG;IAC/C,yBAAyB,EAAE;QACzB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;QACjC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;QAC/B,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;QAClC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE;QACvC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE;QACxC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE;KACnC;CACO,CAAC;AAgCX;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAgC;IACrE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC7B,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,OAAO,CAAC;IAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5D,OAAO,eAAe,CACpB,qBAAqB,EACrB,uBAAuB,QAAQ,CAAC,KAAK,4GAA4G,EACjJ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,EAAE,CAClE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,eAAe,CAAC,2BAA2B,EAAE,YAAY,QAAQ,CAAC,MAAM,6CAA6C,CAAC,CAAC;IAChI,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,IAAI,CACrB,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,EACvC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACV,QAAQ,EAAE,UAAmB;QAC7B,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,8CAA8C;QACvD,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;KACtB,CAAC,CACH,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9E,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IACxC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC;IAEhD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC7F,MAAM,aAAa,GAAyB;QAC1C,IAAI,EAAE,KAAK;QACX,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,QAAQ,CAAC,MAAM;QACtB,UAAU,EAAE,GAAG;QACf,WAAW,EAAE,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/E,KAAK,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAE,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,CAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE;KACzF,CAAC;IAEF,MAAM,SAAS,GAAG,MAAM,IAAI,CAC1B,KAAK,IAAI,EAAE,CACT,OAAO,CAAC,MAAM,CAAC,aAAa,CAC1B,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,EACnE,iCAAiC,EACjC;QACE,IAAI,EAAE,aAAa,CAAC,IAAI;QACxB,EAAE,EAAE,aAAa,CAAC,EAAE;QACpB,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC;QAClC,UAAU,EAAE,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC;QAC5C,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC;QAC9C,KAAK,EAAE,aAAa,CAAC,KAAK;KAC3B,CACF,EACH,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACV,QAAQ,EAAE,UAAmB;QAC7B,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,4DAA4D;QACrE,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;KACtB,CAAC,CACH,CAAC;IACF,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,OAAO,SAAS,CAAC;IAEpC,MAAM,OAAO,GAAmB;QAC9B,WAAW,EAAE,YAAY;QACzB,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ;QACnC,QAAQ;QACR,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,KAAK,EAAE,aAAa,EAAE;QACtD,wEAAwE;QACxE,yEAAyE;QACzE,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;KAClG,CAAC;IACF,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,EAAa,EAAE,CAAC,CAAC;AACvG,CAAC;AAED,gGAAgG;AAChG,SAAS,aAAa,CAAC,IAAY,EAAE,KAAa,EAAE,KAAa;IAC/D,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI;QAC3B,CAAC,gBAAgB,EAAE,IAAI,CAAC;QACxB,CAAC,OAAO,EAAE,KAAK,CAAC;QAChB,CAAC,OAAO,EAAE,KAAK,CAAC;KACR,EAAE,CAAC;QACX,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,eAAe,CAAC,2BAA2B,EAAE,GAAG,KAAK,MAAM,KAAK,6BAA6B,EAAE;gBACpG,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;aACjC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC,OAAmC,CAAC,CAAC;AACjD,CAAC;AA4ED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAA+B;IAE/B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;IAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAyB,EAAE,CAAC;IACzC,MAAM,MAAM,GAAsB;QAChC,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QACtE,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QAChE,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;KAC7E,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAE,IAAmB,EAAE,OAAc,EAAwB,EAAE;QAC5F,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,IAAI,SAAS,EAAO,CAAC;QACnD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,aAAa,CAAC,mBAAmB,EAAE,gFAAgF,CAAC,CAAC;QAC9H,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CACrB,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAC3B,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACV,QAAQ,EAAE,UAAmB;YAC7B,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,kBAAkB,GAAG,mBAAmB;YACjD,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE;YACzB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;SACtB,CAAC,CACH,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC1B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,aAAa,CAAC,wBAAwB,EAAE,gFAAgF,GAAG,EAAE,EAAE;gBACpI,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE;aAC1B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,KAAK,EAAE,GAAW,EAAE,IAAmB,EAAE,QAAyB,EAAyC,EAAE;QACvH,MAAM,QAAQ,GAAG,sBAAsB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC;QAElC,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,MAAM,IAAI,CACxB,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,EACzD,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBACV,QAAQ,EAAE,UAAmB;gBAC7B,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,iDAAiD;gBAC1D,SAAS,EAAE,KAAK;gBAChB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;aACtB,CAAC,CACH,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,EAAE;gBAAE,OAAO,OAAO,CAAC;YAChC,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAAE,OAAO,OAAO,CAAC,KAAK,CAAC;QAC7E,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YAChC,OAAO,eAAe,CACpB,6BAA6B,EAC7B,GAAG,GAAG,wHAAwH,EAC9H,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,CACrB,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC;YACpC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ;YACR,QAAQ,EAAE,QAAQ,CAAC,KAAK;YACxB,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;YACxE,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;SACjE,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAE9B,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAE9B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACzH,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;QAE5B,MAAM,UAAU,GAAG,mBAAmB,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QACzE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC1B,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC1B,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,WAAW,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,4BAA4B,CAAC;YAC5G,OAAO,IAAI,CAAC,OAAO,EAAE,uBAAuB,EAAE,GAAG,GAAG,gCAAgC,MAAM,EAAE,EAAE;gBAC5F,SAAS,EAAE,KAAK;gBAChB,OAAO,EAAE;oBACP,GAAG;oBACH,MAAM;oBACN,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;oBAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE;oBACzC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO;iBAChC;aACF,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAuB;YAClC,MAAM,EAAE,UAAU,CAAC,KAAK,EAAE,WAAW,IAAI,EAAE;YAC3C,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO;YAC/B,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;YAC7B,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;YACrC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAa;YACpD,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,EAAa;YACpD,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK;YACzB,GAAG;YACH,WAAW,EAAE,QAAQ,CAAC,KAAK;YAC3B,UAAU,EAAE,UAAU,CAAC,KAAK,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE;YACpH,UAAU,EAAE,GAAG,EAAE;SAClB,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,qDAAqD,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7G,CAAC;QACH,CAAC;QACD,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE;YACrC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,CAAC,EAAE;gBAAE,OAAO,KAAK,CAAC;YAC5B,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YAErE,MAAM,QAAQ,GAAG,mBAAmB,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1E,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC7B,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,OAAO,QAAQ,CAAC;YAClC,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACjC,OAAO,IAAI,CAAC,OAAO,EAAE,+BAA+B,EAAE,GAAG,GAAG,2BAA2B,WAAW,CAAC,eAAe,qCAAqC,EAAE;oBACvJ,SAAS,EAAE,KAAK;oBAChB,OAAO,EAAE,EAAE,GAAG,EAAE;iBACjB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;QACD,GAAG;QACH,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC;KAC7B,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,SAAS,SAAS,CAAC,OAAoC;IACrD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC9C,OAAO,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAgD,CAAC;IACnE,OAAO,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,IAAI,SAAS,CAAC,OAAO,KAAK,IAAI,CAAC;AACrH,CAAC;AAED,0DAA0D;AAC1D,SAAS,YAAY,CAAC,IAAa;IACjC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACjF,MAAM,SAAS,GAAG,IAAiE,CAAC;IACpF,OAAO,OAAO,SAAS,CAAC,SAAS,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,UAAU,CAAC;AAC5G,CAAC;AAED,oGAAoG;AACpG,SAAS,OAAO,CAAC,QAAsB,EAAE,MAAc;IACrD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAA+C,CAAC;IACtE,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,QAAQ,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO;IACnH,IAAI,CAAC;QACH,MAAM,SAAS,GAAI,IAAI,CAAC,MAAwB,EAAE,CAAC;QACnD,IAAI,OAAQ,SAAiC,EAAE,KAAK,KAAK,UAAU,EAAE,CAAC;YACnE,SAA8B,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACvD,MAAM,CAAC,KAAK,CAAC,gDAAgD,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACjF,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,gDAAgD,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACjF,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IAChB,MAAM,SAAS,GAAI,UAAkC,CAAC,KAAK,CAAC;IAC5D,OAAO,OAAO,SAAS,KAAK,UAAU,CAAC,CAAC,CAAE,SAA4B,CAAC,CAAC,CAAC,SAAS,CAAC;AACrF,CAAC","sourcesContent":["/**\n * The x402 client: sign an EIP-3009 authorization for an `exact` requirement\n * and repeat the request with it.\n *\n * ## Where this sits in Tab\n *\n * Tab never asks an Agent to prepay. This client exists for the two moments a\n * prepayment is the right answer anyway: an Agent with no headroom that would\n * rather pay for one call than settle a tab first, and a Service fronting an\n * x402 upstream that has to pay the upstream before it can meter the Agent.\n * In both, the payment is one call's worth, signed by whoever holds the key,\n * and nothing about the Open Tab changes.\n *\n * ## The signing is hand-rolled, and here is the check on it\n *\n * `@x402/evm` is the reference client for this scheme and it is built on viem.\n * This package signs with ethers, so pulling it in would ship a second EVM\n * library to sign one struct. The struct is small enough to state:\n *\n * - EIP-712 domain `{ name, version, chainId, verifyingContract }`, with `name`\n * and `version` from `requirements.extra`, `chainId` from the CAIP-2 network,\n * and `verifyingContract` the token (`scheme_exact_evm.md`, section 1);\n * - primary type `TransferWithAuthorization(address from, address to, uint256\n * value, uint256 validAfter, uint256 validBefore, bytes32 nonce)` (EIP-3009);\n * - `validAfter` `0`, `validBefore` now plus `maxTimeoutSeconds`, and a random\n * 32-byte `nonce`, exactly as the reference `createEIP3009Payload` builds them.\n *\n * The test suite recovers the signer from every signature this file produces,\n * and the reference client was run over the same authorization while this was\n * written: the two signatures are byte for byte the same.\n *\n * Specification: scheme_exact_evm.md section 1, x402-specification-v2.md section 5.2.\n */\n\nimport { getAddress, hexlify, randomBytes, type Signer } from \"ethers\";\nimport { causeOf, ok, wrap, type Address, type Hex, type Result } from \"../_shared/index.js\";\n\nimport { fail, upstreamError, validationError } from \"../errors.js\";\nimport { headerReaderOf, type HeaderReader, type HeaderRecord } from \"../http/headers.js\";\nimport { defaultLogger, type Logger } from \"../logger.js\";\nimport {\n X402_HEADER,\n X402_VERSION,\n chainIdOfNetwork,\n encodePaymentSignature,\n readPaymentRequired,\n readPaymentResponse,\n selectExactRequirement,\n type PaymentPayload,\n type PaymentRequired,\n type PaymentRequirements,\n type RequirementFilter,\n type SettleResponse,\n} from \"./wire.js\";\n\n/**\n * What signs an x402 payment.\n *\n * Structural rather than the `ethers` class, so a test double or any wallet\n * that can sign typed data satisfies it. An `ethers.Signer` does as it stands;\n * the assertion below fails to compile if that ever stops being true.\n */\nexport interface X402Signer {\n getAddress(): Promise<string>;\n signTypedData(\n domain: { name: string; version: string; chainId: bigint | number; verifyingContract: string },\n types: Record<string, readonly { name: string; type: string }[]>,\n value: Record<string, unknown>,\n ): Promise<string>;\n}\n\ntype Assert<T extends true> = T;\ntype SignerIsAccepted = Assert<Signer extends X402Signer ? true : false>;\nexport type X402SignerAcceptsEthersSigner = SignerIsAccepted;\n\n/**\n * A signer built only when a payment is about to be signed.\n *\n * Returning `undefined` means \"not available here\", the same answer a payment\n * strategy factory may give: the offer is declined and the `402` stands as the\n * credit decision it was. That is what keeps every read keyless: a config file\n * declares the factory, and nothing calls it until a prepayment is the answer.\n */\nexport type X402SignerFactory = () => X402Signer | undefined | Promise<X402Signer | undefined>;\n\n/** A signer, or a factory that may produce one. */\nexport type X402SignerSource = X402Signer | X402SignerFactory;\n\n/** Resolves a source. `ok(undefined)` is a factory declining; `err` is a factory that threw. */\nexport async function resolveX402Signer(source: X402SignerSource | undefined): Promise<Result<X402Signer | undefined>> {\n if (source === undefined) return ok(undefined);\n if (typeof source !== \"function\") return ok(source);\n const produced = await wrap(\n async () => (await source()) as unknown,\n (error) => ({\n category: \"UPSTREAM\" as const,\n code: \"X402_SIGNER_FACTORY_FAILED\",\n message: \"the x402 signer factory threw\",\n retryable: false,\n cause: causeOf(error),\n }),\n );\n if (!produced.ok) return produced;\n if (produced.value === undefined || produced.value === null) return ok(undefined);\n const candidate = produced.value as Partial<X402Signer>;\n if (typeof candidate.getAddress !== \"function\" || typeof candidate.signTypedData !== \"function\") {\n return validationError(\"X402_SIGNER_INVALID\", \"the x402 signer factory returned something without getAddress() and signTypedData()\");\n }\n return ok(candidate as X402Signer);\n}\n\n/** The EIP-712 type of the struct EIP-3009 `transferWithAuthorization` verifies. */\nexport const TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n} as const;\n\n/** The authorization as it goes on the wire: every number a decimal string. */\nexport interface Eip3009Authorization {\n readonly from: string;\n readonly to: string;\n readonly value: string;\n readonly validAfter: string;\n readonly validBefore: string;\n readonly nonce: Hex;\n}\n\nexport interface SignExactPaymentOptions {\n readonly signer: X402Signer;\n /** The server's `PaymentRequired`, echoed into the payload as `resource` and `extensions`. */\n readonly required: PaymentRequired;\n /** The entry of `required.accepts` being paid. */\n readonly accepted: PaymentRequirements;\n /** Seconds since the epoch. Defaults to the wall clock. */\n readonly now?: () => number;\n /** A 32-byte nonce. Defaults to random. A test passes a fixed one. */\n readonly nonce?: () => Hex;\n}\n\n/** What a signature is for, kept beside the payload so a receipt can be built without re-reading it. */\nexport interface SignedExactPayment {\n readonly payload: PaymentPayload;\n readonly authorization: Eip3009Authorization;\n readonly chainId: bigint;\n readonly payer: Address;\n}\n\n/**\n * Signs one `exact` requirement.\n *\n * The domain and the struct are exactly what the reference client signs, so\n * the facilitator's recovery lands on `authorization.from`. The requirement is\n * re-validated here rather than trusted, because it arrived over the network.\n */\nexport async function signExactPayment(options: SignExactPaymentOptions): Promise<Result<SignedExactPayment>> {\n const { accepted } = options;\n const chainId = chainIdOfNetwork(accepted.network);\n if (!chainId.ok) return chainId;\n const name = accepted.extra?.[\"name\"];\n const version = accepted.extra?.[\"version\"];\n if (typeof name !== \"string\" || typeof version !== \"string\") {\n return validationError(\n \"X402_DOMAIN_MISSING\",\n `the requirement for ${accepted.asset} carries no EIP-712 domain name and version in extra, so an EIP-3009 authorization cannot be signed for it`,\n { details: { asset: accepted.asset, network: accepted.network } },\n );\n }\n if (!/^[0-9]+$/.test(accepted.amount)) {\n return validationError(\"X402_REQUIREMENTS_INVALID\", `amount \\`${accepted.amount}\\` is not a decimal integer of atomic units`);\n }\n\n const from = await wrap(\n async () => options.signer.getAddress(),\n (error) => ({\n category: \"UPSTREAM\" as const,\n code: \"X402_SIGNER_UNAVAILABLE\",\n message: \"the x402 signer could not report its address\",\n retryable: false,\n cause: causeOf(error),\n }),\n );\n if (!from.ok) return from;\n\n const checksummed = wrapAddresses(from.value, accepted.payTo, accepted.asset);\n if (!checksummed.ok) return checksummed;\n const [payer, payTo, asset] = checksummed.value;\n\n const nowSeconds = options.now === undefined ? Math.floor(Date.now() / 1000) : options.now();\n const authorization: Eip3009Authorization = {\n from: payer,\n to: payTo,\n value: accepted.amount,\n validAfter: \"0\",\n validBefore: (nowSeconds + Math.floor(accepted.maxTimeoutSeconds)).toString(10),\n nonce: options.nonce === undefined ? (hexlify(randomBytes(32)) as Hex) : options.nonce(),\n };\n\n const signature = await wrap(\n async () =>\n options.signer.signTypedData(\n { name, version, chainId: chainId.value, verifyingContract: asset },\n TRANSFER_WITH_AUTHORIZATION_TYPES,\n {\n from: authorization.from,\n to: authorization.to,\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce,\n },\n ),\n (error) => ({\n category: \"UPSTREAM\" as const,\n code: \"X402_SIGNING_FAILED\",\n message: \"the x402 signer refused to sign the EIP-3009 authorization\",\n retryable: false,\n cause: causeOf(error),\n }),\n );\n if (!signature.ok) return signature;\n\n const payload: PaymentPayload = {\n x402Version: X402_VERSION,\n resource: options.required.resource,\n accepted,\n payload: { signature: signature.value, authorization },\n // Echoed unchanged, as the reference client does: the server advertised\n // them and validates that what comes back carries at least what it sent.\n ...(options.required.extensions === undefined ? {} : { extensions: options.required.extensions }),\n };\n return ok({ payload, authorization, chainId: chainId.value, payer: payer.toLowerCase() as Address });\n}\n\n/** Checksums the three addresses a payload carries, or names the one that is not an address. */\nfunction wrapAddresses(from: string, payTo: string, asset: string): Result<[string, string, string]> {\n const checked: string[] = [];\n for (const [label, value] of [\n [\"signer address\", from],\n [\"payTo\", payTo],\n [\"asset\", asset],\n ] as const) {\n try {\n checked.push(getAddress(value));\n } catch {\n return validationError(\"X402_REQUIREMENTS_INVALID\", `${label} \\`${value}\\` is not a 20-byte address`, {\n details: { field: label, value },\n });\n }\n }\n return ok(checked as [string, string, string]);\n}\n\n/** One payment this client made, as the facilitator reported it back. */\nexport interface X402PaymentReceipt {\n /** The settlement transaction hash. Empty when the server reported success without one. */\n readonly txHash: string;\n readonly network: string;\n readonly chainId: bigint;\n /** Atomic units of `asset`, as the requirement priced it. */\n readonly amount: bigint;\n readonly asset: Address;\n readonly payTo: Address;\n readonly payer: Address;\n /** The absolute URL the payment bought. */\n readonly url: string;\n readonly requirement: PaymentRequirements;\n readonly settlement: SettleResponse;\n readonly observedAt: number;\n}\n\n/** The little of a response this client reads. Structural, as in `client-402.ts`. */\nexport interface X402Response {\n readonly status: number;\n readonly headers: HeaderReader | HeaderRecord;\n readonly body?: unknown;\n readonly bodyUsed?: unknown;\n}\n\nexport interface X402FetchInit {\n readonly headers: Readonly<Record<string, string>>;\n readonly [option: string]: unknown;\n}\n\nexport type X402Fetch<Res extends X402Response> = (url: string, init: X402FetchInit) => Promise<Res>;\n\nexport interface X402ClientOptions<Res extends X402Response = X402Response> extends RequirementFilter {\n readonly signer: X402Signer;\n readonly fetchImpl?: X402Fetch<Res>;\n /**\n * Consulted after the server's `402` and before anything is signed. A refusal\n * stops the payment and is returned as the error. This is where a Service\n * fronting an upstream checks that the Agent it will meter has the headroom\n * before the Service's own funds move.\n */\n readonly authorise?: (requirement: PaymentRequirements, required: PaymentRequired) => Promise<Result<void>> | Result<void>;\n readonly onPayment?: (receipt: X402PaymentReceipt) => void;\n readonly logger?: Logger;\n /** Milliseconds since the epoch, for `observedAt`. Defaults to `Date.now`. */\n readonly now?: () => number;\n /** Seconds since the epoch, for `validBefore`. Defaults to the wall clock. */\n readonly nowSeconds?: () => number;\n readonly nonce?: () => Hex;\n}\n\n/** What one call did: the response, and the payment that bought it if one did. */\nexport interface X402FetchResult<Res extends X402Response = X402Response> {\n readonly response: Res;\n readonly payment?: X402PaymentReceipt;\n}\n\nexport interface X402Client<Res extends X402Response = X402Response> {\n /**\n * Sends the request; on a `402` carrying `PAYMENT-REQUIRED`, signs and sends\n * it once more with `PAYMENT-SIGNATURE`. Exactly once: a second `402` means\n * the payment was refused, and that is returned as an error naming why.\n */\n fetch(url: string, init?: X402FetchInit): Promise<Result<X402FetchResult<Res>>>;\n /**\n * Pays a `402` the caller already holds: the same second step, for a caller\n * that made the first request itself.\n */\n pay(url: string, init: X402FetchInit, required: PaymentRequired): Promise<Result<X402FetchResult<Res>>>;\n /** Every payment made, oldest first. */\n payments(): readonly X402PaymentReceipt[];\n}\n\n/**\n * Builds the client. Construction is total; every failure belongs to a call.\n */\nexport function createX402Client<Res extends X402Response = X402Response>(\n options: X402ClientOptions<Res>,\n): X402Client<Res> {\n const logger = options.logger ?? defaultLogger;\n const now = options.now ?? (() => Date.now());\n const history: X402PaymentReceipt[] = [];\n const filter: RequirementFilter = {\n ...(options.chainId === undefined ? {} : { chainId: options.chainId }),\n ...(options.asset === undefined ? {} : { asset: options.asset }),\n ...(options.maxAmount === undefined ? {} : { maxAmount: options.maxAmount }),\n };\n\n const send = async (url: string, init: X402FetchInit, attempt: 1 | 2): Promise<Result<Res>> => {\n const impl = options.fetchImpl ?? hostFetch<Res>();\n if (impl === undefined) {\n return upstreamError(\"FETCH_UNAVAILABLE\", \"this host has no global fetch; supply fetchImpl, or run on Node 20.10 or later\");\n }\n const sent = await wrap(\n async () => impl(url, init),\n (error) => ({\n category: \"UPSTREAM\" as const,\n code: \"FETCH_FAILED\",\n message: `the request to ${url} did not complete`,\n retryable: true,\n details: { url, attempt },\n cause: causeOf(error),\n }),\n );\n if (!sent.ok) return sent;\n if (!looksLikeResponse(sent.value)) {\n return upstreamError(\"FETCH_RESPONSE_INVALID\", `the fetch given to this client returned something that is not a response for ${url}`, {\n details: { url, attempt },\n });\n }\n return ok(sent.value);\n };\n\n const pay = async (url: string, init: X402FetchInit, required: PaymentRequired): Promise<Result<X402FetchResult<Res>>> => {\n const accepted = selectExactRequirement(required, filter);\n if (!accepted.ok) return accepted;\n\n if (options.authorise !== undefined) {\n const allowed = await wrap(\n async () => options.authorise?.(accepted.value, required),\n (error) => ({\n category: \"INTERNAL\" as const,\n code: \"X402_AUTHORISE_THREW\",\n message: \"the authorise hook threw, so nothing was signed\",\n retryable: false,\n cause: causeOf(error),\n }),\n );\n if (!allowed.ok) return allowed;\n if (allowed.value !== undefined && !allowed.value.ok) return allowed.value;\n }\n\n if (!isReplayable(init[\"body\"])) {\n return validationError(\n \"REQUEST_BODY_NOT_REPLAYABLE\",\n `${url} asks for payment and the request body is a stream, which cannot be sent a second time; buffer the body before calling`,\n { details: { url } },\n );\n }\n\n const signed = await signExactPayment({\n signer: options.signer,\n required,\n accepted: accepted.value,\n ...(options.nowSeconds === undefined ? {} : { now: options.nowSeconds }),\n ...(options.nonce === undefined ? {} : { nonce: options.nonce }),\n });\n if (!signed.ok) return signed;\n\n const header = encodePaymentSignature(signed.value.payload);\n if (!header.ok) return header;\n\n const paid = await send(url, { ...init, headers: { ...init.headers, [X402_HEADER.paymentSignature]: header.value } }, 2);\n if (!paid.ok) return paid;\n const response = paid.value;\n\n const settlement = readPaymentResponse(headerReaderOf(response.headers));\n if (!settlement.ok) {\n discard(response, logger);\n return settlement;\n }\n\n if (response.status === 402) {\n discard(response, logger);\n const reason = settlement.value?.errorReason ?? readError(response.headers) ?? \"the server did not say why\";\n return fail(\"LIMIT\", \"X402_PAYMENT_REJECTED\", `${url} refused the signed payment: ${reason}`, {\n retryable: false,\n details: {\n url,\n reason,\n amount: accepted.value.amount,\n asset: accepted.value.asset.toLowerCase(),\n network: accepted.value.network,\n },\n });\n }\n\n const receipt: X402PaymentReceipt = {\n txHash: settlement.value?.transaction ?? \"\",\n network: accepted.value.network,\n chainId: signed.value.chainId,\n amount: BigInt(accepted.value.amount),\n asset: accepted.value.asset.toLowerCase() as Address,\n payTo: accepted.value.payTo.toLowerCase() as Address,\n payer: signed.value.payer,\n url,\n requirement: accepted.value,\n settlement: settlement.value ?? { success: response.status < 400, transaction: \"\", network: accepted.value.network },\n observedAt: now(),\n };\n history.push(receipt);\n if (options.onPayment !== undefined) {\n try {\n options.onPayment(receipt);\n } catch (error) {\n logger.warn(\"onPayment threw and the payment was recorded anyway\", { url, error: causeOf(error).message });\n }\n }\n return ok({ response, payment: receipt });\n };\n\n return {\n async fetch(url, init = { headers: {} }) {\n const first = await send(url, init, 1);\n if (!first.ok) return first;\n if (first.value.status !== 402) return ok({ response: first.value });\n\n const required = readPaymentRequired(headerReaderOf(first.value.headers));\n discard(first.value, logger);\n if (!required.ok) return required;\n if (required.value === undefined) {\n return fail(\"LIMIT\", \"X402_PAYMENT_REQUIRED_MISSING\", `${url} answered 402 without a ${X402_HEADER.paymentRequired} header, so there is nothing to pay`, {\n retryable: false,\n details: { url },\n });\n }\n return pay(url, init, required.value);\n },\n pay,\n payments: () => [...history],\n };\n}\n\n/** The `error` field of a `PAYMENT-REQUIRED` a refusal may carry, when it carries one. */\nfunction readError(headers: HeaderReader | HeaderRecord): string | undefined {\n const required = readPaymentRequired(headers);\n return required.ok ? required.value?.error : undefined;\n}\n\nfunction looksLikeResponse(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const candidate = value as { status?: unknown; headers?: unknown };\n return typeof candidate.status === \"number\" && typeof candidate.headers === \"object\" && candidate.headers !== null;\n}\n\n/** A stream cannot be sent twice; everything else can. */\nfunction isReplayable(body: unknown): boolean {\n if (body === undefined || body === null || typeof body !== \"object\") return true;\n const candidate = body as { getReader?: unknown; [Symbol.asyncIterator]?: unknown };\n return typeof candidate.getReader !== \"function\" && typeof candidate[Symbol.asyncIterator] !== \"function\";\n}\n\n/** Releases a response that is not going out, so its connection does not wait for the collector. */\nfunction discard(response: X402Response, logger: Logger): void {\n const body = response.body as { cancel?: unknown } | null | undefined;\n if (body === null || body === undefined || typeof body.cancel !== \"function\" || response.bodyUsed === true) return;\n try {\n const cancelled = (body.cancel as () => unknown)();\n if (typeof (cancelled as { catch?: unknown })?.catch === \"function\") {\n (cancelled as Promise<unknown>).catch((error: unknown) => {\n logger.debug(\"discarded response body could not be cancelled\", causeOf(error));\n });\n }\n } catch (error) {\n logger.debug(\"discarded response body could not be cancelled\", causeOf(error));\n }\n}\n\nfunction hostFetch<Res extends X402Response>(): X402Fetch<Res> | undefined {\n const candidate = (globalThis as { fetch?: unknown }).fetch;\n return typeof candidate === \"function\" ? (candidate as X402Fetch<Res>) : undefined;\n}\n"]}
|