bsv-x402 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -596,6 +596,171 @@ function parseChallenge(header) {
596
596
  };
597
597
  }
598
598
 
599
+ // src/x402-fetch.ts
600
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
601
+ function base58DecodeCheck(address) {
602
+ let leadingZeros = 0;
603
+ for (const c of address) {
604
+ if (c === "1") leadingZeros++;
605
+ else break;
606
+ }
607
+ let n = BigInt(0);
608
+ for (const c of address) {
609
+ const i = BASE58_ALPHABET.indexOf(c);
610
+ if (i < 0) throw new Error(`Invalid Base58 character: ${c}`);
611
+ n = n * 58n + BigInt(i);
612
+ }
613
+ const hexFromBigint = n === 0n ? "" : n.toString(16);
614
+ const paddedHex = hexFromBigint.length % 2 ? "0" + hexFromBigint : hexFromBigint;
615
+ const bigintBytes = [];
616
+ for (let i = 0; i < paddedHex.length; i += 2) {
617
+ bigintBytes.push(parseInt(paddedHex.slice(i, i + 2), 16));
618
+ }
619
+ const allBytes = new Uint8Array(leadingZeros + bigintBytes.length);
620
+ allBytes.set(bigintBytes, leadingZeros);
621
+ if (allBytes.length !== 25) {
622
+ throw new Error(`Invalid address length: expected 25 bytes, got ${allBytes.length}`);
623
+ }
624
+ const body = allBytes.slice(0, 21);
625
+ const checksum = allBytes.slice(21);
626
+ const version = allBytes[0];
627
+ if (version !== 0 && version !== 111) {
628
+ throw new Error(`Unsupported address version: 0x${version.toString(16).padStart(2, "0")}`);
629
+ }
630
+ return { version, payload: body.slice(1) };
631
+ }
632
+ function payeeAddressToLockingScript(address) {
633
+ const { payload } = base58DecodeCheck(address);
634
+ if (payload.length !== 20) {
635
+ throw new Error(`Invalid pubkey hash length: expected 20 bytes, got ${payload.length}`);
636
+ }
637
+ const pubkeyHash = Array.from(payload).map((b) => b.toString(16).padStart(2, "0")).join("");
638
+ return `76a914${pubkeyHash}88ac`;
639
+ }
640
+ async function defaultConstructProof(challenge) {
641
+ const cwi = globalThis.CWI;
642
+ if (!cwi || typeof cwi.createAction !== "function") {
643
+ throw new Error(
644
+ "No BRC-100 wallet detected. Install a CWI-compliant browser extension or provide a custom proofConstructor in X402Config."
645
+ );
646
+ }
647
+ const result = await cwi.createAction({
648
+ description: `x402 payment: ${challenge.amount} sats to ${challenge.payee}`,
649
+ outputs: [{
650
+ satoshis: challenge.amount,
651
+ lockingScript: payeeAddressToLockingScript(challenge.payee),
652
+ description: `Payment to ${challenge.payee}`
653
+ }],
654
+ labels: ["x402-payment"],
655
+ options: {
656
+ returnTXIDOnly: false,
657
+ noSend: false
658
+ }
659
+ });
660
+ if (!result || !result.txid) {
661
+ throw new Error("Wallet declined payment or returned invalid result");
662
+ }
663
+ if (!result.rawTx || typeof result.rawTx !== "string" || result.rawTx.length === 0) {
664
+ throw new Error("Wallet did not return raw transaction");
665
+ }
666
+ return {
667
+ txid: result.txid,
668
+ rawTx: result.rawTx
669
+ };
670
+ }
671
+ function createX402Fetch(config = {}) {
672
+ const constructProof = config.proofConstructor ?? defaultConstructProof;
673
+ const brc105ProofConstructor = config.brc105ProofConstructor;
674
+ const brc105Wallet = config.brc105Wallet;
675
+ return async function x402Fetch2(input, init) {
676
+ const response = await fetch(input, init);
677
+ if (response.status !== 402) return response;
678
+ const origin = extractOrigin(input);
679
+ const challengeHeader = response.headers.get("X402-Challenge");
680
+ if (challengeHeader) {
681
+ let challenge;
682
+ try {
683
+ challenge = parseChallenge(challengeHeader);
684
+ } catch {
685
+ return response;
686
+ }
687
+ let proof;
688
+ try {
689
+ proof = await constructProof(challenge);
690
+ } catch (err) {
691
+ console.error("[x402] Proof construction failed (x402):", err);
692
+ config.onProofError?.(err, "x402");
693
+ return response;
694
+ }
695
+ const headers = new Headers(init?.headers);
696
+ headers.set("X402-Proof", JSON.stringify(proof));
697
+ return fetch(input, { ...init, headers });
698
+ }
699
+ const brc105Version = response.headers.get("x-bsv-payment-version");
700
+ if (brc105Version) {
701
+ if (!brc105ProofConstructor && !brc105Wallet) return response;
702
+ let brc105Challenge;
703
+ try {
704
+ brc105Challenge = parseBrc105Challenge(response);
705
+ } catch {
706
+ return response;
707
+ }
708
+ let proof;
709
+ try {
710
+ if (brc105ProofConstructor) {
711
+ proof = await brc105ProofConstructor(brc105Challenge);
712
+ } else {
713
+ proof = await constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
714
+ }
715
+ } catch (err) {
716
+ console.error("[x402] Proof construction failed (brc105):", err);
717
+ config.onProofError?.(err, "brc105");
718
+ return response;
719
+ }
720
+ const headers = new Headers(init?.headers);
721
+ headers.set("x-bsv-payment", JSON.stringify(proof));
722
+ headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
723
+ return fetch(input, { ...init, headers });
724
+ }
725
+ return response;
726
+ };
727
+ }
728
+ var singleton;
729
+ async function x402Fetch(input, init) {
730
+ if (!singleton) singleton = createX402Fetch();
731
+ return singleton(input, init);
732
+ }
733
+ function resolveRelativeUrl(url) {
734
+ const loc = globalThis.location;
735
+ if (loc?.href) {
736
+ return new URL(url, loc.href).origin;
737
+ }
738
+ return "unknown";
739
+ }
740
+ function extractOrigin(input) {
741
+ if (input instanceof URL) return input.origin;
742
+ if (typeof input === "string") {
743
+ try {
744
+ return new URL(input).origin;
745
+ } catch {
746
+ try {
747
+ return resolveRelativeUrl(input);
748
+ } catch {
749
+ return "unknown";
750
+ }
751
+ }
752
+ }
753
+ try {
754
+ return new URL(input.url).origin;
755
+ } catch {
756
+ try {
757
+ return resolveRelativeUrl(input.url);
758
+ } catch {
759
+ return "unknown";
760
+ }
761
+ }
762
+ }
763
+
599
764
  // src/limits.ts
600
765
  var BFG_DAILY_CEILING_SATOSHIS = 1e10;
601
766
  var BFG_PER_TX_CEILING_SATOSHIS = 1e9;
@@ -897,41 +1062,6 @@ var RateLimiter = class {
897
1062
  }
898
1063
  };
899
1064
 
900
- // src/site-policy.ts
901
- var defaultSitePrompt = async (origin) => {
902
- if (typeof globalThis.confirm !== "function") return "global";
903
- const allow = globalThis.confirm(
904
- `First payment to ${origin}.
905
-
906
- Use your global spending limits for this site?
907
-
908
- OK = Use global limits
909
- Cancel = Block this site`
910
- );
911
- return allow ? "global" : "block";
912
- };
913
- async function resolveSitePolicy(origin, limits, twoFactorProvider, promptFn = defaultSitePrompt) {
914
- const existing = limits.sitePolicies[origin];
915
- if (existing) return existing;
916
- if (!limits.requirePerSitePrompt) {
917
- return { origin, action: "global" };
918
- }
919
- if (limits.require2fa.onNewSiteApproval) {
920
- if (!twoFactorProvider) {
921
- return { origin, action: "block" };
922
- }
923
- const verified = await twoFactorProvider.verify({
924
- type: "new-site-approval",
925
- origin
926
- });
927
- if (!verified) {
928
- return { origin, action: "block" };
929
- }
930
- }
931
- const action = await promptFn(origin);
932
- return { origin, action };
933
- }
934
-
935
1065
  // src/storage.ts
936
1066
  var STATE_KEY = "x402:limit-state";
937
1067
  var POLICIES_KEY = "x402:site-policies";
@@ -1006,319 +1136,6 @@ var LocalStorageAdapter = class {
1006
1136
  }
1007
1137
  };
1008
1138
 
1009
- // src/x402-fetch.ts
1010
- var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1011
- function base58DecodeCheck(address) {
1012
- let leadingZeros = 0;
1013
- for (const c of address) {
1014
- if (c === "1") leadingZeros++;
1015
- else break;
1016
- }
1017
- let n = BigInt(0);
1018
- for (const c of address) {
1019
- const i = BASE58_ALPHABET.indexOf(c);
1020
- if (i < 0) throw new Error(`Invalid Base58 character: ${c}`);
1021
- n = n * 58n + BigInt(i);
1022
- }
1023
- const hexFromBigint = n === 0n ? "" : n.toString(16);
1024
- const paddedHex = hexFromBigint.length % 2 ? "0" + hexFromBigint : hexFromBigint;
1025
- const bigintBytes = [];
1026
- for (let i = 0; i < paddedHex.length; i += 2) {
1027
- bigintBytes.push(parseInt(paddedHex.slice(i, i + 2), 16));
1028
- }
1029
- const allBytes = new Uint8Array(leadingZeros + bigintBytes.length);
1030
- allBytes.set(bigintBytes, leadingZeros);
1031
- if (allBytes.length !== 25) {
1032
- throw new Error(`Invalid address length: expected 25 bytes, got ${allBytes.length}`);
1033
- }
1034
- const body = allBytes.slice(0, 21);
1035
- const checksum = allBytes.slice(21);
1036
- const version = allBytes[0];
1037
- if (version !== 0 && version !== 111) {
1038
- throw new Error(`Unsupported address version: 0x${version.toString(16).padStart(2, "0")}`);
1039
- }
1040
- return { version, payload: body.slice(1) };
1041
- }
1042
- function payeeAddressToLockingScript(address) {
1043
- const { payload } = base58DecodeCheck(address);
1044
- if (payload.length !== 20) {
1045
- throw new Error(`Invalid pubkey hash length: expected 20 bytes, got ${payload.length}`);
1046
- }
1047
- const pubkeyHash = Array.from(payload).map((b) => b.toString(16).padStart(2, "0")).join("");
1048
- return `76a914${pubkeyHash}88ac`;
1049
- }
1050
- async function defaultConstructProof(challenge) {
1051
- const cwi = globalThis.CWI;
1052
- if (!cwi || typeof cwi.createAction !== "function") {
1053
- throw new Error(
1054
- "No BRC-100 wallet detected. Install a CWI-compliant browser extension or provide a custom proofConstructor in X402Config."
1055
- );
1056
- }
1057
- const result = await cwi.createAction({
1058
- description: `x402 payment: ${challenge.amount} sats to ${challenge.payee}`,
1059
- outputs: [{
1060
- satoshis: challenge.amount,
1061
- lockingScript: payeeAddressToLockingScript(challenge.payee),
1062
- description: `Payment to ${challenge.payee}`
1063
- }],
1064
- labels: ["x402-payment"],
1065
- options: {
1066
- returnTXIDOnly: false,
1067
- noSend: false
1068
- }
1069
- });
1070
- if (!result || !result.txid) {
1071
- throw new Error("Wallet declined payment or returned invalid result");
1072
- }
1073
- if (!result.rawTx || typeof result.rawTx !== "string" || result.rawTx.length === 0) {
1074
- throw new Error("Wallet did not return raw transaction");
1075
- }
1076
- return {
1077
- txid: result.txid,
1078
- rawTx: result.rawTx
1079
- };
1080
- }
1081
- function createMutex() {
1082
- let chain = Promise.resolve();
1083
- return (fn) => {
1084
- const result = chain.then(fn, fn);
1085
- chain = result.then(() => {
1086
- }, () => {
1087
- });
1088
- return result;
1089
- };
1090
- }
1091
- function createX402Fetch(config = {}) {
1092
- const tier = config.tier ?? "Hey, Not Too Rough";
1093
- const mode = config.mode ?? "interactive";
1094
- if (tier === "Nightmare!" && config.nightmareConfirmation !== "NIGHTMARE") {
1095
- throw new Error('Nightmare! tier requires nightmareConfirmation: "NIGHTMARE"');
1096
- }
1097
- const limits = resolveSpendLimits(tier, mode, config.limits);
1098
- const storage = config.storage ?? new LocalStorageAdapter();
1099
- const twoFactor = config.twoFactorProvider;
1100
- const constructProof = config.proofConstructor ?? defaultConstructProof;
1101
- const brc105ProofConstructor = config.brc105ProofConstructor;
1102
- const brc105Wallet = config.brc105Wallet;
1103
- const nowFn = config.now ?? Date.now;
1104
- const mutex = createMutex();
1105
- const needs2fa = limits.require2fa;
1106
- if (!twoFactor && (needs2fa.onCircuitBreakerReset || needs2fa.onHighValueTx || needs2fa.onNewSiteApproval || needs2fa.onTierChange)) {
1107
- console.warn("x402: tier requires 2FA but no twoFactorProvider configured \u2014 2FA-gated actions will be blocked");
1108
- }
1109
- let limiter;
1110
- let initialised = false;
1111
- async function ensureInitialised() {
1112
- if (limiter && initialised) return limiter;
1113
- const state = await storage.load();
1114
- limiter = new RateLimiter(limits, state ?? void 0, nowFn);
1115
- const policies = await storage.loadSitePolicies();
1116
- Object.assign(limits.sitePolicies, policies);
1117
- initialised = true;
1118
- return limiter;
1119
- }
1120
- async function persist(rl) {
1121
- await storage.save(rl.getState());
1122
- await storage.saveSitePolicies(limits.sitePolicies);
1123
- }
1124
- async function handlePaymentFlow(originalResponse, input, init, origin, amount, protocol, buildProof, retryWithProof, makeLedgerEntry) {
1125
- return mutex(async () => {
1126
- const rl = await ensureInitialised();
1127
- const sitePolicy = await resolveSitePolicy(origin, limits, twoFactor);
1128
- if (sitePolicy.action === "block") return originalResponse;
1129
- if (!limits.sitePolicies[origin]) {
1130
- limits.sitePolicies[origin] = sitePolicy;
1131
- await storage.saveSitePolicies(limits.sitePolicies);
1132
- }
1133
- const spendCheckable = { amount, origin, protocol };
1134
- const result = rl.check(spendCheckable, origin);
1135
- if (result.action === "block") {
1136
- if (result.severity === "trip") {
1137
- rl.trip();
1138
- await persist(rl);
1139
- config.onLimitReached?.(result.reason);
1140
- return originalResponse;
1141
- }
1142
- if (result.severity === "window" && twoFactor) {
1143
- config.onLimitReached?.(result.reason);
1144
- const override = await twoFactor.verify({
1145
- type: "limit-override",
1146
- amount,
1147
- origin,
1148
- reason: result.reason
1149
- });
1150
- if (override) {
1151
- } else {
1152
- return originalResponse;
1153
- }
1154
- } else {
1155
- config.onLimitReached?.(result.reason);
1156
- return originalResponse;
1157
- }
1158
- }
1159
- if (result.action === "yellow-light") {
1160
- const proceed = config.onYellowLight ? await config.onYellowLight(result.detail) : false;
1161
- if (!proceed) return originalResponse;
1162
- }
1163
- if (limits.require2fa.onHighValueTx && amount > limits.require2fa.highValueThreshold) {
1164
- if (!twoFactor) return originalResponse;
1165
- const verified = await twoFactor.verify({
1166
- type: "high-value-tx",
1167
- amount,
1168
- origin
1169
- });
1170
- if (!verified) return originalResponse;
1171
- }
1172
- let proof;
1173
- try {
1174
- proof = await buildProof();
1175
- } catch (err) {
1176
- console.error(`[x402] Proof construction failed (${protocol}):`, err);
1177
- config.onProofError?.(err, protocol);
1178
- return originalResponse;
1179
- }
1180
- rl.record(makeLedgerEntry(proof));
1181
- await persist(rl);
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;
1195
- });
1196
- }
1197
- const fetchFn = async function x402Fetch2(input, init) {
1198
- const response = await fetch(input, init);
1199
- if (response.status !== 402) return response;
1200
- const origin = extractOrigin(input);
1201
- const challengeHeader = response.headers.get("X402-Challenge");
1202
- if (challengeHeader) {
1203
- let challenge;
1204
- try {
1205
- challenge = parseChallenge(challengeHeader);
1206
- } catch {
1207
- return response;
1208
- }
1209
- return handlePaymentFlow(
1210
- response,
1211
- input,
1212
- init,
1213
- origin,
1214
- challenge.amount,
1215
- "x402",
1216
- async () => constructProof(challenge),
1217
- (proof) => {
1218
- const headers = new Headers(init?.headers);
1219
- headers.set("X402-Proof", JSON.stringify(proof));
1220
- return fetch(input, { ...init, headers });
1221
- },
1222
- (proof) => ({
1223
- timestamp: nowFn(),
1224
- origin,
1225
- satoshis: challenge.amount,
1226
- txid: proof.txid
1227
- })
1228
- );
1229
- }
1230
- const brc105Version = response.headers.get("x-bsv-payment-version");
1231
- if (brc105Version) {
1232
- if (!brc105ProofConstructor && !brc105Wallet) return response;
1233
- let brc105Challenge;
1234
- try {
1235
- brc105Challenge = parseBrc105Challenge(response);
1236
- } catch {
1237
- return response;
1238
- }
1239
- return handlePaymentFlow(
1240
- response,
1241
- input,
1242
- init,
1243
- origin,
1244
- brc105Challenge.satoshisRequired,
1245
- "brc105",
1246
- async () => {
1247
- if (brc105ProofConstructor) {
1248
- return brc105ProofConstructor(brc105Challenge);
1249
- }
1250
- return constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
1251
- },
1252
- (proof) => {
1253
- const headers = new Headers(init?.headers);
1254
- headers.set("x-bsv-payment", JSON.stringify(proof));
1255
- headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
1256
- return fetch(input, { ...init, headers });
1257
- },
1258
- (proof) => ({
1259
- timestamp: nowFn(),
1260
- origin,
1261
- satoshis: brc105Challenge.satoshisRequired,
1262
- txid: proof.txid,
1263
- protocol: "brc105"
1264
- })
1265
- );
1266
- }
1267
- return response;
1268
- };
1269
- fetchFn.resetLimits = async () => {
1270
- const rl = await ensureInitialised();
1271
- if (limits.require2fa.onCircuitBreakerReset) {
1272
- if (!twoFactor) throw new Error("2FA required for circuit breaker reset but no twoFactorProvider configured");
1273
- const verified = await twoFactor.verify({ type: "circuit-breaker-reset" });
1274
- if (!verified) throw new Error("2FA verification failed for circuit breaker reset");
1275
- }
1276
- rl.reset();
1277
- await persist(rl);
1278
- };
1279
- fetchFn.getState = () => {
1280
- if (!limiter) return { entries: [], circuitBroken: false };
1281
- const state = limiter.getState();
1282
- return { entries: state.entries, circuitBroken: state.circuitBroken };
1283
- };
1284
- return fetchFn;
1285
- }
1286
- var singleton;
1287
- async function x402Fetch(input, init) {
1288
- if (!singleton) singleton = createX402Fetch();
1289
- return singleton(input, init);
1290
- }
1291
- function resolveRelativeUrl(url) {
1292
- const loc = globalThis.location;
1293
- if (loc?.href) {
1294
- return new URL(url, loc.href).origin;
1295
- }
1296
- return "unknown";
1297
- }
1298
- function extractOrigin(input) {
1299
- if (input instanceof URL) return input.origin;
1300
- if (typeof input === "string") {
1301
- try {
1302
- return new URL(input).origin;
1303
- } catch {
1304
- try {
1305
- return resolveRelativeUrl(input);
1306
- } catch {
1307
- return "unknown";
1308
- }
1309
- }
1310
- }
1311
- try {
1312
- return new URL(input.url).origin;
1313
- } catch {
1314
- try {
1315
- return resolveRelativeUrl(input.url);
1316
- } catch {
1317
- return "unknown";
1318
- }
1319
- }
1320
- }
1321
-
1322
1139
  // src/two-factor.ts
1323
1140
  var WalletTwoFactorProvider = class {
1324
1141
  async verify(action) {
@@ -1361,6 +1178,41 @@ function describeAction(action) {
1361
1178
  Allow this payment of ${action.amount} sats to ${action.origin}?`;
1362
1179
  }
1363
1180
  }
1181
+
1182
+ // src/site-policy.ts
1183
+ var defaultSitePrompt = async (origin) => {
1184
+ if (typeof globalThis.confirm !== "function") return "global";
1185
+ const allow = globalThis.confirm(
1186
+ `First payment to ${origin}.
1187
+
1188
+ Use your global spending limits for this site?
1189
+
1190
+ OK = Use global limits
1191
+ Cancel = Block this site`
1192
+ );
1193
+ return allow ? "global" : "block";
1194
+ };
1195
+ async function resolveSitePolicy(origin, limits, twoFactorProvider, promptFn = defaultSitePrompt) {
1196
+ const existing = limits.sitePolicies[origin];
1197
+ if (existing) return existing;
1198
+ if (!limits.requirePerSitePrompt) {
1199
+ return { origin, action: "global" };
1200
+ }
1201
+ if (limits.require2fa.onNewSiteApproval) {
1202
+ if (!twoFactorProvider) {
1203
+ return { origin, action: "block" };
1204
+ }
1205
+ const verified = await twoFactorProvider.verify({
1206
+ type: "new-site-approval",
1207
+ origin
1208
+ });
1209
+ if (!verified) {
1210
+ return { origin, action: "block" };
1211
+ }
1212
+ }
1213
+ const action = await promptFn(origin);
1214
+ return { origin, action };
1215
+ }
1364
1216
  // Annotate the CommonJS export names for ESM import in node:
1365
1217
  0 && (module.exports = {
1366
1218
  BFG_DAILY_CEILING_SATOSHIS,
package/dist/index.d.cts CHANGED
@@ -87,19 +87,10 @@ interface TierPreset {
87
87
  programmatic: SpendLimits;
88
88
  }
89
89
  interface X402Config {
90
- tier?: TierName;
91
- mode?: SpendMode;
92
- limits?: Partial<SpendLimits>;
93
- storage?: StorageAdapter;
94
- twoFactorProvider?: TwoFactorProvider;
95
90
  proofConstructor?: (challenge: Challenge) => Promise<Proof>;
96
91
  brc105ProofConstructor?: Brc105ProofConstructor;
97
92
  brc105Wallet?: Brc105Wallet;
98
- nightmareConfirmation?: string;
99
- onLimitReached?: (reason: string) => void;
100
- onYellowLight?: (detail: YellowLightEvent) => Promise<boolean>;
101
93
  onProofError?: (error: unknown, protocol: PaymentProtocol) => void;
102
- now?: () => number;
103
94
  }
104
95
  interface YellowLightEvent {
105
96
  origin: string;
@@ -182,14 +173,7 @@ interface TwoFactorProvider {
182
173
  verify(action: TwoFactorAction): Promise<boolean>;
183
174
  }
184
175
 
185
- interface X402FetchFn {
186
- (input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
187
- resetLimits(): Promise<void>;
188
- getState(): {
189
- entries: unknown[];
190
- circuitBroken: boolean;
191
- };
192
- }
176
+ type X402FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
193
177
  declare function createX402Fetch(config?: X402Config): X402FetchFn;
194
178
  declare function x402Fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
195
179
 
package/dist/index.d.ts CHANGED
@@ -87,19 +87,10 @@ interface TierPreset {
87
87
  programmatic: SpendLimits;
88
88
  }
89
89
  interface X402Config {
90
- tier?: TierName;
91
- mode?: SpendMode;
92
- limits?: Partial<SpendLimits>;
93
- storage?: StorageAdapter;
94
- twoFactorProvider?: TwoFactorProvider;
95
90
  proofConstructor?: (challenge: Challenge) => Promise<Proof>;
96
91
  brc105ProofConstructor?: Brc105ProofConstructor;
97
92
  brc105Wallet?: Brc105Wallet;
98
- nightmareConfirmation?: string;
99
- onLimitReached?: (reason: string) => void;
100
- onYellowLight?: (detail: YellowLightEvent) => Promise<boolean>;
101
93
  onProofError?: (error: unknown, protocol: PaymentProtocol) => void;
102
- now?: () => number;
103
94
  }
104
95
  interface YellowLightEvent {
105
96
  origin: string;
@@ -182,14 +173,7 @@ interface TwoFactorProvider {
182
173
  verify(action: TwoFactorAction): Promise<boolean>;
183
174
  }
184
175
 
185
- interface X402FetchFn {
186
- (input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
187
- resetLimits(): Promise<void>;
188
- getState(): {
189
- entries: unknown[];
190
- circuitBroken: boolean;
191
- };
192
- }
176
+ type X402FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
193
177
  declare function createX402Fetch(config?: X402Config): X402FetchFn;
194
178
  declare function x402Fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
195
179
 
package/dist/index.js CHANGED
@@ -558,6 +558,171 @@ function parseChallenge(header) {
558
558
  };
559
559
  }
560
560
 
561
+ // src/x402-fetch.ts
562
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
563
+ function base58DecodeCheck(address) {
564
+ let leadingZeros = 0;
565
+ for (const c of address) {
566
+ if (c === "1") leadingZeros++;
567
+ else break;
568
+ }
569
+ let n = BigInt(0);
570
+ for (const c of address) {
571
+ const i = BASE58_ALPHABET.indexOf(c);
572
+ if (i < 0) throw new Error(`Invalid Base58 character: ${c}`);
573
+ n = n * 58n + BigInt(i);
574
+ }
575
+ const hexFromBigint = n === 0n ? "" : n.toString(16);
576
+ const paddedHex = hexFromBigint.length % 2 ? "0" + hexFromBigint : hexFromBigint;
577
+ const bigintBytes = [];
578
+ for (let i = 0; i < paddedHex.length; i += 2) {
579
+ bigintBytes.push(parseInt(paddedHex.slice(i, i + 2), 16));
580
+ }
581
+ const allBytes = new Uint8Array(leadingZeros + bigintBytes.length);
582
+ allBytes.set(bigintBytes, leadingZeros);
583
+ if (allBytes.length !== 25) {
584
+ throw new Error(`Invalid address length: expected 25 bytes, got ${allBytes.length}`);
585
+ }
586
+ const body = allBytes.slice(0, 21);
587
+ const checksum = allBytes.slice(21);
588
+ const version = allBytes[0];
589
+ if (version !== 0 && version !== 111) {
590
+ throw new Error(`Unsupported address version: 0x${version.toString(16).padStart(2, "0")}`);
591
+ }
592
+ return { version, payload: body.slice(1) };
593
+ }
594
+ function payeeAddressToLockingScript(address) {
595
+ const { payload } = base58DecodeCheck(address);
596
+ if (payload.length !== 20) {
597
+ throw new Error(`Invalid pubkey hash length: expected 20 bytes, got ${payload.length}`);
598
+ }
599
+ const pubkeyHash = Array.from(payload).map((b) => b.toString(16).padStart(2, "0")).join("");
600
+ return `76a914${pubkeyHash}88ac`;
601
+ }
602
+ async function defaultConstructProof(challenge) {
603
+ const cwi = globalThis.CWI;
604
+ if (!cwi || typeof cwi.createAction !== "function") {
605
+ throw new Error(
606
+ "No BRC-100 wallet detected. Install a CWI-compliant browser extension or provide a custom proofConstructor in X402Config."
607
+ );
608
+ }
609
+ const result = await cwi.createAction({
610
+ description: `x402 payment: ${challenge.amount} sats to ${challenge.payee}`,
611
+ outputs: [{
612
+ satoshis: challenge.amount,
613
+ lockingScript: payeeAddressToLockingScript(challenge.payee),
614
+ description: `Payment to ${challenge.payee}`
615
+ }],
616
+ labels: ["x402-payment"],
617
+ options: {
618
+ returnTXIDOnly: false,
619
+ noSend: false
620
+ }
621
+ });
622
+ if (!result || !result.txid) {
623
+ throw new Error("Wallet declined payment or returned invalid result");
624
+ }
625
+ if (!result.rawTx || typeof result.rawTx !== "string" || result.rawTx.length === 0) {
626
+ throw new Error("Wallet did not return raw transaction");
627
+ }
628
+ return {
629
+ txid: result.txid,
630
+ rawTx: result.rawTx
631
+ };
632
+ }
633
+ function createX402Fetch(config = {}) {
634
+ const constructProof = config.proofConstructor ?? defaultConstructProof;
635
+ const brc105ProofConstructor = config.brc105ProofConstructor;
636
+ const brc105Wallet = config.brc105Wallet;
637
+ return async function x402Fetch2(input, init) {
638
+ const response = await fetch(input, init);
639
+ if (response.status !== 402) return response;
640
+ const origin = extractOrigin(input);
641
+ const challengeHeader = response.headers.get("X402-Challenge");
642
+ if (challengeHeader) {
643
+ let challenge;
644
+ try {
645
+ challenge = parseChallenge(challengeHeader);
646
+ } catch {
647
+ return response;
648
+ }
649
+ let proof;
650
+ try {
651
+ proof = await constructProof(challenge);
652
+ } catch (err) {
653
+ console.error("[x402] Proof construction failed (x402):", err);
654
+ config.onProofError?.(err, "x402");
655
+ return response;
656
+ }
657
+ const headers = new Headers(init?.headers);
658
+ headers.set("X402-Proof", JSON.stringify(proof));
659
+ return fetch(input, { ...init, headers });
660
+ }
661
+ const brc105Version = response.headers.get("x-bsv-payment-version");
662
+ if (brc105Version) {
663
+ if (!brc105ProofConstructor && !brc105Wallet) return response;
664
+ let brc105Challenge;
665
+ try {
666
+ brc105Challenge = parseBrc105Challenge(response);
667
+ } catch {
668
+ return response;
669
+ }
670
+ let proof;
671
+ try {
672
+ if (brc105ProofConstructor) {
673
+ proof = await brc105ProofConstructor(brc105Challenge);
674
+ } else {
675
+ proof = await constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
676
+ }
677
+ } catch (err) {
678
+ console.error("[x402] Proof construction failed (brc105):", err);
679
+ config.onProofError?.(err, "brc105");
680
+ return response;
681
+ }
682
+ const headers = new Headers(init?.headers);
683
+ headers.set("x-bsv-payment", JSON.stringify(proof));
684
+ headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
685
+ return fetch(input, { ...init, headers });
686
+ }
687
+ return response;
688
+ };
689
+ }
690
+ var singleton;
691
+ async function x402Fetch(input, init) {
692
+ if (!singleton) singleton = createX402Fetch();
693
+ return singleton(input, init);
694
+ }
695
+ function resolveRelativeUrl(url) {
696
+ const loc = globalThis.location;
697
+ if (loc?.href) {
698
+ return new URL(url, loc.href).origin;
699
+ }
700
+ return "unknown";
701
+ }
702
+ function extractOrigin(input) {
703
+ if (input instanceof URL) return input.origin;
704
+ if (typeof input === "string") {
705
+ try {
706
+ return new URL(input).origin;
707
+ } catch {
708
+ try {
709
+ return resolveRelativeUrl(input);
710
+ } catch {
711
+ return "unknown";
712
+ }
713
+ }
714
+ }
715
+ try {
716
+ return new URL(input.url).origin;
717
+ } catch {
718
+ try {
719
+ return resolveRelativeUrl(input.url);
720
+ } catch {
721
+ return "unknown";
722
+ }
723
+ }
724
+ }
725
+
561
726
  // src/limits.ts
562
727
  var BFG_DAILY_CEILING_SATOSHIS = 1e10;
563
728
  var BFG_PER_TX_CEILING_SATOSHIS = 1e9;
@@ -859,41 +1024,6 @@ var RateLimiter = class {
859
1024
  }
860
1025
  };
861
1026
 
862
- // src/site-policy.ts
863
- var defaultSitePrompt = async (origin) => {
864
- if (typeof globalThis.confirm !== "function") return "global";
865
- const allow = globalThis.confirm(
866
- `First payment to ${origin}.
867
-
868
- Use your global spending limits for this site?
869
-
870
- OK = Use global limits
871
- Cancel = Block this site`
872
- );
873
- return allow ? "global" : "block";
874
- };
875
- async function resolveSitePolicy(origin, limits, twoFactorProvider, promptFn = defaultSitePrompt) {
876
- const existing = limits.sitePolicies[origin];
877
- if (existing) return existing;
878
- if (!limits.requirePerSitePrompt) {
879
- return { origin, action: "global" };
880
- }
881
- if (limits.require2fa.onNewSiteApproval) {
882
- if (!twoFactorProvider) {
883
- return { origin, action: "block" };
884
- }
885
- const verified = await twoFactorProvider.verify({
886
- type: "new-site-approval",
887
- origin
888
- });
889
- if (!verified) {
890
- return { origin, action: "block" };
891
- }
892
- }
893
- const action = await promptFn(origin);
894
- return { origin, action };
895
- }
896
-
897
1027
  // src/storage.ts
898
1028
  var STATE_KEY = "x402:limit-state";
899
1029
  var POLICIES_KEY = "x402:site-policies";
@@ -968,319 +1098,6 @@ var LocalStorageAdapter = class {
968
1098
  }
969
1099
  };
970
1100
 
971
- // src/x402-fetch.ts
972
- var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
973
- function base58DecodeCheck(address) {
974
- let leadingZeros = 0;
975
- for (const c of address) {
976
- if (c === "1") leadingZeros++;
977
- else break;
978
- }
979
- let n = BigInt(0);
980
- for (const c of address) {
981
- const i = BASE58_ALPHABET.indexOf(c);
982
- if (i < 0) throw new Error(`Invalid Base58 character: ${c}`);
983
- n = n * 58n + BigInt(i);
984
- }
985
- const hexFromBigint = n === 0n ? "" : n.toString(16);
986
- const paddedHex = hexFromBigint.length % 2 ? "0" + hexFromBigint : hexFromBigint;
987
- const bigintBytes = [];
988
- for (let i = 0; i < paddedHex.length; i += 2) {
989
- bigintBytes.push(parseInt(paddedHex.slice(i, i + 2), 16));
990
- }
991
- const allBytes = new Uint8Array(leadingZeros + bigintBytes.length);
992
- allBytes.set(bigintBytes, leadingZeros);
993
- if (allBytes.length !== 25) {
994
- throw new Error(`Invalid address length: expected 25 bytes, got ${allBytes.length}`);
995
- }
996
- const body = allBytes.slice(0, 21);
997
- const checksum = allBytes.slice(21);
998
- const version = allBytes[0];
999
- if (version !== 0 && version !== 111) {
1000
- throw new Error(`Unsupported address version: 0x${version.toString(16).padStart(2, "0")}`);
1001
- }
1002
- return { version, payload: body.slice(1) };
1003
- }
1004
- function payeeAddressToLockingScript(address) {
1005
- const { payload } = base58DecodeCheck(address);
1006
- if (payload.length !== 20) {
1007
- throw new Error(`Invalid pubkey hash length: expected 20 bytes, got ${payload.length}`);
1008
- }
1009
- const pubkeyHash = Array.from(payload).map((b) => b.toString(16).padStart(2, "0")).join("");
1010
- return `76a914${pubkeyHash}88ac`;
1011
- }
1012
- async function defaultConstructProof(challenge) {
1013
- const cwi = globalThis.CWI;
1014
- if (!cwi || typeof cwi.createAction !== "function") {
1015
- throw new Error(
1016
- "No BRC-100 wallet detected. Install a CWI-compliant browser extension or provide a custom proofConstructor in X402Config."
1017
- );
1018
- }
1019
- const result = await cwi.createAction({
1020
- description: `x402 payment: ${challenge.amount} sats to ${challenge.payee}`,
1021
- outputs: [{
1022
- satoshis: challenge.amount,
1023
- lockingScript: payeeAddressToLockingScript(challenge.payee),
1024
- description: `Payment to ${challenge.payee}`
1025
- }],
1026
- labels: ["x402-payment"],
1027
- options: {
1028
- returnTXIDOnly: false,
1029
- noSend: false
1030
- }
1031
- });
1032
- if (!result || !result.txid) {
1033
- throw new Error("Wallet declined payment or returned invalid result");
1034
- }
1035
- if (!result.rawTx || typeof result.rawTx !== "string" || result.rawTx.length === 0) {
1036
- throw new Error("Wallet did not return raw transaction");
1037
- }
1038
- return {
1039
- txid: result.txid,
1040
- rawTx: result.rawTx
1041
- };
1042
- }
1043
- function createMutex() {
1044
- let chain = Promise.resolve();
1045
- return (fn) => {
1046
- const result = chain.then(fn, fn);
1047
- chain = result.then(() => {
1048
- }, () => {
1049
- });
1050
- return result;
1051
- };
1052
- }
1053
- function createX402Fetch(config = {}) {
1054
- const tier = config.tier ?? "Hey, Not Too Rough";
1055
- const mode = config.mode ?? "interactive";
1056
- if (tier === "Nightmare!" && config.nightmareConfirmation !== "NIGHTMARE") {
1057
- throw new Error('Nightmare! tier requires nightmareConfirmation: "NIGHTMARE"');
1058
- }
1059
- const limits = resolveSpendLimits(tier, mode, config.limits);
1060
- const storage = config.storage ?? new LocalStorageAdapter();
1061
- const twoFactor = config.twoFactorProvider;
1062
- const constructProof = config.proofConstructor ?? defaultConstructProof;
1063
- const brc105ProofConstructor = config.brc105ProofConstructor;
1064
- const brc105Wallet = config.brc105Wallet;
1065
- const nowFn = config.now ?? Date.now;
1066
- const mutex = createMutex();
1067
- const needs2fa = limits.require2fa;
1068
- if (!twoFactor && (needs2fa.onCircuitBreakerReset || needs2fa.onHighValueTx || needs2fa.onNewSiteApproval || needs2fa.onTierChange)) {
1069
- console.warn("x402: tier requires 2FA but no twoFactorProvider configured \u2014 2FA-gated actions will be blocked");
1070
- }
1071
- let limiter;
1072
- let initialised = false;
1073
- async function ensureInitialised() {
1074
- if (limiter && initialised) return limiter;
1075
- const state = await storage.load();
1076
- limiter = new RateLimiter(limits, state ?? void 0, nowFn);
1077
- const policies = await storage.loadSitePolicies();
1078
- Object.assign(limits.sitePolicies, policies);
1079
- initialised = true;
1080
- return limiter;
1081
- }
1082
- async function persist(rl) {
1083
- await storage.save(rl.getState());
1084
- await storage.saveSitePolicies(limits.sitePolicies);
1085
- }
1086
- async function handlePaymentFlow(originalResponse, input, init, origin, amount, protocol, buildProof, retryWithProof, makeLedgerEntry) {
1087
- return mutex(async () => {
1088
- const rl = await ensureInitialised();
1089
- const sitePolicy = await resolveSitePolicy(origin, limits, twoFactor);
1090
- if (sitePolicy.action === "block") return originalResponse;
1091
- if (!limits.sitePolicies[origin]) {
1092
- limits.sitePolicies[origin] = sitePolicy;
1093
- await storage.saveSitePolicies(limits.sitePolicies);
1094
- }
1095
- const spendCheckable = { amount, origin, protocol };
1096
- const result = rl.check(spendCheckable, origin);
1097
- if (result.action === "block") {
1098
- if (result.severity === "trip") {
1099
- rl.trip();
1100
- await persist(rl);
1101
- config.onLimitReached?.(result.reason);
1102
- return originalResponse;
1103
- }
1104
- if (result.severity === "window" && twoFactor) {
1105
- config.onLimitReached?.(result.reason);
1106
- const override = await twoFactor.verify({
1107
- type: "limit-override",
1108
- amount,
1109
- origin,
1110
- reason: result.reason
1111
- });
1112
- if (override) {
1113
- } else {
1114
- return originalResponse;
1115
- }
1116
- } else {
1117
- config.onLimitReached?.(result.reason);
1118
- return originalResponse;
1119
- }
1120
- }
1121
- if (result.action === "yellow-light") {
1122
- const proceed = config.onYellowLight ? await config.onYellowLight(result.detail) : false;
1123
- if (!proceed) return originalResponse;
1124
- }
1125
- if (limits.require2fa.onHighValueTx && amount > limits.require2fa.highValueThreshold) {
1126
- if (!twoFactor) return originalResponse;
1127
- const verified = await twoFactor.verify({
1128
- type: "high-value-tx",
1129
- amount,
1130
- origin
1131
- });
1132
- if (!verified) return originalResponse;
1133
- }
1134
- let proof;
1135
- try {
1136
- proof = await buildProof();
1137
- } catch (err) {
1138
- console.error(`[x402] Proof construction failed (${protocol}):`, err);
1139
- config.onProofError?.(err, protocol);
1140
- return originalResponse;
1141
- }
1142
- rl.record(makeLedgerEntry(proof));
1143
- await persist(rl);
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;
1157
- });
1158
- }
1159
- const fetchFn = async function x402Fetch2(input, init) {
1160
- const response = await fetch(input, init);
1161
- if (response.status !== 402) return response;
1162
- const origin = extractOrigin(input);
1163
- const challengeHeader = response.headers.get("X402-Challenge");
1164
- if (challengeHeader) {
1165
- let challenge;
1166
- try {
1167
- challenge = parseChallenge(challengeHeader);
1168
- } catch {
1169
- return response;
1170
- }
1171
- return handlePaymentFlow(
1172
- response,
1173
- input,
1174
- init,
1175
- origin,
1176
- challenge.amount,
1177
- "x402",
1178
- async () => constructProof(challenge),
1179
- (proof) => {
1180
- const headers = new Headers(init?.headers);
1181
- headers.set("X402-Proof", JSON.stringify(proof));
1182
- return fetch(input, { ...init, headers });
1183
- },
1184
- (proof) => ({
1185
- timestamp: nowFn(),
1186
- origin,
1187
- satoshis: challenge.amount,
1188
- txid: proof.txid
1189
- })
1190
- );
1191
- }
1192
- const brc105Version = response.headers.get("x-bsv-payment-version");
1193
- if (brc105Version) {
1194
- if (!brc105ProofConstructor && !brc105Wallet) return response;
1195
- let brc105Challenge;
1196
- try {
1197
- brc105Challenge = parseBrc105Challenge(response);
1198
- } catch {
1199
- return response;
1200
- }
1201
- return handlePaymentFlow(
1202
- response,
1203
- input,
1204
- init,
1205
- origin,
1206
- brc105Challenge.satoshisRequired,
1207
- "brc105",
1208
- async () => {
1209
- if (brc105ProofConstructor) {
1210
- return brc105ProofConstructor(brc105Challenge);
1211
- }
1212
- return constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
1213
- },
1214
- (proof) => {
1215
- const headers = new Headers(init?.headers);
1216
- headers.set("x-bsv-payment", JSON.stringify(proof));
1217
- headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
1218
- return fetch(input, { ...init, headers });
1219
- },
1220
- (proof) => ({
1221
- timestamp: nowFn(),
1222
- origin,
1223
- satoshis: brc105Challenge.satoshisRequired,
1224
- txid: proof.txid,
1225
- protocol: "brc105"
1226
- })
1227
- );
1228
- }
1229
- return response;
1230
- };
1231
- fetchFn.resetLimits = async () => {
1232
- const rl = await ensureInitialised();
1233
- if (limits.require2fa.onCircuitBreakerReset) {
1234
- if (!twoFactor) throw new Error("2FA required for circuit breaker reset but no twoFactorProvider configured");
1235
- const verified = await twoFactor.verify({ type: "circuit-breaker-reset" });
1236
- if (!verified) throw new Error("2FA verification failed for circuit breaker reset");
1237
- }
1238
- rl.reset();
1239
- await persist(rl);
1240
- };
1241
- fetchFn.getState = () => {
1242
- if (!limiter) return { entries: [], circuitBroken: false };
1243
- const state = limiter.getState();
1244
- return { entries: state.entries, circuitBroken: state.circuitBroken };
1245
- };
1246
- return fetchFn;
1247
- }
1248
- var singleton;
1249
- async function x402Fetch(input, init) {
1250
- if (!singleton) singleton = createX402Fetch();
1251
- return singleton(input, init);
1252
- }
1253
- function resolveRelativeUrl(url) {
1254
- const loc = globalThis.location;
1255
- if (loc?.href) {
1256
- return new URL(url, loc.href).origin;
1257
- }
1258
- return "unknown";
1259
- }
1260
- function extractOrigin(input) {
1261
- if (input instanceof URL) return input.origin;
1262
- if (typeof input === "string") {
1263
- try {
1264
- return new URL(input).origin;
1265
- } catch {
1266
- try {
1267
- return resolveRelativeUrl(input);
1268
- } catch {
1269
- return "unknown";
1270
- }
1271
- }
1272
- }
1273
- try {
1274
- return new URL(input.url).origin;
1275
- } catch {
1276
- try {
1277
- return resolveRelativeUrl(input.url);
1278
- } catch {
1279
- return "unknown";
1280
- }
1281
- }
1282
- }
1283
-
1284
1101
  // src/two-factor.ts
1285
1102
  var WalletTwoFactorProvider = class {
1286
1103
  async verify(action) {
@@ -1323,6 +1140,41 @@ function describeAction(action) {
1323
1140
  Allow this payment of ${action.amount} sats to ${action.origin}?`;
1324
1141
  }
1325
1142
  }
1143
+
1144
+ // src/site-policy.ts
1145
+ var defaultSitePrompt = async (origin) => {
1146
+ if (typeof globalThis.confirm !== "function") return "global";
1147
+ const allow = globalThis.confirm(
1148
+ `First payment to ${origin}.
1149
+
1150
+ Use your global spending limits for this site?
1151
+
1152
+ OK = Use global limits
1153
+ Cancel = Block this site`
1154
+ );
1155
+ return allow ? "global" : "block";
1156
+ };
1157
+ async function resolveSitePolicy(origin, limits, twoFactorProvider, promptFn = defaultSitePrompt) {
1158
+ const existing = limits.sitePolicies[origin];
1159
+ if (existing) return existing;
1160
+ if (!limits.requirePerSitePrompt) {
1161
+ return { origin, action: "global" };
1162
+ }
1163
+ if (limits.require2fa.onNewSiteApproval) {
1164
+ if (!twoFactorProvider) {
1165
+ return { origin, action: "block" };
1166
+ }
1167
+ const verified = await twoFactorProvider.verify({
1168
+ type: "new-site-approval",
1169
+ origin
1170
+ });
1171
+ if (!verified) {
1172
+ return { origin, action: "block" };
1173
+ }
1174
+ }
1175
+ const action = await promptFn(origin);
1176
+ return { origin, action };
1177
+ }
1326
1178
  export {
1327
1179
  BFG_DAILY_CEILING_SATOSHIS,
1328
1180
  BFG_PER_TX_CEILING_SATOSHIS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bsv-x402",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
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",