bip-321 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +31 -0
- package/AGENTS.md +261 -0
- package/README.md +4 -19
- package/bun.lock +25 -2
- package/index.test.ts +0 -6
- package/index.ts +39 -33
- package/oxlintrc.json +23 -0
- package/package.json +10 -7
- package/CLAUDE.md +0 -107
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["**"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Bun
|
|
17
|
+
uses: oven-sh/setup-bun@v1
|
|
18
|
+
with:
|
|
19
|
+
bun-version: latest
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: bun install
|
|
23
|
+
|
|
24
|
+
- name: Run lint
|
|
25
|
+
run: bun run lint
|
|
26
|
+
|
|
27
|
+
- name: Run type check
|
|
28
|
+
run: bun run check
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: bun test
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# BIP-321 Parser - Agent Development Guide
|
|
2
|
+
|
|
3
|
+
This document provides guidance for AI agents and developers working on the BIP-321 Bitcoin URI parser library.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
A TypeScript/JavaScript library for parsing BIP-321 Bitcoin URIs with support for multiple payment methods (on-chain, Lightning, BOLT12, Silent Payments). Works natively in Node.js, browsers, and React Native without build tools.
|
|
8
|
+
|
|
9
|
+
## Tech Stack
|
|
10
|
+
|
|
11
|
+
### Runtime
|
|
12
|
+
- **Bun** - Primary development runtime
|
|
13
|
+
- Use `bun test` instead of `jest` or `vitest`
|
|
14
|
+
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
15
|
+
- Use `bun install` for package management
|
|
16
|
+
|
|
17
|
+
### Key Dependencies
|
|
18
|
+
|
|
19
|
+
**Why These Specific Dependencies:**
|
|
20
|
+
|
|
21
|
+
1. **`@scure/base`** (^2.0.0)
|
|
22
|
+
- Pure JavaScript base58, bech32, and bech32m encoding
|
|
23
|
+
- Works natively in all environments (Node.js, browser, React Native)
|
|
24
|
+
- No browserify or build tools needed
|
|
25
|
+
- Used for Base58 address decoding and bech32/bech32m validation
|
|
26
|
+
|
|
27
|
+
2. **`@noble/hashes`** (^2.0.1)
|
|
28
|
+
- Pure JavaScript cryptographic hashing (SHA-256)
|
|
29
|
+
- Properly exports all modules (no import warnings)
|
|
30
|
+
- Used for Base58Check checksum validation
|
|
31
|
+
- Import path: `@noble/hashes/sha2.js` (with .js extension)
|
|
32
|
+
|
|
33
|
+
3. **`bitcoinjs-lib`** (^7.0.0)
|
|
34
|
+
- Standard Bitcoin library for address validation
|
|
35
|
+
- Note: Taproot addresses require manual bech32m validation (ECC lib not initialized)
|
|
36
|
+
- Fallback to `@scure/base` for taproot validation
|
|
37
|
+
|
|
38
|
+
4. **`light-bolt11-decoder`** (^3.2.0)
|
|
39
|
+
- Lightweight Lightning BOLT11 invoice parser
|
|
40
|
+
- No heavy dependencies
|
|
41
|
+
|
|
42
|
+
### What We AVOID
|
|
43
|
+
|
|
44
|
+
- ❌ `bs58` - Requires browserify for browsers
|
|
45
|
+
- ❌ `bs58check` - Has module export warnings with `@noble/hashes`
|
|
46
|
+
- ❌ `express` or heavy server frameworks
|
|
47
|
+
- ❌ Build tools (webpack, browserify, rollup)
|
|
48
|
+
|
|
49
|
+
## Development Commands
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Run tests
|
|
53
|
+
bun test
|
|
54
|
+
|
|
55
|
+
# Run TypeScript type check
|
|
56
|
+
bun run check
|
|
57
|
+
# or
|
|
58
|
+
bunx tsc --noEmit
|
|
59
|
+
|
|
60
|
+
# Run examples
|
|
61
|
+
bun example.ts
|
|
62
|
+
|
|
63
|
+
# Run linter
|
|
64
|
+
bun run lint
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Project Structure
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
bip-321/
|
|
71
|
+
├── index.ts # Main parser implementation
|
|
72
|
+
├── index.test.ts # Comprehensive test suite (39 tests)
|
|
73
|
+
├── example.ts # Usage examples
|
|
74
|
+
├── README.md # User documentation
|
|
75
|
+
├── CLAUDE.md # Bun-specific development rules
|
|
76
|
+
├── AGENTS.md # This file
|
|
77
|
+
├── package.json # Dependencies and scripts
|
|
78
|
+
└── tsconfig.json # TypeScript configuration
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Key Design Decisions
|
|
82
|
+
|
|
83
|
+
### 1. Network Detection Strategy
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Order matters for Lightning invoices!
|
|
87
|
+
// Check "lnbcrt" before "lnbc" to avoid false positives
|
|
88
|
+
if (lowerInvoice.startsWith("lnbcrt")) return "regtest";
|
|
89
|
+
else if (lowerInvoice.startsWith("lnbc")) return "mainnet";
|
|
90
|
+
else if (lowerInvoice.startsWith("lntbs")) return "signet";
|
|
91
|
+
else if (lowerInvoice.startsWith("lntb")) return "testnet";
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. Taproot Address Validation
|
|
95
|
+
|
|
96
|
+
bitcoinjs-lib requires ECC library initialization for taproot validation, so we use manual bech32m validation:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Fallback to manual bech32/bech32m validation for taproot
|
|
100
|
+
const decoded = lowerAddress.startsWith("bc1p")
|
|
101
|
+
? bech32m.decode(address as `${string}1${string}`, 90)
|
|
102
|
+
: bech32.decode(address as `${string}1${string}`, 90);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Base58Check Validation
|
|
106
|
+
|
|
107
|
+
Manual implementation using `@scure/base` + `@noble/hashes` to avoid dependencies with browser compatibility issues:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const decoded = base58.decode(address);
|
|
111
|
+
const payload = decoded.slice(0, -4);
|
|
112
|
+
const checksum = decoded.slice(-4);
|
|
113
|
+
const hash = sha256(sha256(payload)); // Double SHA-256
|
|
114
|
+
// Verify checksum matches
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 4. Query Parameter Parsing
|
|
118
|
+
|
|
119
|
+
Manual parsing instead of `URLSearchParams` to preserve encoded values for `pop` parameter:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Parse manually to preserve encoded values for pop parameter
|
|
123
|
+
const paramPairs = queryString.split("&");
|
|
124
|
+
// Keep pop value encoded as per BIP-321 spec
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Supported Payment Methods
|
|
128
|
+
|
|
129
|
+
| Type | Parameter | Validation | Notes |
|
|
130
|
+
|------|-----------|------------|-------|
|
|
131
|
+
| On-chain | `address`, `bc`, `tb`, `bcrt` | Full validation with network check | P2PKH, P2SH, Segwit, Taproot |
|
|
132
|
+
| Lightning | `lightning` | BOLT11 decode + network detection | Mainnet, testnet, regtest, signet |
|
|
133
|
+
| BOLT12 | `lno` | Accept any value | Offers (minimal validation) |
|
|
134
|
+
| Silent Payments | `sp` | Prefix check (`sp1`) | BIP-352 addresses |
|
|
135
|
+
|
|
136
|
+
**Removed:** BIP-351 Private Payments (`pay` parameter) - Unused spec, nobody knows what it is
|
|
137
|
+
|
|
138
|
+
## Testing Guidelines
|
|
139
|
+
|
|
140
|
+
### Test Structure
|
|
141
|
+
- 39 tests covering all functionality
|
|
142
|
+
- Use `test()` and `expect()` from `bun:test`
|
|
143
|
+
- Group related tests with `describe()`
|
|
144
|
+
- Use non-null assertions (`!`) where values are guaranteed to exist
|
|
145
|
+
|
|
146
|
+
### Example Test Pattern
|
|
147
|
+
```typescript
|
|
148
|
+
test("parses mainnet address", () => {
|
|
149
|
+
const result = parseBIP321("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
|
|
150
|
+
expect(result.valid).toBe(true);
|
|
151
|
+
expect(result.network).toBe("mainnet");
|
|
152
|
+
expect(result.paymentMethods[0]!.type).toBe("onchain");
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## TypeScript Type Safety
|
|
157
|
+
|
|
158
|
+
All code must pass strict TypeScript checking with zero errors:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bunx tsc --noEmit # Must pass with 0 errors
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Common Type Patterns
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Use non-null assertions for array access where guaranteed
|
|
168
|
+
result.paymentMethods[0]!.type
|
|
169
|
+
|
|
170
|
+
// Use optional chaining for potentially undefined values
|
|
171
|
+
result.paymentMethods[0]?.network
|
|
172
|
+
|
|
173
|
+
// Use nullish coalescing for fallbacks
|
|
174
|
+
byNetwork.mainnet?.length || 0
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## BIP-321 Compliance Rules
|
|
178
|
+
|
|
179
|
+
1. ✅ URI must start with `bitcoin:` (case-insensitive)
|
|
180
|
+
2. ✅ `label`, `message`, `amount`, `pop` cannot appear multiple times
|
|
181
|
+
3. ✅ `pop` and `req-pop` cannot both be present
|
|
182
|
+
4. ✅ Required parameters (`req-*`) must be understood or URI is invalid
|
|
183
|
+
5. ✅ Network-specific parameters must match address network
|
|
184
|
+
6. ✅ `pop` scheme must not be forbidden (http, https, file, javascript, mailto)
|
|
185
|
+
7. ✅ `amount` must be decimal BTC (no commas)
|
|
186
|
+
8. ✅ At least one valid payment method required
|
|
187
|
+
|
|
188
|
+
## Common Pitfalls
|
|
189
|
+
|
|
190
|
+
### ❌ Don't Do This
|
|
191
|
+
```typescript
|
|
192
|
+
// Wrong: Using .js imports breaks TypeScript
|
|
193
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
194
|
+
|
|
195
|
+
// Wrong: bs58 requires browserify
|
|
196
|
+
import bs58 from "bs58";
|
|
197
|
+
|
|
198
|
+
// Wrong: Checking lnbc before lnbcrt
|
|
199
|
+
if (invoice.startsWith("lnbc")) return "mainnet";
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### ✅ Do This
|
|
203
|
+
```typescript
|
|
204
|
+
// Correct: Use .js extension for @noble/hashes
|
|
205
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
206
|
+
|
|
207
|
+
// Correct: Use @scure/base (works everywhere)
|
|
208
|
+
import { base58 } from "@scure/base";
|
|
209
|
+
|
|
210
|
+
// Correct: Check longer prefix first
|
|
211
|
+
if (invoice.startsWith("lnbcrt")) return "regtest";
|
|
212
|
+
else if (invoice.startsWith("lnbc")) return "mainnet";
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Adding New Payment Methods
|
|
216
|
+
|
|
217
|
+
1. Add to `PaymentMethod` type union
|
|
218
|
+
2. Add parameter parsing in `parseBIP321()`
|
|
219
|
+
3. Implement validation function if needed
|
|
220
|
+
4. Add network detection if applicable
|
|
221
|
+
5. Write tests for new method
|
|
222
|
+
6. Update README documentation
|
|
223
|
+
7. Add example usage
|
|
224
|
+
|
|
225
|
+
## Browser Compatibility
|
|
226
|
+
|
|
227
|
+
All dependencies work natively in browsers via ES modules:
|
|
228
|
+
|
|
229
|
+
```html
|
|
230
|
+
<script type="module">
|
|
231
|
+
import { parseBIP321 } from './index.js';
|
|
232
|
+
// No build tools needed!
|
|
233
|
+
</script>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**No browserify, webpack, rollup, or any build tools required.**
|
|
237
|
+
|
|
238
|
+
## Performance Considerations
|
|
239
|
+
|
|
240
|
+
- Address validation is synchronous and fast
|
|
241
|
+
- Lightning invoice decoding is the slowest operation
|
|
242
|
+
- No async operations in the entire codebase
|
|
243
|
+
- Manual Base58 validation is faster than external libraries
|
|
244
|
+
|
|
245
|
+
## Contributing Guidelines
|
|
246
|
+
|
|
247
|
+
1. All changes must pass TypeScript type check (`bun run check`)
|
|
248
|
+
2. All tests must pass (`bun test`)
|
|
249
|
+
3. Add tests for new functionality
|
|
250
|
+
4. Update README for API changes
|
|
251
|
+
5. Keep dependencies minimal and browser-compatible
|
|
252
|
+
6. No build tools or polyfills
|
|
253
|
+
7. Maintain cross-platform compatibility (Node.js, browser, React Native)
|
|
254
|
+
|
|
255
|
+
## Resources
|
|
256
|
+
|
|
257
|
+
- [BIP-321 Specification](https://bips.dev/321/)
|
|
258
|
+
- [BIP-21 (Original)](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki)
|
|
259
|
+
- [BIP-352 Silent Payments](https://github.com/bitcoin/bips/blob/master/bip-0352.mediawiki)
|
|
260
|
+
- [@scure/base Documentation](https://github.com/paulmillr/scure-base)
|
|
261
|
+
- [@noble/hashes Documentation](https://github.com/paulmillr/noble-hashes)
|
package/README.md
CHANGED
|
@@ -5,43 +5,29 @@ A TypeScript/JavaScript library for parsing BIP-321 Bitcoin URI scheme. This lib
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ✅ **Complete BIP-321 compliance** - Implements the full BIP-321 specification
|
|
8
|
-
- ✅ **Multiple payment methods** - Supports on-chain, Lightning (BOLT11), BOLT12 offers,
|
|
8
|
+
- ✅ **Multiple payment methods** - Supports on-chain, Lightning (BOLT11), BOLT12 offers, and silent payments
|
|
9
9
|
- ✅ **Network detection** - Automatically detects mainnet, testnet, regtest, and signet networks
|
|
10
10
|
- ✅ **Address validation** - Validates Bitcoin addresses (P2PKH, P2SH, Segwit v0, Taproot)
|
|
11
11
|
- ✅ **Lightning invoice validation** - Validates BOLT11 Lightning invoices
|
|
12
|
-
- ✅ **Cross-platform** - Works in Node.js, browsers, and React Native
|
|
13
|
-
- ✅ **TypeScript support** - Fully typed with TypeScript definitions
|
|
14
|
-
- ✅ **Proof of payment** - Supports pop/req-pop parameters for payment callbacks
|
|
15
|
-
- ✅ **Comprehensive error handling** - Clear error messages for invalid URIs
|
|
16
|
-
- ✅ **Full type safety** - Passes strict TypeScript type checking with no errors
|
|
17
|
-
|
|
18
|
-
## TypeScript Type Safety
|
|
19
|
-
|
|
20
|
-
This library is built with full TypeScript support and passes strict type checking (`tsc --noEmit`) with zero errors. All functions return fully-typed objects, providing excellent IDE autocomplete and compile-time safety.
|
|
21
12
|
|
|
22
13
|
```typescript
|
|
23
14
|
import { parseBIP321, type BIP321ParseResult, type PaymentMethod } from "bip-321";
|
|
24
15
|
|
|
25
|
-
// Fully typed result with autocomplete support
|
|
26
16
|
const result: BIP321ParseResult = parseBIP321("bitcoin:...");
|
|
27
17
|
|
|
28
|
-
// TypeScript knows all available properties
|
|
29
18
|
result.valid; // boolean
|
|
30
19
|
result.network; // "mainnet" | "testnet" | "regtest" | "signet" | undefined
|
|
31
20
|
result.paymentMethods; // PaymentMethod[]
|
|
32
21
|
result.errors; // string[]
|
|
33
22
|
|
|
34
|
-
// Payment methods are also fully typed
|
|
35
23
|
result.paymentMethods.forEach((method: PaymentMethod) => {
|
|
36
|
-
method.type; // "onchain" | "lightning" | "lno" | "silent-payment" | "
|
|
24
|
+
method.type; // "onchain" | "lightning" | "lno" | "silent-payment" | "other"
|
|
37
25
|
method.network; // "mainnet" | "testnet" | "regtest" | "signet" | undefined
|
|
38
26
|
method.valid; // boolean
|
|
39
27
|
});
|
|
40
28
|
```
|
|
41
29
|
|
|
42
30
|
## Installation
|
|
43
|
-
</text>
|
|
44
|
-
|
|
45
31
|
|
|
46
32
|
```bash
|
|
47
33
|
bun add bip-321
|
|
@@ -195,7 +181,7 @@ interface BIP321ParseResult {
|
|
|
195
181
|
|
|
196
182
|
```typescript
|
|
197
183
|
interface PaymentMethod {
|
|
198
|
-
type: "onchain" | "lightning" | "lno" | "silent-payment" | "
|
|
184
|
+
type: "onchain" | "lightning" | "lno" | "silent-payment" | "other";
|
|
199
185
|
value: string; // The actual address/invoice value
|
|
200
186
|
network?: "mainnet" | "testnet" | "regtest" | "signet";
|
|
201
187
|
valid: boolean; // Whether this payment method is valid
|
|
@@ -247,7 +233,6 @@ console.log(summary);
|
|
|
247
233
|
| Lightning | `lightning` | BOLT11 Lightning invoices |
|
|
248
234
|
| BOLT12 | `lno` | Lightning BOLT12 offers |
|
|
249
235
|
| Silent Payments | `sp` | BIP352 Silent Payment addresses |
|
|
250
|
-
| Private Payments | `pay` | BIP351 Private Payment addresses |
|
|
251
236
|
|
|
252
237
|
## Network Detection
|
|
253
238
|
|
|
@@ -333,4 +318,4 @@ BSD-2-Clause (same as BIP-321)
|
|
|
333
318
|
## Related
|
|
334
319
|
|
|
335
320
|
- [BIP-321 Specification](https://bips.dev/321/)
|
|
336
|
-
- [BIP-21 (Original)](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki)
|
|
321
|
+
- [BIP-21 (Original)](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki)
|
package/bun.lock
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
"": {
|
|
5
5
|
"name": "bip-321",
|
|
6
6
|
"dependencies": {
|
|
7
|
+
"@noble/hashes": "^2.0.1",
|
|
7
8
|
"@scure/base": "^2.0.0",
|
|
8
9
|
"bitcoinjs-lib": "^7.0.0",
|
|
9
|
-
"bs58check": "^4.0.0",
|
|
10
10
|
"light-bolt11-decoder": "^3.2.0",
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@types/bun": "latest",
|
|
14
|
+
"oxlint": "^1.26.0",
|
|
14
15
|
},
|
|
15
16
|
"peerDependencies": {
|
|
16
17
|
"typescript": "^5",
|
|
@@ -18,7 +19,23 @@
|
|
|
18
19
|
},
|
|
19
20
|
},
|
|
20
21
|
"packages": {
|
|
21
|
-
"@noble/hashes": ["@noble/hashes@
|
|
22
|
+
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
|
|
23
|
+
|
|
24
|
+
"@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.26.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kTmm1opqyn7iZopWHO3Ml4D/44pA5eknZBepgxCnTaPrW8XgCEUI85Q5AvOOvoNve8NziTYb8ax+CyuGJIgn/Q=="],
|
|
25
|
+
|
|
26
|
+
"@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.26.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-/hMfZ9j7ZzVPRmMm02PHNc6MIMk0QYv5VowZJRIp40YLqLPvFfGNGZBj8e1fDVgZMFEGWDQK3yrt1uBKxXAK4Q=="],
|
|
27
|
+
|
|
28
|
+
"@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.26.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-iv4wdrwdCa8bhJxOpKlvfxqTs0LgW5tKBUMvH9B13zREHm1xT9JRZ8cQbbKiyC6LNdggwu5S6TSvODgAu7/DlA=="],
|
|
29
|
+
|
|
30
|
+
"@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.26.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-a3gTbnN1JzedxqYeGTkg38BAs/r3Krd2DPNs/MF7nnHthT3RzkPUk47isMePLuNc4e/Weljn7m2m/Onx22tiNg=="],
|
|
31
|
+
|
|
32
|
+
"@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.26.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cCAyqyuKpFImjlgiBuuwSF+aDBW2h19/aCmHMTMSp6KXwhoQK7/Xx7/EhZKP5wiQJzVUYq5fXr0D8WmpLGsjRg=="],
|
|
33
|
+
|
|
34
|
+
"@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.26.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8VOJ4vQo0G1tNdaghxrWKjKZGg73tv+FoMDrtNYuUesqBHZN68FkYCsgPwEsacLhCmtoZrkF3ePDWDuWEpDyAg=="],
|
|
35
|
+
|
|
36
|
+
"@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.26.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-N8KUtzP6gfEHKvaIBZCS9g8wRfqV5v55a/B8iJjIEhtMehcEM+UX+aYRsQ4dy5oBCrK3FEp4Yy/jHgb0moLm3Q=="],
|
|
37
|
+
|
|
38
|
+
"@oxlint/win32-x64": ["@oxlint/win32-x64@1.26.0", "", { "os": "win32", "cpu": "x64" }, "sha512-7tCyG0laduNQ45vzB9blVEGq/6DOvh7AFmiUAana8mTp0zIKQQmwJ21RqhazH0Rk7O6lL7JYzKcu+zaJHGpRLA=="],
|
|
22
39
|
|
|
23
40
|
"@scure/base": ["@scure/base@2.0.0", "", {}, "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w=="],
|
|
24
41
|
|
|
@@ -46,6 +63,8 @@
|
|
|
46
63
|
|
|
47
64
|
"light-bolt11-decoder": ["light-bolt11-decoder@3.2.0", "", { "dependencies": { "@scure/base": "1.1.1" } }, "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ=="],
|
|
48
65
|
|
|
66
|
+
"oxlint": ["oxlint@1.26.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.26.0", "@oxlint/darwin-x64": "1.26.0", "@oxlint/linux-arm64-gnu": "1.26.0", "@oxlint/linux-arm64-musl": "1.26.0", "@oxlint/linux-x64-gnu": "1.26.0", "@oxlint/linux-x64-musl": "1.26.0", "@oxlint/win32-arm64": "1.26.0", "@oxlint/win32-x64": "1.26.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.4.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint", "oxc_language_server": "bin/oxc_language_server" } }, "sha512-KRpL+SMi07JQyggv5ldIF+wt2pnrKm8NLW0B+8bK+0HZsLmH9/qGA+qMWie5Vf7lnlMBllJmsuzHaKFEGY3rIA=="],
|
|
67
|
+
|
|
49
68
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
50
69
|
|
|
51
70
|
"uint8array-tools": ["uint8array-tools@0.0.9", "", {}, "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A=="],
|
|
@@ -56,6 +75,10 @@
|
|
|
56
75
|
|
|
57
76
|
"varuint-bitcoin": ["varuint-bitcoin@2.0.0", "", { "dependencies": { "uint8array-tools": "^0.0.8" } }, "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog=="],
|
|
58
77
|
|
|
78
|
+
"bitcoinjs-lib/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
|
79
|
+
|
|
80
|
+
"bs58check/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
|
81
|
+
|
|
59
82
|
"light-bolt11-decoder/@scure/base": ["@scure/base@1.1.1", "", {}, "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA=="],
|
|
60
83
|
|
|
61
84
|
"varuint-bitcoin/uint8array-tools": ["uint8array-tools@0.0.8", "", {}, "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g=="],
|
package/index.test.ts
CHANGED
|
@@ -186,12 +186,6 @@ describe("BIP-321 Parser", () => {
|
|
|
186
186
|
expect(result.valid).toBe(true);
|
|
187
187
|
expect(result.paymentMethods.length).toBe(2);
|
|
188
188
|
});
|
|
189
|
-
|
|
190
|
-
test("parses private payment address", () => {
|
|
191
|
-
const result = parseBIP321("bitcoin:?pay=bip351address");
|
|
192
|
-
expect(result.valid).toBe(true);
|
|
193
|
-
expect(result.paymentMethods[0]!.type).toBe("private-payment");
|
|
194
|
-
});
|
|
195
189
|
});
|
|
196
190
|
|
|
197
191
|
describe("Network-specific Parameters", () => {
|
package/index.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import * as bitcoin from "bitcoinjs-lib";
|
|
2
2
|
import { decode as decodeLightning } from "light-bolt11-decoder";
|
|
3
|
-
import
|
|
4
|
-
import { bech32, bech32m } from "@scure/base";
|
|
3
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
4
|
+
import { base58, bech32, bech32m } from "@scure/base";
|
|
5
5
|
|
|
6
6
|
export interface PaymentMethod {
|
|
7
|
-
type:
|
|
8
|
-
| "onchain"
|
|
9
|
-
| "lightning"
|
|
10
|
-
| "lno"
|
|
11
|
-
| "silent-payment"
|
|
12
|
-
| "private-payment"
|
|
13
|
-
| "other";
|
|
7
|
+
type: "onchain" | "lightning" | "lno" | "silent-payment" | "other";
|
|
14
8
|
value: string;
|
|
15
9
|
network?: "mainnet" | "testnet" | "regtest" | "signet";
|
|
16
10
|
valid: boolean;
|
|
@@ -46,7 +40,7 @@ function detectAddressNetwork(
|
|
|
46
40
|
// Try using bitcoinjs-lib first (works for non-taproot)
|
|
47
41
|
bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin);
|
|
48
42
|
return "mainnet";
|
|
49
|
-
} catch
|
|
43
|
+
} catch {
|
|
50
44
|
// Fallback to manual bech32/bech32m validation for taproot
|
|
51
45
|
try {
|
|
52
46
|
const decoded = lowerAddress.startsWith("bc1p")
|
|
@@ -63,7 +57,7 @@ function detectAddressNetwork(
|
|
|
63
57
|
try {
|
|
64
58
|
bitcoin.address.toOutputScript(address, bitcoin.networks.testnet);
|
|
65
59
|
return "testnet";
|
|
66
|
-
} catch
|
|
60
|
+
} catch {
|
|
67
61
|
try {
|
|
68
62
|
const decoded = lowerAddress.startsWith("tb1p")
|
|
69
63
|
? bech32m.decode(address as `${string}1${string}`, 90)
|
|
@@ -79,7 +73,7 @@ function detectAddressNetwork(
|
|
|
79
73
|
try {
|
|
80
74
|
bitcoin.address.toOutputScript(address, bitcoin.networks.regtest);
|
|
81
75
|
return "regtest";
|
|
82
|
-
} catch
|
|
76
|
+
} catch {
|
|
83
77
|
try {
|
|
84
78
|
const decoded = lowerAddress.startsWith("bcrt1p")
|
|
85
79
|
? bech32m.decode(address as `${string}1${string}`, 90)
|
|
@@ -93,19 +87,38 @@ function detectAddressNetwork(
|
|
|
93
87
|
}
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
// Base58 addresses (P2PKH, P2SH)
|
|
97
|
-
|
|
98
|
-
|
|
90
|
+
// Base58 addresses (P2PKH, P2SH) - manual validation with checksum
|
|
91
|
+
try {
|
|
92
|
+
const decoded = base58.decode(address);
|
|
93
|
+
if (decoded.length < 25) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
// Verify checksum
|
|
98
|
+
const payload = decoded.slice(0, -4);
|
|
99
|
+
const checksum = decoded.slice(-4);
|
|
100
|
+
const hash = sha256(sha256(payload));
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < 4; i++) {
|
|
103
|
+
if (checksum[i] !== hash[i]) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const version = payload[0];
|
|
109
|
+
|
|
110
|
+
// Mainnet: P2PKH (0x00), P2SH (0x05)
|
|
111
|
+
if (version === 0x00 || version === 0x05) {
|
|
112
|
+
return "mainnet";
|
|
113
|
+
}
|
|
114
|
+
// Testnet: P2PKH (0x6f), P2SH (0xc4)
|
|
115
|
+
else if (version === 0x6f || version === 0xc4) {
|
|
116
|
+
return "testnet";
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return undefined;
|
|
107
120
|
}
|
|
108
|
-
} catch
|
|
121
|
+
} catch {
|
|
109
122
|
return undefined;
|
|
110
123
|
}
|
|
111
124
|
return undefined;
|
|
@@ -134,7 +147,7 @@ function validateLightningInvoice(invoice: string): {
|
|
|
134
147
|
error?: string;
|
|
135
148
|
} {
|
|
136
149
|
try {
|
|
137
|
-
const
|
|
150
|
+
const _decoded = decodeLightning(invoice);
|
|
138
151
|
let network: "mainnet" | "testnet" | "regtest" | "signet" | undefined;
|
|
139
152
|
|
|
140
153
|
const lowerInvoice = invoice.toLowerCase();
|
|
@@ -174,7 +187,7 @@ function validatePopUri(popUri: string): { valid: boolean; error?: string } {
|
|
|
174
187
|
}
|
|
175
188
|
|
|
176
189
|
return { valid: true };
|
|
177
|
-
} catch
|
|
190
|
+
} catch {
|
|
178
191
|
return { valid: false, error: "Invalid pop URI encoding" };
|
|
179
192
|
}
|
|
180
193
|
}
|
|
@@ -284,7 +297,7 @@ export function parseBIP321(uri: string): BIP321ParseResult {
|
|
|
284
297
|
}
|
|
285
298
|
}
|
|
286
299
|
} else if (lowerKey === "pop" || lowerKey === "req-pop") {
|
|
287
|
-
const
|
|
300
|
+
const _popKey = lowerKey === "req-pop" ? "req-pop" : "pop";
|
|
288
301
|
if (result.pop !== undefined || result.popRequired !== undefined) {
|
|
289
302
|
result.errors.push("Multiple pop/req-pop parameters not allowed");
|
|
290
303
|
result.valid = false;
|
|
@@ -334,13 +347,6 @@ export function parseBIP321(uri: string): BIP321ParseResult {
|
|
|
334
347
|
if (!isSilentPayment) {
|
|
335
348
|
result.errors.push("Invalid silent payment address format");
|
|
336
349
|
}
|
|
337
|
-
} else if (lowerKey === "pay") {
|
|
338
|
-
const decodedValue = decodeURIComponent(value);
|
|
339
|
-
result.paymentMethods.push({
|
|
340
|
-
type: "private-payment",
|
|
341
|
-
value: decodedValue,
|
|
342
|
-
valid: true,
|
|
343
|
-
});
|
|
344
350
|
} else if (
|
|
345
351
|
lowerKey === "bc" ||
|
|
346
352
|
lowerKey === "tb" ||
|
package/oxlintrc.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"categories": {
|
|
3
|
+
"correctness": "warn",
|
|
4
|
+
"suspicious": "warn",
|
|
5
|
+
"pedantic": "warn",
|
|
6
|
+
"perf": "warn",
|
|
7
|
+
"style": "warn",
|
|
8
|
+
"restriction": "warn"
|
|
9
|
+
},
|
|
10
|
+
"rules": {
|
|
11
|
+
"typescript/no-explicit-any": "error",
|
|
12
|
+
"typescript/no-non-null-assertion": "error",
|
|
13
|
+
"no-console": "warn",
|
|
14
|
+
"no-debugger": "error",
|
|
15
|
+
"eqeqeq": "error"
|
|
16
|
+
},
|
|
17
|
+
"ignorePatterns": [
|
|
18
|
+
"**/node_modules/**",
|
|
19
|
+
"**/dist/**",
|
|
20
|
+
"**/build/**",
|
|
21
|
+
"**/*.min.js"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bip-321",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A TypeScript/JavaScript library for parsing BIP-321 Bitcoin URI scheme with support for multiple payment methods",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"test": "bun test",
|
|
17
|
-
"example": "bun example.ts"
|
|
17
|
+
"example": "bun example.ts",
|
|
18
|
+
"check": "tsc --noEmit",
|
|
19
|
+
"lint": "oxlint"
|
|
18
20
|
},
|
|
19
21
|
"keywords": [
|
|
20
22
|
"bitcoin",
|
|
@@ -40,25 +42,26 @@
|
|
|
40
42
|
"author": "",
|
|
41
43
|
"license": "BSD-2-Clause",
|
|
42
44
|
"dependencies": {
|
|
45
|
+
"@noble/hashes": "^2.0.1",
|
|
43
46
|
"@scure/base": "^2.0.0",
|
|
44
47
|
"bitcoinjs-lib": "^7.0.0",
|
|
45
|
-
"bs58check": "^4.0.0",
|
|
46
48
|
"light-bolt11-decoder": "^3.2.0"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
|
-
"@types/bun": "latest"
|
|
51
|
+
"@types/bun": "latest",
|
|
52
|
+
"oxlint": "^1.26.0"
|
|
50
53
|
},
|
|
51
54
|
"peerDependencies": {
|
|
52
55
|
"typescript": "^5"
|
|
53
56
|
},
|
|
54
57
|
"repository": {
|
|
55
58
|
"type": "git",
|
|
56
|
-
"url": "https://github.com/
|
|
59
|
+
"url": "https://github.com/niteshbalusu11/bip-321.git"
|
|
57
60
|
},
|
|
58
61
|
"bugs": {
|
|
59
|
-
"url": "https://github.com/
|
|
62
|
+
"url": "https://github.com/niteshbalusu11/bip-321/issues"
|
|
60
63
|
},
|
|
61
|
-
"homepage": "https://github.com/
|
|
64
|
+
"homepage": "https://github.com/niteshbalusu11/bip-321#readme",
|
|
62
65
|
"engines": {
|
|
63
66
|
"node": ">=16.0.0"
|
|
64
67
|
}
|
package/CLAUDE.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
|
|
3
|
-
Default to using Bun instead of Node.js.
|
|
4
|
-
|
|
5
|
-
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
6
|
-
- Use `bun test` instead of `jest` or `vitest`
|
|
7
|
-
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
8
|
-
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
9
|
-
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
|
10
|
-
- Bun automatically loads .env, so don't use dotenv.
|
|
11
|
-
|
|
12
|
-
## APIs
|
|
13
|
-
|
|
14
|
-
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
|
15
|
-
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
|
16
|
-
- `Bun.redis` for Redis. Don't use `ioredis`.
|
|
17
|
-
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
|
18
|
-
- `WebSocket` is built-in. Don't use `ws`.
|
|
19
|
-
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
|
20
|
-
- Bun.$`ls` instead of execa.
|
|
21
|
-
|
|
22
|
-
## Testing
|
|
23
|
-
|
|
24
|
-
Use `bun test` to run tests.
|
|
25
|
-
|
|
26
|
-
```ts#index.test.ts
|
|
27
|
-
import { test, expect } from "bun:test";
|
|
28
|
-
|
|
29
|
-
test("hello world", () => {
|
|
30
|
-
expect(1).toBe(1);
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Frontend
|
|
35
|
-
|
|
36
|
-
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
|
37
|
-
|
|
38
|
-
Server:
|
|
39
|
-
|
|
40
|
-
```ts#index.ts
|
|
41
|
-
import index from "./index.html"
|
|
42
|
-
|
|
43
|
-
Bun.serve({
|
|
44
|
-
routes: {
|
|
45
|
-
"/": index,
|
|
46
|
-
"/api/users/:id": {
|
|
47
|
-
GET: (req) => {
|
|
48
|
-
return new Response(JSON.stringify({ id: req.params.id }));
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
// optional websocket support
|
|
53
|
-
websocket: {
|
|
54
|
-
open: (ws) => {
|
|
55
|
-
ws.send("Hello, world!");
|
|
56
|
-
},
|
|
57
|
-
message: (ws, message) => {
|
|
58
|
-
ws.send(message);
|
|
59
|
-
},
|
|
60
|
-
close: (ws) => {
|
|
61
|
-
// handle close
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
development: {
|
|
65
|
-
hmr: true,
|
|
66
|
-
console: true,
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
|
72
|
-
|
|
73
|
-
```html#index.html
|
|
74
|
-
<html>
|
|
75
|
-
<body>
|
|
76
|
-
<h1>Hello, world!</h1>
|
|
77
|
-
<script type="module" src="./frontend.tsx"></script>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
With the following `frontend.tsx`:
|
|
83
|
-
|
|
84
|
-
```tsx#frontend.tsx
|
|
85
|
-
import React from "react";
|
|
86
|
-
|
|
87
|
-
// import .css files directly and it works
|
|
88
|
-
import './index.css';
|
|
89
|
-
|
|
90
|
-
import { createRoot } from "react-dom/client";
|
|
91
|
-
|
|
92
|
-
const root = createRoot(document.body);
|
|
93
|
-
|
|
94
|
-
export default function Frontend() {
|
|
95
|
-
return <h1>Hello, world!</h1>;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
root.render(<Frontend />);
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Then, run index.ts
|
|
102
|
-
|
|
103
|
-
```sh
|
|
104
|
-
bun --hot ./index.ts
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
|