@pixeloffice-eu/x402 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/index.d.ts +51 -0
- package/index.js +260 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @pixeloffice-eu/x402
|
|
2
|
+
|
|
3
|
+
Zero-dependency TypeScript & Node.js client for **Agent-Native x402 Micro-Payments on Solana**.
|
|
4
|
+
|
|
5
|
+
Allow autonomous AI swarms (LangChain, CrewAI, AutoGPT, Claude Code) to pay per query directly from their Solana wallet with **0.24ms ed25519 cryptographic settlement** and zero credit card friction.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📦 Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @pixeloffice-eu/x402
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ⚡ Quickstart (Autonomous Agent Client)
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import { PixelRouterX402Client } from '@pixeloffice-eu/x402';
|
|
21
|
+
|
|
22
|
+
// Provide your agent's Solana private key (Base58 or hex)
|
|
23
|
+
const client = new PixelRouterX402Client({
|
|
24
|
+
baseUrl: 'https://api.pixeloffice.eu/v1',
|
|
25
|
+
solanaPrivateKey: process.env.SOLANA_AGENT_PRIVATE_KEY
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Autonomous LLM invocation with automatic HTTP 402 negotiation
|
|
29
|
+
const response = await client.createChatCompletion({
|
|
30
|
+
model: 'ox-alpha', // Or 'deepseek-chat', 'claude-3.5-sonnet'
|
|
31
|
+
messages: [
|
|
32
|
+
{ role: 'user', content: 'Execute autonomous codebase refactor.' }
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log(response.choices[0].message.content);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 🔌 1-Line Drop-in for Official OpenAI SDK
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
import OpenAI from 'openai';
|
|
45
|
+
import { wrapOpenAI } from '@pixeloffice-eu/x402';
|
|
46
|
+
|
|
47
|
+
const openai = wrapOpenAI(new OpenAI({
|
|
48
|
+
baseURL: 'https://api.pixeloffice.eu/v1',
|
|
49
|
+
apiKey: 'x402' // Dummy key, handled autonomously on-chain
|
|
50
|
+
}), {
|
|
51
|
+
solanaPrivateKey: process.env.SOLANA_AGENT_PRIVATE_KEY
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const completion = await openai.chat.completions.create({
|
|
55
|
+
model: 'ox-alpha',
|
|
56
|
+
messages: [{ role: 'user', content: 'Hello autonomous world!' }]
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🧠 Stateful Memory Bridge Integration
|
|
63
|
+
|
|
64
|
+
Pass `sessionId` to retain cross-model state and eliminate up to 85% of redundant context tokens:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const response = await client.createChatCompletion({
|
|
68
|
+
model: 'blun-auto',
|
|
69
|
+
session_id: 'customer_support_thread_42',
|
|
70
|
+
messages: [{ role: 'user', content: 'Summarize previous actions.' }]
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 🛡️ License
|
|
77
|
+
|
|
78
|
+
MIT © 2026 Pixel Office EU (https://pixeloffice.eu)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { KeyObject } from 'crypto';
|
|
2
|
+
|
|
3
|
+
export interface X402ClientOptions {
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
keypair?: { publicKey: KeyObject; privateKey: KeyObject };
|
|
6
|
+
solanaPrivateKey?: string | Buffer;
|
|
7
|
+
solanaPublicKey?: string;
|
|
8
|
+
customSigner?: (challenge: X402Challenge) => Promise<string>;
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface X402Challenge {
|
|
13
|
+
network: string;
|
|
14
|
+
token: string;
|
|
15
|
+
tokenSymbol: string;
|
|
16
|
+
merchantWallet: string;
|
|
17
|
+
requiredMicroUSDC: number;
|
|
18
|
+
requiredAmountUSDC: number;
|
|
19
|
+
nonce: string;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
instructions: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface X402PaymentResult {
|
|
25
|
+
isValid: boolean;
|
|
26
|
+
reason?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
signature?: string;
|
|
29
|
+
clientPubkey?: string;
|
|
30
|
+
microUSDC?: number;
|
|
31
|
+
amountUSDC?: number;
|
|
32
|
+
latencyMs?: number;
|
|
33
|
+
challenge?: X402Challenge;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class PixelRouterX402Client {
|
|
37
|
+
constructor(options?: X402ClientOptions);
|
|
38
|
+
createChatCompletion(params: {
|
|
39
|
+
model?: string;
|
|
40
|
+
messages: Array<{ role: string; content: string }>;
|
|
41
|
+
session_id?: string;
|
|
42
|
+
temperature?: number;
|
|
43
|
+
extra_body?: Record<string, any>;
|
|
44
|
+
}): Promise<any>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createClient(options?: X402ClientOptions): PixelRouterX402Client;
|
|
48
|
+
export function wrapOpenAI<T>(openaiInstance: T, options?: X402ClientOptions): T;
|
|
49
|
+
export function verifyX402Payment(headers: Record<string, string>, requestedModel?: string, clientIp?: string, requestId?: string): X402PaymentResult;
|
|
50
|
+
export function generateX402Challenge(model?: string, clientIp?: string): X402Challenge;
|
|
51
|
+
export function x402Middleware(req: any, res: any, next: () => void): void;
|
package/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pixeloffice-eu/x402
|
|
3
|
+
* Zero-Dependency Cryptographic ed25519 Client for Agent-Native x402 Micro-Payments on PixelRouter
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
|
|
12
|
+
// Base58 Helper
|
|
13
|
+
const BS58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
14
|
+
const BS58_MAP = {};
|
|
15
|
+
for (let i = 0; i < BS58_ALPHABET.length; i++) BS58_MAP[BS58_ALPHABET[i]] = i;
|
|
16
|
+
|
|
17
|
+
function bs58Decode(str) {
|
|
18
|
+
if (typeof str !== 'string' || str.length === 0) return Buffer.alloc(0);
|
|
19
|
+
const bytes = [0];
|
|
20
|
+
for (let i = 0; i < str.length; i++) {
|
|
21
|
+
const char = str[i];
|
|
22
|
+
const val = BS58_MAP[char];
|
|
23
|
+
if (val === undefined) throw new Error('Invalid Base58 character: ' + char);
|
|
24
|
+
let carry = val;
|
|
25
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
26
|
+
carry += bytes[j] * 58;
|
|
27
|
+
bytes[j] = carry & 0xff;
|
|
28
|
+
carry >>= 8;
|
|
29
|
+
}
|
|
30
|
+
while (carry > 0) {
|
|
31
|
+
bytes.push(carry & 0xff);
|
|
32
|
+
carry >>= 8;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (let i = 0; i < str.length && str[i] === '1'; i++) {
|
|
36
|
+
bytes.push(0);
|
|
37
|
+
}
|
|
38
|
+
return Buffer.from(bytes.reverse());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function bs58Encode(buffer) {
|
|
42
|
+
const digits = [0];
|
|
43
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
44
|
+
let carry = buffer[i];
|
|
45
|
+
for (let j = 0; j < digits.length; j++) {
|
|
46
|
+
carry += digits[j] << 8;
|
|
47
|
+
digits[j] = carry % 58;
|
|
48
|
+
carry = (carry / 58) | 0;
|
|
49
|
+
}
|
|
50
|
+
while (carry > 0) {
|
|
51
|
+
digits.push(carry % 58);
|
|
52
|
+
carry = (carry / 58) | 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let str = '';
|
|
56
|
+
for (let i = 0; i < buffer.length && buffer[i] === 0; i++) {
|
|
57
|
+
str += '1';
|
|
58
|
+
}
|
|
59
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
60
|
+
str += BS58_ALPHABET[digits[i]];
|
|
61
|
+
}
|
|
62
|
+
return str;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ED25519_PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
66
|
+
|
|
67
|
+
class PixelRouterX402Client {
|
|
68
|
+
/**
|
|
69
|
+
* @param {Object} options
|
|
70
|
+
* @param {string} [options.baseUrl] - Base URL (default: https://api.pixeloffice.eu/v1)
|
|
71
|
+
* @param {KeyObject|Object|string} [options.keypair] - ed25519 Keypair or private key
|
|
72
|
+
* @param {string} [options.solanaPrivateKey] - Solana Base58 or hex private key (32 bytes seed or 64 bytes keypair)
|
|
73
|
+
* @param {string} [options.solanaPublicKey] - Solana Base58 public key
|
|
74
|
+
* @param {Function} [options.customSigner] - Optional custom signing callback (for hardware / Phantom wallets)
|
|
75
|
+
* @param {string} [options.sessionId] - Stateful Memory Bridge session ID
|
|
76
|
+
*/
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.baseUrl = options.baseUrl || 'https://api.pixeloffice.eu/v1';
|
|
79
|
+
this.sessionId = options.sessionId || null;
|
|
80
|
+
this.customSigner = options.customSigner || null;
|
|
81
|
+
|
|
82
|
+
if (options.keypair && options.keypair.privateKey) {
|
|
83
|
+
this.privateKeyObject = options.keypair.privateKey;
|
|
84
|
+
this.publicKeyObject = options.keypair.publicKey;
|
|
85
|
+
const der = this.publicKeyObject.export({ type: 'spki', format: 'der' });
|
|
86
|
+
this.solanaPublicKey = bs58Encode(der.subarray(12));
|
|
87
|
+
} else if (options.solanaPrivateKey) {
|
|
88
|
+
this._initFromPrivateKey(options.solanaPrivateKey, options.solanaPublicKey);
|
|
89
|
+
} else if (options.customSigner) {
|
|
90
|
+
this.solanaPublicKey = options.solanaPublicKey || 'custom_signer_wallet';
|
|
91
|
+
} else {
|
|
92
|
+
// Create fresh ephemeral ed25519 keypair for autonomous agent
|
|
93
|
+
const kp = crypto.generateKeyPairSync('ed25519');
|
|
94
|
+
this.privateKeyObject = kp.privateKey;
|
|
95
|
+
this.publicKeyObject = kp.publicKey;
|
|
96
|
+
const der = kp.publicKey.export({ type: 'spki', format: 'der' });
|
|
97
|
+
this.solanaPublicKey = bs58Encode(der.subarray(12));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_initFromPrivateKey(privKeyInput, pubKeyStr) {
|
|
102
|
+
let rawPriv;
|
|
103
|
+
if (Buffer.isBuffer(privKeyInput)) rawPriv = privKeyInput;
|
|
104
|
+
else if (typeof privKeyInput === 'string') {
|
|
105
|
+
const clean = privKeyInput.trim();
|
|
106
|
+
rawPriv = (clean.length === 64 && /^[0-9a-fA-F]+$/.test(clean)) ? Buffer.from(clean, 'hex') : bs58Decode(clean);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (rawPriv.length === 64) {
|
|
110
|
+
rawPriv = rawPriv.subarray(0, 32); // Use first 32 bytes seed
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (rawPriv.length !== 32) {
|
|
114
|
+
throw new Error(`Invalid ed25519 private key length: expected 32 bytes seed, got ${rawPriv.length}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const pkcs8 = Buffer.concat([ED25519_PKCS8_PREFIX, rawPriv]);
|
|
118
|
+
this.privateKeyObject = crypto.createPrivateKey({ key: pkcs8, format: 'der', type: 'pkcs8' });
|
|
119
|
+
this.publicKeyObject = crypto.createPublicKey(this.privateKeyObject);
|
|
120
|
+
const der = this.publicKeyObject.export({ type: 'spki', format: 'der' });
|
|
121
|
+
this.solanaPublicKey = pubKeyStr || bs58Encode(der.subarray(12));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Execute chat completion with automated x402 micro-payment negotiation
|
|
126
|
+
*/
|
|
127
|
+
async createChatCompletion(params = {}) {
|
|
128
|
+
const url = new URL(`${this.baseUrl}/chat/completions`);
|
|
129
|
+
const payload = JSON.stringify({
|
|
130
|
+
model: params.model || 'ox-alpha',
|
|
131
|
+
messages: params.messages || [],
|
|
132
|
+
session_id: params.session_id || this.sessionId,
|
|
133
|
+
temperature: params.temperature || 0.7,
|
|
134
|
+
stream: false,
|
|
135
|
+
...params.extra_body
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const initialRes = await this._httpRequest(url, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
'User-Agent': '@pixeloffice-eu/x402-client/1.1.0'
|
|
143
|
+
},
|
|
144
|
+
body: payload
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (initialRes.statusCode === 200) {
|
|
148
|
+
return JSON.parse(initialRes.body);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (initialRes.statusCode === 402) {
|
|
152
|
+
const errorJson = JSON.parse(initialRes.body);
|
|
153
|
+
const challenge = errorJson.error?.x402;
|
|
154
|
+
|
|
155
|
+
if (!challenge) {
|
|
156
|
+
throw new Error(`HTTP 402 received but missing x402 challenge details: ${initialRes.body}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Cryptographic ed25519 Signature over Challenge
|
|
160
|
+
const paymentSignature = await this._signChallenge(challenge);
|
|
161
|
+
|
|
162
|
+
const paidRes = await this._httpRequest(url, {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
'Authorization': `x402 ${paymentSignature}:${challenge.nonce}:${this.solanaPublicKey}`,
|
|
167
|
+
'User-Agent': '@pixeloffice-eu/x402-client/1.1.0'
|
|
168
|
+
},
|
|
169
|
+
body: payload
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (paidRes.statusCode >= 200 && paidRes.statusCode < 300) {
|
|
173
|
+
return JSON.parse(paidRes.body);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw new Error(`x402 Payment failed with status ${paidRes.statusCode}: ${paidRes.body}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
throw new Error(`PixelRouter request failed with status ${initialRes.statusCode}: ${initialRes.body}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async _signChallenge(challenge) {
|
|
183
|
+
if (typeof this.customSigner === 'function') {
|
|
184
|
+
return await this.customSigner(challenge);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!this.privateKeyObject) {
|
|
188
|
+
throw new Error('No ed25519 private key or custom signer configured for x402 settlement.');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const nonce = challenge.nonce;
|
|
192
|
+
const microUSDC = challenge.requiredMicroUSDC;
|
|
193
|
+
const merchant = challenge.merchantWallet;
|
|
194
|
+
const canonicalMessage = Buffer.from(`x402_settlement:${nonce}:${microUSDC}:${merchant}:${this.solanaPublicKey}`, 'utf8');
|
|
195
|
+
|
|
196
|
+
const sigBuffer = crypto.sign(null, canonicalMessage, this.privateKeyObject);
|
|
197
|
+
return bs58Encode(sigBuffer);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
_httpRequest(url, options = {}) {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
const isHttps = url.protocol === 'https:';
|
|
203
|
+
const client = isHttps ? https : http;
|
|
204
|
+
|
|
205
|
+
const req = client.request(url, {
|
|
206
|
+
method: options.method || 'GET',
|
|
207
|
+
headers: options.headers || {},
|
|
208
|
+
timeout: 30000
|
|
209
|
+
}, (res) => {
|
|
210
|
+
let body = '';
|
|
211
|
+
res.on('data', chunk => body += chunk);
|
|
212
|
+
res.on('end', () => resolve({
|
|
213
|
+
statusCode: res.statusCode,
|
|
214
|
+
headers: res.headers,
|
|
215
|
+
body
|
|
216
|
+
}));
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
req.on('error', reject);
|
|
220
|
+
req.on('timeout', () => {
|
|
221
|
+
req.destroy();
|
|
222
|
+
reject(new Error('Request timeout'));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (options.body) {
|
|
226
|
+
req.write(options.body);
|
|
227
|
+
}
|
|
228
|
+
req.end();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Wrap standard OpenAI SDK instance with automatic x402 payment handling
|
|
235
|
+
*/
|
|
236
|
+
function wrapOpenAI(openaiInstance, options = {}) {
|
|
237
|
+
const x402Client = new PixelRouterX402Client(options);
|
|
238
|
+
const originalCreate = openaiInstance.chat.completions.create.bind(openaiInstance.chat.completions);
|
|
239
|
+
|
|
240
|
+
openaiInstance.chat.completions.create = async function (params, requestOptions = {}) {
|
|
241
|
+
try {
|
|
242
|
+
return await originalCreate(params, requestOptions);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
if (err.status === 402 || (err.message && err.message.includes('402'))) {
|
|
245
|
+
return await x402Client.createChatCompletion(params);
|
|
246
|
+
}
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return openaiInstance;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
PixelRouterX402Client,
|
|
256
|
+
createClient: (options) => new PixelRouterX402Client(options),
|
|
257
|
+
wrapOpenAI,
|
|
258
|
+
bs58Encode,
|
|
259
|
+
bs58Decode
|
|
260
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pixeloffice-eu/x402",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Agent-Native x402 Micro-Payment Client & Wrapper for PixelRouter on Solana",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"ai",
|
|
9
|
+
"agents",
|
|
10
|
+
"x402",
|
|
11
|
+
"solana",
|
|
12
|
+
"usdc",
|
|
13
|
+
"pixelrouter",
|
|
14
|
+
"micropayments",
|
|
15
|
+
"langchain",
|
|
16
|
+
"crewai",
|
|
17
|
+
"autogpt"
|
|
18
|
+
],
|
|
19
|
+
"author": "Pixel Office EU (https://pixeloffice.eu)",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"homepage": "https://pixeloffice.eu/router.html#x402",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/pixeloffice-eu/pixelrouter"
|
|
25
|
+
}
|
|
26
|
+
}
|