nullpath-mcp 1.2.0 → 1.3.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/.github/workflows/release.yml +43 -0
- package/.releaserc.json +9 -0
- package/README.md +63 -70
- package/SWARM_SPEC.md +75 -0
- package/dist/__tests__/payment.test.d.ts +2 -0
- package/dist/__tests__/payment.test.d.ts.map +1 -0
- package/dist/__tests__/payment.test.js +106 -0
- package/dist/__tests__/payment.test.js.map +1 -0
- package/dist/index.js +144 -23
- package/dist/index.js.map +1 -1
- package/dist/lib/eip3009.d.ts +128 -0
- package/dist/lib/eip3009.d.ts.map +1 -0
- package/dist/lib/eip3009.js +151 -0
- package/dist/lib/eip3009.js.map +1 -0
- package/dist/lib/payment.d.ts +99 -0
- package/dist/lib/payment.d.ts.map +1 -0
- package/dist/lib/payment.js +254 -0
- package/dist/lib/payment.js.map +1 -0
- package/dist/lib/wallet.d.ts +81 -0
- package/dist/lib/wallet.d.ts.map +1 -0
- package/dist/lib/wallet.js +131 -0
- package/dist/lib/wallet.js.map +1 -0
- package/docs/API_DESIGN.md +698 -0
- package/docs/CODE_REVIEW.md +322 -0
- package/package.json +4 -1
- package/src/__tests__/payment.test.ts +126 -0
- package/src/index.ts +160 -27
- package/src/lib/eip3009.ts +201 -0
- package/src/lib/payment.ts +334 -0
- package/src/lib/wallet.ts +164 -0
- package/dist/__tests__/x402.test.d.ts +0 -2
- package/dist/__tests__/x402.test.d.ts.map +0 -1
- package/dist/__tests__/x402.test.js +0 -187
- package/dist/__tests__/x402.test.js.map +0 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Payment Integration for nullpath MCP Client
|
|
4
|
+
*
|
|
5
|
+
* Handles x402 payment flow:
|
|
6
|
+
* 1. Parse 402 Payment Required responses
|
|
7
|
+
* 2. Sign EIP-3009 TransferWithAuthorization
|
|
8
|
+
* 3. Encode payment header for retry
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.InvalidPrivateKeyError = exports.WalletNotConfiguredError = exports.isWalletConfigured = exports.PaymentSigningError = exports.PaymentRequiredError = void 0;
|
|
12
|
+
exports.parsePaymentRequired = parsePaymentRequired;
|
|
13
|
+
exports.signPayment = signPayment;
|
|
14
|
+
exports.encodePaymentHeader = encodePaymentHeader;
|
|
15
|
+
exports.fetchWithPayment = fetchWithPayment;
|
|
16
|
+
exports.formatUsdcAmount = formatUsdcAmount;
|
|
17
|
+
const eip3009_js_1 = require("./eip3009.js");
|
|
18
|
+
const wallet_js_1 = require("./wallet.js");
|
|
19
|
+
/** Expected network for payments (Base mainnet) */
|
|
20
|
+
const EXPECTED_NETWORK = 8453;
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when payment is required but cannot be made
|
|
23
|
+
*/
|
|
24
|
+
class PaymentRequiredError extends Error {
|
|
25
|
+
requirements;
|
|
26
|
+
constructor(message, requirements) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.requirements = requirements;
|
|
29
|
+
this.name = 'PaymentRequiredError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.PaymentRequiredError = PaymentRequiredError;
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when payment signing fails
|
|
35
|
+
*/
|
|
36
|
+
class PaymentSigningError extends Error {
|
|
37
|
+
cause;
|
|
38
|
+
constructor(message, cause) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.cause = cause;
|
|
41
|
+
this.name = 'PaymentSigningError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.PaymentSigningError = PaymentSigningError;
|
|
45
|
+
/**
|
|
46
|
+
* Parse 402 Payment Required response headers
|
|
47
|
+
*
|
|
48
|
+
* Extracts payment requirements from X-PAYMENT-REQUIRED header.
|
|
49
|
+
* The header contains base64-encoded JSON with payment details.
|
|
50
|
+
*
|
|
51
|
+
* @param response - Fetch Response object
|
|
52
|
+
* @returns PaymentRequirements or null if not a 402 response
|
|
53
|
+
*/
|
|
54
|
+
function parsePaymentRequired(response) {
|
|
55
|
+
if (response.status !== 402) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const header = response.headers.get('X-PAYMENT-REQUIRED');
|
|
59
|
+
if (!header) {
|
|
60
|
+
// Try legacy header name
|
|
61
|
+
const legacyHeader = response.headers.get('X-Payment-Required');
|
|
62
|
+
if (!legacyHeader) {
|
|
63
|
+
throw new PaymentRequiredError('Payment required but X-PAYMENT-REQUIRED header missing');
|
|
64
|
+
}
|
|
65
|
+
return parsePaymentHeader(legacyHeader);
|
|
66
|
+
}
|
|
67
|
+
return parsePaymentHeader(header);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Parse the payment header value
|
|
71
|
+
*/
|
|
72
|
+
function parsePaymentHeader(header) {
|
|
73
|
+
try {
|
|
74
|
+
// Decode base64
|
|
75
|
+
const decoded = Buffer.from(header, 'base64').toString('utf-8');
|
|
76
|
+
const data = JSON.parse(decoded);
|
|
77
|
+
// Extract and validate required fields
|
|
78
|
+
const recipient = data.recipient || data.payee;
|
|
79
|
+
const amount = data.amount || data.maxAmountRequired;
|
|
80
|
+
const asset = data.asset || data.usdcAddress;
|
|
81
|
+
const network = data.network || data.chainId || 8453;
|
|
82
|
+
// Default validity window: now to 5 minutes from now
|
|
83
|
+
const now = Math.floor(Date.now() / 1000);
|
|
84
|
+
const validAfter = BigInt(data.validAfter || 0);
|
|
85
|
+
const validBefore = BigInt(data.validBefore || now + 300);
|
|
86
|
+
// Ensure authorization window is still valid
|
|
87
|
+
if (validBefore <= BigInt(now)) {
|
|
88
|
+
throw new Error(`Payment authorization expired: validBefore ${validBefore} is in the past`);
|
|
89
|
+
}
|
|
90
|
+
if (!recipient || !amount) {
|
|
91
|
+
throw new Error('Missing recipient or amount');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
recipient: recipient,
|
|
95
|
+
amount: BigInt(amount),
|
|
96
|
+
asset: (asset || eip3009_js_1.USDC_ADDRESS_BASE),
|
|
97
|
+
network: Number(network),
|
|
98
|
+
validAfter,
|
|
99
|
+
validBefore,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new PaymentRequiredError(`Failed to parse payment requirements: ${error instanceof Error ? error.message : 'unknown error'}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Sign a payment using EIP-3009 TransferWithAuthorization
|
|
108
|
+
*
|
|
109
|
+
* @param wallet - NullpathWallet instance
|
|
110
|
+
* @param requirements - Payment requirements from 402 response
|
|
111
|
+
* @returns Signed authorization
|
|
112
|
+
*/
|
|
113
|
+
async function signPayment(wallet, requirements) {
|
|
114
|
+
// Validate that the requested payment matches our signing configuration
|
|
115
|
+
const requestedNetwork = requirements.network;
|
|
116
|
+
const requestedAsset = requirements.asset?.toLowerCase();
|
|
117
|
+
const expectedAsset = eip3009_js_1.USDC_ADDRESS_BASE.toLowerCase();
|
|
118
|
+
if (requestedNetwork !== EXPECTED_NETWORK || requestedAsset !== expectedAsset) {
|
|
119
|
+
throw new PaymentRequiredError(`Payment requirements mismatch: requested network ${requestedNetwork} and asset ${requirements.asset} ` +
|
|
120
|
+
`do not match supported Base mainnet USDC (network ${EXPECTED_NETWORK}, asset ${eip3009_js_1.USDC_ADDRESS_BASE}).`);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const params = {
|
|
124
|
+
from: wallet.address,
|
|
125
|
+
to: requirements.recipient,
|
|
126
|
+
value: requirements.amount,
|
|
127
|
+
validAfter: requirements.validAfter,
|
|
128
|
+
validBefore: requirements.validBefore,
|
|
129
|
+
nonce: (0, eip3009_js_1.generateNonce)(),
|
|
130
|
+
};
|
|
131
|
+
return await (0, eip3009_js_1.signTransferAuthorization)(wallet.client, params);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
throw new PaymentSigningError(`Failed to sign payment: ${error instanceof Error ? error.message : 'unknown error'}`, error instanceof Error ? error : undefined);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Encode signed authorization as X-PAYMENT header value
|
|
139
|
+
*
|
|
140
|
+
* @param signed - Signed transfer authorization
|
|
141
|
+
* @returns Base64-encoded JSON string for X-PAYMENT header
|
|
142
|
+
*/
|
|
143
|
+
function encodePaymentHeader(signed) {
|
|
144
|
+
const payload = {
|
|
145
|
+
signature: signed.signature,
|
|
146
|
+
from: signed.from,
|
|
147
|
+
to: signed.to,
|
|
148
|
+
value: signed.value.toString(),
|
|
149
|
+
validAfter: signed.validAfter.toString(),
|
|
150
|
+
validBefore: signed.validBefore.toString(),
|
|
151
|
+
nonce: signed.nonce,
|
|
152
|
+
};
|
|
153
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64');
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Build headers for fetch, properly handling Headers instances
|
|
157
|
+
*/
|
|
158
|
+
function buildHeaders(base, extra) {
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
160
|
+
const headers = new Headers(base);
|
|
161
|
+
if (extra) {
|
|
162
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
163
|
+
headers.set(key, value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return headers;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Execute a fetch request with automatic x402 payment handling
|
|
170
|
+
*
|
|
171
|
+
* If the server returns 402 Payment Required:
|
|
172
|
+
* 1. Parse payment requirements
|
|
173
|
+
* 2. Sign EIP-3009 authorization
|
|
174
|
+
* 3. Retry with X-PAYMENT header
|
|
175
|
+
*
|
|
176
|
+
* @param url - Request URL
|
|
177
|
+
* @param options - Fetch options
|
|
178
|
+
* @returns Fetch Response
|
|
179
|
+
* @throws WalletNotConfiguredError if 402 and no wallet
|
|
180
|
+
* @throws PaymentSigningError if signing fails
|
|
181
|
+
*/
|
|
182
|
+
async function fetchWithPayment(url, options = {}) {
|
|
183
|
+
// Build headers properly (handles both plain objects and Headers instances)
|
|
184
|
+
const initialHeaders = buildHeaders(options.headers, {
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
});
|
|
187
|
+
// Make initial request
|
|
188
|
+
const response = await fetch(url, {
|
|
189
|
+
...options,
|
|
190
|
+
headers: initialHeaders,
|
|
191
|
+
});
|
|
192
|
+
// Check for 402 Payment Required
|
|
193
|
+
if (response.status !== 402) {
|
|
194
|
+
return response;
|
|
195
|
+
}
|
|
196
|
+
// Payment required - check if wallet is configured
|
|
197
|
+
if (!(0, wallet_js_1.isWalletConfigured)()) {
|
|
198
|
+
throw new wallet_js_1.WalletNotConfiguredError();
|
|
199
|
+
}
|
|
200
|
+
// Parse payment requirements
|
|
201
|
+
const requirements = parsePaymentRequired(response);
|
|
202
|
+
if (!requirements) {
|
|
203
|
+
throw new PaymentRequiredError('Payment required but could not parse requirements');
|
|
204
|
+
}
|
|
205
|
+
// Create wallet and sign payment
|
|
206
|
+
let wallet;
|
|
207
|
+
try {
|
|
208
|
+
wallet = (0, wallet_js_1.createWallet)();
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (error instanceof wallet_js_1.InvalidPrivateKeyError) {
|
|
212
|
+
throw new PaymentSigningError(`Invalid wallet configuration: ${error.message}`, error);
|
|
213
|
+
}
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
const signed = await signPayment(wallet, requirements);
|
|
217
|
+
const paymentHeader = encodePaymentHeader(signed);
|
|
218
|
+
// Build retry headers with payment
|
|
219
|
+
const retryHeaders = buildHeaders(options.headers, {
|
|
220
|
+
'Content-Type': 'application/json',
|
|
221
|
+
'X-PAYMENT': paymentHeader,
|
|
222
|
+
});
|
|
223
|
+
// Retry with payment header
|
|
224
|
+
const retryResponse = await fetch(url, {
|
|
225
|
+
...options,
|
|
226
|
+
headers: retryHeaders,
|
|
227
|
+
});
|
|
228
|
+
// If still 402, payment was rejected
|
|
229
|
+
if (retryResponse.status === 402) {
|
|
230
|
+
const errorBody = await retryResponse.text().catch(() => '');
|
|
231
|
+
throw new PaymentRequiredError(`Payment was rejected by the server: ${errorBody || 'no details'}`, requirements);
|
|
232
|
+
}
|
|
233
|
+
// Handle other errors on retry
|
|
234
|
+
if (!retryResponse.ok) {
|
|
235
|
+
const errorBody = await retryResponse.text().catch(() => '');
|
|
236
|
+
throw new Error(`Payment submitted but request failed (${retryResponse.status}): ${errorBody}`);
|
|
237
|
+
}
|
|
238
|
+
return retryResponse;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Format amount in human-readable USDC (using bigint arithmetic to avoid precision loss)
|
|
242
|
+
*/
|
|
243
|
+
function formatUsdcAmount(atomic) {
|
|
244
|
+
const whole = atomic / 1000000n;
|
|
245
|
+
const fraction = atomic % 1000000n;
|
|
246
|
+
const fractionStr = fraction.toString().padStart(6, '0');
|
|
247
|
+
return `$${whole.toString()}.${fractionStr} USDC`;
|
|
248
|
+
}
|
|
249
|
+
// Re-export wallet utilities for convenience
|
|
250
|
+
var wallet_js_2 = require("./wallet.js");
|
|
251
|
+
Object.defineProperty(exports, "isWalletConfigured", { enumerable: true, get: function () { return wallet_js_2.isWalletConfigured; } });
|
|
252
|
+
Object.defineProperty(exports, "WalletNotConfiguredError", { enumerable: true, get: function () { return wallet_js_2.WalletNotConfiguredError; } });
|
|
253
|
+
Object.defineProperty(exports, "InvalidPrivateKeyError", { enumerable: true, get: function () { return wallet_js_2.InvalidPrivateKeyError; } });
|
|
254
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.js","sourceRoot":"","sources":["../../src/lib/payment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Client Setup for nullpath MCP Client
|
|
3
|
+
*
|
|
4
|
+
* Creates a viem wallet client from the NULLPATH_WALLET_KEY
|
|
5
|
+
* environment variable for signing EIP-3009 payments.
|
|
6
|
+
*/
|
|
7
|
+
import { type WalletClient, type Account } from 'viem';
|
|
8
|
+
/**
|
|
9
|
+
* Environment variable name for the wallet private key
|
|
10
|
+
*/
|
|
11
|
+
export declare const WALLET_KEY_ENV = "NULLPATH_WALLET_KEY";
|
|
12
|
+
/**
|
|
13
|
+
* Wallet configuration
|
|
14
|
+
*/
|
|
15
|
+
export interface WalletConfig {
|
|
16
|
+
/** Private key (with or without 0x prefix) */
|
|
17
|
+
privateKey?: string;
|
|
18
|
+
/** RPC URL for Base (optional, uses default public RPC) */
|
|
19
|
+
rpcUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wallet client with account information
|
|
23
|
+
*/
|
|
24
|
+
export interface NullpathWallet {
|
|
25
|
+
/** viem wallet client for signing */
|
|
26
|
+
client: WalletClient;
|
|
27
|
+
/** Account derived from private key */
|
|
28
|
+
account: Account;
|
|
29
|
+
/** Wallet address */
|
|
30
|
+
address: `0x${string}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Error thrown when wallet is not configured
|
|
34
|
+
*/
|
|
35
|
+
export declare class WalletNotConfiguredError extends Error {
|
|
36
|
+
constructor();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when private key is invalid
|
|
40
|
+
*/
|
|
41
|
+
export declare class InvalidPrivateKeyError extends Error {
|
|
42
|
+
constructor(reason: string);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a wallet client from configuration or environment
|
|
46
|
+
*
|
|
47
|
+
* @param config - Optional wallet configuration. If not provided,
|
|
48
|
+
* reads from NULLPATH_WALLET_KEY environment variable.
|
|
49
|
+
* @returns NullpathWallet with client, account, and address
|
|
50
|
+
* @throws WalletNotConfiguredError if no private key available
|
|
51
|
+
* @throws InvalidPrivateKeyError if private key format is invalid
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* // From environment variable
|
|
56
|
+
* const wallet = createWallet();
|
|
57
|
+
*
|
|
58
|
+
* // From explicit config
|
|
59
|
+
* const wallet = createWallet({ privateKey: '0x...' });
|
|
60
|
+
*
|
|
61
|
+
* // Use for signing
|
|
62
|
+
* const signed = await signTransferAuthorization(wallet.client, params);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function createWallet(config?: WalletConfig): NullpathWallet;
|
|
66
|
+
/**
|
|
67
|
+
* Check if wallet is configured (without creating it)
|
|
68
|
+
*
|
|
69
|
+
* @returns true if NULLPATH_WALLET_KEY is set
|
|
70
|
+
*/
|
|
71
|
+
export declare function isWalletConfigured(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Get wallet address without full client setup
|
|
74
|
+
*
|
|
75
|
+
* Useful for checking the configured address without
|
|
76
|
+
* creating a full wallet client.
|
|
77
|
+
*
|
|
78
|
+
* @returns Wallet address or null if not configured
|
|
79
|
+
*/
|
|
80
|
+
export declare function getWalletAddress(): `0x${string}` | null;
|
|
81
|
+
//# sourceMappingURL=wallet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../../src/lib/wallet.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA4B,KAAK,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,MAAM,CAAC;AAIjF;;GAEG;AACH,eAAO,MAAM,cAAc,wBAAwB,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,MAAM,EAAE,YAAY,CAAC;IACrB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,qBAAqB;IACrB,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;;CAOlD;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,MAAM,EAAE,MAAM;CAI3B;AA4BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,cAAc,CA0BlE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,IAAI,KAAK,MAAM,EAAE,GAAG,IAAI,CAWvD"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Wallet Client Setup for nullpath MCP Client
|
|
4
|
+
*
|
|
5
|
+
* Creates a viem wallet client from the NULLPATH_WALLET_KEY
|
|
6
|
+
* environment variable for signing EIP-3009 payments.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.InvalidPrivateKeyError = exports.WalletNotConfiguredError = exports.WALLET_KEY_ENV = void 0;
|
|
10
|
+
exports.createWallet = createWallet;
|
|
11
|
+
exports.isWalletConfigured = isWalletConfigured;
|
|
12
|
+
exports.getWalletAddress = getWalletAddress;
|
|
13
|
+
const viem_1 = require("viem");
|
|
14
|
+
const accounts_1 = require("viem/accounts");
|
|
15
|
+
const chains_1 = require("viem/chains");
|
|
16
|
+
/**
|
|
17
|
+
* Environment variable name for the wallet private key
|
|
18
|
+
*/
|
|
19
|
+
exports.WALLET_KEY_ENV = 'NULLPATH_WALLET_KEY';
|
|
20
|
+
/**
|
|
21
|
+
* Error thrown when wallet is not configured
|
|
22
|
+
*/
|
|
23
|
+
class WalletNotConfiguredError extends Error {
|
|
24
|
+
constructor() {
|
|
25
|
+
super(`Wallet not configured. Set ${exports.WALLET_KEY_ENV} environment variable with your private key.`);
|
|
26
|
+
this.name = 'WalletNotConfiguredError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.WalletNotConfiguredError = WalletNotConfiguredError;
|
|
30
|
+
/**
|
|
31
|
+
* Error thrown when private key is invalid
|
|
32
|
+
*/
|
|
33
|
+
class InvalidPrivateKeyError extends Error {
|
|
34
|
+
constructor(reason) {
|
|
35
|
+
super(`Invalid private key: ${reason}`);
|
|
36
|
+
this.name = 'InvalidPrivateKeyError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.InvalidPrivateKeyError = InvalidPrivateKeyError;
|
|
40
|
+
/**
|
|
41
|
+
* Normalize private key to proper format
|
|
42
|
+
*
|
|
43
|
+
* Accepts keys with or without 0x prefix
|
|
44
|
+
*/
|
|
45
|
+
function normalizePrivateKey(key) {
|
|
46
|
+
const trimmed = key.trim();
|
|
47
|
+
// Add 0x prefix if missing
|
|
48
|
+
const prefixed = trimmed.startsWith('0x') ? trimmed : `0x${trimmed}`;
|
|
49
|
+
// Validate length (0x + 64 hex chars = 66 chars)
|
|
50
|
+
if (prefixed.length !== 66) {
|
|
51
|
+
throw new InvalidPrivateKeyError(`Expected 64 hex characters, got ${prefixed.length - 2}`);
|
|
52
|
+
}
|
|
53
|
+
// Validate hex format
|
|
54
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(prefixed)) {
|
|
55
|
+
throw new InvalidPrivateKeyError('Must contain only hexadecimal characters');
|
|
56
|
+
}
|
|
57
|
+
return prefixed;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a wallet client from configuration or environment
|
|
61
|
+
*
|
|
62
|
+
* @param config - Optional wallet configuration. If not provided,
|
|
63
|
+
* reads from NULLPATH_WALLET_KEY environment variable.
|
|
64
|
+
* @returns NullpathWallet with client, account, and address
|
|
65
|
+
* @throws WalletNotConfiguredError if no private key available
|
|
66
|
+
* @throws InvalidPrivateKeyError if private key format is invalid
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* // From environment variable
|
|
71
|
+
* const wallet = createWallet();
|
|
72
|
+
*
|
|
73
|
+
* // From explicit config
|
|
74
|
+
* const wallet = createWallet({ privateKey: '0x...' });
|
|
75
|
+
*
|
|
76
|
+
* // Use for signing
|
|
77
|
+
* const signed = await signTransferAuthorization(wallet.client, params);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function createWallet(config) {
|
|
81
|
+
// Get private key from config or environment
|
|
82
|
+
const rawKey = config?.privateKey ?? process.env[exports.WALLET_KEY_ENV];
|
|
83
|
+
if (!rawKey) {
|
|
84
|
+
throw new WalletNotConfiguredError();
|
|
85
|
+
}
|
|
86
|
+
// Normalize and validate
|
|
87
|
+
const privateKey = normalizePrivateKey(rawKey);
|
|
88
|
+
// Create account from private key
|
|
89
|
+
const account = (0, accounts_1.privateKeyToAccount)(privateKey);
|
|
90
|
+
// Create wallet client for Base mainnet
|
|
91
|
+
const client = (0, viem_1.createWalletClient)({
|
|
92
|
+
account,
|
|
93
|
+
chain: chains_1.base,
|
|
94
|
+
transport: (0, viem_1.http)(config?.rpcUrl),
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
client,
|
|
98
|
+
account,
|
|
99
|
+
address: account.address,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if wallet is configured (without creating it)
|
|
104
|
+
*
|
|
105
|
+
* @returns true if NULLPATH_WALLET_KEY is set
|
|
106
|
+
*/
|
|
107
|
+
function isWalletConfigured() {
|
|
108
|
+
return !!process.env[exports.WALLET_KEY_ENV];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get wallet address without full client setup
|
|
112
|
+
*
|
|
113
|
+
* Useful for checking the configured address without
|
|
114
|
+
* creating a full wallet client.
|
|
115
|
+
*
|
|
116
|
+
* @returns Wallet address or null if not configured
|
|
117
|
+
*/
|
|
118
|
+
function getWalletAddress() {
|
|
119
|
+
const rawKey = process.env[exports.WALLET_KEY_ENV];
|
|
120
|
+
if (!rawKey)
|
|
121
|
+
return null;
|
|
122
|
+
try {
|
|
123
|
+
const privateKey = normalizePrivateKey(rawKey);
|
|
124
|
+
const account = (0, accounts_1.privateKeyToAccount)(privateKey);
|
|
125
|
+
return account.address;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=wallet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet.js","sourceRoot":"","sources":["../../src/lib/wallet.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAsGH,oCA0BC;AAOD,gDAEC;AAUD,4CAWC;AA5JD,+BAAiF;AACjF,4CAAoD;AACpD,wCAAmC;AAEnC;;GAEG;AACU,QAAA,cAAc,GAAG,qBAAqB,CAAC;AAwBpD;;GAEG;AACH,MAAa,wBAAyB,SAAQ,KAAK;IACjD;QACE,KAAK,CACH,8BAA8B,sBAAc,8CAA8C,CAC3F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAPD,4DAOC;AAED;;GAEG;AACH,MAAa,sBAAuB,SAAQ,KAAK;IAC/C,YAAY,MAAc;QACxB,KAAK,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AALD,wDAKC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAE3B,2BAA2B;IAC3B,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;IAErE,iDAAiD;IACjD,IAAI,QAAQ,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC3B,MAAM,IAAI,sBAAsB,CAC9B,mCAAmC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CACzD,CAAC;IACJ,CAAC;IAED,sBAAsB;IACtB,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,sBAAsB,CAAC,0CAA0C,CAAC,CAAC;IAC/E,CAAC;IAED,OAAO,QAAyB,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,YAAY,CAAC,MAAqB;IAChD,6CAA6C;IAC7C,MAAM,MAAM,GAAG,MAAM,EAAE,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAc,CAAC,CAAC;IAEjE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,wBAAwB,EAAE,CAAC;IACvC,CAAC;IAED,yBAAyB;IACzB,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE/C,kCAAkC;IAClC,MAAM,OAAO,GAAG,IAAA,8BAAmB,EAAC,UAAU,CAAC,CAAC;IAEhD,wCAAwC;IACxC,MAAM,MAAM,GAAG,IAAA,yBAAkB,EAAC;QAChC,OAAO;QACP,KAAK,EAAE,aAAI;QACX,SAAS,EAAE,IAAA,WAAI,EAAC,MAAM,EAAE,MAAM,CAAC;KAChC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM;QACN,OAAO;QACP,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB;IAChC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAc,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,gBAAgB;IAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAc,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAA,8BAAmB,EAAC,UAAU,CAAC,CAAC;QAChD,OAAO,OAAO,CAAC,OAAO,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|