bsv-x402 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -53,12 +53,15 @@ function parseBrc105Challenge(response) {
53
53
  if (!Number.isFinite(satoshisRequired) || !Number.isInteger(satoshisRequired) || satoshisRequired <= 0) {
54
54
  throw new Error("BRC-105: satoshis-required must be a positive integer");
55
55
  }
56
- const serverIdentityKey = response.headers.get("x-bsv-auth-identity-key");
56
+ const authIdentityKey = response.headers.get("x-bsv-auth-identity-key") || null;
57
+ const paymentIdentityKey = response.headers.get("x-bsv-payment-identity-key") || null;
58
+ const authenticated = authIdentityKey !== null && authIdentityKey.length > 0;
59
+ const serverIdentityKey = authIdentityKey || paymentIdentityKey;
57
60
  if (serverIdentityKey === null || serverIdentityKey.length === 0) {
58
- throw new Error("BRC-105: missing or empty x-bsv-auth-identity-key header");
61
+ throw new Error("BRC-105: missing identity key (expected x-bsv-auth-identity-key or x-bsv-payment-identity-key)");
59
62
  }
60
63
  if (!/^0[23][0-9a-fA-F]{64}$/.test(serverIdentityKey)) {
61
- throw new Error("BRC-105: x-bsv-auth-identity-key must be a 33-byte compressed public key (hex)");
64
+ throw new Error("BRC-105: identity key must be a 33-byte compressed public key (hex)");
62
65
  }
63
66
  const derivationPrefix = response.headers.get("x-bsv-payment-derivation-prefix");
64
67
  if (derivationPrefix === null || derivationPrefix.length === 0) {
@@ -68,7 +71,8 @@ function parseBrc105Challenge(response) {
68
71
  version,
69
72
  satoshisRequired,
70
73
  serverIdentityKey,
71
- derivationPrefix
74
+ derivationPrefix,
75
+ authenticated
72
76
  };
73
77
  }
74
78
 
@@ -516,7 +520,8 @@ async function createDerivationSuffix(wallet) {
516
520
  const nonceBytes = [...firstHalf, ...hmac];
517
521
  return numberArrayToBase64(nonceBytes);
518
522
  }
519
- async function constructBrc105Proof(challenge, wallet) {
523
+ async function constructBrc105Proof(challenge, wallet, origin) {
524
+ const { publicKey: clientIdentityKey } = await wallet.getPublicKey({ identityKey: true });
520
525
  const derivationSuffix = await createDerivationSuffix(wallet);
521
526
  const keyID = `${challenge.derivationPrefix} ${derivationSuffix}`;
522
527
  const { publicKey: derivedPublicKey } = await wallet.getPublicKey({
@@ -525,12 +530,13 @@ async function constructBrc105Proof(challenge, wallet) {
525
530
  counterparty: challenge.serverIdentityKey
526
531
  });
527
532
  const lockingScript = await pubkeyToP2PKHLockingScript(derivedPublicKey);
533
+ const description = origin ? `Payment for request to ${origin}` : "BRC-105 payment";
528
534
  const result = await wallet.createAction({
529
- description: "BRC-105 payment",
535
+ description,
530
536
  outputs: [{
531
537
  satoshis: challenge.satoshisRequired,
532
538
  lockingScript,
533
- description: "BRC-105 payment output",
539
+ outputDescription: "HTTP request payment",
534
540
  customInstructions: JSON.stringify({
535
541
  derivationPrefix: challenge.derivationPrefix,
536
542
  derivationSuffix,
@@ -553,6 +559,7 @@ async function constructBrc105Proof(challenge, wallet) {
553
559
  derivationPrefix: challenge.derivationPrefix,
554
560
  derivationSuffix,
555
561
  transaction: transactionBase64,
562
+ clientIdentityKey,
556
563
  txid: result.txid
557
564
  };
558
565
  }
@@ -1165,12 +1172,26 @@ function createX402Fetch(config = {}) {
1165
1172
  let proof;
1166
1173
  try {
1167
1174
  proof = await buildProof();
1168
- } catch {
1175
+ } catch (err) {
1176
+ console.error(`[x402] Proof construction failed (${protocol}):`, err);
1177
+ config.onProofError?.(err, protocol);
1169
1178
  return originalResponse;
1170
1179
  }
1171
1180
  rl.record(makeLedgerEntry(proof));
1172
1181
  await persist(rl);
1173
- return retryWithProof(proof);
1182
+ const maxAttempts = 3;
1183
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1184
+ try {
1185
+ return await retryWithProof(proof);
1186
+ } catch (err) {
1187
+ if (attempt >= maxAttempts) {
1188
+ console.error(`[x402] Paid request failed after ${maxAttempts} attempts (${protocol}):`, err);
1189
+ return originalResponse;
1190
+ }
1191
+ await new Promise((r) => setTimeout(r, 250 * Math.pow(2, attempt)));
1192
+ }
1193
+ }
1194
+ return originalResponse;
1174
1195
  });
1175
1196
  }
1176
1197
  const fetchFn = async function x402Fetch2(input, init) {
@@ -1226,11 +1247,12 @@ function createX402Fetch(config = {}) {
1226
1247
  if (brc105ProofConstructor) {
1227
1248
  return brc105ProofConstructor(brc105Challenge);
1228
1249
  }
1229
- return constructBrc105Proof(brc105Challenge, brc105Wallet);
1250
+ return constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
1230
1251
  },
1231
1252
  (proof) => {
1232
1253
  const headers = new Headers(init?.headers);
1233
1254
  headers.set("x-bsv-payment", JSON.stringify(proof));
1255
+ headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
1234
1256
  return fetch(input, { ...init, headers });
1235
1257
  },
1236
1258
  (proof) => ({
package/dist/index.d.cts CHANGED
@@ -13,6 +13,8 @@ interface Brc105Challenge {
13
13
  satoshisRequired: number;
14
14
  serverIdentityKey: string;
15
15
  derivationPrefix: string;
16
+ /** Whether the identity key came from BRC-103 auth (vs standalone x-bsv-payment-identity-key). */
17
+ authenticated: boolean;
16
18
  }
17
19
  /** Minimal wallet interface for BRC-105 proof construction.
18
20
  * Works with both CWIInterface (page context) and WalletInterface (SDK). */
@@ -21,6 +23,8 @@ interface Brc105Wallet {
21
23
  protocolID: [number, string];
22
24
  keyID: string;
23
25
  counterparty: string;
26
+ } | {
27
+ identityKey: true;
24
28
  }): Promise<{
25
29
  publicKey: string;
26
30
  }>;
@@ -38,6 +42,7 @@ interface Brc105Proof {
38
42
  derivationPrefix: string;
39
43
  derivationSuffix: string;
40
44
  transaction: string;
45
+ clientIdentityKey: string;
41
46
  txid: string;
42
47
  }
43
48
  type Brc105ProofConstructor = (challenge: Brc105Challenge) => Promise<Brc105Proof>;
@@ -93,6 +98,7 @@ interface X402Config {
93
98
  nightmareConfirmation?: string;
94
99
  onLimitReached?: (reason: string) => void;
95
100
  onYellowLight?: (detail: YellowLightEvent) => Promise<boolean>;
101
+ onProofError?: (error: unknown, protocol: PaymentProtocol) => void;
96
102
  now?: () => number;
97
103
  }
98
104
  interface YellowLightEvent {
@@ -135,6 +141,7 @@ interface CWICreateActionOutput {
135
141
  satoshis: number;
136
142
  lockingScript: string;
137
143
  description?: string;
144
+ outputDescription?: string;
138
145
  customInstructions?: string;
139
146
  }
140
147
  interface CWICreateActionParams {
@@ -194,7 +201,7 @@ declare function parseChallenge(header: string): Challenge;
194
201
  * Expects four headers:
195
202
  * - x-bsv-payment-version (must be "1.0")
196
203
  * - x-bsv-payment-satoshis-required (positive integer)
197
- * - x-bsv-auth-identity-key (non-empty)
204
+ * - x-bsv-auth-identity-key or x-bsv-payment-identity-key (compressed pubkey)
198
205
  * - x-bsv-payment-derivation-prefix (non-empty)
199
206
  */
200
207
  declare function parseBrc105Challenge(response: Response): Brc105Challenge;
@@ -209,7 +216,7 @@ declare function parseBrc105Challenge(response: Response): Brc105Challenge;
209
216
  * 4. Call wallet.createAction with the locking script and custom instructions
210
217
  * 5. Convert transaction to base64
211
218
  */
212
- declare function constructBrc105Proof(challenge: Brc105Challenge, wallet: Brc105Wallet): Promise<Brc105Proof>;
219
+ declare function constructBrc105Proof(challenge: Brc105Challenge, wallet: Brc105Wallet, origin?: string): Promise<Brc105Proof>;
213
220
 
214
221
  /** Anything with a numeric `amount` can be checked against spending limits. */
215
222
  type SpendCheckable = Challenge | PaymentRequest;
@@ -267,4 +274,4 @@ type SitePromptFn = (origin: string) => Promise<SitePolicyAction>;
267
274
  */
268
275
  declare function resolveSitePolicy(origin: string, limits: SpendLimits, twoFactorProvider?: TwoFactorProvider, promptFn?: SitePromptFn): Promise<SitePolicy>;
269
276
 
270
- export { BFG_DAILY_CEILING_SATOSHIS, BFG_PER_TX_CEILING_SATOSHIS, type Brc105Challenge, type Brc105Proof, type Brc105ProofConstructor, type Brc105Wallet, type Challenge, type LedgerEntry, type LimitCheckResult, type LimitState, LocalStorageAdapter, type PaymentProtocol, type PaymentRequest, type Proof, RateLimiter, type SitePolicy, type SitePolicyAction, type SpendCheckable, type SpendLimits, type SpendMode, type StorageAdapter, TIER_PRESETS, type TierName, type TierPreset, type TimeWindow, type TwoFactorAction, type TwoFactorPolicy, type TwoFactorProvider, WalletTwoFactorProvider, type WindowLimit, type X402Config, type X402FetchFn, type YellowLightEvent, constructBrc105Proof, createX402Fetch, parseBrc105Challenge, parseChallenge, resolveSitePolicy, resolveSpendLimits, x402Fetch };
277
+ export { BFG_DAILY_CEILING_SATOSHIS, BFG_PER_TX_CEILING_SATOSHIS, type BlockSeverity, type Brc105Challenge, type Brc105Proof, type Brc105ProofConstructor, type Brc105Wallet, type CWICreateActionOutput, type CWICreateActionParams, type CWICreateActionResult, type Challenge, type KeyDeriver, type LedgerEntry, type LimitCheckResult, type LimitState, LocalStorageAdapter, type PaymentProtocol, type PaymentRequest, type Proof, RateLimiter, type SitePolicy, type SitePolicyAction, type SitePromptFn, type SpendCheckable, type SpendLimits, type SpendMode, type StorageAdapter, TIER_PRESETS, type TierName, type TierPreset, type TimeWindow, type TwoFactorAction, type TwoFactorPolicy, type TwoFactorProvider, WalletTwoFactorProvider, type WindowLimit, type X402Config, type X402FetchFn, type YellowLightEvent, constructBrc105Proof, createX402Fetch, parseBrc105Challenge, parseChallenge, resolveSitePolicy, resolveSpendLimits, x402Fetch };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ interface Brc105Challenge {
13
13
  satoshisRequired: number;
14
14
  serverIdentityKey: string;
15
15
  derivationPrefix: string;
16
+ /** Whether the identity key came from BRC-103 auth (vs standalone x-bsv-payment-identity-key). */
17
+ authenticated: boolean;
16
18
  }
17
19
  /** Minimal wallet interface for BRC-105 proof construction.
18
20
  * Works with both CWIInterface (page context) and WalletInterface (SDK). */
@@ -21,6 +23,8 @@ interface Brc105Wallet {
21
23
  protocolID: [number, string];
22
24
  keyID: string;
23
25
  counterparty: string;
26
+ } | {
27
+ identityKey: true;
24
28
  }): Promise<{
25
29
  publicKey: string;
26
30
  }>;
@@ -38,6 +42,7 @@ interface Brc105Proof {
38
42
  derivationPrefix: string;
39
43
  derivationSuffix: string;
40
44
  transaction: string;
45
+ clientIdentityKey: string;
41
46
  txid: string;
42
47
  }
43
48
  type Brc105ProofConstructor = (challenge: Brc105Challenge) => Promise<Brc105Proof>;
@@ -93,6 +98,7 @@ interface X402Config {
93
98
  nightmareConfirmation?: string;
94
99
  onLimitReached?: (reason: string) => void;
95
100
  onYellowLight?: (detail: YellowLightEvent) => Promise<boolean>;
101
+ onProofError?: (error: unknown, protocol: PaymentProtocol) => void;
96
102
  now?: () => number;
97
103
  }
98
104
  interface YellowLightEvent {
@@ -135,6 +141,7 @@ interface CWICreateActionOutput {
135
141
  satoshis: number;
136
142
  lockingScript: string;
137
143
  description?: string;
144
+ outputDescription?: string;
138
145
  customInstructions?: string;
139
146
  }
140
147
  interface CWICreateActionParams {
@@ -194,7 +201,7 @@ declare function parseChallenge(header: string): Challenge;
194
201
  * Expects four headers:
195
202
  * - x-bsv-payment-version (must be "1.0")
196
203
  * - x-bsv-payment-satoshis-required (positive integer)
197
- * - x-bsv-auth-identity-key (non-empty)
204
+ * - x-bsv-auth-identity-key or x-bsv-payment-identity-key (compressed pubkey)
198
205
  * - x-bsv-payment-derivation-prefix (non-empty)
199
206
  */
200
207
  declare function parseBrc105Challenge(response: Response): Brc105Challenge;
@@ -209,7 +216,7 @@ declare function parseBrc105Challenge(response: Response): Brc105Challenge;
209
216
  * 4. Call wallet.createAction with the locking script and custom instructions
210
217
  * 5. Convert transaction to base64
211
218
  */
212
- declare function constructBrc105Proof(challenge: Brc105Challenge, wallet: Brc105Wallet): Promise<Brc105Proof>;
219
+ declare function constructBrc105Proof(challenge: Brc105Challenge, wallet: Brc105Wallet, origin?: string): Promise<Brc105Proof>;
213
220
 
214
221
  /** Anything with a numeric `amount` can be checked against spending limits. */
215
222
  type SpendCheckable = Challenge | PaymentRequest;
@@ -267,4 +274,4 @@ type SitePromptFn = (origin: string) => Promise<SitePolicyAction>;
267
274
  */
268
275
  declare function resolveSitePolicy(origin: string, limits: SpendLimits, twoFactorProvider?: TwoFactorProvider, promptFn?: SitePromptFn): Promise<SitePolicy>;
269
276
 
270
- export { BFG_DAILY_CEILING_SATOSHIS, BFG_PER_TX_CEILING_SATOSHIS, type Brc105Challenge, type Brc105Proof, type Brc105ProofConstructor, type Brc105Wallet, type Challenge, type LedgerEntry, type LimitCheckResult, type LimitState, LocalStorageAdapter, type PaymentProtocol, type PaymentRequest, type Proof, RateLimiter, type SitePolicy, type SitePolicyAction, type SpendCheckable, type SpendLimits, type SpendMode, type StorageAdapter, TIER_PRESETS, type TierName, type TierPreset, type TimeWindow, type TwoFactorAction, type TwoFactorPolicy, type TwoFactorProvider, WalletTwoFactorProvider, type WindowLimit, type X402Config, type X402FetchFn, type YellowLightEvent, constructBrc105Proof, createX402Fetch, parseBrc105Challenge, parseChallenge, resolveSitePolicy, resolveSpendLimits, x402Fetch };
277
+ export { BFG_DAILY_CEILING_SATOSHIS, BFG_PER_TX_CEILING_SATOSHIS, type BlockSeverity, type Brc105Challenge, type Brc105Proof, type Brc105ProofConstructor, type Brc105Wallet, type CWICreateActionOutput, type CWICreateActionParams, type CWICreateActionResult, type Challenge, type KeyDeriver, type LedgerEntry, type LimitCheckResult, type LimitState, LocalStorageAdapter, type PaymentProtocol, type PaymentRequest, type Proof, RateLimiter, type SitePolicy, type SitePolicyAction, type SitePromptFn, type SpendCheckable, type SpendLimits, type SpendMode, type StorageAdapter, TIER_PRESETS, type TierName, type TierPreset, type TimeWindow, type TwoFactorAction, type TwoFactorPolicy, type TwoFactorProvider, WalletTwoFactorProvider, type WindowLimit, type X402Config, type X402FetchFn, type YellowLightEvent, constructBrc105Proof, createX402Fetch, parseBrc105Challenge, parseChallenge, resolveSitePolicy, resolveSpendLimits, x402Fetch };
package/dist/index.js CHANGED
@@ -15,12 +15,15 @@ function parseBrc105Challenge(response) {
15
15
  if (!Number.isFinite(satoshisRequired) || !Number.isInteger(satoshisRequired) || satoshisRequired <= 0) {
16
16
  throw new Error("BRC-105: satoshis-required must be a positive integer");
17
17
  }
18
- const serverIdentityKey = response.headers.get("x-bsv-auth-identity-key");
18
+ const authIdentityKey = response.headers.get("x-bsv-auth-identity-key") || null;
19
+ const paymentIdentityKey = response.headers.get("x-bsv-payment-identity-key") || null;
20
+ const authenticated = authIdentityKey !== null && authIdentityKey.length > 0;
21
+ const serverIdentityKey = authIdentityKey || paymentIdentityKey;
19
22
  if (serverIdentityKey === null || serverIdentityKey.length === 0) {
20
- throw new Error("BRC-105: missing or empty x-bsv-auth-identity-key header");
23
+ throw new Error("BRC-105: missing identity key (expected x-bsv-auth-identity-key or x-bsv-payment-identity-key)");
21
24
  }
22
25
  if (!/^0[23][0-9a-fA-F]{64}$/.test(serverIdentityKey)) {
23
- throw new Error("BRC-105: x-bsv-auth-identity-key must be a 33-byte compressed public key (hex)");
26
+ throw new Error("BRC-105: identity key must be a 33-byte compressed public key (hex)");
24
27
  }
25
28
  const derivationPrefix = response.headers.get("x-bsv-payment-derivation-prefix");
26
29
  if (derivationPrefix === null || derivationPrefix.length === 0) {
@@ -30,7 +33,8 @@ function parseBrc105Challenge(response) {
30
33
  version,
31
34
  satoshisRequired,
32
35
  serverIdentityKey,
33
- derivationPrefix
36
+ derivationPrefix,
37
+ authenticated
34
38
  };
35
39
  }
36
40
 
@@ -478,7 +482,8 @@ async function createDerivationSuffix(wallet) {
478
482
  const nonceBytes = [...firstHalf, ...hmac];
479
483
  return numberArrayToBase64(nonceBytes);
480
484
  }
481
- async function constructBrc105Proof(challenge, wallet) {
485
+ async function constructBrc105Proof(challenge, wallet, origin) {
486
+ const { publicKey: clientIdentityKey } = await wallet.getPublicKey({ identityKey: true });
482
487
  const derivationSuffix = await createDerivationSuffix(wallet);
483
488
  const keyID = `${challenge.derivationPrefix} ${derivationSuffix}`;
484
489
  const { publicKey: derivedPublicKey } = await wallet.getPublicKey({
@@ -487,12 +492,13 @@ async function constructBrc105Proof(challenge, wallet) {
487
492
  counterparty: challenge.serverIdentityKey
488
493
  });
489
494
  const lockingScript = await pubkeyToP2PKHLockingScript(derivedPublicKey);
495
+ const description = origin ? `Payment for request to ${origin}` : "BRC-105 payment";
490
496
  const result = await wallet.createAction({
491
- description: "BRC-105 payment",
497
+ description,
492
498
  outputs: [{
493
499
  satoshis: challenge.satoshisRequired,
494
500
  lockingScript,
495
- description: "BRC-105 payment output",
501
+ outputDescription: "HTTP request payment",
496
502
  customInstructions: JSON.stringify({
497
503
  derivationPrefix: challenge.derivationPrefix,
498
504
  derivationSuffix,
@@ -515,6 +521,7 @@ async function constructBrc105Proof(challenge, wallet) {
515
521
  derivationPrefix: challenge.derivationPrefix,
516
522
  derivationSuffix,
517
523
  transaction: transactionBase64,
524
+ clientIdentityKey,
518
525
  txid: result.txid
519
526
  };
520
527
  }
@@ -1127,12 +1134,26 @@ function createX402Fetch(config = {}) {
1127
1134
  let proof;
1128
1135
  try {
1129
1136
  proof = await buildProof();
1130
- } catch {
1137
+ } catch (err) {
1138
+ console.error(`[x402] Proof construction failed (${protocol}):`, err);
1139
+ config.onProofError?.(err, protocol);
1131
1140
  return originalResponse;
1132
1141
  }
1133
1142
  rl.record(makeLedgerEntry(proof));
1134
1143
  await persist(rl);
1135
- return retryWithProof(proof);
1144
+ const maxAttempts = 3;
1145
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1146
+ try {
1147
+ return await retryWithProof(proof);
1148
+ } catch (err) {
1149
+ if (attempt >= maxAttempts) {
1150
+ console.error(`[x402] Paid request failed after ${maxAttempts} attempts (${protocol}):`, err);
1151
+ return originalResponse;
1152
+ }
1153
+ await new Promise((r) => setTimeout(r, 250 * Math.pow(2, attempt)));
1154
+ }
1155
+ }
1156
+ return originalResponse;
1136
1157
  });
1137
1158
  }
1138
1159
  const fetchFn = async function x402Fetch2(input, init) {
@@ -1188,11 +1209,12 @@ function createX402Fetch(config = {}) {
1188
1209
  if (brc105ProofConstructor) {
1189
1210
  return brc105ProofConstructor(brc105Challenge);
1190
1211
  }
1191
- return constructBrc105Proof(brc105Challenge, brc105Wallet);
1212
+ return constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
1192
1213
  },
1193
1214
  (proof) => {
1194
1215
  const headers = new Headers(init?.headers);
1195
1216
  headers.set("x-bsv-payment", JSON.stringify(proof));
1217
+ headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
1196
1218
  return fetch(input, { ...init, headers });
1197
1219
  },
1198
1220
  (proof) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bsv-x402",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "x402 payment protocol client — a fetch() wrapper that handles 402 payment flows transparently",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -25,19 +25,23 @@
25
25
  "build:firefox": "tsx plugins/build.ts --target firefox",
26
26
  "build:safari": "tsx plugins/build.ts --target safari",
27
27
  "build:all": "npm run build && npm run build:plugins",
28
- "demo": "npm run build && tsx demo/server.ts"
28
+ "demo": "npm run build && tsx demo/server.ts",
29
+ "docs": "typedoc",
30
+ "postinstall": "patch-package"
29
31
  },
30
32
  "repository": {
31
33
  "type": "git",
32
- "url": "https://github.com/sgbett/bsv-x402.git"
34
+ "url": "git+https://github.com/sgbett/bsv-x402.git"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@bsv/wallet-toolbox-client": "^2.1.17",
36
38
  "@types/chrome": "^0.1.38",
37
39
  "@types/express": "^5.0.6",
38
40
  "express": "^5.2.1",
41
+ "patch-package": "^8.0.1",
39
42
  "tsup": "^8.0.0",
40
43
  "tsx": "^4.21.0",
44
+ "typedoc": "^0.28.18",
41
45
  "typescript": "^5.0.0",
42
46
  "vitest": "^3.0.0"
43
47
  }