bsv-x402 0.5.0 → 0.7.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
@@ -20,18 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- BFG_DAILY_CEILING_SATOSHIS: () => BFG_DAILY_CEILING_SATOSHIS,
24
- BFG_PER_TX_CEILING_SATOSHIS: () => BFG_PER_TX_CEILING_SATOSHIS,
25
- LocalStorageAdapter: () => LocalStorageAdapter,
26
- RateLimiter: () => RateLimiter,
27
- TIER_PRESETS: () => TIER_PRESETS,
28
- WalletTwoFactorProvider: () => WalletTwoFactorProvider,
23
+ PICKUP_PERCENTAGES: () => PICKUP_PERCENTAGES,
24
+ TIER_CAPS: () => TIER_CAPS,
25
+ WEAPON_CAPS: () => WEAPON_CAPS,
26
+ applyPickup: () => applyPickup,
27
+ checkPayment: () => checkPayment,
28
+ clampBalanceToTier: () => clampBalanceToTier,
29
29
  constructBrc105Proof: () => constructBrc105Proof,
30
30
  createX402Fetch: () => createX402Fetch,
31
+ initialState: () => initialState,
31
32
  parseBrc105Challenge: () => parseBrc105Challenge,
32
33
  parseChallenge: () => parseChallenge,
33
- resolveSitePolicy: () => resolveSitePolicy,
34
- resolveSpendLimits: () => resolveSpendLimits,
34
+ recordPayment: () => recordPayment,
35
35
  x402Fetch: () => x402Fetch
36
36
  });
37
37
  module.exports = __toCommonJS(index_exports);
@@ -544,6 +544,8 @@ async function constructBrc105Proof(challenge, wallet, origin) {
544
544
  })
545
545
  }],
546
546
  options: {
547
+ returnTXIDOnly: false,
548
+ noSend: true,
547
549
  randomizeOutputs: false
548
550
  }
549
551
  });
@@ -555,13 +557,21 @@ async function constructBrc105Proof(challenge, wallet, origin) {
555
557
  } else {
556
558
  throw new Error("Wallet returned no transaction data (neither tx nor rawTx)");
557
559
  }
558
- return {
560
+ const proof = {
559
561
  derivationPrefix: challenge.derivationPrefix,
560
562
  derivationSuffix,
561
563
  transaction: transactionBase64,
562
564
  clientIdentityKey,
563
565
  txid: result.txid
564
566
  };
567
+ const abort = wallet.abortAction ? async () => {
568
+ try {
569
+ await wallet.abortAction({ reference: result.txid });
570
+ } catch (err) {
571
+ console.warn("[x402] abortAction failed:", err);
572
+ }
573
+ } : void 0;
574
+ return { proof, abort };
565
575
  }
566
576
 
567
577
  // src/challenge.ts
@@ -597,6 +607,22 @@ function parseChallenge(header) {
597
607
  }
598
608
 
599
609
  // src/x402-fetch.ts
610
+ function bytesToBase642(bytes) {
611
+ let binary = "";
612
+ for (const b of bytes) binary += String.fromCharCode(b);
613
+ return btoa(binary);
614
+ }
615
+ function numberArrayToBase642(arr) {
616
+ return bytesToBase642(new Uint8Array(arr));
617
+ }
618
+ function hexToBytes2(hex) {
619
+ if (hex.length % 2 !== 0) throw new Error("Hex string must have even length");
620
+ const bytes = new Uint8Array(hex.length / 2);
621
+ for (let i = 0; i < hex.length; i += 2) {
622
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
623
+ }
624
+ return bytes;
625
+ }
600
626
  var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
601
627
  function base58DecodeCheck(address) {
602
628
  let leadingZeros = 0;
@@ -660,12 +686,17 @@ async function defaultConstructProof(challenge) {
660
686
  if (!result || !result.txid) {
661
687
  throw new Error("Wallet declined payment or returned invalid result");
662
688
  }
663
- if (!result.rawTx || typeof result.rawTx !== "string" || result.rawTx.length === 0) {
664
- throw new Error("Wallet did not return raw transaction");
689
+ let beef;
690
+ if (result.tx && Array.isArray(result.tx) && result.tx.length > 0) {
691
+ beef = numberArrayToBase642(result.tx);
692
+ } else if (result.rawTx && typeof result.rawTx === "string" && result.rawTx.length > 0) {
693
+ beef = bytesToBase642(hexToBytes2(result.rawTx));
694
+ } else {
695
+ throw new Error("Wallet returned no transaction data (neither tx nor rawTx)");
665
696
  }
666
697
  return {
667
698
  txid: result.txid,
668
- rawTx: result.rawTx
699
+ beef
669
700
  };
670
701
  }
671
702
  function createX402Fetch(config = {}) {
@@ -706,11 +737,16 @@ function createX402Fetch(config = {}) {
706
737
  return response;
707
738
  }
708
739
  let proof;
740
+ let abort;
709
741
  try {
710
742
  if (brc105ProofConstructor) {
711
- proof = await brc105ProofConstructor(brc105Challenge);
743
+ const result = await brc105ProofConstructor(brc105Challenge);
744
+ proof = result.proof;
745
+ abort = result.abort;
712
746
  } else {
713
- proof = await constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
747
+ const result = await constructBrc105Proof(brc105Challenge, brc105Wallet, origin);
748
+ proof = result.proof;
749
+ abort = result.abort;
714
750
  }
715
751
  } catch (err) {
716
752
  console.error("[x402] Proof construction failed (brc105):", err);
@@ -720,7 +756,21 @@ function createX402Fetch(config = {}) {
720
756
  const headers = new Headers(init?.headers);
721
757
  headers.set("x-bsv-payment", JSON.stringify(proof));
722
758
  headers.set("x-bsv-auth-identity-key", proof.clientIdentityKey);
723
- return fetch(input, { ...init, headers });
759
+ let retryResponse;
760
+ try {
761
+ retryResponse = await fetch(input, { ...init, headers });
762
+ } catch (err) {
763
+ if (abort) {
764
+ await abort();
765
+ console.warn("[x402] BRC-105 retry fetch failed, UTXOs released via abortAction");
766
+ }
767
+ throw err;
768
+ }
769
+ if (!retryResponse.ok && abort) {
770
+ await abort();
771
+ console.warn("[x402] Server rejected BRC-105 payment, UTXOs released via abortAction");
772
+ }
773
+ return retryResponse;
724
774
  }
725
775
  return response;
726
776
  };
@@ -761,471 +811,80 @@ function extractOrigin(input) {
761
811
  }
762
812
  }
763
813
 
764
- // src/limits.ts
765
- var BFG_DAILY_CEILING_SATOSHIS = 1e10;
766
- var BFG_PER_TX_CEILING_SATOSHIS = 1e9;
767
- var WINDOW_MS = {
768
- minute: 6e4,
769
- hour: 36e5,
770
- day: 864e5,
771
- week: 6048e5
772
- };
773
- function windowToMs(window2) {
774
- return WINDOW_MS[window2];
775
- }
776
- function makeLimits(windows, perTxMaxSatoshis, opts = {}) {
777
- return {
778
- windows,
779
- perTxMaxSatoshis,
780
- yellowLightThreshold: 0.8,
781
- requirePerSitePrompt: false,
782
- sitePolicies: {},
783
- require2fa: {
784
- onCircuitBreakerReset: true,
785
- onTierChange: true,
786
- onHighValueTx: false,
787
- highValueThreshold: 0,
788
- onNewSiteApproval: false
789
- },
790
- ...opts
791
- };
792
- }
793
- var TOO_YOUNG_TO_DIE = {
794
- interactive: makeLimits(
795
- [{ window: "day", maxSatoshis: 1e8, maxTransactions: Infinity }],
796
- 1e8
797
- ),
798
- programmatic: makeLimits(
799
- [{ window: "day", maxSatoshis: 1e8, maxTransactions: Infinity }],
800
- 1e6
801
- )
802
- };
803
- var HEY_NOT_TOO_ROUGH = {
804
- interactive: makeLimits(
805
- [
806
- { window: "day", maxSatoshis: 1e8, maxTransactions: 100 },
807
- { window: "week", maxSatoshis: 5e8, maxTransactions: 500 }
808
- ],
809
- 1e7,
810
- {
811
- requirePerSitePrompt: true,
812
- require2fa: {
813
- onCircuitBreakerReset: true,
814
- onTierChange: true,
815
- onHighValueTx: true,
816
- highValueThreshold: 5e7,
817
- onNewSiteApproval: true
818
- }
819
- }
820
- ),
821
- programmatic: makeLimits(
822
- [
823
- { window: "day", maxSatoshis: 1e8, maxTransactions: 1e4 },
824
- { window: "week", maxSatoshis: 5e8, maxTransactions: 5e4 }
825
- ],
826
- 1e5,
827
- {
828
- requirePerSitePrompt: true,
829
- require2fa: {
830
- onCircuitBreakerReset: true,
831
- onTierChange: true,
832
- onHighValueTx: true,
833
- highValueThreshold: 5e7,
834
- onNewSiteApproval: true
835
- }
836
- }
837
- )
838
- };
839
- var HURT_ME_PLENTY = {
840
- interactive: makeLimits(
841
- [
842
- { window: "minute", maxSatoshis: 5e6, maxTransactions: 10 },
843
- { window: "hour", maxSatoshis: 5e7, maxTransactions: 60 },
844
- { window: "day", maxSatoshis: 2e8, maxTransactions: 200 },
845
- { window: "week", maxSatoshis: 1e9, maxTransactions: 1e3 }
846
- ],
847
- 2e7,
848
- {
849
- requirePerSitePrompt: true,
850
- require2fa: {
851
- onCircuitBreakerReset: true,
852
- onTierChange: true,
853
- onHighValueTx: true,
854
- highValueThreshold: 1e8,
855
- onNewSiteApproval: true
856
- }
857
- }
858
- ),
859
- programmatic: makeLimits(
860
- [
861
- { window: "minute", maxSatoshis: 5e6, maxTransactions: 1e3 },
862
- { window: "hour", maxSatoshis: 5e7, maxTransactions: 6e3 },
863
- { window: "day", maxSatoshis: 2e8, maxTransactions: 2e4 },
864
- { window: "week", maxSatoshis: 1e9, maxTransactions: 1e5 }
865
- ],
866
- 2e5,
867
- {
868
- requirePerSitePrompt: true,
869
- require2fa: {
870
- onCircuitBreakerReset: true,
871
- onTierChange: true,
872
- onHighValueTx: true,
873
- highValueThreshold: 1e8,
874
- onNewSiteApproval: true
875
- }
876
- }
877
- )
878
- };
879
- var ULTRA_VIOLENCE = {
880
- interactive: makeLimits(
881
- [...HURT_ME_PLENTY.interactive.windows],
882
- HURT_ME_PLENTY.interactive.perTxMaxSatoshis,
883
- {
884
- requirePerSitePrompt: true,
885
- require2fa: {
886
- onCircuitBreakerReset: true,
887
- onTierChange: true,
888
- onHighValueTx: false,
889
- highValueThreshold: 0,
890
- onNewSiteApproval: true
891
- }
892
- }
893
- ),
894
- programmatic: makeLimits(
895
- [...HURT_ME_PLENTY.programmatic.windows],
896
- HURT_ME_PLENTY.programmatic.perTxMaxSatoshis,
897
- {
898
- requirePerSitePrompt: true,
899
- require2fa: {
900
- onCircuitBreakerReset: true,
901
- onTierChange: true,
902
- onHighValueTx: false,
903
- highValueThreshold: 0,
904
- onNewSiteApproval: true
905
- }
906
- }
907
- )
814
+ // src/autospend.ts
815
+ var TIER_CAPS = {
816
+ "I'm Too Young to Die": 1e6,
817
+ // 0.01 BSV
818
+ "Hey, Not Too Rough": 1e7,
819
+ // 0.1 BSV
820
+ "Hurt Me Plenty": 1e8,
821
+ // 1 BSV
822
+ "Ultra-Violence": 1e9,
823
+ // 10 BSV
824
+ "Nightmare!": 1e11
825
+ // 100 BSV
908
826
  };
909
- var NIGHTMARE = {
910
- interactive: makeLimits([], BFG_PER_TX_CEILING_SATOSHIS),
911
- programmatic: makeLimits([], BFG_PER_TX_CEILING_SATOSHIS)
827
+ var WEAPON_CAPS = {
828
+ "Fists": 1e5,
829
+ "Chainsaw": 25e4,
830
+ "Pistol": 5e5,
831
+ "Shotgun": 1e6,
832
+ "Super Shotgun": 1e7,
833
+ "Chaingun": 5e7,
834
+ "Rocket Launcher": 25e7,
835
+ "Plasma Rifle": 1e9,
836
+ "BFG9000": Infinity
912
837
  };
913
- var TIER_PRESETS = {
914
- "I'm Too Young to Die": TOO_YOUNG_TO_DIE,
915
- "Hey, Not Too Rough": HEY_NOT_TOO_ROUGH,
916
- "Hurt Me Plenty": HURT_ME_PLENTY,
917
- "Ultra-Violence": ULTRA_VIOLENCE,
918
- "Nightmare!": NIGHTMARE
838
+ var PICKUP_PERCENTAGES = {
839
+ "Medkit": 0.1,
840
+ "Stimpak": 0.25,
841
+ "Soul Sphere": 1,
842
+ "New Game": 1
843
+ // hard-set to 100% (not additive)
919
844
  };
920
- function cloneSpendLimits(preset) {
921
- return {
922
- ...preset,
923
- windows: preset.windows.map((w) => ({ ...w })),
924
- require2fa: { ...preset.require2fa },
925
- sitePolicies: { ...preset.sitePolicies }
926
- };
845
+ function isValidAmount(amount) {
846
+ return Number.isFinite(amount) && amount > 0;
927
847
  }
928
- function resolveSpendLimits(tier = "Hey, Not Too Rough", mode = "interactive", overrides) {
929
- const preset = TIER_PRESETS[tier][mode];
930
- const base = cloneSpendLimits(preset);
931
- if (!overrides) return base;
932
- return {
933
- ...base,
934
- ...overrides,
935
- // Deep-merge require2fa if provided
936
- require2fa: overrides.require2fa ? { ...base.require2fa, ...overrides.require2fa } : base.require2fa,
937
- // Deep-merge sitePolicies if provided
938
- sitePolicies: overrides.sitePolicies ? { ...base.sitePolicies, ...overrides.sitePolicies } : base.sitePolicies
939
- };
848
+ function checkPayment(amount, state, config) {
849
+ if (!isValidAmount(amount)) return "confirm";
850
+ const perTxMax = WEAPON_CAPS[config.weapon];
851
+ const effectiveMax = Math.min(perTxMax, state.balance);
852
+ return amount <= effectiveMax ? "auto" : "confirm";
940
853
  }
941
- var RateLimiter = class {
942
- constructor(limits, state, now) {
943
- this.limits = limits;
944
- this.entries = state?.entries ?? [];
945
- this.broken = state?.circuitBroken ?? false;
946
- this.now = now ?? Date.now;
947
- }
948
- check(request, origin) {
949
- if (this.broken) {
950
- return { action: "block", reason: "Circuit breaker tripped \u2014 call resetLimits() to clear", severity: "trip" };
951
- }
952
- const amount = request.amount;
953
- if (!Number.isFinite(amount) || !Number.isInteger(amount) || amount <= 0) {
954
- return { action: "block", reason: "Invalid transaction amount rejected", severity: "reject" };
955
- }
956
- if (amount > BFG_PER_TX_CEILING_SATOSHIS) {
957
- return { action: "block", reason: `Exceeds BFG per-tx ceiling (${BFG_PER_TX_CEILING_SATOSHIS} sats)`, severity: "reject" };
958
- }
959
- const dayAgo = this.now() - WINDOW_MS.day;
960
- const dailyTotal = this.sumSatoshis(dayAgo);
961
- if (dailyTotal + amount > BFG_DAILY_CEILING_SATOSHIS) {
962
- return { action: "block", reason: `Exceeds BFG daily ceiling (${BFG_DAILY_CEILING_SATOSHIS} sats)`, severity: "trip" };
963
- }
964
- if (amount > this.limits.perTxMaxSatoshis) {
965
- return { action: "block", reason: `Exceeds per-tx limit (${this.limits.perTxMaxSatoshis} sats)`, severity: "reject" };
966
- }
967
- const isCustomSite = this.hasCustomPolicy(origin);
968
- const effectiveLimits = this.effectiveWindows(origin);
969
- const effectivePerTx = this.effectivePerTxMax(origin);
970
- if (effectivePerTx !== void 0 && amount > effectivePerTx) {
971
- return { action: "block", reason: `Exceeds per-tx limit for ${origin} (${effectivePerTx} sats)`, severity: "reject" };
972
- }
973
- let yellowLight;
974
- for (const wl of effectiveLimits) {
975
- const cutoff = this.now() - windowToMs(wl.window);
976
- const windowEntries = this.entriesInWindow(cutoff, isCustomSite ? origin : void 0);
977
- const totalSats = windowEntries.reduce((sum, e) => sum + e.satoshis, 0);
978
- const totalTx = windowEntries.length;
979
- if (totalSats + amount > wl.maxSatoshis) {
980
- return { action: "block", reason: `Exceeds ${wl.window} sats limit (${wl.maxSatoshis})`, severity: "window" };
981
- }
982
- if (totalTx + 1 > wl.maxTransactions) {
983
- return { action: "block", reason: `Exceeds ${wl.window} tx count limit (${wl.maxTransactions})`, severity: "window" };
984
- }
985
- if (this.limits.yellowLightThreshold < 1 && !yellowLight && totalSats + amount > wl.maxSatoshis * this.limits.yellowLightThreshold) {
986
- yellowLight = {
987
- origin,
988
- currentSpend: totalSats,
989
- limit: wl.maxSatoshis,
990
- window: wl.window,
991
- challenge: request
992
- };
993
- }
994
- }
995
- if (yellowLight) {
996
- return { action: "yellow-light", detail: yellowLight };
997
- }
998
- return { action: "allow" };
999
- }
1000
- record(entry) {
1001
- this.entries.push(entry);
1002
- this.prune();
1003
- }
1004
- trip() {
1005
- this.broken = true;
1006
- }
1007
- reset() {
1008
- this.broken = false;
1009
- }
1010
- isBroken() {
1011
- return this.broken;
1012
- }
1013
- getState() {
1014
- return {
1015
- entries: [...this.entries],
1016
- circuitBroken: this.broken,
1017
- hmac: ""
1018
- };
1019
- }
1020
- hasCustomPolicy(origin) {
1021
- const policy = this.limits.sitePolicies[origin];
1022
- return policy?.action === "custom" && !!policy.limits;
1023
- }
1024
- effectiveWindows(origin) {
1025
- const policy = this.limits.sitePolicies[origin];
1026
- if (policy?.action === "custom" && policy.limits) {
1027
- return policy.limits;
1028
- }
1029
- return this.limits.windows;
1030
- }
1031
- effectivePerTxMax(origin) {
1032
- const policy = this.limits.sitePolicies[origin];
1033
- if (policy?.action === "custom" && policy.perTxMaxSatoshis !== void 0) {
1034
- return policy.perTxMaxSatoshis;
1035
- }
1036
- return void 0;
1037
- }
1038
- entriesInWindow(cutoff, filterOrigin) {
1039
- return this.entries.filter(
1040
- (e) => e.timestamp >= cutoff && (filterOrigin === void 0 || e.origin === filterOrigin)
1041
- );
1042
- }
1043
- sumSatoshis(cutoff) {
1044
- return this.entries.filter((e) => e.timestamp >= cutoff).reduce((sum, e) => sum + e.satoshis, 0);
1045
- }
1046
- prune() {
1047
- let longestWindow = WINDOW_MS.day;
1048
- for (const wl of this.limits.windows) {
1049
- longestWindow = Math.max(longestWindow, windowToMs(wl.window));
1050
- }
1051
- if (this.limits.sitePolicies) {
1052
- for (const policy of Object.values(this.limits.sitePolicies)) {
1053
- if (policy?.action === "custom" && Array.isArray(policy.limits)) {
1054
- for (const wl of policy.limits) {
1055
- longestWindow = Math.max(longestWindow, windowToMs(wl.window));
1056
- }
1057
- }
1058
- }
1059
- }
1060
- const cutoff = this.now() - longestWindow;
1061
- this.entries = this.entries.filter((e) => e.timestamp >= cutoff);
1062
- }
1063
- };
1064
-
1065
- // src/storage.ts
1066
- var STATE_KEY = "x402:limit-state";
1067
- var POLICIES_KEY = "x402:site-policies";
1068
- async function computeHmac(data, key) {
1069
- const cryptoKey = await crypto.subtle.importKey(
1070
- "raw",
1071
- key,
1072
- { name: "HMAC", hash: "SHA-256" },
1073
- false,
1074
- ["sign"]
1075
- );
1076
- const encoded = new TextEncoder().encode(data);
1077
- const sig = await crypto.subtle.sign("HMAC", cryptoKey, encoded);
1078
- return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
854
+ function recordPayment(amount, state) {
855
+ if (!isValidAmount(amount)) return state;
856
+ return { balance: Math.max(0, state.balance - amount) };
1079
857
  }
1080
- function serializeForHmac(state) {
1081
- return JSON.stringify({
1082
- entries: state.entries,
1083
- circuitBroken: state.circuitBroken
1084
- });
858
+ function applyPickup(pickup, state, config, walletBalance) {
859
+ const tierCap = TIER_CAPS[config.tier];
860
+ const cap = Math.min(tierCap, walletBalance);
861
+ if (pickup === "New Game") {
862
+ return { balance: cap };
863
+ }
864
+ const bonus = Math.floor(tierCap * PICKUP_PERCENTAGES[pickup]);
865
+ return { balance: Math.min(cap, state.balance + bonus) };
1085
866
  }
1086
- var LocalStorageAdapter = class {
1087
- constructor(keyDeriver, storage) {
1088
- this.keyDeriver = keyDeriver;
1089
- this.storage = storage ?? globalThis.localStorage;
1090
- }
1091
- async load() {
1092
- const raw = this.storage.getItem(STATE_KEY);
1093
- if (!raw) return null;
1094
- let state;
1095
- try {
1096
- state = JSON.parse(raw);
1097
- } catch {
1098
- console.warn("x402: limit state JSON parse failed \u2014 treating as tampered");
1099
- return { entries: [], circuitBroken: true, hmac: "" };
1100
- }
1101
- if (this.keyDeriver) {
1102
- if (!state.hmac) {
1103
- console.warn("x402: limit state missing HMAC \u2014 treating as tampered");
1104
- return { entries: [], circuitBroken: true, hmac: "" };
1105
- }
1106
- const key = await this.keyDeriver();
1107
- const expected = await computeHmac(serializeForHmac(state), key);
1108
- if (expected !== state.hmac) {
1109
- console.warn("x402: limit state HMAC mismatch \u2014 state may have been tampered with");
1110
- return { entries: [], circuitBroken: true, hmac: "" };
1111
- }
1112
- }
1113
- state.entries = (Array.isArray(state.entries) ? state.entries : []).filter(
1114
- (e) => e != null && typeof e.origin === "string" && typeof e.txid === "string" && typeof e.satoshis === "number" && Number.isFinite(e.satoshis) && e.satoshis >= 0 && typeof e.timestamp === "number" && Number.isFinite(e.timestamp) && e.timestamp > 0
1115
- );
1116
- return state;
1117
- }
1118
- async save(state) {
1119
- if (this.keyDeriver) {
1120
- const key = await this.keyDeriver();
1121
- state.hmac = await computeHmac(serializeForHmac(state), key);
1122
- }
1123
- this.storage.setItem(STATE_KEY, JSON.stringify(state));
1124
- }
1125
- async loadSitePolicies() {
1126
- const raw = this.storage.getItem(POLICIES_KEY);
1127
- if (!raw) return {};
1128
- try {
1129
- return JSON.parse(raw);
1130
- } catch {
1131
- return {};
1132
- }
1133
- }
1134
- async saveSitePolicies(policies) {
1135
- this.storage.setItem(POLICIES_KEY, JSON.stringify(policies));
1136
- }
1137
- };
1138
-
1139
- // src/two-factor.ts
1140
- var WalletTwoFactorProvider = class {
1141
- async verify(action) {
1142
- if (typeof window === "undefined" || !window.CWI) {
1143
- return this.promptFallback(action);
1144
- }
1145
- const challengeData = `x402-2fa:${JSON.stringify(action)}:${Date.now()}`;
1146
- try {
1147
- const sig = await window.CWI.createSignature({
1148
- data: new TextEncoder().encode(challengeData),
1149
- protocolID: [1, "x402-2fa"],
1150
- keyID: "spending-limits"
1151
- });
1152
- return sig !== null;
1153
- } catch {
1154
- return false;
1155
- }
1156
- }
1157
- promptFallback(action) {
1158
- if (typeof window === "undefined" || !window.prompt) return false;
1159
- const message = describeAction(action);
1160
- const result = window.prompt(`${message}
1161
-
1162
- Type CONFIRM to proceed:`);
1163
- return result === "CONFIRM";
1164
- }
1165
- };
1166
- function describeAction(action) {
1167
- switch (action.type) {
1168
- case "circuit-breaker-reset":
1169
- return "Reset spending circuit breaker? This re-enables automated payments.";
1170
- case "tier-change":
1171
- return `Change spending tier from "${action.from}" to "${action.to}"?`;
1172
- case "high-value-tx":
1173
- return `Approve high-value payment of ${action.amount} sats to ${action.origin}?`;
1174
- case "new-site-approval":
1175
- return `Allow automated payments to ${action.origin}?`;
1176
- case "limit-override":
1177
- return `Spending limit reached: ${action.reason}
1178
- Allow this payment of ${action.amount} sats to ${action.origin}?`;
1179
- }
867
+ function clampBalanceToTier(state, config, walletBalance) {
868
+ const cap = Math.min(TIER_CAPS[config.tier], walletBalance);
869
+ return { balance: Math.min(state.balance, cap) };
1180
870
  }
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 };
871
+ function initialState(config, walletBalance) {
872
+ const cap = Math.min(TIER_CAPS[config.tier], walletBalance);
873
+ return { balance: cap };
1215
874
  }
1216
875
  // Annotate the CommonJS export names for ESM import in node:
1217
876
  0 && (module.exports = {
1218
- BFG_DAILY_CEILING_SATOSHIS,
1219
- BFG_PER_TX_CEILING_SATOSHIS,
1220
- LocalStorageAdapter,
1221
- RateLimiter,
1222
- TIER_PRESETS,
1223
- WalletTwoFactorProvider,
877
+ PICKUP_PERCENTAGES,
878
+ TIER_CAPS,
879
+ WEAPON_CAPS,
880
+ applyPickup,
881
+ checkPayment,
882
+ clampBalanceToTier,
1224
883
  constructBrc105Proof,
1225
884
  createX402Fetch,
885
+ initialState,
1226
886
  parseBrc105Challenge,
1227
887
  parseChallenge,
1228
- resolveSitePolicy,
1229
- resolveSpendLimits,
888
+ recordPayment,
1230
889
  x402Fetch
1231
890
  });