husdledger

package
v1.49.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package husdledger wires the chain-backed credit ledger (treasury mint service + husdindex projector, Steps 2-3) to commerce's production per-org SQLite stores, and exposes the ONE mint entrypoint (Service.MintCredit) the billing handlers call in place of a direct DB credit write (Step 4).

Decomplected from package treasury on purpose: treasury/husdindex stay free of the datastore (cgo) dependency so their logic is unit-tested + live-proved CGO-free; the datastore-touching adapters live here and are CI-tested.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetDefault

func SetDefault(s *Service)

SetDefault installs the process-wide chain-backed ledger service. Called once at Bootstrap after the DB is wired.

func ValidateConfig

func ValidateConfig(cfg husd.Config, seed []byte) error

ValidateConfig reports a FATAL chain-ledger misconfiguration that a caller must refuse to boot on: a derivation seed is supplied — the chain ledger is clearly intended — but the HUSD token address and/or treasury key are missing, so the ledger cannot run and would silently fall back to the DB credit path for a ledger the operator meant to enable. Fail closed.

The asymmetry is deliberate: HUSD token+key WITHOUT a seed is NOT an error — it is the inert state. The shared HUSD config (util/husd) is ALSO used by the OSS contributor payout path, which needs a token+key but no ledger seed; there the ledger stays disabled (Enabled()==false) and the existing DB credit path is used, exactly as before the chain ledger existed. The seed is the ledger's SOLE intent signal, so only "seed set but not Configured" is a partial/incoherent config worth refusing.

Types

type MigrateResult

type MigrateResult struct {
	Org               string `json:"org"`
	Subjects          int    `json:"subjects"`
	DBBalanceCents    int64  `json:"dbBalanceCents"`    // pre-migration DB balance (org total)
	MintedCents       int64  `json:"mintedCents"`       // total minted on chain this run
	ChainBalanceCents int64  `json:"chainBalanceCents"` // post-migration on-chain balanceOf(org)
	PostDBCents       int64  `json:"postDbCents"`       // post-migration DB balance (must equal DBBalance)
	Reconciled        bool   `json:"reconciled"`        // chain == DB == postDB, exact to the cent
	DryRun            bool   `json:"dryRun"`
	Reason            string `json:"reason,omitempty"`
}

MigrateResult is one org's migration outcome.

type Option

type Option func(*Service)

Option configures a Service (chain-seam injection for tests).

func WithBalanceReader

func WithBalanceReader(br husdindex.BalanceReader) Option

WithBalanceReader overrides the on-chain balance reader used by settlement + migration reconcile (tests inject a fake balanceOf so drift/reconcile is proven without a chain).

func WithIndexReader

func WithIndexReader(r husdindex.Reader) Option

WithIndexReader overrides the indexer's chain reader (tests feed the projector synthetic Transfers so MintCredit's projection runs without a chain).

func WithMintTransfer

func WithMintTransfer(fn blockchain.TokenTransferFn) Option

WithMintTransfer overrides the treasury mint transfer (tests fake it).

func WithSettleTransfer

func WithSettleTransfer(fn blockchain.TokenTransferFn) Option

WithSettleTransfer overrides the org→treasury transfer function (tests inject a fake so the settlement flow is proven without a chain).

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the chain-backed credit ledger, wired to production stores. It owns the treasury mint service (Steps 1-2), the on-chain indexer/projector (Step 3), and the settlement job (Step 5). MintCredit is the ONE way a credit is created once the chain path is enabled: a treasury-signed on-chain HUSD issuance whose value is projected back into the commerce ledger the balance endpoint reads.

When the chain path is NOT configured (no token/key/seed), Enabled() is false and the billing handlers keep their existing DB-mint behavior — so a deploy without HUSD configured is unchanged, and the migration (Step 6) flips reads over deliberately.

func Default

func Default() *Service

Default returns the process-wide service (may be a disabled Service, never nil once SetDefault ran; callers must still guard with Enabled()).

func New

func New(cfg husd.Config, seed []byte, opts ...Option) *Service

New builds a Service from the HUSD config and the org-derivation master seed. If the chain path is not fully configured it returns a DISABLED service (no panic, no partial state) so callers can safely wire it unconditionally and let Enabled() gate the on-chain path.

func (*Service) Config

func (s *Service) Config() husd.Config

Config returns the HUSD config (chain id, token, decimals) — read-only view for callers deciding Test partitioning / reconciliation.

func (*Service) Enabled

func (s *Service) Enabled() bool

Enabled reports whether the on-chain credit path is live.

func (*Service) Migrate

func (s *Service) Migrate(ctx context.Context, dryRun bool) ([]MigrateResult, error)

Migrate migrates every org's ledger onto chain (dry-run reports the snapshot + current chain balance without minting). Platform/CronJob-driven, one-time.

func (*Service) MigrateOrg

func (s *Service) MigrateOrg(ctx context.Context, org string, dryRun bool) (MigrateResult, error)

MigrateOrg migrates ONE org and reconciles chain==DB to the cent.

func (*Service) MintCredit

func (s *Service) MintCredit(ctx context.Context, req treasury.MintRequest) (*treasury.Receipt, error)

MintCredit is the ONE mint entrypoint: a treasury-signed on-chain HUSD issuance for req, projected back into the commerce ledger. It authorizes via the passed ctx (mintauth) exactly like the DB path, then (best-effort, bounded) projects the minted tx so the balance reflects it on return. Idempotent by req.IdemKey.

func (*Service) Settle

func (s *Service) Settle(ctx context.Context) ([]SettleResult, error)

Settle sweeps every org whose on-chain balance has drifted above its ledger balance by at least the threshold. Idempotent: a second pass finds ~zero drift. Driven by an external CronJob (mirrors the OSS/contributor payout endpoints).

func (*Service) SettleOrg

func (s *Service) SettleOrg(ctx context.Context, org string) (SettleResult, error)

SettleOrg computes one org's drift and, if it clears the threshold, sweeps it org→treasury (signed by the org's derived key) and records an audit line.

func (*Service) SyncOnce

func (s *Service) SyncOnce(ctx context.Context) (int, error)

SyncOnce runs one indexer pass (scan new final Transfers → project). It is the backfill + reconcile safety net behind the synchronous MintCredit projection, and the entrypoint an external CronJob drives. Idempotent. Returns the number of transfers projected this pass.

type SettleResult

type SettleResult struct {
	Org            string `json:"org"`
	OrgAddress     string `json:"orgAddress"`
	OnChainCents   int64  `json:"onChainCents"`
	SpendableCents int64  `json:"spendableCents"`
	DriftCents     int64  `json:"driftCents"`
	Settled        bool   `json:"settled"`
	TxHash         string `json:"txHash,omitempty"`
	Reason         string `json:"reason,omitempty"`
}

SettleResult is the per-org outcome of a settlement pass.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL