bsv-x402 0.3.0 → 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
@@ -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
  }
@@ -589,6 +596,171 @@ function parseChallenge(header) {
589
596
  };
590
597
  }
591
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
+
592
764
  // src/limits.ts
593
765
  var BFG_DAILY_CEILING_SATOSHIS = 1e10;
594
766
  var BFG_PER_TX_CEILING_SATOSHIS = 1e9;
@@ -890,41 +1062,6 @@ var RateLimiter = class {
890
1062
  }
891
1063
  };
892
1064
 
893
- // src/site-policy.ts
894
- var defaultSitePrompt = async (origin) => {
895
- if (typeof globalThis.confirm !== "function") return "global";
896
- const allow = globalThis.confirm(
897
- `First payment to ${origin}.
898
-
899
- Use your global spending limits for this site?
900
-
901
- OK = Use global limits
902
- Cancel = Block this site`
903
- );
904
- return allow ? "global" : "block";
905
- };
906
- async function resolveSitePolicy(origin, limits, twoFactorProvider, promptFn = defaultSitePrompt) {
907
- const existing = limits.sitePolicies[origin];
908
- if (existing) return existing;
909
- if (!limits.requirePerSitePrompt) {
910
- return { origin, action: "global" };
911
- }
912
- if (limits.require2fa.onNewSiteApproval) {
913
- if (!twoFactorProvider) {
914
- return { origin, action: "block" };
915
- }
916
- const verified = await twoFactorProvider.verify({
917
- type: "new-site-approval",
918
- origin
919
- });
920
- if (!verified) {
921
- return { origin, action: "block" };
922
- }
923
- }
924
- const action = await promptFn(origin);
925
- return { origin, action };
926
- }
927
-
928
1065
  // src/storage.ts
929
1066
  var STATE_KEY = "x402:limit-state";
930
1067
  var POLICIES_KEY = "x402:site-policies";
@@ -999,304 +1136,6 @@ var LocalStorageAdapter = class {
999
1136
  }
1000
1137
  };
1001
1138
 
1002
- // src/x402-fetch.ts
1003
- var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1004
- function base58DecodeCheck(address) {
1005
- let leadingZeros = 0;
1006
- for (const c of address) {
1007
- if (c === "1") leadingZeros++;
1008
- else break;
1009
- }
1010
- let n = BigInt(0);
1011
- for (const c of address) {
1012
- const i = BASE58_ALPHABET.indexOf(c);
1013
- if (i < 0) throw new Error(`Invalid Base58 character: ${c}`);
1014
- n = n * 58n + BigInt(i);
1015
- }
1016
- const hexFromBigint = n === 0n ? "" : n.toString(16);
1017
- const paddedHex = hexFromBigint.length % 2 ? "0" + hexFromBigint : hexFromBigint;
1018
- const bigintBytes = [];
1019
- for (let i = 0; i < paddedHex.length; i += 2) {
1020
- bigintBytes.push(parseInt(paddedHex.slice(i, i + 2), 16));
1021
- }
1022
- const allBytes = new Uint8Array(leadingZeros + bigintBytes.length);
1023
- allBytes.set(bigintBytes, leadingZeros);
1024
- if (allBytes.length !== 25) {
1025
- throw new Error(`Invalid address length: expected 25 bytes, got ${allBytes.length}`);
1026
- }
1027
- const body = allBytes.slice(0, 21);
1028
- const checksum = allBytes.slice(21);
1029
- const version = allBytes[0];
1030
- if (version !== 0 && version !== 111) {
1031
- throw new Error(`Unsupported address version: 0x${version.toString(16).padStart(2, "0")}`);
1032
- }
1033
- return { version, payload: body.slice(1) };
1034
- }
1035
- function payeeAddressToLockingScript(address) {
1036
- const { payload } = base58DecodeCheck(address);
1037
- if (payload.length !== 20) {
1038
- throw new Error(`Invalid pubkey hash length: expected 20 bytes, got ${payload.length}`);
1039
- }
1040
- const pubkeyHash = Array.from(payload).map((b) => b.toString(16).padStart(2, "0")).join("");
1041
- return `76a914${pubkeyHash}88ac`;
1042
- }
1043
- async function defaultConstructProof(challenge) {
1044
- const cwi = globalThis.CWI;
1045
- if (!cwi || typeof cwi.createAction !== "function") {
1046
- throw new Error(
1047
- "No BRC-100 wallet detected. Install a CWI-compliant browser extension or provide a custom proofConstructor in X402Config."
1048
- );
1049
- }
1050
- const result = await cwi.createAction({
1051
- description: `x402 payment: ${challenge.amount} sats to ${challenge.payee}`,
1052
- outputs: [{
1053
- satoshis: challenge.amount,
1054
- lockingScript: payeeAddressToLockingScript(challenge.payee),
1055
- description: `Payment to ${challenge.payee}`
1056
- }],
1057
- labels: ["x402-payment"],
1058
- options: {
1059
- returnTXIDOnly: false,
1060
- noSend: false
1061
- }
1062
- });
1063
- if (!result || !result.txid) {
1064
- throw new Error("Wallet declined payment or returned invalid result");
1065
- }
1066
- if (!result.rawTx || typeof result.rawTx !== "string" || result.rawTx.length === 0) {
1067
- throw new Error("Wallet did not return raw transaction");
1068
- }
1069
- return {
1070
- txid: result.txid,
1071
- rawTx: result.rawTx
1072
- };
1073
- }
1074
- function createMutex() {
1075
- let chain = Promise.resolve();
1076
- return (fn) => {
1077
- const result = chain.then(fn, fn);
1078
- chain = result.then(() => {
1079
- }, () => {
1080
- });
1081
- return result;
1082
- };
1083
- }
1084
- function createX402Fetch(config = {}) {
1085
- const tier = config.tier ?? "Hey, Not Too Rough";
1086
- const mode = config.mode ?? "interactive";
1087
- if (tier === "Nightmare!" && config.nightmareConfirmation !== "NIGHTMARE") {
1088
- throw new Error('Nightmare! tier requires nightmareConfirmation: "NIGHTMARE"');
1089
- }
1090
- const limits = resolveSpendLimits(tier, mode, config.limits);
1091
- const storage = config.storage ?? new LocalStorageAdapter();
1092
- const twoFactor = config.twoFactorProvider;
1093
- const constructProof = config.proofConstructor ?? defaultConstructProof;
1094
- const brc105ProofConstructor = config.brc105ProofConstructor;
1095
- const brc105Wallet = config.brc105Wallet;
1096
- const nowFn = config.now ?? Date.now;
1097
- const mutex = createMutex();
1098
- const needs2fa = limits.require2fa;
1099
- if (!twoFactor && (needs2fa.onCircuitBreakerReset || needs2fa.onHighValueTx || needs2fa.onNewSiteApproval || needs2fa.onTierChange)) {
1100
- console.warn("x402: tier requires 2FA but no twoFactorProvider configured \u2014 2FA-gated actions will be blocked");
1101
- }
1102
- let limiter;
1103
- let initialised = false;
1104
- async function ensureInitialised() {
1105
- if (limiter && initialised) return limiter;
1106
- const state = await storage.load();
1107
- limiter = new RateLimiter(limits, state ?? void 0, nowFn);
1108
- const policies = await storage.loadSitePolicies();
1109
- Object.assign(limits.sitePolicies, policies);
1110
- initialised = true;
1111
- return limiter;
1112
- }
1113
- async function persist(rl) {
1114
- await storage.save(rl.getState());
1115
- await storage.saveSitePolicies(limits.sitePolicies);
1116
- }
1117
- async function handlePaymentFlow(originalResponse, input, init, origin, amount, protocol, buildProof, retryWithProof, makeLedgerEntry) {
1118
- return mutex(async () => {
1119
- const rl = await ensureInitialised();
1120
- const sitePolicy = await resolveSitePolicy(origin, limits, twoFactor);
1121
- if (sitePolicy.action === "block") return originalResponse;
1122
- if (!limits.sitePolicies[origin]) {
1123
- limits.sitePolicies[origin] = sitePolicy;
1124
- await storage.saveSitePolicies(limits.sitePolicies);
1125
- }
1126
- const spendCheckable = { amount, origin, protocol };
1127
- const result = rl.check(spendCheckable, origin);
1128
- if (result.action === "block") {
1129
- if (result.severity === "trip") {
1130
- rl.trip();
1131
- await persist(rl);
1132
- config.onLimitReached?.(result.reason);
1133
- return originalResponse;
1134
- }
1135
- if (result.severity === "window" && twoFactor) {
1136
- config.onLimitReached?.(result.reason);
1137
- const override = await twoFactor.verify({
1138
- type: "limit-override",
1139
- amount,
1140
- origin,
1141
- reason: result.reason
1142
- });
1143
- if (override) {
1144
- } else {
1145
- return originalResponse;
1146
- }
1147
- } else {
1148
- config.onLimitReached?.(result.reason);
1149
- return originalResponse;
1150
- }
1151
- }
1152
- if (result.action === "yellow-light") {
1153
- const proceed = config.onYellowLight ? await config.onYellowLight(result.detail) : false;
1154
- if (!proceed) return originalResponse;
1155
- }
1156
- if (limits.require2fa.onHighValueTx && amount > limits.require2fa.highValueThreshold) {
1157
- if (!twoFactor) return originalResponse;
1158
- const verified = await twoFactor.verify({
1159
- type: "high-value-tx",
1160
- amount,
1161
- origin
1162
- });
1163
- if (!verified) return originalResponse;
1164
- }
1165
- let proof;
1166
- try {
1167
- proof = await buildProof();
1168
- } catch {
1169
- return originalResponse;
1170
- }
1171
- rl.record(makeLedgerEntry(proof));
1172
- await persist(rl);
1173
- return retryWithProof(proof);
1174
- });
1175
- }
1176
- const fetchFn = async function x402Fetch2(input, init) {
1177
- const response = await fetch(input, init);
1178
- if (response.status !== 402) return response;
1179
- const origin = extractOrigin(input);
1180
- const challengeHeader = response.headers.get("X402-Challenge");
1181
- if (challengeHeader) {
1182
- let challenge;
1183
- try {
1184
- challenge = parseChallenge(challengeHeader);
1185
- } catch {
1186
- return response;
1187
- }
1188
- return handlePaymentFlow(
1189
- response,
1190
- input,
1191
- init,
1192
- origin,
1193
- challenge.amount,
1194
- "x402",
1195
- async () => constructProof(challenge),
1196
- (proof) => {
1197
- const headers = new Headers(init?.headers);
1198
- headers.set("X402-Proof", JSON.stringify(proof));
1199
- return fetch(input, { ...init, headers });
1200
- },
1201
- (proof) => ({
1202
- timestamp: nowFn(),
1203
- origin,
1204
- satoshis: challenge.amount,
1205
- txid: proof.txid
1206
- })
1207
- );
1208
- }
1209
- const brc105Version = response.headers.get("x-bsv-payment-version");
1210
- if (brc105Version) {
1211
- if (!brc105ProofConstructor && !brc105Wallet) return response;
1212
- let brc105Challenge;
1213
- try {
1214
- brc105Challenge = parseBrc105Challenge(response);
1215
- } catch {
1216
- return response;
1217
- }
1218
- return handlePaymentFlow(
1219
- response,
1220
- input,
1221
- init,
1222
- origin,
1223
- brc105Challenge.satoshisRequired,
1224
- "brc105",
1225
- async () => {
1226
- if (brc105ProofConstructor) {
1227
- return brc105ProofConstructor(brc105Challenge);
1228
- }
1229
- return constructBrc105Proof(brc105Challenge, brc105Wallet);
1230
- },
1231
- (proof) => {
1232
- const headers = new Headers(init?.headers);
1233
- headers.set("x-bsv-payment", JSON.stringify(proof));
1234
- return fetch(input, { ...init, headers });
1235
- },
1236
- (proof) => ({
1237
- timestamp: nowFn(),
1238
- origin,
1239
- satoshis: brc105Challenge.satoshisRequired,
1240
- txid: proof.txid,
1241
- protocol: "brc105"
1242
- })
1243
- );
1244
- }
1245
- return response;
1246
- };
1247
- fetchFn.resetLimits = async () => {
1248
- const rl = await ensureInitialised();
1249
- if (limits.require2fa.onCircuitBreakerReset) {
1250
- if (!twoFactor) throw new Error("2FA required for circuit breaker reset but no twoFactorProvider configured");
1251
- const verified = await twoFactor.verify({ type: "circuit-breaker-reset" });
1252
- if (!verified) throw new Error("2FA verification failed for circuit breaker reset");
1253
- }
1254
- rl.reset();
1255
- await persist(rl);
1256
- };
1257
- fetchFn.getState = () => {
1258
- if (!limiter) return { entries: [], circuitBroken: false };
1259
- const state = limiter.getState();
1260
- return { entries: state.entries, circuitBroken: state.circuitBroken };
1261
- };
1262
- return fetchFn;
1263
- }
1264
- var singleton;
1265
- async function x402Fetch(input, init) {
1266
- if (!singleton) singleton = createX402Fetch();
1267
- return singleton(input, init);
1268
- }
1269
- function resolveRelativeUrl(url) {
1270
- const loc = globalThis.location;
1271
- if (loc?.href) {
1272
- return new URL(url, loc.href).origin;
1273
- }
1274
- return "unknown";
1275
- }
1276
- function extractOrigin(input) {
1277
- if (input instanceof URL) return input.origin;
1278
- if (typeof input === "string") {
1279
- try {
1280
- return new URL(input).origin;
1281
- } catch {
1282
- try {
1283
- return resolveRelativeUrl(input);
1284
- } catch {
1285
- return "unknown";
1286
- }
1287
- }
1288
- }
1289
- try {
1290
- return new URL(input.url).origin;
1291
- } catch {
1292
- try {
1293
- return resolveRelativeUrl(input.url);
1294
- } catch {
1295
- return "unknown";
1296
- }
1297
- }
1298
- }
1299
-
1300
1139
  // src/two-factor.ts
1301
1140
  var WalletTwoFactorProvider = class {
1302
1141
  async verify(action) {
@@ -1339,6 +1178,41 @@ function describeAction(action) {
1339
1178
  Allow this payment of ${action.amount} sats to ${action.origin}?`;
1340
1179
  }
1341
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
+ }
1342
1216
  // Annotate the CommonJS export names for ESM import in node:
1343
1217
  0 && (module.exports = {
1344
1218
  BFG_DAILY_CEILING_SATOSHIS,