@reinconsole/x402-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/index.d.ts +507 -0
- package/dist/index.js +808 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rein contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @reinconsole/x402-rails
|
|
2
|
+
|
|
3
|
+
The real payment rails for **[Rein](https://github.com/bugiiiii11/rein)** — [x402](https://www.x402.org) on Base Sepolia. An EIP-3009 payer that signs real USDC payments (gasless for the agent — the facilitator submits the tx), an HTTP client for the hosted x402.org facilitator, a strict x402-v1 in-process vendor, and an on-chain indexer that reconciles USDC transfers back to the exact intents the policy engine allowed.
|
|
4
|
+
|
|
5
|
+
> **Status: v0.1 — early open-source infrastructure, live on testnet.** Real USDC settled on Base Sepolia through the hosted facilitator — no API key needed. APIs may change before 1.0.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @reinconsole/x402-rails
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What's in it
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {
|
|
17
|
+
createX402Payer, // EIP-3009/EIP-712 signer — plugs into @reinconsole/sdk's guard as its `payer`
|
|
18
|
+
FacilitatorClient, // verify/settle against the hosted x402.org facilitator
|
|
19
|
+
OnchainIndexer, // getLogs polling; reconciles transfers to intents via the nonce memo
|
|
20
|
+
createRealVendor, // strict x402-v1 in-process vendor
|
|
21
|
+
intentNonce, // keccak256(intent.id) — the on-chain memo
|
|
22
|
+
generateWallet, createBaseSepoliaClient, getUsdcBalance, // wallet + chain helpers
|
|
23
|
+
} from '@reinconsole/x402-rails';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **Gasless for the agent.** The payer signs an EIP-3009 `transferWithAuthorization`; the facilitator submits the transaction and pays gas. A funded USDC balance is all the agent wallet needs ([free testnet USDC](https://faucet.circle.com)).
|
|
27
|
+
- **An on-chain memo, no fuzzy matching.** The authorization nonce is derived as `keccak256(intent.id)`; USDC emits it back in `AuthorizationUsed` on settlement, so the indexer reconciles each transfer to the exact intent the engine allowed — and flags everything else from managed wallets as **shadow spend**.
|
|
28
|
+
- **The hosted facilitator, for free.** `FacilitatorClient` speaks the x402.org dialect (`DEFAULT_FACILITATOR_URL`, no API key). The same client powers [`@reinconsole/gate`](https://www.npmjs.com/package/@reinconsole/gate)'s real-rails settlement via `facilitatorClientRails`.
|
|
29
|
+
- **Wire schemas included** — payment payloads, verify/settle responses, header codecs; all zod-validated.
|
|
30
|
+
|
|
31
|
+
Behind a TLS-intercepting proxy or antivirus, point Node at your local root CA (`NODE_EXTRA_CA_CERTS`) before any live run.
|
|
32
|
+
|
|
33
|
+
The [monorepo](https://github.com/bugiiiii11/rein)'s Sepolia demos run this end to end — a guarded $0.01 payment settled on-chain, then a guard-bypassing payment caught by the indexer.
|
|
34
|
+
|
|
35
|
+
MIT © Rein contributors · [Repository](https://github.com/bugiiiii11/rein) · [Issues](https://github.com/bugiiiii11/rein/issues)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Hex, Address, PublicClient, HttpTransport } from 'viem';
|
|
3
|
+
import { Payer, FetchLike, PaymentRequirement } from '@reinconsole/sdk';
|
|
4
|
+
import { ReinEvent, Chain, Agent } from '@reinconsole/core';
|
|
5
|
+
import { baseSepolia } from 'viem/chains';
|
|
6
|
+
|
|
7
|
+
/** The EIP-3009 TransferWithAuthorization message, decimal-stringified. */
|
|
8
|
+
declare const ExactEvmAuthorization: z.ZodObject<{
|
|
9
|
+
from: z.ZodString;
|
|
10
|
+
to: z.ZodString;
|
|
11
|
+
/** Atomic-unit amount, mirroring the requirement's maxAmountRequired. */
|
|
12
|
+
value: z.ZodString;
|
|
13
|
+
validAfter: z.ZodString;
|
|
14
|
+
validBefore: z.ZodString;
|
|
15
|
+
/** bytes32 — Rein derives it from the intent id (see nonce.ts). */
|
|
16
|
+
nonce: z.ZodString;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
from: string;
|
|
19
|
+
to: string;
|
|
20
|
+
value: string;
|
|
21
|
+
validAfter: string;
|
|
22
|
+
validBefore: string;
|
|
23
|
+
nonce: string;
|
|
24
|
+
}, {
|
|
25
|
+
from: string;
|
|
26
|
+
to: string;
|
|
27
|
+
value: string;
|
|
28
|
+
validAfter: string;
|
|
29
|
+
validBefore: string;
|
|
30
|
+
nonce: string;
|
|
31
|
+
}>;
|
|
32
|
+
type ExactEvmAuthorization = z.infer<typeof ExactEvmAuthorization>;
|
|
33
|
+
declare const ExactEvmPayload: z.ZodObject<{
|
|
34
|
+
/** 65-byte EIP-712 signature over the authorization. */
|
|
35
|
+
signature: z.ZodString;
|
|
36
|
+
authorization: z.ZodObject<{
|
|
37
|
+
from: z.ZodString;
|
|
38
|
+
to: z.ZodString;
|
|
39
|
+
/** Atomic-unit amount, mirroring the requirement's maxAmountRequired. */
|
|
40
|
+
value: z.ZodString;
|
|
41
|
+
validAfter: z.ZodString;
|
|
42
|
+
validBefore: z.ZodString;
|
|
43
|
+
/** bytes32 — Rein derives it from the intent id (see nonce.ts). */
|
|
44
|
+
nonce: z.ZodString;
|
|
45
|
+
}, "strip", z.ZodTypeAny, {
|
|
46
|
+
from: string;
|
|
47
|
+
to: string;
|
|
48
|
+
value: string;
|
|
49
|
+
validAfter: string;
|
|
50
|
+
validBefore: string;
|
|
51
|
+
nonce: string;
|
|
52
|
+
}, {
|
|
53
|
+
from: string;
|
|
54
|
+
to: string;
|
|
55
|
+
value: string;
|
|
56
|
+
validAfter: string;
|
|
57
|
+
validBefore: string;
|
|
58
|
+
nonce: string;
|
|
59
|
+
}>;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
signature: string;
|
|
62
|
+
authorization: {
|
|
63
|
+
from: string;
|
|
64
|
+
to: string;
|
|
65
|
+
value: string;
|
|
66
|
+
validAfter: string;
|
|
67
|
+
validBefore: string;
|
|
68
|
+
nonce: string;
|
|
69
|
+
};
|
|
70
|
+
}, {
|
|
71
|
+
signature: string;
|
|
72
|
+
authorization: {
|
|
73
|
+
from: string;
|
|
74
|
+
to: string;
|
|
75
|
+
value: string;
|
|
76
|
+
validAfter: string;
|
|
77
|
+
validBefore: string;
|
|
78
|
+
nonce: string;
|
|
79
|
+
};
|
|
80
|
+
}>;
|
|
81
|
+
type ExactEvmPayload = z.infer<typeof ExactEvmPayload>;
|
|
82
|
+
/** The full X-PAYMENT header body (x402 spec v1 envelope). */
|
|
83
|
+
declare const PaymentPayload: z.ZodObject<{
|
|
84
|
+
x402Version: z.ZodLiteral<1>;
|
|
85
|
+
scheme: z.ZodString;
|
|
86
|
+
network: z.ZodString;
|
|
87
|
+
payload: z.ZodObject<{
|
|
88
|
+
/** 65-byte EIP-712 signature over the authorization. */
|
|
89
|
+
signature: z.ZodString;
|
|
90
|
+
authorization: z.ZodObject<{
|
|
91
|
+
from: z.ZodString;
|
|
92
|
+
to: z.ZodString;
|
|
93
|
+
/** Atomic-unit amount, mirroring the requirement's maxAmountRequired. */
|
|
94
|
+
value: z.ZodString;
|
|
95
|
+
validAfter: z.ZodString;
|
|
96
|
+
validBefore: z.ZodString;
|
|
97
|
+
/** bytes32 — Rein derives it from the intent id (see nonce.ts). */
|
|
98
|
+
nonce: z.ZodString;
|
|
99
|
+
}, "strip", z.ZodTypeAny, {
|
|
100
|
+
from: string;
|
|
101
|
+
to: string;
|
|
102
|
+
value: string;
|
|
103
|
+
validAfter: string;
|
|
104
|
+
validBefore: string;
|
|
105
|
+
nonce: string;
|
|
106
|
+
}, {
|
|
107
|
+
from: string;
|
|
108
|
+
to: string;
|
|
109
|
+
value: string;
|
|
110
|
+
validAfter: string;
|
|
111
|
+
validBefore: string;
|
|
112
|
+
nonce: string;
|
|
113
|
+
}>;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
signature: string;
|
|
116
|
+
authorization: {
|
|
117
|
+
from: string;
|
|
118
|
+
to: string;
|
|
119
|
+
value: string;
|
|
120
|
+
validAfter: string;
|
|
121
|
+
validBefore: string;
|
|
122
|
+
nonce: string;
|
|
123
|
+
};
|
|
124
|
+
}, {
|
|
125
|
+
signature: string;
|
|
126
|
+
authorization: {
|
|
127
|
+
from: string;
|
|
128
|
+
to: string;
|
|
129
|
+
value: string;
|
|
130
|
+
validAfter: string;
|
|
131
|
+
validBefore: string;
|
|
132
|
+
nonce: string;
|
|
133
|
+
};
|
|
134
|
+
}>;
|
|
135
|
+
}, "strip", z.ZodTypeAny, {
|
|
136
|
+
x402Version: 1;
|
|
137
|
+
scheme: string;
|
|
138
|
+
network: string;
|
|
139
|
+
payload: {
|
|
140
|
+
signature: string;
|
|
141
|
+
authorization: {
|
|
142
|
+
from: string;
|
|
143
|
+
to: string;
|
|
144
|
+
value: string;
|
|
145
|
+
validAfter: string;
|
|
146
|
+
validBefore: string;
|
|
147
|
+
nonce: string;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
}, {
|
|
151
|
+
x402Version: 1;
|
|
152
|
+
scheme: string;
|
|
153
|
+
network: string;
|
|
154
|
+
payload: {
|
|
155
|
+
signature: string;
|
|
156
|
+
authorization: {
|
|
157
|
+
from: string;
|
|
158
|
+
to: string;
|
|
159
|
+
value: string;
|
|
160
|
+
validAfter: string;
|
|
161
|
+
validBefore: string;
|
|
162
|
+
nonce: string;
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
}>;
|
|
166
|
+
type PaymentPayload = z.infer<typeof PaymentPayload>;
|
|
167
|
+
/** Facilitator POST /verify response. Reason strings stay lenient on purpose. */
|
|
168
|
+
declare const VerifyResponse: z.ZodObject<{
|
|
169
|
+
isValid: z.ZodBoolean;
|
|
170
|
+
invalidReason: z.ZodOptional<z.ZodString>;
|
|
171
|
+
payer: z.ZodOptional<z.ZodString>;
|
|
172
|
+
}, "strip", z.ZodTypeAny, {
|
|
173
|
+
isValid: boolean;
|
|
174
|
+
invalidReason?: string | undefined;
|
|
175
|
+
payer?: string | undefined;
|
|
176
|
+
}, {
|
|
177
|
+
isValid: boolean;
|
|
178
|
+
invalidReason?: string | undefined;
|
|
179
|
+
payer?: string | undefined;
|
|
180
|
+
}>;
|
|
181
|
+
type VerifyResponse = z.infer<typeof VerifyResponse>;
|
|
182
|
+
/** Facilitator POST /settle response (also what X-PAYMENT-RESPONSE carries). */
|
|
183
|
+
declare const SettleResponse: z.ZodObject<{
|
|
184
|
+
success: z.ZodBoolean;
|
|
185
|
+
errorReason: z.ZodOptional<z.ZodString>;
|
|
186
|
+
payer: z.ZodOptional<z.ZodString>;
|
|
187
|
+
/** The on-chain tx hash (the guard surfaces this on the receipt). */
|
|
188
|
+
transaction: z.ZodString;
|
|
189
|
+
network: z.ZodString;
|
|
190
|
+
}, "strip", z.ZodTypeAny, {
|
|
191
|
+
network: string;
|
|
192
|
+
success: boolean;
|
|
193
|
+
transaction: string;
|
|
194
|
+
payer?: string | undefined;
|
|
195
|
+
errorReason?: string | undefined;
|
|
196
|
+
}, {
|
|
197
|
+
network: string;
|
|
198
|
+
success: boolean;
|
|
199
|
+
transaction: string;
|
|
200
|
+
payer?: string | undefined;
|
|
201
|
+
errorReason?: string | undefined;
|
|
202
|
+
}>;
|
|
203
|
+
type SettleResponse = z.infer<typeof SettleResponse>;
|
|
204
|
+
declare function encodePaymentHeader(payload: PaymentPayload): string;
|
|
205
|
+
declare function decodePaymentHeader(raw: string): PaymentPayload;
|
|
206
|
+
declare function encodeSettlementHeader(response: SettleResponse): string;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Deterministic EIP-3009 nonce: keccak256(utf8(intent.id)).
|
|
210
|
+
*
|
|
211
|
+
* This is the on-chain memo. EIP-3009 only requires per-authorizer uniqueness
|
|
212
|
+
* (intent ids are ULIDs, so collisions are off the table), and USDC emits
|
|
213
|
+
* `AuthorizationUsed(authorizer, nonce)` on settlement — so the indexer can
|
|
214
|
+
* recompute this for every allowed intent and reconcile transfers exactly,
|
|
215
|
+
* the same memo-first semantics the mock rails get from a ledger memo field.
|
|
216
|
+
*/
|
|
217
|
+
declare function intentNonce(intentId: string): Hex;
|
|
218
|
+
|
|
219
|
+
declare function chainIdForNetwork(network: string): number | undefined;
|
|
220
|
+
|
|
221
|
+
/** The EIP-3009 type tuple USDC's FiatTokenV2 verifies against. */
|
|
222
|
+
declare const transferWithAuthorizationTypes: {
|
|
223
|
+
readonly TransferWithAuthorization: readonly [{
|
|
224
|
+
readonly name: "from";
|
|
225
|
+
readonly type: "address";
|
|
226
|
+
}, {
|
|
227
|
+
readonly name: "to";
|
|
228
|
+
readonly type: "address";
|
|
229
|
+
}, {
|
|
230
|
+
readonly name: "value";
|
|
231
|
+
readonly type: "uint256";
|
|
232
|
+
}, {
|
|
233
|
+
readonly name: "validAfter";
|
|
234
|
+
readonly type: "uint256";
|
|
235
|
+
}, {
|
|
236
|
+
readonly name: "validBefore";
|
|
237
|
+
readonly type: "uint256";
|
|
238
|
+
}, {
|
|
239
|
+
readonly name: "nonce";
|
|
240
|
+
readonly type: "bytes32";
|
|
241
|
+
}];
|
|
242
|
+
};
|
|
243
|
+
interface X402PayerOptions {
|
|
244
|
+
/** The agent wallet's private key (a local account; never leaves the process). */
|
|
245
|
+
privateKey: Hex;
|
|
246
|
+
/** How far into the past validAfter reaches, absorbing clock skew. */
|
|
247
|
+
validAfterSkewSeconds?: number;
|
|
248
|
+
/** validBefore window when the requirement omits maxTimeoutSeconds. */
|
|
249
|
+
defaultTimeoutSeconds?: number;
|
|
250
|
+
/** Injectable clock (unix seconds) for deterministic tests. */
|
|
251
|
+
now?: () => number;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The real x402 `Payer`: signs an EIP-3009 TransferWithAuthorization for the
|
|
255
|
+
* requirement's token (EIP-712, fully offline — no RPC) and returns the v1
|
|
256
|
+
* X-PAYMENT header. Gasless for the agent: the facilitator submits the tx.
|
|
257
|
+
*
|
|
258
|
+
* The authorization nonce is derived from the intent id, which is what lets
|
|
259
|
+
* the on-chain indexer reconcile the settlement back to the decision that
|
|
260
|
+
* allowed it (see nonce.ts).
|
|
261
|
+
*/
|
|
262
|
+
declare function createX402Payer(options: X402PayerOptions): Payer;
|
|
263
|
+
|
|
264
|
+
/** Coinbase's hosted testnet facilitator: free, no API key, v1 + base-sepolia. */
|
|
265
|
+
declare const DEFAULT_FACILITATOR_URL = "https://x402.org/facilitator";
|
|
266
|
+
interface FacilitatorClientOptions {
|
|
267
|
+
/** Facilitator base URL. Defaults to the hosted x402.org testnet facilitator. */
|
|
268
|
+
url?: string;
|
|
269
|
+
/** Transport override (tests inject a stub here). Defaults to global fetch. */
|
|
270
|
+
fetch?: FetchLike;
|
|
271
|
+
}
|
|
272
|
+
/** Any x402 payment envelope — the POST's x402Version is read off of it. */
|
|
273
|
+
type AnyPaymentPayload = PaymentPayload | ({
|
|
274
|
+
x402Version: number;
|
|
275
|
+
} & Record<string, unknown>);
|
|
276
|
+
/** Requirements in whichever dialect matches the payload (v1 or v2 shape). */
|
|
277
|
+
type AnyPaymentRequirements = PaymentRequirement | Record<string, unknown>;
|
|
278
|
+
/**
|
|
279
|
+
* HTTP client for a real x402 facilitator (v1 AND v2 — x402.org serves both).
|
|
280
|
+
* The vendor side of the rails: verify checks the payment signature against
|
|
281
|
+
* the requirement, settle submits the EIP-3009 authorization on-chain (the
|
|
282
|
+
* facilitator pays gas) and returns the tx hash. The POST's `x402Version` is
|
|
283
|
+
* derived from the payload envelope itself, and the caller must pass
|
|
284
|
+
* requirements in the SAME dialect (v2 payload → v2 `amount`/CAIP-2 shape;
|
|
285
|
+
* @reinconsole/gate's facilitatorClientRails does this conversion).
|
|
286
|
+
*/
|
|
287
|
+
declare class FacilitatorClient {
|
|
288
|
+
readonly url: string;
|
|
289
|
+
private readonly fetch;
|
|
290
|
+
constructor(options?: FacilitatorClientOptions);
|
|
291
|
+
verify(payload: AnyPaymentPayload, requirements: AnyPaymentRequirements): Promise<VerifyResponse>;
|
|
292
|
+
settle(payload: AnyPaymentPayload, requirements: AnyPaymentRequirements): Promise<SettleResponse>;
|
|
293
|
+
/** The facilitator's advertised (x402Version, scheme, network) kinds. */
|
|
294
|
+
supported(): Promise<unknown>;
|
|
295
|
+
private post;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
interface RealVendorOptions {
|
|
299
|
+
facilitator: FacilitatorClient;
|
|
300
|
+
/** Price in atomic units (e.g. "10000" = 0.01 USDC at 6 decimals). */
|
|
301
|
+
atomicPrice: string;
|
|
302
|
+
/** Where the vendor wants to be paid (a real EVM address). */
|
|
303
|
+
payTo: string;
|
|
304
|
+
network?: string;
|
|
305
|
+
/** Token contract address. Defaults to USDC on Base Sepolia. */
|
|
306
|
+
asset?: string;
|
|
307
|
+
description?: string;
|
|
308
|
+
maxTimeoutSeconds?: number;
|
|
309
|
+
/** EIP-712 domain hints for the payer. Defaults to Base Sepolia USDC's. */
|
|
310
|
+
extra?: Record<string, unknown>;
|
|
311
|
+
/** JSON body served once payment settles. */
|
|
312
|
+
body?: unknown;
|
|
313
|
+
}
|
|
314
|
+
interface VendorCall {
|
|
315
|
+
url: string;
|
|
316
|
+
payment: string | null;
|
|
317
|
+
}
|
|
318
|
+
interface RealVendor {
|
|
319
|
+
/** Drop-in fetch for the guard's vendor-facing side. */
|
|
320
|
+
fetch: FetchLike;
|
|
321
|
+
/** Every request the vendor saw, in order. */
|
|
322
|
+
calls: VendorCall[];
|
|
323
|
+
/** The payment requirement this vendor quotes for a given URL. */
|
|
324
|
+
requirementFor(url: string): PaymentRequirement;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* An in-process x402 vendor wired to a REAL facilitator: quotes a 402 with
|
|
328
|
+
* payment requirements until the request carries X-PAYMENT, then verifies and
|
|
329
|
+
* settles through the facilitator — a real on-chain USDC transfer — and serves
|
|
330
|
+
* the content with the settlement (tx hash included) in X-PAYMENT-RESPONSE.
|
|
331
|
+
*
|
|
332
|
+
* Unlike the mock vendor, the quoted requirement is fully populated: the
|
|
333
|
+
* hosted facilitator validates it strictly (resource must be a URL,
|
|
334
|
+
* description/mimeType/maxTimeoutSeconds required, real addresses).
|
|
335
|
+
*/
|
|
336
|
+
declare function createRealVendor(options: RealVendorOptions): RealVendor;
|
|
337
|
+
|
|
338
|
+
/** The slice of the policy engine the indexer subscribes to (NATS in prod). */
|
|
339
|
+
interface EngineEvents {
|
|
340
|
+
onEvent(handler: (event: ReinEvent) => void): void;
|
|
341
|
+
}
|
|
342
|
+
/** The two FiatTokenV2 events one transferWithAuthorization settlement emits. */
|
|
343
|
+
declare const railEventsAbi: readonly [{
|
|
344
|
+
readonly name: "Transfer";
|
|
345
|
+
readonly type: "event";
|
|
346
|
+
readonly inputs: readonly [{
|
|
347
|
+
readonly type: "address";
|
|
348
|
+
readonly name: "from";
|
|
349
|
+
readonly indexed: true;
|
|
350
|
+
}, {
|
|
351
|
+
readonly type: "address";
|
|
352
|
+
readonly name: "to";
|
|
353
|
+
readonly indexed: true;
|
|
354
|
+
}, {
|
|
355
|
+
readonly type: "uint256";
|
|
356
|
+
readonly name: "value";
|
|
357
|
+
}];
|
|
358
|
+
}, {
|
|
359
|
+
readonly name: "AuthorizationUsed";
|
|
360
|
+
readonly type: "event";
|
|
361
|
+
readonly inputs: readonly [{
|
|
362
|
+
readonly type: "address";
|
|
363
|
+
readonly name: "authorizer";
|
|
364
|
+
readonly indexed: true;
|
|
365
|
+
}, {
|
|
366
|
+
readonly type: "bytes32";
|
|
367
|
+
readonly name: "nonce";
|
|
368
|
+
readonly indexed: true;
|
|
369
|
+
}];
|
|
370
|
+
}];
|
|
371
|
+
/** The decoded log shape the indexer consumes. */
|
|
372
|
+
interface RailLog {
|
|
373
|
+
eventName: 'Transfer' | 'AuthorizationUsed';
|
|
374
|
+
transactionHash: Hex | null;
|
|
375
|
+
blockNumber: bigint | null;
|
|
376
|
+
args: {
|
|
377
|
+
from?: Address;
|
|
378
|
+
to?: Address;
|
|
379
|
+
value?: bigint;
|
|
380
|
+
authorizer?: Address;
|
|
381
|
+
nonce?: Hex;
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* What the indexer needs from a chain: current head + decoded event logs.
|
|
386
|
+
* viem's PublicClient satisfies this structurally; tests inject a fake.
|
|
387
|
+
*/
|
|
388
|
+
interface ChainReader {
|
|
389
|
+
getBlockNumber(): Promise<bigint>;
|
|
390
|
+
getLogs(args: {
|
|
391
|
+
address: Address;
|
|
392
|
+
events: typeof railEventsAbi;
|
|
393
|
+
fromBlock: bigint;
|
|
394
|
+
toBlock: bigint;
|
|
395
|
+
}): Promise<readonly RailLog[]>;
|
|
396
|
+
}
|
|
397
|
+
interface OnchainIndexerOptions {
|
|
398
|
+
client: ChainReader;
|
|
399
|
+
/** Token contract whose transfers are watched. Defaults to Base Sepolia USDC. */
|
|
400
|
+
usdcAddress?: Address;
|
|
401
|
+
/** The Rein chain tag recorded on events (testnets map to mainnet). */
|
|
402
|
+
chain?: Chain;
|
|
403
|
+
/**
|
|
404
|
+
* Directory of managed agents and their wallets (the agents service in
|
|
405
|
+
* prod). Called per transfer, so agents registered later are still seen.
|
|
406
|
+
*/
|
|
407
|
+
agents: () => readonly Agent[];
|
|
408
|
+
/** Recorded as SettledPayment.facilitator on reconciled payments. */
|
|
409
|
+
facilitator?: string;
|
|
410
|
+
pollIntervalMs?: number;
|
|
411
|
+
/** First block to scan. Defaults to the chain head at start(). */
|
|
412
|
+
fromBlock?: bigint;
|
|
413
|
+
/** Token decimals for amount conversion. */
|
|
414
|
+
decimals?: number;
|
|
415
|
+
/** RPC errors are retried next tick; surface them here if you care. */
|
|
416
|
+
onError?: (err: unknown) => void;
|
|
417
|
+
}
|
|
418
|
+
/** A `shadow.spend` event, extracted for convenience accessors. */
|
|
419
|
+
type ShadowSpend = Extract<ReinEvent, {
|
|
420
|
+
type: 'shadow.spend';
|
|
421
|
+
}>;
|
|
422
|
+
/**
|
|
423
|
+
* The real indexer: polls token logs on Base Sepolia and classifies every
|
|
424
|
+
* transfer that leaves a managed agent wallet — the same semantics as the
|
|
425
|
+
* mock indexer, against a real chain.
|
|
426
|
+
*
|
|
427
|
+
* Reconciliation is memo-first via the EIP-3009 nonce: Rein's payer derives
|
|
428
|
+
* the authorization nonce from the intent id, and settlement emits
|
|
429
|
+
* `AuthorizationUsed(authorizer, nonce)` alongside the `Transfer`. A transfer
|
|
430
|
+
* whose nonce resolves to an allowed, unsettled intent of the same agent is
|
|
431
|
+
* `payment.settled`; anything else leaving a managed wallet — random nonce,
|
|
432
|
+
* replay, plain transfer, no ALLOW behind it — is `shadow.spend`. There is no
|
|
433
|
+
* fuzzy fallback on-chain: the nonce IS the memo.
|
|
434
|
+
*/
|
|
435
|
+
declare class OnchainIndexer {
|
|
436
|
+
private readonly options;
|
|
437
|
+
private readonly bus;
|
|
438
|
+
private readonly emitted;
|
|
439
|
+
/** Every intent the engine has seen, by id (from `intent.created`). */
|
|
440
|
+
private readonly intents;
|
|
441
|
+
/** Intents with an ALLOW decision, by id. */
|
|
442
|
+
private readonly allowed;
|
|
443
|
+
private readonly settledIntents;
|
|
444
|
+
/** Expected on-chain nonce -> intent id, for every allowed intent. */
|
|
445
|
+
private readonly nonceToIntent;
|
|
446
|
+
private timer;
|
|
447
|
+
private nextBlock;
|
|
448
|
+
private scanning;
|
|
449
|
+
constructor(options: OnchainIndexerOptions);
|
|
450
|
+
/** Subscribe to a policy engine's event stream to learn which intents were allowed. */
|
|
451
|
+
connectEngine(engine: EngineEvents): void;
|
|
452
|
+
/** Begin polling. Scans from `fromBlock` (default: the current head). */
|
|
453
|
+
start(): Promise<void>;
|
|
454
|
+
stop(): void;
|
|
455
|
+
onEvent(handler: (event: ReinEvent) => void): void;
|
|
456
|
+
/** Everything the indexer has emitted, oldest first. */
|
|
457
|
+
events(): readonly ReinEvent[];
|
|
458
|
+
settledPayments(): {
|
|
459
|
+
chain: "base" | "solana" | "polygon" | "bnb";
|
|
460
|
+
intentId: string;
|
|
461
|
+
txHash: string;
|
|
462
|
+
blockNumber: bigint;
|
|
463
|
+
confirmedAt: Date;
|
|
464
|
+
facilitator?: string | undefined;
|
|
465
|
+
feePaid?: string | undefined;
|
|
466
|
+
}[];
|
|
467
|
+
shadowSpends(): ShadowSpend[];
|
|
468
|
+
/** Resolve when an event (past or future) matches; reject on timeout. */
|
|
469
|
+
waitFor(predicate: (event: ReinEvent) => boolean, timeoutMs?: number): Promise<ReinEvent>;
|
|
470
|
+
/** One poll: fetch logs since the last scanned block and classify them. */
|
|
471
|
+
scan(): Promise<void>;
|
|
472
|
+
private emit;
|
|
473
|
+
private agentFor;
|
|
474
|
+
private observe;
|
|
475
|
+
private reconcile;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** USDC (FiatTokenV2) on Base Sepolia. */
|
|
479
|
+
declare const BASE_SEPOLIA_USDC: Address;
|
|
480
|
+
/** Circle's testnet faucet — funds Base Sepolia USDC for free. */
|
|
481
|
+
declare const CIRCLE_FAUCET_URL = "https://faucet.circle.com";
|
|
482
|
+
declare const basescanTxUrl: (txHash: string) => string;
|
|
483
|
+
interface GeneratedWallet {
|
|
484
|
+
privateKey: Hex;
|
|
485
|
+
address: Address;
|
|
486
|
+
}
|
|
487
|
+
/** A fresh local account — fund it with faucet USDC; it never needs ETH. */
|
|
488
|
+
declare function generateWallet(): GeneratedWallet;
|
|
489
|
+
type BaseSepoliaClient = PublicClient<HttpTransport, typeof baseSepolia>;
|
|
490
|
+
declare function createBaseSepoliaClient(rpcUrl?: string): BaseSepoliaClient;
|
|
491
|
+
/** Atomic-unit USDC balance (6 decimals). */
|
|
492
|
+
declare function getUsdcBalance(client: BaseSepoliaClient, address: Address, usdc?: Address): Promise<bigint>;
|
|
493
|
+
|
|
494
|
+
type RailsErrorCode = 'malformed_payment' | 'unsupported_network';
|
|
495
|
+
/** A payload or requirement these rails refuse to handle, with a machine-readable code. */
|
|
496
|
+
declare class RailsError extends Error {
|
|
497
|
+
readonly code: RailsErrorCode;
|
|
498
|
+
constructor(code: RailsErrorCode, message: string);
|
|
499
|
+
}
|
|
500
|
+
/** The facilitator answered with a non-2xx status; body kept for forensics. */
|
|
501
|
+
declare class FacilitatorHttpError extends Error {
|
|
502
|
+
readonly status: number;
|
|
503
|
+
readonly body: string;
|
|
504
|
+
constructor(status: number, body: string);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export { BASE_SEPOLIA_USDC, type BaseSepoliaClient, CIRCLE_FAUCET_URL, type ChainReader, DEFAULT_FACILITATOR_URL, type EngineEvents, ExactEvmAuthorization, ExactEvmPayload, FacilitatorClient, type FacilitatorClientOptions, FacilitatorHttpError, type GeneratedWallet, OnchainIndexer, type OnchainIndexerOptions, PaymentPayload, type RailLog, RailsError, type RailsErrorCode, type RealVendor, type RealVendorOptions, SettleResponse, type ShadowSpend, type VendorCall, VerifyResponse, type X402PayerOptions, basescanTxUrl, chainIdForNetwork, createBaseSepoliaClient, createRealVendor, createX402Payer, decodePaymentHeader, encodePaymentHeader, encodeSettlementHeader, generateWallet, getUsdcBalance, intentNonce, railEventsAbi, transferWithAuthorizationTypes };
|