@402lane/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +71 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @402lane/client
|
|
2
|
+
|
|
3
|
+
A fetch wrapper that transparently pays x402 challenges in USDG on Robinhood
|
|
4
|
+
Chain (chain 4663) — part of [402LANE](https://402lane.vercel.app).
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { createSigner, payingFetch } from "@402lane/client";
|
|
8
|
+
|
|
9
|
+
const pay = payingFetch({
|
|
10
|
+
account: createSigner(process.env.WALLET_KEY),
|
|
11
|
+
maxValue: 10_000n, // spend cap per request: 0.01 USDG
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const res = await pay("https://api.example.dev/report");
|
|
15
|
+
// 402 → signs EIP-3009 authorization → retries → 200
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Includes a per-payment spend cap and network allowlist so an agent can pay
|
|
19
|
+
autonomously without being drained. See the [docs](https://402lane.vercel.app/docs). MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PrivateKeyAccount } from "viem/accounts";
|
|
2
|
+
import { type PaymentPayload, type PaymentRequirements } from "@402lane/core";
|
|
3
|
+
export interface PayingFetchOptions {
|
|
4
|
+
account: PrivateKeyAccount;
|
|
5
|
+
/** Refuse any single payment above this many USDG base units. Default 1 USDG. */
|
|
6
|
+
maxValue?: bigint;
|
|
7
|
+
/** Restrict which CAIP-2 networks we will pay on. Default: all 402LANE networks. */
|
|
8
|
+
networks?: string[];
|
|
9
|
+
fetchImpl?: typeof fetch;
|
|
10
|
+
onPayment?: (info: {
|
|
11
|
+
resource: string;
|
|
12
|
+
value: string;
|
|
13
|
+
network: string;
|
|
14
|
+
}) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function createSigner(privateKey: `0x${string}`): PrivateKeyAccount;
|
|
17
|
+
/**
|
|
18
|
+
* fetch wrapper that transparently pays x402 challenges in USDG on Robinhood Chain.
|
|
19
|
+
* On a 402 it signs an EIP-3009 authorization and retries once.
|
|
20
|
+
*/
|
|
21
|
+
export declare function payingFetch(options: PayingFetchOptions): typeof fetch;
|
|
22
|
+
export declare function signPayment(account: PrivateKeyAccount, requirements: PaymentRequirements): Promise<PaymentPayload>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3
|
+
import { HEADER_PAYMENT_REQUIRED, HEADER_PAYMENT_SIGNATURE, NETWORKS, TRANSFER_WITH_AUTHORIZATION_TYPES, X402_VERSION, decodeHeader, eip3009Domain, encodeHeader, paymentRequiredBodySchema, toTypedMessage, } from "@402lane/core";
|
|
4
|
+
export function createSigner(privateKey) {
|
|
5
|
+
return privateKeyToAccount(privateKey);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* fetch wrapper that transparently pays x402 challenges in USDG on Robinhood Chain.
|
|
9
|
+
* On a 402 it signs an EIP-3009 authorization and retries once.
|
|
10
|
+
*/
|
|
11
|
+
export function payingFetch(options) {
|
|
12
|
+
const base = options.fetchImpl ?? fetch;
|
|
13
|
+
const allowed = new Set(options.networks ?? Object.keys(NETWORKS));
|
|
14
|
+
const maxValue = options.maxValue ?? 1000000n; // 1 USDG
|
|
15
|
+
return async (input, init) => {
|
|
16
|
+
const first = await base(input, init);
|
|
17
|
+
if (first.status !== 402)
|
|
18
|
+
return first;
|
|
19
|
+
const requirements = await extractRequirements(first);
|
|
20
|
+
const pick = requirements.find((r) => r.scheme === "exact" && allowed.has(r.network));
|
|
21
|
+
if (!pick)
|
|
22
|
+
return first;
|
|
23
|
+
if (BigInt(pick.maxAmountRequired) > maxValue) {
|
|
24
|
+
throw new Error(`402LANE client: challenge asks ${pick.maxAmountRequired} base units, above maxValue ${maxValue}`);
|
|
25
|
+
}
|
|
26
|
+
const payload = await signPayment(options.account, pick);
|
|
27
|
+
options.onPayment?.({
|
|
28
|
+
resource: pick.resource,
|
|
29
|
+
value: pick.maxAmountRequired,
|
|
30
|
+
network: pick.network,
|
|
31
|
+
});
|
|
32
|
+
const headers = new Headers(init?.headers);
|
|
33
|
+
headers.set(HEADER_PAYMENT_SIGNATURE, encodeHeader(payload));
|
|
34
|
+
return base(input, { ...init, headers });
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function extractRequirements(res) {
|
|
38
|
+
const header = res.headers.get(HEADER_PAYMENT_REQUIRED);
|
|
39
|
+
if (header)
|
|
40
|
+
return decodeHeader(header, paymentRequiredBodySchema).accepts;
|
|
41
|
+
try {
|
|
42
|
+
const body = paymentRequiredBodySchema.parse(await res.clone().json());
|
|
43
|
+
return body.accepts;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new Error("402LANE client: 402 without parseable payment requirements");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function signPayment(account, requirements) {
|
|
50
|
+
const now = Math.floor(Date.now() / 1000);
|
|
51
|
+
const authorization = {
|
|
52
|
+
from: account.address,
|
|
53
|
+
to: requirements.payTo,
|
|
54
|
+
value: requirements.maxAmountRequired,
|
|
55
|
+
validAfter: "0",
|
|
56
|
+
validBefore: String(now + (requirements.maxTimeoutSeconds ?? 300)),
|
|
57
|
+
nonce: `0x${randomBytes(32).toString("hex")}`,
|
|
58
|
+
};
|
|
59
|
+
const signature = await account.signTypedData({
|
|
60
|
+
domain: eip3009Domain(requirements.network),
|
|
61
|
+
types: TRANSFER_WITH_AUTHORIZATION_TYPES,
|
|
62
|
+
primaryType: "TransferWithAuthorization",
|
|
63
|
+
message: toTypedMessage(authorization),
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
x402Version: X402_VERSION,
|
|
67
|
+
scheme: "exact",
|
|
68
|
+
network: requirements.network,
|
|
69
|
+
payload: { authorization, signature },
|
|
70
|
+
};
|
|
71
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@402lane/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@402lane/core": "workspace:*",
|
|
15
|
+
"viem": "^2.21.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.5.0",
|
|
19
|
+
"@types/node": "^22.0.0"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/oggyfoxy/lane402"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
]
|
|
33
|
+
}
|