@alleyboss/micropay-solana-x402-paywall 3.1.4 → 3.2.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/dist/agent/index.cjs +12 -5
- package/dist/agent/index.js +8 -5
- package/dist/client/index.cjs +131 -0
- package/dist/client/index.d.cts +94 -1
- package/dist/client/index.d.ts +94 -1
- package/dist/client/index.js +130 -1
- package/dist/index.cjs +142 -5
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +138 -7
- package/package.json +8 -2
package/dist/agent/index.cjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var web3_js = require('@solana/web3.js');
|
|
4
|
+
var bs58 = require('bs58');
|
|
4
5
|
var jose = require('jose');
|
|
5
6
|
|
|
7
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
8
|
+
|
|
9
|
+
var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
10
|
+
|
|
6
11
|
// src/agent/agentPayment.ts
|
|
7
12
|
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
8
13
|
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
@@ -51,8 +56,6 @@ async function buildVersionedTransaction(config) {
|
|
|
51
56
|
lastValidBlockHeight
|
|
52
57
|
};
|
|
53
58
|
}
|
|
54
|
-
|
|
55
|
-
// src/agent/agentPayment.ts
|
|
56
59
|
var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
57
60
|
function isValidWalletAddress(address) {
|
|
58
61
|
if (!address || typeof address !== "string") return false;
|
|
@@ -153,15 +156,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
|
|
|
153
156
|
};
|
|
154
157
|
}
|
|
155
158
|
function keypairFromBase58(base58Secret) {
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
try {
|
|
160
|
+
const bytes = bs58__default.default.decode(base58Secret);
|
|
161
|
+
if (bytes.length !== 64) {
|
|
162
|
+
throw new Error("Invalid secret key length. Expected 64 bytes.");
|
|
163
|
+
}
|
|
164
|
+
return web3_js.Keypair.fromSecretKey(bytes);
|
|
165
|
+
} catch (error) {
|
|
158
166
|
const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
|
|
159
167
|
if (parts.length === 64) {
|
|
160
168
|
return web3_js.Keypair.fromSecretKey(Uint8Array.from(parts));
|
|
161
169
|
}
|
|
162
170
|
throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
|
|
163
171
|
}
|
|
164
|
-
return web3_js.Keypair.fromSecretKey(bytes);
|
|
165
172
|
}
|
|
166
173
|
function generateAgentKeypair() {
|
|
167
174
|
const keypair = web3_js.Keypair.generate();
|
package/dist/agent/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PublicKey, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
|
|
2
|
+
import bs58 from 'bs58';
|
|
2
3
|
import { SignJWT, jwtVerify } from 'jose';
|
|
3
4
|
|
|
4
5
|
// src/agent/agentPayment.ts
|
|
@@ -49,8 +50,6 @@ async function buildVersionedTransaction(config) {
|
|
|
49
50
|
lastValidBlockHeight
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
// src/agent/agentPayment.ts
|
|
54
53
|
var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
55
54
|
function isValidWalletAddress(address) {
|
|
56
55
|
if (!address || typeof address !== "string") return false;
|
|
@@ -151,15 +150,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
|
|
|
151
150
|
};
|
|
152
151
|
}
|
|
153
152
|
function keypairFromBase58(base58Secret) {
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
try {
|
|
154
|
+
const bytes = bs58.decode(base58Secret);
|
|
155
|
+
if (bytes.length !== 64) {
|
|
156
|
+
throw new Error("Invalid secret key length. Expected 64 bytes.");
|
|
157
|
+
}
|
|
158
|
+
return Keypair.fromSecretKey(bytes);
|
|
159
|
+
} catch (error) {
|
|
156
160
|
const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
|
|
157
161
|
if (parts.length === 64) {
|
|
158
162
|
return Keypair.fromSecretKey(Uint8Array.from(parts));
|
|
159
163
|
}
|
|
160
164
|
throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
|
|
161
165
|
}
|
|
162
|
-
return Keypair.fromSecretKey(bytes);
|
|
163
166
|
}
|
|
164
167
|
function generateAgentKeypair() {
|
|
165
168
|
const keypair = Keypair.generate();
|
package/dist/client/index.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var http = require('@x402/core/http');
|
|
4
|
+
var web3_js = require('@solana/web3.js');
|
|
5
|
+
var react = require('react');
|
|
4
6
|
|
|
5
7
|
// src/client/types.ts
|
|
6
8
|
var TOKEN_MINTS = {
|
|
@@ -111,8 +113,137 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
|
|
|
111
113
|
const token = http.encodePaymentSignatureHeader(payload);
|
|
112
114
|
return `x402 ${token}`;
|
|
113
115
|
}
|
|
116
|
+
async function sendSolanaPayment({
|
|
117
|
+
connection,
|
|
118
|
+
wallet,
|
|
119
|
+
recipientAddress,
|
|
120
|
+
amount,
|
|
121
|
+
// memo, // TODO: Add memo support to transaction
|
|
122
|
+
commitment = "confirmed"
|
|
123
|
+
}) {
|
|
124
|
+
if (!wallet.publicKey) {
|
|
125
|
+
throw new Error("Wallet not connected");
|
|
126
|
+
}
|
|
127
|
+
if (amount <= 0n) {
|
|
128
|
+
throw new Error("Amount must be greater than 0");
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const recipientPubkey = new web3_js.PublicKey(recipientAddress);
|
|
132
|
+
const transaction = new web3_js.Transaction().add(
|
|
133
|
+
web3_js.SystemProgram.transfer({
|
|
134
|
+
fromPubkey: wallet.publicKey,
|
|
135
|
+
toPubkey: recipientPubkey,
|
|
136
|
+
lamports: amount
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
140
|
+
transaction.recentBlockhash = blockhash;
|
|
141
|
+
transaction.feePayer = wallet.publicKey;
|
|
142
|
+
const signature = await wallet.sendTransaction(transaction, connection);
|
|
143
|
+
await connection.confirmTransaction({
|
|
144
|
+
signature,
|
|
145
|
+
blockhash,
|
|
146
|
+
lastValidBlockHeight
|
|
147
|
+
}, commitment);
|
|
148
|
+
return {
|
|
149
|
+
signature,
|
|
150
|
+
amountSol: Number(amount) / web3_js.LAMPORTS_PER_SOL
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error("Payment failed:", error);
|
|
154
|
+
throw new Error(error.message || "Payment failed");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function usePaywallResource({
|
|
158
|
+
url,
|
|
159
|
+
connection,
|
|
160
|
+
wallet
|
|
161
|
+
}) {
|
|
162
|
+
const [data, setData] = react.useState(null);
|
|
163
|
+
const [isLocked, setIsLocked] = react.useState(true);
|
|
164
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
165
|
+
const [error, setError] = react.useState(null);
|
|
166
|
+
const [paymentHeader, setPaymentHeader] = react.useState(null);
|
|
167
|
+
const [price, setPrice] = react.useState();
|
|
168
|
+
const [recipient, setRecipient] = react.useState();
|
|
169
|
+
const fetchData = react.useCallback(async (authHeader) => {
|
|
170
|
+
setIsLoading(true);
|
|
171
|
+
setError(null);
|
|
172
|
+
try {
|
|
173
|
+
const headers = {
|
|
174
|
+
"Content-Type": "application/json"
|
|
175
|
+
};
|
|
176
|
+
if (authHeader) {
|
|
177
|
+
headers["Authorization"] = authHeader;
|
|
178
|
+
}
|
|
179
|
+
const res = await fetch(url, { headers });
|
|
180
|
+
if (res.status === 402) {
|
|
181
|
+
const wwwAuth = res.headers.get("WWW-Authenticate");
|
|
182
|
+
if (wwwAuth) {
|
|
183
|
+
setPaymentHeader(wwwAuth);
|
|
184
|
+
try {
|
|
185
|
+
const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
|
|
186
|
+
if (match) {
|
|
187
|
+
setPrice(BigInt(match[1]));
|
|
188
|
+
setRecipient(match[2]);
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
console.warn("Failed to parse 402 details from header", e);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
setIsLocked(true);
|
|
195
|
+
setData(null);
|
|
196
|
+
} else if (res.ok) {
|
|
197
|
+
const json = await res.json();
|
|
198
|
+
setData(json);
|
|
199
|
+
setIsLocked(false);
|
|
200
|
+
} else {
|
|
201
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
setError(err.message || "Failed to fetch resource");
|
|
205
|
+
} finally {
|
|
206
|
+
setIsLoading(false);
|
|
207
|
+
}
|
|
208
|
+
}, [url]);
|
|
209
|
+
react.useEffect(() => {
|
|
210
|
+
fetchData();
|
|
211
|
+
}, [fetchData]);
|
|
212
|
+
const unlock = react.useCallback(async () => {
|
|
213
|
+
if (!paymentHeader || !price || !recipient) {
|
|
214
|
+
setError("Missing payment requirements");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
setIsLoading(true);
|
|
218
|
+
try {
|
|
219
|
+
const { signature } = await sendSolanaPayment({
|
|
220
|
+
connection,
|
|
221
|
+
wallet,
|
|
222
|
+
recipientAddress: recipient,
|
|
223
|
+
amount: price
|
|
224
|
+
});
|
|
225
|
+
const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
|
|
226
|
+
await fetchData(authHeader);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error("Unlock failed", err);
|
|
229
|
+
setError(err.message || "Payment/Unlock failed");
|
|
230
|
+
setIsLoading(false);
|
|
231
|
+
}
|
|
232
|
+
}, [connection, wallet, paymentHeader, price, recipient, fetchData]);
|
|
233
|
+
return {
|
|
234
|
+
data,
|
|
235
|
+
isLocked,
|
|
236
|
+
isLoading,
|
|
237
|
+
price,
|
|
238
|
+
recipient,
|
|
239
|
+
error,
|
|
240
|
+
unlock
|
|
241
|
+
};
|
|
242
|
+
}
|
|
114
243
|
|
|
115
244
|
exports.buildSolanaPayUrl = buildSolanaPayUrl;
|
|
116
245
|
exports.createPaymentFlow = createPaymentFlow;
|
|
117
246
|
exports.createPaymentReference = createPaymentReference;
|
|
118
247
|
exports.createX402AuthorizationHeader = createX402AuthorizationHeader;
|
|
248
|
+
exports.sendSolanaPayment = sendSolanaPayment;
|
|
249
|
+
exports.usePaywallResource = usePaywallResource;
|
package/dist/client/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PublicKey, Transaction, Connection, TransactionSignature } from '@solana/web3.js';
|
|
2
|
+
|
|
1
3
|
/** SPL Token asset specification */
|
|
2
4
|
interface SPLTokenAsset {
|
|
3
5
|
/** Token mint address */
|
|
@@ -126,4 +128,95 @@ declare function createPaymentReference(): string;
|
|
|
126
128
|
*/
|
|
127
129
|
declare function createX402AuthorizationHeader(signature: string, paymentRequiredHeader: string): string;
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Minimal wallet adapter interface compatible with @solana/wallet-adapter-base
|
|
133
|
+
*/
|
|
134
|
+
interface WalletAdapterInterface {
|
|
135
|
+
publicKey: PublicKey | null;
|
|
136
|
+
signTransaction?: (transaction: Transaction) => Promise<Transaction>;
|
|
137
|
+
sendTransaction: (transaction: Transaction, connection: Connection, options?: any) => Promise<TransactionSignature>;
|
|
138
|
+
}
|
|
139
|
+
interface SendPaymentParams {
|
|
140
|
+
/** Solana Connection object */
|
|
141
|
+
connection: Connection;
|
|
142
|
+
/** Connected wallet adapter */
|
|
143
|
+
wallet: WalletAdapterInterface;
|
|
144
|
+
/** Recipient wallet address */
|
|
145
|
+
recipientAddress: string;
|
|
146
|
+
/** Amount in lamports */
|
|
147
|
+
amount: bigint;
|
|
148
|
+
/** Optional memo for the transaction */
|
|
149
|
+
memo?: string;
|
|
150
|
+
/** Commitment level (default: 'confirmed') */
|
|
151
|
+
commitment?: 'processed' | 'confirmed' | 'finalized';
|
|
152
|
+
}
|
|
153
|
+
interface PaymentResult {
|
|
154
|
+
signature: string;
|
|
155
|
+
amountSol: number;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send a SOL payment from the client-side wallet
|
|
159
|
+
*
|
|
160
|
+
* Handles transaction creation, recent blockhash, and sending.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* const result = await sendSolanaPayment({
|
|
165
|
+
* connection,
|
|
166
|
+
* wallet,
|
|
167
|
+
* recipientAddress: '...',
|
|
168
|
+
* amount: 10_000_000n // 0.01 SOL
|
|
169
|
+
* });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function sendSolanaPayment({ connection, wallet, recipientAddress, amount, commitment }: SendPaymentParams): Promise<PaymentResult>;
|
|
173
|
+
|
|
174
|
+
interface PaywallConfig {
|
|
175
|
+
/** URL to fetch protected content from */
|
|
176
|
+
url: string;
|
|
177
|
+
/** Solana Connection */
|
|
178
|
+
connection: Connection;
|
|
179
|
+
/** Wallet Adapter */
|
|
180
|
+
wallet: WalletAdapterInterface;
|
|
181
|
+
}
|
|
182
|
+
interface PaywallState<T> {
|
|
183
|
+
/** Content data if unlocked */
|
|
184
|
+
data: T | null;
|
|
185
|
+
/** Whether content is currently locked by 402 */
|
|
186
|
+
isLocked: boolean;
|
|
187
|
+
/** Whether an operation is in progress (initial load or unlocking) */
|
|
188
|
+
isLoading: boolean;
|
|
189
|
+
/** Payment amount required (in lamports) */
|
|
190
|
+
price?: bigint;
|
|
191
|
+
/** Recipient address */
|
|
192
|
+
recipient?: string;
|
|
193
|
+
/** Error message */
|
|
194
|
+
error: string | null;
|
|
195
|
+
/** Function to trigger unlock flow */
|
|
196
|
+
unlock: () => Promise<void>;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* React Hook to manage x402 Payment Loop
|
|
200
|
+
*
|
|
201
|
+
* Handles:
|
|
202
|
+
* 1. Initial fetch
|
|
203
|
+
* 2. 402 Payment Required detection
|
|
204
|
+
* 3. Payment execution (via wallet)
|
|
205
|
+
* 4. Auth header creation
|
|
206
|
+
* 5. Re-fetch with proof
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```tsx
|
|
210
|
+
* const { data, isLocked, unlock, price } = usePaywallResource<Article>({
|
|
211
|
+
* url: `/api/articles/${slug}`,
|
|
212
|
+
* connection,
|
|
213
|
+
* wallet
|
|
214
|
+
* });
|
|
215
|
+
*
|
|
216
|
+
* if (isLocked) return <Button onClick={unlock}>Pay {price} Lamports</Button>;
|
|
217
|
+
* return <Article data={data} />;
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function usePaywallResource<T = any>({ url, connection, wallet }: PaywallConfig): PaywallState<T>;
|
|
221
|
+
|
|
222
|
+
export { type PaymentFlowConfig, type PaymentResult, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PublicKey, Transaction, Connection, TransactionSignature } from '@solana/web3.js';
|
|
2
|
+
|
|
1
3
|
/** SPL Token asset specification */
|
|
2
4
|
interface SPLTokenAsset {
|
|
3
5
|
/** Token mint address */
|
|
@@ -126,4 +128,95 @@ declare function createPaymentReference(): string;
|
|
|
126
128
|
*/
|
|
127
129
|
declare function createX402AuthorizationHeader(signature: string, paymentRequiredHeader: string): string;
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Minimal wallet adapter interface compatible with @solana/wallet-adapter-base
|
|
133
|
+
*/
|
|
134
|
+
interface WalletAdapterInterface {
|
|
135
|
+
publicKey: PublicKey | null;
|
|
136
|
+
signTransaction?: (transaction: Transaction) => Promise<Transaction>;
|
|
137
|
+
sendTransaction: (transaction: Transaction, connection: Connection, options?: any) => Promise<TransactionSignature>;
|
|
138
|
+
}
|
|
139
|
+
interface SendPaymentParams {
|
|
140
|
+
/** Solana Connection object */
|
|
141
|
+
connection: Connection;
|
|
142
|
+
/** Connected wallet adapter */
|
|
143
|
+
wallet: WalletAdapterInterface;
|
|
144
|
+
/** Recipient wallet address */
|
|
145
|
+
recipientAddress: string;
|
|
146
|
+
/** Amount in lamports */
|
|
147
|
+
amount: bigint;
|
|
148
|
+
/** Optional memo for the transaction */
|
|
149
|
+
memo?: string;
|
|
150
|
+
/** Commitment level (default: 'confirmed') */
|
|
151
|
+
commitment?: 'processed' | 'confirmed' | 'finalized';
|
|
152
|
+
}
|
|
153
|
+
interface PaymentResult {
|
|
154
|
+
signature: string;
|
|
155
|
+
amountSol: number;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Send a SOL payment from the client-side wallet
|
|
159
|
+
*
|
|
160
|
+
* Handles transaction creation, recent blockhash, and sending.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* const result = await sendSolanaPayment({
|
|
165
|
+
* connection,
|
|
166
|
+
* wallet,
|
|
167
|
+
* recipientAddress: '...',
|
|
168
|
+
* amount: 10_000_000n // 0.01 SOL
|
|
169
|
+
* });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
declare function sendSolanaPayment({ connection, wallet, recipientAddress, amount, commitment }: SendPaymentParams): Promise<PaymentResult>;
|
|
173
|
+
|
|
174
|
+
interface PaywallConfig {
|
|
175
|
+
/** URL to fetch protected content from */
|
|
176
|
+
url: string;
|
|
177
|
+
/** Solana Connection */
|
|
178
|
+
connection: Connection;
|
|
179
|
+
/** Wallet Adapter */
|
|
180
|
+
wallet: WalletAdapterInterface;
|
|
181
|
+
}
|
|
182
|
+
interface PaywallState<T> {
|
|
183
|
+
/** Content data if unlocked */
|
|
184
|
+
data: T | null;
|
|
185
|
+
/** Whether content is currently locked by 402 */
|
|
186
|
+
isLocked: boolean;
|
|
187
|
+
/** Whether an operation is in progress (initial load or unlocking) */
|
|
188
|
+
isLoading: boolean;
|
|
189
|
+
/** Payment amount required (in lamports) */
|
|
190
|
+
price?: bigint;
|
|
191
|
+
/** Recipient address */
|
|
192
|
+
recipient?: string;
|
|
193
|
+
/** Error message */
|
|
194
|
+
error: string | null;
|
|
195
|
+
/** Function to trigger unlock flow */
|
|
196
|
+
unlock: () => Promise<void>;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* React Hook to manage x402 Payment Loop
|
|
200
|
+
*
|
|
201
|
+
* Handles:
|
|
202
|
+
* 1. Initial fetch
|
|
203
|
+
* 2. 402 Payment Required detection
|
|
204
|
+
* 3. Payment execution (via wallet)
|
|
205
|
+
* 4. Auth header creation
|
|
206
|
+
* 5. Re-fetch with proof
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```tsx
|
|
210
|
+
* const { data, isLocked, unlock, price } = usePaywallResource<Article>({
|
|
211
|
+
* url: `/api/articles/${slug}`,
|
|
212
|
+
* connection,
|
|
213
|
+
* wallet
|
|
214
|
+
* });
|
|
215
|
+
*
|
|
216
|
+
* if (isLocked) return <Button onClick={unlock}>Pay {price} Lamports</Button>;
|
|
217
|
+
* return <Article data={data} />;
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function usePaywallResource<T = any>({ url, connection, wallet }: PaywallConfig): PaywallState<T>;
|
|
221
|
+
|
|
222
|
+
export { type PaymentFlowConfig, type PaymentResult, type PaywallConfig, type PaywallState, type SendPaymentParams, type SolanaPayUrlParams, type WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
|
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
|
|
2
|
+
import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
3
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
4
|
|
|
3
5
|
// src/client/types.ts
|
|
4
6
|
var TOKEN_MINTS = {
|
|
@@ -109,5 +111,132 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
|
|
|
109
111
|
const token = encodePaymentSignatureHeader(payload);
|
|
110
112
|
return `x402 ${token}`;
|
|
111
113
|
}
|
|
114
|
+
async function sendSolanaPayment({
|
|
115
|
+
connection,
|
|
116
|
+
wallet,
|
|
117
|
+
recipientAddress,
|
|
118
|
+
amount,
|
|
119
|
+
// memo, // TODO: Add memo support to transaction
|
|
120
|
+
commitment = "confirmed"
|
|
121
|
+
}) {
|
|
122
|
+
if (!wallet.publicKey) {
|
|
123
|
+
throw new Error("Wallet not connected");
|
|
124
|
+
}
|
|
125
|
+
if (amount <= 0n) {
|
|
126
|
+
throw new Error("Amount must be greater than 0");
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const recipientPubkey = new PublicKey(recipientAddress);
|
|
130
|
+
const transaction = new Transaction().add(
|
|
131
|
+
SystemProgram.transfer({
|
|
132
|
+
fromPubkey: wallet.publicKey,
|
|
133
|
+
toPubkey: recipientPubkey,
|
|
134
|
+
lamports: amount
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
138
|
+
transaction.recentBlockhash = blockhash;
|
|
139
|
+
transaction.feePayer = wallet.publicKey;
|
|
140
|
+
const signature = await wallet.sendTransaction(transaction, connection);
|
|
141
|
+
await connection.confirmTransaction({
|
|
142
|
+
signature,
|
|
143
|
+
blockhash,
|
|
144
|
+
lastValidBlockHeight
|
|
145
|
+
}, commitment);
|
|
146
|
+
return {
|
|
147
|
+
signature,
|
|
148
|
+
amountSol: Number(amount) / LAMPORTS_PER_SOL
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error("Payment failed:", error);
|
|
152
|
+
throw new Error(error.message || "Payment failed");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function usePaywallResource({
|
|
156
|
+
url,
|
|
157
|
+
connection,
|
|
158
|
+
wallet
|
|
159
|
+
}) {
|
|
160
|
+
const [data, setData] = useState(null);
|
|
161
|
+
const [isLocked, setIsLocked] = useState(true);
|
|
162
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
163
|
+
const [error, setError] = useState(null);
|
|
164
|
+
const [paymentHeader, setPaymentHeader] = useState(null);
|
|
165
|
+
const [price, setPrice] = useState();
|
|
166
|
+
const [recipient, setRecipient] = useState();
|
|
167
|
+
const fetchData = useCallback(async (authHeader) => {
|
|
168
|
+
setIsLoading(true);
|
|
169
|
+
setError(null);
|
|
170
|
+
try {
|
|
171
|
+
const headers = {
|
|
172
|
+
"Content-Type": "application/json"
|
|
173
|
+
};
|
|
174
|
+
if (authHeader) {
|
|
175
|
+
headers["Authorization"] = authHeader;
|
|
176
|
+
}
|
|
177
|
+
const res = await fetch(url, { headers });
|
|
178
|
+
if (res.status === 402) {
|
|
179
|
+
const wwwAuth = res.headers.get("WWW-Authenticate");
|
|
180
|
+
if (wwwAuth) {
|
|
181
|
+
setPaymentHeader(wwwAuth);
|
|
182
|
+
try {
|
|
183
|
+
const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
|
|
184
|
+
if (match) {
|
|
185
|
+
setPrice(BigInt(match[1]));
|
|
186
|
+
setRecipient(match[2]);
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.warn("Failed to parse 402 details from header", e);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
setIsLocked(true);
|
|
193
|
+
setData(null);
|
|
194
|
+
} else if (res.ok) {
|
|
195
|
+
const json = await res.json();
|
|
196
|
+
setData(json);
|
|
197
|
+
setIsLocked(false);
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
setError(err.message || "Failed to fetch resource");
|
|
203
|
+
} finally {
|
|
204
|
+
setIsLoading(false);
|
|
205
|
+
}
|
|
206
|
+
}, [url]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
fetchData();
|
|
209
|
+
}, [fetchData]);
|
|
210
|
+
const unlock = useCallback(async () => {
|
|
211
|
+
if (!paymentHeader || !price || !recipient) {
|
|
212
|
+
setError("Missing payment requirements");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
setIsLoading(true);
|
|
216
|
+
try {
|
|
217
|
+
const { signature } = await sendSolanaPayment({
|
|
218
|
+
connection,
|
|
219
|
+
wallet,
|
|
220
|
+
recipientAddress: recipient,
|
|
221
|
+
amount: price
|
|
222
|
+
});
|
|
223
|
+
const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
|
|
224
|
+
await fetchData(authHeader);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error("Unlock failed", err);
|
|
227
|
+
setError(err.message || "Payment/Unlock failed");
|
|
228
|
+
setIsLoading(false);
|
|
229
|
+
}
|
|
230
|
+
}, [connection, wallet, paymentHeader, price, recipient, fetchData]);
|
|
231
|
+
return {
|
|
232
|
+
data,
|
|
233
|
+
isLocked,
|
|
234
|
+
isLoading,
|
|
235
|
+
price,
|
|
236
|
+
recipient,
|
|
237
|
+
error,
|
|
238
|
+
unlock
|
|
239
|
+
};
|
|
240
|
+
}
|
|
112
241
|
|
|
113
|
-
export { buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader };
|
|
242
|
+
export { buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource };
|
package/dist/index.cjs
CHANGED
|
@@ -6,8 +6,14 @@ var client = require('@x402/core/client');
|
|
|
6
6
|
var svm = require('@x402/svm');
|
|
7
7
|
var http = require('@x402/core/http');
|
|
8
8
|
var web3_js = require('@solana/web3.js');
|
|
9
|
+
var react = require('react');
|
|
10
|
+
var bs58 = require('bs58');
|
|
9
11
|
var jose = require('jose');
|
|
10
12
|
|
|
13
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
|
+
|
|
15
|
+
var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
16
|
+
|
|
11
17
|
// src/index.ts
|
|
12
18
|
|
|
13
19
|
// src/client/types.ts
|
|
@@ -119,6 +125,133 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
|
|
|
119
125
|
const token = http.encodePaymentSignatureHeader(payload);
|
|
120
126
|
return `x402 ${token}`;
|
|
121
127
|
}
|
|
128
|
+
async function sendSolanaPayment({
|
|
129
|
+
connection,
|
|
130
|
+
wallet,
|
|
131
|
+
recipientAddress,
|
|
132
|
+
amount,
|
|
133
|
+
// memo, // TODO: Add memo support to transaction
|
|
134
|
+
commitment = "confirmed"
|
|
135
|
+
}) {
|
|
136
|
+
if (!wallet.publicKey) {
|
|
137
|
+
throw new Error("Wallet not connected");
|
|
138
|
+
}
|
|
139
|
+
if (amount <= 0n) {
|
|
140
|
+
throw new Error("Amount must be greater than 0");
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const recipientPubkey = new web3_js.PublicKey(recipientAddress);
|
|
144
|
+
const transaction = new web3_js.Transaction().add(
|
|
145
|
+
web3_js.SystemProgram.transfer({
|
|
146
|
+
fromPubkey: wallet.publicKey,
|
|
147
|
+
toPubkey: recipientPubkey,
|
|
148
|
+
lamports: amount
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
152
|
+
transaction.recentBlockhash = blockhash;
|
|
153
|
+
transaction.feePayer = wallet.publicKey;
|
|
154
|
+
const signature = await wallet.sendTransaction(transaction, connection);
|
|
155
|
+
await connection.confirmTransaction({
|
|
156
|
+
signature,
|
|
157
|
+
blockhash,
|
|
158
|
+
lastValidBlockHeight
|
|
159
|
+
}, commitment);
|
|
160
|
+
return {
|
|
161
|
+
signature,
|
|
162
|
+
amountSol: Number(amount) / web3_js.LAMPORTS_PER_SOL
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error("Payment failed:", error);
|
|
166
|
+
throw new Error(error.message || "Payment failed");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function usePaywallResource({
|
|
170
|
+
url,
|
|
171
|
+
connection,
|
|
172
|
+
wallet
|
|
173
|
+
}) {
|
|
174
|
+
const [data, setData] = react.useState(null);
|
|
175
|
+
const [isLocked, setIsLocked] = react.useState(true);
|
|
176
|
+
const [isLoading, setIsLoading] = react.useState(true);
|
|
177
|
+
const [error, setError] = react.useState(null);
|
|
178
|
+
const [paymentHeader, setPaymentHeader] = react.useState(null);
|
|
179
|
+
const [price, setPrice] = react.useState();
|
|
180
|
+
const [recipient, setRecipient] = react.useState();
|
|
181
|
+
const fetchData = react.useCallback(async (authHeader) => {
|
|
182
|
+
setIsLoading(true);
|
|
183
|
+
setError(null);
|
|
184
|
+
try {
|
|
185
|
+
const headers = {
|
|
186
|
+
"Content-Type": "application/json"
|
|
187
|
+
};
|
|
188
|
+
if (authHeader) {
|
|
189
|
+
headers["Authorization"] = authHeader;
|
|
190
|
+
}
|
|
191
|
+
const res = await fetch(url, { headers });
|
|
192
|
+
if (res.status === 402) {
|
|
193
|
+
const wwwAuth = res.headers.get("WWW-Authenticate");
|
|
194
|
+
if (wwwAuth) {
|
|
195
|
+
setPaymentHeader(wwwAuth);
|
|
196
|
+
try {
|
|
197
|
+
const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
|
|
198
|
+
if (match) {
|
|
199
|
+
setPrice(BigInt(match[1]));
|
|
200
|
+
setRecipient(match[2]);
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.warn("Failed to parse 402 details from header", e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
setIsLocked(true);
|
|
207
|
+
setData(null);
|
|
208
|
+
} else if (res.ok) {
|
|
209
|
+
const json = await res.json();
|
|
210
|
+
setData(json);
|
|
211
|
+
setIsLocked(false);
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
setError(err.message || "Failed to fetch resource");
|
|
217
|
+
} finally {
|
|
218
|
+
setIsLoading(false);
|
|
219
|
+
}
|
|
220
|
+
}, [url]);
|
|
221
|
+
react.useEffect(() => {
|
|
222
|
+
fetchData();
|
|
223
|
+
}, [fetchData]);
|
|
224
|
+
const unlock = react.useCallback(async () => {
|
|
225
|
+
if (!paymentHeader || !price || !recipient) {
|
|
226
|
+
setError("Missing payment requirements");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
setIsLoading(true);
|
|
230
|
+
try {
|
|
231
|
+
const { signature } = await sendSolanaPayment({
|
|
232
|
+
connection,
|
|
233
|
+
wallet,
|
|
234
|
+
recipientAddress: recipient,
|
|
235
|
+
amount: price
|
|
236
|
+
});
|
|
237
|
+
const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
|
|
238
|
+
await fetchData(authHeader);
|
|
239
|
+
} catch (err) {
|
|
240
|
+
console.error("Unlock failed", err);
|
|
241
|
+
setError(err.message || "Payment/Unlock failed");
|
|
242
|
+
setIsLoading(false);
|
|
243
|
+
}
|
|
244
|
+
}, [connection, wallet, paymentHeader, price, recipient, fetchData]);
|
|
245
|
+
return {
|
|
246
|
+
data,
|
|
247
|
+
isLocked,
|
|
248
|
+
isLoading,
|
|
249
|
+
price,
|
|
250
|
+
recipient,
|
|
251
|
+
error,
|
|
252
|
+
unlock
|
|
253
|
+
};
|
|
254
|
+
}
|
|
122
255
|
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
123
256
|
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
124
257
|
function createPriorityFeeInstructions(config2 = {}) {
|
|
@@ -166,8 +299,6 @@ async function buildVersionedTransaction(config2) {
|
|
|
166
299
|
lastValidBlockHeight
|
|
167
300
|
};
|
|
168
301
|
}
|
|
169
|
-
|
|
170
|
-
// src/agent/agentPayment.ts
|
|
171
302
|
var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
172
303
|
function isValidWalletAddress(address) {
|
|
173
304
|
if (!address || typeof address !== "string") return false;
|
|
@@ -268,15 +399,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
|
|
|
268
399
|
};
|
|
269
400
|
}
|
|
270
401
|
function keypairFromBase58(base58Secret) {
|
|
271
|
-
|
|
272
|
-
|
|
402
|
+
try {
|
|
403
|
+
const bytes = bs58__default.default.decode(base58Secret);
|
|
404
|
+
if (bytes.length !== 64) {
|
|
405
|
+
throw new Error("Invalid secret key length. Expected 64 bytes.");
|
|
406
|
+
}
|
|
407
|
+
return web3_js.Keypair.fromSecretKey(bytes);
|
|
408
|
+
} catch (error) {
|
|
273
409
|
const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
|
|
274
410
|
if (parts.length === 64) {
|
|
275
411
|
return web3_js.Keypair.fromSecretKey(Uint8Array.from(parts));
|
|
276
412
|
}
|
|
277
413
|
throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
|
|
278
414
|
}
|
|
279
|
-
return web3_js.Keypair.fromSecretKey(bytes);
|
|
280
415
|
}
|
|
281
416
|
function generateAgentKeypair() {
|
|
282
417
|
const keypair = web3_js.Keypair.generate();
|
|
@@ -614,8 +749,10 @@ exports.hasAgentSufficientBalance = hasAgentSufficientBalance;
|
|
|
614
749
|
exports.keypairFromBase58 = keypairFromBase58;
|
|
615
750
|
exports.lamportsToSol = lamportsToSol;
|
|
616
751
|
exports.lamportsToUsd = lamportsToUsd;
|
|
752
|
+
exports.sendSolanaPayment = sendSolanaPayment;
|
|
617
753
|
exports.usdToLamports = usdToLamports;
|
|
618
754
|
exports.useCredit = useCredit;
|
|
755
|
+
exports.usePaywallResource = usePaywallResource;
|
|
619
756
|
exports.validateCreditSession = validateCreditSession;
|
|
620
757
|
Object.keys(core).forEach(function (k) {
|
|
621
758
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
package/dist/index.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ export * from '@x402/core';
|
|
|
2
2
|
export * from '@x402/core/types';
|
|
3
3
|
export * from '@x402/core/client';
|
|
4
4
|
export * from '@x402/svm';
|
|
5
|
-
export { PaymentFlowConfig, SolanaPayUrlParams, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader } from './client/index.cjs';
|
|
5
|
+
export { PaymentFlowConfig, PaymentResult, PaywallConfig, PaywallState, SendPaymentParams, SolanaPayUrlParams, WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource } from './client/index.cjs';
|
|
6
6
|
export { AgentPaymentResult, CreditSessionClaims, CreditSessionConfig, CreditSessionData, CreditValidation, ExecuteAgentPaymentParams, UseCreditResult, addCredits, createCreditSession, executeAgentPayment, generateAgentKeypair, getAgentBalance, getRemainingCredits, hasAgentSufficientBalance, keypairFromBase58, useCredit, validateCreditSession } from './agent/index.cjs';
|
|
7
7
|
export { CustomPriceProvider, PriceConfig, PriceData, clearPriceCache, configurePricing, formatPriceDisplay, formatPriceSync, getProviders, getSolPrice, lamportsToSol, lamportsToUsd, usdToLamports } from './pricing/index.cjs';
|
|
8
8
|
import '@solana/web3.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from '@x402/core';
|
|
|
2
2
|
export * from '@x402/core/types';
|
|
3
3
|
export * from '@x402/core/client';
|
|
4
4
|
export * from '@x402/svm';
|
|
5
|
-
export { PaymentFlowConfig, SolanaPayUrlParams, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader } from './client/index.js';
|
|
5
|
+
export { PaymentFlowConfig, PaymentResult, PaywallConfig, PaywallState, SendPaymentParams, SolanaPayUrlParams, WalletAdapterInterface, buildSolanaPayUrl, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, sendSolanaPayment, usePaywallResource } from './client/index.js';
|
|
6
6
|
export { AgentPaymentResult, CreditSessionClaims, CreditSessionConfig, CreditSessionData, CreditValidation, ExecuteAgentPaymentParams, UseCreditResult, addCredits, createCreditSession, executeAgentPayment, generateAgentKeypair, getAgentBalance, getRemainingCredits, hasAgentSufficientBalance, keypairFromBase58, useCredit, validateCreditSession } from './agent/index.js';
|
|
7
7
|
export { CustomPriceProvider, PriceConfig, PriceData, clearPriceCache, configurePricing, formatPriceDisplay, formatPriceSync, getProviders, getSolPrice, lamportsToSol, lamportsToUsd, usdToLamports } from './pricing/index.js';
|
|
8
8
|
import '@solana/web3.js';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,9 @@ export * from '@x402/core/types';
|
|
|
3
3
|
export * from '@x402/core/client';
|
|
4
4
|
export * from '@x402/svm';
|
|
5
5
|
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
|
|
6
|
-
import { PublicKey, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
|
|
6
|
+
import { PublicKey, Transaction, SystemProgram, LAMPORTS_PER_SOL, Keypair, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } from '@solana/web3.js';
|
|
7
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
8
|
+
import bs58 from 'bs58';
|
|
7
9
|
import { SignJWT, jwtVerify } from 'jose';
|
|
8
10
|
|
|
9
11
|
// src/index.ts
|
|
@@ -117,6 +119,133 @@ function createX402AuthorizationHeader(signature, paymentRequiredHeader) {
|
|
|
117
119
|
const token = encodePaymentSignatureHeader(payload);
|
|
118
120
|
return `x402 ${token}`;
|
|
119
121
|
}
|
|
122
|
+
async function sendSolanaPayment({
|
|
123
|
+
connection,
|
|
124
|
+
wallet,
|
|
125
|
+
recipientAddress,
|
|
126
|
+
amount,
|
|
127
|
+
// memo, // TODO: Add memo support to transaction
|
|
128
|
+
commitment = "confirmed"
|
|
129
|
+
}) {
|
|
130
|
+
if (!wallet.publicKey) {
|
|
131
|
+
throw new Error("Wallet not connected");
|
|
132
|
+
}
|
|
133
|
+
if (amount <= 0n) {
|
|
134
|
+
throw new Error("Amount must be greater than 0");
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const recipientPubkey = new PublicKey(recipientAddress);
|
|
138
|
+
const transaction = new Transaction().add(
|
|
139
|
+
SystemProgram.transfer({
|
|
140
|
+
fromPubkey: wallet.publicKey,
|
|
141
|
+
toPubkey: recipientPubkey,
|
|
142
|
+
lamports: amount
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(commitment);
|
|
146
|
+
transaction.recentBlockhash = blockhash;
|
|
147
|
+
transaction.feePayer = wallet.publicKey;
|
|
148
|
+
const signature = await wallet.sendTransaction(transaction, connection);
|
|
149
|
+
await connection.confirmTransaction({
|
|
150
|
+
signature,
|
|
151
|
+
blockhash,
|
|
152
|
+
lastValidBlockHeight
|
|
153
|
+
}, commitment);
|
|
154
|
+
return {
|
|
155
|
+
signature,
|
|
156
|
+
amountSol: Number(amount) / LAMPORTS_PER_SOL
|
|
157
|
+
};
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error("Payment failed:", error);
|
|
160
|
+
throw new Error(error.message || "Payment failed");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function usePaywallResource({
|
|
164
|
+
url,
|
|
165
|
+
connection,
|
|
166
|
+
wallet
|
|
167
|
+
}) {
|
|
168
|
+
const [data, setData] = useState(null);
|
|
169
|
+
const [isLocked, setIsLocked] = useState(true);
|
|
170
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
171
|
+
const [error, setError] = useState(null);
|
|
172
|
+
const [paymentHeader, setPaymentHeader] = useState(null);
|
|
173
|
+
const [price, setPrice] = useState();
|
|
174
|
+
const [recipient, setRecipient] = useState();
|
|
175
|
+
const fetchData = useCallback(async (authHeader) => {
|
|
176
|
+
setIsLoading(true);
|
|
177
|
+
setError(null);
|
|
178
|
+
try {
|
|
179
|
+
const headers = {
|
|
180
|
+
"Content-Type": "application/json"
|
|
181
|
+
};
|
|
182
|
+
if (authHeader) {
|
|
183
|
+
headers["Authorization"] = authHeader;
|
|
184
|
+
}
|
|
185
|
+
const res = await fetch(url, { headers });
|
|
186
|
+
if (res.status === 402) {
|
|
187
|
+
const wwwAuth = res.headers.get("WWW-Authenticate");
|
|
188
|
+
if (wwwAuth) {
|
|
189
|
+
setPaymentHeader(wwwAuth);
|
|
190
|
+
try {
|
|
191
|
+
const match = wwwAuth.match(/amount="([^"]+)",\s*payTo="([^"]+)"/);
|
|
192
|
+
if (match) {
|
|
193
|
+
setPrice(BigInt(match[1]));
|
|
194
|
+
setRecipient(match[2]);
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.warn("Failed to parse 402 details from header", e);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
setIsLocked(true);
|
|
201
|
+
setData(null);
|
|
202
|
+
} else if (res.ok) {
|
|
203
|
+
const json = await res.json();
|
|
204
|
+
setData(json);
|
|
205
|
+
setIsLocked(false);
|
|
206
|
+
} else {
|
|
207
|
+
throw new Error(`Request failed with status ${res.status}`);
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
setError(err.message || "Failed to fetch resource");
|
|
211
|
+
} finally {
|
|
212
|
+
setIsLoading(false);
|
|
213
|
+
}
|
|
214
|
+
}, [url]);
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
fetchData();
|
|
217
|
+
}, [fetchData]);
|
|
218
|
+
const unlock = useCallback(async () => {
|
|
219
|
+
if (!paymentHeader || !price || !recipient) {
|
|
220
|
+
setError("Missing payment requirements");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
setIsLoading(true);
|
|
224
|
+
try {
|
|
225
|
+
const { signature } = await sendSolanaPayment({
|
|
226
|
+
connection,
|
|
227
|
+
wallet,
|
|
228
|
+
recipientAddress: recipient,
|
|
229
|
+
amount: price
|
|
230
|
+
});
|
|
231
|
+
const authHeader = createX402AuthorizationHeader(signature, paymentHeader);
|
|
232
|
+
await fetchData(authHeader);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error("Unlock failed", err);
|
|
235
|
+
setError(err.message || "Payment/Unlock failed");
|
|
236
|
+
setIsLoading(false);
|
|
237
|
+
}
|
|
238
|
+
}, [connection, wallet, paymentHeader, price, recipient, fetchData]);
|
|
239
|
+
return {
|
|
240
|
+
data,
|
|
241
|
+
isLocked,
|
|
242
|
+
isLoading,
|
|
243
|
+
price,
|
|
244
|
+
recipient,
|
|
245
|
+
error,
|
|
246
|
+
unlock
|
|
247
|
+
};
|
|
248
|
+
}
|
|
120
249
|
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
121
250
|
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
122
251
|
function createPriorityFeeInstructions(config2 = {}) {
|
|
@@ -164,8 +293,6 @@ async function buildVersionedTransaction(config2) {
|
|
|
164
293
|
lastValidBlockHeight
|
|
165
294
|
};
|
|
166
295
|
}
|
|
167
|
-
|
|
168
|
-
// src/agent/agentPayment.ts
|
|
169
296
|
var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
170
297
|
function isValidWalletAddress(address) {
|
|
171
298
|
if (!address || typeof address !== "string") return false;
|
|
@@ -266,15 +393,19 @@ async function hasAgentSufficientBalance(connection, agentKeypair, requiredLampo
|
|
|
266
393
|
};
|
|
267
394
|
}
|
|
268
395
|
function keypairFromBase58(base58Secret) {
|
|
269
|
-
|
|
270
|
-
|
|
396
|
+
try {
|
|
397
|
+
const bytes = bs58.decode(base58Secret);
|
|
398
|
+
if (bytes.length !== 64) {
|
|
399
|
+
throw new Error("Invalid secret key length. Expected 64 bytes.");
|
|
400
|
+
}
|
|
401
|
+
return Keypair.fromSecretKey(bytes);
|
|
402
|
+
} catch (error) {
|
|
271
403
|
const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
|
|
272
404
|
if (parts.length === 64) {
|
|
273
405
|
return Keypair.fromSecretKey(Uint8Array.from(parts));
|
|
274
406
|
}
|
|
275
407
|
throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
|
|
276
408
|
}
|
|
277
|
-
return Keypair.fromSecretKey(bytes);
|
|
278
409
|
}
|
|
279
410
|
function generateAgentKeypair() {
|
|
280
411
|
const keypair = Keypair.generate();
|
|
@@ -592,4 +723,4 @@ function getProviders() {
|
|
|
592
723
|
return PROVIDERS.map((p) => ({ name: p.name, url: p.url }));
|
|
593
724
|
}
|
|
594
725
|
|
|
595
|
-
export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, usdToLamports, useCredit, validateCreditSession };
|
|
726
|
+
export { addCredits, buildSolanaPayUrl, clearPriceCache, configurePricing, createCreditSession, createPaymentFlow, createPaymentReference, createX402AuthorizationHeader, executeAgentPayment, formatPriceDisplay, formatPriceSync, generateAgentKeypair, getAgentBalance, getProviders, getRemainingCredits, getSolPrice, hasAgentSufficientBalance, keypairFromBase58, lamportsToSol, lamportsToUsd, sendSolanaPayment, usdToLamports, useCredit, usePaywallResource, validateCreditSession };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alleyboss/micropay-solana-x402-paywall",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
4
4
|
"description": "Production-ready Solana micropayments library wrapper for official x402 SDK",
|
|
5
5
|
"author": "AlleyBoss",
|
|
6
6
|
"license": "MIT",
|
|
@@ -108,20 +108,26 @@
|
|
|
108
108
|
"prepublishOnly": "npm run test && npm run build"
|
|
109
109
|
},
|
|
110
110
|
"dependencies": {
|
|
111
|
+
"@types/bs58": "^4.0.4",
|
|
111
112
|
"@x402/core": "^2.1.0",
|
|
112
113
|
"@x402/next": "^2.1.0",
|
|
113
114
|
"@x402/svm": "^2.1.0",
|
|
115
|
+
"bs58": "^6.0.0",
|
|
114
116
|
"jose": "^6.1.3"
|
|
115
117
|
},
|
|
116
118
|
"peerDependencies": {
|
|
117
|
-
"@solana/web3.js": "^1.90.0"
|
|
119
|
+
"@solana/web3.js": "^1.90.0",
|
|
120
|
+
"react": ">=16.8.0",
|
|
121
|
+
"react-dom": ">=16.8.0"
|
|
118
122
|
},
|
|
119
123
|
"devDependencies": {
|
|
120
124
|
"@solana/web3.js": "^1.98.4",
|
|
121
125
|
"@types/express": "^5.0.6",
|
|
122
126
|
"@types/node": "^20",
|
|
127
|
+
"@types/react": "^19.2.7",
|
|
123
128
|
"@types/uuid": "^10.0.0",
|
|
124
129
|
"express": "^5.2.1",
|
|
130
|
+
"react": "^19.2.3",
|
|
125
131
|
"tsup": "^8.0.0",
|
|
126
132
|
"typescript": "^5",
|
|
127
133
|
"vitest": "^3.0.0"
|