@dexterai/x402 1.7.2 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -6
- package/dist/adapters/index.cjs +419 -412
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +11 -5
- package/dist/adapters/index.d.ts +11 -5
- package/dist/adapters/index.js +395 -415
- package/dist/adapters/index.js.map +1 -1
- package/dist/client/index.cjs +585 -566
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +6 -6
- package/dist/client/index.d.ts +6 -6
- package/dist/client/index.js +595 -572
- package/dist/client/index.js.map +1 -1
- package/dist/react/index.cjs +415 -416
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +410 -413
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +220 -94
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +53 -36
- package/dist/server/index.d.ts +53 -36
- package/dist/server/index.js +220 -94
- package/dist/server/index.js.map +1 -1
- package/dist/{solana-BWNn6iue.d.cts → solana-BeGAqPta.d.cts} +16 -4
- package/dist/{solana-q6Na2BF-.d.ts → solana-CQD9yMju.d.ts} +16 -4
- package/dist/{types-BzL-q-AE.d.cts → types-B477nBpg.d.cts} +7 -2
- package/dist/{types-By5Hhdmr.d.ts → types-BWnUAPvD.d.ts} +7 -2
- package/dist/{x402-client-rG7xFxSR.d.ts → x402-client-D9b3PHai.d.ts} +23 -2
- package/dist/{x402-client-BvQfgGwD.d.cts → x402-client-Dk9q2QQF.d.cts} +23 -2
- package/package.json +13 -3
package/dist/client/index.js
CHANGED
|
@@ -1,9 +1,180 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
1
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
3
|
var __esm = (fn, res) => function __init() {
|
|
3
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
4
5
|
};
|
|
5
|
-
var
|
|
6
|
-
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// node_modules/base-x/src/esm/index.js
|
|
12
|
+
function base(ALPHABET2) {
|
|
13
|
+
if (ALPHABET2.length >= 255) {
|
|
14
|
+
throw new TypeError("Alphabet too long");
|
|
15
|
+
}
|
|
16
|
+
const BASE_MAP = new Uint8Array(256);
|
|
17
|
+
for (let j = 0; j < BASE_MAP.length; j++) {
|
|
18
|
+
BASE_MAP[j] = 255;
|
|
19
|
+
}
|
|
20
|
+
for (let i = 0; i < ALPHABET2.length; i++) {
|
|
21
|
+
const x = ALPHABET2.charAt(i);
|
|
22
|
+
const xc = x.charCodeAt(0);
|
|
23
|
+
if (BASE_MAP[xc] !== 255) {
|
|
24
|
+
throw new TypeError(x + " is ambiguous");
|
|
25
|
+
}
|
|
26
|
+
BASE_MAP[xc] = i;
|
|
27
|
+
}
|
|
28
|
+
const BASE = ALPHABET2.length;
|
|
29
|
+
const LEADER = ALPHABET2.charAt(0);
|
|
30
|
+
const FACTOR = Math.log(BASE) / Math.log(256);
|
|
31
|
+
const iFACTOR = Math.log(256) / Math.log(BASE);
|
|
32
|
+
function encode(source) {
|
|
33
|
+
if (source instanceof Uint8Array) {
|
|
34
|
+
} else if (ArrayBuffer.isView(source)) {
|
|
35
|
+
source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
36
|
+
} else if (Array.isArray(source)) {
|
|
37
|
+
source = Uint8Array.from(source);
|
|
38
|
+
}
|
|
39
|
+
if (!(source instanceof Uint8Array)) {
|
|
40
|
+
throw new TypeError("Expected Uint8Array");
|
|
41
|
+
}
|
|
42
|
+
if (source.length === 0) {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
let zeroes = 0;
|
|
46
|
+
let length = 0;
|
|
47
|
+
let pbegin = 0;
|
|
48
|
+
const pend = source.length;
|
|
49
|
+
while (pbegin !== pend && source[pbegin] === 0) {
|
|
50
|
+
pbegin++;
|
|
51
|
+
zeroes++;
|
|
52
|
+
}
|
|
53
|
+
const size = (pend - pbegin) * iFACTOR + 1 >>> 0;
|
|
54
|
+
const b58 = new Uint8Array(size);
|
|
55
|
+
while (pbegin !== pend) {
|
|
56
|
+
let carry = source[pbegin];
|
|
57
|
+
let i = 0;
|
|
58
|
+
for (let it1 = size - 1; (carry !== 0 || i < length) && it1 !== -1; it1--, i++) {
|
|
59
|
+
carry += 256 * b58[it1] >>> 0;
|
|
60
|
+
b58[it1] = carry % BASE >>> 0;
|
|
61
|
+
carry = carry / BASE >>> 0;
|
|
62
|
+
}
|
|
63
|
+
if (carry !== 0) {
|
|
64
|
+
throw new Error("Non-zero carry");
|
|
65
|
+
}
|
|
66
|
+
length = i;
|
|
67
|
+
pbegin++;
|
|
68
|
+
}
|
|
69
|
+
let it2 = size - length;
|
|
70
|
+
while (it2 !== size && b58[it2] === 0) {
|
|
71
|
+
it2++;
|
|
72
|
+
}
|
|
73
|
+
let str = LEADER.repeat(zeroes);
|
|
74
|
+
for (; it2 < size; ++it2) {
|
|
75
|
+
str += ALPHABET2.charAt(b58[it2]);
|
|
76
|
+
}
|
|
77
|
+
return str;
|
|
78
|
+
}
|
|
79
|
+
function decodeUnsafe(source) {
|
|
80
|
+
if (typeof source !== "string") {
|
|
81
|
+
throw new TypeError("Expected String");
|
|
82
|
+
}
|
|
83
|
+
if (source.length === 0) {
|
|
84
|
+
return new Uint8Array();
|
|
85
|
+
}
|
|
86
|
+
let psz = 0;
|
|
87
|
+
let zeroes = 0;
|
|
88
|
+
let length = 0;
|
|
89
|
+
while (source[psz] === LEADER) {
|
|
90
|
+
zeroes++;
|
|
91
|
+
psz++;
|
|
92
|
+
}
|
|
93
|
+
const size = (source.length - psz) * FACTOR + 1 >>> 0;
|
|
94
|
+
const b256 = new Uint8Array(size);
|
|
95
|
+
while (psz < source.length) {
|
|
96
|
+
const charCode = source.charCodeAt(psz);
|
|
97
|
+
if (charCode > 255) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let carry = BASE_MAP[charCode];
|
|
101
|
+
if (carry === 255) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let i = 0;
|
|
105
|
+
for (let it3 = size - 1; (carry !== 0 || i < length) && it3 !== -1; it3--, i++) {
|
|
106
|
+
carry += BASE * b256[it3] >>> 0;
|
|
107
|
+
b256[it3] = carry % 256 >>> 0;
|
|
108
|
+
carry = carry / 256 >>> 0;
|
|
109
|
+
}
|
|
110
|
+
if (carry !== 0) {
|
|
111
|
+
throw new Error("Non-zero carry");
|
|
112
|
+
}
|
|
113
|
+
length = i;
|
|
114
|
+
psz++;
|
|
115
|
+
}
|
|
116
|
+
let it4 = size - length;
|
|
117
|
+
while (it4 !== size && b256[it4] === 0) {
|
|
118
|
+
it4++;
|
|
119
|
+
}
|
|
120
|
+
const vch = new Uint8Array(zeroes + (size - it4));
|
|
121
|
+
let j = zeroes;
|
|
122
|
+
while (it4 !== size) {
|
|
123
|
+
vch[j++] = b256[it4++];
|
|
124
|
+
}
|
|
125
|
+
return vch;
|
|
126
|
+
}
|
|
127
|
+
function decode(string) {
|
|
128
|
+
const buffer = decodeUnsafe(string);
|
|
129
|
+
if (buffer) {
|
|
130
|
+
return buffer;
|
|
131
|
+
}
|
|
132
|
+
throw new Error("Non-base" + BASE + " character");
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
encode,
|
|
136
|
+
decodeUnsafe,
|
|
137
|
+
decode
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
var esm_default;
|
|
141
|
+
var init_esm = __esm({
|
|
142
|
+
"node_modules/base-x/src/esm/index.js"() {
|
|
143
|
+
"use strict";
|
|
144
|
+
esm_default = base;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// node_modules/bs58/src/esm/index.js
|
|
149
|
+
var esm_exports = {};
|
|
150
|
+
__export(esm_exports, {
|
|
151
|
+
default: () => esm_default2
|
|
152
|
+
});
|
|
153
|
+
var ALPHABET, esm_default2;
|
|
154
|
+
var init_esm2 = __esm({
|
|
155
|
+
"node_modules/bs58/src/esm/index.js"() {
|
|
156
|
+
"use strict";
|
|
157
|
+
init_esm();
|
|
158
|
+
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
159
|
+
esm_default2 = esm_default(ALPHABET);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// src/types.ts
|
|
164
|
+
var USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
165
|
+
var DEXTER_FACILITATOR_URL = "https://x402.dexter.cash";
|
|
166
|
+
var X402Error = class _X402Error extends Error {
|
|
167
|
+
/** Error code for programmatic handling */
|
|
168
|
+
code;
|
|
169
|
+
/** Additional error details */
|
|
170
|
+
details;
|
|
171
|
+
constructor(code, message, details) {
|
|
172
|
+
super(message);
|
|
173
|
+
this.name = "X402Error";
|
|
174
|
+
this.code = code;
|
|
175
|
+
this.details = details;
|
|
176
|
+
Object.setPrototypeOf(this, _X402Error.prototype);
|
|
177
|
+
}
|
|
7
178
|
};
|
|
8
179
|
|
|
9
180
|
// src/adapters/solana.ts
|
|
@@ -22,568 +193,420 @@ import {
|
|
|
22
193
|
TOKEN_PROGRAM_ID,
|
|
23
194
|
TOKEN_2022_PROGRAM_ID
|
|
24
195
|
} from "@solana/spl-token";
|
|
196
|
+
var SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
197
|
+
var SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
198
|
+
var SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
|
|
199
|
+
var DEFAULT_RPC_URLS = {
|
|
200
|
+
[SOLANA_MAINNET]: "https://api.dexter.cash/api/solana/rpc",
|
|
201
|
+
[SOLANA_DEVNET]: "https://api.devnet.solana.com",
|
|
202
|
+
[SOLANA_TESTNET]: "https://api.testnet.solana.com"
|
|
203
|
+
};
|
|
204
|
+
var DEFAULT_COMPUTE_UNIT_LIMIT = 12e3;
|
|
205
|
+
var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
25
206
|
function isSolanaWallet(wallet) {
|
|
26
207
|
if (!wallet || typeof wallet !== "object") return false;
|
|
27
208
|
const w = wallet;
|
|
28
209
|
return "publicKey" in w && "signTransaction" in w && typeof w.signTransaction === "function";
|
|
29
210
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
39
|
-
SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
|
|
40
|
-
DEFAULT_RPC_URLS = {
|
|
41
|
-
[SOLANA_MAINNET]: "https://api.dexter.cash/api/solana/rpc",
|
|
42
|
-
[SOLANA_DEVNET]: "https://api.devnet.solana.com",
|
|
43
|
-
[SOLANA_TESTNET]: "https://api.testnet.solana.com"
|
|
211
|
+
var SolanaAdapter = class {
|
|
212
|
+
name = "Solana";
|
|
213
|
+
networks = [SOLANA_MAINNET, SOLANA_DEVNET, SOLANA_TESTNET];
|
|
214
|
+
config;
|
|
215
|
+
log;
|
|
216
|
+
constructor(config = {}) {
|
|
217
|
+
this.config = config;
|
|
218
|
+
this.log = config.verbose ? console.log.bind(console, "[x402:solana]") : () => {
|
|
44
219
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
197
|
-
const message = new TransactionMessage({
|
|
198
|
-
payerKey: feePayerPubkey,
|
|
199
|
-
recentBlockhash: blockhash,
|
|
200
|
-
instructions
|
|
201
|
-
}).compileToV0Message();
|
|
202
|
-
const transaction = new VersionedTransaction(message);
|
|
203
|
-
const signedTx = await wallet.signTransaction(transaction);
|
|
204
|
-
this.log("Transaction signed successfully");
|
|
205
|
-
return {
|
|
206
|
-
serialized: Buffer.from(signedTx.serialize()).toString("base64")
|
|
207
|
-
};
|
|
208
|
-
}
|
|
220
|
+
}
|
|
221
|
+
canHandle(network) {
|
|
222
|
+
if (this.networks.includes(network)) return true;
|
|
223
|
+
if (network === "solana") return true;
|
|
224
|
+
if (network === "solana-devnet") return true;
|
|
225
|
+
if (network === "solana-testnet") return true;
|
|
226
|
+
if (network.startsWith("solana:")) return true;
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
getDefaultRpcUrl(network) {
|
|
230
|
+
if (this.config.rpcUrls?.[network]) {
|
|
231
|
+
return this.config.rpcUrls[network];
|
|
232
|
+
}
|
|
233
|
+
if (DEFAULT_RPC_URLS[network]) {
|
|
234
|
+
return DEFAULT_RPC_URLS[network];
|
|
235
|
+
}
|
|
236
|
+
if (network === "solana") return DEFAULT_RPC_URLS[SOLANA_MAINNET];
|
|
237
|
+
if (network === "solana-devnet") return DEFAULT_RPC_URLS[SOLANA_DEVNET];
|
|
238
|
+
if (network === "solana-testnet") return DEFAULT_RPC_URLS[SOLANA_TESTNET];
|
|
239
|
+
return DEFAULT_RPC_URLS[SOLANA_MAINNET];
|
|
240
|
+
}
|
|
241
|
+
getAddress(wallet) {
|
|
242
|
+
if (!isSolanaWallet(wallet)) return null;
|
|
243
|
+
return wallet.publicKey?.toBase58() ?? null;
|
|
244
|
+
}
|
|
245
|
+
isConnected(wallet) {
|
|
246
|
+
if (!isSolanaWallet(wallet)) return false;
|
|
247
|
+
return wallet.publicKey !== null;
|
|
248
|
+
}
|
|
249
|
+
async getBalance(accept, wallet, rpcUrl) {
|
|
250
|
+
if (!isSolanaWallet(wallet) || !wallet.publicKey) {
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
254
|
+
const connection = new Connection(url, "confirmed");
|
|
255
|
+
const userPubkey = new PublicKey(wallet.publicKey.toBase58());
|
|
256
|
+
const mintPubkey = new PublicKey(accept.asset);
|
|
257
|
+
try {
|
|
258
|
+
const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
|
|
259
|
+
const programId = mintInfo?.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
260
|
+
const ata = await getAssociatedTokenAddress(
|
|
261
|
+
mintPubkey,
|
|
262
|
+
userPubkey,
|
|
263
|
+
false,
|
|
264
|
+
programId
|
|
265
|
+
);
|
|
266
|
+
const account = await getAccount(connection, ata, void 0, programId);
|
|
267
|
+
const decimals = accept.extra?.decimals ?? 6;
|
|
268
|
+
return Number(account.amount) / Math.pow(10, decimals);
|
|
269
|
+
} catch {
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async buildTransaction(accept, wallet, rpcUrl) {
|
|
274
|
+
if (!isSolanaWallet(wallet)) {
|
|
275
|
+
throw new Error("Invalid Solana wallet");
|
|
276
|
+
}
|
|
277
|
+
if (!wallet.publicKey) {
|
|
278
|
+
throw new Error("Wallet not connected");
|
|
279
|
+
}
|
|
280
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
281
|
+
const connection = new Connection(url, "confirmed");
|
|
282
|
+
const userPubkey = new PublicKey(wallet.publicKey.toBase58());
|
|
283
|
+
const { payTo, asset, extra } = accept;
|
|
284
|
+
const amount = accept.amount || accept.maxAmountRequired;
|
|
285
|
+
if (!amount) {
|
|
286
|
+
throw new Error("Missing amount in payment requirements");
|
|
287
|
+
}
|
|
288
|
+
if (!extra?.feePayer) {
|
|
289
|
+
throw new Error("Missing feePayer in payment requirements");
|
|
290
|
+
}
|
|
291
|
+
const feePayerPubkey = new PublicKey(extra.feePayer);
|
|
292
|
+
const mintPubkey = new PublicKey(asset);
|
|
293
|
+
const destinationPubkey = new PublicKey(payTo);
|
|
294
|
+
this.log("Building transaction:", {
|
|
295
|
+
from: userPubkey.toBase58(),
|
|
296
|
+
to: payTo,
|
|
297
|
+
amount,
|
|
298
|
+
asset,
|
|
299
|
+
feePayer: extra.feePayer
|
|
300
|
+
});
|
|
301
|
+
const instructions = [];
|
|
302
|
+
instructions.push(
|
|
303
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
304
|
+
units: DEFAULT_COMPUTE_UNIT_LIMIT
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
instructions.push(
|
|
308
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
309
|
+
microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
|
|
313
|
+
if (!mintInfo) {
|
|
314
|
+
throw new Error(`Token mint ${asset} not found`);
|
|
315
|
+
}
|
|
316
|
+
const programId = mintInfo.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
|
|
317
|
+
const mint = await getMint(connection, mintPubkey, void 0, programId);
|
|
318
|
+
if (typeof extra?.decimals === "number" && mint.decimals !== extra.decimals) {
|
|
319
|
+
this.log(
|
|
320
|
+
`Decimals mismatch: requirements say ${extra.decimals}, mint says ${mint.decimals}`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
const sourceAta = await getAssociatedTokenAddress(
|
|
324
|
+
mintPubkey,
|
|
325
|
+
userPubkey,
|
|
326
|
+
false,
|
|
327
|
+
programId
|
|
328
|
+
);
|
|
329
|
+
const destinationAta = await getAssociatedTokenAddress(
|
|
330
|
+
mintPubkey,
|
|
331
|
+
destinationPubkey,
|
|
332
|
+
false,
|
|
333
|
+
programId
|
|
334
|
+
);
|
|
335
|
+
const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
|
|
336
|
+
if (!sourceAtaInfo) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`No token account found for ${asset}. Please ensure you have USDC in your wallet.`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
|
|
342
|
+
if (!destAtaInfo) {
|
|
343
|
+
throw new Error(
|
|
344
|
+
`Seller token account not found. The seller (${payTo}) must have a USDC account.`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
const amountBigInt = BigInt(amount);
|
|
348
|
+
instructions.push(
|
|
349
|
+
createTransferCheckedInstruction(
|
|
350
|
+
sourceAta,
|
|
351
|
+
mintPubkey,
|
|
352
|
+
destinationAta,
|
|
353
|
+
userPubkey,
|
|
354
|
+
amountBigInt,
|
|
355
|
+
mint.decimals,
|
|
356
|
+
[],
|
|
357
|
+
programId
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
361
|
+
const message = new TransactionMessage({
|
|
362
|
+
payerKey: feePayerPubkey,
|
|
363
|
+
recentBlockhash: blockhash,
|
|
364
|
+
instructions
|
|
365
|
+
}).compileToV0Message();
|
|
366
|
+
const transaction = new VersionedTransaction(message);
|
|
367
|
+
const signedTx = await wallet.signTransaction(transaction);
|
|
368
|
+
this.log("Transaction signed successfully");
|
|
369
|
+
return {
|
|
370
|
+
serialized: Buffer.from(signedTx.serialize()).toString("base64")
|
|
209
371
|
};
|
|
210
372
|
}
|
|
211
|
-
}
|
|
373
|
+
};
|
|
374
|
+
function createSolanaAdapter(config) {
|
|
375
|
+
return new SolanaAdapter(config);
|
|
376
|
+
}
|
|
212
377
|
|
|
213
378
|
// src/adapters/evm.ts
|
|
379
|
+
var BASE_MAINNET = "eip155:8453";
|
|
380
|
+
var BASE_SEPOLIA = "eip155:84532";
|
|
381
|
+
var ARBITRUM_ONE = "eip155:42161";
|
|
382
|
+
var POLYGON = "eip155:137";
|
|
383
|
+
var OPTIMISM = "eip155:10";
|
|
384
|
+
var AVALANCHE = "eip155:43114";
|
|
385
|
+
var SKALE_BASE = "eip155:1187947933";
|
|
386
|
+
var SKALE_BASE_SEPOLIA = "eip155:324705682";
|
|
387
|
+
var ETHEREUM_MAINNET = "eip155:1";
|
|
388
|
+
var CHAIN_IDS = {
|
|
389
|
+
[BASE_MAINNET]: 8453,
|
|
390
|
+
[BASE_SEPOLIA]: 84532,
|
|
391
|
+
[ARBITRUM_ONE]: 42161,
|
|
392
|
+
[POLYGON]: 137,
|
|
393
|
+
[OPTIMISM]: 10,
|
|
394
|
+
[AVALANCHE]: 43114,
|
|
395
|
+
[SKALE_BASE]: 1187947933,
|
|
396
|
+
[SKALE_BASE_SEPOLIA]: 324705682,
|
|
397
|
+
[ETHEREUM_MAINNET]: 1
|
|
398
|
+
};
|
|
399
|
+
var DEFAULT_RPC_URLS2 = {
|
|
400
|
+
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
401
|
+
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
402
|
+
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc",
|
|
403
|
+
[POLYGON]: "https://polygon-rpc.com",
|
|
404
|
+
[OPTIMISM]: "https://mainnet.optimism.io",
|
|
405
|
+
[AVALANCHE]: "https://api.avax.network/ext/bc/C/rpc",
|
|
406
|
+
[SKALE_BASE]: "https://skale-base.skalenodes.com/v1/base",
|
|
407
|
+
[SKALE_BASE_SEPOLIA]: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
|
|
408
|
+
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com"
|
|
409
|
+
};
|
|
410
|
+
var USDC_ADDRESSES = {
|
|
411
|
+
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
412
|
+
[BASE_SEPOLIA]: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
413
|
+
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
414
|
+
[POLYGON]: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
415
|
+
[OPTIMISM]: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
416
|
+
[AVALANCHE]: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
417
|
+
[SKALE_BASE]: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
418
|
+
[SKALE_BASE_SEPOLIA]: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
419
|
+
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
420
|
+
};
|
|
214
421
|
function isEvmWallet(wallet) {
|
|
215
422
|
if (!wallet || typeof wallet !== "object") return false;
|
|
216
423
|
const w = wallet;
|
|
217
424
|
return "address" in w && typeof w.address === "string" && w.address.startsWith("0x");
|
|
218
425
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
BASE_SEPOLIA = "eip155:84532";
|
|
228
|
-
ETHEREUM_MAINNET = "eip155:1";
|
|
229
|
-
ARBITRUM_ONE = "eip155:42161";
|
|
230
|
-
CHAIN_IDS = {
|
|
231
|
-
[BASE_MAINNET]: 8453,
|
|
232
|
-
[BASE_SEPOLIA]: 84532,
|
|
233
|
-
[ETHEREUM_MAINNET]: 1,
|
|
234
|
-
[ARBITRUM_ONE]: 42161
|
|
235
|
-
};
|
|
236
|
-
DEFAULT_RPC_URLS2 = {
|
|
237
|
-
[BASE_MAINNET]: "https://api.dexter.cash/api/base/rpc",
|
|
238
|
-
[BASE_SEPOLIA]: "https://sepolia.base.org",
|
|
239
|
-
[ETHEREUM_MAINNET]: "https://eth.llamarpc.com",
|
|
240
|
-
[ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc"
|
|
241
|
-
};
|
|
242
|
-
USDC_ADDRESSES = {
|
|
243
|
-
[BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
244
|
-
[ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
245
|
-
[ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
|
|
246
|
-
};
|
|
247
|
-
EvmAdapter = class {
|
|
248
|
-
name = "EVM";
|
|
249
|
-
networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
250
|
-
config;
|
|
251
|
-
log;
|
|
252
|
-
constructor(config = {}) {
|
|
253
|
-
this.config = config;
|
|
254
|
-
this.log = config.verbose ? console.log.bind(console, "[x402:evm]") : () => {
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
canHandle(network) {
|
|
258
|
-
if (this.networks.includes(network)) return true;
|
|
259
|
-
if (network === "base") return true;
|
|
260
|
-
if (network === "ethereum") return true;
|
|
261
|
-
if (network === "arbitrum") return true;
|
|
262
|
-
if (network.startsWith("eip155:")) return true;
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
getDefaultRpcUrl(network) {
|
|
266
|
-
if (this.config.rpcUrls?.[network]) {
|
|
267
|
-
return this.config.rpcUrls[network];
|
|
268
|
-
}
|
|
269
|
-
if (DEFAULT_RPC_URLS2[network]) {
|
|
270
|
-
return DEFAULT_RPC_URLS2[network];
|
|
271
|
-
}
|
|
272
|
-
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
273
|
-
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
274
|
-
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
275
|
-
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
276
|
-
}
|
|
277
|
-
getAddress(wallet) {
|
|
278
|
-
if (!isEvmWallet(wallet)) return null;
|
|
279
|
-
return wallet.address;
|
|
280
|
-
}
|
|
281
|
-
isConnected(wallet) {
|
|
282
|
-
if (!isEvmWallet(wallet)) return false;
|
|
283
|
-
return !!wallet.address;
|
|
284
|
-
}
|
|
285
|
-
getChainId(network) {
|
|
286
|
-
if (CHAIN_IDS[network]) return CHAIN_IDS[network];
|
|
287
|
-
if (network.startsWith("eip155:")) {
|
|
288
|
-
const chainIdStr = network.split(":")[1];
|
|
289
|
-
return parseInt(chainIdStr, 10);
|
|
290
|
-
}
|
|
291
|
-
if (network === "base") return 8453;
|
|
292
|
-
if (network === "ethereum") return 1;
|
|
293
|
-
if (network === "arbitrum") return 42161;
|
|
294
|
-
return 8453;
|
|
295
|
-
}
|
|
296
|
-
async getBalance(accept, wallet, rpcUrl) {
|
|
297
|
-
if (!isEvmWallet(wallet) || !wallet.address) {
|
|
298
|
-
return 0;
|
|
299
|
-
}
|
|
300
|
-
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
301
|
-
try {
|
|
302
|
-
const data = this.encodeBalanceOf(wallet.address);
|
|
303
|
-
const response = await fetch(url, {
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: { "Content-Type": "application/json" },
|
|
306
|
-
body: JSON.stringify({
|
|
307
|
-
jsonrpc: "2.0",
|
|
308
|
-
id: 1,
|
|
309
|
-
method: "eth_call",
|
|
310
|
-
params: [
|
|
311
|
-
{
|
|
312
|
-
to: accept.asset,
|
|
313
|
-
data
|
|
314
|
-
},
|
|
315
|
-
"latest"
|
|
316
|
-
]
|
|
317
|
-
})
|
|
318
|
-
});
|
|
319
|
-
const result = await response.json();
|
|
320
|
-
if (result.error || !result.result) {
|
|
321
|
-
return 0;
|
|
322
|
-
}
|
|
323
|
-
const balance = BigInt(result.result);
|
|
324
|
-
const decimals = accept.extra?.decimals ?? 6;
|
|
325
|
-
return Number(balance) / Math.pow(10, decimals);
|
|
326
|
-
} catch {
|
|
327
|
-
return 0;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
encodeBalanceOf(address) {
|
|
331
|
-
const selector = "0x70a08231";
|
|
332
|
-
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
333
|
-
return selector + paddedAddress;
|
|
334
|
-
}
|
|
335
|
-
async buildTransaction(accept, wallet, _rpcUrl) {
|
|
336
|
-
if (!isEvmWallet(wallet)) {
|
|
337
|
-
throw new Error("Invalid EVM wallet");
|
|
338
|
-
}
|
|
339
|
-
if (!wallet.address) {
|
|
340
|
-
throw new Error("Wallet not connected");
|
|
341
|
-
}
|
|
342
|
-
const { payTo, asset, extra } = accept;
|
|
343
|
-
const amount = accept.amount || accept.maxAmountRequired;
|
|
344
|
-
if (!amount) {
|
|
345
|
-
throw new Error("Missing amount in payment requirements");
|
|
346
|
-
}
|
|
347
|
-
this.log("Building EVM transaction:", {
|
|
348
|
-
from: wallet.address,
|
|
349
|
-
to: payTo,
|
|
350
|
-
amount,
|
|
351
|
-
asset,
|
|
352
|
-
network: accept.network
|
|
353
|
-
});
|
|
354
|
-
const chainId = this.getChainId(accept.network);
|
|
355
|
-
const domain = {
|
|
356
|
-
name: extra?.name ?? "USD Coin",
|
|
357
|
-
version: extra?.version ?? "2",
|
|
358
|
-
chainId: BigInt(chainId),
|
|
359
|
-
verifyingContract: asset
|
|
360
|
-
};
|
|
361
|
-
const types = {
|
|
362
|
-
TransferWithAuthorization: [
|
|
363
|
-
{ name: "from", type: "address" },
|
|
364
|
-
{ name: "to", type: "address" },
|
|
365
|
-
{ name: "value", type: "uint256" },
|
|
366
|
-
{ name: "validAfter", type: "uint256" },
|
|
367
|
-
{ name: "validBefore", type: "uint256" },
|
|
368
|
-
{ name: "nonce", type: "bytes32" }
|
|
369
|
-
]
|
|
370
|
-
};
|
|
371
|
-
const nonce = "0x" + [...Array(32)].map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
|
|
372
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
373
|
-
const authorization = {
|
|
374
|
-
from: wallet.address,
|
|
375
|
-
to: payTo,
|
|
376
|
-
value: amount,
|
|
377
|
-
// string
|
|
378
|
-
validAfter: String(now - 600),
|
|
379
|
-
// 10 minutes before (matching upstream)
|
|
380
|
-
validBefore: String(now + (accept.maxTimeoutSeconds || 60)),
|
|
381
|
-
nonce
|
|
382
|
-
};
|
|
383
|
-
const message = {
|
|
384
|
-
from: wallet.address,
|
|
385
|
-
to: payTo,
|
|
386
|
-
value: BigInt(amount),
|
|
387
|
-
validAfter: BigInt(now - 600),
|
|
388
|
-
validBefore: BigInt(now + (accept.maxTimeoutSeconds || 60)),
|
|
389
|
-
nonce
|
|
390
|
-
};
|
|
391
|
-
if (!wallet.signTypedData) {
|
|
392
|
-
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
393
|
-
}
|
|
394
|
-
const signature = await wallet.signTypedData({
|
|
395
|
-
domain,
|
|
396
|
-
types,
|
|
397
|
-
primaryType: "TransferWithAuthorization",
|
|
398
|
-
message
|
|
399
|
-
});
|
|
400
|
-
this.log("EIP-712 signature obtained");
|
|
401
|
-
const payload = {
|
|
402
|
-
authorization,
|
|
403
|
-
signature
|
|
404
|
-
};
|
|
405
|
-
return {
|
|
406
|
-
serialized: JSON.stringify(payload),
|
|
407
|
-
signature
|
|
408
|
-
};
|
|
409
|
-
}
|
|
426
|
+
var EvmAdapter = class {
|
|
427
|
+
name = "EVM";
|
|
428
|
+
networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
|
|
429
|
+
config;
|
|
430
|
+
log;
|
|
431
|
+
constructor(config = {}) {
|
|
432
|
+
this.config = config;
|
|
433
|
+
this.log = config.verbose ? console.log.bind(console, "[x402:evm]") : () => {
|
|
410
434
|
};
|
|
411
435
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
"
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
while (it2 !== size && b58[it2] === 0) {
|
|
478
|
-
it2++;
|
|
479
|
-
}
|
|
480
|
-
let str = LEADER.repeat(zeroes);
|
|
481
|
-
for (; it2 < size; ++it2) {
|
|
482
|
-
str += ALPHABET.charAt(b58[it2]);
|
|
483
|
-
}
|
|
484
|
-
return str;
|
|
485
|
-
}
|
|
486
|
-
function decodeUnsafe(source) {
|
|
487
|
-
if (typeof source !== "string") {
|
|
488
|
-
throw new TypeError("Expected String");
|
|
489
|
-
}
|
|
490
|
-
if (source.length === 0) {
|
|
491
|
-
return new Uint8Array();
|
|
492
|
-
}
|
|
493
|
-
let psz = 0;
|
|
494
|
-
let zeroes = 0;
|
|
495
|
-
let length = 0;
|
|
496
|
-
while (source[psz] === LEADER) {
|
|
497
|
-
zeroes++;
|
|
498
|
-
psz++;
|
|
499
|
-
}
|
|
500
|
-
const size = (source.length - psz) * FACTOR + 1 >>> 0;
|
|
501
|
-
const b256 = new Uint8Array(size);
|
|
502
|
-
while (psz < source.length) {
|
|
503
|
-
const charCode = source.charCodeAt(psz);
|
|
504
|
-
if (charCode > 255) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
let carry = BASE_MAP[charCode];
|
|
508
|
-
if (carry === 255) {
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
let i = 0;
|
|
512
|
-
for (let it3 = size - 1; (carry !== 0 || i < length) && it3 !== -1; it3--, i++) {
|
|
513
|
-
carry += BASE * b256[it3] >>> 0;
|
|
514
|
-
b256[it3] = carry % 256 >>> 0;
|
|
515
|
-
carry = carry / 256 >>> 0;
|
|
516
|
-
}
|
|
517
|
-
if (carry !== 0) {
|
|
518
|
-
throw new Error("Non-zero carry");
|
|
519
|
-
}
|
|
520
|
-
length = i;
|
|
521
|
-
psz++;
|
|
522
|
-
}
|
|
523
|
-
let it4 = size - length;
|
|
524
|
-
while (it4 !== size && b256[it4] === 0) {
|
|
525
|
-
it4++;
|
|
526
|
-
}
|
|
527
|
-
const vch = new Uint8Array(zeroes + (size - it4));
|
|
528
|
-
let j = zeroes;
|
|
529
|
-
while (it4 !== size) {
|
|
530
|
-
vch[j++] = b256[it4++];
|
|
531
|
-
}
|
|
532
|
-
return vch;
|
|
533
|
-
}
|
|
534
|
-
function decode(string) {
|
|
535
|
-
const buffer = decodeUnsafe(string);
|
|
536
|
-
if (buffer) {
|
|
537
|
-
return buffer;
|
|
538
|
-
}
|
|
539
|
-
throw new Error("Non-base" + BASE + " character");
|
|
436
|
+
canHandle(network) {
|
|
437
|
+
if (this.networks.includes(network)) return true;
|
|
438
|
+
if (network === "base") return true;
|
|
439
|
+
if (network === "ethereum") return true;
|
|
440
|
+
if (network === "arbitrum") return true;
|
|
441
|
+
if (network.startsWith("eip155:")) return true;
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
getDefaultRpcUrl(network) {
|
|
445
|
+
if (this.config.rpcUrls?.[network]) {
|
|
446
|
+
return this.config.rpcUrls[network];
|
|
447
|
+
}
|
|
448
|
+
if (DEFAULT_RPC_URLS2[network]) {
|
|
449
|
+
return DEFAULT_RPC_URLS2[network];
|
|
450
|
+
}
|
|
451
|
+
if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
452
|
+
if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
|
|
453
|
+
if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
|
|
454
|
+
return DEFAULT_RPC_URLS2[BASE_MAINNET];
|
|
455
|
+
}
|
|
456
|
+
getAddress(wallet) {
|
|
457
|
+
if (!isEvmWallet(wallet)) return null;
|
|
458
|
+
return wallet.address;
|
|
459
|
+
}
|
|
460
|
+
isConnected(wallet) {
|
|
461
|
+
if (!isEvmWallet(wallet)) return false;
|
|
462
|
+
return !!wallet.address;
|
|
463
|
+
}
|
|
464
|
+
getChainId(network) {
|
|
465
|
+
if (CHAIN_IDS[network]) return CHAIN_IDS[network];
|
|
466
|
+
if (network.startsWith("eip155:")) {
|
|
467
|
+
const chainIdStr = network.split(":")[1];
|
|
468
|
+
return parseInt(chainIdStr, 10);
|
|
469
|
+
}
|
|
470
|
+
if (network === "base") return 8453;
|
|
471
|
+
if (network === "ethereum") return 1;
|
|
472
|
+
if (network === "arbitrum") return 42161;
|
|
473
|
+
return 8453;
|
|
474
|
+
}
|
|
475
|
+
async getBalance(accept, wallet, rpcUrl) {
|
|
476
|
+
if (!isEvmWallet(wallet) || !wallet.address) {
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
479
|
+
const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
|
|
480
|
+
try {
|
|
481
|
+
const data = this.encodeBalanceOf(wallet.address);
|
|
482
|
+
const response = await fetch(url, {
|
|
483
|
+
method: "POST",
|
|
484
|
+
headers: { "Content-Type": "application/json" },
|
|
485
|
+
body: JSON.stringify({
|
|
486
|
+
jsonrpc: "2.0",
|
|
487
|
+
id: 1,
|
|
488
|
+
method: "eth_call",
|
|
489
|
+
params: [
|
|
490
|
+
{
|
|
491
|
+
to: accept.asset,
|
|
492
|
+
data
|
|
493
|
+
},
|
|
494
|
+
"latest"
|
|
495
|
+
]
|
|
496
|
+
})
|
|
497
|
+
});
|
|
498
|
+
const result = await response.json();
|
|
499
|
+
if (result.error || !result.result) {
|
|
500
|
+
return 0;
|
|
540
501
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
502
|
+
const balance = BigInt(result.result);
|
|
503
|
+
const decimals = accept.extra?.decimals ?? 6;
|
|
504
|
+
return Number(balance) / Math.pow(10, decimals);
|
|
505
|
+
} catch {
|
|
506
|
+
return 0;
|
|
546
507
|
}
|
|
547
|
-
exports.default = base;
|
|
548
508
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
"node_modules/bs58/src/cjs/index.cjs"(exports) {
|
|
554
|
-
"use strict";
|
|
555
|
-
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
556
|
-
return mod && mod.__esModule ? mod : { "default": mod };
|
|
557
|
-
};
|
|
558
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
559
|
-
var base_x_1 = __importDefault(require_cjs());
|
|
560
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
561
|
-
exports.default = (0, base_x_1.default)(ALPHABET);
|
|
509
|
+
encodeBalanceOf(address) {
|
|
510
|
+
const selector = "0x70a08231";
|
|
511
|
+
const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
|
|
512
|
+
return selector + paddedAddress;
|
|
562
513
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
this.
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
514
|
+
async buildTransaction(accept, wallet, _rpcUrl) {
|
|
515
|
+
if (!isEvmWallet(wallet)) {
|
|
516
|
+
throw new Error("Invalid EVM wallet");
|
|
517
|
+
}
|
|
518
|
+
if (!wallet.address) {
|
|
519
|
+
throw new Error("Wallet not connected");
|
|
520
|
+
}
|
|
521
|
+
const { payTo, asset, extra } = accept;
|
|
522
|
+
const amount = accept.amount || accept.maxAmountRequired;
|
|
523
|
+
if (!amount) {
|
|
524
|
+
throw new Error("Missing amount in payment requirements");
|
|
525
|
+
}
|
|
526
|
+
this.log("Building EVM transaction:", {
|
|
527
|
+
from: wallet.address,
|
|
528
|
+
to: payTo,
|
|
529
|
+
amount,
|
|
530
|
+
asset,
|
|
531
|
+
network: accept.network
|
|
532
|
+
});
|
|
533
|
+
const chainId = this.getChainId(accept.network);
|
|
534
|
+
const domain = {
|
|
535
|
+
name: extra?.name ?? "USD Coin",
|
|
536
|
+
version: extra?.version ?? "2",
|
|
537
|
+
chainId: BigInt(chainId),
|
|
538
|
+
verifyingContract: asset
|
|
539
|
+
};
|
|
540
|
+
const types = {
|
|
541
|
+
TransferWithAuthorization: [
|
|
542
|
+
{ name: "from", type: "address" },
|
|
543
|
+
{ name: "to", type: "address" },
|
|
544
|
+
{ name: "value", type: "uint256" },
|
|
545
|
+
{ name: "validAfter", type: "uint256" },
|
|
546
|
+
{ name: "validBefore", type: "uint256" },
|
|
547
|
+
{ name: "nonce", type: "bytes32" }
|
|
548
|
+
]
|
|
549
|
+
};
|
|
550
|
+
const nonce = "0x" + [...Array(32)].map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
|
|
551
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
552
|
+
const authorization = {
|
|
553
|
+
from: wallet.address,
|
|
554
|
+
to: payTo,
|
|
555
|
+
value: amount,
|
|
556
|
+
// string
|
|
557
|
+
validAfter: String(now - 600),
|
|
558
|
+
// 10 minutes before (matching upstream)
|
|
559
|
+
validBefore: String(now + (accept.maxTimeoutSeconds || 60)),
|
|
560
|
+
nonce
|
|
561
|
+
};
|
|
562
|
+
const message = {
|
|
563
|
+
from: wallet.address,
|
|
564
|
+
to: payTo,
|
|
565
|
+
value: BigInt(amount),
|
|
566
|
+
validAfter: BigInt(now - 600),
|
|
567
|
+
validBefore: BigInt(now + (accept.maxTimeoutSeconds || 60)),
|
|
568
|
+
nonce
|
|
569
|
+
};
|
|
570
|
+
if (!wallet.signTypedData) {
|
|
571
|
+
throw new Error("Wallet does not support signTypedData (EIP-712)");
|
|
572
|
+
}
|
|
573
|
+
const signature = await wallet.signTypedData({
|
|
574
|
+
domain,
|
|
575
|
+
types,
|
|
576
|
+
primaryType: "TransferWithAuthorization",
|
|
577
|
+
message
|
|
578
|
+
});
|
|
579
|
+
this.log("EIP-712 signature obtained");
|
|
580
|
+
const payload = {
|
|
581
|
+
authorization,
|
|
582
|
+
signature
|
|
583
|
+
};
|
|
584
|
+
return {
|
|
585
|
+
serialized: JSON.stringify(payload),
|
|
586
|
+
signature
|
|
587
|
+
};
|
|
579
588
|
}
|
|
580
589
|
};
|
|
590
|
+
function createEvmAdapter(config) {
|
|
591
|
+
return new EvmAdapter(config);
|
|
592
|
+
}
|
|
581
593
|
|
|
582
594
|
// src/adapters/index.ts
|
|
583
|
-
|
|
584
|
-
|
|
595
|
+
function isKnownUSDC(asset) {
|
|
596
|
+
if (asset === "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") return true;
|
|
597
|
+
if (asset === "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU") return true;
|
|
598
|
+
const lc = asset.toLowerCase();
|
|
599
|
+
for (const addr of Object.values(USDC_ADDRESSES)) {
|
|
600
|
+
if (addr.toLowerCase() === lc) return true;
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
585
604
|
|
|
586
605
|
// src/client/x402-client.ts
|
|
606
|
+
var receiptStore = /* @__PURE__ */ new WeakMap();
|
|
607
|
+
function getPaymentReceipt(response) {
|
|
608
|
+
return receiptStore.get(response);
|
|
609
|
+
}
|
|
587
610
|
function createX402Client(config) {
|
|
588
611
|
const {
|
|
589
612
|
adapters = [createSolanaAdapter({ verbose: config.verbose }), createEvmAdapter({ verbose: config.verbose })],
|
|
@@ -701,13 +724,7 @@ function createX402Client(config) {
|
|
|
701
724
|
if (!match) return null;
|
|
702
725
|
const { accept, adapter, wallet } = match;
|
|
703
726
|
if (adapter.name === "Solana" && !accept.extra?.feePayer) return null;
|
|
704
|
-
const
|
|
705
|
-
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
706
|
-
"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
707
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
708
|
-
"0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
709
|
-
];
|
|
710
|
-
const decimals = accept.extra?.decimals ?? (USDC_MINTS.includes(accept.asset) ? 6 : void 0);
|
|
727
|
+
const decimals = accept.extra?.decimals ?? (isKnownUSDC(accept.asset) ? 6 : void 0);
|
|
711
728
|
if (typeof decimals !== "number") return null;
|
|
712
729
|
const paymentAmount = accept.amount || accept.maxAmountRequired;
|
|
713
730
|
if (!paymentAmount) return null;
|
|
@@ -846,17 +863,7 @@ function createX402Client(config) {
|
|
|
846
863
|
"Solana payment option missing feePayer in extra"
|
|
847
864
|
);
|
|
848
865
|
}
|
|
849
|
-
const
|
|
850
|
-
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
851
|
-
// Solana mainnet
|
|
852
|
-
"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
|
|
853
|
-
// Solana devnet
|
|
854
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
855
|
-
// Base mainnet
|
|
856
|
-
"0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
857
|
-
// Base sepolia
|
|
858
|
-
];
|
|
859
|
-
const decimals = accept.extra?.decimals ?? (USDC_MINTS.includes(accept.asset) ? 6 : void 0);
|
|
866
|
+
const decimals = accept.extra?.decimals ?? (isKnownUSDC(accept.asset) ? 6 : void 0);
|
|
860
867
|
if (typeof decimals !== "number") {
|
|
861
868
|
throw new X402Error(
|
|
862
869
|
"missing_decimals",
|
|
@@ -953,7 +960,8 @@ function createX402Client(config) {
|
|
|
953
960
|
if (paymentResponseHeader) {
|
|
954
961
|
try {
|
|
955
962
|
const receipt = JSON.parse(atob(paymentResponseHeader));
|
|
956
|
-
retryResponse
|
|
963
|
+
receiptStore.set(retryResponse, receipt);
|
|
964
|
+
retryResponse["_x402"] = receipt;
|
|
957
965
|
if (receipt.extensions) {
|
|
958
966
|
log("Settlement extensions:", Object.keys(receipt.extensions).join(", "));
|
|
959
967
|
}
|
|
@@ -969,16 +977,22 @@ function createX402Client(config) {
|
|
|
969
977
|
|
|
970
978
|
// src/client/keypair-wallet.ts
|
|
971
979
|
import { Keypair, VersionedTransaction as VersionedTransaction2, Transaction } from "@solana/web3.js";
|
|
972
|
-
function createKeypairWallet(privateKey) {
|
|
980
|
+
async function createKeypairWallet(privateKey) {
|
|
973
981
|
let keypair;
|
|
974
982
|
if (typeof privateKey === "string") {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
983
|
+
let bs58Decode;
|
|
984
|
+
try {
|
|
985
|
+
const mod = await Promise.resolve().then(() => (init_esm2(), esm_exports));
|
|
986
|
+
const resolve = mod.decode ?? mod.default?.decode;
|
|
987
|
+
if (!resolve) throw new Error("decode not found");
|
|
988
|
+
bs58Decode = resolve;
|
|
989
|
+
} catch (e) {
|
|
990
|
+
throw new Error(
|
|
991
|
+
'The "bs58" package is required for base58 private keys. Install it with: npm install bs58'
|
|
992
|
+
);
|
|
979
993
|
}
|
|
980
994
|
try {
|
|
981
|
-
const decoded =
|
|
995
|
+
const decoded = bs58Decode(privateKey);
|
|
982
996
|
keypair = Keypair.fromSecretKey(decoded);
|
|
983
997
|
} catch (e) {
|
|
984
998
|
try {
|
|
@@ -1057,7 +1071,6 @@ function wrapFetch(fetchImpl, options) {
|
|
|
1057
1071
|
walletPrivateKey,
|
|
1058
1072
|
evmPrivateKey,
|
|
1059
1073
|
preferredNetwork,
|
|
1060
|
-
// facilitatorUrl is reserved for future use when we add facilitator selection
|
|
1061
1074
|
rpcUrls,
|
|
1062
1075
|
maxAmountAtomic,
|
|
1063
1076
|
verbose,
|
|
@@ -1067,17 +1080,26 @@ function wrapFetch(fetchImpl, options) {
|
|
|
1067
1080
|
throw new Error("At least one wallet private key is required (walletPrivateKey or evmPrivateKey)");
|
|
1068
1081
|
}
|
|
1069
1082
|
const wallets = {};
|
|
1083
|
+
const walletInits = [];
|
|
1070
1084
|
if (walletPrivateKey) {
|
|
1071
|
-
|
|
1085
|
+
walletInits.push(
|
|
1086
|
+
createKeypairWallet(walletPrivateKey).then((w) => {
|
|
1087
|
+
wallets.solana = w;
|
|
1088
|
+
}).catch((e) => {
|
|
1089
|
+
console.warn(`[x402] Solana wallet init failed: ${e.message}`);
|
|
1090
|
+
})
|
|
1091
|
+
);
|
|
1072
1092
|
}
|
|
1073
|
-
let evmReady = null;
|
|
1074
1093
|
if (evmPrivateKey) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1094
|
+
walletInits.push(
|
|
1095
|
+
createEvmKeypairWallet(evmPrivateKey).then((w) => {
|
|
1096
|
+
wallets.evm = w;
|
|
1097
|
+
}).catch((e) => {
|
|
1098
|
+
console.warn(`[x402] EVM wallet init failed: ${e.message}`);
|
|
1099
|
+
})
|
|
1100
|
+
);
|
|
1080
1101
|
}
|
|
1102
|
+
const walletsReady = walletInits.length > 0 ? Promise.all(walletInits) : null;
|
|
1081
1103
|
const clientConfig = {
|
|
1082
1104
|
wallets,
|
|
1083
1105
|
preferredNetwork,
|
|
@@ -1089,9 +1111,9 @@ function wrapFetch(fetchImpl, options) {
|
|
|
1089
1111
|
};
|
|
1090
1112
|
const client = createX402Client(clientConfig);
|
|
1091
1113
|
const clientFetch = client.fetch.bind(client);
|
|
1092
|
-
if (
|
|
1114
|
+
if (walletsReady) {
|
|
1093
1115
|
return (async (input, init) => {
|
|
1094
|
-
await
|
|
1116
|
+
await walletsReady;
|
|
1095
1117
|
return clientFetch(input, init);
|
|
1096
1118
|
});
|
|
1097
1119
|
}
|
|
@@ -1108,6 +1130,7 @@ export {
|
|
|
1108
1130
|
createKeypairWallet,
|
|
1109
1131
|
createSolanaAdapter,
|
|
1110
1132
|
createX402Client,
|
|
1133
|
+
getPaymentReceipt,
|
|
1111
1134
|
isEvmKeypairWallet,
|
|
1112
1135
|
isKeypairWallet,
|
|
1113
1136
|
wrapFetch
|