@alchemy/x402 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/README.md +145 -0
- package/dist/cli/index.mjs +136 -0
- package/dist/index.cjs +124 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +32 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +92 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# @alchemy/x402
|
|
2
|
+
|
|
3
|
+
CLI and library for Alchemy x402 authentication and payments. Handles SIWE (Sign-In With Ethereum) auth and x402 per-request payments for the Alchemy agentic gateway.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @alchemy/x402
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Generate a new wallet
|
|
15
|
+
npx alchemy-x402 wallet generate
|
|
16
|
+
|
|
17
|
+
# Import an existing wallet (accepts key or file path)
|
|
18
|
+
npx alchemy-x402 wallet import --private-key <key>
|
|
19
|
+
|
|
20
|
+
# Generate a SIWE token
|
|
21
|
+
npx alchemy-x402 sign-siwe --private-key <key> --expires-after 1h
|
|
22
|
+
|
|
23
|
+
# Create an x402 payment from a PAYMENT-REQUIRED header
|
|
24
|
+
npx alchemy-x402 pay --private-key <key> --payment-required <header>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Library
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { signSiwe, generateWallet, getWalletAddress, createPayment, buildX402Client } from "@alchemy/x402";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Generate a wallet
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const wallet = generateWallet();
|
|
37
|
+
// { privateKey: "0x...", address: "0x..." }
|
|
38
|
+
|
|
39
|
+
const address = getWalletAddress("0x<private-key>");
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Sign a SIWE token
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const token = await signSiwe({
|
|
46
|
+
privateKey: "0x<private-key>",
|
|
47
|
+
expiresAfter: "1h", // optional, default "1h"
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Create an x402 payment
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const paymentHeader = await createPayment({
|
|
55
|
+
privateKey: "0x<private-key>",
|
|
56
|
+
paymentRequiredHeader: "<raw PAYMENT-REQUIRED header value>",
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Use with @x402/fetch
|
|
61
|
+
|
|
62
|
+
For full request orchestration with automatic 402 payment handling, use `buildX402Client` with `@x402/fetch`:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { buildX402Client, signSiwe } from "@alchemy/x402";
|
|
66
|
+
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
67
|
+
|
|
68
|
+
const privateKey = "0x<private-key>";
|
|
69
|
+
const client = buildX402Client(privateKey);
|
|
70
|
+
const siweToken = await signSiwe({ privateKey });
|
|
71
|
+
|
|
72
|
+
// Wrap fetch with SIWE auth
|
|
73
|
+
const authedFetch: typeof fetch = async (input, init) => {
|
|
74
|
+
const headers = new Headers(init?.headers);
|
|
75
|
+
headers.set("Authorization", `SIWE ${siweToken}`);
|
|
76
|
+
return fetch(input, { ...init, headers });
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Wrap with automatic x402 payment handling
|
|
80
|
+
const paymentFetch = wrapFetchWithPayment(authedFetch, client);
|
|
81
|
+
|
|
82
|
+
const response = await paymentFetch("https://x402.alchemy.com/...");
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Private key input
|
|
86
|
+
|
|
87
|
+
All commands and functions accept private keys as:
|
|
88
|
+
|
|
89
|
+
- Hex string with `0x` prefix: `0xac09...`
|
|
90
|
+
- Raw hex string: `ac09...`
|
|
91
|
+
- File path: `/path/to/keyfile`
|
|
92
|
+
|
|
93
|
+
## Development
|
|
94
|
+
|
|
95
|
+
### Prerequisites
|
|
96
|
+
|
|
97
|
+
- Node.js >= 20
|
|
98
|
+
- [pnpm](https://pnpm.io/)
|
|
99
|
+
|
|
100
|
+
**Option A** — [mise](https://mise.jdx.dev/) (recommended, installs both Node and pnpm from `.tool-versions`):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
mise install
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Option B** — [corepack](https://nodejs.org/api/corepack.html) (ships with Node, reads `packageManager` from package.json):
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
corepack enable
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Getting started
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
git clone git@github.com:alchemyplatform/alchemy-x402.git
|
|
116
|
+
cd alchemy-x402
|
|
117
|
+
pnpm install
|
|
118
|
+
pnpm run build
|
|
119
|
+
pnpm run typecheck
|
|
120
|
+
pnpm test
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## For maintainers
|
|
124
|
+
|
|
125
|
+
### Adding a changeset
|
|
126
|
+
|
|
127
|
+
This project uses [Changesets](https://github.com/changesets/changesets) for versioning and npm publishing.
|
|
128
|
+
|
|
129
|
+
When your PR includes a user-facing change, add a changeset:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pnpm run changeset
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Select the semver bump type (patch/minor/major) and describe the change. Commit the generated `.changeset/*.md` file with your PR.
|
|
136
|
+
|
|
137
|
+
### Release flow
|
|
138
|
+
|
|
139
|
+
1. Merge PRs with changeset files to `main`
|
|
140
|
+
2. CI automatically opens a "Version Packages" PR that bumps the version and updates `CHANGELOG.md`
|
|
141
|
+
3. Merge the version PR to publish to npm
|
|
142
|
+
|
|
143
|
+
### Secrets
|
|
144
|
+
|
|
145
|
+
Add an `NPM_PUBLISH_TOKEN` secret to the repo (**Settings > Secrets and variables > Actions**) with a token from [npmjs.com](https://www.npmjs.com/settings/~/tokens).
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "@commander-js/extra-typings";
|
|
3
|
+
import { randomBytes } from "crypto";
|
|
4
|
+
import ms from "ms";
|
|
5
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { existsSync, readFileSync } from "fs";
|
|
7
|
+
import { createPublicClient, http } from "viem";
|
|
8
|
+
import { base } from "viem/chains";
|
|
9
|
+
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
|
|
10
|
+
import { x402Client } from "@x402/core/client";
|
|
11
|
+
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from "@x402/core/http";
|
|
12
|
+
|
|
13
|
+
//#region src/lib/wallet.ts
|
|
14
|
+
const RAW_HEX_RE = /^[0-9a-fA-F]{64}$/;
|
|
15
|
+
function resolvePrivateKey(keyOrPath) {
|
|
16
|
+
if (keyOrPath.startsWith("0x") || RAW_HEX_RE.test(keyOrPath)) return keyOrPath;
|
|
17
|
+
if (existsSync(keyOrPath)) return readFileSync(keyOrPath, "utf-8").trim();
|
|
18
|
+
return keyOrPath;
|
|
19
|
+
}
|
|
20
|
+
function normalizePrivateKey(key) {
|
|
21
|
+
const resolved = resolvePrivateKey(key);
|
|
22
|
+
return resolved.startsWith("0x") ? resolved : `0x${resolved}`;
|
|
23
|
+
}
|
|
24
|
+
function generateWallet() {
|
|
25
|
+
const privateKey = generatePrivateKey();
|
|
26
|
+
return {
|
|
27
|
+
privateKey,
|
|
28
|
+
address: privateKeyToAccount(privateKey).address
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function getWalletAddress(privateKey) {
|
|
32
|
+
return privateKeyToAccount(normalizePrivateKey(privateKey)).address;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/lib/siwe.ts
|
|
37
|
+
function generateNonce() {
|
|
38
|
+
return randomBytes(16).toString("hex");
|
|
39
|
+
}
|
|
40
|
+
async function signSiwe(opts) {
|
|
41
|
+
const account = privateKeyToAccount(normalizePrivateKey(opts.privateKey));
|
|
42
|
+
const issuedAt = opts.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
43
|
+
const nonce = opts.nonce ?? generateNonce();
|
|
44
|
+
const duration = ms(opts.expiresAfter ?? "1h");
|
|
45
|
+
if (duration === void 0) throw new Error(`Invalid duration: ${opts.expiresAfter}`);
|
|
46
|
+
const expirationTime = new Date(new Date(issuedAt).getTime() + duration).toISOString();
|
|
47
|
+
const message = [
|
|
48
|
+
"x402.alchemy.com wants you to sign in with your Ethereum account:",
|
|
49
|
+
account.address,
|
|
50
|
+
"",
|
|
51
|
+
"Sign in to Alchemy Gateway",
|
|
52
|
+
"",
|
|
53
|
+
"URI: https://x402.alchemy.com",
|
|
54
|
+
"Version: 1",
|
|
55
|
+
"Chain ID: 8453",
|
|
56
|
+
`Nonce: ${nonce}`,
|
|
57
|
+
`Issued At: ${issuedAt}`,
|
|
58
|
+
`Expiration Time: ${expirationTime}`
|
|
59
|
+
].join("\n");
|
|
60
|
+
const signature = await account.signMessage({ message });
|
|
61
|
+
return `${Buffer.from(message).toString("base64url")}.${signature}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/cli/commands/sign-siwe.ts
|
|
66
|
+
const signSiweCommand = new Command("sign-siwe").description("Generate a SIWE authentication token").requiredOption("--private-key <key>", "Wallet private key").option("--expires-after <duration>", "Token expiration duration (e.g. 1h, 30m, 7d)", "1h").action(async (opts) => {
|
|
67
|
+
const token = await signSiwe({
|
|
68
|
+
privateKey: opts.privateKey,
|
|
69
|
+
expiresAfter: opts.expiresAfter
|
|
70
|
+
});
|
|
71
|
+
process.stdout.write(token);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/cli/commands/wallet.ts
|
|
76
|
+
const walletCommand = new Command("wallet").description("Wallet management commands");
|
|
77
|
+
walletCommand.command("generate").description("Generate a new wallet").action(() => {
|
|
78
|
+
const wallet = generateWallet();
|
|
79
|
+
console.log(JSON.stringify(wallet, null, 2));
|
|
80
|
+
});
|
|
81
|
+
walletCommand.command("import").description("Import an existing wallet and display its address").requiredOption("--private-key <key>", "Wallet private key").action((opts) => {
|
|
82
|
+
const address = getWalletAddress(opts.privateKey);
|
|
83
|
+
console.log(JSON.stringify({ address }, null, 2));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/lib/payment.ts
|
|
88
|
+
function buildX402Client(privateKey) {
|
|
89
|
+
const account = privateKeyToAccount(normalizePrivateKey(privateKey));
|
|
90
|
+
const publicClient = createPublicClient({
|
|
91
|
+
chain: base,
|
|
92
|
+
transport: http()
|
|
93
|
+
});
|
|
94
|
+
const scheme = new ExactEvmScheme(toClientEvmSigner({
|
|
95
|
+
address: account.address,
|
|
96
|
+
signTypedData: async (params) => {
|
|
97
|
+
return account.signTypedData(params);
|
|
98
|
+
},
|
|
99
|
+
readContract: async (params) => {
|
|
100
|
+
return publicClient.readContract(params);
|
|
101
|
+
}
|
|
102
|
+
}));
|
|
103
|
+
const client = new x402Client();
|
|
104
|
+
client.register("eip155:8453", scheme);
|
|
105
|
+
client.register("eip155:84532", scheme);
|
|
106
|
+
return client;
|
|
107
|
+
}
|
|
108
|
+
async function createPayment(opts) {
|
|
109
|
+
const client = buildX402Client(opts.privateKey);
|
|
110
|
+
const paymentRequired = decodePaymentRequiredHeader(opts.paymentRequiredHeader);
|
|
111
|
+
return encodePaymentSignatureHeader(await client.createPaymentPayload(paymentRequired));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/cli/commands/pay.ts
|
|
116
|
+
const payCommand = new Command("pay").description("Create an x402 payment from a PAYMENT-REQUIRED header").requiredOption("--private-key <key>", "Wallet private key").requiredOption("--payment-required <header>", "Raw PAYMENT-REQUIRED header value").action(async (opts) => {
|
|
117
|
+
const paymentHeader = await createPayment({
|
|
118
|
+
privateKey: opts.privateKey,
|
|
119
|
+
paymentRequiredHeader: opts.paymentRequired
|
|
120
|
+
});
|
|
121
|
+
process.stdout.write(paymentHeader);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/cli/index.ts
|
|
126
|
+
const program = new Command().name("alchemy-x402").description("CLI for Alchemy x402 authentication and payments").version("0.1.0");
|
|
127
|
+
program.addCommand(signSiweCommand);
|
|
128
|
+
program.addCommand(walletCommand);
|
|
129
|
+
program.addCommand(payCommand);
|
|
130
|
+
program.parseAsync().catch((err) => {
|
|
131
|
+
console.error(err);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
//#endregion
|
|
136
|
+
export { };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let crypto = require("crypto");
|
|
30
|
+
let ms = require("ms");
|
|
31
|
+
ms = __toESM(ms);
|
|
32
|
+
let viem_accounts = require("viem/accounts");
|
|
33
|
+
let fs = require("fs");
|
|
34
|
+
let viem = require("viem");
|
|
35
|
+
let viem_chains = require("viem/chains");
|
|
36
|
+
let _x402_evm = require("@x402/evm");
|
|
37
|
+
let _x402_core_client = require("@x402/core/client");
|
|
38
|
+
let _x402_core_http = require("@x402/core/http");
|
|
39
|
+
|
|
40
|
+
//#region src/lib/wallet.ts
|
|
41
|
+
const RAW_HEX_RE = /^[0-9a-fA-F]{64}$/;
|
|
42
|
+
function resolvePrivateKey(keyOrPath) {
|
|
43
|
+
if (keyOrPath.startsWith("0x") || RAW_HEX_RE.test(keyOrPath)) return keyOrPath;
|
|
44
|
+
if ((0, fs.existsSync)(keyOrPath)) return (0, fs.readFileSync)(keyOrPath, "utf-8").trim();
|
|
45
|
+
return keyOrPath;
|
|
46
|
+
}
|
|
47
|
+
function normalizePrivateKey(key) {
|
|
48
|
+
const resolved = resolvePrivateKey(key);
|
|
49
|
+
return resolved.startsWith("0x") ? resolved : `0x${resolved}`;
|
|
50
|
+
}
|
|
51
|
+
function generateWallet() {
|
|
52
|
+
const privateKey = (0, viem_accounts.generatePrivateKey)();
|
|
53
|
+
return {
|
|
54
|
+
privateKey,
|
|
55
|
+
address: (0, viem_accounts.privateKeyToAccount)(privateKey).address
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function getWalletAddress(privateKey) {
|
|
59
|
+
return (0, viem_accounts.privateKeyToAccount)(normalizePrivateKey(privateKey)).address;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/lib/siwe.ts
|
|
64
|
+
function generateNonce() {
|
|
65
|
+
return (0, crypto.randomBytes)(16).toString("hex");
|
|
66
|
+
}
|
|
67
|
+
async function signSiwe(opts) {
|
|
68
|
+
const account = (0, viem_accounts.privateKeyToAccount)(normalizePrivateKey(opts.privateKey));
|
|
69
|
+
const issuedAt = opts.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
70
|
+
const nonce = opts.nonce ?? generateNonce();
|
|
71
|
+
const duration = (0, ms.default)(opts.expiresAfter ?? "1h");
|
|
72
|
+
if (duration === void 0) throw new Error(`Invalid duration: ${opts.expiresAfter}`);
|
|
73
|
+
const expirationTime = new Date(new Date(issuedAt).getTime() + duration).toISOString();
|
|
74
|
+
const message = [
|
|
75
|
+
"x402.alchemy.com wants you to sign in with your Ethereum account:",
|
|
76
|
+
account.address,
|
|
77
|
+
"",
|
|
78
|
+
"Sign in to Alchemy Gateway",
|
|
79
|
+
"",
|
|
80
|
+
"URI: https://x402.alchemy.com",
|
|
81
|
+
"Version: 1",
|
|
82
|
+
"Chain ID: 8453",
|
|
83
|
+
`Nonce: ${nonce}`,
|
|
84
|
+
`Issued At: ${issuedAt}`,
|
|
85
|
+
`Expiration Time: ${expirationTime}`
|
|
86
|
+
].join("\n");
|
|
87
|
+
const signature = await account.signMessage({ message });
|
|
88
|
+
return `${Buffer.from(message).toString("base64url")}.${signature}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/lib/payment.ts
|
|
93
|
+
function buildX402Client(privateKey) {
|
|
94
|
+
const account = (0, viem_accounts.privateKeyToAccount)(normalizePrivateKey(privateKey));
|
|
95
|
+
const publicClient = (0, viem.createPublicClient)({
|
|
96
|
+
chain: viem_chains.base,
|
|
97
|
+
transport: (0, viem.http)()
|
|
98
|
+
});
|
|
99
|
+
const scheme = new _x402_evm.ExactEvmScheme((0, _x402_evm.toClientEvmSigner)({
|
|
100
|
+
address: account.address,
|
|
101
|
+
signTypedData: async (params) => {
|
|
102
|
+
return account.signTypedData(params);
|
|
103
|
+
},
|
|
104
|
+
readContract: async (params) => {
|
|
105
|
+
return publicClient.readContract(params);
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
const client = new _x402_core_client.x402Client();
|
|
109
|
+
client.register("eip155:8453", scheme);
|
|
110
|
+
client.register("eip155:84532", scheme);
|
|
111
|
+
return client;
|
|
112
|
+
}
|
|
113
|
+
async function createPayment(opts) {
|
|
114
|
+
const client = buildX402Client(opts.privateKey);
|
|
115
|
+
const paymentRequired = (0, _x402_core_http.decodePaymentRequiredHeader)(opts.paymentRequiredHeader);
|
|
116
|
+
return (0, _x402_core_http.encodePaymentSignatureHeader)(await client.createPaymentPayload(paymentRequired));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
exports.buildX402Client = buildX402Client;
|
|
121
|
+
exports.createPayment = createPayment;
|
|
122
|
+
exports.generateWallet = generateWallet;
|
|
123
|
+
exports.getWalletAddress = getWalletAddress;
|
|
124
|
+
exports.signSiwe = signSiwe;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { x402Client } from "@x402/core/client";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type Hex = `0x${string}`;
|
|
5
|
+
interface WalletInfo {
|
|
6
|
+
privateKey: Hex;
|
|
7
|
+
address: Hex;
|
|
8
|
+
}
|
|
9
|
+
interface SignSiweOptions {
|
|
10
|
+
privateKey: string;
|
|
11
|
+
expiresAfter?: string;
|
|
12
|
+
nonce?: string;
|
|
13
|
+
issuedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
interface CreatePaymentOptions {
|
|
16
|
+
privateKey: string;
|
|
17
|
+
paymentRequiredHeader: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/lib/siwe.d.ts
|
|
21
|
+
declare function signSiwe(opts: SignSiweOptions): Promise<string>;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/lib/wallet.d.ts
|
|
24
|
+
declare function generateWallet(): WalletInfo;
|
|
25
|
+
declare function getWalletAddress(privateKey: string): Hex;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/lib/payment.d.ts
|
|
28
|
+
declare function buildX402Client(privateKey: string): x402Client;
|
|
29
|
+
declare function createPayment(opts: CreatePaymentOptions): Promise<string>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { type CreatePaymentOptions, type Hex, type SignSiweOptions, type WalletInfo, buildX402Client, createPayment, generateWallet, getWalletAddress, signSiwe };
|
|
32
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/lib/siwe.ts","../src/lib/wallet.ts","../src/lib/payment.ts"],"mappings":";;;KAAY,GAAA;AAAA,UAEK,UAAA;EACf,UAAA,EAAY,GAAA;EACZ,OAAA,EAAS,GAAA;AAAA;AAAA,UAGM,eAAA;EACf,UAAA;EACA,YAAA;EACA,KAAA;EACA,QAAA;AAAA;AAAA,UAGe,oBAAA;EACf,UAAA;EACA,qBAAA;AAAA;;;iBCNoB,QAAA,CAAS,IAAA,EAAM,eAAA,GAAkB,OAAA;;;iBCWvC,cAAA,CAAA,GAAkB,UAAA;AAAA,iBAMlB,gBAAA,CAAiB,UAAA,WAAqB,GAAA;;;iBClBtC,eAAA,CAAgB,UAAA,WAAkB,UAAA;AAAA,iBA0B5B,aAAA,CAAc,IAAA,EAAM,oBAAA,GAAuB,OAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { x402Client } from "@x402/core/client";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type Hex = `0x${string}`;
|
|
5
|
+
interface WalletInfo {
|
|
6
|
+
privateKey: Hex;
|
|
7
|
+
address: Hex;
|
|
8
|
+
}
|
|
9
|
+
interface SignSiweOptions {
|
|
10
|
+
privateKey: string;
|
|
11
|
+
expiresAfter?: string;
|
|
12
|
+
nonce?: string;
|
|
13
|
+
issuedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
interface CreatePaymentOptions {
|
|
16
|
+
privateKey: string;
|
|
17
|
+
paymentRequiredHeader: string;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/lib/siwe.d.ts
|
|
21
|
+
declare function signSiwe(opts: SignSiweOptions): Promise<string>;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/lib/wallet.d.ts
|
|
24
|
+
declare function generateWallet(): WalletInfo;
|
|
25
|
+
declare function getWalletAddress(privateKey: string): Hex;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/lib/payment.d.ts
|
|
28
|
+
declare function buildX402Client(privateKey: string): x402Client;
|
|
29
|
+
declare function createPayment(opts: CreatePaymentOptions): Promise<string>;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { type CreatePaymentOptions, type Hex, type SignSiweOptions, type WalletInfo, buildX402Client, createPayment, generateWallet, getWalletAddress, signSiwe };
|
|
32
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/lib/siwe.ts","../src/lib/wallet.ts","../src/lib/payment.ts"],"mappings":";;;KAAY,GAAA;AAAA,UAEK,UAAA;EACf,UAAA,EAAY,GAAA;EACZ,OAAA,EAAS,GAAA;AAAA;AAAA,UAGM,eAAA;EACf,UAAA;EACA,YAAA;EACA,KAAA;EACA,QAAA;AAAA;AAAA,UAGe,oBAAA;EACf,UAAA;EACA,qBAAA;AAAA;;;iBCNoB,QAAA,CAAS,IAAA,EAAM,eAAA,GAAkB,OAAA;;;iBCWvC,cAAA,CAAA,GAAkB,UAAA;AAAA,iBAMlB,gBAAA,CAAiB,UAAA,WAAqB,GAAA;;;iBClBtC,eAAA,CAAgB,UAAA,WAAkB,UAAA;AAAA,iBA0B5B,aAAA,CAAc,IAAA,EAAM,oBAAA,GAAuB,OAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
import ms from "ms";
|
|
3
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { createPublicClient, http } from "viem";
|
|
6
|
+
import { base } from "viem/chains";
|
|
7
|
+
import { ExactEvmScheme, toClientEvmSigner } from "@x402/evm";
|
|
8
|
+
import { x402Client } from "@x402/core/client";
|
|
9
|
+
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from "@x402/core/http";
|
|
10
|
+
|
|
11
|
+
//#region src/lib/wallet.ts
|
|
12
|
+
const RAW_HEX_RE = /^[0-9a-fA-F]{64}$/;
|
|
13
|
+
function resolvePrivateKey(keyOrPath) {
|
|
14
|
+
if (keyOrPath.startsWith("0x") || RAW_HEX_RE.test(keyOrPath)) return keyOrPath;
|
|
15
|
+
if (existsSync(keyOrPath)) return readFileSync(keyOrPath, "utf-8").trim();
|
|
16
|
+
return keyOrPath;
|
|
17
|
+
}
|
|
18
|
+
function normalizePrivateKey(key) {
|
|
19
|
+
const resolved = resolvePrivateKey(key);
|
|
20
|
+
return resolved.startsWith("0x") ? resolved : `0x${resolved}`;
|
|
21
|
+
}
|
|
22
|
+
function generateWallet() {
|
|
23
|
+
const privateKey = generatePrivateKey();
|
|
24
|
+
return {
|
|
25
|
+
privateKey,
|
|
26
|
+
address: privateKeyToAccount(privateKey).address
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function getWalletAddress(privateKey) {
|
|
30
|
+
return privateKeyToAccount(normalizePrivateKey(privateKey)).address;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/lib/siwe.ts
|
|
35
|
+
function generateNonce() {
|
|
36
|
+
return randomBytes(16).toString("hex");
|
|
37
|
+
}
|
|
38
|
+
async function signSiwe(opts) {
|
|
39
|
+
const account = privateKeyToAccount(normalizePrivateKey(opts.privateKey));
|
|
40
|
+
const issuedAt = opts.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
41
|
+
const nonce = opts.nonce ?? generateNonce();
|
|
42
|
+
const duration = ms(opts.expiresAfter ?? "1h");
|
|
43
|
+
if (duration === void 0) throw new Error(`Invalid duration: ${opts.expiresAfter}`);
|
|
44
|
+
const expirationTime = new Date(new Date(issuedAt).getTime() + duration).toISOString();
|
|
45
|
+
const message = [
|
|
46
|
+
"x402.alchemy.com wants you to sign in with your Ethereum account:",
|
|
47
|
+
account.address,
|
|
48
|
+
"",
|
|
49
|
+
"Sign in to Alchemy Gateway",
|
|
50
|
+
"",
|
|
51
|
+
"URI: https://x402.alchemy.com",
|
|
52
|
+
"Version: 1",
|
|
53
|
+
"Chain ID: 8453",
|
|
54
|
+
`Nonce: ${nonce}`,
|
|
55
|
+
`Issued At: ${issuedAt}`,
|
|
56
|
+
`Expiration Time: ${expirationTime}`
|
|
57
|
+
].join("\n");
|
|
58
|
+
const signature = await account.signMessage({ message });
|
|
59
|
+
return `${Buffer.from(message).toString("base64url")}.${signature}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/lib/payment.ts
|
|
64
|
+
function buildX402Client(privateKey) {
|
|
65
|
+
const account = privateKeyToAccount(normalizePrivateKey(privateKey));
|
|
66
|
+
const publicClient = createPublicClient({
|
|
67
|
+
chain: base,
|
|
68
|
+
transport: http()
|
|
69
|
+
});
|
|
70
|
+
const scheme = new ExactEvmScheme(toClientEvmSigner({
|
|
71
|
+
address: account.address,
|
|
72
|
+
signTypedData: async (params) => {
|
|
73
|
+
return account.signTypedData(params);
|
|
74
|
+
},
|
|
75
|
+
readContract: async (params) => {
|
|
76
|
+
return publicClient.readContract(params);
|
|
77
|
+
}
|
|
78
|
+
}));
|
|
79
|
+
const client = new x402Client();
|
|
80
|
+
client.register("eip155:8453", scheme);
|
|
81
|
+
client.register("eip155:84532", scheme);
|
|
82
|
+
return client;
|
|
83
|
+
}
|
|
84
|
+
async function createPayment(opts) {
|
|
85
|
+
const client = buildX402Client(opts.privateKey);
|
|
86
|
+
const paymentRequired = decodePaymentRequiredHeader(opts.paymentRequiredHeader);
|
|
87
|
+
return encodePaymentSignatureHeader(await client.createPaymentPayload(paymentRequired));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
export { buildX402Client, createPayment, generateWallet, getWalletAddress, signSiwe };
|
|
92
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/wallet.ts","../src/lib/siwe.ts","../src/lib/payment.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\";\nimport { generatePrivateKey, privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex, WalletInfo } from \"../types.js\";\n\nconst RAW_HEX_RE = /^[0-9a-fA-F]{64}$/;\n\nexport function resolvePrivateKey(keyOrPath: string): string {\n if (keyOrPath.startsWith(\"0x\") || RAW_HEX_RE.test(keyOrPath)) {\n return keyOrPath;\n }\n if (existsSync(keyOrPath)) {\n return readFileSync(keyOrPath, \"utf-8\").trim();\n }\n return keyOrPath;\n}\n\nexport function normalizePrivateKey(key: string): Hex {\n const resolved = resolvePrivateKey(key);\n return resolved.startsWith(\"0x\") ? (resolved as Hex) : (`0x${resolved}` as Hex);\n}\n\nexport function generateWallet(): WalletInfo {\n const privateKey = generatePrivateKey();\n const account = privateKeyToAccount(privateKey);\n return { privateKey, address: account.address };\n}\n\nexport function getWalletAddress(privateKey: string): Hex {\n const normalized = normalizePrivateKey(privateKey);\n const account = privateKeyToAccount(normalized);\n return account.address;\n}\n","import { randomBytes } from \"crypto\";\nimport ms from \"ms\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { SignSiweOptions } from \"../types.js\";\nimport { normalizePrivateKey } from \"./wallet.js\";\n\nfunction generateNonce(): string {\n return randomBytes(16).toString(\"hex\");\n}\n\nexport async function signSiwe(opts: SignSiweOptions): Promise<string> {\n const normalized = normalizePrivateKey(opts.privateKey);\n const account = privateKeyToAccount(normalized);\n\n const issuedAt = opts.issuedAt ?? new Date().toISOString();\n const nonce = opts.nonce ?? generateNonce();\n\n const duration = ms((opts.expiresAfter ?? \"1h\") as Parameters<typeof ms>[0]);\n if (duration === undefined) {\n throw new Error(`Invalid duration: ${opts.expiresAfter}`);\n }\n const expirationTime = new Date(new Date(issuedAt).getTime() + duration).toISOString();\n\n const message = [\n \"x402.alchemy.com wants you to sign in with your Ethereum account:\",\n account.address,\n \"\",\n \"Sign in to Alchemy Gateway\",\n \"\",\n \"URI: https://x402.alchemy.com\",\n \"Version: 1\",\n \"Chain ID: 8453\",\n `Nonce: ${nonce}`,\n `Issued At: ${issuedAt}`,\n `Expiration Time: ${expirationTime}`,\n ].join(\"\\n\");\n\n const signature = await account.signMessage({ message });\n\n const encodedMessage = Buffer.from(message).toString(\"base64url\");\n return `${encodedMessage}.${signature}`;\n}\n","import { createPublicClient, http } from \"viem\";\nimport { base } from \"viem/chains\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { ExactEvmScheme, toClientEvmSigner } from \"@x402/evm\";\nimport { x402Client } from \"@x402/core/client\";\nimport { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from \"@x402/core/http\";\nimport type { CreatePaymentOptions } from \"../types.js\";\nimport { normalizePrivateKey } from \"./wallet.js\";\n\nexport function buildX402Client(privateKey: string) {\n const normalized = normalizePrivateKey(privateKey);\n const account = privateKeyToAccount(normalized);\n\n const publicClient = createPublicClient({\n chain: base,\n transport: http(),\n });\n\n const signer = toClientEvmSigner({\n address: account.address,\n signTypedData: async (params) => {\n return account.signTypedData(params as Parameters<typeof account.signTypedData>[0]);\n },\n readContract: async (params) => {\n return publicClient.readContract(params as Parameters<typeof publicClient.readContract>[0]);\n },\n });\n\n const scheme = new ExactEvmScheme(signer);\n const client = new x402Client();\n client.register(\"eip155:8453\", scheme);\n client.register(\"eip155:84532\", scheme);\n return client;\n}\n\nexport async function createPayment(opts: CreatePaymentOptions): Promise<string> {\n const client = buildX402Client(opts.privateKey);\n const paymentRequired = decodePaymentRequiredHeader(opts.paymentRequiredHeader);\n const payload = await client.createPaymentPayload(paymentRequired);\n return encodePaymentSignatureHeader(payload);\n}\n"],"mappings":";;;;;;;;;;;AAIA,MAAM,aAAa;AAEnB,SAAgB,kBAAkB,WAA2B;AAC3D,KAAI,UAAU,WAAW,KAAK,IAAI,WAAW,KAAK,UAAU,CAC1D,QAAO;AAET,KAAI,WAAW,UAAU,CACvB,QAAO,aAAa,WAAW,QAAQ,CAAC,MAAM;AAEhD,QAAO;;AAGT,SAAgB,oBAAoB,KAAkB;CACpD,MAAM,WAAW,kBAAkB,IAAI;AACvC,QAAO,SAAS,WAAW,KAAK,GAAI,WAAoB,KAAK;;AAG/D,SAAgB,iBAA6B;CAC3C,MAAM,aAAa,oBAAoB;AAEvC,QAAO;EAAE;EAAY,SADL,oBAAoB,WAAW,CACT;EAAS;;AAGjD,SAAgB,iBAAiB,YAAyB;AAGxD,QADgB,oBADG,oBAAoB,WAAW,CACH,CAChC;;;;;ACxBjB,SAAS,gBAAwB;AAC/B,QAAO,YAAY,GAAG,CAAC,SAAS,MAAM;;AAGxC,eAAsB,SAAS,MAAwC;CAErE,MAAM,UAAU,oBADG,oBAAoB,KAAK,WAAW,CACR;CAE/C,MAAM,WAAW,KAAK,6BAAY,IAAI,MAAM,EAAC,aAAa;CAC1D,MAAM,QAAQ,KAAK,SAAS,eAAe;CAE3C,MAAM,WAAW,GAAI,KAAK,gBAAgB,KAAkC;AAC5E,KAAI,aAAa,OACf,OAAM,IAAI,MAAM,qBAAqB,KAAK,eAAe;CAE3D,MAAM,iBAAiB,IAAI,KAAK,IAAI,KAAK,SAAS,CAAC,SAAS,GAAG,SAAS,CAAC,aAAa;CAEtF,MAAM,UAAU;EACd;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;EACV,cAAc;EACd,oBAAoB;EACrB,CAAC,KAAK,KAAK;CAEZ,MAAM,YAAY,MAAM,QAAQ,YAAY,EAAE,SAAS,CAAC;AAGxD,QAAO,GADgB,OAAO,KAAK,QAAQ,CAAC,SAAS,YAAY,CACxC,GAAG;;;;;AC/B9B,SAAgB,gBAAgB,YAAoB;CAElD,MAAM,UAAU,oBADG,oBAAoB,WAAW,CACH;CAE/C,MAAM,eAAe,mBAAmB;EACtC,OAAO;EACP,WAAW,MAAM;EAClB,CAAC;CAYF,MAAM,SAAS,IAAI,eAVJ,kBAAkB;EAC/B,SAAS,QAAQ;EACjB,eAAe,OAAO,WAAW;AAC/B,UAAO,QAAQ,cAAc,OAAsD;;EAErF,cAAc,OAAO,WAAW;AAC9B,UAAO,aAAa,aAAa,OAA0D;;EAE9F,CAAC,CAEuC;CACzC,MAAM,SAAS,IAAI,YAAY;AAC/B,QAAO,SAAS,eAAe,OAAO;AACtC,QAAO,SAAS,gBAAgB,OAAO;AACvC,QAAO;;AAGT,eAAsB,cAAc,MAA6C;CAC/E,MAAM,SAAS,gBAAgB,KAAK,WAAW;CAC/C,MAAM,kBAAkB,4BAA4B,KAAK,sBAAsB;AAE/E,QAAO,6BADS,MAAM,OAAO,qBAAqB,gBAAgB,CACtB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alchemy/x402",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "CLI and library for Alchemy x402 authentication and payments",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": {
|
|
9
|
+
"types": "./dist/index.d.mts",
|
|
10
|
+
"default": "./dist/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"require": {
|
|
13
|
+
"types": "./dist/index.d.cts",
|
|
14
|
+
"default": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"alchemy-x402": "./dist/cli/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
23
|
+
"@x402/core": "^2.4.0",
|
|
24
|
+
"@x402/evm": "^2.4.0",
|
|
25
|
+
"@x402/fetch": "^2.4.0",
|
|
26
|
+
"commander": "^14.0.3",
|
|
27
|
+
"ms": "^2.1.3",
|
|
28
|
+
"viem": "^2.46.3"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@changesets/changelog-github": "^0.5.2",
|
|
32
|
+
"@changesets/cli": "^2.29.8",
|
|
33
|
+
"@types/ms": "^2.1.0",
|
|
34
|
+
"@types/node": "^25.3.0",
|
|
35
|
+
"tsdown": "^0.20.3",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.0.0"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsdown",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"changeset": "changeset",
|
|
54
|
+
"version-packages": "changeset version",
|
|
55
|
+
"release": "pnpm run build && changeset publish"
|
|
56
|
+
}
|
|
57
|
+
}
|