Documentation
¶
Overview ¶
Package husdindex is Step 3 of the chain-backed credit ledger: it makes the on-chain HUSD balance the source of truth and the commerce DB a cache.
It has two decomplected halves:
- client.go: a minimal JSON-RPC READ client (net/http, encoding/json) for the Hanzo EVM — block height, ERC-20 balanceOf, and Transfer logs. No geth, no cgo: reads only, so the indexer builds and the live read-proof runs CGO-free.
- index.go: the Sync projector — scan Transfer events INTO org addresses and project each, idempotently, into the ledger tagged by its off-chain issuance bucket. Pure logic over small interfaces, unit-tested with fakes.
The invariant it upholds: an org's indexed balance == its on-chain balanceOf(address), reconciled to the cent (Σ projected credits − debits).
Index ¶
- Constants
- func OnChainBalanceCents(ctx context.Context, br BalanceReader, addr string, decimals int) (int64, error)
- type AddressBook
- type BalanceReader
- type Client
- func (c *Client) BalanceOf(ctx context.Context, addr string) (*big.Int, error)
- func (c *Client) BlockNumber(ctx context.Context) (uint64, error)
- func (c *Client) TransfersInTx(ctx context.Context, txHash string, addrs []string) ([]Transfer, error)
- func (c *Client) TransfersTo(ctx context.Context, addrs []string, fromBlock, toBlock uint64) ([]Transfer, error)
- type Config
- type Credit
- type Cursor
- type Indexer
- type IssuanceLookup
- type Ledger
- type Reader
- type Transfer
- type TxReader
Constants ¶
const TransferTopic0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
TransferTopic0 is keccak256("Transfer(address,address,uint256)") — the ERC-20 Transfer event signature topic. Verified against luxfi/crypto.Keccak256 in the tests so a typo can never silently mis-filter logs.
Variables ¶
This section is empty.
Functions ¶
func OnChainBalanceCents ¶
func OnChainBalanceCents(ctx context.Context, br BalanceReader, addr string, decimals int) (int64, error)
OnChainBalanceCents returns the org's on-chain HUSD balance in whole cents.
Types ¶
type AddressBook ¶
type AddressBook interface {
// Addresses returns the set of org HUSD addresses to watch.
Addresses(ctx context.Context) ([]string, error)
// OrgFor returns the orgID owning a (lowercased) address, ok=false if unknown.
OrgFor(addr string) (string, bool)
}
AddressBook maps derived org addresses (lowercased) → orgID, and lists the addresses to filter Transfer logs by. Production derives it from the org list + master seed (treasury.AddressForOrg); tests supply it directly.
type BalanceReader ¶
Reconcile fetches an org's on-chain balanceOf and returns it in cents together with any sub-cent remainder — the exact check the migration + monitoring assert against the indexed ledger balance. balanceReader is *Client (or a fake). Kept separate from Sync so reconciliation reads chain truth directly.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reads HUSD state over JSON-RPC. It is safe for concurrent use.
func (*Client) BlockNumber ¶
BlockNumber returns the current chain head.
func (*Client) TransfersInTx ¶
func (c *Client) TransfersInTx(ctx context.Context, txHash string, addrs []string) ([]Transfer, error)
TransfersInTx returns the HUSD Transfer events emitted by ONE mined tx whose recipient is one of addrs. It reads eth_getTransactionReceipt (not eth_getLogs) so a just-submitted mint can be projected the instant it is mined, without waiting for a block-range scan. addrs match case-insensitively; only logs from the HUSD token contract are considered. Returns nil if the tx is not yet mined (no receipt) — the caller falls back to the background Sync.
func (*Client) TransfersTo ¶
func (c *Client) TransfersTo(ctx context.Context, addrs []string, fromBlock, toBlock uint64) ([]Transfer, error)
TransfersTo returns HUSD Transfer events whose recipient (topic2) is one of addrs, between fromBlock and toBlock (inclusive). addrs are matched case-insensitively.
type Config ¶
type Config struct {
Decimals int
Confirmations uint64 // blocks below head to treat as final (reorg safety)
StartBlock uint64 // first block to scan when the cursor is empty
MaxRange uint64 // 0 → default 2000
}
Config parameterizes an Indexer.
type Credit ¶
type Credit struct {
OrgID string
// Subject is the ledger DestinationId to credit (the mint's subject; for an
// external payin with no issuance it is the org slug). This is where the
// balance read looks, so the projection MUST write to it, not to OrgID.
Subject string
AmountCents int64
Tag string // billing/bucket ledger tag (credit:husd | husd)
Test bool // ledger Test partition (matches the mint / balance read)
TxHash string
LogIndex uint64
DedupKey string // txHash:logIndex — idempotency key for the projection
}
Credit is one projected on-chain HUSD credit for an org.
type Cursor ¶
type Cursor interface {
Last(ctx context.Context) (uint64, error)
Save(ctx context.Context, block uint64) error
}
Cursor persists the last fully-scanned block so a restart resumes, not restarts.
type Indexer ¶
type Indexer struct {
// contains filtered or unexported fields
}
Indexer projects on-chain HUSD Transfers into the ledger.
func NewIndexer ¶
func NewIndexer(reader Reader, ledger Ledger, lookup IssuanceLookup, cursor Cursor, book AddressBook, cfg Config) *Indexer
NewIndexer builds an Indexer.
func (*Indexer) ProjectTx ¶
ProjectTx synchronously projects the HUSD Transfers in ONE mined tx (a just-submitted treasury mint) into the ledger, so the balance reflects the mint immediately rather than after the next Sync tick. It funnels through the SAME idempotent project() the background Sync uses (dedup on txHash:logIndex), so a later Sync over the same block never double-credits. Returns the number of transfers projected (0 if the tx touched none of our addresses — e.g. it is not yet mined; the background Sync will pick it up once final).
It does NOT advance the Sync cursor: the background Sync remains the authoritative, gap-free scanner + reconciler; ProjectTx is a latency optimization layered on top, never a replacement.
type IssuanceLookup ¶
type IssuanceLookup interface {
ByTxHash(ctx context.Context, txHash string) (*treasury.Issuance, error)
}
IssuanceLookup resolves a mint tx to its off-chain issuance so the projected credit is tagged into the right bucket (a fungible Transfer carries no tag). Returns nil for a transfer not originated by a treasury mint (external payin).
type Ledger ¶
Ledger is where the indexer PROJECTS on-chain HUSD credits. In production this writes an idempotent tagged deposit into the commerce transaction ledger (so the existing GET /v1/billing/*/balance reads chain-sourced balance unchanged); in tests it is a fake. Credit MUST be idempotent on Credit.DedupKey — the indexer re-scans overlapping ranges after restarts and must never double-credit.
type Reader ¶
type Reader interface {
BlockNumber(ctx context.Context) (uint64, error)
TransfersTo(ctx context.Context, addrs []string, fromBlock, toBlock uint64) ([]Transfer, error)
}
Reader is the subset of the RPC client the projector needs. *Client satisfies it; unit tests inject a fake so Sync is proven without a chain.
type Transfer ¶
type Transfer struct {
From string // 0x, lowercased
To string // 0x, lowercased
ValueWei *big.Int
TxHash string
LogIndex uint64
Block uint64
}
Transfer is a decoded ERC-20 Transfer event.
type TxReader ¶
type TxReader interface {
TransfersInTx(ctx context.Context, txHash string, addrs []string) ([]Transfer, error)
}
TxReader reads the HUSD Transfer logs contained in a single already-mined tx. *Client satisfies it. It is a NARROW extension of Reader (kept separate so the Sync fakes need not implement it) used by ProjectTx for immediate, synchronous projection of a just-submitted mint.