@alleyboss/micropay-solana-x402-paywall 2.3.0 → 3.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 +72 -116
- package/dist/agent/index.cjs +358 -0
- package/dist/agent/index.cjs.map +1 -0
- package/dist/agent/index.d.cts +221 -0
- package/dist/agent/index.d.ts +221 -0
- package/dist/agent/index.js +347 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +10 -1
- package/dist/client/index.d.ts +10 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/express/index.cjs +79 -0
- package/dist/express/index.cjs.map +1 -0
- package/dist/express/index.d.cts +40 -0
- package/dist/express/index.d.ts +40 -0
- package/dist/express/index.js +76 -0
- package/dist/express/index.js.map +1 -0
- package/dist/index.cjs +315 -1116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -10
- package/dist/index.d.ts +6 -10
- package/dist/index.js +283 -1074
- package/dist/index.js.map +1 -1
- package/dist/session/index.cjs.map +1 -1
- package/dist/session/index.d.cts +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js.map +1 -1
- package/dist/{session-D2IoWAWV.d.cts → types-BWYQMw03.d.cts} +1 -16
- package/dist/{session-D2IoWAWV.d.ts → types-BWYQMw03.d.ts} +1 -16
- package/package.json +29 -59
- package/dist/client-D-dteoJw.d.cts +0 -63
- package/dist/client-DfCIRrNG.d.ts +0 -63
- package/dist/memory-Daxkczti.d.cts +0 -29
- package/dist/memory-Daxkczti.d.ts +0 -29
- package/dist/middleware/index.cjs +0 -273
- package/dist/middleware/index.cjs.map +0 -1
- package/dist/middleware/index.d.cts +0 -91
- package/dist/middleware/index.d.ts +0 -91
- package/dist/middleware/index.js +0 -267
- package/dist/middleware/index.js.map +0 -1
- package/dist/nextjs-BDyOqGAq.d.cts +0 -81
- package/dist/nextjs-CbX8_9yK.d.ts +0 -81
- package/dist/payment-BGp7eMQl.d.cts +0 -103
- package/dist/payment-BGp7eMQl.d.ts +0 -103
- package/dist/solana/index.cjs +0 -589
- package/dist/solana/index.cjs.map +0 -1
- package/dist/solana/index.d.cts +0 -240
- package/dist/solana/index.d.ts +0 -240
- package/dist/solana/index.js +0 -567
- package/dist/solana/index.js.map +0 -1
- package/dist/store/index.cjs +0 -99
- package/dist/store/index.cjs.map +0 -1
- package/dist/store/index.d.cts +0 -38
- package/dist/store/index.d.ts +0 -38
- package/dist/store/index.js +0 -96
- package/dist/store/index.js.map +0 -1
- package/dist/utils/index.cjs +0 -68
- package/dist/utils/index.cjs.map +0 -1
- package/dist/utils/index.d.cts +0 -30
- package/dist/utils/index.d.ts +0 -30
- package/dist/utils/index.js +0 -65
- package/dist/utils/index.js.map +0 -1
- package/dist/x402/index.cjs +0 -387
- package/dist/x402/index.cjs.map +0 -1
- package/dist/x402/index.d.cts +0 -96
- package/dist/x402/index.d.ts +0 -96
- package/dist/x402/index.js +0 -375
- package/dist/x402/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @alleyboss/micropay-solana-x402-paywall
|
|
2
2
|
|
|
3
|
-
> Production-ready Solana micropayments library
|
|
3
|
+
> Production-ready Solana micropayments library wrapper for the official x402 SDK.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@alleyboss/micropay-solana-x402-paywall)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -8,158 +8,114 @@
|
|
|
8
8
|
|
|
9
9
|
## 🚀 What It Does
|
|
10
10
|
|
|
11
|
-
Turn any content into paid content with **one-time micropayments** on Solana.
|
|
11
|
+
Turn any content into paid content with **one-time micropayments** on Solana. fully compatible with the official [x402.org](https://x402.org) protocol.
|
|
12
|
+
|
|
13
|
+
This library enhances the official SDK with features like **AI Agent Payments**, **Hybrid Sessions**, and **Express.js Middleware**.
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
|
-
npm install @alleyboss/micropay-solana-x402-paywall @solana/web3.js
|
|
16
|
+
npm install @alleyboss/micropay-solana-x402-paywall @x402/core @x402/svm @solana/web3.js
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
## ✨ Features
|
|
18
20
|
|
|
19
|
-
| Feature | Description |
|
|
20
|
-
|
|
21
|
-
| 💰 **SOL & USDC
|
|
22
|
-
| 🔐 **x402 Protocol** | Full HTTP 402 compliance
|
|
23
|
-
| 🔑 **JWT Sessions** |
|
|
24
|
-
| 🛡️ **
|
|
25
|
-
| 🔌 **Express
|
|
26
|
-
| 💵 **Price Conversion** | USD↔SOL with multi-provider fallback |
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
## 📦 Quick Example
|
|
21
|
+
| Feature | Description | Status |
|
|
22
|
+
|---------|-------------|--------|
|
|
23
|
+
| 💰 **SOL & USDC** | Native SOL and SPL tokens (USDC, USDT) | ✅ Verified by `@x402/svm` |
|
|
24
|
+
| 🔐 **x402 Protocol** | Full HTTP 402 compliance | ✅ Powered by `@x402/core` |
|
|
25
|
+
| 🔑 **JWT Sessions** | "Pay once, unlock for 24h" logic | ✅ Built-in (Hybrid Support) |
|
|
26
|
+
| 🛡️ **Replay Protection** | Prevent double-spend / replay attacks | ✅ Managed by x402 Facilitator |
|
|
27
|
+
| 🔌 **Express Integration** | Middleware for Express/Node.js | ✅ Built-in |
|
|
28
|
+
| 💵 **Price Conversion** | USD↔SOL with multi-provider fallback | ✅ Built-in |
|
|
29
|
+
| 🤖 **AI Agents** | Autonomous payment execution for agents | ✅ Built-in |
|
|
30
|
+
| ⚡ **Priority Fees** | Compute unit price optimization | ✅ Supported (Agent Module) |
|
|
31
|
+
| 📦 **Versioned Tx** | v0 Transaction support | ✅ Native (x402 SDK) |
|
|
32
|
+
| 🌳 **Tree-Shakeable** | Modular exports | ✅ Built-in |
|
|
33
|
+
|
|
34
|
+
## 📦 Quick Example (Express.js)
|
|
33
35
|
|
|
34
36
|
```typescript
|
|
35
|
-
import
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
import express from 'express';
|
|
38
|
+
import { x402ResourceServer } from '@x402/core/server';
|
|
39
|
+
import { x402Middleware } from '@alleyboss/micropay-solana-x402-paywall/express';
|
|
40
|
+
|
|
41
|
+
const app = express();
|
|
42
|
+
// The x402ResourceServer handles protocol logic and facilitator communication
|
|
43
|
+
const server = new x402ResourceServer({
|
|
44
|
+
facilitatorUrl: process.env.X402_FACILITATOR_URL,
|
|
45
|
+
serviceId: process.env.X402_SERVICE_ID,
|
|
43
46
|
});
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
48
|
+
app.get('/premium', x402Middleware(server, {
|
|
49
|
+
accepts: {
|
|
50
|
+
scheme: 'exact',
|
|
51
|
+
amount: '1000000', // 0.001 SOL
|
|
52
|
+
network: 'solana-mainnet'
|
|
53
|
+
},
|
|
54
|
+
description: 'Premium Article'
|
|
55
|
+
}), (req, res) => {
|
|
56
|
+
// Session token or payment proof is available
|
|
57
|
+
res.send('Thank you for your payment!');
|
|
58
|
+
});
|
|
53
59
|
```
|
|
54
60
|
|
|
55
61
|
## 🔧 Modules
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
Import only what you need:
|
|
58
64
|
|
|
59
65
|
```typescript
|
|
60
|
-
//
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
// Session management
|
|
64
|
-
import { createSession, validateSession } from '@alleyboss/micropay-solana-x402-paywall/session';
|
|
65
|
-
|
|
66
|
-
// x402 protocol
|
|
67
|
-
import { buildPaymentRequirement } from '@alleyboss/micropay-solana-x402-paywall/x402';
|
|
66
|
+
// Express Middleware
|
|
67
|
+
import { x402Middleware } from '@alleyboss/micropay-solana-x402-paywall/express';
|
|
68
68
|
|
|
69
|
-
//
|
|
70
|
-
import {
|
|
69
|
+
// AI Agent Payments
|
|
70
|
+
import { executeAgentPayment } from '@alleyboss/micropay-solana-x402-paywall/agent';
|
|
71
71
|
|
|
72
|
-
//
|
|
73
|
-
import {
|
|
72
|
+
// Pricing Utilities
|
|
73
|
+
import { getSolPrice, lamportsToUsd } from '@alleyboss/micropay-solana-x402-paywall/pricing';
|
|
74
74
|
|
|
75
|
-
//
|
|
76
|
-
import {
|
|
77
|
-
|
|
78
|
-
// Price conversion (4-provider rotation)
|
|
79
|
-
import { getSolPrice, formatPriceDisplay, configurePricing } from '@alleyboss/micropay-solana-x402-paywall/pricing';
|
|
75
|
+
// Session Management (Hybrid)
|
|
76
|
+
import { createSession, validateSession } from '@alleyboss/micropay-solana-x402-paywall/session';
|
|
80
77
|
|
|
81
|
-
//
|
|
82
|
-
import {
|
|
78
|
+
// Client Helpers
|
|
79
|
+
import { createPaymentFlow } from '@alleyboss/micropay-solana-x402-paywall/client';
|
|
83
80
|
```
|
|
84
81
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
### x402 Protocol Compliance
|
|
82
|
+
## 🤖 AI Agent Payments
|
|
88
83
|
|
|
89
|
-
|
|
84
|
+
Enable autonomous AI agents to pay for premium API access.
|
|
90
85
|
|
|
91
86
|
```typescript
|
|
92
|
-
import {
|
|
93
|
-
|
|
94
|
-
// Create 402 response with X-Payment-Required header
|
|
95
|
-
const response = create402Response({
|
|
96
|
-
scheme: 'exact',
|
|
97
|
-
network: 'solana-mainnet',
|
|
98
|
-
maxAmountRequired: '10000000',
|
|
99
|
-
payTo: 'CreatorWallet...',
|
|
100
|
-
resource: '/api/premium',
|
|
101
|
-
description: 'Premium content access',
|
|
102
|
-
maxTimeoutSeconds: 300,
|
|
103
|
-
asset: 'native',
|
|
104
|
-
});
|
|
105
|
-
// Response includes: X-Payment-Required: <base64-encoded-requirement>
|
|
106
|
-
|
|
107
|
-
// Parse X-Payment header from client
|
|
108
|
-
const payload = parsePaymentHeader(request.headers.get('x-payment'));
|
|
109
|
-
```
|
|
87
|
+
import { executeAgentPayment } from '@alleyboss/micropay-solana-x402-paywall/agent';
|
|
88
|
+
import { Keypair, Connection } from '@solana/web3.js';
|
|
110
89
|
|
|
111
|
-
|
|
90
|
+
const agentKeypair = Keypair.fromSecretKey(/* ... */);
|
|
112
91
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Priority fees
|
|
120
|
-
import { createPriorityFeeInstructions } from '@alleyboss/micropay-solana-x402-paywall/solana';
|
|
121
|
-
|
|
122
|
-
const instructions = createPriorityFeeInstructions({
|
|
123
|
-
enabled: true,
|
|
124
|
-
microLamports: 5000,
|
|
125
|
-
computeUnits: 200_000,
|
|
92
|
+
const result = await executeAgentPayment({
|
|
93
|
+
connection: new Connection('https://api.mainnet-beta.solana.com'),
|
|
94
|
+
agentKeypair,
|
|
95
|
+
recipientAddress: 'CREATOR_WALLET',
|
|
96
|
+
amountLamports: 2_000_000n,
|
|
97
|
+
priorityFee: { enabled: true, microLamports: 10000 }, // Priority Fees Supported
|
|
126
98
|
});
|
|
127
99
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const { transaction } = await buildVersionedTransaction({
|
|
132
|
-
connection,
|
|
133
|
-
payer: wallet.publicKey,
|
|
134
|
-
instructions: [transferIx],
|
|
135
|
-
priorityFee: { enabled: true },
|
|
136
|
-
});
|
|
100
|
+
if (result.success) {
|
|
101
|
+
console.log('Payment confirmed:', result.signature);
|
|
102
|
+
}
|
|
137
103
|
```
|
|
138
104
|
|
|
139
|
-
##
|
|
105
|
+
## 📚 Documentation
|
|
140
106
|
|
|
141
|
-
|
|
107
|
+
For full documentation:
|
|
108
|
+
- **Library Docs**: [solana-x402-paywall.vercel.app/docs](https://solana-x402-paywall.vercel.app/docs)
|
|
109
|
+
- **Protocol Docs**: [x402.org](https://docs.x402.org)
|
|
142
110
|
|
|
143
|
-
|
|
144
|
-
const config = {
|
|
145
|
-
network: 'mainnet-beta',
|
|
146
|
-
// Tatum.io
|
|
147
|
-
tatumApiKey: 'your-key',
|
|
148
|
-
// Or custom (Helius, QuickNode, etc.)
|
|
149
|
-
rpcUrl: 'https://your-rpc.com',
|
|
150
|
-
// Optional: enable fallback
|
|
151
|
-
enableFallback: true,
|
|
152
|
-
fallbackRpcUrls: ['https://backup.rpc.com'],
|
|
153
|
-
};
|
|
154
|
-
```
|
|
111
|
+
## ☕ Support
|
|
155
112
|
|
|
156
|
-
|
|
113
|
+
If you find this library useful, you can buy me a coffee by sending some SOL to:
|
|
157
114
|
|
|
158
|
-
|
|
115
|
+
**`7fPjNJaEHtepp1ZRr6GsaW1k22U1FupQtwuHUkTb6Xg9`**
|
|
159
116
|
|
|
160
|
-
|
|
117
|
+
Your support helps maintain this project!
|
|
161
118
|
|
|
162
119
|
## 📄 License
|
|
163
120
|
|
|
164
121
|
MIT © AlleyBoss
|
|
165
|
-
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var web3_js = require('@solana/web3.js');
|
|
4
|
+
var jose = require('jose');
|
|
5
|
+
var uuid = require('uuid');
|
|
6
|
+
|
|
7
|
+
// src/agent/agentPayment.ts
|
|
8
|
+
var DEFAULT_COMPUTE_UNITS = 2e5;
|
|
9
|
+
var DEFAULT_MICRO_LAMPORTS = 1e3;
|
|
10
|
+
function createPriorityFeeInstructions(config = {}) {
|
|
11
|
+
const { enabled = false, microLamports, computeUnits } = config;
|
|
12
|
+
if (!enabled) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const instructions = [];
|
|
16
|
+
const units = computeUnits ?? DEFAULT_COMPUTE_UNITS;
|
|
17
|
+
instructions.push(web3_js.ComputeBudgetProgram.setComputeUnitLimit({ units }));
|
|
18
|
+
const price = microLamports ?? DEFAULT_MICRO_LAMPORTS;
|
|
19
|
+
instructions.push(web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: price }));
|
|
20
|
+
return instructions;
|
|
21
|
+
}
|
|
22
|
+
async function buildVersionedTransaction(config) {
|
|
23
|
+
const {
|
|
24
|
+
connection,
|
|
25
|
+
payer,
|
|
26
|
+
instructions,
|
|
27
|
+
priorityFee,
|
|
28
|
+
recentBlockhash
|
|
29
|
+
} = config;
|
|
30
|
+
const priorityIxs = createPriorityFeeInstructions(priorityFee);
|
|
31
|
+
const allInstructions = [...priorityIxs, ...instructions];
|
|
32
|
+
let blockhash;
|
|
33
|
+
let lastValidBlockHeight;
|
|
34
|
+
if (recentBlockhash) {
|
|
35
|
+
blockhash = recentBlockhash;
|
|
36
|
+
const slot = await connection.getSlot();
|
|
37
|
+
lastValidBlockHeight = slot + 150;
|
|
38
|
+
} else {
|
|
39
|
+
const latestBlockhash = await connection.getLatestBlockhash("confirmed");
|
|
40
|
+
blockhash = latestBlockhash.blockhash;
|
|
41
|
+
lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
|
|
42
|
+
}
|
|
43
|
+
const message = new web3_js.TransactionMessage({
|
|
44
|
+
payerKey: payer,
|
|
45
|
+
recentBlockhash: blockhash,
|
|
46
|
+
instructions: allInstructions
|
|
47
|
+
}).compileToV0Message([]);
|
|
48
|
+
const transaction = new web3_js.VersionedTransaction(message);
|
|
49
|
+
return {
|
|
50
|
+
transaction,
|
|
51
|
+
blockhash,
|
|
52
|
+
lastValidBlockHeight
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/agent/agentPayment.ts
|
|
57
|
+
var WALLET_REGEX = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
58
|
+
function isValidWalletAddress(address) {
|
|
59
|
+
if (!address || typeof address !== "string") return false;
|
|
60
|
+
return WALLET_REGEX.test(address);
|
|
61
|
+
}
|
|
62
|
+
async function executeAgentPayment(params) {
|
|
63
|
+
const {
|
|
64
|
+
connection,
|
|
65
|
+
agentKeypair,
|
|
66
|
+
recipientAddress,
|
|
67
|
+
amountLamports,
|
|
68
|
+
priorityFee,
|
|
69
|
+
confirmationTimeout = 6e4
|
|
70
|
+
} = params;
|
|
71
|
+
if (!isValidWalletAddress(recipientAddress)) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: "Invalid recipient address format"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (amountLamports <= 0n) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: "Amount must be greater than 0"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const recipientPubkey = new web3_js.PublicKey(recipientAddress);
|
|
85
|
+
const transferInstruction = web3_js.SystemProgram.transfer({
|
|
86
|
+
fromPubkey: agentKeypair.publicKey,
|
|
87
|
+
toPubkey: recipientPubkey,
|
|
88
|
+
lamports: amountLamports
|
|
89
|
+
});
|
|
90
|
+
const { transaction, lastValidBlockHeight } = await buildVersionedTransaction({
|
|
91
|
+
connection,
|
|
92
|
+
payer: agentKeypair.publicKey,
|
|
93
|
+
instructions: [transferInstruction],
|
|
94
|
+
priorityFee
|
|
95
|
+
});
|
|
96
|
+
transaction.sign([agentKeypair]);
|
|
97
|
+
const signature = await connection.sendTransaction(transaction, {
|
|
98
|
+
maxRetries: 3,
|
|
99
|
+
skipPreflight: false
|
|
100
|
+
});
|
|
101
|
+
const confirmationPromise = connection.confirmTransaction(
|
|
102
|
+
{
|
|
103
|
+
signature,
|
|
104
|
+
lastValidBlockHeight,
|
|
105
|
+
blockhash: transaction.message.recentBlockhash
|
|
106
|
+
},
|
|
107
|
+
"confirmed"
|
|
108
|
+
);
|
|
109
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
110
|
+
setTimeout(() => reject(new Error("Confirmation timeout")), confirmationTimeout);
|
|
111
|
+
});
|
|
112
|
+
const confirmation = await Promise.race([confirmationPromise, timeoutPromise]);
|
|
113
|
+
if (confirmation.value.err) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
signature,
|
|
117
|
+
error: "Transaction failed on-chain"
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const txDetails = await connection.getTransaction(signature, {
|
|
121
|
+
commitment: "confirmed",
|
|
122
|
+
maxSupportedTransactionVersion: 0
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
signature,
|
|
127
|
+
confirmedAt: txDetails?.blockTime ?? Math.floor(Date.now() / 1e3),
|
|
128
|
+
slot: txDetails?.slot ?? confirmation.context.slot,
|
|
129
|
+
amountLamports,
|
|
130
|
+
amountSol: Number(amountLamports) / web3_js.LAMPORTS_PER_SOL
|
|
131
|
+
};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: errorMessage
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function getAgentBalance(connection, agentKeypair) {
|
|
141
|
+
const balance = await connection.getBalance(agentKeypair.publicKey);
|
|
142
|
+
return {
|
|
143
|
+
balance: BigInt(balance),
|
|
144
|
+
balanceSol: balance / web3_js.LAMPORTS_PER_SOL
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function hasAgentSufficientBalance(connection, agentKeypair, requiredLamports) {
|
|
148
|
+
const { balance } = await getAgentBalance(connection, agentKeypair);
|
|
149
|
+
const totalRequired = requiredLamports + 10000n;
|
|
150
|
+
return {
|
|
151
|
+
sufficient: balance >= totalRequired,
|
|
152
|
+
balance,
|
|
153
|
+
required: totalRequired
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function keypairFromBase58(base58Secret) {
|
|
157
|
+
const bytes = Buffer.from(base58Secret, "base64");
|
|
158
|
+
if (bytes.length !== 64) {
|
|
159
|
+
const parts = base58Secret.split(",").map((n) => parseInt(n.trim(), 10));
|
|
160
|
+
if (parts.length === 64) {
|
|
161
|
+
return web3_js.Keypair.fromSecretKey(Uint8Array.from(parts));
|
|
162
|
+
}
|
|
163
|
+
throw new Error("Invalid secret key format. Expected base58 string or comma-separated bytes.");
|
|
164
|
+
}
|
|
165
|
+
return web3_js.Keypair.fromSecretKey(bytes);
|
|
166
|
+
}
|
|
167
|
+
function generateAgentKeypair() {
|
|
168
|
+
const keypair = web3_js.Keypair.generate();
|
|
169
|
+
const secretBytes = Array.from(keypair.secretKey);
|
|
170
|
+
return {
|
|
171
|
+
keypair,
|
|
172
|
+
secretBase58: secretBytes.join(","),
|
|
173
|
+
// Comma-separated for easy storage
|
|
174
|
+
publicKey: keypair.publicKey.toBase58()
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
var MAX_CREDITS = 1e3;
|
|
178
|
+
var MIN_SECRET_LENGTH = 32;
|
|
179
|
+
function getSecretKey(secret) {
|
|
180
|
+
if (!secret || typeof secret !== "string") {
|
|
181
|
+
throw new Error("Session secret is required");
|
|
182
|
+
}
|
|
183
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
184
|
+
throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);
|
|
185
|
+
}
|
|
186
|
+
return new TextEncoder().encode(secret);
|
|
187
|
+
}
|
|
188
|
+
function validateWalletAddress(address) {
|
|
189
|
+
if (!address || typeof address !== "string") return false;
|
|
190
|
+
const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
191
|
+
return base58Regex.test(address);
|
|
192
|
+
}
|
|
193
|
+
async function createCreditSession(walletAddress, purchaseId, config) {
|
|
194
|
+
if (!validateWalletAddress(walletAddress)) {
|
|
195
|
+
throw new Error("Invalid wallet address format");
|
|
196
|
+
}
|
|
197
|
+
if (config.initialCredits <= 0 || config.initialCredits > MAX_CREDITS) {
|
|
198
|
+
throw new Error(`Credits must be between 1 and ${MAX_CREDITS}`);
|
|
199
|
+
}
|
|
200
|
+
if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 8760) {
|
|
201
|
+
throw new Error("Session duration must be between 1 and 8760 hours (1 year)");
|
|
202
|
+
}
|
|
203
|
+
const sessionId = uuid.v4();
|
|
204
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
205
|
+
const expiresAt = now + config.durationHours * 3600;
|
|
206
|
+
const bundleExpiry = config.bundleExpiryHours ? now + config.bundleExpiryHours * 3600 : expiresAt;
|
|
207
|
+
const session = {
|
|
208
|
+
id: sessionId,
|
|
209
|
+
walletAddress,
|
|
210
|
+
unlockedArticles: [purchaseId],
|
|
211
|
+
siteWideUnlock: false,
|
|
212
|
+
createdAt: now,
|
|
213
|
+
expiresAt,
|
|
214
|
+
credits: config.initialCredits,
|
|
215
|
+
bundleExpiry,
|
|
216
|
+
bundleType: config.bundleType
|
|
217
|
+
};
|
|
218
|
+
const payload = {
|
|
219
|
+
sub: walletAddress,
|
|
220
|
+
sid: sessionId,
|
|
221
|
+
articles: session.unlockedArticles,
|
|
222
|
+
siteWide: false,
|
|
223
|
+
credits: config.initialCredits,
|
|
224
|
+
bundleExpiry,
|
|
225
|
+
bundleType: config.bundleType,
|
|
226
|
+
iat: now,
|
|
227
|
+
exp: expiresAt
|
|
228
|
+
};
|
|
229
|
+
const token = await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(`${config.durationHours}h`).sign(getSecretKey(config.secret));
|
|
230
|
+
return { token, session };
|
|
231
|
+
}
|
|
232
|
+
async function validateCreditSession(token, secret) {
|
|
233
|
+
if (!token || typeof token !== "string") {
|
|
234
|
+
return { valid: false, reason: "Invalid token format" };
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const { payload } = await jose.jwtVerify(token, getSecretKey(secret));
|
|
238
|
+
const creditPayload = payload;
|
|
239
|
+
if (!creditPayload.sub || !creditPayload.sid || !creditPayload.exp) {
|
|
240
|
+
return { valid: false, reason: "Malformed session payload" };
|
|
241
|
+
}
|
|
242
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
243
|
+
if (creditPayload.exp < now) {
|
|
244
|
+
return { valid: false, reason: "Session expired" };
|
|
245
|
+
}
|
|
246
|
+
if (creditPayload.bundleExpiry && creditPayload.bundleExpiry < now) {
|
|
247
|
+
return { valid: false, reason: "Bundle expired" };
|
|
248
|
+
}
|
|
249
|
+
if (!validateWalletAddress(creditPayload.sub)) {
|
|
250
|
+
return { valid: false, reason: "Invalid session data" };
|
|
251
|
+
}
|
|
252
|
+
const session = {
|
|
253
|
+
id: creditPayload.sid,
|
|
254
|
+
walletAddress: creditPayload.sub,
|
|
255
|
+
unlockedArticles: Array.isArray(creditPayload.articles) ? creditPayload.articles : [],
|
|
256
|
+
siteWideUnlock: Boolean(creditPayload.siteWide),
|
|
257
|
+
createdAt: creditPayload.iat ?? 0,
|
|
258
|
+
expiresAt: creditPayload.exp,
|
|
259
|
+
credits: creditPayload.credits ?? 0,
|
|
260
|
+
bundleExpiry: creditPayload.bundleExpiry,
|
|
261
|
+
bundleType: creditPayload.bundleType
|
|
262
|
+
};
|
|
263
|
+
return { valid: true, session };
|
|
264
|
+
} catch {
|
|
265
|
+
return { valid: false, reason: "Invalid session" };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async function useCredit(token, secret, creditsToUse = 1) {
|
|
269
|
+
if (creditsToUse <= 0) {
|
|
270
|
+
return { success: false, remainingCredits: 0, error: "Invalid credit amount" };
|
|
271
|
+
}
|
|
272
|
+
const validation = await validateCreditSession(token, secret);
|
|
273
|
+
if (!validation.valid || !validation.session) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
remainingCredits: 0,
|
|
277
|
+
error: validation.reason || "Invalid session"
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const session = validation.session;
|
|
281
|
+
if (session.credits < creditsToUse) {
|
|
282
|
+
return {
|
|
283
|
+
success: false,
|
|
284
|
+
remainingCredits: session.credits,
|
|
285
|
+
error: "Insufficient credits"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const newCredits = session.credits - creditsToUse;
|
|
289
|
+
const payload = {
|
|
290
|
+
sub: session.walletAddress,
|
|
291
|
+
sid: session.id,
|
|
292
|
+
articles: session.unlockedArticles,
|
|
293
|
+
siteWide: session.siteWideUnlock,
|
|
294
|
+
credits: newCredits,
|
|
295
|
+
bundleExpiry: session.bundleExpiry,
|
|
296
|
+
bundleType: session.bundleType,
|
|
297
|
+
iat: session.createdAt,
|
|
298
|
+
exp: session.expiresAt
|
|
299
|
+
};
|
|
300
|
+
const newToken = await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).sign(getSecretKey(secret));
|
|
301
|
+
return {
|
|
302
|
+
success: true,
|
|
303
|
+
remainingCredits: newCredits,
|
|
304
|
+
newToken
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async function addCredits(token, secret, creditsToAdd) {
|
|
308
|
+
if (creditsToAdd <= 0 || creditsToAdd > MAX_CREDITS) {
|
|
309
|
+
return { success: false, error: "Invalid credit amount" };
|
|
310
|
+
}
|
|
311
|
+
const validation = await validateCreditSession(token, secret);
|
|
312
|
+
if (!validation.valid || !validation.session) {
|
|
313
|
+
return { success: false, error: validation.reason || "Invalid session" };
|
|
314
|
+
}
|
|
315
|
+
const session = validation.session;
|
|
316
|
+
const newCredits = Math.min(session.credits + creditsToAdd, MAX_CREDITS);
|
|
317
|
+
const payload = {
|
|
318
|
+
sub: session.walletAddress,
|
|
319
|
+
sid: session.id,
|
|
320
|
+
articles: session.unlockedArticles,
|
|
321
|
+
siteWide: session.siteWideUnlock,
|
|
322
|
+
credits: newCredits,
|
|
323
|
+
bundleExpiry: session.bundleExpiry,
|
|
324
|
+
bundleType: session.bundleType,
|
|
325
|
+
iat: session.createdAt,
|
|
326
|
+
exp: session.expiresAt
|
|
327
|
+
};
|
|
328
|
+
const newToken = await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).sign(getSecretKey(secret));
|
|
329
|
+
return {
|
|
330
|
+
success: true,
|
|
331
|
+
newToken,
|
|
332
|
+
totalCredits: newCredits
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async function getRemainingCredits(token, secret) {
|
|
336
|
+
const validation = await validateCreditSession(token, secret);
|
|
337
|
+
if (!validation.valid || !validation.session) {
|
|
338
|
+
return { credits: 0, valid: false };
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
credits: validation.session.credits,
|
|
342
|
+
valid: true,
|
|
343
|
+
bundleExpiry: validation.session.bundleExpiry
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
exports.addCredits = addCredits;
|
|
348
|
+
exports.createCreditSession = createCreditSession;
|
|
349
|
+
exports.executeAgentPayment = executeAgentPayment;
|
|
350
|
+
exports.generateAgentKeypair = generateAgentKeypair;
|
|
351
|
+
exports.getAgentBalance = getAgentBalance;
|
|
352
|
+
exports.getRemainingCredits = getRemainingCredits;
|
|
353
|
+
exports.hasAgentSufficientBalance = hasAgentSufficientBalance;
|
|
354
|
+
exports.keypairFromBase58 = keypairFromBase58;
|
|
355
|
+
exports.useCredit = useCredit;
|
|
356
|
+
exports.validateCreditSession = validateCreditSession;
|
|
357
|
+
//# sourceMappingURL=index.cjs.map
|
|
358
|
+
//# sourceMappingURL=index.cjs.map
|