bsv-x402 0.4.1 → 0.6.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);
@@ -596,416 +596,6 @@ function parseChallenge(header) {
596
596
  };
597
597
  }
598
598
 
599
- // src/limits.ts
600
- var BFG_DAILY_CEILING_SATOSHIS = 1e10;
601
- var BFG_PER_TX_CEILING_SATOSHIS = 1e9;
602
- var WINDOW_MS = {
603
- minute: 6e4,
604
- hour: 36e5,
605
- day: 864e5,
606
- week: 6048e5
607
- };
608
- function windowToMs(window2) {
609
- return WINDOW_MS[window2];
610
- }
611
- function makeLimits(windows, perTxMaxSatoshis, opts = {}) {
612
- return {
613
- windows,
614
- perTxMaxSatoshis,
615
- yellowLightThreshold: 0.8,
616
- requirePerSitePrompt: false,
617
- sitePolicies: {},
618
- require2fa: {
619
- onCircuitBreakerReset: true,
620
- onTierChange: true,
621
- onHighValueTx: false,
622
- highValueThreshold: 0,
623
- onNewSiteApproval: false
624
- },
625
- ...opts
626
- };
627
- }
628
- var TOO_YOUNG_TO_DIE = {
629
- interactive: makeLimits(
630
- [{ window: "day", maxSatoshis: 1e8, maxTransactions: Infinity }],
631
- 1e8
632
- ),
633
- programmatic: makeLimits(
634
- [{ window: "day", maxSatoshis: 1e8, maxTransactions: Infinity }],
635
- 1e6
636
- )
637
- };
638
- var HEY_NOT_TOO_ROUGH = {
639
- interactive: makeLimits(
640
- [
641
- { window: "day", maxSatoshis: 1e8, maxTransactions: 100 },
642
- { window: "week", maxSatoshis: 5e8, maxTransactions: 500 }
643
- ],
644
- 1e7,
645
- {
646
- requirePerSitePrompt: true,
647
- require2fa: {
648
- onCircuitBreakerReset: true,
649
- onTierChange: true,
650
- onHighValueTx: true,
651
- highValueThreshold: 5e7,
652
- onNewSiteApproval: true
653
- }
654
- }
655
- ),
656
- programmatic: makeLimits(
657
- [
658
- { window: "day", maxSatoshis: 1e8, maxTransactions: 1e4 },
659
- { window: "week", maxSatoshis: 5e8, maxTransactions: 5e4 }
660
- ],
661
- 1e5,
662
- {
663
- requirePerSitePrompt: true,
664
- require2fa: {
665
- onCircuitBreakerReset: true,
666
- onTierChange: true,
667
- onHighValueTx: true,
668
- highValueThreshold: 5e7,
669
- onNewSiteApproval: true
670
- }
671
- }
672
- )
673
- };
674
- var HURT_ME_PLENTY = {
675
- interactive: makeLimits(
676
- [
677
- { window: "minute", maxSatoshis: 5e6, maxTransactions: 10 },
678
- { window: "hour", maxSatoshis: 5e7, maxTransactions: 60 },
679
- { window: "day", maxSatoshis: 2e8, maxTransactions: 200 },
680
- { window: "week", maxSatoshis: 1e9, maxTransactions: 1e3 }
681
- ],
682
- 2e7,
683
- {
684
- requirePerSitePrompt: true,
685
- require2fa: {
686
- onCircuitBreakerReset: true,
687
- onTierChange: true,
688
- onHighValueTx: true,
689
- highValueThreshold: 1e8,
690
- onNewSiteApproval: true
691
- }
692
- }
693
- ),
694
- programmatic: makeLimits(
695
- [
696
- { window: "minute", maxSatoshis: 5e6, maxTransactions: 1e3 },
697
- { window: "hour", maxSatoshis: 5e7, maxTransactions: 6e3 },
698
- { window: "day", maxSatoshis: 2e8, maxTransactions: 2e4 },
699
- { window: "week", maxSatoshis: 1e9, maxTransactions: 1e5 }
700
- ],
701
- 2e5,
702
- {
703
- requirePerSitePrompt: true,
704
- require2fa: {
705
- onCircuitBreakerReset: true,
706
- onTierChange: true,
707
- onHighValueTx: true,
708
- highValueThreshold: 1e8,
709
- onNewSiteApproval: true
710
- }
711
- }
712
- )
713
- };
714
- var ULTRA_VIOLENCE = {
715
- interactive: makeLimits(
716
- [...HURT_ME_PLENTY.interactive.windows],
717
- HURT_ME_PLENTY.interactive.perTxMaxSatoshis,
718
- {
719
- requirePerSitePrompt: true,
720
- require2fa: {
721
- onCircuitBreakerReset: true,
722
- onTierChange: true,
723
- onHighValueTx: false,
724
- highValueThreshold: 0,
725
- onNewSiteApproval: true
726
- }
727
- }
728
- ),
729
- programmatic: makeLimits(
730
- [...HURT_ME_PLENTY.programmatic.windows],
731
- HURT_ME_PLENTY.programmatic.perTxMaxSatoshis,
732
- {
733
- requirePerSitePrompt: true,
734
- require2fa: {
735
- onCircuitBreakerReset: true,
736
- onTierChange: true,
737
- onHighValueTx: false,
738
- highValueThreshold: 0,
739
- onNewSiteApproval: true
740
- }
741
- }
742
- )
743
- };
744
- var NIGHTMARE = {
745
- interactive: makeLimits([], BFG_PER_TX_CEILING_SATOSHIS),
746
- programmatic: makeLimits([], BFG_PER_TX_CEILING_SATOSHIS)
747
- };
748
- var TIER_PRESETS = {
749
- "I'm Too Young to Die": TOO_YOUNG_TO_DIE,
750
- "Hey, Not Too Rough": HEY_NOT_TOO_ROUGH,
751
- "Hurt Me Plenty": HURT_ME_PLENTY,
752
- "Ultra-Violence": ULTRA_VIOLENCE,
753
- "Nightmare!": NIGHTMARE
754
- };
755
- function cloneSpendLimits(preset) {
756
- return {
757
- ...preset,
758
- windows: preset.windows.map((w) => ({ ...w })),
759
- require2fa: { ...preset.require2fa },
760
- sitePolicies: { ...preset.sitePolicies }
761
- };
762
- }
763
- function resolveSpendLimits(tier = "Hey, Not Too Rough", mode = "interactive", overrides) {
764
- const preset = TIER_PRESETS[tier][mode];
765
- const base = cloneSpendLimits(preset);
766
- if (!overrides) return base;
767
- return {
768
- ...base,
769
- ...overrides,
770
- // Deep-merge require2fa if provided
771
- require2fa: overrides.require2fa ? { ...base.require2fa, ...overrides.require2fa } : base.require2fa,
772
- // Deep-merge sitePolicies if provided
773
- sitePolicies: overrides.sitePolicies ? { ...base.sitePolicies, ...overrides.sitePolicies } : base.sitePolicies
774
- };
775
- }
776
- var RateLimiter = class {
777
- constructor(limits, state, now) {
778
- this.limits = limits;
779
- this.entries = state?.entries ?? [];
780
- this.broken = state?.circuitBroken ?? false;
781
- this.now = now ?? Date.now;
782
- }
783
- check(request, origin) {
784
- if (this.broken) {
785
- return { action: "block", reason: "Circuit breaker tripped \u2014 call resetLimits() to clear", severity: "trip" };
786
- }
787
- const amount = request.amount;
788
- if (!Number.isFinite(amount) || !Number.isInteger(amount) || amount <= 0) {
789
- return { action: "block", reason: "Invalid transaction amount rejected", severity: "reject" };
790
- }
791
- if (amount > BFG_PER_TX_CEILING_SATOSHIS) {
792
- return { action: "block", reason: `Exceeds BFG per-tx ceiling (${BFG_PER_TX_CEILING_SATOSHIS} sats)`, severity: "reject" };
793
- }
794
- const dayAgo = this.now() - WINDOW_MS.day;
795
- const dailyTotal = this.sumSatoshis(dayAgo);
796
- if (dailyTotal + amount > BFG_DAILY_CEILING_SATOSHIS) {
797
- return { action: "block", reason: `Exceeds BFG daily ceiling (${BFG_DAILY_CEILING_SATOSHIS} sats)`, severity: "trip" };
798
- }
799
- if (amount > this.limits.perTxMaxSatoshis) {
800
- return { action: "block", reason: `Exceeds per-tx limit (${this.limits.perTxMaxSatoshis} sats)`, severity: "reject" };
801
- }
802
- const isCustomSite = this.hasCustomPolicy(origin);
803
- const effectiveLimits = this.effectiveWindows(origin);
804
- const effectivePerTx = this.effectivePerTxMax(origin);
805
- if (effectivePerTx !== void 0 && amount > effectivePerTx) {
806
- return { action: "block", reason: `Exceeds per-tx limit for ${origin} (${effectivePerTx} sats)`, severity: "reject" };
807
- }
808
- let yellowLight;
809
- for (const wl of effectiveLimits) {
810
- const cutoff = this.now() - windowToMs(wl.window);
811
- const windowEntries = this.entriesInWindow(cutoff, isCustomSite ? origin : void 0);
812
- const totalSats = windowEntries.reduce((sum, e) => sum + e.satoshis, 0);
813
- const totalTx = windowEntries.length;
814
- if (totalSats + amount > wl.maxSatoshis) {
815
- return { action: "block", reason: `Exceeds ${wl.window} sats limit (${wl.maxSatoshis})`, severity: "window" };
816
- }
817
- if (totalTx + 1 > wl.maxTransactions) {
818
- return { action: "block", reason: `Exceeds ${wl.window} tx count limit (${wl.maxTransactions})`, severity: "window" };
819
- }
820
- if (this.limits.yellowLightThreshold < 1 && !yellowLight && totalSats + amount > wl.maxSatoshis * this.limits.yellowLightThreshold) {
821
- yellowLight = {
822
- origin,
823
- currentSpend: totalSats,
824
- limit: wl.maxSatoshis,
825
- window: wl.window,
826
- challenge: request
827
- };
828
- }
829
- }
830
- if (yellowLight) {
831
- return { action: "yellow-light", detail: yellowLight };
832
- }
833
- return { action: "allow" };
834
- }
835
- record(entry) {
836
- this.entries.push(entry);
837
- this.prune();
838
- }
839
- trip() {
840
- this.broken = true;
841
- }
842
- reset() {
843
- this.broken = false;
844
- }
845
- isBroken() {
846
- return this.broken;
847
- }
848
- getState() {
849
- return {
850
- entries: [...this.entries],
851
- circuitBroken: this.broken,
852
- hmac: ""
853
- };
854
- }
855
- hasCustomPolicy(origin) {
856
- const policy = this.limits.sitePolicies[origin];
857
- return policy?.action === "custom" && !!policy.limits;
858
- }
859
- effectiveWindows(origin) {
860
- const policy = this.limits.sitePolicies[origin];
861
- if (policy?.action === "custom" && policy.limits) {
862
- return policy.limits;
863
- }
864
- return this.limits.windows;
865
- }
866
- effectivePerTxMax(origin) {
867
- const policy = this.limits.sitePolicies[origin];
868
- if (policy?.action === "custom" && policy.perTxMaxSatoshis !== void 0) {
869
- return policy.perTxMaxSatoshis;
870
- }
871
- return void 0;
872
- }
873
- entriesInWindow(cutoff, filterOrigin) {
874
- return this.entries.filter(
875
- (e) => e.timestamp >= cutoff && (filterOrigin === void 0 || e.origin === filterOrigin)
876
- );
877
- }
878
- sumSatoshis(cutoff) {
879
- return this.entries.filter((e) => e.timestamp >= cutoff).reduce((sum, e) => sum + e.satoshis, 0);
880
- }
881
- prune() {
882
- let longestWindow = WINDOW_MS.day;
883
- for (const wl of this.limits.windows) {
884
- longestWindow = Math.max(longestWindow, windowToMs(wl.window));
885
- }
886
- if (this.limits.sitePolicies) {
887
- for (const policy of Object.values(this.limits.sitePolicies)) {
888
- if (policy?.action === "custom" && Array.isArray(policy.limits)) {
889
- for (const wl of policy.limits) {
890
- longestWindow = Math.max(longestWindow, windowToMs(wl.window));
891
- }
892
- }
893
- }
894
- }
895
- const cutoff = this.now() - longestWindow;
896
- this.entries = this.entries.filter((e) => e.timestamp >= cutoff);
897
- }
898
- };
899
-
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
- // src/storage.ts
936
- var STATE_KEY = "x402:limit-state";
937
- var POLICIES_KEY = "x402:site-policies";
938
- async function computeHmac(data, key) {
939
- const cryptoKey = await crypto.subtle.importKey(
940
- "raw",
941
- key,
942
- { name: "HMAC", hash: "SHA-256" },
943
- false,
944
- ["sign"]
945
- );
946
- const encoded = new TextEncoder().encode(data);
947
- const sig = await crypto.subtle.sign("HMAC", cryptoKey, encoded);
948
- return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
949
- }
950
- function serializeForHmac(state) {
951
- return JSON.stringify({
952
- entries: state.entries,
953
- circuitBroken: state.circuitBroken
954
- });
955
- }
956
- var LocalStorageAdapter = class {
957
- constructor(keyDeriver, storage) {
958
- this.keyDeriver = keyDeriver;
959
- this.storage = storage ?? globalThis.localStorage;
960
- }
961
- async load() {
962
- const raw = this.storage.getItem(STATE_KEY);
963
- if (!raw) return null;
964
- let state;
965
- try {
966
- state = JSON.parse(raw);
967
- } catch {
968
- console.warn("x402: limit state JSON parse failed \u2014 treating as tampered");
969
- return { entries: [], circuitBroken: true, hmac: "" };
970
- }
971
- if (this.keyDeriver) {
972
- if (!state.hmac) {
973
- console.warn("x402: limit state missing HMAC \u2014 treating as tampered");
974
- return { entries: [], circuitBroken: true, hmac: "" };
975
- }
976
- const key = await this.keyDeriver();
977
- const expected = await computeHmac(serializeForHmac(state), key);
978
- if (expected !== state.hmac) {
979
- console.warn("x402: limit state HMAC mismatch \u2014 state may have been tampered with");
980
- return { entries: [], circuitBroken: true, hmac: "" };
981
- }
982
- }
983
- state.entries = (Array.isArray(state.entries) ? state.entries : []).filter(
984
- (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
985
- );
986
- return state;
987
- }
988
- async save(state) {
989
- if (this.keyDeriver) {
990
- const key = await this.keyDeriver();
991
- state.hmac = await computeHmac(serializeForHmac(state), key);
992
- }
993
- this.storage.setItem(STATE_KEY, JSON.stringify(state));
994
- }
995
- async loadSitePolicies() {
996
- const raw = this.storage.getItem(POLICIES_KEY);
997
- if (!raw) return {};
998
- try {
999
- return JSON.parse(raw);
1000
- } catch {
1001
- return {};
1002
- }
1003
- }
1004
- async saveSitePolicies(policies) {
1005
- this.storage.setItem(POLICIES_KEY, JSON.stringify(policies));
1006
- }
1007
- };
1008
-
1009
599
  // src/x402-fetch.ts
1010
600
  var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1011
601
  function base58DecodeCheck(address) {
@@ -1078,123 +668,11 @@ async function defaultConstructProof(challenge) {
1078
668
  rawTx: result.rawTx
1079
669
  };
1080
670
  }
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
671
  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
672
  const constructProof = config.proofConstructor ?? defaultConstructProof;
1101
673
  const brc105ProofConstructor = config.brc105ProofConstructor;
1102
674
  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) {
675
+ return async function x402Fetch2(input, init) {
1198
676
  const response = await fetch(input, init);
1199
677
  if (response.status !== 402) return response;
1200
678
  const origin = extractOrigin(input);
@@ -1206,26 +684,17 @@ function createX402Fetch(config = {}) {
1206
684
  } catch {
1207
685
  return response;
1208
686
  }
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
- );
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 });
1229
698
  }
1230
699
  const brc105Version = response.headers.get("x-bsv-payment-version");
1231
700
  if (brc105Version) {
@@ -1236,52 +705,25 @@ function createX402Fetch(config = {}) {
1236
705
  } catch {
1237
706
  return response;
1238
707
  }
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
- );
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 });
1266
724
  }
1267
725
  return response;
1268
726
  };
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
727
  }
1286
728
  var singleton;
1287
729
  async function x402Fetch(input, init) {
@@ -1319,61 +761,80 @@ function extractOrigin(input) {
1319
761
  }
1320
762
  }
1321
763
 
1322
- // src/two-factor.ts
1323
- var WalletTwoFactorProvider = class {
1324
- async verify(action) {
1325
- if (typeof window === "undefined" || !window.CWI) {
1326
- return this.promptFallback(action);
1327
- }
1328
- const challengeData = `x402-2fa:${JSON.stringify(action)}:${Date.now()}`;
1329
- try {
1330
- const sig = await window.CWI.createSignature({
1331
- data: new TextEncoder().encode(challengeData),
1332
- protocolID: [1, "x402-2fa"],
1333
- keyID: "spending-limits"
1334
- });
1335
- return sig !== null;
1336
- } catch {
1337
- return false;
1338
- }
1339
- }
1340
- promptFallback(action) {
1341
- if (typeof window === "undefined" || !window.prompt) return false;
1342
- const message = describeAction(action);
1343
- const result = window.prompt(`${message}
1344
-
1345
- Type CONFIRM to proceed:`);
1346
- return result === "CONFIRM";
1347
- }
764
+ // src/autospend.ts
765
+ var TIER_CAPS = {
766
+ "I'm Too Young to Die": 1e6,
767
+ // 0.01 BSV
768
+ "Hey, Not Too Rough": 1e7,
769
+ // 0.1 BSV
770
+ "Hurt Me Plenty": 1e8,
771
+ // 1 BSV
772
+ "Ultra-Violence": 1e9,
773
+ // 10 BSV
774
+ "Nightmare!": 1e11
775
+ // 100 BSV
1348
776
  };
1349
- function describeAction(action) {
1350
- switch (action.type) {
1351
- case "circuit-breaker-reset":
1352
- return "Reset spending circuit breaker? This re-enables automated payments.";
1353
- case "tier-change":
1354
- return `Change spending tier from "${action.from}" to "${action.to}"?`;
1355
- case "high-value-tx":
1356
- return `Approve high-value payment of ${action.amount} sats to ${action.origin}?`;
1357
- case "new-site-approval":
1358
- return `Allow automated payments to ${action.origin}?`;
1359
- case "limit-override":
1360
- return `Spending limit reached: ${action.reason}
1361
- Allow this payment of ${action.amount} sats to ${action.origin}?`;
1362
- }
777
+ var WEAPON_CAPS = {
778
+ "Fists": 1e5,
779
+ "Chainsaw": 25e4,
780
+ "Pistol": 5e5,
781
+ "Shotgun": 1e6,
782
+ "Super Shotgun": 1e7,
783
+ "Chaingun": 5e7,
784
+ "Rocket Launcher": 25e7,
785
+ "Plasma Rifle": 1e9,
786
+ "BFG9000": Infinity
787
+ };
788
+ var PICKUP_PERCENTAGES = {
789
+ "Medkit": 0.1,
790
+ "Stimpak": 0.25,
791
+ "Soul Sphere": 1,
792
+ "New Game": 1
793
+ // hard-set to 100% (not additive)
794
+ };
795
+ function isValidAmount(amount) {
796
+ return Number.isFinite(amount) && amount > 0;
797
+ }
798
+ function checkPayment(amount, state, config) {
799
+ if (!isValidAmount(amount)) return "confirm";
800
+ const perTxMax = WEAPON_CAPS[config.weapon];
801
+ const effectiveMax = Math.min(perTxMax, state.balance);
802
+ return amount <= effectiveMax ? "auto" : "confirm";
803
+ }
804
+ function recordPayment(amount, state) {
805
+ if (!isValidAmount(amount)) return state;
806
+ return { balance: Math.max(0, state.balance - amount) };
807
+ }
808
+ function applyPickup(pickup, state, config, walletBalance) {
809
+ const tierCap = TIER_CAPS[config.tier];
810
+ const cap = Math.min(tierCap, walletBalance);
811
+ if (pickup === "New Game") {
812
+ return { balance: cap };
813
+ }
814
+ const bonus = Math.floor(tierCap * PICKUP_PERCENTAGES[pickup]);
815
+ return { balance: Math.min(cap, state.balance + bonus) };
816
+ }
817
+ function clampBalanceToTier(state, config, walletBalance) {
818
+ const cap = Math.min(TIER_CAPS[config.tier], walletBalance);
819
+ return { balance: Math.min(state.balance, cap) };
820
+ }
821
+ function initialState(config, walletBalance) {
822
+ const cap = Math.min(TIER_CAPS[config.tier], walletBalance);
823
+ return { balance: cap };
1363
824
  }
1364
825
  // Annotate the CommonJS export names for ESM import in node:
1365
826
  0 && (module.exports = {
1366
- BFG_DAILY_CEILING_SATOSHIS,
1367
- BFG_PER_TX_CEILING_SATOSHIS,
1368
- LocalStorageAdapter,
1369
- RateLimiter,
1370
- TIER_PRESETS,
1371
- WalletTwoFactorProvider,
827
+ PICKUP_PERCENTAGES,
828
+ TIER_CAPS,
829
+ WEAPON_CAPS,
830
+ applyPickup,
831
+ checkPayment,
832
+ clampBalanceToTier,
1372
833
  constructBrc105Proof,
1373
834
  createX402Fetch,
835
+ initialState,
1374
836
  parseBrc105Challenge,
1375
837
  parseChallenge,
1376
- resolveSitePolicy,
1377
- resolveSpendLimits,
838
+ recordPayment,
1378
839
  x402Fetch
1379
840
  });