@relai-fi/x402 0.5.20 → 0.5.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,301 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/crossmint.ts
31
+ var crossmint_exports = {};
32
+ __export(crossmint_exports, {
33
+ createCrossmintDelegatedX402Fetch: () => createCrossmintDelegatedX402Fetch,
34
+ createCrossmintX402Fetch: () => createCrossmintX402Fetch
35
+ });
36
+ module.exports = __toCommonJS(crossmint_exports);
37
+ var import_web3 = require("@solana/web3.js");
38
+ var import_spl_token = require("@solana/spl-token");
39
+ var import_tweetnacl = __toESM(require("tweetnacl"), 1);
40
+ var DEFAULT_API_BASE = "https://www.crossmint.com/api/2025-06-09";
41
+ var STAGING_API_BASE = "https://staging.crossmint.com/api/2025-06-09";
42
+ function detectApiBase(apiKey) {
43
+ if (apiKey.startsWith("sk_staging")) return STAGING_API_BASE;
44
+ return DEFAULT_API_BASE;
45
+ }
46
+ function toBase58(bytes) {
47
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
48
+ const digits = [0];
49
+ for (const byte of bytes) {
50
+ let carry = byte;
51
+ for (let j = 0; j < digits.length; j++) {
52
+ carry += digits[j] << 8;
53
+ digits[j] = carry % 58;
54
+ carry = carry / 58 | 0;
55
+ }
56
+ while (carry > 0) {
57
+ digits.push(carry % 58);
58
+ carry = carry / 58 | 0;
59
+ }
60
+ }
61
+ let str = "";
62
+ for (const byte of bytes) {
63
+ if (byte === 0) str += ALPHABET[0];
64
+ else break;
65
+ }
66
+ for (let i = digits.length - 1; i >= 0; i--) {
67
+ str += ALPHABET[digits[i]];
68
+ }
69
+ return str;
70
+ }
71
+ function fromBase58(str) {
72
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
73
+ const BASE_MAP = new Uint8Array(256).fill(255);
74
+ for (let i = 0; i < ALPHABET.length; i++) BASE_MAP[ALPHABET.charCodeAt(i)] = i;
75
+ const bytes = [0];
76
+ for (const ch of str) {
77
+ const carry_init = BASE_MAP[ch.charCodeAt(0)];
78
+ if (carry_init === 255) throw new Error(`Invalid base58 character: ${ch}`);
79
+ let carry = carry_init;
80
+ for (let j = 0; j < bytes.length; j++) {
81
+ carry += bytes[j] * 58;
82
+ bytes[j] = carry & 255;
83
+ carry >>= 8;
84
+ }
85
+ while (carry > 0) {
86
+ bytes.push(carry & 255);
87
+ carry >>= 8;
88
+ }
89
+ }
90
+ for (const ch of str) {
91
+ if (ch === ALPHABET[0]) bytes.push(0);
92
+ else break;
93
+ }
94
+ return new Uint8Array(bytes.reverse());
95
+ }
96
+ function extractSignature(onChainTx) {
97
+ if (!onChainTx) return null;
98
+ if (onChainTx.length <= 100) return onChainTx;
99
+ try {
100
+ const decoded = fromBase58(onChainTx);
101
+ const vtx = import_web3.VersionedTransaction.deserialize(decoded);
102
+ if (vtx.signatures?.[0]) return toBase58(vtx.signatures[0]);
103
+ } catch {
104
+ }
105
+ return null;
106
+ }
107
+ async function crossmintApi(baseUrl, apiKey, endpoint, body, method = "POST") {
108
+ const url = `${baseUrl}/wallets/${endpoint}`;
109
+ const opts = {
110
+ method,
111
+ headers: { "Content-Type": "application/json", "X-API-KEY": apiKey }
112
+ };
113
+ if (method === "POST" && body) opts.body = JSON.stringify(body);
114
+ const resp = await fetch(url, opts);
115
+ const json = await resp.json();
116
+ if (!resp.ok) throw new Error(`[x402/crossmint] API ${resp.status}: ${JSON.stringify(json)}`);
117
+ return json;
118
+ }
119
+ function signMessage(message, secretKey) {
120
+ let messageBytes;
121
+ try {
122
+ messageBytes = fromBase58(message);
123
+ } catch {
124
+ messageBytes = new TextEncoder().encode(message);
125
+ }
126
+ const signature = import_tweetnacl.default.sign.detached(messageBytes, secretKey);
127
+ return toBase58(signature);
128
+ }
129
+ function deriveSignerAddress(secretKey) {
130
+ const publicKey = secretKey.length === 64 ? secretKey.slice(32) : import_tweetnacl.default.sign.keyPair.fromSeed(secretKey).publicKey;
131
+ return toBase58(publicKey);
132
+ }
133
+ async function pollForSignature(apiBase, apiKey, wallet, txId, maxPolls, pollMs, initialData) {
134
+ let sig = initialData?.onChain?.txId || extractSignature(initialData?.onChain?.transaction) || null;
135
+ for (let i = 0; i < maxPolls && !sig; i++) {
136
+ await new Promise((r) => setTimeout(r, pollMs));
137
+ const p = await crossmintApi(
138
+ apiBase,
139
+ apiKey,
140
+ `${encodeURIComponent(wallet)}/transactions/${txId}`,
141
+ void 0,
142
+ "GET"
143
+ );
144
+ if (p.status === "failed") {
145
+ throw new Error(`[x402/crossmint] Crossmint tx failed: ${p.error?.message || "unknown"}`);
146
+ }
147
+ sig = p.onChain?.txId || extractSignature(p.onChain?.transaction) || null;
148
+ }
149
+ return sig;
150
+ }
151
+ function buildXPaymentHeader(unsignedTx, sig, accept) {
152
+ const payload = {
153
+ x402Version: 2,
154
+ scheme: "exact",
155
+ network: accept.network,
156
+ payload: {
157
+ transaction: Buffer.from(unsignedTx.serialize()).toString("base64"),
158
+ txId: sig
159
+ },
160
+ accepted: {
161
+ scheme: accept.scheme,
162
+ network: accept.network,
163
+ amount: accept.amount,
164
+ asset: accept.asset,
165
+ payTo: accept.payTo
166
+ }
167
+ };
168
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
169
+ }
170
+ async function buildTransferTx(connection, userPubkey, accept) {
171
+ const mintPubkey = new import_web3.PublicKey(accept.asset);
172
+ const merchantPubkey = new import_web3.PublicKey(accept.payTo);
173
+ const mintAccountInfo = await connection.getAccountInfo(mintPubkey);
174
+ const programId = mintAccountInfo?.owner.equals(import_spl_token.TOKEN_2022_PROGRAM_ID) ? import_spl_token.TOKEN_2022_PROGRAM_ID : import_spl_token.TOKEN_PROGRAM_ID;
175
+ const mintInfo = await (0, import_spl_token.getMint)(connection, mintPubkey, "confirmed", programId);
176
+ const amount = BigInt(accept.amount);
177
+ const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(mintPubkey, userPubkey, true, programId);
178
+ const destAta = await (0, import_spl_token.getAssociatedTokenAddress)(mintPubkey, merchantPubkey, true, programId);
179
+ const ix = (0, import_spl_token.createTransferCheckedInstruction)(
180
+ sourceAta,
181
+ mintPubkey,
182
+ destAta,
183
+ userPubkey,
184
+ amount,
185
+ mintInfo.decimals,
186
+ [],
187
+ programId
188
+ );
189
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
190
+ const msg = new import_web3.TransactionMessage({
191
+ payerKey: userPubkey,
192
+ recentBlockhash: blockhash,
193
+ instructions: [ix]
194
+ }).compileToV0Message();
195
+ return new import_web3.VersionedTransaction(msg);
196
+ }
197
+ function createCrossmintX402Fetch(config) {
198
+ const { apiKey, wallet, connection } = config;
199
+ const apiBase = config.crossmintApiBase || detectApiBase(apiKey);
200
+ const maxPolls = config.maxPollAttempts ?? 30;
201
+ const pollMs = config.pollIntervalMs ?? 2e3;
202
+ const userPubkey = new import_web3.PublicKey(wallet);
203
+ return async function fetch402(url, init) {
204
+ const firstResp = await fetch(url, {
205
+ ...init,
206
+ headers: { Accept: "application/json", ...init?.headers }
207
+ });
208
+ if (firstResp.status !== 402) return firstResp;
209
+ const requirements = await firstResp.json();
210
+ const accept = requirements.accepts?.[0];
211
+ if (!accept) throw new Error("[x402/crossmint] No payment requirements in 402 response");
212
+ const unsignedTx = await buildTransferTx(connection, userPubkey, accept);
213
+ const txData = await crossmintApi(
214
+ apiBase,
215
+ apiKey,
216
+ `${encodeURIComponent(wallet)}/transactions`,
217
+ { params: { transaction: toBase58(unsignedTx.serialize()) } }
218
+ );
219
+ const sig = await pollForSignature(apiBase, apiKey, wallet, txData.id, maxPolls, pollMs, txData);
220
+ if (!sig) throw new Error("[x402/crossmint] Timed out waiting for tx confirmation");
221
+ const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);
222
+ return fetch(url, {
223
+ ...init,
224
+ headers: { Accept: "application/json", "X-PAYMENT": xPayment, ...init?.headers }
225
+ });
226
+ };
227
+ }
228
+ function createCrossmintDelegatedX402Fetch(config) {
229
+ const { apiKey, wallet, connection, signerSecretKey } = config;
230
+ const apiBase = config.crossmintApiBase || detectApiBase(apiKey);
231
+ const maxPolls = config.maxPollAttempts ?? 30;
232
+ const pollMs = config.pollIntervalMs ?? 2e3;
233
+ const userPubkey = new import_web3.PublicKey(wallet);
234
+ const signerAddress = deriveSignerAddress(signerSecretKey);
235
+ return async function fetch402(url, init) {
236
+ const firstResp = await fetch(url, {
237
+ ...init,
238
+ headers: { Accept: "application/json", ...init?.headers }
239
+ });
240
+ if (firstResp.status !== 402) return firstResp;
241
+ const requirements = await firstResp.json();
242
+ const accept = requirements.accepts?.[0];
243
+ if (!accept) throw new Error("[x402/crossmint] No payment requirements in 402 response");
244
+ const unsignedTx = await buildTransferTx(connection, userPubkey, accept);
245
+ const createResp = await crossmintApi(
246
+ apiBase,
247
+ apiKey,
248
+ `${encodeURIComponent(wallet)}/transactions`,
249
+ {
250
+ params: {
251
+ transaction: toBase58(unsignedTx.serialize()),
252
+ signer: {
253
+ type: "external-wallet",
254
+ locator: `external-wallet:${signerAddress}`
255
+ }
256
+ }
257
+ }
258
+ );
259
+ const transactionId = createResp.id;
260
+ const pendingApproval = createResp.approvals?.pending?.[0];
261
+ if (!transactionId || !pendingApproval) {
262
+ throw new Error(
263
+ `[x402/crossmint] No pending approval returned. Ensure external signer ${signerAddress} is registered on wallet ${wallet}. Response: ${JSON.stringify(createResp)}`
264
+ );
265
+ }
266
+ const pendingSigner = pendingApproval.signer?.address ?? pendingApproval.signer?.locator?.split(":")[1] ?? signerAddress;
267
+ const approvalSig = signMessage(pendingApproval.message, signerSecretKey);
268
+ const approvalResp = await crossmintApi(
269
+ apiBase,
270
+ apiKey,
271
+ `${encodeURIComponent(wallet)}/transactions/${encodeURIComponent(transactionId)}/approvals`,
272
+ {
273
+ approvals: [{
274
+ signer: `external-wallet:${pendingSigner}`,
275
+ signature: approvalSig
276
+ }]
277
+ }
278
+ );
279
+ const sig = await pollForSignature(
280
+ apiBase,
281
+ apiKey,
282
+ wallet,
283
+ transactionId,
284
+ maxPolls,
285
+ pollMs,
286
+ approvalResp
287
+ );
288
+ if (!sig) throw new Error("[x402/crossmint] Timed out waiting for tx confirmation");
289
+ const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);
290
+ return fetch(url, {
291
+ ...init,
292
+ headers: { Accept: "application/json", "X-PAYMENT": xPayment, ...init?.headers }
293
+ });
294
+ };
295
+ }
296
+ // Annotate the CommonJS export names for ESM import in node:
297
+ 0 && (module.exports = {
298
+ createCrossmintDelegatedX402Fetch,
299
+ createCrossmintX402Fetch
300
+ });
301
+ //# sourceMappingURL=crossmint.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crossmint.ts"],"sourcesContent":["/**\n * Crossmint smart wallet integration for RelAI x402.\n *\n * Two modes:\n *\n * **API-key mode** — zero private keys, Crossmint signs + broadcasts:\n * ```ts\n * import { createCrossmintX402Fetch } from \"@relai-fi/x402/crossmint\";\n *\n * const fetch402 = createCrossmintX402Fetch({\n * apiKey: process.env.CROSSMINT_API_KEY!,\n * wallet: process.env.CROSSMINT_WALLET!,\n * connection: new Connection(process.env.RPC_URL!),\n * });\n * const resp = await fetch402(\"https://api.example.com/protected\");\n * ```\n *\n * **Delegated mode** — external signer approves each transaction:\n * ```ts\n * import { createCrossmintDelegatedX402Fetch } from \"@relai-fi/x402/crossmint\";\n *\n * const fetch402 = createCrossmintDelegatedX402Fetch({\n * apiKey: process.env.CROSSMINT_API_KEY!,\n * wallet: process.env.CROSSMINT_WALLET!,\n * signerSecretKey: myKeypairBytes, // 64-byte Ed25519\n * connection: new Connection(process.env.RPC_URL!),\n * });\n * const resp = await fetch402(\"https://api.example.com/protected\");\n * ```\n *\n * @module\n */\nimport {\n Connection,\n PublicKey,\n VersionedTransaction,\n TransactionMessage,\n} from \"@solana/web3.js\";\nimport {\n getAssociatedTokenAddress,\n createTransferCheckedInstruction,\n getMint,\n TOKEN_PROGRAM_ID,\n TOKEN_2022_PROGRAM_ID,\n} from \"@solana/spl-token\";\n\nimport nacl from \"tweetnacl\";\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface CrossmintX402Config {\n /** Crossmint server-side API key (`sk_production_...` or `sk_staging_...`) */\n apiKey: string;\n\n /** Crossmint smart wallet address (Solana public key) */\n wallet: string;\n\n /** Solana RPC connection */\n connection: Connection;\n\n /** Override Crossmint API base URL (default: `https://www.crossmint.com/api/2025-06-09`) */\n crossmintApiBase?: string;\n\n /** Max polling attempts for tx confirmation (default: 30) */\n maxPollAttempts?: number;\n\n /** Polling interval in ms (default: 2000) */\n pollIntervalMs?: number;\n}\n\nexport interface CrossmintDelegatedX402Config extends CrossmintX402Config {\n /**\n * Ed25519 secret key for the external signer (64 bytes).\n * The corresponding public key must be registered as an external signer\n * on the Crossmint smart wallet.\n */\n signerSecretKey: Uint8Array;\n\n /** Crossmint environment: \"staging\" or \"production\" (default: auto-detect from apiKey) */\n environment?: \"staging\" | \"production\";\n}\n\n// ── Internals ───────────────────────────────────────────────────────\n\nconst DEFAULT_API_BASE = \"https://www.crossmint.com/api/2025-06-09\";\nconst STAGING_API_BASE = \"https://staging.crossmint.com/api/2025-06-09\";\n\n/** Auto-detect Crossmint API base from API key prefix. */\nfunction detectApiBase(apiKey: string): string {\n if (apiKey.startsWith(\"sk_staging\")) return STAGING_API_BASE;\n return DEFAULT_API_BASE;\n}\n\n/** Encode bytes to base58 without external dependency. */\nfunction toBase58(bytes: Uint8Array): string {\n const ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n const digits = [0];\n for (const byte of bytes) {\n let carry = byte;\n for (let j = 0; j < digits.length; j++) {\n carry += digits[j] << 8;\n digits[j] = carry % 58;\n carry = (carry / 58) | 0;\n }\n while (carry > 0) {\n digits.push(carry % 58);\n carry = (carry / 58) | 0;\n }\n }\n let str = \"\";\n for (const byte of bytes) {\n if (byte === 0) str += ALPHABET[0];\n else break;\n }\n for (let i = digits.length - 1; i >= 0; i--) {\n str += ALPHABET[digits[i]];\n }\n return str;\n}\n\n/** Decode base58 string to bytes. */\nfunction fromBase58(str: string): Uint8Array {\n const ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n const BASE_MAP = new Uint8Array(256).fill(255);\n for (let i = 0; i < ALPHABET.length; i++) BASE_MAP[ALPHABET.charCodeAt(i)] = i;\n const bytes: number[] = [0];\n for (const ch of str) {\n const carry_init = BASE_MAP[ch.charCodeAt(0)];\n if (carry_init === 255) throw new Error(`Invalid base58 character: ${ch}`);\n let carry = carry_init;\n for (let j = 0; j < bytes.length; j++) {\n carry += bytes[j] * 58;\n bytes[j] = carry & 0xff;\n carry >>= 8;\n }\n while (carry > 0) {\n bytes.push(carry & 0xff);\n carry >>= 8;\n }\n }\n for (const ch of str) {\n if (ch === ALPHABET[0]) bytes.push(0);\n else break;\n }\n return new Uint8Array(bytes.reverse());\n}\n\n/** Extract Solana signature from Crossmint's full serialized tx (base58). */\nfunction extractSignature(onChainTx: string): string | null {\n if (!onChainTx) return null;\n if (onChainTx.length <= 100) return onChainTx; // Already a bare signature\n try {\n const decoded = fromBase58(onChainTx);\n const vtx = VersionedTransaction.deserialize(decoded);\n if (vtx.signatures?.[0]) return toBase58(vtx.signatures[0]);\n } catch {\n // Not a valid serialized tx\n }\n return null;\n}\n\n/** Make a Crossmint API call. */\nasync function crossmintApi(\n baseUrl: string, apiKey: string, endpoint: string,\n body?: unknown, method: \"GET\" | \"POST\" = \"POST\",\n): Promise<any> {\n const url = `${baseUrl}/wallets/${endpoint}`;\n const opts: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\", \"X-API-KEY\": apiKey },\n };\n if (method === \"POST\" && body) opts.body = JSON.stringify(body);\n const resp = await fetch(url, opts);\n const json = await resp.json();\n if (!resp.ok) throw new Error(`[x402/crossmint] API ${resp.status}: ${JSON.stringify(json)}`);\n return json;\n}\n\n/** Sign a message with Ed25519. Message can be base58-encoded or UTF-8 string. */\nfunction signMessage(message: string, secretKey: Uint8Array): string {\n let messageBytes: Uint8Array;\n try {\n messageBytes = fromBase58(message);\n } catch {\n messageBytes = new TextEncoder().encode(message);\n }\n const signature = nacl.sign.detached(messageBytes, secretKey);\n return toBase58(signature);\n}\n\n/** Derive public key address from a 64-byte secret key. */\nfunction deriveSignerAddress(secretKey: Uint8Array): string {\n const publicKey = secretKey.length === 64\n ? secretKey.slice(32)\n : nacl.sign.keyPair.fromSeed(secretKey).publicKey;\n return toBase58(publicKey);\n}\n\n/** Poll Crossmint for on-chain txId. Returns signature or null. */\nasync function pollForSignature(\n apiBase: string, apiKey: string, wallet: string,\n txId: string, maxPolls: number, pollMs: number,\n initialData?: any,\n): Promise<string | null> {\n let sig = initialData?.onChain?.txId\n || extractSignature(initialData?.onChain?.transaction) || null;\n\n for (let i = 0; i < maxPolls && !sig; i++) {\n await new Promise((r) => setTimeout(r, pollMs));\n const p = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions/${txId}`, undefined, \"GET\");\n if (p.status === \"failed\") {\n throw new Error(`[x402/crossmint] Crossmint tx failed: ${p.error?.message || \"unknown\"}`);\n }\n sig = p.onChain?.txId || extractSignature(p.onChain?.transaction) || null;\n }\n return sig;\n}\n\n/** Build X-PAYMENT header from unsigned tx + on-chain signature + payment requirements. */\nfunction buildXPaymentHeader(unsignedTx: VersionedTransaction, sig: string, accept: any): string {\n const payload = {\n x402Version: 2,\n scheme: \"exact\",\n network: accept.network,\n payload: {\n transaction: Buffer.from(unsignedTx.serialize()).toString(\"base64\"),\n txId: sig,\n },\n accepted: {\n scheme: accept.scheme,\n network: accept.network,\n amount: accept.amount,\n asset: accept.asset,\n payTo: accept.payTo,\n },\n };\n return Buffer.from(JSON.stringify(payload)).toString(\"base64\");\n}\n\n/** Build unsigned SPL transfer tx from 402 payment requirements. */\nasync function buildTransferTx(\n connection: Connection, userPubkey: PublicKey, accept: any,\n): Promise<VersionedTransaction> {\n const mintPubkey = new PublicKey(accept.asset);\n const merchantPubkey = new PublicKey(accept.payTo);\n const mintAccountInfo = await connection.getAccountInfo(mintPubkey);\n const programId = mintAccountInfo?.owner.equals(TOKEN_2022_PROGRAM_ID)\n ? TOKEN_2022_PROGRAM_ID\n : TOKEN_PROGRAM_ID;\n\n const mintInfo = await getMint(connection, mintPubkey, \"confirmed\", programId);\n const amount = BigInt(accept.amount);\n\n const sourceAta = await getAssociatedTokenAddress(mintPubkey, userPubkey, true, programId);\n const destAta = await getAssociatedTokenAddress(mintPubkey, merchantPubkey, true, programId);\n\n const ix = createTransferCheckedInstruction(\n sourceAta, mintPubkey, destAta, userPubkey,\n amount, mintInfo.decimals, [], programId,\n );\n\n const { blockhash } = await connection.getLatestBlockhash(\"confirmed\");\n const msg = new TransactionMessage({\n payerKey: userPubkey,\n recentBlockhash: blockhash,\n instructions: [ix],\n }).compileToV0Message();\n\n return new VersionedTransaction(msg);\n}\n\n// ── Public API ──────────────────────────────────────────────────────\n\n/**\n * **API-key mode.** Create a fetch wrapper that auto-handles x402 `402`\n * responses using a Crossmint smart wallet on Solana.\n *\n * Crossmint signs and broadcasts — no private key needed.\n * The returned function has the same signature as `fetch()`.\n */\nexport function createCrossmintX402Fetch(config: CrossmintX402Config) {\n const { apiKey, wallet, connection } = config;\n const apiBase = config.crossmintApiBase || detectApiBase(apiKey);\n const maxPolls = config.maxPollAttempts ?? 30;\n const pollMs = config.pollIntervalMs ?? 2000;\n const userPubkey = new PublicKey(wallet);\n\n return async function fetch402(url: string, init?: RequestInit): Promise<Response> {\n // 1. Initial request\n const firstResp = await fetch(url, {\n ...init, headers: { Accept: \"application/json\", ...init?.headers },\n });\n if (firstResp.status !== 402) return firstResp;\n\n // 2. Parse 402\n const requirements = (await firstResp.json()) as any;\n const accept = requirements.accepts?.[0];\n if (!accept) throw new Error(\"[x402/crossmint] No payment requirements in 402 response\");\n\n // 3. Build transfer tx\n const unsignedTx = await buildTransferTx(connection, userPubkey, accept);\n\n // 4. Submit to Crossmint (signs + broadcasts)\n const txData = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions`,\n { params: { transaction: toBase58(unsignedTx.serialize()) } });\n\n // 5. Poll for on-chain signature\n const sig = await pollForSignature(apiBase, apiKey, wallet, txData.id, maxPolls, pollMs, txData);\n if (!sig) throw new Error(\"[x402/crossmint] Timed out waiting for tx confirmation\");\n\n // 6. Retry with X-PAYMENT\n const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);\n return fetch(url, {\n ...init, headers: { Accept: \"application/json\", \"X-PAYMENT\": xPayment, ...init?.headers },\n });\n };\n}\n\n/**\n * **Delegated mode.** Create a fetch wrapper that auto-handles x402 `402`\n * responses using a Crossmint smart wallet with external signer approval.\n *\n * Each transaction requires cryptographic approval from `signerSecretKey`\n * before Crossmint broadcasts. This provides an extra security layer —\n * even with the API key, transactions cannot execute without the signer.\n *\n * The external signer must be registered on the Crossmint smart wallet\n * via the Crossmint console or API.\n */\nexport function createCrossmintDelegatedX402Fetch(config: CrossmintDelegatedX402Config) {\n const { apiKey, wallet, connection, signerSecretKey } = config;\n const apiBase = config.crossmintApiBase || detectApiBase(apiKey);\n const maxPolls = config.maxPollAttempts ?? 30;\n const pollMs = config.pollIntervalMs ?? 2000;\n const userPubkey = new PublicKey(wallet);\n const signerAddress = deriveSignerAddress(signerSecretKey);\n\n return async function fetch402(url: string, init?: RequestInit): Promise<Response> {\n // 1. Initial request\n const firstResp = await fetch(url, {\n ...init, headers: { Accept: \"application/json\", ...init?.headers },\n });\n if (firstResp.status !== 402) return firstResp;\n\n // 2. Parse 402\n const requirements = (await firstResp.json()) as any;\n const accept = requirements.accepts?.[0];\n if (!accept) throw new Error(\"[x402/crossmint] No payment requirements in 402 response\");\n\n // 3. Build transfer tx\n const unsignedTx = await buildTransferTx(connection, userPubkey, accept);\n\n // 4. Submit to Crossmint with external signer\n const createResp = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions`,\n {\n params: {\n transaction: toBase58(unsignedTx.serialize()),\n signer: {\n type: \"external-wallet\",\n locator: `external-wallet:${signerAddress}`,\n },\n },\n });\n\n const transactionId = createResp.id;\n const pendingApproval = createResp.approvals?.pending?.[0];\n\n if (!transactionId || !pendingApproval) {\n throw new Error(\n `[x402/crossmint] No pending approval returned. ` +\n `Ensure external signer ${signerAddress} is registered on wallet ${wallet}. ` +\n `Response: ${JSON.stringify(createResp)}`\n );\n }\n\n // 5. Sign the approval message\n const pendingSigner = pendingApproval.signer?.address\n ?? pendingApproval.signer?.locator?.split(\":\")[1]\n ?? signerAddress;\n\n const approvalSig = signMessage(pendingApproval.message, signerSecretKey);\n\n // 6. Submit approval\n const approvalResp = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions/${encodeURIComponent(transactionId)}/approvals`,\n {\n approvals: [{\n signer: `external-wallet:${pendingSigner}`,\n signature: approvalSig,\n }],\n });\n\n // 7. Poll for on-chain signature\n const sig = await pollForSignature(\n apiBase, apiKey, wallet, transactionId, maxPolls, pollMs, approvalResp);\n if (!sig) throw new Error(\"[x402/crossmint] Timed out waiting for tx confirmation\");\n\n // 8. Retry with X-PAYMENT\n const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);\n return fetch(url, {\n ...init, headers: { Accept: \"application/json\", \"X-PAYMENT\": xPayment, ...init?.headers },\n });\n };\n}\n"],"mappings":"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","names":["nacl"]}
@@ -0,0 +1,81 @@
1
+ import { Connection } from '@solana/web3.js';
2
+
3
+ /**
4
+ * Crossmint smart wallet integration for RelAI x402.
5
+ *
6
+ * Two modes:
7
+ *
8
+ * **API-key mode** — zero private keys, Crossmint signs + broadcasts:
9
+ * ```ts
10
+ * import { createCrossmintX402Fetch } from "@relai-fi/x402/crossmint";
11
+ *
12
+ * const fetch402 = createCrossmintX402Fetch({
13
+ * apiKey: process.env.CROSSMINT_API_KEY!,
14
+ * wallet: process.env.CROSSMINT_WALLET!,
15
+ * connection: new Connection(process.env.RPC_URL!),
16
+ * });
17
+ * const resp = await fetch402("https://api.example.com/protected");
18
+ * ```
19
+ *
20
+ * **Delegated mode** — external signer approves each transaction:
21
+ * ```ts
22
+ * import { createCrossmintDelegatedX402Fetch } from "@relai-fi/x402/crossmint";
23
+ *
24
+ * const fetch402 = createCrossmintDelegatedX402Fetch({
25
+ * apiKey: process.env.CROSSMINT_API_KEY!,
26
+ * wallet: process.env.CROSSMINT_WALLET!,
27
+ * signerSecretKey: myKeypairBytes, // 64-byte Ed25519
28
+ * connection: new Connection(process.env.RPC_URL!),
29
+ * });
30
+ * const resp = await fetch402("https://api.example.com/protected");
31
+ * ```
32
+ *
33
+ * @module
34
+ */
35
+
36
+ interface CrossmintX402Config {
37
+ /** Crossmint server-side API key (`sk_production_...` or `sk_staging_...`) */
38
+ apiKey: string;
39
+ /** Crossmint smart wallet address (Solana public key) */
40
+ wallet: string;
41
+ /** Solana RPC connection */
42
+ connection: Connection;
43
+ /** Override Crossmint API base URL (default: `https://www.crossmint.com/api/2025-06-09`) */
44
+ crossmintApiBase?: string;
45
+ /** Max polling attempts for tx confirmation (default: 30) */
46
+ maxPollAttempts?: number;
47
+ /** Polling interval in ms (default: 2000) */
48
+ pollIntervalMs?: number;
49
+ }
50
+ interface CrossmintDelegatedX402Config extends CrossmintX402Config {
51
+ /**
52
+ * Ed25519 secret key for the external signer (64 bytes).
53
+ * The corresponding public key must be registered as an external signer
54
+ * on the Crossmint smart wallet.
55
+ */
56
+ signerSecretKey: Uint8Array;
57
+ /** Crossmint environment: "staging" or "production" (default: auto-detect from apiKey) */
58
+ environment?: "staging" | "production";
59
+ }
60
+ /**
61
+ * **API-key mode.** Create a fetch wrapper that auto-handles x402 `402`
62
+ * responses using a Crossmint smart wallet on Solana.
63
+ *
64
+ * Crossmint signs and broadcasts — no private key needed.
65
+ * The returned function has the same signature as `fetch()`.
66
+ */
67
+ declare function createCrossmintX402Fetch(config: CrossmintX402Config): (url: string, init?: RequestInit) => Promise<Response>;
68
+ /**
69
+ * **Delegated mode.** Create a fetch wrapper that auto-handles x402 `402`
70
+ * responses using a Crossmint smart wallet with external signer approval.
71
+ *
72
+ * Each transaction requires cryptographic approval from `signerSecretKey`
73
+ * before Crossmint broadcasts. This provides an extra security layer —
74
+ * even with the API key, transactions cannot execute without the signer.
75
+ *
76
+ * The external signer must be registered on the Crossmint smart wallet
77
+ * via the Crossmint console or API.
78
+ */
79
+ declare function createCrossmintDelegatedX402Fetch(config: CrossmintDelegatedX402Config): (url: string, init?: RequestInit) => Promise<Response>;
80
+
81
+ export { type CrossmintDelegatedX402Config, type CrossmintX402Config, createCrossmintDelegatedX402Fetch, createCrossmintX402Fetch };
@@ -0,0 +1,81 @@
1
+ import { Connection } from '@solana/web3.js';
2
+
3
+ /**
4
+ * Crossmint smart wallet integration for RelAI x402.
5
+ *
6
+ * Two modes:
7
+ *
8
+ * **API-key mode** — zero private keys, Crossmint signs + broadcasts:
9
+ * ```ts
10
+ * import { createCrossmintX402Fetch } from "@relai-fi/x402/crossmint";
11
+ *
12
+ * const fetch402 = createCrossmintX402Fetch({
13
+ * apiKey: process.env.CROSSMINT_API_KEY!,
14
+ * wallet: process.env.CROSSMINT_WALLET!,
15
+ * connection: new Connection(process.env.RPC_URL!),
16
+ * });
17
+ * const resp = await fetch402("https://api.example.com/protected");
18
+ * ```
19
+ *
20
+ * **Delegated mode** — external signer approves each transaction:
21
+ * ```ts
22
+ * import { createCrossmintDelegatedX402Fetch } from "@relai-fi/x402/crossmint";
23
+ *
24
+ * const fetch402 = createCrossmintDelegatedX402Fetch({
25
+ * apiKey: process.env.CROSSMINT_API_KEY!,
26
+ * wallet: process.env.CROSSMINT_WALLET!,
27
+ * signerSecretKey: myKeypairBytes, // 64-byte Ed25519
28
+ * connection: new Connection(process.env.RPC_URL!),
29
+ * });
30
+ * const resp = await fetch402("https://api.example.com/protected");
31
+ * ```
32
+ *
33
+ * @module
34
+ */
35
+
36
+ interface CrossmintX402Config {
37
+ /** Crossmint server-side API key (`sk_production_...` or `sk_staging_...`) */
38
+ apiKey: string;
39
+ /** Crossmint smart wallet address (Solana public key) */
40
+ wallet: string;
41
+ /** Solana RPC connection */
42
+ connection: Connection;
43
+ /** Override Crossmint API base URL (default: `https://www.crossmint.com/api/2025-06-09`) */
44
+ crossmintApiBase?: string;
45
+ /** Max polling attempts for tx confirmation (default: 30) */
46
+ maxPollAttempts?: number;
47
+ /** Polling interval in ms (default: 2000) */
48
+ pollIntervalMs?: number;
49
+ }
50
+ interface CrossmintDelegatedX402Config extends CrossmintX402Config {
51
+ /**
52
+ * Ed25519 secret key for the external signer (64 bytes).
53
+ * The corresponding public key must be registered as an external signer
54
+ * on the Crossmint smart wallet.
55
+ */
56
+ signerSecretKey: Uint8Array;
57
+ /** Crossmint environment: "staging" or "production" (default: auto-detect from apiKey) */
58
+ environment?: "staging" | "production";
59
+ }
60
+ /**
61
+ * **API-key mode.** Create a fetch wrapper that auto-handles x402 `402`
62
+ * responses using a Crossmint smart wallet on Solana.
63
+ *
64
+ * Crossmint signs and broadcasts — no private key needed.
65
+ * The returned function has the same signature as `fetch()`.
66
+ */
67
+ declare function createCrossmintX402Fetch(config: CrossmintX402Config): (url: string, init?: RequestInit) => Promise<Response>;
68
+ /**
69
+ * **Delegated mode.** Create a fetch wrapper that auto-handles x402 `402`
70
+ * responses using a Crossmint smart wallet with external signer approval.
71
+ *
72
+ * Each transaction requires cryptographic approval from `signerSecretKey`
73
+ * before Crossmint broadcasts. This provides an extra security layer —
74
+ * even with the API key, transactions cannot execute without the signer.
75
+ *
76
+ * The external signer must be registered on the Crossmint smart wallet
77
+ * via the Crossmint console or API.
78
+ */
79
+ declare function createCrossmintDelegatedX402Fetch(config: CrossmintDelegatedX402Config): (url: string, init?: RequestInit) => Promise<Response>;
80
+
81
+ export { type CrossmintDelegatedX402Config, type CrossmintX402Config, createCrossmintDelegatedX402Fetch, createCrossmintX402Fetch };
@@ -0,0 +1,275 @@
1
+ // src/crossmint.ts
2
+ import {
3
+ PublicKey,
4
+ VersionedTransaction,
5
+ TransactionMessage
6
+ } from "@solana/web3.js";
7
+ import {
8
+ getAssociatedTokenAddress,
9
+ createTransferCheckedInstruction,
10
+ getMint,
11
+ TOKEN_PROGRAM_ID,
12
+ TOKEN_2022_PROGRAM_ID
13
+ } from "@solana/spl-token";
14
+ import nacl from "tweetnacl";
15
+ var DEFAULT_API_BASE = "https://www.crossmint.com/api/2025-06-09";
16
+ var STAGING_API_BASE = "https://staging.crossmint.com/api/2025-06-09";
17
+ function detectApiBase(apiKey) {
18
+ if (apiKey.startsWith("sk_staging")) return STAGING_API_BASE;
19
+ return DEFAULT_API_BASE;
20
+ }
21
+ function toBase58(bytes) {
22
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
23
+ const digits = [0];
24
+ for (const byte of bytes) {
25
+ let carry = byte;
26
+ for (let j = 0; j < digits.length; j++) {
27
+ carry += digits[j] << 8;
28
+ digits[j] = carry % 58;
29
+ carry = carry / 58 | 0;
30
+ }
31
+ while (carry > 0) {
32
+ digits.push(carry % 58);
33
+ carry = carry / 58 | 0;
34
+ }
35
+ }
36
+ let str = "";
37
+ for (const byte of bytes) {
38
+ if (byte === 0) str += ALPHABET[0];
39
+ else break;
40
+ }
41
+ for (let i = digits.length - 1; i >= 0; i--) {
42
+ str += ALPHABET[digits[i]];
43
+ }
44
+ return str;
45
+ }
46
+ function fromBase58(str) {
47
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
48
+ const BASE_MAP = new Uint8Array(256).fill(255);
49
+ for (let i = 0; i < ALPHABET.length; i++) BASE_MAP[ALPHABET.charCodeAt(i)] = i;
50
+ const bytes = [0];
51
+ for (const ch of str) {
52
+ const carry_init = BASE_MAP[ch.charCodeAt(0)];
53
+ if (carry_init === 255) throw new Error(`Invalid base58 character: ${ch}`);
54
+ let carry = carry_init;
55
+ for (let j = 0; j < bytes.length; j++) {
56
+ carry += bytes[j] * 58;
57
+ bytes[j] = carry & 255;
58
+ carry >>= 8;
59
+ }
60
+ while (carry > 0) {
61
+ bytes.push(carry & 255);
62
+ carry >>= 8;
63
+ }
64
+ }
65
+ for (const ch of str) {
66
+ if (ch === ALPHABET[0]) bytes.push(0);
67
+ else break;
68
+ }
69
+ return new Uint8Array(bytes.reverse());
70
+ }
71
+ function extractSignature(onChainTx) {
72
+ if (!onChainTx) return null;
73
+ if (onChainTx.length <= 100) return onChainTx;
74
+ try {
75
+ const decoded = fromBase58(onChainTx);
76
+ const vtx = VersionedTransaction.deserialize(decoded);
77
+ if (vtx.signatures?.[0]) return toBase58(vtx.signatures[0]);
78
+ } catch {
79
+ }
80
+ return null;
81
+ }
82
+ async function crossmintApi(baseUrl, apiKey, endpoint, body, method = "POST") {
83
+ const url = `${baseUrl}/wallets/${endpoint}`;
84
+ const opts = {
85
+ method,
86
+ headers: { "Content-Type": "application/json", "X-API-KEY": apiKey }
87
+ };
88
+ if (method === "POST" && body) opts.body = JSON.stringify(body);
89
+ const resp = await fetch(url, opts);
90
+ const json = await resp.json();
91
+ if (!resp.ok) throw new Error(`[x402/crossmint] API ${resp.status}: ${JSON.stringify(json)}`);
92
+ return json;
93
+ }
94
+ function signMessage(message, secretKey) {
95
+ let messageBytes;
96
+ try {
97
+ messageBytes = fromBase58(message);
98
+ } catch {
99
+ messageBytes = new TextEncoder().encode(message);
100
+ }
101
+ const signature = nacl.sign.detached(messageBytes, secretKey);
102
+ return toBase58(signature);
103
+ }
104
+ function deriveSignerAddress(secretKey) {
105
+ const publicKey = secretKey.length === 64 ? secretKey.slice(32) : nacl.sign.keyPair.fromSeed(secretKey).publicKey;
106
+ return toBase58(publicKey);
107
+ }
108
+ async function pollForSignature(apiBase, apiKey, wallet, txId, maxPolls, pollMs, initialData) {
109
+ let sig = initialData?.onChain?.txId || extractSignature(initialData?.onChain?.transaction) || null;
110
+ for (let i = 0; i < maxPolls && !sig; i++) {
111
+ await new Promise((r) => setTimeout(r, pollMs));
112
+ const p = await crossmintApi(
113
+ apiBase,
114
+ apiKey,
115
+ `${encodeURIComponent(wallet)}/transactions/${txId}`,
116
+ void 0,
117
+ "GET"
118
+ );
119
+ if (p.status === "failed") {
120
+ throw new Error(`[x402/crossmint] Crossmint tx failed: ${p.error?.message || "unknown"}`);
121
+ }
122
+ sig = p.onChain?.txId || extractSignature(p.onChain?.transaction) || null;
123
+ }
124
+ return sig;
125
+ }
126
+ function buildXPaymentHeader(unsignedTx, sig, accept) {
127
+ const payload = {
128
+ x402Version: 2,
129
+ scheme: "exact",
130
+ network: accept.network,
131
+ payload: {
132
+ transaction: Buffer.from(unsignedTx.serialize()).toString("base64"),
133
+ txId: sig
134
+ },
135
+ accepted: {
136
+ scheme: accept.scheme,
137
+ network: accept.network,
138
+ amount: accept.amount,
139
+ asset: accept.asset,
140
+ payTo: accept.payTo
141
+ }
142
+ };
143
+ return Buffer.from(JSON.stringify(payload)).toString("base64");
144
+ }
145
+ async function buildTransferTx(connection, userPubkey, accept) {
146
+ const mintPubkey = new PublicKey(accept.asset);
147
+ const merchantPubkey = new PublicKey(accept.payTo);
148
+ const mintAccountInfo = await connection.getAccountInfo(mintPubkey);
149
+ const programId = mintAccountInfo?.owner.equals(TOKEN_2022_PROGRAM_ID) ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
150
+ const mintInfo = await getMint(connection, mintPubkey, "confirmed", programId);
151
+ const amount = BigInt(accept.amount);
152
+ const sourceAta = await getAssociatedTokenAddress(mintPubkey, userPubkey, true, programId);
153
+ const destAta = await getAssociatedTokenAddress(mintPubkey, merchantPubkey, true, programId);
154
+ const ix = createTransferCheckedInstruction(
155
+ sourceAta,
156
+ mintPubkey,
157
+ destAta,
158
+ userPubkey,
159
+ amount,
160
+ mintInfo.decimals,
161
+ [],
162
+ programId
163
+ );
164
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
165
+ const msg = new TransactionMessage({
166
+ payerKey: userPubkey,
167
+ recentBlockhash: blockhash,
168
+ instructions: [ix]
169
+ }).compileToV0Message();
170
+ return new VersionedTransaction(msg);
171
+ }
172
+ function createCrossmintX402Fetch(config) {
173
+ const { apiKey, wallet, connection } = config;
174
+ const apiBase = config.crossmintApiBase || detectApiBase(apiKey);
175
+ const maxPolls = config.maxPollAttempts ?? 30;
176
+ const pollMs = config.pollIntervalMs ?? 2e3;
177
+ const userPubkey = new PublicKey(wallet);
178
+ return async function fetch402(url, init) {
179
+ const firstResp = await fetch(url, {
180
+ ...init,
181
+ headers: { Accept: "application/json", ...init?.headers }
182
+ });
183
+ if (firstResp.status !== 402) return firstResp;
184
+ const requirements = await firstResp.json();
185
+ const accept = requirements.accepts?.[0];
186
+ if (!accept) throw new Error("[x402/crossmint] No payment requirements in 402 response");
187
+ const unsignedTx = await buildTransferTx(connection, userPubkey, accept);
188
+ const txData = await crossmintApi(
189
+ apiBase,
190
+ apiKey,
191
+ `${encodeURIComponent(wallet)}/transactions`,
192
+ { params: { transaction: toBase58(unsignedTx.serialize()) } }
193
+ );
194
+ const sig = await pollForSignature(apiBase, apiKey, wallet, txData.id, maxPolls, pollMs, txData);
195
+ if (!sig) throw new Error("[x402/crossmint] Timed out waiting for tx confirmation");
196
+ const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);
197
+ return fetch(url, {
198
+ ...init,
199
+ headers: { Accept: "application/json", "X-PAYMENT": xPayment, ...init?.headers }
200
+ });
201
+ };
202
+ }
203
+ function createCrossmintDelegatedX402Fetch(config) {
204
+ const { apiKey, wallet, connection, signerSecretKey } = config;
205
+ const apiBase = config.crossmintApiBase || detectApiBase(apiKey);
206
+ const maxPolls = config.maxPollAttempts ?? 30;
207
+ const pollMs = config.pollIntervalMs ?? 2e3;
208
+ const userPubkey = new PublicKey(wallet);
209
+ const signerAddress = deriveSignerAddress(signerSecretKey);
210
+ return async function fetch402(url, init) {
211
+ const firstResp = await fetch(url, {
212
+ ...init,
213
+ headers: { Accept: "application/json", ...init?.headers }
214
+ });
215
+ if (firstResp.status !== 402) return firstResp;
216
+ const requirements = await firstResp.json();
217
+ const accept = requirements.accepts?.[0];
218
+ if (!accept) throw new Error("[x402/crossmint] No payment requirements in 402 response");
219
+ const unsignedTx = await buildTransferTx(connection, userPubkey, accept);
220
+ const createResp = await crossmintApi(
221
+ apiBase,
222
+ apiKey,
223
+ `${encodeURIComponent(wallet)}/transactions`,
224
+ {
225
+ params: {
226
+ transaction: toBase58(unsignedTx.serialize()),
227
+ signer: {
228
+ type: "external-wallet",
229
+ locator: `external-wallet:${signerAddress}`
230
+ }
231
+ }
232
+ }
233
+ );
234
+ const transactionId = createResp.id;
235
+ const pendingApproval = createResp.approvals?.pending?.[0];
236
+ if (!transactionId || !pendingApproval) {
237
+ throw new Error(
238
+ `[x402/crossmint] No pending approval returned. Ensure external signer ${signerAddress} is registered on wallet ${wallet}. Response: ${JSON.stringify(createResp)}`
239
+ );
240
+ }
241
+ const pendingSigner = pendingApproval.signer?.address ?? pendingApproval.signer?.locator?.split(":")[1] ?? signerAddress;
242
+ const approvalSig = signMessage(pendingApproval.message, signerSecretKey);
243
+ const approvalResp = await crossmintApi(
244
+ apiBase,
245
+ apiKey,
246
+ `${encodeURIComponent(wallet)}/transactions/${encodeURIComponent(transactionId)}/approvals`,
247
+ {
248
+ approvals: [{
249
+ signer: `external-wallet:${pendingSigner}`,
250
+ signature: approvalSig
251
+ }]
252
+ }
253
+ );
254
+ const sig = await pollForSignature(
255
+ apiBase,
256
+ apiKey,
257
+ wallet,
258
+ transactionId,
259
+ maxPolls,
260
+ pollMs,
261
+ approvalResp
262
+ );
263
+ if (!sig) throw new Error("[x402/crossmint] Timed out waiting for tx confirmation");
264
+ const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);
265
+ return fetch(url, {
266
+ ...init,
267
+ headers: { Accept: "application/json", "X-PAYMENT": xPayment, ...init?.headers }
268
+ });
269
+ };
270
+ }
271
+ export {
272
+ createCrossmintDelegatedX402Fetch,
273
+ createCrossmintX402Fetch
274
+ };
275
+ //# sourceMappingURL=crossmint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/crossmint.ts"],"sourcesContent":["/**\n * Crossmint smart wallet integration for RelAI x402.\n *\n * Two modes:\n *\n * **API-key mode** — zero private keys, Crossmint signs + broadcasts:\n * ```ts\n * import { createCrossmintX402Fetch } from \"@relai-fi/x402/crossmint\";\n *\n * const fetch402 = createCrossmintX402Fetch({\n * apiKey: process.env.CROSSMINT_API_KEY!,\n * wallet: process.env.CROSSMINT_WALLET!,\n * connection: new Connection(process.env.RPC_URL!),\n * });\n * const resp = await fetch402(\"https://api.example.com/protected\");\n * ```\n *\n * **Delegated mode** — external signer approves each transaction:\n * ```ts\n * import { createCrossmintDelegatedX402Fetch } from \"@relai-fi/x402/crossmint\";\n *\n * const fetch402 = createCrossmintDelegatedX402Fetch({\n * apiKey: process.env.CROSSMINT_API_KEY!,\n * wallet: process.env.CROSSMINT_WALLET!,\n * signerSecretKey: myKeypairBytes, // 64-byte Ed25519\n * connection: new Connection(process.env.RPC_URL!),\n * });\n * const resp = await fetch402(\"https://api.example.com/protected\");\n * ```\n *\n * @module\n */\nimport {\n Connection,\n PublicKey,\n VersionedTransaction,\n TransactionMessage,\n} from \"@solana/web3.js\";\nimport {\n getAssociatedTokenAddress,\n createTransferCheckedInstruction,\n getMint,\n TOKEN_PROGRAM_ID,\n TOKEN_2022_PROGRAM_ID,\n} from \"@solana/spl-token\";\n\nimport nacl from \"tweetnacl\";\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface CrossmintX402Config {\n /** Crossmint server-side API key (`sk_production_...` or `sk_staging_...`) */\n apiKey: string;\n\n /** Crossmint smart wallet address (Solana public key) */\n wallet: string;\n\n /** Solana RPC connection */\n connection: Connection;\n\n /** Override Crossmint API base URL (default: `https://www.crossmint.com/api/2025-06-09`) */\n crossmintApiBase?: string;\n\n /** Max polling attempts for tx confirmation (default: 30) */\n maxPollAttempts?: number;\n\n /** Polling interval in ms (default: 2000) */\n pollIntervalMs?: number;\n}\n\nexport interface CrossmintDelegatedX402Config extends CrossmintX402Config {\n /**\n * Ed25519 secret key for the external signer (64 bytes).\n * The corresponding public key must be registered as an external signer\n * on the Crossmint smart wallet.\n */\n signerSecretKey: Uint8Array;\n\n /** Crossmint environment: \"staging\" or \"production\" (default: auto-detect from apiKey) */\n environment?: \"staging\" | \"production\";\n}\n\n// ── Internals ───────────────────────────────────────────────────────\n\nconst DEFAULT_API_BASE = \"https://www.crossmint.com/api/2025-06-09\";\nconst STAGING_API_BASE = \"https://staging.crossmint.com/api/2025-06-09\";\n\n/** Auto-detect Crossmint API base from API key prefix. */\nfunction detectApiBase(apiKey: string): string {\n if (apiKey.startsWith(\"sk_staging\")) return STAGING_API_BASE;\n return DEFAULT_API_BASE;\n}\n\n/** Encode bytes to base58 without external dependency. */\nfunction toBase58(bytes: Uint8Array): string {\n const ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n const digits = [0];\n for (const byte of bytes) {\n let carry = byte;\n for (let j = 0; j < digits.length; j++) {\n carry += digits[j] << 8;\n digits[j] = carry % 58;\n carry = (carry / 58) | 0;\n }\n while (carry > 0) {\n digits.push(carry % 58);\n carry = (carry / 58) | 0;\n }\n }\n let str = \"\";\n for (const byte of bytes) {\n if (byte === 0) str += ALPHABET[0];\n else break;\n }\n for (let i = digits.length - 1; i >= 0; i--) {\n str += ALPHABET[digits[i]];\n }\n return str;\n}\n\n/** Decode base58 string to bytes. */\nfunction fromBase58(str: string): Uint8Array {\n const ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n const BASE_MAP = new Uint8Array(256).fill(255);\n for (let i = 0; i < ALPHABET.length; i++) BASE_MAP[ALPHABET.charCodeAt(i)] = i;\n const bytes: number[] = [0];\n for (const ch of str) {\n const carry_init = BASE_MAP[ch.charCodeAt(0)];\n if (carry_init === 255) throw new Error(`Invalid base58 character: ${ch}`);\n let carry = carry_init;\n for (let j = 0; j < bytes.length; j++) {\n carry += bytes[j] * 58;\n bytes[j] = carry & 0xff;\n carry >>= 8;\n }\n while (carry > 0) {\n bytes.push(carry & 0xff);\n carry >>= 8;\n }\n }\n for (const ch of str) {\n if (ch === ALPHABET[0]) bytes.push(0);\n else break;\n }\n return new Uint8Array(bytes.reverse());\n}\n\n/** Extract Solana signature from Crossmint's full serialized tx (base58). */\nfunction extractSignature(onChainTx: string): string | null {\n if (!onChainTx) return null;\n if (onChainTx.length <= 100) return onChainTx; // Already a bare signature\n try {\n const decoded = fromBase58(onChainTx);\n const vtx = VersionedTransaction.deserialize(decoded);\n if (vtx.signatures?.[0]) return toBase58(vtx.signatures[0]);\n } catch {\n // Not a valid serialized tx\n }\n return null;\n}\n\n/** Make a Crossmint API call. */\nasync function crossmintApi(\n baseUrl: string, apiKey: string, endpoint: string,\n body?: unknown, method: \"GET\" | \"POST\" = \"POST\",\n): Promise<any> {\n const url = `${baseUrl}/wallets/${endpoint}`;\n const opts: RequestInit = {\n method,\n headers: { \"Content-Type\": \"application/json\", \"X-API-KEY\": apiKey },\n };\n if (method === \"POST\" && body) opts.body = JSON.stringify(body);\n const resp = await fetch(url, opts);\n const json = await resp.json();\n if (!resp.ok) throw new Error(`[x402/crossmint] API ${resp.status}: ${JSON.stringify(json)}`);\n return json;\n}\n\n/** Sign a message with Ed25519. Message can be base58-encoded or UTF-8 string. */\nfunction signMessage(message: string, secretKey: Uint8Array): string {\n let messageBytes: Uint8Array;\n try {\n messageBytes = fromBase58(message);\n } catch {\n messageBytes = new TextEncoder().encode(message);\n }\n const signature = nacl.sign.detached(messageBytes, secretKey);\n return toBase58(signature);\n}\n\n/** Derive public key address from a 64-byte secret key. */\nfunction deriveSignerAddress(secretKey: Uint8Array): string {\n const publicKey = secretKey.length === 64\n ? secretKey.slice(32)\n : nacl.sign.keyPair.fromSeed(secretKey).publicKey;\n return toBase58(publicKey);\n}\n\n/** Poll Crossmint for on-chain txId. Returns signature or null. */\nasync function pollForSignature(\n apiBase: string, apiKey: string, wallet: string,\n txId: string, maxPolls: number, pollMs: number,\n initialData?: any,\n): Promise<string | null> {\n let sig = initialData?.onChain?.txId\n || extractSignature(initialData?.onChain?.transaction) || null;\n\n for (let i = 0; i < maxPolls && !sig; i++) {\n await new Promise((r) => setTimeout(r, pollMs));\n const p = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions/${txId}`, undefined, \"GET\");\n if (p.status === \"failed\") {\n throw new Error(`[x402/crossmint] Crossmint tx failed: ${p.error?.message || \"unknown\"}`);\n }\n sig = p.onChain?.txId || extractSignature(p.onChain?.transaction) || null;\n }\n return sig;\n}\n\n/** Build X-PAYMENT header from unsigned tx + on-chain signature + payment requirements. */\nfunction buildXPaymentHeader(unsignedTx: VersionedTransaction, sig: string, accept: any): string {\n const payload = {\n x402Version: 2,\n scheme: \"exact\",\n network: accept.network,\n payload: {\n transaction: Buffer.from(unsignedTx.serialize()).toString(\"base64\"),\n txId: sig,\n },\n accepted: {\n scheme: accept.scheme,\n network: accept.network,\n amount: accept.amount,\n asset: accept.asset,\n payTo: accept.payTo,\n },\n };\n return Buffer.from(JSON.stringify(payload)).toString(\"base64\");\n}\n\n/** Build unsigned SPL transfer tx from 402 payment requirements. */\nasync function buildTransferTx(\n connection: Connection, userPubkey: PublicKey, accept: any,\n): Promise<VersionedTransaction> {\n const mintPubkey = new PublicKey(accept.asset);\n const merchantPubkey = new PublicKey(accept.payTo);\n const mintAccountInfo = await connection.getAccountInfo(mintPubkey);\n const programId = mintAccountInfo?.owner.equals(TOKEN_2022_PROGRAM_ID)\n ? TOKEN_2022_PROGRAM_ID\n : TOKEN_PROGRAM_ID;\n\n const mintInfo = await getMint(connection, mintPubkey, \"confirmed\", programId);\n const amount = BigInt(accept.amount);\n\n const sourceAta = await getAssociatedTokenAddress(mintPubkey, userPubkey, true, programId);\n const destAta = await getAssociatedTokenAddress(mintPubkey, merchantPubkey, true, programId);\n\n const ix = createTransferCheckedInstruction(\n sourceAta, mintPubkey, destAta, userPubkey,\n amount, mintInfo.decimals, [], programId,\n );\n\n const { blockhash } = await connection.getLatestBlockhash(\"confirmed\");\n const msg = new TransactionMessage({\n payerKey: userPubkey,\n recentBlockhash: blockhash,\n instructions: [ix],\n }).compileToV0Message();\n\n return new VersionedTransaction(msg);\n}\n\n// ── Public API ──────────────────────────────────────────────────────\n\n/**\n * **API-key mode.** Create a fetch wrapper that auto-handles x402 `402`\n * responses using a Crossmint smart wallet on Solana.\n *\n * Crossmint signs and broadcasts — no private key needed.\n * The returned function has the same signature as `fetch()`.\n */\nexport function createCrossmintX402Fetch(config: CrossmintX402Config) {\n const { apiKey, wallet, connection } = config;\n const apiBase = config.crossmintApiBase || detectApiBase(apiKey);\n const maxPolls = config.maxPollAttempts ?? 30;\n const pollMs = config.pollIntervalMs ?? 2000;\n const userPubkey = new PublicKey(wallet);\n\n return async function fetch402(url: string, init?: RequestInit): Promise<Response> {\n // 1. Initial request\n const firstResp = await fetch(url, {\n ...init, headers: { Accept: \"application/json\", ...init?.headers },\n });\n if (firstResp.status !== 402) return firstResp;\n\n // 2. Parse 402\n const requirements = (await firstResp.json()) as any;\n const accept = requirements.accepts?.[0];\n if (!accept) throw new Error(\"[x402/crossmint] No payment requirements in 402 response\");\n\n // 3. Build transfer tx\n const unsignedTx = await buildTransferTx(connection, userPubkey, accept);\n\n // 4. Submit to Crossmint (signs + broadcasts)\n const txData = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions`,\n { params: { transaction: toBase58(unsignedTx.serialize()) } });\n\n // 5. Poll for on-chain signature\n const sig = await pollForSignature(apiBase, apiKey, wallet, txData.id, maxPolls, pollMs, txData);\n if (!sig) throw new Error(\"[x402/crossmint] Timed out waiting for tx confirmation\");\n\n // 6. Retry with X-PAYMENT\n const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);\n return fetch(url, {\n ...init, headers: { Accept: \"application/json\", \"X-PAYMENT\": xPayment, ...init?.headers },\n });\n };\n}\n\n/**\n * **Delegated mode.** Create a fetch wrapper that auto-handles x402 `402`\n * responses using a Crossmint smart wallet with external signer approval.\n *\n * Each transaction requires cryptographic approval from `signerSecretKey`\n * before Crossmint broadcasts. This provides an extra security layer —\n * even with the API key, transactions cannot execute without the signer.\n *\n * The external signer must be registered on the Crossmint smart wallet\n * via the Crossmint console or API.\n */\nexport function createCrossmintDelegatedX402Fetch(config: CrossmintDelegatedX402Config) {\n const { apiKey, wallet, connection, signerSecretKey } = config;\n const apiBase = config.crossmintApiBase || detectApiBase(apiKey);\n const maxPolls = config.maxPollAttempts ?? 30;\n const pollMs = config.pollIntervalMs ?? 2000;\n const userPubkey = new PublicKey(wallet);\n const signerAddress = deriveSignerAddress(signerSecretKey);\n\n return async function fetch402(url: string, init?: RequestInit): Promise<Response> {\n // 1. Initial request\n const firstResp = await fetch(url, {\n ...init, headers: { Accept: \"application/json\", ...init?.headers },\n });\n if (firstResp.status !== 402) return firstResp;\n\n // 2. Parse 402\n const requirements = (await firstResp.json()) as any;\n const accept = requirements.accepts?.[0];\n if (!accept) throw new Error(\"[x402/crossmint] No payment requirements in 402 response\");\n\n // 3. Build transfer tx\n const unsignedTx = await buildTransferTx(connection, userPubkey, accept);\n\n // 4. Submit to Crossmint with external signer\n const createResp = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions`,\n {\n params: {\n transaction: toBase58(unsignedTx.serialize()),\n signer: {\n type: \"external-wallet\",\n locator: `external-wallet:${signerAddress}`,\n },\n },\n });\n\n const transactionId = createResp.id;\n const pendingApproval = createResp.approvals?.pending?.[0];\n\n if (!transactionId || !pendingApproval) {\n throw new Error(\n `[x402/crossmint] No pending approval returned. ` +\n `Ensure external signer ${signerAddress} is registered on wallet ${wallet}. ` +\n `Response: ${JSON.stringify(createResp)}`\n );\n }\n\n // 5. Sign the approval message\n const pendingSigner = pendingApproval.signer?.address\n ?? pendingApproval.signer?.locator?.split(\":\")[1]\n ?? signerAddress;\n\n const approvalSig = signMessage(pendingApproval.message, signerSecretKey);\n\n // 6. Submit approval\n const approvalResp = await crossmintApi(apiBase, apiKey,\n `${encodeURIComponent(wallet)}/transactions/${encodeURIComponent(transactionId)}/approvals`,\n {\n approvals: [{\n signer: `external-wallet:${pendingSigner}`,\n signature: approvalSig,\n }],\n });\n\n // 7. Poll for on-chain signature\n const sig = await pollForSignature(\n apiBase, apiKey, wallet, transactionId, maxPolls, pollMs, approvalResp);\n if (!sig) throw new Error(\"[x402/crossmint] Timed out waiting for tx confirmation\");\n\n // 8. Retry with X-PAYMENT\n const xPayment = buildXPaymentHeader(unsignedTx, sig, accept);\n return fetch(url, {\n ...init, headers: { Accept: \"application/json\", \"X-PAYMENT\": xPayment, ...init?.headers },\n });\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relai-fi/x402",
3
- "version": "0.5.20",
3
+ "version": "0.5.22",
4
4
  "description": "Unified x402 payment SDK for Solana, Base, Avalanche, SKALE Base, SKALE BITE, Polygon, and Ethereum. Automatic 402 handling with zero gas fees.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -43,7 +43,8 @@
43
43
  "dependencies": {
44
44
  "@solana/spl-token": "^0.4.14",
45
45
  "@solana/web3.js": "^1.98.4",
46
- "axios": "^1.6.7"
46
+ "axios": "^1.6.7",
47
+ "tweetnacl": "^1.0.3"
47
48
  },
48
49
  "devDependencies": {
49
50
  "@types/node": "^20.19.24",
@@ -75,6 +76,11 @@
75
76
  "import": "./dist/react/index.js",
76
77
  "require": "./dist/react/index.cjs"
77
78
  },
79
+ "./crossmint": {
80
+ "types": "./dist/crossmint.d.ts",
81
+ "import": "./dist/crossmint.js",
82
+ "require": "./dist/crossmint.cjs"
83
+ },
78
84
  "./utils": {
79
85
  "types": "./dist/utils/index.d.ts",
80
86
  "import": "./dist/utils/index.js",