Compare commits
No commits in common. "490605333c1a5d21ea19de50b4a785813b774de4" and "47b7efc53c7e31035387dce0a5aa2e6a53ec0af9" have entirely different histories.
490605333c
...
47b7efc53c
5 changed files with 29 additions and 153 deletions
|
|
@ -82,7 +82,7 @@ async def m001_satmachine_v2_initial(db):
|
||||||
CREATE TABLE IF NOT EXISTS spirekeeper.dca_machines (
|
CREATE TABLE IF NOT EXISTS spirekeeper.dca_machines (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
operator_user_id TEXT NOT NULL,
|
operator_user_id TEXT NOT NULL,
|
||||||
machine_npub TEXT UNIQUE,
|
machine_npub TEXT NOT NULL UNIQUE,
|
||||||
wallet_id TEXT NOT NULL,
|
wallet_id TEXT NOT NULL,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
location TEXT,
|
location TEXT,
|
||||||
|
|
@ -776,72 +776,3 @@ async def m010_add_machine_bunker_pairing(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"ALTER TABLE spirekeeper.{table} ADD COLUMN {col} {coltype}"
|
f"ALTER TABLE spirekeeper.{table} ADD COLUMN {col} {coltype}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m011_machine_npub_nullable(db):
|
|
||||||
"""Make dca_machines.machine_npub nullable so an operator can register a
|
|
||||||
machine *unpaired* (no npub) and have its identity minted by the bunker
|
|
||||||
at pairing time (model A1, aiolabs/spirekeeper#9). The npub is only
|
|
||||||
supplied up front on the development self-key path (a machine that holds
|
|
||||||
its own signing key). UNIQUE stays — NULLs don't collide, so any number
|
|
||||||
of unpaired machines coexist.
|
|
||||||
|
|
||||||
Pre-public-launch: no back-compat shim. Existing rows are preserved by
|
|
||||||
the rebuild; the column simply loses NOT NULL.
|
|
||||||
"""
|
|
||||||
if db.type != "SQLITE":
|
|
||||||
# Postgres / Cockroach can drop the constraint in place.
|
|
||||||
await db.execute(
|
|
||||||
"ALTER TABLE spirekeeper.dca_machines "
|
|
||||||
"ALTER COLUMN machine_npub DROP NOT NULL"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# SQLite can't drop NOT NULL in place — rebuild the table (same pattern
|
|
||||||
# as m008/m009), preserving every row + the indexes.
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE spirekeeper.dca_machines_new (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
operator_user_id TEXT NOT NULL,
|
|
||||||
machine_npub TEXT UNIQUE,
|
|
||||||
wallet_id TEXT NOT NULL,
|
|
||||||
name TEXT,
|
|
||||||
location TEXT,
|
|
||||||
fiat_code TEXT NOT NULL DEFAULT 'GTQ',
|
|
||||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
|
||||||
updated_at TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
|
||||||
operator_cash_in_fee_fraction DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
|
|
||||||
operator_cash_out_fee_fraction DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
|
|
||||||
bunker_spire_key_name TEXT,
|
|
||||||
paired_at TIMESTAMP
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO spirekeeper.dca_machines_new
|
|
||||||
(id, operator_user_id, machine_npub, wallet_id, name, location,
|
|
||||||
fiat_code, is_active, created_at, updated_at,
|
|
||||||
operator_cash_in_fee_fraction, operator_cash_out_fee_fraction,
|
|
||||||
bunker_spire_key_name, paired_at)
|
|
||||||
SELECT id, operator_user_id, machine_npub, wallet_id, name, location,
|
|
||||||
fiat_code, is_active, created_at, updated_at,
|
|
||||||
operator_cash_in_fee_fraction, operator_cash_out_fee_fraction,
|
|
||||||
bunker_spire_key_name, paired_at
|
|
||||||
FROM spirekeeper.dca_machines
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute("DROP TABLE spirekeeper.dca_machines")
|
|
||||||
await db.execute(
|
|
||||||
"ALTER TABLE spirekeeper.dca_machines_new RENAME TO dca_machines"
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
"CREATE INDEX IF NOT EXISTS dca_machines_operator_idx "
|
|
||||||
"ON dca_machines (operator_user_id)"
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS dca_machines_wallet_id_uq "
|
|
||||||
"ON dca_machines (wallet_id)"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
20
models.py
20
models.py
|
|
@ -28,11 +28,7 @@ class CreateMachineData(BaseModel):
|
||||||
not against any fee total. See aiolabs/satmachineadmin#37 / #38.
|
not against any fee total. See aiolabs/satmachineadmin#37 / #38.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Optional: blank = register the machine UNPAIRED — the bunker mints its
|
machine_npub: str
|
||||||
# identity at pairing (model A1, the normal path). Supplying an npub here
|
|
||||||
# is the development self-key path (a machine that holds its own signing
|
|
||||||
# key); see views_api.api_create_machine.
|
|
||||||
machine_npub: str | None = None
|
|
||||||
wallet_id: str
|
wallet_id: str
|
||||||
name: str | None = None
|
name: str | None = None
|
||||||
location: str | None = None
|
location: str | None = None
|
||||||
|
|
@ -52,7 +48,7 @@ class CreateMachineData(BaseModel):
|
||||||
class Machine(BaseModel):
|
class Machine(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
operator_user_id: str
|
operator_user_id: str
|
||||||
machine_npub: str | None # NULL until paired (or supplied on the dev self-key path)
|
machine_npub: str
|
||||||
wallet_id: str
|
wallet_id: str
|
||||||
name: str | None
|
name: str | None
|
||||||
location: str | None
|
location: str | None
|
||||||
|
|
@ -88,17 +84,11 @@ class UpdateMachineData(BaseModel):
|
||||||
class PairMachineData(BaseModel):
|
class PairMachineData(BaseModel):
|
||||||
"""Body for POST /machines/{id}/pair (S0 / #9). `relays` are the relays
|
"""Body for POST /machines/{id}/pair (S0 / #9). `relays` are the relays
|
||||||
the spire will use for its own events (kind-21000/30078) — typically the
|
the spire will use for its own events (kind-21000/30078) — typically the
|
||||||
operator's nostrrelay. `bunker_relay` overrides the relay embedded in the
|
operator's nostrrelay; the bunker connection relay is added separately
|
||||||
seed's `bunker://` URL (the relay the spire uses to *reach* the bunker);
|
from the lnbits bunker settings. `duration_hours` optionally time-bounds
|
||||||
when omitted it defaults to `settings.lnbits_nsec_bunker_url`. Set it when
|
the spire's connect token (None = non-expiring)."""
|
||||||
the relay lnbits uses to reach the bunker differs from the one the spire
|
|
||||||
must reach — e.g. an internal docker hostname (`ws://lnbits:5001/…`) vs a
|
|
||||||
LAN/public URL (`ws://192.168.0.32:5001/…`), or any split-relay deploy.
|
|
||||||
`duration_hours` optionally time-bounds the spire's connect token
|
|
||||||
(None = non-expiring)."""
|
|
||||||
|
|
||||||
relays: list[str]
|
relays: list[str]
|
||||||
bunker_relay: str | None = None
|
|
||||||
duration_hours: int | None = None
|
duration_hours: int | None = None
|
||||||
|
|
||||||
@validator("duration_hours")
|
@validator("duration_hours")
|
||||||
|
|
|
||||||
|
|
@ -581,29 +581,8 @@ window.app = Vue.createApp({
|
||||||
this.superFeeDialog.show = true
|
this.superFeeDialog.show = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// Guard the decimal-vs-percent trap shared by the super + operator fee
|
|
||||||
// forms: fees are decimal fractions (3% = 0.03), capped at 0.15. A value
|
|
||||||
// > 0.15 almost always means a percent was typed (3 instead of 0.03).
|
|
||||||
// Returns false + shows a clear toast so the operator never sees a raw 400.
|
|
||||||
_assertFeesDecimal(...fracs) {
|
|
||||||
if (fracs.some((v) => !Number.isFinite(v) || v < 0 || v > 0.15)) {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'negative',
|
|
||||||
message: 'Enter each fee as a decimal fraction (e.g. 3% = 0.03)',
|
|
||||||
caption:
|
|
||||||
'Range 0–0.15. A value above 0.15 usually means a percent was typed (3 instead of 0.03).'
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
async submitSuperFee() {
|
async submitSuperFee() {
|
||||||
const d = this.superFeeDialog.data
|
const d = this.superFeeDialog.data
|
||||||
if (!this._assertFeesDecimal(
|
|
||||||
Number(d.super_cash_in_fee_fraction),
|
|
||||||
Number(d.super_cash_out_fee_fraction)
|
|
||||||
)) return
|
|
||||||
this.superFeeDialog.saving = true
|
this.superFeeDialog.saving = true
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
|
|
@ -720,17 +699,13 @@ window.app = Vue.createApp({
|
||||||
|
|
||||||
async submitAddMachine() {
|
async submitAddMachine() {
|
||||||
const body = this._cleanMachineForm(this.addMachineDialog.data)
|
const body = this._cleanMachineForm(this.addMachineDialog.data)
|
||||||
if (!body.wallet_id) {
|
if (!body.machine_npub || !body.wallet_id) {
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: 'A wallet is required'
|
message: 'machine_npub and wallet_id are required'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!this._assertFeesDecimal(
|
|
||||||
Number(body.operator_cash_in_fee_fraction) || 0,
|
|
||||||
Number(body.operator_cash_out_fee_fraction) || 0
|
|
||||||
)) return
|
|
||||||
this.addMachineDialog.saving = true
|
this.addMachineDialog.saving = true
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request('POST', MACHINES_PATH, null, body)
|
const {data} = await LNbits.api.request('POST', MACHINES_PATH, null, body)
|
||||||
|
|
@ -738,7 +713,7 @@ window.app = Vue.createApp({
|
||||||
this.addMachineDialog.show = false
|
this.addMachineDialog.show = false
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: `Machine ${data.name || (data.machine_npub || 'unpaired').slice(0, 12)} added`
|
message: `Machine ${data.name || data.machine_npub.slice(0, 12)} added`
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._notifyError(e, 'Failed to add machine')
|
this._notifyError(e, 'Failed to add machine')
|
||||||
|
|
@ -766,10 +741,6 @@ window.app = Vue.createApp({
|
||||||
|
|
||||||
async submitEditMachine() {
|
async submitEditMachine() {
|
||||||
const d = this.editMachineDialog.data
|
const d = this.editMachineDialog.data
|
||||||
if (!this._assertFeesDecimal(
|
|
||||||
Number(d.operator_cash_in_fee_fraction) || 0,
|
|
||||||
Number(d.operator_cash_out_fee_fraction) || 0
|
|
||||||
)) return
|
|
||||||
this.editMachineDialog.saving = true
|
this.editMachineDialog.saving = true
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
|
|
@ -801,7 +772,7 @@ window.app = Vue.createApp({
|
||||||
Quasar.Dialog.create({
|
Quasar.Dialog.create({
|
||||||
title: 'Delete machine?',
|
title: 'Delete machine?',
|
||||||
message:
|
message:
|
||||||
`This removes <b>${machine.name || (machine.machine_npub || 'unpaired').slice(0, 12)}</b>` +
|
`This removes <b>${machine.name || machine.machine_npub.slice(0, 12)}</b>` +
|
||||||
' from your fleet. Existing settlements and payment history are preserved' +
|
' from your fleet. Existing settlements and payment history are preserved' +
|
||||||
' — only the machine row itself is removed. Continue?',
|
' — only the machine row itself is removed. Continue?',
|
||||||
html: true,
|
html: true,
|
||||||
|
|
@ -877,7 +848,7 @@ window.app = Vue.createApp({
|
||||||
Quasar.Dialog.create({
|
Quasar.Dialog.create({
|
||||||
title: 'Revoke spire access?',
|
title: 'Revoke spire access?',
|
||||||
message:
|
message:
|
||||||
`This cuts <b>${machine.name || (machine.machine_npub || 'unpaired').slice(0, 12)}</b>'s` +
|
`This cuts <b>${machine.name || machine.machine_npub.slice(0, 12)}</b>'s` +
|
||||||
' signing access at the bunker — the spire can no longer submit' +
|
' signing access at the bunker — the spire can no longer submit' +
|
||||||
' cash-outs until you re-pair it. Continue?',
|
' cash-outs until you re-pair it. Continue?',
|
||||||
html: true,
|
html: true,
|
||||||
|
|
@ -1544,7 +1515,7 @@ window.app = Vue.createApp({
|
||||||
// Helpers
|
// Helpers
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
shortNpub(npub) {
|
shortNpub(npub) {
|
||||||
if (!npub) return 'unpaired'
|
if (!npub) return ''
|
||||||
if (npub.length <= 16) return npub
|
if (npub.length <= 16) return npub
|
||||||
return npub.slice(0, 8) + '…' + npub.slice(-6)
|
return npub.slice(0, 8) + '…' + npub.slice(-6)
|
||||||
},
|
},
|
||||||
|
|
@ -1630,7 +1601,7 @@ window.app = Vue.createApp({
|
||||||
|
|
||||||
_cleanMachineForm(d) {
|
_cleanMachineForm(d) {
|
||||||
return {
|
return {
|
||||||
machine_npub: (d.machine_npub || '').trim() || null,
|
machine_npub: (d.machine_npub || '').trim(),
|
||||||
wallet_id: d.wallet_id,
|
wallet_id: d.wallet_id,
|
||||||
name: (d.name || '').trim() || null,
|
name: (d.name || '').trim() || null,
|
||||||
location: (d.location || '').trim() || null,
|
location: (d.location || '').trim() || null,
|
||||||
|
|
|
||||||
|
|
@ -792,13 +792,13 @@
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="addMachineDialog.data.machine_npub"
|
v-model="addMachineDialog.data.machine_npub"
|
||||||
label="Machine npub — DEVELOPMENT ONLY (blank = normal bunker pairing)"
|
label="Machine npub (hex or bech32)"
|
||||||
hint="⚠ Leave blank for normal operation: the bunker mints this machine's key when you pair it (no nsec ever lands on the machine). Only fill this to register a machine that holds its OWN signing key — development / self-signing. Hex or npub1…"
|
hint="64-char hex pubkey or npub1... bech32 string"
|
||||||
color="orange"
|
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
dense outlined
|
dense outlined
|
||||||
:rules="[
|
:rules="[
|
||||||
v => !v || v.length >= 32 || 'Looks too short — use a full hex/npub, or leave blank'
|
v => !!v || 'Required',
|
||||||
|
v => (v && v.length >= 32) || 'Looks too short'
|
||||||
]"></q-input>
|
]"></q-input>
|
||||||
|
|
||||||
<q-select
|
<q-select
|
||||||
|
|
@ -819,7 +819,7 @@
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model.number="addMachineDialog.data.operator_cash_in_fee_fraction"
|
v-model.number="addMachineDialog.data.operator_cash_in_fee_fraction"
|
||||||
label="Operator cash-in fee (decimal fraction, 0-0.15)"
|
label="Operator cash-in fee % (decimal, 0..0.15)"
|
||||||
hint="Your per-machine cut on cash-in. Sits on top of the platform fee; cap is 15% total per direction."
|
hint="Your per-machine cut on cash-in. Sits on top of the platform fee; cap is 15% total per direction."
|
||||||
type="number" step="0.0001" min="0" max="0.15"
|
type="number" step="0.0001" min="0" max="0.15"
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
|
|
@ -827,7 +827,7 @@
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model.number="addMachineDialog.data.operator_cash_out_fee_fraction"
|
v-model.number="addMachineDialog.data.operator_cash_out_fee_fraction"
|
||||||
label="Operator cash-out fee (decimal fraction, 0-0.15)"
|
label="Operator cash-out fee % (decimal, 0..0.15)"
|
||||||
hint="Your per-machine cut on cash-out. Sits on top of the platform fee; cap is 15% total per direction."
|
hint="Your per-machine cut on cash-out. Sits on top of the platform fee; cap is 15% total per direction."
|
||||||
type="number" step="0.0001" min="0" max="0.15"
|
type="number" step="0.0001" min="0" max="0.15"
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
|
|
@ -1368,12 +1368,12 @@
|
||||||
typically a wallet you (the super) own.
|
typically a wallet you (the super) own.
|
||||||
</p>
|
</p>
|
||||||
<q-input v-model.number="superFeeDialog.data.super_cash_in_fee_fraction"
|
<q-input v-model.number="superFeeDialog.data.super_cash_in_fee_fraction"
|
||||||
label="Cash-in fee (decimal fraction, 0-0.15)"
|
label="Cash-in fee % (decimal, 0..0.15)"
|
||||||
hint="0.03 = 3% of principal on cash-in transactions"
|
hint="0.03 = 3% of principal on cash-in transactions"
|
||||||
type="number" step="0.0001" min="0" max="0.15"
|
type="number" step="0.0001" min="0" max="0.15"
|
||||||
class="q-mb-md" dense outlined></q-input>
|
class="q-mb-md" dense outlined></q-input>
|
||||||
<q-input v-model.number="superFeeDialog.data.super_cash_out_fee_fraction"
|
<q-input v-model.number="superFeeDialog.data.super_cash_out_fee_fraction"
|
||||||
label="Cash-out fee (decimal fraction, 0-0.15)"
|
label="Cash-out fee % (decimal, 0..0.15)"
|
||||||
hint="0.03 = 3% of principal on cash-out transactions"
|
hint="0.03 = 3% of principal on cash-out transactions"
|
||||||
type="number" step="0.0001" min="0" max="0.15"
|
type="number" step="0.0001" min="0" max="0.15"
|
||||||
class="q-mb-md" dense outlined></q-input>
|
class="q-mb-md" dense outlined></q-input>
|
||||||
|
|
@ -1627,12 +1627,12 @@
|
||||||
<q-input v-model="editMachineDialog.data.fiat_code"
|
<q-input v-model="editMachineDialog.data.fiat_code"
|
||||||
label="Fiat code" class="q-mb-md" dense outlined></q-input>
|
label="Fiat code" class="q-mb-md" dense outlined></q-input>
|
||||||
<q-input v-model.number="editMachineDialog.data.operator_cash_in_fee_fraction"
|
<q-input v-model.number="editMachineDialog.data.operator_cash_in_fee_fraction"
|
||||||
label="Operator cash-in fee (decimal fraction, 0-0.15)"
|
label="Operator cash-in fee % (decimal, 0..0.15)"
|
||||||
hint="Sits on top of the platform cash-in fee. Cap 15% total per direction."
|
hint="Sits on top of the platform cash-in fee. Cap 15% total per direction."
|
||||||
type="number" step="0.0001" min="0" max="0.15"
|
type="number" step="0.0001" min="0" max="0.15"
|
||||||
class="q-mb-md" dense outlined></q-input>
|
class="q-mb-md" dense outlined></q-input>
|
||||||
<q-input v-model.number="editMachineDialog.data.operator_cash_out_fee_fraction"
|
<q-input v-model.number="editMachineDialog.data.operator_cash_out_fee_fraction"
|
||||||
label="Operator cash-out fee (decimal fraction, 0-0.15)"
|
label="Operator cash-out fee % (decimal, 0..0.15)"
|
||||||
hint="Sits on top of the platform cash-out fee. Cap 15% total per direction."
|
hint="Sits on top of the platform cash-out fee. Cap 15% total per direction."
|
||||||
type="number" step="0.0001" min="0" max="0.15"
|
type="number" step="0.0001" min="0" max="0.15"
|
||||||
class="q-mb-md" dense outlined></q-input>
|
class="q-mb-md" dense outlined></q-input>
|
||||||
|
|
|
||||||
28
views_api.py
28
views_api.py
|
|
@ -248,7 +248,7 @@ async def _assert_super_config_cap_safe(
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
(
|
(
|
||||||
f"super cash-in fee {effective_in:.4f} would exceed cap "
|
f"super cash-in fee {effective_in:.4f} would exceed cap "
|
||||||
f"on machine {m.id} ({m.name or (m.machine_npub or m.id)[:12]}): "
|
f"on machine {m.id} ({m.name or m.machine_npub[:12]}): "
|
||||||
f"+ operator {op_in:.4f} = "
|
f"+ operator {op_in:.4f} = "
|
||||||
f"{total_in:.4f} > {MAX_FEE_FRACTION_PER_DIRECTION}"
|
f"{total_in:.4f} > {MAX_FEE_FRACTION_PER_DIRECTION}"
|
||||||
),
|
),
|
||||||
|
|
@ -258,7 +258,7 @@ async def _assert_super_config_cap_safe(
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
(
|
(
|
||||||
f"super cash-out fee {effective_out:.4f} would exceed cap "
|
f"super cash-out fee {effective_out:.4f} would exceed cap "
|
||||||
f"on machine {m.id} ({m.name or (m.machine_npub or m.id)[:12]}): "
|
f"on machine {m.id} ({m.name or m.machine_npub[:12]}): "
|
||||||
f"+ operator {op_out:.4f} = "
|
f"+ operator {op_out:.4f} = "
|
||||||
f"{total_out:.4f} > {MAX_FEE_FRACTION_PER_DIRECTION}"
|
f"{total_out:.4f} > {MAX_FEE_FRACTION_PER_DIRECTION}"
|
||||||
),
|
),
|
||||||
|
|
@ -275,13 +275,7 @@ async def api_create_machine(
|
||||||
data: CreateMachineData, user: User = Depends(check_user_exists)
|
data: CreateMachineData, user: User = Depends(check_user_exists)
|
||||||
) -> Machine:
|
) -> Machine:
|
||||||
await _assert_wallet_owned_by(data.wallet_id, user.id)
|
await _assert_wallet_owned_by(data.wallet_id, user.id)
|
||||||
# machine_npub is optional: blank = register UNPAIRED — the bunker mints
|
await _assert_no_pubkey_collision(data.machine_npub)
|
||||||
# the identity at pairing (the normal path). An npub supplied up front is
|
|
||||||
# the development self-key path; only then do we collision-check + publish
|
|
||||||
# a fee config now (an unpaired machine has no target yet, so it gets its
|
|
||||||
# config at pairing instead — see api_pair_machine).
|
|
||||||
if data.machine_npub:
|
|
||||||
await _assert_no_pubkey_collision(data.machine_npub)
|
|
||||||
await _assert_machine_fee_cap_safe(
|
await _assert_machine_fee_cap_safe(
|
||||||
data.operator_cash_in_fee_fraction,
|
data.operator_cash_in_fee_fraction,
|
||||||
data.operator_cash_out_fee_fraction,
|
data.operator_cash_out_fee_fraction,
|
||||||
|
|
@ -290,10 +284,9 @@ async def api_create_machine(
|
||||||
# Layer 2 (#39): publish initial fee config to the ATM so it can
|
# Layer 2 (#39): publish initial fee config to the ATM so it can
|
||||||
# unblock past its `awaiting-fees` maintenance gate. Soft-fails on
|
# unblock past its `awaiting-fees` maintenance gate. Soft-fails on
|
||||||
# transport errors — machine creation has already succeeded.
|
# transport errors — machine creation has already succeeded.
|
||||||
if machine.machine_npub:
|
super_config = await get_super_config()
|
||||||
super_config = await get_super_config()
|
if super_config is not None:
|
||||||
if super_config is not None:
|
await publish_fee_config(machine, super_config, user.id)
|
||||||
await publish_fee_config(machine, super_config, user.id)
|
|
||||||
return machine
|
return machine
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -324,7 +317,6 @@ async def api_pair_machine(
|
||||||
machine,
|
machine,
|
||||||
relays=data.relays,
|
relays=data.relays,
|
||||||
admin_client=client,
|
admin_client=client,
|
||||||
bunker_relay=data.bunker_relay,
|
|
||||||
duration_hours=data.duration_hours,
|
duration_hours=data.duration_hours,
|
||||||
)
|
)
|
||||||
except NsecBunkerNotConfiguredError as exc:
|
except NsecBunkerNotConfiguredError as exc:
|
||||||
|
|
@ -345,14 +337,6 @@ async def api_pair_machine(
|
||||||
bunker_spire_key_name=result.bunker_key_name,
|
bunker_spire_key_name=result.bunker_key_name,
|
||||||
paired_at=datetime.now(timezone.utc),
|
paired_at=datetime.now(timezone.utc),
|
||||||
)
|
)
|
||||||
# Now that the machine has a bunker identity, publish its fee config so
|
|
||||||
# the spire can clear its `awaiting-fees` gate. For a machine created
|
|
||||||
# unpaired, this is the first time it has a target. Soft-fails (mirrors
|
|
||||||
# create); pairing has already succeeded.
|
|
||||||
super_config = await get_super_config()
|
|
||||||
if super_config is not None:
|
|
||||||
paired = await _machine_owned_by(machine_id, user.id)
|
|
||||||
await publish_fee_config(paired, super_config, user.id)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue