bsv-x402 0.5.0 → 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 +68 -459
- package/dist/index.d.cts +44 -142
- package/dist/index.d.ts +44 -142
- package/dist/index.js +60 -451
- package/package.json +1 -1
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
resolveSpendLimits: () => resolveSpendLimits,
|
|
34
|
+
recordPayment: () => recordPayment,
|
|
35
35
|
x402Fetch: () => x402Fetch
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -761,471 +761,80 @@ function extractOrigin(input) {
|
|
|
761
761
|
}
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
-
// src/
|
|
765
|
-
var
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
|
772
776
|
};
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
)
|
|
908
|
-
};
|
|
909
|
-
var NIGHTMARE = {
|
|
910
|
-
interactive: makeLimits([], BFG_PER_TX_CEILING_SATOSHIS),
|
|
911
|
-
programmatic: makeLimits([], BFG_PER_TX_CEILING_SATOSHIS)
|
|
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
|
|
912
787
|
};
|
|
913
|
-
var
|
|
914
|
-
"
|
|
915
|
-
"
|
|
916
|
-
"
|
|
917
|
-
"
|
|
918
|
-
|
|
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)
|
|
919
794
|
};
|
|
920
|
-
function
|
|
921
|
-
return
|
|
922
|
-
...preset,
|
|
923
|
-
windows: preset.windows.map((w) => ({ ...w })),
|
|
924
|
-
require2fa: { ...preset.require2fa },
|
|
925
|
-
sitePolicies: { ...preset.sitePolicies }
|
|
926
|
-
};
|
|
795
|
+
function isValidAmount(amount) {
|
|
796
|
+
return Number.isFinite(amount) && amount > 0;
|
|
927
797
|
}
|
|
928
|
-
function
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
|
|
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
|
-
};
|
|
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";
|
|
940
803
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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("");
|
|
804
|
+
function recordPayment(amount, state) {
|
|
805
|
+
if (!isValidAmount(amount)) return state;
|
|
806
|
+
return { balance: Math.max(0, state.balance - amount) };
|
|
1079
807
|
}
|
|
1080
|
-
function
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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) };
|
|
1085
816
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
-
}
|
|
817
|
+
function clampBalanceToTier(state, config, walletBalance) {
|
|
818
|
+
const cap = Math.min(TIER_CAPS[config.tier], walletBalance);
|
|
819
|
+
return { balance: Math.min(state.balance, cap) };
|
|
1180
820
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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 };
|
|
821
|
+
function initialState(config, walletBalance) {
|
|
822
|
+
const cap = Math.min(TIER_CAPS[config.tier], walletBalance);
|
|
823
|
+
return { balance: cap };
|
|
1215
824
|
}
|
|
1216
825
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1217
826
|
0 && (module.exports = {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
827
|
+
PICKUP_PERCENTAGES,
|
|
828
|
+
TIER_CAPS,
|
|
829
|
+
WEAPON_CAPS,
|
|
830
|
+
applyPickup,
|
|
831
|
+
checkPayment,
|
|
832
|
+
clampBalanceToTier,
|
|
1224
833
|
constructBrc105Proof,
|
|
1225
834
|
createX402Fetch,
|
|
835
|
+
initialState,
|
|
1226
836
|
parseBrc105Challenge,
|
|
1227
837
|
parseChallenge,
|
|
1228
|
-
|
|
1229
|
-
resolveSpendLimits,
|
|
838
|
+
recordPayment,
|
|
1230
839
|
x402Fetch
|
|
1231
840
|
});
|