Ctrl K
ComunidadGuías…

internal/store/donations.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package store

import (
	"context"

	"telesrv/internal/domain"
)

// DonationStore is the persistence boundary for crypto donations: the
// encrypted wallet seed, each user's deterministic deposit address, the
// chain/token registry, per-chain watcher cursors and the deposit ledger.
// See internal/app/donations for the wallet crypto and on-chain watcher
// that sit above this.
type DonationStore interface {
	// WalletSeed returns the single encrypted master seed row, if one has
	// been generated. found is false before the operator ever creates one.
	WalletSeed(ctx context.Context) (encryptedSeed, nonce []byte, found bool, err error)
	// CreateWalletSeed inserts the one-and-only wallet row. It fails with
	// domain.ErrDonationWalletAlreadyExists if a row already exists --
	// generating a second seed would silently orphan every address already
	// handed out under the first one.
	CreateWalletSeed(ctx context.Context, encryptedSeed, nonce []byte) error

	// DonationAddressForUser looks up a user's already-assigned address, if
	// any. It never allocates one -- see NextDonationAddressIndex.
	DonationAddressForUser(ctx context.Context, userID int64) (domain.DonationAddress, bool, error)
	// DonationUserByAddress is the watcher's reverse lookup: given an
	// on-chain "to" address (lowercase hex), which user (if any) owns it.
	DonationUserByAddress(ctx context.Context, address string) (userID int64, found bool, err error)
	// NextDonationAddressIndex reserves the next BIP32 derivation index.
	// Reserving does not persist an address by itself; the caller derives
	// one from this index and then calls InsertDonationAddress. A reserved
	// index that loses the insert race (see InsertDonationAddress) is
	// simply skipped -- gaps in the sequence are harmless.
	NextDonationAddressIndex(ctx context.Context) (int64, error)
	// InsertDonationAddress persists a newly derived address for a user
	// that doesn't have one yet. If another request already inserted one
	// for this user in the meantime (ON CONFLICT (user_id) DO NOTHING), it
	// returns THAT row instead -- the caller's freshly derived index/address
	// is discarded, not an error.
	InsertDonationAddress(ctx context.Context, userID, index int64, address string) (domain.DonationAddress, error)

	// EnabledDonationChains lists every chain row with enabled=true,
	// regardless of whether it's actually watchable yet (RPCURL may still
	// be empty) -- callers filter with domain.DonationChain.Watchable.
	EnabledDonationChains(ctx context.Context) ([]domain.DonationChain, error)
	// DonationChain looks up one chain by key, enabled or not.
	DonationChain(ctx context.Context, chainKey string) (domain.DonationChain, bool, error)
	// UpdateDonationChainConfig writes the operator-editable fields of one
	// chain (RPC/WS endpoints, enabled flag, confirmation depth, price
	// feed/manual rate) -- never its identity or native currency, which are
	// fixed at migration time. Returns domain.ErrDonationChainNotFound if
	// chainKey doesn't exist.
	UpdateDonationChainConfig(ctx context.Context, upd domain.DonationChainConfigUpdate) (domain.DonationChain, error)
	// DonationTokens lists the stablecoin rows configured for one chain,
	// including any with an empty ContractAddress the operator hasn't
	// filled in yet -- callers filter with domain.DonationToken.Watchable.
	DonationTokens(ctx context.Context, chainKey string) ([]domain.DonationToken, error)

	// DonationChainCursor is the last block height fully scanned for a
	// chain (0 before the watcher has run at all).
	DonationChainCursor(ctx context.Context, chainKey string) (int64, error)
	// SetDonationChainCursor advances the cursor after a block range has
	// been scanned end to end (deposits recorded, confirmations updated).
	SetDonationChainCursor(ctx context.Context, chainKey string, block int64) error

	// RecordDonationDeposit inserts a newly observed deposit
	// (chain_key, tx_hash, log_index) uniquely identifies it, so a watcher
	// that re-scans a block range it already covered is a no-op: created
	// is false and the existing row is returned unchanged.
	RecordDonationDeposit(ctx context.Context, deposit domain.DonationDeposit) (result domain.DonationDeposit, created bool, err error)
	// UpdateDonationDepositConfirmations refreshes a pending deposit's
	// confirmation count as later blocks arrive, advancing it to
	// DonationDepositConfirmed once it reaches the chain's required depth.
	// It is a no-op once the deposit has left the pending state (confirmed,
	// credited or orphaned), so a stale watcher pass can never regress it.
	UpdateDonationDepositConfirmations(ctx context.Context, chainKey, txHash string, logIndex int, confirmations int) (domain.DonationDeposit, error)
	// PendingDonationDeposits lists deposits still short of the chain's
	// required confirmation depth, for the watcher to refresh each pass.
	PendingDonationDeposits(ctx context.Context, chainKey string) ([]domain.DonationDeposit, error)
	// ConfirmedUncreditedDeposits lists deposits sitting in
	// DonationDepositConfirmed, ready to be priced and credited.
	ConfirmedUncreditedDeposits(ctx context.Context, chainKey string, limit int) ([]domain.DonationDeposit, error)
	// CreditDonationDeposit atomically credits the deposit's user with
	// stars (via the Stars ledger, in the same transaction) and marks the
	// deposit DonationDepositCredited. Idempotent: if the deposit is not in
	// DonationDepositConfirmed when called (already credited, or somehow
	// orphaned), credited is false and nothing is written twice.
	CreditDonationDeposit(ctx context.Context, depositID int64, usdValueMicros, stars int64, date int) (result domain.DonationDeposit, credited bool, err error)
	// UserDonationDeposits lists one user's deposit history, newest first,
	// for the premium bot's /deposit history and any future admin view.
	UserDonationDeposits(ctx context.Context, userID int64, limit int) ([]domain.DonationDeposit, error)
}