Documentation
¶
Overview ¶
Package treasury is the chain-backed credit ledger's mint authority boundary.
The recurring C1 money-mint vulnerability class exists because credits are a mutable DB number any code path can increment. This package removes that by construction: a credit exists ONLY as HUSD (Hanzo USD, ERC-20 on the Hanzo EVM) minted by the treasury key — which lives in KMS, never in this process. Commerce can only REQUEST a mint (treasury.Mint); the request is refused unless the caller carries proven mint authority (mintauth), and the value is created by a treasury-signed on-chain transaction, not a DB write.
This file (derive.go) is Step 1: deterministic per-org on-chain addresses. The same (master seed, orgID) yields the same EVM address byte-for-byte across processes and restarts, so an org's HUSD balance has one stable home on chain; distinct orgs get distinct addresses with cryptographic certainty. The master seed is KMS-sourced (like the treasury key) and never persisted.
Index ¶
- Variables
- func AddressForKey(privHex string) (string, error)
- func AddressForOrg(masterSeed []byte, orgID string) (string, error)
- func AmountWeiForCents(cents int64, decimals int) (*big.Int, error)
- func IssuanceID(idemKey string) string
- func SeedFromHex(s string) ([]byte, error)
- type Account
- type Bucket
- type Issuance
- type IssuanceStatus
- type IssuanceStore
- type MintRequest
- type Option
- type Receipt
- type TransferFunc
- type Treasury
Constants ¶
This section is empty.
Variables ¶
var ( ErrNotConfigured = husd.ErrNotConfigured // ErrInFlight: an issuance for this key exists and is mid-submit (pending, no // tx). The caller must retry after the in-flight attempt resolves — resending // would risk a double mint. Not an error the ledger invented value. ErrInFlight = errors.New("treasury: mint already in flight for this idempotency key") // ErrNotAuthorized re-exports the mintauth refusal for callers. ErrNotAuthorized = mintauth.ErrNotAuthorized )
Errors.
var ErrEmptyOrg = errors.New("treasury: org id must not be empty")
ErrEmptyOrg is returned when an empty org id is passed to derivation.
var ErrSeedTooShort = fmt.Errorf("treasury: master derivation seed must be >= %d bytes", minSeedLen)
ErrSeedTooShort is returned when the master derivation seed is missing or has insufficient entropy. Fail closed: no seed ⇒ no addresses ⇒ no mint target.
Functions ¶
func AddressForKey ¶
AddressForKey returns the lowercased 0x EVM address controlled by a secp256k1 private key (hex, with or without 0x). It is the inverse of "what address does this signer send FROM" — used to locate the treasury's own on-chain address (settlement destination, Step 5) and to reconcile a derived org account's address against the key that will sign its settlement transfers.
func AddressForOrg ¶
AddressForOrg is the mint-side convenience: it derives only the org's on-chain address, discarding the private key (minting a credit only needs where to send the HUSD). Use DeriveAccount when you also need the settlement-signing key.
func AmountWeiForCents ¶
AmountWeiForCents exposes the cents→wei conversion for callers verifying a mint amount against the chain (e.g. the indexer reconciliation).
func IssuanceID ¶
IssuanceID is the deterministic storage id for an idempotency key. Distinct keys → distinct ids; the same key → the same id, so a replay lands on the same row and concurrent duplicates collapse.
func SeedFromHex ¶
SeedFromHex decodes a hex master seed (with or without 0x) sourced from KMS (env HUSD_ORG_DERIVATION_SEED). It enforces the minimum entropy so a misconfigured deploy fails closed rather than deriving weak addresses.
Types ¶
type Account ¶
type Account struct {
OrgID string
Address string // 0x-prefixed, lowercased for stable indexing/matching
// contains filtered or unexported fields
}
Account is a per-org on-chain HUSD holding: a deterministic EVM address (where the org's HUSD balance lives — safe to expose and index) and the secp256k1 signing key for it. The private key is held in memory only, never persisted and never logged; it is needed only to SIGN settlement transfers (org → treasury) in Step 5. Minting only ever needs the Address.
func DeriveAccount ¶
DeriveAccount deterministically derives an org's HUSD account from a master seed, BIP-32-style: I = HMAC-SHA512(seed, "hanzo-husd-org-v1:"||orgID||ctr), take the left 256 bits as the secp256k1 scalar. The counter increments only on the astronomically rare event that the scalar is not a valid key (>= curve order or zero) — so derivation is total and deterministic. Distinct orgIDs produce distinct scalars (HMAC collision resistance) hence distinct addresses; the mapping is collision-safe for arbitrary string org ids (no lossy string→uint32 BIP-44 index).
func (Account) PrivateKeyHex ¶
PrivateKeyHex returns the account's secp256k1 private key as hex (no 0x). It is the settlement-signing key; callers must treat it as a secret and must never log or persist it.
type Bucket ¶
type Bucket string
Bucket names which billing bucket a mint funds. On-chain HUSD is fungible and a Transfer event carries no tag, so the credit-vs-prepaid distinction is recorded HERE, off-chain, at issuance time; the indexer (step 3) reads it back to tag the projected ledger deposit (billing/bucket classifies from that tag).
const ( // BucketCredit is a non-cash grant (welcome/starter/promo). Spendable on // non-GPU usage only (billing/bucket.Credit). BucketCredit Bucket = "credit" // BucketPrepaid is real money paid in (card top-up, crypto payin, migration // of a real balance). The only bucket GPUs may draw from (billing/bucket.Prepaid). BucketPrepaid Bucket = "prepaid" )
func (Bucket) LedgerTag ¶
LedgerTag maps a bucket to the transaction Tags string the indexer writes so billing/bucket.DepositKind classifies the projected deposit into the same bucket. Credit → "credit:husd" (a grant prefix bucket.DepositKind treats as Credit); Prepaid → "husd" (real money, treated as Prepaid).
type Issuance ¶
type Issuance struct {
Id string `json:"id"`
IdemKey string `json:"idemKey"`
OrgID string `json:"orgId"`
// Subject is the off-chain ledger DestinationId this mint credits (an IAM
// "owner/name" per-user key, or the org slug for org-pooled billing). On-chain
// HUSD is per-ORG (one derived address per OrgID), so many subjects share one
// address; Subject records which sub-ledger the projected credit lands in.
// Defaults to OrgID (org-pooled) when the caller leaves it empty.
Subject string `json:"subject"`
OrgAddress string `json:"orgAddress"`
AmountCents int64 `json:"amountCents"`
Bucket Bucket `json:"bucket"`
Reason string `json:"reason"`
// Test marks a mint on the test chain (matches the ledger's Test partition so
// the balance read finds it). A test-mode org mints test HUSD; a live org mints
// live HUSD — the projected credit's Test flag MUST equal the balance read's.
Test bool `json:"test"`
ChainID int64 `json:"chainId"`
TokenAddr string `json:"tokenAddr"`
TxHash string `json:"txHash,omitempty"`
Status IssuanceStatus `json:"status"`
CreatedAt time.Time `json:"createdAt"`
MintedAt time.Time `json:"mintedAt,omitempty"`
}
Issuance is the durable, idempotent record of ONE mint request. Its storage id is deterministic in the idempotency key so concurrent duplicate requests collapse onto one row (the money-safe primitive; see billing/bucket + gift card design). It is also the audit trail: every credit that exists traces to exactly one Issuance and its on-chain TxHash.
type IssuanceStatus ¶
type IssuanceStatus string
IssuanceStatus tracks a mint's lifecycle. An issuance is the off-chain, idempotent record of a mint request keyed by its idempotency key.
const ( // StatusPending: the record was created and we are (or a crashed prior // attempt was) submitting the on-chain tx. A pending record with no TxHash is // treated as in-flight — never resubmitted blindly. StatusPending IssuanceStatus = "pending" // StatusMinted: the on-chain tx was submitted; TxHash is set. Replays return // the stored receipt with no new tx. StatusMinted IssuanceStatus = "minted" // StatusFailed: submission failed before broadcast (validation/signing); the // key may be retried. StatusFailed IssuanceStatus = "failed" )
type IssuanceStore ¶
type IssuanceStore interface {
// CreateIfAbsent records iss iff its Id is not already present. Returns
// created==true with iss when this call created it, or created==false with the
// pre-existing record otherwise. Exactly one concurrent caller sees created==true.
CreateIfAbsent(ctx context.Context, iss *Issuance) (created bool, existing *Issuance, err error)
// Update persists status/txHash/mintedAt on an existing issuance.
Update(ctx context.Context, iss *Issuance) error
// Get loads an issuance by id (nil,nil if absent).
Get(ctx context.Context, id string) (*Issuance, error)
}
IssuanceStore persists issuances. It is an interface (not a concrete datastore) so the mint service's logic is unit-tested with an in-memory fake (CGO-free, no sqlite) and wired to the real per-org SQLite in production. The store MUST make CreateIfAbsent atomic on Issuance.Id: concurrent same-id calls yield exactly one created==true (the deterministic-id ON CONFLICT upsert).
type MintRequest ¶
type MintRequest struct {
OrgID string
// Subject is the ledger DestinationId to credit (defaults to OrgID when empty —
// org-pooled billing). See Issuance.Subject.
Subject string
AmountCents int64
Bucket Bucket
Reason string
IdemKey string
// Test routes the mint to the test partition of the ledger (must match the
// balance read's Test flag). Set from org.TestMode() by the caller.
Test bool
}
MintRequest is a request to bring AmountCents of HUSD into existence for an org. IdemKey makes it exactly-once: the same key mints at most one on-chain tx.
type Option ¶
type Option func(*Treasury)
Option configures a Treasury.
func WithTransfer ¶
func WithTransfer(fn TransferFunc) Option
WithTransfer overrides the on-chain transfer function (tests inject a fake).
type Receipt ¶
type Receipt struct {
IdemKey string
OrgID string
Subject string
OrgAddress string
AmountCents int64
Bucket Bucket
Reason string
Test bool
ChainID int64
TxHash string
Replayed bool // true if the key had already minted; no new tx was sent
}
Receipt is the result of a mint: the on-chain tx and where the value landed.
type TransferFunc ¶
type TransferFunc func(ctx context.Context, t blockchain.TokenTransfer) (string, error)
TransferFunc signs+submits a treasury ERC-20 transfer, returning the tx hash. Defaults to util/blockchain.TransferToken (KMS-keyed, geth signing in the linked sub-module); injectable so unit tests need no chain.
type Treasury ¶
type Treasury struct {
// contains filtered or unexported fields
}
Treasury is the mint service. Construct with New; call Mint.
func New ¶
New builds a Treasury. cfg is the HUSD config (KMS-injected; must be Configured for real mints). masterSeed is the KMS-sourced org-address derivation seed. store persists issuances. By default the on-chain transfer is util/blockchain.TransferToken (requires the geth signer sub-module linked).
func (*Treasury) Mint ¶
Mint brings AmountCents of HUSD into existence for req.OrgID by a treasury-signed on-chain transfer to the org's derived address. It is:
- authorized: refused unless mintauth.Require(ctx) passes (a gated HTTP caller must carry proven mint authority; crons/migrations are ungated).
- idempotent: the same IdemKey mints at most one on-chain tx; a replay returns the stored receipt (Replayed=true) with no new tx.
- fail-closed: no token/key configured ⇒ ErrNotConfigured (no DB fallback, no silent mint). The value is created ONLY by the KMS-keyed transfer.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package datastorestore is the production treasury.IssuanceStore: it persists HUSD mint issuances to the per-org SQLite ledger via models/husdissuance.
|
Package datastorestore is the production treasury.IssuanceStore: it persists HUSD mint issuances to the per-org SQLite ledger via models/husdissuance. |