feat: Layer 2 — publish operator fee config to bitspire via Nostr (kind-30078) #39
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Layer 2 of the operator-configurable fee architecture (parent: #37).
Wire side: satmachineadmin publishes; bitspire consumes (
aiolabs/lamassu-next#57is the consumer half — needs coordinated wire-format agreement before either side ships).Locked design decisions (per #37)
schema_versionin payload — version-gated upgrade path preserved for future fields (notably future promo additions).What ships (publisher side)
Mirrors the cassette-config publisher pattern from #29 (PR #30) — same kind, same encryption envelope, same trigger points. Operator-config-over-Nostr is now a multi-document pattern; this issue adds the second document type.
Wire format (draft — needs lamassu-next ack on #57)
30078(NIP-78 addressable app data)bitspire-fees:<atm_pubkey_hex>(sibling tobitspire-cassettes:<atm_pubkey_hex>)#ptag:[atm_pubkey_hex](recipient — the ATM the fees apply to)lnbits.core.signers.resolve_signerper PR #30's signer-abstraction pattern)cash_in_fee_fractionandcash_out_fee_fractionare independent fields carryingsuper_cash_*_fee_fraction + operator_cash_*_fee_fractionper direction. Both mandatory.schema_versionlets future evolutions (promo discounts, per-currency rates, time-of-day variations) carry version-gated payload shapes without breaking v1 consumers.published_atis a UTC Unix timestamp; ATM accepts the most-recent-by-created_atevent regardless of order and rejects events older than the last-appliedpublished_at(watermark dedup, same pattern as cassette state).Trigger points (publisher)
api_create_machinepublishes initial fee config immediately after_assert_no_pubkey_collision+create_machine. If bothoperator_cash_*_fee_fraction=0(default), publishes anyway so ATM has authoritativecash_*_fee_fraction = super_cash_*only.api_update_machinerepublishes if eitheroperator_cash_*_fee_fractionchanges.update_super_configrepublishes to every active machine (one event per machine; per-machine payload combines that machine's operator fractions with the new super fractions).Computed fields
cash_in_fee_fraction=super.super_cash_in_fee_fraction + machine.operator_cash_in_fee_fractioncash_out_fee_fraction=super.super_cash_out_fee_fraction + machine.operator_cash_out_fee_fractionBoth capped at 1.0 (reject above with 400 on the satmachineadmin-side update path).
Soft-fail behavior
If publish fails (relay unreachable, signer error, nostrclient extension absent), log + skip — same soft-fail discipline as
cassette_transport.publish_to_atmin PR #30. Machine creation/update succeeds locally; the operator gets a non-fatal warning in the UI ("fee config not yet pushed to ATM; will retry on next machine edit or super-config save"). Background catch-up republish task is out-of-scope for v1.Still-open design questions (need lnbits / bitspire input)
super + operatorper direction? Hardcoded ceiling (my straw is 25% per direction), super-admin-settable, or unrestricted? Today's bitspire defaults of 3.33% / 7.77% suggest 25% would be a reasonable per-direction ceiling that doesn't constrain real-world operators but flags obvious typos.enforce_fee_matchdefault — once Layers 2+3 ship, should satmachineadmin reject settlements whosefee_satsdoesn't matchprincipal × (super + operator)± tolerance? Recommend "yes, default-on" once the wire is reliable.bitspire-cassettes:from #29,bitspire-fees:here). Is "one d-tag per document type" the right scaling shape, or should we converge on one envelope with multi-key payload? Flagging for lnbits-session review.Future-proofing for promos
schema_versionis the migration vehicle — a future v2 payload can adddiscounts: [{npub, applies_to: "super"|"operator", fraction, expires_at}, ...]or similar. v1 consumers ignore unknown fields gracefully. No code added in this issue attempts to implement promos.Tests
test_publish_fee_config_on_machine_create— new machine triggers a kind-30078 event with correct d-tag and content (both directions populated)test_publish_fee_config_on_machine_update_fee_change— updating either operator fee fraction triggers republish; updating other fields (name, location) does NOTtest_publish_fee_config_on_super_config_update— changing eithersuper_cash_*_fee_fractiontriggers republish to every active machinetest_publish_payload_shape_includes_schema_version— JSON content matches the agreed wire format withschema_version: 1test_publish_soft_fails_when_nostrclient_missing— relay/signer absence logs warning, doesn't break the API calltest_total_fee_cap_validation_per_direction— super + operator > cap raises 400 in the affected direction (TBD once design question 1 answered)Parent: #37
Partner issue:
aiolabs/lamassu-next#57(consumer side).operator_fee_fraction+ principal-based split math (closes super under-payment) #38