@tempoxyz/patron 0.0.29 → 0.0.30

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.
@@ -960,6 +960,203 @@ function normalizePayee(input) {
960
960
  return normalizeDomain(trimmed);
961
961
  }
962
962
 
963
+ // src/lock.ts
964
+ import { open, rm, stat } from "node:fs/promises";
965
+ async function withLock(path, fn, options = {}) {
966
+ const deadline = Date.now() + (options.timeoutMs ?? 3e3);
967
+ const staleMs = options.staleMs ?? 1e4;
968
+ for (; ; ) {
969
+ try {
970
+ const handle = await open(path, "wx", 384);
971
+ await handle.close();
972
+ break;
973
+ } catch (error) {
974
+ if (error.code !== "EEXIST") throw error;
975
+ const age = await stat(path).then(
976
+ (stats) => Date.now() - stats.mtimeMs,
977
+ () => 0
978
+ );
979
+ if (age > staleMs) {
980
+ await rm(path, { force: true });
981
+ continue;
982
+ }
983
+ if (Date.now() > deadline) throw new Error(`Patron state is busy: ${path}`);
984
+ await new Promise((resolve2) => setTimeout(resolve2, 10 + Math.random() * 20));
985
+ }
986
+ }
987
+ try {
988
+ return await fn();
989
+ } finally {
990
+ await rm(path, { force: true });
991
+ }
992
+ }
993
+ async function tryLock(path, staleMs) {
994
+ try {
995
+ const handle = await open(path, "wx", 384);
996
+ await handle.close();
997
+ } catch (error) {
998
+ if (error.code !== "EEXIST") throw error;
999
+ const age = await stat(path).then(
1000
+ (stats) => Date.now() - stats.mtimeMs,
1001
+ () => 0
1002
+ );
1003
+ if (age <= staleMs) return void 0;
1004
+ await rm(path, { force: true });
1005
+ return tryLock(path, staleMs);
1006
+ }
1007
+ return () => rm(path, { force: true });
1008
+ }
1009
+
1010
+ // src/state.ts
1011
+ import { randomBytes } from "node:crypto";
1012
+ import {
1013
+ chmod,
1014
+ lstat,
1015
+ mkdir,
1016
+ readFile,
1017
+ readlink,
1018
+ rename,
1019
+ rm as rm2,
1020
+ writeFile
1021
+ } from "node:fs/promises";
1022
+ import { dirname, join, resolve } from "node:path";
1023
+ var AGENT_IDS = ["claude-code", "codex", "cursor", "amp"];
1024
+ function statePaths(home) {
1025
+ return {
1026
+ config: join(home, "config.json"),
1027
+ issuer: join(home, "issuer.json"),
1028
+ key: join(home, "key"),
1029
+ linkRefill: join(home, "link-refill.json"),
1030
+ purchaseLock: join(home, "purchase.lock"),
1031
+ refillLock: join(home, "refill.lock"),
1032
+ refillWait: join(home, "refill-wait.lock"),
1033
+ spacing: join(home, "spacing.json"),
1034
+ spacingLock: join(home, "spacing.lock"),
1035
+ stateLock: join(home, "state.lock"),
1036
+ tokens: join(home, "tokens.enc"),
1037
+ usage: join(home, "usage.json"),
1038
+ usageLock: join(home, "usage.lock"),
1039
+ wallet: join(home, "wallet.json")
1040
+ };
1041
+ }
1042
+ function monthOf(now) {
1043
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
1044
+ }
1045
+ async function ensureHome(home) {
1046
+ await mkdir(home, { mode: 448, recursive: true });
1047
+ }
1048
+ async function readJson(path) {
1049
+ try {
1050
+ return JSON.parse(await readFile(path, "utf8"));
1051
+ } catch (error) {
1052
+ if (error.code === "ENOENT") return void 0;
1053
+ throw error;
1054
+ }
1055
+ }
1056
+ async function writePrivateFile(link, contents) {
1057
+ const target = await symlinkTarget(link);
1058
+ if (target !== link) {
1059
+ try {
1060
+ return await replaceFile(target, contents);
1061
+ } catch (error) {
1062
+ const code = error.code;
1063
+ if (code !== "EACCES" && code !== "EROFS" && code !== "EPERM") throw error;
1064
+ }
1065
+ }
1066
+ await replaceFile(link, contents);
1067
+ }
1068
+ async function symlinkTarget(path, depth = 0) {
1069
+ if (depth > 8) return path;
1070
+ try {
1071
+ const stats = await lstat(path);
1072
+ if (!stats.isSymbolicLink()) return path;
1073
+ return symlinkTarget(resolve(dirname(path), await readlink(path)), depth + 1);
1074
+ } catch {
1075
+ return path;
1076
+ }
1077
+ }
1078
+ async function replaceFile(path, contents) {
1079
+ const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
1080
+ try {
1081
+ await writeFile(temporary, contents, { mode: 384 });
1082
+ await chmod(temporary, 384);
1083
+ await rename(temporary, path);
1084
+ } catch (error) {
1085
+ await rm2(temporary, { force: true }).catch(() => void 0);
1086
+ throw error;
1087
+ }
1088
+ }
1089
+ async function readConfig(home) {
1090
+ const value = await readJson(statePaths(home).config).catch(() => void 0);
1091
+ if (!value || typeof value !== "object") return void 0;
1092
+ const config = value;
1093
+ if (config.version !== 1 || typeof config.address !== "string") return void 0;
1094
+ return {
1095
+ agents: Array.isArray(config.agents) ? config.agents.filter(
1096
+ (agent) => AGENT_IDS.includes(agent)
1097
+ ) : [],
1098
+ address: config.address,
1099
+ gatewayUrl: String(config.gatewayUrl ?? ""),
1100
+ monthlyBudgetUsd: Number(config.monthlyBudgetUsd ?? 0),
1101
+ network: config.network === "testnet" ? "testnet" : "mainnet",
1102
+ paused: config.paused === true,
1103
+ relayUrl: String(config.relayUrl ?? ""),
1104
+ version: 1,
1105
+ wallet: config.wallet === "key" || config.wallet === "link" ? config.wallet : "tempo",
1106
+ ...typeof config.linkPaymentMethodId === "string" ? { linkPaymentMethodId: config.linkPaymentMethodId } : {},
1107
+ ...typeof config.name === "string" ? { name: config.name } : {},
1108
+ ...typeof config.tokensPerPage === "number" ? { tokensPerPage: config.tokensPerPage } : {}
1109
+ };
1110
+ }
1111
+ async function writeConfig(home, config) {
1112
+ await ensureHome(home);
1113
+ await writePrivateFile(
1114
+ statePaths(home).config,
1115
+ `${JSON.stringify(config, null, " ")}
1116
+ `
1117
+ );
1118
+ }
1119
+ function emptyUsage(month) {
1120
+ return { boughtTokens: 0, domains: {}, month, paidPages: 0, tippedTokens: 0 };
1121
+ }
1122
+ async function readUsage(home, now = /* @__PURE__ */ new Date()) {
1123
+ const month = monthOf(now);
1124
+ const path = statePaths(home).usage;
1125
+ const value = await readJson(path).catch(() => {
1126
+ throw unreadableUsage(path);
1127
+ });
1128
+ if (value === void 0) return emptyUsage(month);
1129
+ if (!isUsageRecord(value)) throw unreadableUsage(path);
1130
+ if (value.month !== month) return emptyUsage(month);
1131
+ return {
1132
+ boughtTokens: value.boughtTokens,
1133
+ domains: value.domains && typeof value.domains === "object" ? { ...value.domains } : {},
1134
+ month,
1135
+ paidPages: Number(value.paidPages ?? 0),
1136
+ tippedTokens: Number(value.tippedTokens ?? 0)
1137
+ };
1138
+ }
1139
+ function isUsageRecord(value) {
1140
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1141
+ const usage = value;
1142
+ return typeof usage.month === "string" && typeof usage.boughtTokens === "number" && Number.isFinite(usage.boughtTokens) && usage.boughtTokens >= 0;
1143
+ }
1144
+ function unreadableUsage(path) {
1145
+ return new Error(
1146
+ `${path} is unreadable, so Patron stops adding to your balance. Delete it to restart this month's budget count.`
1147
+ );
1148
+ }
1149
+ async function updateUsage(home, update, now = /* @__PURE__ */ new Date()) {
1150
+ await ensureHome(home);
1151
+ return withLock(statePaths(home).usageLock, async () => {
1152
+ const usage = await readUsage(home, now);
1153
+ update(usage);
1154
+ await writePrivateFile(statePaths(home).usage, `${JSON.stringify(usage)}
1155
+ `);
1156
+ return usage;
1157
+ });
1158
+ }
1159
+
963
1160
  // ../protocol/src/encoding.ts
964
1161
  function toBase64Url(bytes) {
965
1162
  let binary = "";
@@ -1164,53 +1361,6 @@ function tokensToUnits(tokens) {
1164
1361
  return BigInt(tokens) * TOKEN_PRICE_UNITS;
1165
1362
  }
1166
1363
 
1167
- // src/lock.ts
1168
- import { open, rm, stat } from "node:fs/promises";
1169
- async function withLock(path, fn, options = {}) {
1170
- const deadline = Date.now() + (options.timeoutMs ?? 3e3);
1171
- const staleMs = options.staleMs ?? 1e4;
1172
- for (; ; ) {
1173
- try {
1174
- const handle = await open(path, "wx", 384);
1175
- await handle.close();
1176
- break;
1177
- } catch (error) {
1178
- if (error.code !== "EEXIST") throw error;
1179
- const age = await stat(path).then(
1180
- (stats) => Date.now() - stats.mtimeMs,
1181
- () => 0
1182
- );
1183
- if (age > staleMs) {
1184
- await rm(path, { force: true });
1185
- continue;
1186
- }
1187
- if (Date.now() > deadline) throw new Error(`Patron state is busy: ${path}`);
1188
- await new Promise((resolve) => setTimeout(resolve, 10 + Math.random() * 20));
1189
- }
1190
- }
1191
- try {
1192
- return await fn();
1193
- } finally {
1194
- await rm(path, { force: true });
1195
- }
1196
- }
1197
- async function tryLock(path, staleMs) {
1198
- try {
1199
- const handle = await open(path, "wx", 384);
1200
- await handle.close();
1201
- } catch (error) {
1202
- if (error.code !== "EEXIST") throw error;
1203
- const age = await stat(path).then(
1204
- (stats) => Date.now() - stats.mtimeMs,
1205
- () => 0
1206
- );
1207
- if (age <= staleMs) return void 0;
1208
- await rm(path, { force: true });
1209
- return tryLock(path, staleMs);
1210
- }
1211
- return () => rm(path, { force: true });
1212
- }
1213
-
1214
1364
  export {
1215
1365
  TOKEN_PRICE_UNITS,
1216
1366
  PACK_SIZE,
@@ -1229,5 +1379,14 @@ export {
1229
1379
  formatUsd,
1230
1380
  tokensToUnits,
1231
1381
  withLock,
1232
- tryLock
1382
+ tryLock,
1383
+ AGENT_IDS,
1384
+ statePaths,
1385
+ ensureHome,
1386
+ readJson,
1387
+ writePrivateFile,
1388
+ readConfig,
1389
+ writeConfig,
1390
+ readUsage,
1391
+ updateUsage
1233
1392
  };
@@ -2,16 +2,22 @@ import { createRequire as __patronCreateRequire } from 'node:module'
2
2
  const require = __patronCreateRequire(import.meta.url)
3
3
 
4
4
  // src/settings.ts
5
+ import { existsSync } from "node:fs";
5
6
  import { homedir } from "node:os";
6
7
  import { join } from "node:path";
7
8
  var DEFAULT_GATEWAY_URL = "https://patron-gateway.tempo-dev.workers.dev";
8
9
  var DEFAULT_RELAY_URL = "https://patron-relay.tempo-dev.workers.dev";
9
- function resolveSettings(env = process.env) {
10
+ function inferHome(script) {
11
+ const match = script ? /^(.+)[/\\]cli[/\\][^/\\]+[/\\]dist[/\\]bin\.js$/.exec(script) : null;
12
+ const home = match?.[1];
13
+ return home && existsSync(join(home, "cli", "current")) ? home : void 0;
14
+ }
15
+ function resolveSettings(env = process.env, script = process.argv[1]) {
10
16
  const key = env.PATRON_PRIVATE_KEY?.trim();
11
17
  const privateKey = key && /^0x[0-9a-fA-F]{64}$/.test(key) ? key : void 0;
12
18
  return {
13
19
  gatewayUrl: env.PATRON_GATEWAY_URL || DEFAULT_GATEWAY_URL,
14
- home: env.PATRON_HOME || join(homedir(), ".patron"),
20
+ home: env.PATRON_HOME || inferHome(script) || join(homedir(), ".patron"),
15
21
  network: env.PATRON_NETWORK === "testnet" ? "testnet" : "mainnet",
16
22
  overridden: {
17
23
  gatewayUrl: Boolean(env.PATRON_GATEWAY_URL),
@@ -32,6 +38,7 @@ function settingsFor(settings, saved) {
32
38
  }
33
39
 
34
40
  export {
41
+ inferHome,
35
42
  resolveSettings,
36
43
  settingsFor
37
44
  };
@@ -1,9 +1,8 @@
1
1
  import { createRequire as __patronCreateRequire } from 'node:module'
2
2
  const require = __patronCreateRequire(import.meta.url)
3
3
  import {
4
- MAX_TOKENS_PER_PURCHASE,
5
- withLock
6
- } from "./chunk-EISHX7Q3.js";
4
+ MAX_TOKENS_PER_PURCHASE
5
+ } from "./chunk-AKOXBXV3.js";
7
6
  import {
8
7
  __export
9
8
  } from "./chunk-GTMLTZRN.js";
@@ -5075,7 +5074,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
5075
5074
  var NONE = 0;
5076
5075
  var ASSUMED = 1;
5077
5076
  var PROVEN = 2;
5078
- function isRecursive(inst, stack, resolve2) {
5077
+ function isRecursive(inst, stack, resolve) {
5079
5078
  const cached2 = recursive.get(inst);
5080
5079
  if (cached2 !== void 0)
5081
5080
  return cached2 ? PROVEN : NONE;
@@ -5085,7 +5084,7 @@ function isRecursive(inst, stack, resolve2) {
5085
5084
  let result = NONE;
5086
5085
  const check2 = (child) => {
5087
5086
  if (result !== PROVEN && child?._zod) {
5088
- const answer = isRecursive(child, stack, resolve2);
5087
+ const answer = isRecursive(child, stack, resolve);
5089
5088
  if (answer > result)
5090
5089
  result = answer;
5091
5090
  }
@@ -5096,7 +5095,7 @@ function isRecursive(inst, stack, resolve2) {
5096
5095
  const desc = Object.getOwnPropertyDescriptor(sh, key);
5097
5096
  if (spread && !desc.enumerable)
5098
5097
  continue;
5099
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve2) : NONE;
5098
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
5100
5099
  if (child > answer)
5101
5100
  answer = child;
5102
5101
  }
@@ -5160,7 +5159,7 @@ function isRecursive(inst, stack, resolve2) {
5160
5159
  break;
5161
5160
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
5162
5161
  case "lazy": {
5163
- const inner = def._cachedInner ?? (resolve2 ? inst._zod.innerType : void 0);
5162
+ const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);
5164
5163
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
5165
5164
  break;
5166
5165
  }
@@ -19861,168 +19860,11 @@ address: ${address2.toLowerCase()}
19861
19860
  name: ${name}`;
19862
19861
  }
19863
19862
 
19864
- // src/state.ts
19865
- import { randomBytes } from "node:crypto";
19866
- import {
19867
- chmod,
19868
- lstat,
19869
- mkdir,
19870
- readFile,
19871
- readlink,
19872
- rename,
19873
- rm,
19874
- writeFile
19875
- } from "node:fs/promises";
19876
- import { dirname, join, resolve } from "node:path";
19877
- var AGENT_IDS = ["claude-code", "codex", "cursor", "amp"];
19878
- function statePaths(home) {
19879
- return {
19880
- config: join(home, "config.json"),
19881
- issuer: join(home, "issuer.json"),
19882
- key: join(home, "key"),
19883
- linkRefill: join(home, "link-refill.json"),
19884
- purchaseLock: join(home, "purchase.lock"),
19885
- refillLock: join(home, "refill.lock"),
19886
- refillWait: join(home, "refill-wait.lock"),
19887
- stateLock: join(home, "state.lock"),
19888
- tokens: join(home, "tokens.enc"),
19889
- usage: join(home, "usage.json"),
19890
- usageLock: join(home, "usage.lock"),
19891
- wallet: join(home, "wallet.json")
19892
- };
19893
- }
19894
- function monthOf(now) {
19895
- return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
19896
- }
19897
- async function ensureHome(home) {
19898
- await mkdir(home, { mode: 448, recursive: true });
19899
- }
19900
- async function readJson(path) {
19901
- try {
19902
- return JSON.parse(await readFile(path, "utf8"));
19903
- } catch (error62) {
19904
- if (error62.code === "ENOENT") return void 0;
19905
- throw error62;
19906
- }
19907
- }
19908
- async function writePrivateFile(link, contents) {
19909
- const target = await symlinkTarget(link);
19910
- if (target !== link) {
19911
- try {
19912
- return await replaceFile(target, contents);
19913
- } catch (error62) {
19914
- const code = error62.code;
19915
- if (code !== "EACCES" && code !== "EROFS" && code !== "EPERM") throw error62;
19916
- }
19917
- }
19918
- await replaceFile(link, contents);
19919
- }
19920
- async function symlinkTarget(path, depth = 0) {
19921
- if (depth > 8) return path;
19922
- try {
19923
- const stats = await lstat(path);
19924
- if (!stats.isSymbolicLink()) return path;
19925
- return symlinkTarget(resolve(dirname(path), await readlink(path)), depth + 1);
19926
- } catch {
19927
- return path;
19928
- }
19929
- }
19930
- async function replaceFile(path, contents) {
19931
- const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
19932
- try {
19933
- await writeFile(temporary, contents, { mode: 384 });
19934
- await chmod(temporary, 384);
19935
- await rename(temporary, path);
19936
- } catch (error62) {
19937
- await rm(temporary, { force: true }).catch(() => void 0);
19938
- throw error62;
19939
- }
19940
- }
19941
- async function readConfig(home) {
19942
- const value = await readJson(statePaths(home).config).catch(() => void 0);
19943
- if (!value || typeof value !== "object") return void 0;
19944
- const config2 = value;
19945
- if (config2.version !== 1 || typeof config2.address !== "string") return void 0;
19946
- return {
19947
- agents: Array.isArray(config2.agents) ? config2.agents.filter(
19948
- (agent) => AGENT_IDS.includes(agent)
19949
- ) : [],
19950
- address: config2.address,
19951
- gatewayUrl: String(config2.gatewayUrl ?? ""),
19952
- monthlyBudgetUsd: Number(config2.monthlyBudgetUsd ?? 0),
19953
- network: config2.network === "testnet" ? "testnet" : "mainnet",
19954
- paused: config2.paused === true,
19955
- relayUrl: String(config2.relayUrl ?? ""),
19956
- version: 1,
19957
- wallet: config2.wallet === "key" || config2.wallet === "link" ? config2.wallet : "tempo",
19958
- ...typeof config2.linkPaymentMethodId === "string" ? { linkPaymentMethodId: config2.linkPaymentMethodId } : {},
19959
- ...typeof config2.name === "string" ? { name: config2.name } : {},
19960
- ...typeof config2.tokensPerPage === "number" ? { tokensPerPage: config2.tokensPerPage } : {}
19961
- };
19962
- }
19963
- async function writeConfig(home, config2) {
19964
- await ensureHome(home);
19965
- await writePrivateFile(
19966
- statePaths(home).config,
19967
- `${JSON.stringify(config2, null, " ")}
19968
- `
19969
- );
19970
- }
19971
- function emptyUsage(month) {
19972
- return { boughtTokens: 0, domains: {}, month, paidPages: 0, tippedTokens: 0 };
19973
- }
19974
- async function readUsage(home, now = /* @__PURE__ */ new Date()) {
19975
- const month = monthOf(now);
19976
- const path = statePaths(home).usage;
19977
- const value = await readJson(path).catch(() => {
19978
- throw unreadableUsage(path);
19979
- });
19980
- if (value === void 0) return emptyUsage(month);
19981
- if (!isUsageRecord(value)) throw unreadableUsage(path);
19982
- if (value.month !== month) return emptyUsage(month);
19983
- return {
19984
- boughtTokens: value.boughtTokens,
19985
- domains: value.domains && typeof value.domains === "object" ? { ...value.domains } : {},
19986
- month,
19987
- paidPages: Number(value.paidPages ?? 0),
19988
- tippedTokens: Number(value.tippedTokens ?? 0)
19989
- };
19990
- }
19991
- function isUsageRecord(value) {
19992
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
19993
- const usage = value;
19994
- return typeof usage.month === "string" && typeof usage.boughtTokens === "number" && Number.isFinite(usage.boughtTokens) && usage.boughtTokens >= 0;
19995
- }
19996
- function unreadableUsage(path) {
19997
- return new Error(
19998
- `${path} is unreadable, so Patron stops adding to your balance. Delete it to restart this month's budget count.`
19999
- );
20000
- }
20001
- async function updateUsage(home, update, now = /* @__PURE__ */ new Date()) {
20002
- await ensureHome(home);
20003
- return withLock(statePaths(home).usageLock, async () => {
20004
- const usage = await readUsage(home, now);
20005
- update(usage);
20006
- await writePrivateFile(statePaths(home).usage, `${JSON.stringify(usage)}
20007
- `);
20008
- return usage;
20009
- });
20010
- }
20011
-
20012
19863
  export {
20013
19864
  IssuerInfo,
20014
19865
  IssueResponse,
20015
19866
  RedeemResponse,
20016
19867
  LeaderboardResponse,
20017
19868
  PatronInfo,
20018
- profileMessage,
20019
- AGENT_IDS,
20020
- statePaths,
20021
- ensureHome,
20022
- readJson,
20023
- writePrivateFile,
20024
- readConfig,
20025
- writeConfig,
20026
- readUsage,
20027
- updateUsage
19869
+ profileMessage
20028
19870
  };
@@ -0,0 +1,179 @@
1
+ import { createRequire as __patronCreateRequire } from 'node:module'
2
+ const require = __patronCreateRequire(import.meta.url)
3
+ import {
4
+ ensureHome,
5
+ readJson,
6
+ statePaths,
7
+ withLock,
8
+ writePrivateFile
9
+ } from "./chunk-AKOXBXV3.js";
10
+
11
+ // src/refill-process.ts
12
+ import { spawn } from "node:child_process";
13
+ import { createRequire } from "node:module";
14
+ function startRefill(options = {}, script = process.argv[1]) {
15
+ try {
16
+ if (!script) return;
17
+ const [command, refillArgs] = refillCommand(script);
18
+ const args = options.delayed ? [...refillArgs, "--delay"] : refillArgs;
19
+ const child = spawn(command, args, {
20
+ detached: true,
21
+ env: process.env,
22
+ stdio: "ignore"
23
+ });
24
+ child.on("error", () => void 0);
25
+ child.unref();
26
+ } catch {
27
+ }
28
+ }
29
+ function refillCommand(script) {
30
+ try {
31
+ createRequire(script).resolve("mppx");
32
+ return [process.execPath, [...process.execArgv, script, "refill"]];
33
+ } catch {
34
+ return [
35
+ process.platform === "win32" ? "npx.cmd" : "npx",
36
+ ["-y", "@tempoxyz/patron", "refill"]
37
+ ];
38
+ }
39
+ }
40
+ function startDrain(script = process.argv[1]) {
41
+ return new Promise((resolve) => {
42
+ try {
43
+ if (!script) return resolve(false);
44
+ const child = spawn(process.execPath, [...process.execArgv, script, "pay"], {
45
+ detached: true,
46
+ env: process.env,
47
+ stdio: "ignore"
48
+ });
49
+ child.on("error", () => resolve(false));
50
+ child.on("spawn", () => {
51
+ child.unref();
52
+ resolve(true);
53
+ });
54
+ } catch {
55
+ resolve(false);
56
+ }
57
+ });
58
+ }
59
+
60
+ // src/spacing.ts
61
+ import { randomBytes } from "node:crypto";
62
+ var REDEEM_HOLD_MIN_MS = 2 * 6e4;
63
+ var REDEEM_HOLD_SPREAD_MS = 8 * 6e4;
64
+ var GATEWAY_QUIET_MS = 2 * 6e4;
65
+ var GATEWAY_WANT_MS = 10 * 6e4;
66
+ var GATEWAY_LEASE_MS = 15 * 6e4;
67
+ async function update(home, fn) {
68
+ await ensureHome(home);
69
+ const paths = statePaths(home);
70
+ return withLock(paths.spacingLock, async () => {
71
+ const value = await readJson(paths.spacing).catch(() => void 0);
72
+ const current = typeof value === "object" && value !== null ? value : {};
73
+ const { result, next } = fn(current);
74
+ if (next) await writePrivateFile(paths.spacing, JSON.stringify(next));
75
+ return result;
76
+ });
77
+ }
78
+ function redeemWait(spacing, now) {
79
+ const running = Object.values(spacing.gateways ?? {}).filter((until) => until > now);
80
+ const wanted = spacing.gatewayWanted !== void 0 && now >= spacing.gatewayWanted ? spacing.gatewayWanted + GATEWAY_WANT_MS : 0;
81
+ return Math.max(
82
+ 0,
83
+ (spacing.redeemAfter ?? 0) - now,
84
+ wanted - now,
85
+ ...running.map((until) => until - now)
86
+ );
87
+ }
88
+ function claimRedemptions(home, now, leaseMs) {
89
+ return update(home, (spacing) => {
90
+ if (redeemWait(spacing, now) > 0) return { result: false };
91
+ const until = Math.max(spacing.redeemingUntil ?? 0, now + leaseMs);
92
+ return { result: true, next: { ...spacing, redeemingUntil: until } };
93
+ });
94
+ }
95
+ function redemptionWait(home, now = Date.now()) {
96
+ return update(home, (spacing) => ({ result: redeemWait(spacing, now) }));
97
+ }
98
+ function claimGateway(home, now, random = Math.random) {
99
+ return update(home, (spacing) => {
100
+ const wait = Math.max(
101
+ 0,
102
+ (spacing.redeemingUntil ?? Number.NEGATIVE_INFINITY) + GATEWAY_QUIET_MS - now
103
+ );
104
+ if (wait > 0) {
105
+ const active = spacing.gatewayWanted !== void 0 && now < spacing.gatewayWanted + GATEWAY_WANT_MS;
106
+ return {
107
+ result: wait,
108
+ ...active ? {} : { next: { ...spacing, gatewayWanted: now } }
109
+ };
110
+ }
111
+ const id = randomBytes(6).toString("hex");
112
+ const gateways = Object.fromEntries(
113
+ Object.entries(spacing.gateways ?? {}).filter(([, until]) => until > now)
114
+ );
115
+ const { gatewayWanted: _, ...rest } = spacing;
116
+ return {
117
+ result: id,
118
+ next: { ...rest, gateways: { ...gateways, [id]: now + GATEWAY_LEASE_MS } }
119
+ };
120
+ }).then(
121
+ (result) => typeof result === "number" ? result : () => update(home, (spacing) => {
122
+ const end = Date.now();
123
+ const { [result]: _, ...gateways } = spacing.gateways ?? {};
124
+ const hold = end + REDEEM_HOLD_MIN_MS + Math.floor(random() * REDEEM_HOLD_SPREAD_MS);
125
+ return {
126
+ result: void 0,
127
+ next: {
128
+ ...spacing,
129
+ gateways,
130
+ redeemAfter: Math.max(spacing.redeemAfter ?? 0, hold)
131
+ }
132
+ };
133
+ })
134
+ );
135
+ }
136
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
137
+ var RECHECK_MS = 6e4;
138
+ async function gatewayTurn(home, fn, options = {}) {
139
+ const sleep = options.sleep ?? defaultSleep;
140
+ let waited = 0;
141
+ for (; ; ) {
142
+ const claim = await claimGateway(home, Date.now(), options.random);
143
+ if (typeof claim !== "number") {
144
+ try {
145
+ return await fn();
146
+ } finally {
147
+ await claim();
148
+ }
149
+ }
150
+ if (waited + claim > (options.maxWaitMs ?? Number.POSITIVE_INFINITY)) return void 0;
151
+ if (waited === 0) options.onWait?.(claim);
152
+ const ms = claim + 1e3;
153
+ await sleep(ms);
154
+ waited += ms;
155
+ }
156
+ }
157
+ async function awaitRedemptions(home, leaseMs, options = {}) {
158
+ const sleep = options.sleep ?? defaultSleep;
159
+ let waited = 0;
160
+ for (; ; ) {
161
+ if (options.cancelled?.()) return false;
162
+ if (await claimRedemptions(home, Date.now(), leaseMs)) return true;
163
+ const wait = await redemptionWait(home);
164
+ if (waited + wait > (options.maxWaitMs ?? Number.POSITIVE_INFINITY)) return false;
165
+ if (waited === 0) options.onWait?.(wait);
166
+ const ms = Math.min(Math.max(wait, 1e3), RECHECK_MS);
167
+ await sleep(ms);
168
+ waited += ms;
169
+ }
170
+ }
171
+
172
+ export {
173
+ startRefill,
174
+ startDrain,
175
+ claimRedemptions,
176
+ redemptionWait,
177
+ gatewayTurn,
178
+ awaitRedemptions
179
+ };