111 lines
3.4 KiB
TypeScript
111 lines
3.4 KiB
TypeScript
import { sdk } from '../sdk'
|
|
import { configFile } from '../file-models/config.json'
|
|
|
|
const { InputSpec, Value } = sdk
|
|
|
|
// Operator-editable bundle pricing for credit purchases. The bundle
|
|
// SIZES are fixed (5, 10, 20 credits — three bundles fit cleanly in
|
|
// the buyer modal's single row). Only their sats prices move via
|
|
// this action. If you need different bundle sizes, edit
|
|
// relay_credit_packages_json directly on the relay's data volume.
|
|
const inputSpec = InputSpec.of({
|
|
pkg_5_sats: Value.number({
|
|
name: '5 credit bundle — price (sats)',
|
|
description: 'Price the buyer pays for a 5-credit top-up.',
|
|
required: true,
|
|
default: 4000,
|
|
min: 1,
|
|
max: 100_000_000,
|
|
integer: true,
|
|
step: 1,
|
|
units: 'sats',
|
|
placeholder: null,
|
|
}),
|
|
pkg_10_sats: Value.number({
|
|
name: '10 credit bundle — price (sats)',
|
|
description: 'Price the buyer pays for a 10-credit top-up.',
|
|
required: true,
|
|
default: 6000,
|
|
min: 1,
|
|
max: 100_000_000,
|
|
integer: true,
|
|
step: 1,
|
|
units: 'sats',
|
|
placeholder: null,
|
|
}),
|
|
pkg_20_sats: Value.number({
|
|
name: '20 credit bundle — price (sats)',
|
|
description: 'Price the buyer pays for a 20-credit top-up.',
|
|
required: true,
|
|
default: 10000,
|
|
min: 1,
|
|
max: 100_000_000,
|
|
integer: true,
|
|
step: 1,
|
|
units: 'sats',
|
|
placeholder: null,
|
|
}),
|
|
})
|
|
|
|
const FIXED_CREDIT_SIZES = [5, 10, 20] as const
|
|
|
|
export const setCreditPackages = sdk.Action.withInput(
|
|
'set-credit-packages',
|
|
|
|
async ({ effects }) => ({
|
|
name: 'Set Credit Bundle Prices',
|
|
description:
|
|
'Per-bundle sats prices shown to buyers in the Recap credit-purchase modal. Bundle sizes (5, 10, 20 credits) are fixed by this action; edit relay_credit_packages_json directly if you need different sizes. Changes apply to the next buyer immediately — no daemon restart.',
|
|
warning: null,
|
|
allowedStatuses: 'any',
|
|
group: 'Tiers',
|
|
visibility: 'enabled',
|
|
}),
|
|
|
|
inputSpec,
|
|
|
|
async ({ effects }) => {
|
|
const config = await configFile.read().once()
|
|
// Translate the stored JSON array back into per-bundle inputs.
|
|
// We only care about the four fixed sizes; anything else in the
|
|
// stored array is preserved on save (see merge step below) but
|
|
// not exposed in the form.
|
|
let parsed: Array<{ credits: number; sats: number }> = []
|
|
try {
|
|
const raw = JSON.parse(config?.relay_credit_packages_json || '[]')
|
|
if (Array.isArray(raw)) {
|
|
parsed = raw
|
|
.map((p: any) => ({
|
|
credits: Number(p?.credits),
|
|
sats: Number(p?.sats),
|
|
}))
|
|
.filter((p) => Number.isFinite(p.credits) && Number.isFinite(p.sats))
|
|
}
|
|
} catch {
|
|
// ignored — fall back to defaults
|
|
}
|
|
const lookup = (n: number, fallback: number) =>
|
|
parsed.find((p) => p.credits === n)?.sats ?? fallback
|
|
return {
|
|
pkg_5_sats: lookup(5, 4000),
|
|
pkg_10_sats: lookup(10, 6000),
|
|
pkg_20_sats: lookup(20, 10000),
|
|
}
|
|
},
|
|
|
|
async ({ effects, input }) => {
|
|
const packages = [
|
|
{ credits: 5, sats: Number(input.pkg_5_sats) },
|
|
{ credits: 10, sats: Number(input.pkg_10_sats) },
|
|
{ credits: 20, sats: Number(input.pkg_20_sats) },
|
|
].filter(
|
|
(p) =>
|
|
Number.isFinite(p.sats) && p.sats > 0 && FIXED_CREDIT_SIZES.includes(p.credits as 5 | 10 | 20)
|
|
)
|
|
await configFile.merge(effects, {
|
|
relay_credit_packages_json: JSON.stringify(packages),
|
|
})
|
|
return null
|
|
},
|
|
)
|