@relai-fi/x402 0.5.21 → 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.
- package/dist/crossmint.cjs +206 -88
- package/dist/crossmint.cjs.map +1 -1
- package/dist/crossmint.d.cts +39 -14
- package/dist/crossmint.d.ts +39 -14
- package/dist/crossmint.js +195 -88
- package/dist/crossmint.js.map +1 -1
- package/package.json +3 -2
package/dist/crossmint.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,17 +17,32 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
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
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/crossmint.ts
|
|
21
31
|
var crossmint_exports = {};
|
|
22
32
|
__export(crossmint_exports, {
|
|
33
|
+
createCrossmintDelegatedX402Fetch: () => createCrossmintDelegatedX402Fetch,
|
|
23
34
|
createCrossmintX402Fetch: () => createCrossmintX402Fetch
|
|
24
35
|
});
|
|
25
36
|
module.exports = __toCommonJS(crossmint_exports);
|
|
26
37
|
var import_web3 = require("@solana/web3.js");
|
|
27
38
|
var import_spl_token = require("@solana/spl-token");
|
|
39
|
+
var import_tweetnacl = __toESM(require("tweetnacl"), 1);
|
|
28
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
|
+
}
|
|
29
46
|
function toBase58(bytes) {
|
|
30
47
|
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
31
48
|
const digits = [0];
|
|
@@ -51,42 +68,135 @@ function toBase58(bytes) {
|
|
|
51
68
|
}
|
|
52
69
|
return str;
|
|
53
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
|
+
}
|
|
54
96
|
function extractSignature(onChainTx) {
|
|
55
97
|
if (!onChainTx) return null;
|
|
56
98
|
if (onChainTx.length <= 100) return onChainTx;
|
|
57
99
|
try {
|
|
58
|
-
const
|
|
59
|
-
const BASE_MAP = new Uint8Array(256).fill(255);
|
|
60
|
-
for (let i = 0; i < ALPHABET.length; i++) BASE_MAP[ALPHABET.charCodeAt(i)] = i;
|
|
61
|
-
const bytes = [0];
|
|
62
|
-
for (const ch of onChainTx) {
|
|
63
|
-
const carry_init = BASE_MAP[ch.charCodeAt(0)];
|
|
64
|
-
if (carry_init === 255) return null;
|
|
65
|
-
let carry = carry_init;
|
|
66
|
-
for (let j = 0; j < bytes.length; j++) {
|
|
67
|
-
carry += bytes[j] * 58;
|
|
68
|
-
bytes[j] = carry & 255;
|
|
69
|
-
carry >>= 8;
|
|
70
|
-
}
|
|
71
|
-
while (carry > 0) {
|
|
72
|
-
bytes.push(carry & 255);
|
|
73
|
-
carry >>= 8;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
for (const ch of onChainTx) {
|
|
77
|
-
if (ch === ALPHABET[0]) bytes.push(0);
|
|
78
|
-
else break;
|
|
79
|
-
}
|
|
80
|
-
const decoded = new Uint8Array(bytes.reverse());
|
|
100
|
+
const decoded = fromBase58(onChainTx);
|
|
81
101
|
const vtx = import_web3.VersionedTransaction.deserialize(decoded);
|
|
82
102
|
if (vtx.signatures?.[0]) return toBase58(vtx.signatures[0]);
|
|
83
103
|
} catch {
|
|
84
104
|
}
|
|
85
105
|
return null;
|
|
86
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
|
+
}
|
|
87
197
|
function createCrossmintX402Fetch(config) {
|
|
88
198
|
const { apiKey, wallet, connection } = config;
|
|
89
|
-
const apiBase = config.crossmintApiBase ||
|
|
199
|
+
const apiBase = config.crossmintApiBase || detectApiBase(apiKey);
|
|
90
200
|
const maxPolls = config.maxPollAttempts ?? 30;
|
|
91
201
|
const pollMs = config.pollIntervalMs ?? 2e3;
|
|
92
202
|
const userPubkey = new import_web3.PublicKey(wallet);
|
|
@@ -99,77 +209,84 @@ function createCrossmintX402Fetch(config) {
|
|
|
99
209
|
const requirements = await firstResp.json();
|
|
100
210
|
const accept = requirements.accepts?.[0];
|
|
101
211
|
if (!accept) throw new Error("[x402/crossmint] No payment requirements in 402 response");
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(mintPubkey, userPubkey, true, programId);
|
|
109
|
-
const destAta = await (0, import_spl_token.getAssociatedTokenAddress)(mintPubkey, merchantPubkey, true, programId);
|
|
110
|
-
const ix = (0, import_spl_token.createTransferCheckedInstruction)(
|
|
111
|
-
sourceAta,
|
|
112
|
-
mintPubkey,
|
|
113
|
-
destAta,
|
|
114
|
-
userPubkey,
|
|
115
|
-
amount,
|
|
116
|
-
mintInfo.decimals,
|
|
117
|
-
[],
|
|
118
|
-
programId
|
|
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()) } }
|
|
119
218
|
);
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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`,
|
|
130
249
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
250
|
+
params: {
|
|
251
|
+
transaction: toBase58(unsignedTx.serialize()),
|
|
252
|
+
signer: {
|
|
253
|
+
type: "external-wallet",
|
|
254
|
+
locator: `external-wallet:${signerAddress}`
|
|
255
|
+
}
|
|
256
|
+
}
|
|
134
257
|
}
|
|
135
258
|
);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
for (let i = 0; i < maxPolls && !sig; i++) {
|
|
142
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
143
|
-
const poll = await fetch(
|
|
144
|
-
`${apiBase}/wallets/${encodeURIComponent(wallet)}/transactions/${txData.id}`,
|
|
145
|
-
{ headers: { "X-API-KEY": apiKey } }
|
|
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)}`
|
|
146
264
|
);
|
|
147
|
-
const p = await poll.json();
|
|
148
|
-
if (p.status === "failed") {
|
|
149
|
-
throw new Error(`[x402/crossmint] Crossmint tx failed: ${p.error?.message || "unknown"}`);
|
|
150
|
-
}
|
|
151
|
-
sig = p.onChain?.txId || extractSignature(p.onChain?.transaction) || null;
|
|
152
|
-
}
|
|
153
|
-
if (!sig) {
|
|
154
|
-
throw new Error("[x402/crossmint] Timed out waiting for Crossmint tx confirmation");
|
|
155
265
|
}
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
amount: accept.amount,
|
|
168
|
-
asset: accept.asset,
|
|
169
|
-
payTo: accept.payTo
|
|
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
|
+
}]
|
|
170
277
|
}
|
|
171
|
-
|
|
172
|
-
const
|
|
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);
|
|
173
290
|
return fetch(url, {
|
|
174
291
|
...init,
|
|
175
292
|
headers: { Accept: "application/json", "X-PAYMENT": xPayment, ...init?.headers }
|
|
@@ -178,6 +295,7 @@ function createCrossmintX402Fetch(config) {
|
|
|
178
295
|
}
|
|
179
296
|
// Annotate the CommonJS export names for ESM import in node:
|
|
180
297
|
0 && (module.exports = {
|
|
298
|
+
createCrossmintDelegatedX402Fetch,
|
|
181
299
|
createCrossmintX402Fetch
|
|
182
300
|
});
|
|
183
301
|
//# sourceMappingURL=crossmint.cjs.map
|
package/dist/crossmint.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/crossmint.ts"],"sourcesContent":["/**\n * Crossmint smart wallet integration for RelAI x402.\n *\n * Auto-handles 402 Payment Required responses using a Crossmint\n * API-key smart wallet on Solana. Zero private keys needed —\n * Crossmint signs and broadcasts, RelAI facilitator verifies on-chain.\n *\n * @example\n * ```ts\n * import { createCrossmintX402Fetch } from \"@relai-fi/x402/crossmint\";\n * import { Connection } from \"@solana/web3.js\";\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 *\n * const resp = await fetch402(\"https://api.example.com/protected\");\n * console.log(await resp.json()); // paid content\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\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\n// ── Internals ───────────────────────────────────────────────────────\n\nconst DEFAULT_API_BASE = \"https://www.crossmint.com/api/2025-06-09\";\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/** 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 // Decode base58 → deserialize VersionedTransaction → first signature\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 onChainTx) {\n const carry_init = BASE_MAP[ch.charCodeAt(0)];\n if (carry_init === 255) return null;\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 onChainTx) {\n if (ch === ALPHABET[0]) bytes.push(0);\n else break;\n }\n const decoded = new Uint8Array(bytes.reverse());\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// ── Public API ──────────────────────────────────────────────────────\n\n/**\n * Create a fetch wrapper that auto-handles x402 `402 Payment Required`\n * responses using a Crossmint smart wallet on Solana.\n *\n * The returned function has the same signature as `fetch()`.\n * On a 402 response it will:\n * 1. Parse payment requirements\n * 2. Build a Solana SPL transfer instruction\n * 3. Submit to Crossmint API (which signs + broadcasts)\n * 4. Poll for on-chain confirmation\n * 5. Retry the original request with an `X-PAYMENT` header\n */\nexport function createCrossmintX402Fetch(config: CrossmintX402Config) {\n const { apiKey, wallet, connection } = config;\n const apiBase = config.crossmintApiBase || DEFAULT_API_BASE;\n const maxPolls = config.maxPollAttempts ?? 30;\n const pollMs = config.pollIntervalMs ?? 2000;\n const userPubkey = new PublicKey(wallet);\n\n return async function fetch402(\n url: string,\n init?: RequestInit,\n ): Promise<Response> {\n // 1. Initial request\n const firstResp = await fetch(url, {\n ...init,\n headers: { Accept: \"application/json\", ...init?.headers },\n });\n if (firstResp.status !== 402) return firstResp;\n\n // 2. Parse payment requirements\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 SPL transfer instruction\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 const unsignedTx = new VersionedTransaction(msg);\n const serializedBase58 = toBase58(unsignedTx.serialize());\n\n // 4. Send to Crossmint (signs + broadcasts)\n const txResp = await fetch(\n `${apiBase}/wallets/${encodeURIComponent(wallet)}/transactions`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-API-KEY\": apiKey },\n body: JSON.stringify({ params: { transaction: serializedBase58 } }),\n },\n );\n const txData = (await txResp.json()) as any;\n if (!txResp.ok || !txData.id) {\n throw new Error(`[x402/crossmint] Crossmint API error: ${JSON.stringify(txData)}`);\n }\n\n // 5. Poll for on-chain signature\n let sig: string | null =\n txData.onChain?.txId || extractSignature(txData.onChain?.transaction) || null;\n\n for (let i = 0; i < maxPolls && !sig; i++) {\n await new Promise((r) => setTimeout(r, pollMs));\n const poll = await fetch(\n `${apiBase}/wallets/${encodeURIComponent(wallet)}/transactions/${txData.id}`,\n { headers: { \"X-API-KEY\": apiKey } },\n );\n const p = (await poll.json()) as any;\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 if (!sig) {\n throw new Error(\"[x402/crossmint] Timed out waiting for Crossmint tx confirmation\");\n }\n\n // 6. Build X-PAYMENT header with pre-broadcast txId\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 const xPayment = Buffer.from(JSON.stringify(payload)).toString(\"base64\");\n\n // 7. Retry with payment\n return fetch(url, {\n ...init,\n headers: { Accept: \"application/json\", \"X-PAYMENT\": xPayment, ...init?.headers },\n });\n };\n}\n"],"mappings":"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","names":[]}
|
|
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"]}
|
package/dist/crossmint.d.cts
CHANGED
|
@@ -3,23 +3,31 @@ import { Connection } from '@solana/web3.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Crossmint smart wallet integration for RelAI x402.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* API-key smart wallet on Solana. Zero private keys needed —
|
|
8
|
-
* Crossmint signs and broadcasts, RelAI facilitator verifies on-chain.
|
|
6
|
+
* Two modes:
|
|
9
7
|
*
|
|
10
|
-
*
|
|
8
|
+
* **API-key mode** — zero private keys, Crossmint signs + broadcasts:
|
|
11
9
|
* ```ts
|
|
12
10
|
* import { createCrossmintX402Fetch } from "@relai-fi/x402/crossmint";
|
|
13
|
-
* import { Connection } from "@solana/web3.js";
|
|
14
11
|
*
|
|
15
12
|
* const fetch402 = createCrossmintX402Fetch({
|
|
16
13
|
* apiKey: process.env.CROSSMINT_API_KEY!,
|
|
17
14
|
* wallet: process.env.CROSSMINT_WALLET!,
|
|
18
15
|
* connection: new Connection(process.env.RPC_URL!),
|
|
19
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";
|
|
20
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
|
+
* });
|
|
21
30
|
* const resp = await fetch402("https://api.example.com/protected");
|
|
22
|
-
* console.log(await resp.json()); // paid content
|
|
23
31
|
* ```
|
|
24
32
|
*
|
|
25
33
|
* @module
|
|
@@ -39,18 +47,35 @@ interface CrossmintX402Config {
|
|
|
39
47
|
/** Polling interval in ms (default: 2000) */
|
|
40
48
|
pollIntervalMs?: number;
|
|
41
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
|
+
}
|
|
42
60
|
/**
|
|
43
|
-
* Create a fetch wrapper that auto-handles x402 `402
|
|
61
|
+
* **API-key mode.** Create a fetch wrapper that auto-handles x402 `402`
|
|
44
62
|
* responses using a Crossmint smart wallet on Solana.
|
|
45
63
|
*
|
|
64
|
+
* Crossmint signs and broadcasts — no private key needed.
|
|
46
65
|
* The returned function has the same signature as `fetch()`.
|
|
47
|
-
* On a 402 response it will:
|
|
48
|
-
* 1. Parse payment requirements
|
|
49
|
-
* 2. Build a Solana SPL transfer instruction
|
|
50
|
-
* 3. Submit to Crossmint API (which signs + broadcasts)
|
|
51
|
-
* 4. Poll for on-chain confirmation
|
|
52
|
-
* 5. Retry the original request with an `X-PAYMENT` header
|
|
53
66
|
*/
|
|
54
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>;
|
|
55
80
|
|
|
56
|
-
export { type CrossmintX402Config, createCrossmintX402Fetch };
|
|
81
|
+
export { type CrossmintDelegatedX402Config, type CrossmintX402Config, createCrossmintDelegatedX402Fetch, createCrossmintX402Fetch };
|
package/dist/crossmint.d.ts
CHANGED
|
@@ -3,23 +3,31 @@ import { Connection } from '@solana/web3.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* Crossmint smart wallet integration for RelAI x402.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* API-key smart wallet on Solana. Zero private keys needed —
|
|
8
|
-
* Crossmint signs and broadcasts, RelAI facilitator verifies on-chain.
|
|
6
|
+
* Two modes:
|
|
9
7
|
*
|
|
10
|
-
*
|
|
8
|
+
* **API-key mode** — zero private keys, Crossmint signs + broadcasts:
|
|
11
9
|
* ```ts
|
|
12
10
|
* import { createCrossmintX402Fetch } from "@relai-fi/x402/crossmint";
|
|
13
|
-
* import { Connection } from "@solana/web3.js";
|
|
14
11
|
*
|
|
15
12
|
* const fetch402 = createCrossmintX402Fetch({
|
|
16
13
|
* apiKey: process.env.CROSSMINT_API_KEY!,
|
|
17
14
|
* wallet: process.env.CROSSMINT_WALLET!,
|
|
18
15
|
* connection: new Connection(process.env.RPC_URL!),
|
|
19
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";
|
|
20
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
|
+
* });
|
|
21
30
|
* const resp = await fetch402("https://api.example.com/protected");
|
|
22
|
-
* console.log(await resp.json()); // paid content
|
|
23
31
|
* ```
|
|
24
32
|
*
|
|
25
33
|
* @module
|
|
@@ -39,18 +47,35 @@ interface CrossmintX402Config {
|
|
|
39
47
|
/** Polling interval in ms (default: 2000) */
|
|
40
48
|
pollIntervalMs?: number;
|
|
41
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
|
+
}
|
|
42
60
|
/**
|
|
43
|
-
* Create a fetch wrapper that auto-handles x402 `402
|
|
61
|
+
* **API-key mode.** Create a fetch wrapper that auto-handles x402 `402`
|
|
44
62
|
* responses using a Crossmint smart wallet on Solana.
|
|
45
63
|
*
|
|
64
|
+
* Crossmint signs and broadcasts — no private key needed.
|
|
46
65
|
* The returned function has the same signature as `fetch()`.
|
|
47
|
-
* On a 402 response it will:
|
|
48
|
-
* 1. Parse payment requirements
|
|
49
|
-
* 2. Build a Solana SPL transfer instruction
|
|
50
|
-
* 3. Submit to Crossmint API (which signs + broadcasts)
|
|
51
|
-
* 4. Poll for on-chain confirmation
|
|
52
|
-
* 5. Retry the original request with an `X-PAYMENT` header
|
|
53
66
|
*/
|
|
54
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>;
|
|
55
80
|
|
|
56
|
-
export { type CrossmintX402Config, createCrossmintX402Fetch };
|
|
81
|
+
export { type CrossmintDelegatedX402Config, type CrossmintX402Config, createCrossmintDelegatedX402Fetch, createCrossmintX402Fetch };
|
package/dist/crossmint.js
CHANGED
|
@@ -11,7 +11,13 @@ import {
|
|
|
11
11
|
TOKEN_PROGRAM_ID,
|
|
12
12
|
TOKEN_2022_PROGRAM_ID
|
|
13
13
|
} from "@solana/spl-token";
|
|
14
|
+
import nacl from "tweetnacl";
|
|
14
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
|
+
}
|
|
15
21
|
function toBase58(bytes) {
|
|
16
22
|
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
17
23
|
const digits = [0];
|
|
@@ -37,42 +43,135 @@ function toBase58(bytes) {
|
|
|
37
43
|
}
|
|
38
44
|
return str;
|
|
39
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
|
+
}
|
|
40
71
|
function extractSignature(onChainTx) {
|
|
41
72
|
if (!onChainTx) return null;
|
|
42
73
|
if (onChainTx.length <= 100) return onChainTx;
|
|
43
74
|
try {
|
|
44
|
-
const
|
|
45
|
-
const BASE_MAP = new Uint8Array(256).fill(255);
|
|
46
|
-
for (let i = 0; i < ALPHABET.length; i++) BASE_MAP[ALPHABET.charCodeAt(i)] = i;
|
|
47
|
-
const bytes = [0];
|
|
48
|
-
for (const ch of onChainTx) {
|
|
49
|
-
const carry_init = BASE_MAP[ch.charCodeAt(0)];
|
|
50
|
-
if (carry_init === 255) return null;
|
|
51
|
-
let carry = carry_init;
|
|
52
|
-
for (let j = 0; j < bytes.length; j++) {
|
|
53
|
-
carry += bytes[j] * 58;
|
|
54
|
-
bytes[j] = carry & 255;
|
|
55
|
-
carry >>= 8;
|
|
56
|
-
}
|
|
57
|
-
while (carry > 0) {
|
|
58
|
-
bytes.push(carry & 255);
|
|
59
|
-
carry >>= 8;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
for (const ch of onChainTx) {
|
|
63
|
-
if (ch === ALPHABET[0]) bytes.push(0);
|
|
64
|
-
else break;
|
|
65
|
-
}
|
|
66
|
-
const decoded = new Uint8Array(bytes.reverse());
|
|
75
|
+
const decoded = fromBase58(onChainTx);
|
|
67
76
|
const vtx = VersionedTransaction.deserialize(decoded);
|
|
68
77
|
if (vtx.signatures?.[0]) return toBase58(vtx.signatures[0]);
|
|
69
78
|
} catch {
|
|
70
79
|
}
|
|
71
80
|
return null;
|
|
72
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
|
+
}
|
|
73
172
|
function createCrossmintX402Fetch(config) {
|
|
74
173
|
const { apiKey, wallet, connection } = config;
|
|
75
|
-
const apiBase = config.crossmintApiBase ||
|
|
174
|
+
const apiBase = config.crossmintApiBase || detectApiBase(apiKey);
|
|
76
175
|
const maxPolls = config.maxPollAttempts ?? 30;
|
|
77
176
|
const pollMs = config.pollIntervalMs ?? 2e3;
|
|
78
177
|
const userPubkey = new PublicKey(wallet);
|
|
@@ -85,77 +184,84 @@ function createCrossmintX402Fetch(config) {
|
|
|
85
184
|
const requirements = await firstResp.json();
|
|
86
185
|
const accept = requirements.accepts?.[0];
|
|
87
186
|
if (!accept) throw new Error("[x402/crossmint] No payment requirements in 402 response");
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const sourceAta = await getAssociatedTokenAddress(mintPubkey, userPubkey, true, programId);
|
|
95
|
-
const destAta = await getAssociatedTokenAddress(mintPubkey, merchantPubkey, true, programId);
|
|
96
|
-
const ix = createTransferCheckedInstruction(
|
|
97
|
-
sourceAta,
|
|
98
|
-
mintPubkey,
|
|
99
|
-
destAta,
|
|
100
|
-
userPubkey,
|
|
101
|
-
amount,
|
|
102
|
-
mintInfo.decimals,
|
|
103
|
-
[],
|
|
104
|
-
programId
|
|
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()) } }
|
|
105
193
|
);
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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`,
|
|
116
224
|
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
225
|
+
params: {
|
|
226
|
+
transaction: toBase58(unsignedTx.serialize()),
|
|
227
|
+
signer: {
|
|
228
|
+
type: "external-wallet",
|
|
229
|
+
locator: `external-wallet:${signerAddress}`
|
|
230
|
+
}
|
|
231
|
+
}
|
|
120
232
|
}
|
|
121
233
|
);
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (let i = 0; i < maxPolls && !sig; i++) {
|
|
128
|
-
await new Promise((r) => setTimeout(r, pollMs));
|
|
129
|
-
const poll = await fetch(
|
|
130
|
-
`${apiBase}/wallets/${encodeURIComponent(wallet)}/transactions/${txData.id}`,
|
|
131
|
-
{ headers: { "X-API-KEY": apiKey } }
|
|
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)}`
|
|
132
239
|
);
|
|
133
|
-
const p = await poll.json();
|
|
134
|
-
if (p.status === "failed") {
|
|
135
|
-
throw new Error(`[x402/crossmint] Crossmint tx failed: ${p.error?.message || "unknown"}`);
|
|
136
|
-
}
|
|
137
|
-
sig = p.onChain?.txId || extractSignature(p.onChain?.transaction) || null;
|
|
138
|
-
}
|
|
139
|
-
if (!sig) {
|
|
140
|
-
throw new Error("[x402/crossmint] Timed out waiting for Crossmint tx confirmation");
|
|
141
240
|
}
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
amount: accept.amount,
|
|
154
|
-
asset: accept.asset,
|
|
155
|
-
payTo: accept.payTo
|
|
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
|
+
}]
|
|
156
252
|
}
|
|
157
|
-
|
|
158
|
-
const
|
|
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);
|
|
159
265
|
return fetch(url, {
|
|
160
266
|
...init,
|
|
161
267
|
headers: { Accept: "application/json", "X-PAYMENT": xPayment, ...init?.headers }
|
|
@@ -163,6 +269,7 @@ function createCrossmintX402Fetch(config) {
|
|
|
163
269
|
};
|
|
164
270
|
}
|
|
165
271
|
export {
|
|
272
|
+
createCrossmintDelegatedX402Fetch,
|
|
166
273
|
createCrossmintX402Fetch
|
|
167
274
|
};
|
|
168
275
|
//# sourceMappingURL=crossmint.js.map
|
package/dist/crossmint.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/crossmint.ts"],"sourcesContent":["/**\n * Crossmint smart wallet integration for RelAI x402.\n *\n * Auto-handles 402 Payment Required responses using a Crossmint\n * API-key smart wallet on Solana. Zero private keys needed —\n * Crossmint signs and broadcasts, RelAI facilitator verifies on-chain.\n *\n * @example\n * ```ts\n * import { createCrossmintX402Fetch } from \"@relai-fi/x402/crossmint\";\n * import { Connection } from \"@solana/web3.js\";\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 *\n * const resp = await fetch402(\"https://api.example.com/protected\");\n * console.log(await resp.json()); // paid content\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\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\n// ── Internals ───────────────────────────────────────────────────────\n\nconst DEFAULT_API_BASE = \"https://www.crossmint.com/api/2025-06-09\";\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/** 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 // Decode base58 → deserialize VersionedTransaction → first signature\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 onChainTx) {\n const carry_init = BASE_MAP[ch.charCodeAt(0)];\n if (carry_init === 255) return null;\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 onChainTx) {\n if (ch === ALPHABET[0]) bytes.push(0);\n else break;\n }\n const decoded = new Uint8Array(bytes.reverse());\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// ── Public API ──────────────────────────────────────────────────────\n\n/**\n * Create a fetch wrapper that auto-handles x402 `402 Payment Required`\n * responses using a Crossmint smart wallet on Solana.\n *\n * The returned function has the same signature as `fetch()`.\n * On a 402 response it will:\n * 1. Parse payment requirements\n * 2. Build a Solana SPL transfer instruction\n * 3. Submit to Crossmint API (which signs + broadcasts)\n * 4. Poll for on-chain confirmation\n * 5. Retry the original request with an `X-PAYMENT` header\n */\nexport function createCrossmintX402Fetch(config: CrossmintX402Config) {\n const { apiKey, wallet, connection } = config;\n const apiBase = config.crossmintApiBase || DEFAULT_API_BASE;\n const maxPolls = config.maxPollAttempts ?? 30;\n const pollMs = config.pollIntervalMs ?? 2000;\n const userPubkey = new PublicKey(wallet);\n\n return async function fetch402(\n url: string,\n init?: RequestInit,\n ): Promise<Response> {\n // 1. Initial request\n const firstResp = await fetch(url, {\n ...init,\n headers: { Accept: \"application/json\", ...init?.headers },\n });\n if (firstResp.status !== 402) return firstResp;\n\n // 2. Parse payment requirements\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 SPL transfer instruction\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 const unsignedTx = new VersionedTransaction(msg);\n const serializedBase58 = toBase58(unsignedTx.serialize());\n\n // 4. Send to Crossmint (signs + broadcasts)\n const txResp = await fetch(\n `${apiBase}/wallets/${encodeURIComponent(wallet)}/transactions`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-API-KEY\": apiKey },\n body: JSON.stringify({ params: { transaction: serializedBase58 } }),\n },\n );\n const txData = (await txResp.json()) as any;\n if (!txResp.ok || !txData.id) {\n throw new Error(`[x402/crossmint] Crossmint API error: ${JSON.stringify(txData)}`);\n }\n\n // 5. Poll for on-chain signature\n let sig: string | null =\n txData.onChain?.txId || extractSignature(txData.onChain?.transaction) || null;\n\n for (let i = 0; i < maxPolls && !sig; i++) {\n await new Promise((r) => setTimeout(r, pollMs));\n const poll = await fetch(\n `${apiBase}/wallets/${encodeURIComponent(wallet)}/transactions/${txData.id}`,\n { headers: { \"X-API-KEY\": apiKey } },\n );\n const p = (await poll.json()) as any;\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 if (!sig) {\n throw new Error(\"[x402/crossmint] Timed out waiting for Crossmint tx confirmation\");\n }\n\n // 6. Build X-PAYMENT header with pre-broadcast txId\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 const xPayment = Buffer.from(JSON.stringify(payload)).toString(\"base64\");\n\n // 7. Retry with payment\n return fetch(url, {\n ...init,\n headers: { Accept: \"application/json\", \"X-PAYMENT\": xPayment, ...init?.headers },\n });\n };\n}\n"],"mappings":";AAwBA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA0BP,IAAM,mBAAmB;AAGzB,SAAS,SAAS,OAA2B;AAC3C,QAAM,WAAW;AACjB,QAAM,SAAS,CAAC,CAAC;AACjB,aAAW,QAAQ,OAAO;AACxB,QAAI,QAAQ;AACZ,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,eAAS,OAAO,CAAC,KAAK;AACtB,aAAO,CAAC,IAAI,QAAQ;AACpB,cAAS,QAAQ,KAAM;AAAA,IACzB;AACA,WAAO,QAAQ,GAAG;AAChB,aAAO,KAAK,QAAQ,EAAE;AACtB,cAAS,QAAQ,KAAM;AAAA,IACzB;AAAA,EACF;AACA,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,EAAG,QAAO,SAAS,CAAC;AAAA,QAC5B;AAAA,EACP;AACA,WAAS,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK;AAC3C,WAAO,SAAS,OAAO,CAAC,CAAC;AAAA,EAC3B;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,WAAkC;AAC1D,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI,UAAU,UAAU,IAAK,QAAO;AACpC,MAAI;AAEF,UAAM,WAAW;AACjB,UAAM,WAAW,IAAI,WAAW,GAAG,EAAE,KAAK,GAAG;AAC7C,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,IAAK,UAAS,SAAS,WAAW,CAAC,CAAC,IAAI;AAC7E,UAAM,QAAkB,CAAC,CAAC;AAC1B,eAAW,MAAM,WAAW;AAC1B,YAAM,aAAa,SAAS,GAAG,WAAW,CAAC,CAAC;AAC5C,UAAI,eAAe,IAAK,QAAO;AAC/B,UAAI,QAAQ;AACZ,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,iBAAS,MAAM,CAAC,IAAI;AACpB,cAAM,CAAC,IAAI,QAAQ;AACnB,kBAAU;AAAA,MACZ;AACA,aAAO,QAAQ,GAAG;AAChB,cAAM,KAAK,QAAQ,GAAI;AACvB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,eAAW,MAAM,WAAW;AAC1B,UAAI,OAAO,SAAS,CAAC,EAAG,OAAM,KAAK,CAAC;AAAA,UAC/B;AAAA,IACP;AACA,UAAM,UAAU,IAAI,WAAW,MAAM,QAAQ,CAAC;AAC9C,UAAM,MAAM,qBAAqB,YAAY,OAAO;AACpD,QAAI,IAAI,aAAa,CAAC,EAAG,QAAO,SAAS,IAAI,WAAW,CAAC,CAAC;AAAA,EAC5D,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAgBO,SAAS,yBAAyB,QAA6B;AACpE,QAAM,EAAE,QAAQ,QAAQ,WAAW,IAAI;AACvC,QAAM,UAAU,OAAO,oBAAoB;AAC3C,QAAM,WAAW,OAAO,mBAAmB;AAC3C,QAAM,SAAS,OAAO,kBAAkB;AACxC,QAAM,aAAa,IAAI,UAAU,MAAM;AAEvC,SAAO,eAAe,SACpB,KACA,MACmB;AAEnB,UAAM,YAAY,MAAM,MAAM,KAAK;AAAA,MACjC,GAAG;AAAA,MACH,SAAS,EAAE,QAAQ,oBAAoB,GAAG,MAAM,QAAQ;AAAA,IAC1D,CAAC;AACD,QAAI,UAAU,WAAW,IAAK,QAAO;AAGrC,UAAM,eAAgB,MAAM,UAAU,KAAK;AAC3C,UAAM,SAAS,aAAa,UAAU,CAAC;AACvC,QAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,0DAA0D;AAGvF,UAAM,aAAa,IAAI,UAAU,OAAO,KAAK;AAC7C,UAAM,iBAAiB,IAAI,UAAU,OAAO,KAAK;AACjD,UAAM,kBAAkB,MAAM,WAAW,eAAe,UAAU;AAClE,UAAM,YAAY,iBAAiB,MAAM,OAAO,qBAAqB,IACjE,wBACA;AAEJ,UAAM,WAAW,MAAM,QAAQ,YAAY,YAAY,aAAa,SAAS;AAC7E,UAAM,SAAS,OAAO,OAAO,MAAM;AAEnC,UAAM,YAAY,MAAM,0BAA0B,YAAY,YAAY,MAAM,SAAS;AACzF,UAAM,UAAU,MAAM,0BAA0B,YAAY,gBAAgB,MAAM,SAAS;AAE3F,UAAM,KAAK;AAAA,MACT;AAAA,MAAW;AAAA,MAAY;AAAA,MAAS;AAAA,MAChC;AAAA,MAAQ,SAAS;AAAA,MAAU,CAAC;AAAA,MAAG;AAAA,IACjC;AAEA,UAAM,EAAE,UAAU,IAAI,MAAM,WAAW,mBAAmB,WAAW;AACrE,UAAM,MAAM,IAAI,mBAAmB;AAAA,MACjC,UAAU;AAAA,MACV,iBAAiB;AAAA,MACjB,cAAc,CAAC,EAAE;AAAA,IACnB,CAAC,EAAE,mBAAmB;AAEtB,UAAM,aAAa,IAAI,qBAAqB,GAAG;AAC/C,UAAM,mBAAmB,SAAS,WAAW,UAAU,CAAC;AAGxD,UAAM,SAAS,MAAM;AAAA,MACnB,GAAG,OAAO,YAAY,mBAAmB,MAAM,CAAC;AAAA,MAChD;AAAA,QACE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oBAAoB,aAAa,OAAO;AAAA,QACnE,MAAM,KAAK,UAAU,EAAE,QAAQ,EAAE,aAAa,iBAAiB,EAAE,CAAC;AAAA,MACpE;AAAA,IACF;AACA,UAAM,SAAU,MAAM,OAAO,KAAK;AAClC,QAAI,CAAC,OAAO,MAAM,CAAC,OAAO,IAAI;AAC5B,YAAM,IAAI,MAAM,yCAAyC,KAAK,UAAU,MAAM,CAAC,EAAE;AAAA,IACnF;AAGA,QAAI,MACF,OAAO,SAAS,QAAQ,iBAAiB,OAAO,SAAS,WAAW,KAAK;AAE3E,aAAS,IAAI,GAAG,IAAI,YAAY,CAAC,KAAK,KAAK;AACzC,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAC9C,YAAM,OAAO,MAAM;AAAA,QACjB,GAAG,OAAO,YAAY,mBAAmB,MAAM,CAAC,iBAAiB,OAAO,EAAE;AAAA,QAC1E,EAAE,SAAS,EAAE,aAAa,OAAO,EAAE;AAAA,MACrC;AACA,YAAM,IAAK,MAAM,KAAK,KAAK;AAC3B,UAAI,EAAE,WAAW,UAAU;AACzB,cAAM,IAAI,MAAM,yCAAyC,EAAE,OAAO,WAAW,SAAS,EAAE;AAAA,MAC1F;AACA,YAAM,EAAE,SAAS,QAAQ,iBAAiB,EAAE,SAAS,WAAW,KAAK;AAAA,IACvE;AACA,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAGA,UAAM,UAAU;AAAA,MACd,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,OAAO;AAAA,MAChB,SAAS;AAAA,QACP,aAAa,OAAO,KAAK,WAAW,UAAU,CAAC,EAAE,SAAS,QAAQ;AAAA,QAClE,MAAM;AAAA,MACR;AAAA,MACA,UAAU;AAAA,QACR,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO;AAAA,QAChB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,OAAO,OAAO;AAAA,MAChB;AAAA,IACF;AACA,UAAM,WAAW,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC,EAAE,SAAS,QAAQ;AAGvE,WAAO,MAAM,KAAK;AAAA,MAChB,GAAG;AAAA,MACH,SAAS,EAAE,QAAQ,oBAAoB,aAAa,UAAU,GAAG,MAAM,QAAQ;AAAA,IACjF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
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.
|
|
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",
|