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.
@@ -0,0 +1,201 @@
1
+ /**
2
+ * EIP-3009: Transfer With Authorization for USDC
3
+ *
4
+ * Implements typed data structures and signing for USDC's
5
+ * TransferWithAuthorization function, enabling gasless transfers
6
+ * where a third party can submit the transaction.
7
+ *
8
+ * @see https://eips.ethereum.org/EIPS/eip-3009
9
+ */
10
+
11
+ import type { WalletClient } from 'viem';
12
+
13
+ /**
14
+ * USDC contract address on Base mainnet
15
+ */
16
+ export const USDC_ADDRESS_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as const;
17
+
18
+ /**
19
+ * USDC decimals (standard across all networks)
20
+ */
21
+ export const USDC_DECIMALS = 6;
22
+
23
+ /**
24
+ * EIP-712 Domain for USDC on Base
25
+ */
26
+ export const USDC_DOMAIN = {
27
+ name: 'USD Coin',
28
+ version: '2',
29
+ chainId: 8453,
30
+ verifyingContract: USDC_ADDRESS_BASE,
31
+ } as const;
32
+
33
+ /**
34
+ * EIP-712 types for TransferWithAuthorization
35
+ */
36
+ export const TRANSFER_WITH_AUTHORIZATION_TYPES = {
37
+ TransferWithAuthorization: [
38
+ { name: 'from', type: 'address' },
39
+ { name: 'to', type: 'address' },
40
+ { name: 'value', type: 'uint256' },
41
+ { name: 'validAfter', type: 'uint256' },
42
+ { name: 'validBefore', type: 'uint256' },
43
+ { name: 'nonce', type: 'bytes32' },
44
+ ],
45
+ } as const;
46
+
47
+ /**
48
+ * Parameters for TransferWithAuthorization
49
+ */
50
+ export interface TransferAuthorizationParams {
51
+ /** Sender address (must match wallet) */
52
+ from: `0x${string}`;
53
+ /** Recipient address */
54
+ to: `0x${string}`;
55
+ /** Amount in atomic units (6 decimals for USDC) */
56
+ value: bigint;
57
+ /** Unix timestamp after which the authorization is valid */
58
+ validAfter: bigint;
59
+ /** Unix timestamp before which the authorization is valid */
60
+ validBefore: bigint;
61
+ /** Unique nonce (32 bytes) to prevent replay */
62
+ nonce: `0x${string}`;
63
+ }
64
+
65
+ /**
66
+ * Signed authorization ready to submit on-chain
67
+ */
68
+ export interface SignedTransferAuthorization extends TransferAuthorizationParams {
69
+ /** EIP-712 signature */
70
+ signature: `0x${string}`;
71
+ /** Signature v component */
72
+ v: number;
73
+ /** Signature r component */
74
+ r: `0x${string}`;
75
+ /** Signature s component */
76
+ s: `0x${string}`;
77
+ }
78
+
79
+ /**
80
+ * Generate a cryptographically random nonce (32 bytes)
81
+ */
82
+ export function generateNonce(): `0x${string}` {
83
+ const bytes = new Uint8Array(32);
84
+ crypto.getRandomValues(bytes);
85
+ return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}` as `0x${string}`;
86
+ }
87
+
88
+ /**
89
+ * Convert USD amount to USDC atomic units
90
+ */
91
+ export function usdToAtomicUsdc(usd: number): bigint {
92
+ return BigInt(Math.round(usd * 10 ** USDC_DECIMALS));
93
+ }
94
+
95
+ /**
96
+ * Convert USDC atomic units to USD
97
+ */
98
+ export function atomicUsdcToUsd(atomic: bigint): number {
99
+ return Number(atomic) / 10 ** USDC_DECIMALS;
100
+ }
101
+
102
+ /**
103
+ * Sign a TransferWithAuthorization message
104
+ *
105
+ * Creates an EIP-712 signature that authorizes a transfer of USDC
106
+ * from the signer's wallet to a recipient. The signature can be
107
+ * submitted by anyone to execute the transfer.
108
+ *
109
+ * @param walletClient - viem wallet client with signing capability
110
+ * @param params - Transfer authorization parameters
111
+ * @returns Signed authorization with signature components
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const signed = await signTransferAuthorization(walletClient, {
116
+ * from: '0x...',
117
+ * to: '0x...',
118
+ * value: usdToAtomicUsdc(0.10),
119
+ * validAfter: 0n,
120
+ * validBefore: BigInt(Math.floor(Date.now() / 1000) + 3600),
121
+ * nonce: generateNonce(),
122
+ * });
123
+ * ```
124
+ */
125
+ export async function signTransferAuthorization(
126
+ walletClient: WalletClient,
127
+ params: TransferAuthorizationParams
128
+ ): Promise<SignedTransferAuthorization> {
129
+ const account = walletClient.account;
130
+ if (!account) {
131
+ throw new Error('Wallet client must have an account');
132
+ }
133
+
134
+ // Verify from address matches wallet
135
+ if (params.from.toLowerCase() !== account.address.toLowerCase()) {
136
+ throw new Error(
137
+ `From address ${params.from} does not match wallet address ${account.address}`
138
+ );
139
+ }
140
+
141
+ // Sign the typed data
142
+ const signature = await walletClient.signTypedData({
143
+ account,
144
+ domain: USDC_DOMAIN,
145
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
146
+ primaryType: 'TransferWithAuthorization',
147
+ message: {
148
+ from: params.from,
149
+ to: params.to,
150
+ value: params.value,
151
+ validAfter: params.validAfter,
152
+ validBefore: params.validBefore,
153
+ nonce: params.nonce,
154
+ },
155
+ });
156
+
157
+ // Validate signature length (65 bytes = 130 hex chars + 0x prefix)
158
+ if (signature.length !== 132) {
159
+ throw new Error(`Unexpected signature length: ${signature.length}, expected 132`);
160
+ }
161
+
162
+ // Extract v, r, s components from signature
163
+ const r = `0x${signature.slice(2, 66)}` as `0x${string}`;
164
+ const s = `0x${signature.slice(66, 130)}` as `0x${string}`;
165
+ const v = parseInt(signature.slice(130, 132), 16);
166
+
167
+ return {
168
+ ...params,
169
+ signature,
170
+ v,
171
+ r,
172
+ s,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Create transfer authorization params with sensible defaults
178
+ *
179
+ * @param from - Sender address
180
+ * @param to - Recipient address
181
+ * @param amountUsd - Amount in USD
182
+ * @param validitySeconds - How long the authorization is valid (default 5 minutes)
183
+ * @returns TransferAuthorizationParams ready for signing
184
+ */
185
+ export function createTransferAuthorizationParams(
186
+ from: `0x${string}`,
187
+ to: `0x${string}`,
188
+ amountUsd: number,
189
+ validitySeconds: number = 300
190
+ ): TransferAuthorizationParams {
191
+ const now = Math.floor(Date.now() / 1000);
192
+
193
+ return {
194
+ from,
195
+ to,
196
+ value: usdToAtomicUsdc(amountUsd),
197
+ validAfter: 0n, // Valid immediately
198
+ validBefore: BigInt(now + validitySeconds),
199
+ nonce: generateNonce(),
200
+ };
201
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Payment Integration for nullpath MCP Client
3
+ *
4
+ * Handles x402 payment flow:
5
+ * 1. Parse 402 Payment Required responses
6
+ * 2. Sign EIP-3009 TransferWithAuthorization
7
+ * 3. Encode payment header for retry
8
+ */
9
+
10
+ import {
11
+ signTransferAuthorization,
12
+ generateNonce,
13
+ USDC_ADDRESS_BASE,
14
+ type TransferAuthorizationParams,
15
+ type SignedTransferAuthorization,
16
+ } from './eip3009.js';
17
+ import {
18
+ createWallet,
19
+ isWalletConfigured,
20
+ WalletNotConfiguredError,
21
+ InvalidPrivateKeyError,
22
+ type NullpathWallet,
23
+ } from './wallet.js';
24
+
25
+ /** Expected network for payments (Base mainnet) */
26
+ const EXPECTED_NETWORK = 8453;
27
+
28
+ /**
29
+ * Payment requirements from 402 response
30
+ */
31
+ export interface PaymentRequirements {
32
+ /** Recipient wallet address */
33
+ recipient: `0x${string}`;
34
+ /** Amount in atomic USDC units */
35
+ amount: bigint;
36
+ /** USDC contract address */
37
+ asset: `0x${string}`;
38
+ /** Chain ID (8453 for Base) */
39
+ network: number;
40
+ /** Unix timestamp - authorization valid after */
41
+ validAfter: bigint;
42
+ /** Unix timestamp - authorization valid before */
43
+ validBefore: bigint;
44
+ }
45
+
46
+ /**
47
+ * Payment header payload
48
+ */
49
+ export interface PaymentPayload {
50
+ signature: string;
51
+ from: string;
52
+ to: string;
53
+ value: string;
54
+ validAfter: string;
55
+ validBefore: string;
56
+ nonce: string;
57
+ }
58
+
59
+ /**
60
+ * Error thrown when payment is required but cannot be made
61
+ */
62
+ export class PaymentRequiredError extends Error {
63
+ constructor(
64
+ message: string,
65
+ public readonly requirements?: PaymentRequirements
66
+ ) {
67
+ super(message);
68
+ this.name = 'PaymentRequiredError';
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Error thrown when payment signing fails
74
+ */
75
+ export class PaymentSigningError extends Error {
76
+ constructor(message: string, public readonly cause?: Error) {
77
+ super(message);
78
+ this.name = 'PaymentSigningError';
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Parse 402 Payment Required response headers
84
+ *
85
+ * Extracts payment requirements from X-PAYMENT-REQUIRED header.
86
+ * The header contains base64-encoded JSON with payment details.
87
+ *
88
+ * @param response - Fetch Response object
89
+ * @returns PaymentRequirements or null if not a 402 response
90
+ */
91
+ export function parsePaymentRequired(response: Response): PaymentRequirements | null {
92
+ if (response.status !== 402) {
93
+ return null;
94
+ }
95
+
96
+ const header = response.headers.get('X-PAYMENT-REQUIRED');
97
+ if (!header) {
98
+ // Try legacy header name
99
+ const legacyHeader = response.headers.get('X-Payment-Required');
100
+ if (!legacyHeader) {
101
+ throw new PaymentRequiredError(
102
+ 'Payment required but X-PAYMENT-REQUIRED header missing'
103
+ );
104
+ }
105
+ return parsePaymentHeader(legacyHeader);
106
+ }
107
+
108
+ return parsePaymentHeader(header);
109
+ }
110
+
111
+ /**
112
+ * Parse the payment header value
113
+ */
114
+ function parsePaymentHeader(header: string): PaymentRequirements {
115
+ try {
116
+ // Decode base64
117
+ const decoded = Buffer.from(header, 'base64').toString('utf-8');
118
+ const data = JSON.parse(decoded);
119
+
120
+ // Extract and validate required fields
121
+ const recipient = data.recipient || data.payee;
122
+ const amount = data.amount || data.maxAmountRequired;
123
+ const asset = data.asset || data.usdcAddress;
124
+ const network = data.network || data.chainId || 8453;
125
+
126
+ // Default validity window: now to 5 minutes from now
127
+ const now = Math.floor(Date.now() / 1000);
128
+ const validAfter = BigInt(data.validAfter || 0);
129
+ const validBefore = BigInt(data.validBefore || now + 300);
130
+
131
+ // Ensure authorization window is still valid
132
+ if (validBefore <= BigInt(now)) {
133
+ throw new Error(`Payment authorization expired: validBefore ${validBefore} is in the past`);
134
+ }
135
+
136
+ if (!recipient || !amount) {
137
+ throw new Error('Missing recipient or amount');
138
+ }
139
+
140
+ return {
141
+ recipient: recipient as `0x${string}`,
142
+ amount: BigInt(amount),
143
+ asset: (asset || USDC_ADDRESS_BASE) as `0x${string}`,
144
+ network: Number(network),
145
+ validAfter,
146
+ validBefore,
147
+ };
148
+ } catch (error) {
149
+ throw new PaymentRequiredError(
150
+ `Failed to parse payment requirements: ${error instanceof Error ? error.message : 'unknown error'}`
151
+ );
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Sign a payment using EIP-3009 TransferWithAuthorization
157
+ *
158
+ * @param wallet - NullpathWallet instance
159
+ * @param requirements - Payment requirements from 402 response
160
+ * @returns Signed authorization
161
+ */
162
+ export async function signPayment(
163
+ wallet: NullpathWallet,
164
+ requirements: PaymentRequirements
165
+ ): Promise<SignedTransferAuthorization> {
166
+ // Validate that the requested payment matches our signing configuration
167
+ const requestedNetwork = requirements.network;
168
+ const requestedAsset = requirements.asset?.toLowerCase();
169
+ const expectedAsset = USDC_ADDRESS_BASE.toLowerCase();
170
+
171
+ if (requestedNetwork !== EXPECTED_NETWORK || requestedAsset !== expectedAsset) {
172
+ throw new PaymentRequiredError(
173
+ `Payment requirements mismatch: requested network ${requestedNetwork} and asset ${requirements.asset} ` +
174
+ `do not match supported Base mainnet USDC (network ${EXPECTED_NETWORK}, asset ${USDC_ADDRESS_BASE}).`
175
+ );
176
+ }
177
+
178
+ try {
179
+ const params: TransferAuthorizationParams = {
180
+ from: wallet.address,
181
+ to: requirements.recipient,
182
+ value: requirements.amount,
183
+ validAfter: requirements.validAfter,
184
+ validBefore: requirements.validBefore,
185
+ nonce: generateNonce(),
186
+ };
187
+
188
+ return await signTransferAuthorization(wallet.client, params);
189
+ } catch (error) {
190
+ throw new PaymentSigningError(
191
+ `Failed to sign payment: ${error instanceof Error ? error.message : 'unknown error'}`,
192
+ error instanceof Error ? error : undefined
193
+ );
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Encode signed authorization as X-PAYMENT header value
199
+ *
200
+ * @param signed - Signed transfer authorization
201
+ * @returns Base64-encoded JSON string for X-PAYMENT header
202
+ */
203
+ export function encodePaymentHeader(signed: SignedTransferAuthorization): string {
204
+ const payload: PaymentPayload = {
205
+ signature: signed.signature,
206
+ from: signed.from,
207
+ to: signed.to,
208
+ value: signed.value.toString(),
209
+ validAfter: signed.validAfter.toString(),
210
+ validBefore: signed.validBefore.toString(),
211
+ nonce: signed.nonce,
212
+ };
213
+
214
+ return Buffer.from(JSON.stringify(payload)).toString('base64');
215
+ }
216
+
217
+ /**
218
+ * Build headers for fetch, properly handling Headers instances
219
+ */
220
+ function buildHeaders(base?: RequestInit['headers'], extra?: Record<string, string>): Headers {
221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
+ const headers = new Headers(base as any);
223
+ if (extra) {
224
+ for (const [key, value] of Object.entries(extra)) {
225
+ headers.set(key, value);
226
+ }
227
+ }
228
+ return headers;
229
+ }
230
+
231
+ /**
232
+ * Execute a fetch request with automatic x402 payment handling
233
+ *
234
+ * If the server returns 402 Payment Required:
235
+ * 1. Parse payment requirements
236
+ * 2. Sign EIP-3009 authorization
237
+ * 3. Retry with X-PAYMENT header
238
+ *
239
+ * @param url - Request URL
240
+ * @param options - Fetch options
241
+ * @returns Fetch Response
242
+ * @throws WalletNotConfiguredError if 402 and no wallet
243
+ * @throws PaymentSigningError if signing fails
244
+ */
245
+ export async function fetchWithPayment(
246
+ url: string,
247
+ options: RequestInit = {}
248
+ ): Promise<Response> {
249
+ // Build headers properly (handles both plain objects and Headers instances)
250
+ const initialHeaders = buildHeaders(options.headers, {
251
+ 'Content-Type': 'application/json',
252
+ });
253
+
254
+ // Make initial request
255
+ const response = await fetch(url, {
256
+ ...options,
257
+ headers: initialHeaders,
258
+ });
259
+
260
+ // Check for 402 Payment Required
261
+ if (response.status !== 402) {
262
+ return response;
263
+ }
264
+
265
+ // Payment required - check if wallet is configured
266
+ if (!isWalletConfigured()) {
267
+ throw new WalletNotConfiguredError();
268
+ }
269
+
270
+ // Parse payment requirements
271
+ const requirements = parsePaymentRequired(response);
272
+ if (!requirements) {
273
+ throw new PaymentRequiredError('Payment required but could not parse requirements');
274
+ }
275
+
276
+ // Create wallet and sign payment
277
+ let wallet: NullpathWallet;
278
+ try {
279
+ wallet = createWallet();
280
+ } catch (error) {
281
+ if (error instanceof InvalidPrivateKeyError) {
282
+ throw new PaymentSigningError(
283
+ `Invalid wallet configuration: ${error.message}`,
284
+ error
285
+ );
286
+ }
287
+ throw error;
288
+ }
289
+
290
+ const signed = await signPayment(wallet, requirements);
291
+ const paymentHeader = encodePaymentHeader(signed);
292
+
293
+ // Build retry headers with payment
294
+ const retryHeaders = buildHeaders(options.headers, {
295
+ 'Content-Type': 'application/json',
296
+ 'X-PAYMENT': paymentHeader,
297
+ });
298
+
299
+ // Retry with payment header
300
+ const retryResponse = await fetch(url, {
301
+ ...options,
302
+ headers: retryHeaders,
303
+ });
304
+
305
+ // If still 402, payment was rejected
306
+ if (retryResponse.status === 402) {
307
+ const errorBody = await retryResponse.text().catch(() => '');
308
+ throw new PaymentRequiredError(
309
+ `Payment was rejected by the server: ${errorBody || 'no details'}`,
310
+ requirements
311
+ );
312
+ }
313
+
314
+ // Handle other errors on retry
315
+ if (!retryResponse.ok) {
316
+ const errorBody = await retryResponse.text().catch(() => '');
317
+ throw new Error(`Payment submitted but request failed (${retryResponse.status}): ${errorBody}`);
318
+ }
319
+
320
+ return retryResponse;
321
+ }
322
+
323
+ /**
324
+ * Format amount in human-readable USDC (using bigint arithmetic to avoid precision loss)
325
+ */
326
+ export function formatUsdcAmount(atomic: bigint): string {
327
+ const whole = atomic / 1_000_000n;
328
+ const fraction = atomic % 1_000_000n;
329
+ const fractionStr = fraction.toString().padStart(6, '0');
330
+ return `$${whole.toString()}.${fractionStr} USDC`;
331
+ }
332
+
333
+ // Re-export wallet utilities for convenience
334
+ export { isWalletConfigured, WalletNotConfiguredError, InvalidPrivateKeyError } from './wallet.js';
@@ -0,0 +1,164 @@
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
+
8
+ import { createWalletClient, http, type WalletClient, type Account } from 'viem';
9
+ import { privateKeyToAccount } from 'viem/accounts';
10
+ import { base } from 'viem/chains';
11
+
12
+ /**
13
+ * Environment variable name for the wallet private key
14
+ */
15
+ export const WALLET_KEY_ENV = 'NULLPATH_WALLET_KEY';
16
+
17
+ /**
18
+ * Wallet configuration
19
+ */
20
+ export interface WalletConfig {
21
+ /** Private key (with or without 0x prefix) */
22
+ privateKey?: string;
23
+ /** RPC URL for Base (optional, uses default public RPC) */
24
+ rpcUrl?: string;
25
+ }
26
+
27
+ /**
28
+ * Wallet client with account information
29
+ */
30
+ export interface NullpathWallet {
31
+ /** viem wallet client for signing */
32
+ client: WalletClient;
33
+ /** Account derived from private key */
34
+ account: Account;
35
+ /** Wallet address */
36
+ address: `0x${string}`;
37
+ }
38
+
39
+ /**
40
+ * Error thrown when wallet is not configured
41
+ */
42
+ export class WalletNotConfiguredError extends Error {
43
+ constructor() {
44
+ super(
45
+ `Wallet not configured. Set ${WALLET_KEY_ENV} environment variable with your private key.`
46
+ );
47
+ this.name = 'WalletNotConfiguredError';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Error thrown when private key is invalid
53
+ */
54
+ export class InvalidPrivateKeyError extends Error {
55
+ constructor(reason: string) {
56
+ super(`Invalid private key: ${reason}`);
57
+ this.name = 'InvalidPrivateKeyError';
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Normalize private key to proper format
63
+ *
64
+ * Accepts keys with or without 0x prefix
65
+ */
66
+ function normalizePrivateKey(key: string): `0x${string}` {
67
+ const trimmed = key.trim();
68
+
69
+ // Add 0x prefix if missing
70
+ const prefixed = trimmed.startsWith('0x') ? trimmed : `0x${trimmed}`;
71
+
72
+ // Validate length (0x + 64 hex chars = 66 chars)
73
+ if (prefixed.length !== 66) {
74
+ throw new InvalidPrivateKeyError(
75
+ `Expected 64 hex characters, got ${prefixed.length - 2}`
76
+ );
77
+ }
78
+
79
+ // Validate hex format
80
+ if (!/^0x[0-9a-fA-F]{64}$/.test(prefixed)) {
81
+ throw new InvalidPrivateKeyError('Must contain only hexadecimal characters');
82
+ }
83
+
84
+ return prefixed as `0x${string}`;
85
+ }
86
+
87
+ /**
88
+ * Create a wallet client from configuration or environment
89
+ *
90
+ * @param config - Optional wallet configuration. If not provided,
91
+ * reads from NULLPATH_WALLET_KEY environment variable.
92
+ * @returns NullpathWallet with client, account, and address
93
+ * @throws WalletNotConfiguredError if no private key available
94
+ * @throws InvalidPrivateKeyError if private key format is invalid
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * // From environment variable
99
+ * const wallet = createWallet();
100
+ *
101
+ * // From explicit config
102
+ * const wallet = createWallet({ privateKey: '0x...' });
103
+ *
104
+ * // Use for signing
105
+ * const signed = await signTransferAuthorization(wallet.client, params);
106
+ * ```
107
+ */
108
+ export function createWallet(config?: WalletConfig): NullpathWallet {
109
+ // Get private key from config or environment
110
+ const rawKey = config?.privateKey ?? process.env[WALLET_KEY_ENV];
111
+
112
+ if (!rawKey) {
113
+ throw new WalletNotConfiguredError();
114
+ }
115
+
116
+ // Normalize and validate
117
+ const privateKey = normalizePrivateKey(rawKey);
118
+
119
+ // Create account from private key
120
+ const account = privateKeyToAccount(privateKey);
121
+
122
+ // Create wallet client for Base mainnet
123
+ const client = createWalletClient({
124
+ account,
125
+ chain: base,
126
+ transport: http(config?.rpcUrl),
127
+ });
128
+
129
+ return {
130
+ client,
131
+ account,
132
+ address: account.address,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Check if wallet is configured (without creating it)
138
+ *
139
+ * @returns true if NULLPATH_WALLET_KEY is set
140
+ */
141
+ export function isWalletConfigured(): boolean {
142
+ return !!process.env[WALLET_KEY_ENV];
143
+ }
144
+
145
+ /**
146
+ * Get wallet address without full client setup
147
+ *
148
+ * Useful for checking the configured address without
149
+ * creating a full wallet client.
150
+ *
151
+ * @returns Wallet address or null if not configured
152
+ */
153
+ export function getWalletAddress(): `0x${string}` | null {
154
+ const rawKey = process.env[WALLET_KEY_ENV];
155
+ if (!rawKey) return null;
156
+
157
+ try {
158
+ const privateKey = normalizePrivateKey(rawKey);
159
+ const account = privateKeyToAccount(privateKey);
160
+ return account.address;
161
+ } catch {
162
+ return null;
163
+ }
164
+ }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=x402.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"x402.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/x402.test.ts"],"names":[],"mappings":""}