@dexterai/x402 1.0.2 → 1.0.4
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 +139 -159
- package/dist/utils/index.cjs +110 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +67 -0
- package/dist/utils/index.d.ts +67 -0
- package/dist/utils/index.js +79 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -2,132 +2,91 @@
|
|
|
2
2
|
<img src="./assets/dexter-wordmark.svg" alt="Dexter" width="360">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<h1 align="center">@dexterai/x402</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<
|
|
9
|
-
<a href="https://www.npmjs.com/package/@dexterai/x402"><img src="https://img.shields.io/npm/v/@dexterai/x402.svg" alt="npm version"></a>
|
|
10
|
-
<a href="https://x402.dexter.cash"><img src="https://img.shields.io/badge/facilitator-x402.dexter.cash-orange.svg" alt="x402 Facilitator"></a>
|
|
8
|
+
<strong>The x402 SDK that actually works.</strong>
|
|
11
9
|
</p>
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@dexterai/x402"><img src="https://img.shields.io/npm/v/@dexterai/x402.svg" alt="npm"></a>
|
|
13
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E=18-brightgreen.svg" alt="Node"></a>
|
|
14
|
+
<a href="https://dexter.cash/sdk"><img src="https://img.shields.io/badge/🎮_Live_Demo-dexter.cash%2Fsdk-blueviolet" alt="Live Demo"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="https://dexter.cash/sdk"><strong>👉 Try it with real payments →</strong></a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ✨ Why This SDK?
|
|
24
|
+
|
|
25
|
+
- **🔗 Multi-chain** — Solana and Base, same API
|
|
26
|
+
- **⚡ x402 v2** — Full protocol support, verified working
|
|
27
|
+
- **⚛️ React Hook** — `useX402Payment` with loading states, balances, and transaction tracking
|
|
28
|
+
- **💰 Smart Balance Check** — Clear "insufficient funds" error *before* the wallet popup
|
|
29
|
+
- **👻 Phantom Compatible** — Handles Lighthouse safety assertions automatically
|
|
30
|
+
- **📦 Zero Config** — Wrap `fetch()`, payments just work
|
|
14
31
|
|
|
15
32
|
---
|
|
16
33
|
|
|
17
|
-
##
|
|
34
|
+
## 🎮 See It Working
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
**Don't take our word for it.** Make a real payment yourself:
|
|
37
|
+
|
|
38
|
+
**[→ dexter.cash/sdk](https://dexter.cash/sdk)**
|
|
39
|
+
|
|
40
|
+
The demo uses this exact SDK. Solana and Base. Real USDC. Real transactions.
|
|
24
41
|
|
|
25
42
|
---
|
|
26
43
|
|
|
27
|
-
## Quick Start
|
|
44
|
+
## 🚀 Quick Start
|
|
28
45
|
|
|
29
46
|
### Install
|
|
30
47
|
|
|
31
48
|
```bash
|
|
32
|
-
npm install @dexterai/x402
|
|
49
|
+
npm install @dexterai/x402
|
|
33
50
|
```
|
|
34
51
|
|
|
35
|
-
### Client (Browser
|
|
52
|
+
### Client (Browser)
|
|
36
53
|
|
|
37
54
|
```typescript
|
|
38
55
|
import { createX402Client } from '@dexterai/x402/client';
|
|
39
56
|
|
|
40
|
-
// Single wallet (Solana)
|
|
41
|
-
const client = createX402Client({
|
|
42
|
-
wallet: solanaWallet,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Multi-chain: provide wallets for each chain
|
|
46
57
|
const client = createX402Client({
|
|
47
58
|
wallets: {
|
|
48
59
|
solana: solanaWallet,
|
|
49
|
-
evm: evmWallet,
|
|
60
|
+
evm: evmWallet,
|
|
61
|
+
},
|
|
62
|
+
rpcUrls: {
|
|
63
|
+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'https://your-solana-rpc.com',
|
|
64
|
+
'eip155:8453': 'https://your-base-rpc.com',
|
|
50
65
|
},
|
|
51
|
-
preferredNetwork: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
52
66
|
});
|
|
53
67
|
|
|
54
|
-
//
|
|
68
|
+
// That's it. 402 responses are handled automatically.
|
|
55
69
|
const response = await client.fetch('https://api.example.com/protected');
|
|
56
70
|
```
|
|
57
71
|
|
|
58
|
-
### Server (Express/Next.js)
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { createX402Server } from '@dexterai/x402/server';
|
|
62
|
-
|
|
63
|
-
// Create server for Solana payments
|
|
64
|
-
const server = createX402Server({
|
|
65
|
-
payTo: 'YourSolanaAddress...',
|
|
66
|
-
network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Or for Base payments
|
|
70
|
-
const baseServer = createX402Server({
|
|
71
|
-
payTo: '0xYourEvmAddress...',
|
|
72
|
-
network: 'eip155:8453',
|
|
73
|
-
asset: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 },
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// In your route handler
|
|
77
|
-
app.post('/protected', async (req, res) => {
|
|
78
|
-
const paymentSig = req.headers['payment-signature'];
|
|
79
|
-
|
|
80
|
-
if (!paymentSig) {
|
|
81
|
-
const requirements = await server.buildRequirements({
|
|
82
|
-
amountAtomic: '50000', // 0.05 USDC
|
|
83
|
-
resourceUrl: req.originalUrl,
|
|
84
|
-
});
|
|
85
|
-
res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
|
|
86
|
-
return res.status(402).json({});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const result = await server.settlePayment(paymentSig);
|
|
90
|
-
if (!result.success) {
|
|
91
|
-
return res.status(402).json({ error: result.errorReason });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
res.json({ data: 'protected content', transaction: result.transaction });
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
72
|
### React
|
|
99
73
|
|
|
100
74
|
```tsx
|
|
101
75
|
import { useX402Payment } from '@dexterai/x402/react';
|
|
102
|
-
import { useWallet } from '@solana/wallet-adapter-react';
|
|
103
|
-
import { useAccount } from 'wagmi';
|
|
104
76
|
|
|
105
77
|
function PayButton() {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const {
|
|
110
|
-
fetch,
|
|
111
|
-
isLoading,
|
|
112
|
-
balances,
|
|
113
|
-
connectedChains,
|
|
114
|
-
transactionUrl,
|
|
115
|
-
} = useX402Payment({
|
|
116
|
-
wallets: {
|
|
117
|
-
solana: solanaWallet,
|
|
118
|
-
evm: evmWallet,
|
|
119
|
-
},
|
|
78
|
+
const { fetch, isLoading, balances, transactionUrl } = useX402Payment({
|
|
79
|
+
wallets: { solana: solanaWallet, evm: evmWallet },
|
|
80
|
+
rpcUrls: { /* your RPC endpoints */ },
|
|
120
81
|
});
|
|
121
82
|
|
|
122
83
|
return (
|
|
123
84
|
<div>
|
|
124
|
-
{balances.
|
|
125
|
-
<p key={b.network}>{b.chainName}: ${b.balance.toFixed(2)}</p>
|
|
126
|
-
))}
|
|
85
|
+
<p>Balance: ${balances[0]?.balance.toFixed(2)}</p>
|
|
127
86
|
<button onClick={() => fetch(url)} disabled={isLoading}>
|
|
128
|
-
{isLoading ? 'Paying...' : 'Pay
|
|
87
|
+
{isLoading ? 'Paying...' : 'Pay'}
|
|
129
88
|
</button>
|
|
130
|
-
{transactionUrl && <a href={transactionUrl}>View Transaction
|
|
89
|
+
{transactionUrl && <a href={transactionUrl}>View Transaction ↗</a>}
|
|
131
90
|
</div>
|
|
132
91
|
);
|
|
133
92
|
}
|
|
@@ -135,120 +94,141 @@ function PayButton() {
|
|
|
135
94
|
|
|
136
95
|
---
|
|
137
96
|
|
|
138
|
-
## Supported Networks
|
|
97
|
+
## 🌐 Supported Networks
|
|
139
98
|
|
|
140
|
-
| Network |
|
|
141
|
-
|
|
142
|
-
| Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` |
|
|
143
|
-
| Base Mainnet | `eip155:8453` |
|
|
144
|
-
| Arbitrum One | `eip155:42161` | USDC |
|
|
145
|
-
| Ethereum | `eip155:1` | USDC |
|
|
99
|
+
| Network | Identifier | Status |
|
|
100
|
+
|---------|------------|--------|
|
|
101
|
+
| Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | ✅ Verified |
|
|
102
|
+
| Base Mainnet | `eip155:8453` | ✅ Verified |
|
|
146
103
|
|
|
147
|
-
|
|
104
|
+
All networks use USDC.
|
|
148
105
|
|
|
149
|
-
|
|
106
|
+
---
|
|
150
107
|
|
|
151
|
-
|
|
108
|
+
## 📦 Package Exports
|
|
152
109
|
|
|
153
110
|
```typescript
|
|
111
|
+
// Client - browser & Node.js
|
|
154
112
|
import { createX402Client } from '@dexterai/x402/client';
|
|
155
113
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
114
|
+
// React hook
|
|
115
|
+
import { useX402Payment } from '@dexterai/x402/react';
|
|
116
|
+
|
|
117
|
+
// Server helpers (see note below)
|
|
118
|
+
import { createX402Server } from '@dexterai/x402/server';
|
|
119
|
+
|
|
120
|
+
// Chain adapters (advanced)
|
|
121
|
+
import { createSolanaAdapter, createEvmAdapter } from '@dexterai/x402/adapters';
|
|
164
122
|
|
|
165
|
-
|
|
123
|
+
// Utilities
|
|
124
|
+
import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
|
|
166
125
|
```
|
|
167
126
|
|
|
168
|
-
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🛠️ Utilities
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';
|
|
133
|
+
|
|
134
|
+
// Convert dollars to atomic units (for API calls)
|
|
135
|
+
toAtomicUnits(0.05, 6); // '50000'
|
|
136
|
+
toAtomicUnits(1.50, 6); // '1500000'
|
|
137
|
+
|
|
138
|
+
// Convert atomic units back to dollars (for display)
|
|
139
|
+
fromAtomicUnits('50000', 6); // 0.05
|
|
140
|
+
fromAtomicUnits(1500000n, 6); // 1.5
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 🖥️ Server SDK
|
|
169
146
|
|
|
170
147
|
```typescript
|
|
171
148
|
import { createX402Server } from '@dexterai/x402/server';
|
|
172
149
|
|
|
173
150
|
const server = createX402Server({
|
|
174
|
-
payTo: '
|
|
151
|
+
payTo: 'YourAddress...',
|
|
175
152
|
network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
|
|
176
|
-
asset: { address: 'mint', decimals: 6 },
|
|
177
153
|
facilitatorUrl: 'https://x402.dexter.cash',
|
|
178
|
-
defaultTimeoutSeconds: 60,
|
|
179
154
|
});
|
|
180
155
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await server.settlePayment(header);
|
|
185
|
-
```
|
|
156
|
+
// In your route handler
|
|
157
|
+
app.post('/protected', async (req, res) => {
|
|
158
|
+
const paymentSig = req.headers['payment-signature'];
|
|
186
159
|
|
|
187
|
-
|
|
160
|
+
if (!paymentSig) {
|
|
161
|
+
const requirements = await server.buildRequirements({
|
|
162
|
+
amountAtomic: '50000', // $0.05 USDC
|
|
163
|
+
resourceUrl: req.originalUrl,
|
|
164
|
+
});
|
|
165
|
+
res.setHeader('PAYMENT-REQUIRED', server.encodeRequirements(requirements));
|
|
166
|
+
return res.status(402).json({});
|
|
167
|
+
}
|
|
188
168
|
|
|
189
|
-
|
|
190
|
-
|
|
169
|
+
const result = await server.settlePayment(paymentSig);
|
|
170
|
+
if (!result.success) {
|
|
171
|
+
return res.status(402).json({ error: result.errorReason });
|
|
172
|
+
}
|
|
191
173
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
isLoading, // Payment in progress
|
|
195
|
-
status, // 'idle' | 'pending' | 'success' | 'error'
|
|
196
|
-
error, // Error if failed
|
|
197
|
-
transactionId, // Tx signature on success
|
|
198
|
-
transactionUrl, // Explorer link
|
|
199
|
-
balances, // Token balances per chain
|
|
200
|
-
connectedChains, // { solana: bool, evm: bool }
|
|
201
|
-
reset, // Clear state
|
|
202
|
-
refreshBalances, // Manual balance refresh
|
|
203
|
-
} = useX402Payment({ wallets, preferredNetwork, verbose });
|
|
174
|
+
res.json({ data: 'Your protected content' });
|
|
175
|
+
});
|
|
204
176
|
```
|
|
205
177
|
|
|
206
|
-
|
|
178
|
+
> ⚠️ **Note:** The server SDK has not been battle-tested in production yet. The client SDK and React hook have been verified with real payments at [dexter.cash/sdk](https://dexter.cash/sdk).
|
|
207
179
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 📋 API Reference
|
|
183
|
+
|
|
184
|
+
### `createX402Client(options)`
|
|
185
|
+
|
|
186
|
+
| Option | Type | Description |
|
|
187
|
+
|--------|------|-------------|
|
|
188
|
+
| `wallet` | `SolanaWallet` | Single Solana wallet (legacy) |
|
|
189
|
+
| `wallets` | `{ solana?, evm? }` | Multi-chain wallets |
|
|
190
|
+
| `preferredNetwork` | `string` | Prefer this network when multiple options available |
|
|
191
|
+
| `rpcUrls` | `Record<string, string>` | RPC endpoints per network (CAIP-2 format) |
|
|
192
|
+
| `maxAmountAtomic` | `string` | Maximum payment cap |
|
|
193
|
+
| `verbose` | `boolean` | Enable debug logging |
|
|
194
|
+
|
|
195
|
+
### `useX402Payment(options)`
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
|
|
199
|
+
| Property | Type | Description |
|
|
200
|
+
|----------|------|-------------|
|
|
201
|
+
| `fetch` | `function` | Payment-aware fetch |
|
|
202
|
+
| `isLoading` | `boolean` | Payment in progress |
|
|
203
|
+
| `status` | `string` | `'idle'` \| `'pending'` \| `'success'` \| `'error'` |
|
|
204
|
+
| `error` | `X402Error?` | Error details if failed |
|
|
205
|
+
| `transactionId` | `string?` | Transaction signature |
|
|
206
|
+
| `transactionUrl` | `string?` | Block explorer link |
|
|
207
|
+
| `balances` | `Balance[]` | Token balances per chain |
|
|
208
|
+
| `refreshBalances` | `function` | Manual refresh |
|
|
209
|
+
| `reset` | `function` | Clear state |
|
|
230
210
|
|
|
231
211
|
---
|
|
232
212
|
|
|
233
|
-
## Development
|
|
213
|
+
## 🔧 Development
|
|
234
214
|
|
|
235
215
|
```bash
|
|
236
216
|
npm run build # Build ESM + CJS
|
|
237
217
|
npm run dev # Watch mode
|
|
238
|
-
npm run typecheck # TypeScript
|
|
218
|
+
npm run typecheck # TypeScript
|
|
239
219
|
npm test # Run tests
|
|
240
220
|
```
|
|
241
221
|
|
|
242
222
|
---
|
|
243
223
|
|
|
244
|
-
##
|
|
224
|
+
## 📄 License
|
|
245
225
|
|
|
246
|
-
|
|
247
|
-
- [x402 Protocol Spec](https://docs.cdp.coinbase.com/x402)
|
|
248
|
-
- [Seller Onboarding](https://dexter.cash/onboard)
|
|
226
|
+
MIT — see [LICENSE](./LICENSE)
|
|
249
227
|
|
|
250
228
|
---
|
|
251
229
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
230
|
+
<p align="center">
|
|
231
|
+
<a href="https://x402.dexter.cash">Dexter Facilitator</a> ·
|
|
232
|
+
<a href="https://dexter.cash/sdk">Live Demo</a> ·
|
|
233
|
+
<a href="https://dexter.cash/onboard">Become a Seller</a>
|
|
234
|
+
</p>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/utils/index.ts
|
|
21
|
+
var utils_exports = {};
|
|
22
|
+
__export(utils_exports, {
|
|
23
|
+
fromAtomicUnits: () => fromAtomicUnits,
|
|
24
|
+
getChainFamily: () => getChainFamily,
|
|
25
|
+
getChainName: () => getChainName,
|
|
26
|
+
getExplorerUrl: () => getExplorerUrl,
|
|
27
|
+
toAtomicUnits: () => toAtomicUnits
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(utils_exports);
|
|
30
|
+
|
|
31
|
+
// src/types.ts
|
|
32
|
+
var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
33
|
+
var BASE_MAINNET_NETWORK = "eip155:8453";
|
|
34
|
+
|
|
35
|
+
// src/utils.ts
|
|
36
|
+
function toAtomicUnits(amount, decimals) {
|
|
37
|
+
const multiplier = Math.pow(10, decimals);
|
|
38
|
+
return Math.floor(amount * multiplier).toString();
|
|
39
|
+
}
|
|
40
|
+
function fromAtomicUnits(atomicUnits, decimals) {
|
|
41
|
+
const divisor = Math.pow(10, decimals);
|
|
42
|
+
return Number(atomicUnits) / divisor;
|
|
43
|
+
}
|
|
44
|
+
function getChainFamily(network) {
|
|
45
|
+
if (network.startsWith("solana:") || network === "solana") {
|
|
46
|
+
return "solana";
|
|
47
|
+
}
|
|
48
|
+
if (network.startsWith("eip155:") || ["base", "ethereum", "arbitrum"].includes(network)) {
|
|
49
|
+
return "evm";
|
|
50
|
+
}
|
|
51
|
+
return "unknown";
|
|
52
|
+
}
|
|
53
|
+
function getChainName(network) {
|
|
54
|
+
const mapping = {
|
|
55
|
+
[SOLANA_MAINNET_NETWORK]: "Solana",
|
|
56
|
+
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "Solana Devnet",
|
|
57
|
+
"solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "Solana Testnet",
|
|
58
|
+
"solana": "Solana",
|
|
59
|
+
[BASE_MAINNET_NETWORK]: "Base",
|
|
60
|
+
"eip155:84532": "Base Sepolia",
|
|
61
|
+
"eip155:1": "Ethereum",
|
|
62
|
+
"eip155:42161": "Arbitrum One",
|
|
63
|
+
"base": "Base",
|
|
64
|
+
"ethereum": "Ethereum",
|
|
65
|
+
"arbitrum": "Arbitrum"
|
|
66
|
+
};
|
|
67
|
+
return mapping[network] || network;
|
|
68
|
+
}
|
|
69
|
+
function getExplorerUrl(txSignature, network) {
|
|
70
|
+
const family = getChainFamily(network);
|
|
71
|
+
if (family === "solana") {
|
|
72
|
+
const isDevnet = network.includes("devnet") || network === "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
73
|
+
if (isDevnet) {
|
|
74
|
+
return `https://solscan.io/tx/${txSignature}?cluster=devnet`;
|
|
75
|
+
}
|
|
76
|
+
return `https://www.orbmarkets.io/tx/${txSignature}`;
|
|
77
|
+
}
|
|
78
|
+
if (family === "evm") {
|
|
79
|
+
let chainId = "8453";
|
|
80
|
+
if (network.startsWith("eip155:")) {
|
|
81
|
+
chainId = network.split(":")[1];
|
|
82
|
+
} else if (network === "ethereum") {
|
|
83
|
+
chainId = "1";
|
|
84
|
+
} else if (network === "arbitrum") {
|
|
85
|
+
chainId = "42161";
|
|
86
|
+
}
|
|
87
|
+
switch (chainId) {
|
|
88
|
+
case "8453":
|
|
89
|
+
return `https://basescan.org/tx/${txSignature}`;
|
|
90
|
+
case "84532":
|
|
91
|
+
return `https://sepolia.basescan.org/tx/${txSignature}`;
|
|
92
|
+
case "1":
|
|
93
|
+
return `https://etherscan.io/tx/${txSignature}`;
|
|
94
|
+
case "42161":
|
|
95
|
+
return `https://arbiscan.io/tx/${txSignature}`;
|
|
96
|
+
default:
|
|
97
|
+
return `https://basescan.org/tx/${txSignature}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return `https://solscan.io/tx/${txSignature}`;
|
|
101
|
+
}
|
|
102
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
103
|
+
0 && (module.exports = {
|
|
104
|
+
fromAtomicUnits,
|
|
105
|
+
getChainFamily,
|
|
106
|
+
getChainName,
|
|
107
|
+
getExplorerUrl,
|
|
108
|
+
toAtomicUnits
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts","../../src/types.ts","../../src/utils.ts"],"sourcesContent":["/**\n * @dexterai/x402 Utils\n *\n * Helper functions for x402 payments.\n *\n * @example\n * ```typescript\n * import { toAtomicUnits, fromAtomicUnits } from '@dexterai/x402/utils';\n *\n * const atomic = toAtomicUnits(0.05, 6); // '50000'\n * const human = fromAtomicUnits('50000', 6); // 0.05\n * ```\n */\n\nexport {\n toAtomicUnits,\n fromAtomicUnits,\n getChainFamily,\n getChainName,\n getExplorerUrl,\n type ChainFamily,\n} from '../utils';\n","/**\n * x402 v2 SDK — Shared Types\n *\n * Chain-agnostic types for x402 v2 payments.\n * Works with Solana, Base, and any future x402-compatible networks.\n */\n\n// ============================================================================\n// Network Constants\n// ============================================================================\n\n/** CAIP-2 network identifier for Solana mainnet */\nexport const SOLANA_MAINNET_NETWORK = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';\n\n/** CAIP-2 network identifier for Base mainnet */\nexport const BASE_MAINNET_NETWORK = 'eip155:8453';\n\n/** Alias for Solana mainnet */\nexport const SOLANA_MAINNET = SOLANA_MAINNET_NETWORK;\n\n/** Alias for Base mainnet */\nexport const BASE_MAINNET = BASE_MAINNET_NETWORK;\n\n// ============================================================================\n// Asset Constants\n// ============================================================================\n\n/** USDC mint on Solana mainnet */\nexport const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\n\n/** USDC address on Base mainnet */\nexport const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';\n\n// ============================================================================\n// Facilitator Constants\n// ============================================================================\n\n/** Dexter's public x402 v2 facilitator URL */\nexport const DEXTER_FACILITATOR_URL = 'https://x402.dexter.cash';\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/**\n * Asset configuration for payments\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n /** Optional: Human-readable symbol */\n symbol?: string;\n}\n\n/**\n * Resource info included in payment requirements\n */\nexport interface ResourceInfo {\n /** Resource URL */\n url: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the resource */\n mimeType?: string;\n}\n\n/**\n * Extra fields in payment requirements\n * Chain-specific fields may vary\n */\nexport interface AcceptsExtra {\n /** Facilitator address that pays tx fees (required) */\n feePayer: string;\n /** Token decimals (required) */\n decimals: number;\n /** EIP-712: Token name (EVM only) */\n name?: string;\n /** EIP-712: Token version (EVM only) */\n version?: string;\n /** Additional chain-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * A single payment option in the accepts array\n */\nexport interface PaymentAccept {\n /** Payment scheme (always 'exact' for x402 v2) */\n scheme: 'exact';\n /** CAIP-2 network identifier */\n network: string;\n /** Payment amount in atomic units (as string to avoid precision loss) */\n amount: string;\n /** Token address */\n asset: string;\n /** Seller's address to receive payment */\n payTo: string;\n /** Maximum seconds until payment expires */\n maxTimeoutSeconds: number;\n /** Chain-specific extra data */\n extra: AcceptsExtra;\n}\n\n/**\n * Full PaymentRequired structure (sent in PAYMENT-REQUIRED header)\n */\nexport interface PaymentRequired {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** Available payment options */\n accepts: PaymentAccept[];\n /** Optional error message */\n error?: string;\n}\n\n/**\n * PaymentSignature structure (sent in PAYMENT-SIGNATURE header)\n */\nexport interface PaymentSignature {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** The payment option that was accepted */\n accepted: PaymentAccept;\n /** The signed payment */\n payload: {\n /** Signed transaction (base64 for Solana, JSON for EVM) */\n transaction: string;\n };\n}\n\n// ============================================================================\n// Facilitator Response Types\n// ============================================================================\n\n/**\n * Response from /verify endpoint\n */\nexport interface VerifyResponse {\n /** Whether the payment is valid */\n isValid: boolean;\n /** Reason for invalidity (if invalid) */\n invalidReason?: string;\n /** Payer address */\n payer?: string;\n}\n\n/**\n * Response from /settle endpoint\n */\nexport interface SettleResponse {\n /** Whether settlement succeeded */\n success: boolean;\n /** Transaction signature/hash */\n transaction?: string;\n /** Network the payment was made on */\n network: string;\n /** Error reason (if failed) */\n errorReason?: string;\n /** Error code (if failed) */\n errorCode?: string;\n /** Payer address */\n payer?: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * SDK error codes\n */\nexport type X402ErrorCode =\n // Client errors\n | 'missing_payment_required_header'\n | 'invalid_payment_required'\n | 'unsupported_network'\n | 'no_matching_payment_option'\n | 'no_solana_accept' // Legacy, kept for compatibility\n | 'missing_fee_payer'\n | 'missing_decimals'\n | 'amount_exceeds_max'\n | 'insufficient_balance'\n | 'wallet_missing_sign_transaction'\n | 'wallet_not_connected'\n | 'transaction_build_failed'\n | 'payment_rejected'\n // Server errors\n | 'invalid_payment_signature'\n | 'facilitator_verify_failed'\n | 'facilitator_settle_failed'\n | 'facilitator_request_failed'\n | 'no_matching_requirement';\n\n/**\n * Custom error class for x402 operations\n */\nexport class X402Error extends Error {\n /** Error code for programmatic handling */\n code: X402ErrorCode;\n /** Additional error details */\n details?: unknown;\n\n constructor(code: X402ErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'X402Error';\n this.code = code;\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, X402Error.prototype);\n }\n}\n","/**\n * Utility Functions\n *\n * Chain-agnostic helpers for x402 payments.\n */\n\nimport { SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK } from './types';\n\n// ============================================================================\n// Amount Conversion\n// ============================================================================\n\n/**\n * Convert human-readable amount to atomic units\n *\n * @param amount - Human-readable amount (e.g., 0.05 for $0.05)\n * @param decimals - Token decimals (e.g., 6 for USDC)\n * @returns Amount in atomic units as string\n *\n * @example\n * ```typescript\n * toAtomicUnits(0.05, 6) // '50000'\n * toAtomicUnits(1.50, 6) // '1500000'\n * ```\n */\nexport function toAtomicUnits(amount: number, decimals: number): string {\n const multiplier = Math.pow(10, decimals);\n return Math.floor(amount * multiplier).toString();\n}\n\n/**\n * Convert atomic units to human-readable amount\n *\n * @param atomicUnits - Amount in smallest units\n * @param decimals - Token decimals\n * @returns Human-readable amount\n *\n * @example\n * ```typescript\n * fromAtomicUnits('50000', 6) // 0.05\n * fromAtomicUnits(1500000n, 6) // 1.5\n * ```\n */\nexport function fromAtomicUnits(\n atomicUnits: string | bigint | number,\n decimals: number\n): number {\n const divisor = Math.pow(10, decimals);\n return Number(atomicUnits) / divisor;\n}\n\n// ============================================================================\n// Network Helpers\n// ============================================================================\n\n/**\n * Network type\n */\nexport type ChainFamily = 'solana' | 'evm' | 'unknown';\n\n/**\n * Get the chain family from a CAIP-2 network identifier\n *\n * @param network - CAIP-2 network identifier\n * @returns Chain family\n *\n * @example\n * ```typescript\n * getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'\n * getChainFamily('eip155:8453') // 'evm'\n * ```\n */\nexport function getChainFamily(network: string): ChainFamily {\n if (network.startsWith('solana:') || network === 'solana') {\n return 'solana';\n }\n if (network.startsWith('eip155:') || ['base', 'ethereum', 'arbitrum'].includes(network)) {\n return 'evm';\n }\n return 'unknown';\n}\n\n/**\n * Get default RPC URL for a network\n *\n * @param network - CAIP-2 network identifier\n * @returns Default RPC URL\n */\nexport function getDefaultRpcUrl(network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n if (network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1') {\n return 'https://api.devnet.solana.com';\n }\n if (network.includes('testnet') || network === 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z') {\n return 'https://api.testnet.solana.com';\n }\n return 'https://api.mainnet-beta.solana.com';\n }\n\n if (family === 'evm') {\n // Extract chain ID from CAIP-2\n if (network.startsWith('eip155:')) {\n const chainId = network.split(':')[1];\n switch (chainId) {\n case '8453': return 'https://mainnet.base.org';\n case '84532': return 'https://sepolia.base.org';\n case '1': return 'https://eth.llamarpc.com';\n case '42161': return 'https://arb1.arbitrum.io/rpc';\n default: return 'https://mainnet.base.org';\n }\n }\n // Legacy names\n if (network === 'base') return 'https://mainnet.base.org';\n if (network === 'ethereum') return 'https://eth.llamarpc.com';\n if (network === 'arbitrum') return 'https://arb1.arbitrum.io/rpc';\n return 'https://mainnet.base.org';\n }\n\n // Unknown - return a generic\n return 'https://api.mainnet-beta.solana.com';\n}\n\n/**\n * Get human-readable chain name\n *\n * @param network - CAIP-2 network identifier\n * @returns Human-readable name\n */\nexport function getChainName(network: string): string {\n const mapping: Record<string, string> = {\n [SOLANA_MAINNET_NETWORK]: 'Solana',\n 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'Solana Devnet',\n 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'Solana Testnet',\n 'solana': 'Solana',\n [BASE_MAINNET_NETWORK]: 'Base',\n 'eip155:84532': 'Base Sepolia',\n 'eip155:1': 'Ethereum',\n 'eip155:42161': 'Arbitrum One',\n 'base': 'Base',\n 'ethereum': 'Ethereum',\n 'arbitrum': 'Arbitrum',\n };\n return mapping[network] || network;\n}\n\n// ============================================================================\n// Transaction URL Helpers\n// ============================================================================\n\n/**\n * Get explorer URL for a transaction\n *\n * @param txSignature - Transaction signature/hash\n * @param network - CAIP-2 network identifier\n * @returns Explorer URL\n */\nexport function getExplorerUrl(txSignature: string, network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n const isDevnet = network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';\n if (isDevnet) {\n return `https://solscan.io/tx/${txSignature}?cluster=devnet`;\n }\n // Prefer Orb Markets for mainnet\n return `https://www.orbmarkets.io/tx/${txSignature}`;\n }\n\n if (family === 'evm') {\n // Extract chain ID\n let chainId = '8453'; // Default to Base\n if (network.startsWith('eip155:')) {\n chainId = network.split(':')[1];\n } else if (network === 'ethereum') {\n chainId = '1';\n } else if (network === 'arbitrum') {\n chainId = '42161';\n }\n\n switch (chainId) {\n case '8453': return `https://basescan.org/tx/${txSignature}`;\n case '84532': return `https://sepolia.basescan.org/tx/${txSignature}`;\n case '1': return `https://etherscan.io/tx/${txSignature}`;\n case '42161': return `https://arbiscan.io/tx/${txSignature}`;\n default: return `https://basescan.org/tx/${txSignature}`;\n }\n }\n\n return `https://solscan.io/tx/${txSignature}`;\n}\n\n// ============================================================================\n// Encoding Helpers\n// ============================================================================\n\n/**\n * Encode an object as base64 JSON\n */\nexport function encodeBase64Json(obj: unknown): string {\n return btoa(JSON.stringify(obj));\n}\n\n/**\n * Decode base64 JSON to object\n */\nexport function decodeBase64Json<T>(encoded: string): T {\n return JSON.parse(atob(encoded)) as T;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,IAAM,yBAAyB;AAG/B,IAAM,uBAAuB;;;ACU7B,SAAS,cAAc,QAAgB,UAA0B;AACtE,QAAM,aAAa,KAAK,IAAI,IAAI,QAAQ;AACxC,SAAO,KAAK,MAAM,SAAS,UAAU,EAAE,SAAS;AAClD;AAeO,SAAS,gBACd,aACA,UACQ;AACR,QAAM,UAAU,KAAK,IAAI,IAAI,QAAQ;AACrC,SAAO,OAAO,WAAW,IAAI;AAC/B;AAuBO,SAAS,eAAe,SAA8B;AAC3D,MAAI,QAAQ,WAAW,SAAS,KAAK,YAAY,UAAU;AACzD,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,WAAW,SAAS,KAAK,CAAC,QAAQ,YAAY,UAAU,EAAE,SAAS,OAAO,GAAG;AACvF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAkDO,SAAS,aAAa,SAAyB;AACpD,QAAM,UAAkC;AAAA,IACtC,CAAC,sBAAsB,GAAG;AAAA,IAC1B,2CAA2C;AAAA,IAC3C,2CAA2C;AAAA,IAC3C,UAAU;AAAA,IACV,CAAC,oBAAoB,GAAG;AAAA,IACxB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AACA,SAAO,QAAQ,OAAO,KAAK;AAC7B;AAaO,SAAS,eAAe,aAAqB,SAAyB;AAC3E,QAAM,SAAS,eAAe,OAAO;AAErC,MAAI,WAAW,UAAU;AACvB,UAAM,WAAW,QAAQ,SAAS,QAAQ,KAAK,YAAY;AAC3D,QAAI,UAAU;AACZ,aAAO,yBAAyB,WAAW;AAAA,IAC7C;AAEA,WAAO,gCAAgC,WAAW;AAAA,EACpD;AAEA,MAAI,WAAW,OAAO;AAEpB,QAAI,UAAU;AACd,QAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,gBAAU,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,IAChC,WAAW,YAAY,YAAY;AACjC,gBAAU;AAAA,IACZ,WAAW,YAAY,YAAY;AACjC,gBAAU;AAAA,IACZ;AAEA,YAAQ,SAAS;AAAA,MACf,KAAK;AAAQ,eAAO,2BAA2B,WAAW;AAAA,MAC1D,KAAK;AAAS,eAAO,mCAAmC,WAAW;AAAA,MACnE,KAAK;AAAK,eAAO,2BAA2B,WAAW;AAAA,MACvD,KAAK;AAAS,eAAO,0BAA0B,WAAW;AAAA,MAC1D;AAAS,eAAO,2BAA2B,WAAW;AAAA,IACxD;AAAA,EACF;AAEA,SAAO,yBAAyB,WAAW;AAC7C;","names":[]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* Chain-agnostic helpers for x402 payments.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Convert human-readable amount to atomic units
|
|
8
|
+
*
|
|
9
|
+
* @param amount - Human-readable amount (e.g., 0.05 for $0.05)
|
|
10
|
+
* @param decimals - Token decimals (e.g., 6 for USDC)
|
|
11
|
+
* @returns Amount in atomic units as string
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* toAtomicUnits(0.05, 6) // '50000'
|
|
16
|
+
* toAtomicUnits(1.50, 6) // '1500000'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare function toAtomicUnits(amount: number, decimals: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* Convert atomic units to human-readable amount
|
|
22
|
+
*
|
|
23
|
+
* @param atomicUnits - Amount in smallest units
|
|
24
|
+
* @param decimals - Token decimals
|
|
25
|
+
* @returns Human-readable amount
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* fromAtomicUnits('50000', 6) // 0.05
|
|
30
|
+
* fromAtomicUnits(1500000n, 6) // 1.5
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare function fromAtomicUnits(atomicUnits: string | bigint | number, decimals: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* Network type
|
|
36
|
+
*/
|
|
37
|
+
type ChainFamily = 'solana' | 'evm' | 'unknown';
|
|
38
|
+
/**
|
|
39
|
+
* Get the chain family from a CAIP-2 network identifier
|
|
40
|
+
*
|
|
41
|
+
* @param network - CAIP-2 network identifier
|
|
42
|
+
* @returns Chain family
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'
|
|
47
|
+
* getChainFamily('eip155:8453') // 'evm'
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function getChainFamily(network: string): ChainFamily;
|
|
51
|
+
/**
|
|
52
|
+
* Get human-readable chain name
|
|
53
|
+
*
|
|
54
|
+
* @param network - CAIP-2 network identifier
|
|
55
|
+
* @returns Human-readable name
|
|
56
|
+
*/
|
|
57
|
+
declare function getChainName(network: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Get explorer URL for a transaction
|
|
60
|
+
*
|
|
61
|
+
* @param txSignature - Transaction signature/hash
|
|
62
|
+
* @param network - CAIP-2 network identifier
|
|
63
|
+
* @returns Explorer URL
|
|
64
|
+
*/
|
|
65
|
+
declare function getExplorerUrl(txSignature: string, network: string): string;
|
|
66
|
+
|
|
67
|
+
export { type ChainFamily, fromAtomicUnits, getChainFamily, getChainName, getExplorerUrl, toAtomicUnits };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* Chain-agnostic helpers for x402 payments.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Convert human-readable amount to atomic units
|
|
8
|
+
*
|
|
9
|
+
* @param amount - Human-readable amount (e.g., 0.05 for $0.05)
|
|
10
|
+
* @param decimals - Token decimals (e.g., 6 for USDC)
|
|
11
|
+
* @returns Amount in atomic units as string
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* toAtomicUnits(0.05, 6) // '50000'
|
|
16
|
+
* toAtomicUnits(1.50, 6) // '1500000'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare function toAtomicUnits(amount: number, decimals: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* Convert atomic units to human-readable amount
|
|
22
|
+
*
|
|
23
|
+
* @param atomicUnits - Amount in smallest units
|
|
24
|
+
* @param decimals - Token decimals
|
|
25
|
+
* @returns Human-readable amount
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* fromAtomicUnits('50000', 6) // 0.05
|
|
30
|
+
* fromAtomicUnits(1500000n, 6) // 1.5
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare function fromAtomicUnits(atomicUnits: string | bigint | number, decimals: number): number;
|
|
34
|
+
/**
|
|
35
|
+
* Network type
|
|
36
|
+
*/
|
|
37
|
+
type ChainFamily = 'solana' | 'evm' | 'unknown';
|
|
38
|
+
/**
|
|
39
|
+
* Get the chain family from a CAIP-2 network identifier
|
|
40
|
+
*
|
|
41
|
+
* @param network - CAIP-2 network identifier
|
|
42
|
+
* @returns Chain family
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'
|
|
47
|
+
* getChainFamily('eip155:8453') // 'evm'
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function getChainFamily(network: string): ChainFamily;
|
|
51
|
+
/**
|
|
52
|
+
* Get human-readable chain name
|
|
53
|
+
*
|
|
54
|
+
* @param network - CAIP-2 network identifier
|
|
55
|
+
* @returns Human-readable name
|
|
56
|
+
*/
|
|
57
|
+
declare function getChainName(network: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Get explorer URL for a transaction
|
|
60
|
+
*
|
|
61
|
+
* @param txSignature - Transaction signature/hash
|
|
62
|
+
* @param network - CAIP-2 network identifier
|
|
63
|
+
* @returns Explorer URL
|
|
64
|
+
*/
|
|
65
|
+
declare function getExplorerUrl(txSignature: string, network: string): string;
|
|
66
|
+
|
|
67
|
+
export { type ChainFamily, fromAtomicUnits, getChainFamily, getChainName, getExplorerUrl, toAtomicUnits };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
3
|
+
var BASE_MAINNET_NETWORK = "eip155:8453";
|
|
4
|
+
|
|
5
|
+
// src/utils.ts
|
|
6
|
+
function toAtomicUnits(amount, decimals) {
|
|
7
|
+
const multiplier = Math.pow(10, decimals);
|
|
8
|
+
return Math.floor(amount * multiplier).toString();
|
|
9
|
+
}
|
|
10
|
+
function fromAtomicUnits(atomicUnits, decimals) {
|
|
11
|
+
const divisor = Math.pow(10, decimals);
|
|
12
|
+
return Number(atomicUnits) / divisor;
|
|
13
|
+
}
|
|
14
|
+
function getChainFamily(network) {
|
|
15
|
+
if (network.startsWith("solana:") || network === "solana") {
|
|
16
|
+
return "solana";
|
|
17
|
+
}
|
|
18
|
+
if (network.startsWith("eip155:") || ["base", "ethereum", "arbitrum"].includes(network)) {
|
|
19
|
+
return "evm";
|
|
20
|
+
}
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
function getChainName(network) {
|
|
24
|
+
const mapping = {
|
|
25
|
+
[SOLANA_MAINNET_NETWORK]: "Solana",
|
|
26
|
+
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "Solana Devnet",
|
|
27
|
+
"solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "Solana Testnet",
|
|
28
|
+
"solana": "Solana",
|
|
29
|
+
[BASE_MAINNET_NETWORK]: "Base",
|
|
30
|
+
"eip155:84532": "Base Sepolia",
|
|
31
|
+
"eip155:1": "Ethereum",
|
|
32
|
+
"eip155:42161": "Arbitrum One",
|
|
33
|
+
"base": "Base",
|
|
34
|
+
"ethereum": "Ethereum",
|
|
35
|
+
"arbitrum": "Arbitrum"
|
|
36
|
+
};
|
|
37
|
+
return mapping[network] || network;
|
|
38
|
+
}
|
|
39
|
+
function getExplorerUrl(txSignature, network) {
|
|
40
|
+
const family = getChainFamily(network);
|
|
41
|
+
if (family === "solana") {
|
|
42
|
+
const isDevnet = network.includes("devnet") || network === "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
43
|
+
if (isDevnet) {
|
|
44
|
+
return `https://solscan.io/tx/${txSignature}?cluster=devnet`;
|
|
45
|
+
}
|
|
46
|
+
return `https://www.orbmarkets.io/tx/${txSignature}`;
|
|
47
|
+
}
|
|
48
|
+
if (family === "evm") {
|
|
49
|
+
let chainId = "8453";
|
|
50
|
+
if (network.startsWith("eip155:")) {
|
|
51
|
+
chainId = network.split(":")[1];
|
|
52
|
+
} else if (network === "ethereum") {
|
|
53
|
+
chainId = "1";
|
|
54
|
+
} else if (network === "arbitrum") {
|
|
55
|
+
chainId = "42161";
|
|
56
|
+
}
|
|
57
|
+
switch (chainId) {
|
|
58
|
+
case "8453":
|
|
59
|
+
return `https://basescan.org/tx/${txSignature}`;
|
|
60
|
+
case "84532":
|
|
61
|
+
return `https://sepolia.basescan.org/tx/${txSignature}`;
|
|
62
|
+
case "1":
|
|
63
|
+
return `https://etherscan.io/tx/${txSignature}`;
|
|
64
|
+
case "42161":
|
|
65
|
+
return `https://arbiscan.io/tx/${txSignature}`;
|
|
66
|
+
default:
|
|
67
|
+
return `https://basescan.org/tx/${txSignature}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return `https://solscan.io/tx/${txSignature}`;
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
fromAtomicUnits,
|
|
74
|
+
getChainFamily,
|
|
75
|
+
getChainName,
|
|
76
|
+
getExplorerUrl,
|
|
77
|
+
toAtomicUnits
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts","../../src/utils.ts"],"sourcesContent":["/**\n * x402 v2 SDK — Shared Types\n *\n * Chain-agnostic types for x402 v2 payments.\n * Works with Solana, Base, and any future x402-compatible networks.\n */\n\n// ============================================================================\n// Network Constants\n// ============================================================================\n\n/** CAIP-2 network identifier for Solana mainnet */\nexport const SOLANA_MAINNET_NETWORK = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';\n\n/** CAIP-2 network identifier for Base mainnet */\nexport const BASE_MAINNET_NETWORK = 'eip155:8453';\n\n/** Alias for Solana mainnet */\nexport const SOLANA_MAINNET = SOLANA_MAINNET_NETWORK;\n\n/** Alias for Base mainnet */\nexport const BASE_MAINNET = BASE_MAINNET_NETWORK;\n\n// ============================================================================\n// Asset Constants\n// ============================================================================\n\n/** USDC mint on Solana mainnet */\nexport const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';\n\n/** USDC address on Base mainnet */\nexport const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';\n\n// ============================================================================\n// Facilitator Constants\n// ============================================================================\n\n/** Dexter's public x402 v2 facilitator URL */\nexport const DEXTER_FACILITATOR_URL = 'https://x402.dexter.cash';\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/**\n * Asset configuration for payments\n */\nexport interface AssetConfig {\n /** Token address (mint on Solana, contract on EVM) */\n address: string;\n /** Token decimals */\n decimals: number;\n /** Optional: Human-readable symbol */\n symbol?: string;\n}\n\n/**\n * Resource info included in payment requirements\n */\nexport interface ResourceInfo {\n /** Resource URL */\n url: string;\n /** Human-readable description */\n description?: string;\n /** MIME type of the resource */\n mimeType?: string;\n}\n\n/**\n * Extra fields in payment requirements\n * Chain-specific fields may vary\n */\nexport interface AcceptsExtra {\n /** Facilitator address that pays tx fees (required) */\n feePayer: string;\n /** Token decimals (required) */\n decimals: number;\n /** EIP-712: Token name (EVM only) */\n name?: string;\n /** EIP-712: Token version (EVM only) */\n version?: string;\n /** Additional chain-specific fields */\n [key: string]: unknown;\n}\n\n/**\n * A single payment option in the accepts array\n */\nexport interface PaymentAccept {\n /** Payment scheme (always 'exact' for x402 v2) */\n scheme: 'exact';\n /** CAIP-2 network identifier */\n network: string;\n /** Payment amount in atomic units (as string to avoid precision loss) */\n amount: string;\n /** Token address */\n asset: string;\n /** Seller's address to receive payment */\n payTo: string;\n /** Maximum seconds until payment expires */\n maxTimeoutSeconds: number;\n /** Chain-specific extra data */\n extra: AcceptsExtra;\n}\n\n/**\n * Full PaymentRequired structure (sent in PAYMENT-REQUIRED header)\n */\nexport interface PaymentRequired {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** Available payment options */\n accepts: PaymentAccept[];\n /** Optional error message */\n error?: string;\n}\n\n/**\n * PaymentSignature structure (sent in PAYMENT-SIGNATURE header)\n */\nexport interface PaymentSignature {\n /** x402 version (always 2) */\n x402Version: 2;\n /** Resource being accessed */\n resource: ResourceInfo;\n /** The payment option that was accepted */\n accepted: PaymentAccept;\n /** The signed payment */\n payload: {\n /** Signed transaction (base64 for Solana, JSON for EVM) */\n transaction: string;\n };\n}\n\n// ============================================================================\n// Facilitator Response Types\n// ============================================================================\n\n/**\n * Response from /verify endpoint\n */\nexport interface VerifyResponse {\n /** Whether the payment is valid */\n isValid: boolean;\n /** Reason for invalidity (if invalid) */\n invalidReason?: string;\n /** Payer address */\n payer?: string;\n}\n\n/**\n * Response from /settle endpoint\n */\nexport interface SettleResponse {\n /** Whether settlement succeeded */\n success: boolean;\n /** Transaction signature/hash */\n transaction?: string;\n /** Network the payment was made on */\n network: string;\n /** Error reason (if failed) */\n errorReason?: string;\n /** Error code (if failed) */\n errorCode?: string;\n /** Payer address */\n payer?: string;\n}\n\n// ============================================================================\n// Error Types\n// ============================================================================\n\n/**\n * SDK error codes\n */\nexport type X402ErrorCode =\n // Client errors\n | 'missing_payment_required_header'\n | 'invalid_payment_required'\n | 'unsupported_network'\n | 'no_matching_payment_option'\n | 'no_solana_accept' // Legacy, kept for compatibility\n | 'missing_fee_payer'\n | 'missing_decimals'\n | 'amount_exceeds_max'\n | 'insufficient_balance'\n | 'wallet_missing_sign_transaction'\n | 'wallet_not_connected'\n | 'transaction_build_failed'\n | 'payment_rejected'\n // Server errors\n | 'invalid_payment_signature'\n | 'facilitator_verify_failed'\n | 'facilitator_settle_failed'\n | 'facilitator_request_failed'\n | 'no_matching_requirement';\n\n/**\n * Custom error class for x402 operations\n */\nexport class X402Error extends Error {\n /** Error code for programmatic handling */\n code: X402ErrorCode;\n /** Additional error details */\n details?: unknown;\n\n constructor(code: X402ErrorCode, message: string, details?: unknown) {\n super(message);\n this.name = 'X402Error';\n this.code = code;\n this.details = details;\n // Maintain proper prototype chain\n Object.setPrototypeOf(this, X402Error.prototype);\n }\n}\n","/**\n * Utility Functions\n *\n * Chain-agnostic helpers for x402 payments.\n */\n\nimport { SOLANA_MAINNET_NETWORK, BASE_MAINNET_NETWORK } from './types';\n\n// ============================================================================\n// Amount Conversion\n// ============================================================================\n\n/**\n * Convert human-readable amount to atomic units\n *\n * @param amount - Human-readable amount (e.g., 0.05 for $0.05)\n * @param decimals - Token decimals (e.g., 6 for USDC)\n * @returns Amount in atomic units as string\n *\n * @example\n * ```typescript\n * toAtomicUnits(0.05, 6) // '50000'\n * toAtomicUnits(1.50, 6) // '1500000'\n * ```\n */\nexport function toAtomicUnits(amount: number, decimals: number): string {\n const multiplier = Math.pow(10, decimals);\n return Math.floor(amount * multiplier).toString();\n}\n\n/**\n * Convert atomic units to human-readable amount\n *\n * @param atomicUnits - Amount in smallest units\n * @param decimals - Token decimals\n * @returns Human-readable amount\n *\n * @example\n * ```typescript\n * fromAtomicUnits('50000', 6) // 0.05\n * fromAtomicUnits(1500000n, 6) // 1.5\n * ```\n */\nexport function fromAtomicUnits(\n atomicUnits: string | bigint | number,\n decimals: number\n): number {\n const divisor = Math.pow(10, decimals);\n return Number(atomicUnits) / divisor;\n}\n\n// ============================================================================\n// Network Helpers\n// ============================================================================\n\n/**\n * Network type\n */\nexport type ChainFamily = 'solana' | 'evm' | 'unknown';\n\n/**\n * Get the chain family from a CAIP-2 network identifier\n *\n * @param network - CAIP-2 network identifier\n * @returns Chain family\n *\n * @example\n * ```typescript\n * getChainFamily('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp') // 'solana'\n * getChainFamily('eip155:8453') // 'evm'\n * ```\n */\nexport function getChainFamily(network: string): ChainFamily {\n if (network.startsWith('solana:') || network === 'solana') {\n return 'solana';\n }\n if (network.startsWith('eip155:') || ['base', 'ethereum', 'arbitrum'].includes(network)) {\n return 'evm';\n }\n return 'unknown';\n}\n\n/**\n * Get default RPC URL for a network\n *\n * @param network - CAIP-2 network identifier\n * @returns Default RPC URL\n */\nexport function getDefaultRpcUrl(network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n if (network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1') {\n return 'https://api.devnet.solana.com';\n }\n if (network.includes('testnet') || network === 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z') {\n return 'https://api.testnet.solana.com';\n }\n return 'https://api.mainnet-beta.solana.com';\n }\n\n if (family === 'evm') {\n // Extract chain ID from CAIP-2\n if (network.startsWith('eip155:')) {\n const chainId = network.split(':')[1];\n switch (chainId) {\n case '8453': return 'https://mainnet.base.org';\n case '84532': return 'https://sepolia.base.org';\n case '1': return 'https://eth.llamarpc.com';\n case '42161': return 'https://arb1.arbitrum.io/rpc';\n default: return 'https://mainnet.base.org';\n }\n }\n // Legacy names\n if (network === 'base') return 'https://mainnet.base.org';\n if (network === 'ethereum') return 'https://eth.llamarpc.com';\n if (network === 'arbitrum') return 'https://arb1.arbitrum.io/rpc';\n return 'https://mainnet.base.org';\n }\n\n // Unknown - return a generic\n return 'https://api.mainnet-beta.solana.com';\n}\n\n/**\n * Get human-readable chain name\n *\n * @param network - CAIP-2 network identifier\n * @returns Human-readable name\n */\nexport function getChainName(network: string): string {\n const mapping: Record<string, string> = {\n [SOLANA_MAINNET_NETWORK]: 'Solana',\n 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1': 'Solana Devnet',\n 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z': 'Solana Testnet',\n 'solana': 'Solana',\n [BASE_MAINNET_NETWORK]: 'Base',\n 'eip155:84532': 'Base Sepolia',\n 'eip155:1': 'Ethereum',\n 'eip155:42161': 'Arbitrum One',\n 'base': 'Base',\n 'ethereum': 'Ethereum',\n 'arbitrum': 'Arbitrum',\n };\n return mapping[network] || network;\n}\n\n// ============================================================================\n// Transaction URL Helpers\n// ============================================================================\n\n/**\n * Get explorer URL for a transaction\n *\n * @param txSignature - Transaction signature/hash\n * @param network - CAIP-2 network identifier\n * @returns Explorer URL\n */\nexport function getExplorerUrl(txSignature: string, network: string): string {\n const family = getChainFamily(network);\n\n if (family === 'solana') {\n const isDevnet = network.includes('devnet') || network === 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';\n if (isDevnet) {\n return `https://solscan.io/tx/${txSignature}?cluster=devnet`;\n }\n // Prefer Orb Markets for mainnet\n return `https://www.orbmarkets.io/tx/${txSignature}`;\n }\n\n if (family === 'evm') {\n // Extract chain ID\n let chainId = '8453'; // Default to Base\n if (network.startsWith('eip155:')) {\n chainId = network.split(':')[1];\n } else if (network === 'ethereum') {\n chainId = '1';\n } else if (network === 'arbitrum') {\n chainId = '42161';\n }\n\n switch (chainId) {\n case '8453': return `https://basescan.org/tx/${txSignature}`;\n case '84532': return `https://sepolia.basescan.org/tx/${txSignature}`;\n case '1': return `https://etherscan.io/tx/${txSignature}`;\n case '42161': return `https://arbiscan.io/tx/${txSignature}`;\n default: return `https://basescan.org/tx/${txSignature}`;\n }\n }\n\n return `https://solscan.io/tx/${txSignature}`;\n}\n\n// ============================================================================\n// Encoding Helpers\n// ============================================================================\n\n/**\n * Encode an object as base64 JSON\n */\nexport function encodeBase64Json(obj: unknown): string {\n return btoa(JSON.stringify(obj));\n}\n\n/**\n * Decode base64 JSON to object\n */\nexport function decodeBase64Json<T>(encoded: string): T {\n return JSON.parse(atob(encoded)) as T;\n}\n"],"mappings":";AAYO,IAAM,yBAAyB;AAG/B,IAAM,uBAAuB;;;ACU7B,SAAS,cAAc,QAAgB,UAA0B;AACtE,QAAM,aAAa,KAAK,IAAI,IAAI,QAAQ;AACxC,SAAO,KAAK,MAAM,SAAS,UAAU,EAAE,SAAS;AAClD;AAeO,SAAS,gBACd,aACA,UACQ;AACR,QAAM,UAAU,KAAK,IAAI,IAAI,QAAQ;AACrC,SAAO,OAAO,WAAW,IAAI;AAC/B;AAuBO,SAAS,eAAe,SAA8B;AAC3D,MAAI,QAAQ,WAAW,SAAS,KAAK,YAAY,UAAU;AACzD,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,WAAW,SAAS,KAAK,CAAC,QAAQ,YAAY,UAAU,EAAE,SAAS,OAAO,GAAG;AACvF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAkDO,SAAS,aAAa,SAAyB;AACpD,QAAM,UAAkC;AAAA,IACtC,CAAC,sBAAsB,GAAG;AAAA,IAC1B,2CAA2C;AAAA,IAC3C,2CAA2C;AAAA,IAC3C,UAAU;AAAA,IACV,CAAC,oBAAoB,GAAG;AAAA,IACxB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AACA,SAAO,QAAQ,OAAO,KAAK;AAC7B;AAaO,SAAS,eAAe,aAAqB,SAAyB;AAC3E,QAAM,SAAS,eAAe,OAAO;AAErC,MAAI,WAAW,UAAU;AACvB,UAAM,WAAW,QAAQ,SAAS,QAAQ,KAAK,YAAY;AAC3D,QAAI,UAAU;AACZ,aAAO,yBAAyB,WAAW;AAAA,IAC7C;AAEA,WAAO,gCAAgC,WAAW;AAAA,EACpD;AAEA,MAAI,WAAW,OAAO;AAEpB,QAAI,UAAU;AACd,QAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,gBAAU,QAAQ,MAAM,GAAG,EAAE,CAAC;AAAA,IAChC,WAAW,YAAY,YAAY;AACjC,gBAAU;AAAA,IACZ,WAAW,YAAY,YAAY;AACjC,gBAAU;AAAA,IACZ;AAEA,YAAQ,SAAS;AAAA,MACf,KAAK;AAAQ,eAAO,2BAA2B,WAAW;AAAA,MAC1D,KAAK;AAAS,eAAO,mCAAmC,WAAW;AAAA,MACnE,KAAK;AAAK,eAAO,2BAA2B,WAAW;AAAA,MACvD,KAAK;AAAS,eAAO,0BAA0B,WAAW;AAAA,MAC1D;AAAS,eAAO,2BAA2B,WAAW;AAAA,IACxD;AAAA,EACF;AAEA,SAAO,yBAAyB,WAAW;AAC7C;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/x402",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Chain-agnostic x402 v2 SDK for Solana, Base, and EVM payments",
|
|
5
5
|
"author": "Dexter",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"types": "./dist/adapters/index.d.ts",
|
|
26
26
|
"import": "./dist/adapters/index.js",
|
|
27
27
|
"require": "./dist/adapters/index.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./utils": {
|
|
30
|
+
"types": "./dist/utils/index.d.ts",
|
|
31
|
+
"import": "./dist/utils/index.js",
|
|
32
|
+
"require": "./dist/utils/index.cjs"
|
|
28
33
|
}
|
|
29
34
|
},
|
|
30
35
|
"files": [
|
|
@@ -37,7 +42,7 @@
|
|
|
37
42
|
"build": "tsup",
|
|
38
43
|
"dev": "tsup --watch",
|
|
39
44
|
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "
|
|
45
|
+
"test": "echo 'Real payment tests require funded wallets. Run: npx tsx test/real-payment-solana.ts'",
|
|
41
46
|
"test:watch": "vitest",
|
|
42
47
|
"prepublishOnly": "npm run build",
|
|
43
48
|
"release": "npm version patch && npm publish --access public",
|
|
@@ -90,7 +95,7 @@
|
|
|
90
95
|
],
|
|
91
96
|
"repository": {
|
|
92
97
|
"type": "git",
|
|
93
|
-
"url": "https://github.com/Dexter-DAO/x402-sdk"
|
|
98
|
+
"url": "https://github.com/Dexter-DAO/dexter-x402-sdk"
|
|
94
99
|
},
|
|
95
100
|
"homepage": "https://dexter.cash/sdk",
|
|
96
101
|
"bugs": {
|