@1sat/cli 0.0.85 → 0.0.86

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1sat/cli",
3
- "version": "0.0.85",
3
+ "version": "0.0.86",
4
4
  "description": "CLI for 1Sat Ordinals SDK",
5
5
  "type": "module",
6
6
  "main": "./src/cli.ts",
@@ -30,7 +30,7 @@
30
30
  "@1sat/client": "0.0.45",
31
31
  "@1sat/types": "0.0.36",
32
32
  "@1sat/wallet-node": "0.0.62",
33
- "@1sat/wallet-server": "0.0.31",
33
+ "@1sat/wallet-server": "0.0.32",
34
34
  "@bsv/sdk": "^2.1.6",
35
35
  "@bsv/wallet-toolbox": "2.1.24",
36
36
  "chalk": "^5.0.0",
@@ -34,6 +34,7 @@ import { initLogger } from 'evlog'
34
34
  import knexLib from 'knex'
35
35
  import type { GlobalFlags } from '../args'
36
36
  import {
37
+ type RepricerConfig,
37
38
  type ServerAccountsConfig,
38
39
  type ServerMessageboxConfig,
39
40
  type ServerStorageConfig,
@@ -46,6 +47,7 @@ import { loadKey } from '../keys'
46
47
  import { clearMonitorPid, writeMonitorPid } from '../monitor-lock'
47
48
  import { fatal } from '../output'
48
49
  import {
50
+ type RepriceTarget,
49
51
  buildPriceUpdateTask,
50
52
  createAccountsConfigLoader,
51
53
  resolveRateProvider,
@@ -57,6 +59,7 @@ const DEFAULT_ONESAT_URL = 'https://api.1sat.app/1sat'
57
59
  const DEFAULT_BASELINE_BYTES = 1024 * 1024 * 1024 // 1 GB
58
60
  const DEFAULT_PURCHASE_UNIT_BYTES = 1_073_741_824 // 1 GB chunks for production
59
61
  const DEFAULT_SATS_PER_UNIT = 1_000_000
62
+ const DEFAULT_HOSTING_PRICE_SATS = 10_000
60
63
  const DEFAULT_DURATION_BLOCKS = 4383
61
64
  const DEFAULT_STORAGE_IDENTITY_KEY = '1sat-cli-default'
62
65
 
@@ -74,6 +77,7 @@ interface ResolvedServe {
74
77
  activeRemote?: string
75
78
  backups?: string[]
76
79
  accounts: ResolvedAccounts
80
+ repricer?: RepricerConfig
77
81
  monitorEnabled: boolean
78
82
  privateKey: PrivateKey
79
83
  }
@@ -85,14 +89,7 @@ interface ResolvedAccounts {
85
89
  satsPerUnit: number
86
90
  durationBlocks: number
87
91
  freeIdentityKeys: string[]
88
- repricer?: {
89
- enabled?: boolean
90
- targetUsd?: number
91
- intervalMs?: number
92
- provider?: string
93
- maxMovePct?: number
94
- minSats?: number
95
- }
92
+ targetUsd?: number
96
93
  }
97
94
 
98
95
  export async function handleServeCommand(
@@ -179,7 +176,8 @@ async function resolveServe(opts: GlobalFlags): Promise<ResolvedServe> {
179
176
  activeRemote: config.activeRemote,
180
177
  backups: config.backups,
181
178
  accounts: resolveAccounts(server.accounts),
182
- monitorEnabled: server.monitor?.enabled !== false,
179
+ repricer: server.repricer,
180
+ monitorEnabled: resolveMonitorEnabled(server.monitor?.enabled),
183
181
  privateKey,
184
182
  }
185
183
  }
@@ -188,6 +186,19 @@ function deriveSqliteFilename(dataDir: string, chain: string): string {
188
186
  return join(dataDir, `wallet-${chain}.db`)
189
187
  }
190
188
 
189
+ /**
190
+ * Env beats config, default on. ONESAT_MONITOR=false is set per-process by
191
+ * clustered deployments (PM2 ecosystem) that run a dedicated `serve monitor`;
192
+ * a plain `1sat serve` runs all-in-one.
193
+ */
194
+ function resolveMonitorEnabled(configured: boolean | undefined): boolean {
195
+ const fromEnv = process.env.ONESAT_MONITOR
196
+ if (fromEnv && fromEnv.trim() !== '') {
197
+ return !['false', '0', 'off'].includes(fromEnv.trim().toLowerCase())
198
+ }
199
+ return configured !== false
200
+ }
201
+
191
202
  function resolvePort(configured: number | undefined): number {
192
203
  const fromEnv = process.env.ONESAT_PORT
193
204
  if (fromEnv && fromEnv.trim() !== '') {
@@ -224,7 +235,7 @@ function resolveAccounts(accounts?: ServerAccountsConfig): ResolvedAccounts {
224
235
  satsPerUnit: accounts?.satsPerUnit ?? DEFAULT_SATS_PER_UNIT,
225
236
  durationBlocks: accounts?.durationBlocks ?? DEFAULT_DURATION_BLOCKS,
226
237
  freeIdentityKeys: accounts?.freeIdentityKeys ?? [],
227
- repricer: accounts?.repricer,
238
+ targetUsd: accounts?.targetUsd,
228
239
  }
229
240
  }
230
241
 
@@ -273,14 +284,32 @@ async function runWithStorage(
273
284
  : await startWalletServer(resolved, walletResult, accounts, mode)
274
285
 
275
286
  if (runMonitor) {
276
- const accountsCfg = resolved.accounts
277
- const r = accountsCfg.repricer
278
- if (
279
- accountsCfg.enabled &&
280
- r?.enabled &&
281
- typeof r.targetUsd === 'number' &&
282
- r.targetUsd > 0
283
- ) {
287
+ const r = resolved.repricer
288
+ const targets: RepriceTarget[] = []
289
+ if (resolved.accounts.enabled && (resolved.accounts.targetUsd ?? 0) > 0) {
290
+ targets.push({
291
+ name: 'accounts',
292
+ targetUsd: resolved.accounts.targetUsd!,
293
+ readCurrentSats: () =>
294
+ loadConfig().server?.accounts?.satsPerUnit ?? DEFAULT_SATS_PER_UNIT,
295
+ onPersist: async (sats) => {
296
+ setConfigPath('server.accounts.satsPerUnit', sats)
297
+ },
298
+ })
299
+ }
300
+ const hostingCfg = loadConfig().server?.hosting
301
+ if (hostingCfg?.enabled === true && (hostingCfg.targetUsd ?? 0) > 0) {
302
+ targets.push({
303
+ name: 'hosting',
304
+ targetUsd: hostingCfg.targetUsd!,
305
+ readCurrentSats: () =>
306
+ loadConfig().server?.hosting?.priceSats ?? DEFAULT_HOSTING_PRICE_SATS,
307
+ onPersist: async (sats) => {
308
+ setConfigPath('server.hosting.priceSats', sats)
309
+ },
310
+ })
311
+ }
312
+ if (r?.enabled && targets.length > 0) {
284
313
  walletResult.monitor.addTask(
285
314
  buildPriceUpdateTask({
286
315
  monitor: walletResult.monitor,
@@ -288,20 +317,17 @@ async function runWithStorage(
288
317
  chain: resolved.chain,
289
318
  }),
290
319
  intervalMs: r.intervalMs ?? 15 * 60 * 1000,
291
- targetUsd: r.targetUsd,
292
320
  bounds: {
293
321
  maxMovePct: r.maxMovePct ?? 25,
294
322
  minSats: r.minSats ?? 1,
295
323
  },
296
- readCurrentSats: () =>
297
- loadConfig().server?.accounts?.satsPerUnit ?? DEFAULT_SATS_PER_UNIT,
298
- onPersist: async (sats) => {
299
- setConfigPath('server.accounts.satsPerUnit', sats)
300
- },
324
+ targets,
301
325
  }),
302
326
  )
303
327
  console.log(
304
- `[repricer] enabled — $${r.targetUsd}/unit every ${Math.round(
328
+ `[repricer] enabled — ${targets
329
+ .map((t) => `${t.name} $${t.targetUsd}`)
330
+ .join(', ')} every ${Math.round(
305
331
  (r.intervalMs ?? 900_000) / 1000,
306
332
  )}s via ${r.provider ?? 'whatsonchain'}`,
307
333
  )
@@ -348,7 +374,8 @@ async function startWalletServer(
348
374
  const h = loadConfig().server?.hosting
349
375
  return {
350
376
  enabled: h?.enabled === true,
351
- priceSats: h?.priceSats ?? 10_000,
377
+ priceSats: h?.priceSats ?? DEFAULT_HOSTING_PRICE_SATS,
378
+ priceUsd: h?.targetUsd,
352
379
  periodSeconds: h?.periodSeconds ?? 2_592_000,
353
380
  }
354
381
  },
@@ -400,12 +427,13 @@ async function startWalletServer(
400
427
  accounts: accounts?.walletServerAccounts,
401
428
  hosting,
402
429
  paymail: {
403
- baseUrl: paymailCfg?.baseUrl ?? `http://${resolved.host}:${resolved.port}`,
430
+ baseUrl:
431
+ paymailCfg?.baseUrl ?? `http://${resolved.host}:${resolved.port}`,
404
432
  stackUrl: paymailCfg?.stackUrl ?? 'https://api.1sat.app',
405
433
  pendingStore,
406
434
  hostWallet: hostingEnabled ? walletResult.wallet : undefined,
407
435
  requireEntitlement: hostingEnabled,
408
- messageboxUrl: `http://127.0.0.1:${resolved.port}/`,
436
+ messageboxUrl: `http://127.0.0.1:${resolved.port}/messagebox`,
409
437
  hostPrivateKey: resolved.privateKey,
410
438
  },
411
439
  messagebox: {
@@ -440,7 +468,8 @@ function createMessageboxKnex(
440
468
  ) {
441
469
  if (storage.provider === 'bun-sqlite') {
442
470
  const filename =
443
- messagebox?.dbPath ?? join(resolved.dataDir, `messagebox-${resolved.chain}.db`)
471
+ messagebox?.dbPath ??
472
+ join(resolved.dataDir, `messagebox-${resolved.chain}.db`)
444
473
  return (knexLib as any)({
445
474
  client: 'sqlite3',
446
475
  connection: { filename },
package/src/config.ts CHANGED
@@ -25,18 +25,23 @@ export type ServerStorageConfig =
25
25
  | ServerStorageBunSqliteConfig
26
26
  | ServerStoragePgConfig
27
27
 
28
+ /**
29
+ * Shared repricer engine (`server.repricer`). When enabled, a monitor task
30
+ * fetches the BSV/USD rate on the interval and rewrites each service's sats
31
+ * price toward its own `targetUsd` (`server.accounts.targetUsd` →
32
+ * `satsPerUnit`, `server.hosting.targetUsd` → `priceSats`). Services with no
33
+ * `targetUsd` set are left alone.
34
+ */
28
35
  export interface RepricerConfig {
29
36
  /** Master toggle. Defaults to false. */
30
37
  enabled?: boolean
31
- /** Target price per `purchaseUnitBytes` chunk, in USD. */
32
- targetUsd?: number
33
38
  /** Interval between rate checks in ms. Defaults to 900000 (15 min). */
34
39
  intervalMs?: number
35
40
  /** Rate provider name. Defaults to "whatsonchain". */
36
41
  provider?: string
37
42
  /** Max percent change allowed per update. Larger moves are skipped. Defaults to 25. */
38
43
  maxMovePct?: number
39
- /** Lower bound for `satsPerUnit`. Defaults to 1. */
44
+ /** Lower bound for repriced sats values. Defaults to 1. */
40
45
  minSats?: number
41
46
  }
42
47
 
@@ -49,12 +54,12 @@ export interface ServerAccountsConfig {
49
54
  purchaseUnitBytes?: number
50
55
  /** Sats charged per purchase unit. */
51
56
  satsPerUnit?: number
57
+ /** USD target per purchase unit, maintained by `server.repricer`. */
58
+ targetUsd?: number
52
59
  /** Block window a payment remains valid for. */
53
60
  durationBlocks?: number
54
61
  /** Identity keys that bypass metering (server's own key is auto-added). */
55
62
  freeIdentityKeys?: string[]
56
- /** Optional auto-repricer. When enabled, updates `satsPerUnit` from a live BSV/USD rate. */
57
- repricer?: RepricerConfig
58
63
  }
59
64
 
60
65
  export interface ServerMessageboxConfig {
@@ -123,6 +128,12 @@ export interface ServerHostingConfig {
123
128
  enabled?: boolean
124
129
  /** Sats per subscription period. */
125
130
  priceSats?: number
131
+ /**
132
+ * USD price per subscription period. Returned as `priceUsd` on the
133
+ * pricing API for display, and maintained as the repricing target for
134
+ * `priceSats` when `server.repricer` is enabled.
135
+ */
136
+ targetUsd?: number
126
137
  /** Period length in seconds (e.g. 2592000 ≈ 30 days). */
127
138
  periodSeconds?: number
128
139
  }
@@ -153,6 +164,8 @@ export interface ServerConfig {
153
164
  paymail?: ServerPaymailConfig
154
165
  /** Hosted paymail subscription (wallet serve /hosting/*). */
155
166
  hosting?: ServerHostingConfig
167
+ /** Shared BSV/USD repricer engine for accounts + hosting prices. */
168
+ repricer?: RepricerConfig
156
169
  /** Background monitor task loop. Defaults to enabled. */
157
170
  monitor?: ServerMonitorConfig
158
171
  }
@@ -1,18 +1,25 @@
1
1
  import { computeReprice } from './computeReprice'
2
2
  import type { RateProvider, RepricerBounds } from './types'
3
3
 
4
+ /** One price the repricer maintains from the shared BSV/USD quote. */
5
+ export interface RepriceTarget {
6
+ /** Label for task result logs, e.g. 'accounts' or 'hosting'. */
7
+ name: string
8
+ /** USD target for this price. Targets with 0 or negative are ignored. */
9
+ targetUsd: number
10
+ /** Read the current sats value (typically from disk via the same loader the servers use). */
11
+ readCurrentSats: () => number
12
+ /** Persist the new sats value (wraps `setConfigPath`). */
13
+ onPersist: (newSats: number) => Promise<void>
14
+ }
15
+
4
16
  export interface PriceUpdateTaskOptions {
5
17
  monitor: unknown
6
18
  rateProvider: RateProvider
7
19
  /** Interval between successful tick attempts. */
8
20
  intervalMs: number
9
- /** USD target per purchase unit. Task no-ops when this is 0. */
10
- targetUsd: number
11
21
  bounds: RepricerBounds
12
- /** Read current `satsPerUnit` (typically from disk via the same loader the servers use). */
13
- readCurrentSats: () => number
14
- /** Persist the new `satsPerUnit` value. Wraps `setConfigPath('server.accounts.satsPerUnit', sats)`. */
15
- onPersist: (newSats: number) => Promise<void>
22
+ targets: RepriceTarget[]
16
23
  }
17
24
 
18
25
  export interface PriceUpdateTask {
@@ -28,15 +35,8 @@ export interface PriceUpdateTask {
28
35
  export function buildPriceUpdateTask(
29
36
  options: PriceUpdateTaskOptions,
30
37
  ): PriceUpdateTask {
31
- const {
32
- monitor,
33
- rateProvider,
34
- intervalMs,
35
- targetUsd,
36
- bounds,
37
- readCurrentSats,
38
- onPersist,
39
- } = options
38
+ const { monitor, rateProvider, intervalMs, bounds } = options
39
+ const targets = options.targets.filter((t) => t.targetUsd > 0)
40
40
 
41
41
  const storage = (monitor as { storage?: unknown } | null | undefined)?.storage
42
42
 
@@ -47,7 +47,7 @@ export function buildPriceUpdateTask(
47
47
  lastRunMsecsSinceEpoch: 0,
48
48
  async asyncSetup() {},
49
49
  trigger(now: number): { run: boolean } {
50
- if (!(targetUsd > 0)) return { run: false }
50
+ if (targets.length === 0) return { run: false }
51
51
  if (now - this.lastRunMsecsSinceEpoch < intervalMs) return { run: false }
52
52
  return { run: true }
53
53
  },
@@ -59,22 +59,32 @@ export function buildPriceUpdateTask(
59
59
  return `rate fetch failed: ${(err as Error).message}`
60
60
  }
61
61
 
62
- const currentSats = readCurrentSats()
63
- const result = computeReprice({
64
- targetUsd,
65
- bsvUsd: quote.bsvUsd,
66
- currentSats,
67
- bounds,
68
- })
62
+ const parts: string[] = []
63
+ for (const target of targets) {
64
+ const currentSats = target.readCurrentSats()
65
+ const result = computeReprice({
66
+ targetUsd: target.targetUsd,
67
+ bsvUsd: quote.bsvUsd,
68
+ currentSats,
69
+ bounds,
70
+ })
69
71
 
70
- if (result.status === 'skipped') return `skipped: ${result.reason}`
72
+ if (result.status === 'skipped') {
73
+ parts.push(`${target.name}: skipped: ${result.reason}`)
74
+ continue
75
+ }
71
76
 
72
- try {
73
- await onPersist(result.newSats)
74
- } catch (err) {
75
- return `persist failed: ${(err as Error).message}`
77
+ try {
78
+ await target.onPersist(result.newSats)
79
+ } catch (err) {
80
+ parts.push(
81
+ `${target.name}: persist failed: ${(err as Error).message}`,
82
+ )
83
+ continue
84
+ }
85
+ parts.push(`${target.name}: ${currentSats} → ${result.newSats} sats`)
76
86
  }
77
- return `${currentSats} → ${result.newSats} sats/unit @ $${quote.bsvUsd}/BSV`
87
+ return `${parts.join('; ')} @ $${quote.bsvUsd}/BSV`
78
88
  },
79
89
  }
80
90
  }
@@ -1,5 +1,8 @@
1
1
  export { buildPriceUpdateTask } from './buildPriceUpdateTask'
2
- export type { PriceUpdateTaskOptions } from './buildPriceUpdateTask'
2
+ export type {
3
+ PriceUpdateTaskOptions,
4
+ RepriceTarget,
5
+ } from './buildPriceUpdateTask'
3
6
  export { computeReprice } from './computeReprice'
4
7
  export { createAccountsConfigLoader } from './configLoader'
5
8
  export type { AccountsConfigLoaderOptions } from './configLoader'