Documentation
¶
Overview ¶
Package withdrawals implements the data-model §5 withdrawal state machine over storage.Store: the FR-032 pre-sign checklist, the externally supplied compliance gate, idempotency-key single execution (FR-033), simulation and ceiling-rounded fee calculation (FR-040), and the FR-035 broadcaster (broadcast.go) with its eight status distinctions.
Every status write goes through the repository layer, which enforces the legal transitions; concurrent drivers lose optimistically with ErrStatusConflict instead of double-executing. Sequences come exclusively from sequences.Manager (FR-034); signing crosses only the signer.TransactionSigner boundary; signed bytes are persisted at SIGNED so recovery can rebroadcast identical bytes and never re-sign.
Index ¶
- Variables
- func VerifySignedResponse(signDocBytes []byte, resp signer.SigningResponse, expectedSender string) error
- type Chain
- type Config
- type Option
- type Outcome
- type Request
- type SimulatePolicy
- type Workflow
- func (w *Workflow) ApproveCompliance(ctx context.Context, withdrawalID string) error
- func (w *Workflow) Broadcast(ctx context.Context, withdrawalID string) (Outcome, error)
- func (w *Workflow) Build(ctx context.Context, withdrawalID string) error
- func (w *Workflow) Cancel(ctx context.Context, withdrawalID string) error
- func (w *Workflow) Confirm(ctx context.Context, withdrawalID string) (Outcome, error)
- func (w *Workflow) Recover(ctx context.Context, withdrawalID string) (Outcome, error)
- func (w *Workflow) ReserveFunds(ctx context.Context, withdrawalID string) error
- func (w *Workflow) ReserveSequence(ctx context.Context, withdrawalID string) error
- func (w *Workflow) Sign(ctx context.Context, withdrawalID string) error
- func (w *Workflow) Simulate(ctx context.Context, withdrawalID string) error
- func (w *Workflow) Submit(ctx context.Context, req Request) (storage.WithdrawalRecord, error)
- func (w *Workflow) ValidateAddress(ctx context.Context, withdrawalID string) error
Constants ¶
This section is empty.
Variables ¶
var ( // ErrSignatureInvalid reports a signer response whose signature does not // verify over SHA-256(signDocBytes) for the returned public key. ErrSignatureInvalid = errors.New("withdrawals: signature invalid over sign doc") // ErrWrongSigningKey reports a signer response whose public key does not // derive the intended sender address. ErrWrongSigningKey = errors.New("withdrawals: public key does not derive the expected sender") )
var ( // ErrInvalidRequest reports a request rejected before any record exists. ErrInvalidRequest = errors.New("withdrawals: invalid request") // ErrAwaitingCompliance reports a withdrawal blocked on the externally // supplied compliance gate (FR-031); the kit never decides it. ErrAwaitingCompliance = errors.New("withdrawals: awaiting external compliance approval") // ErrBelowMinimum reports an amount under the configured exchange minimum. ErrBelowMinimum = errors.New("withdrawals: amount below configured minimum") // ErrInsufficientSpendable reports a hot wallet that cannot cover // amount + max fee. ErrInsufficientSpendable = errors.New("withdrawals: insufficient spendable balance") // the withdrawal stays queued at TRANSACTION_BUILT until simulation // returns or the operator opts into the static policy. ErrSimulationUnavailable = errors.New("withdrawals: simulation unavailable; withdrawal held (simulate_unavailable: queue)") // ErrFeeExceedsMax reports a computed fee above max_fee_usovr; the // withdrawal is quarantined, never broadcast with an unbounded fee. ErrFeeExceedsMax = errors.New("withdrawals: computed fee exceeds max_fee_usovr") // ErrPaused reports an operational-controls pause for the needed flow. ErrPaused = errors.New("withdrawals: flow paused by operational controls") // ErrQuarantined reports a withdrawal moved to REVIEW_REQUIRED by the // current call; details are on the review-queue item. ErrQuarantined = errors.New("withdrawals: quarantined as REVIEW_REQUIRED") // queued (graceful degradation — nothing unsigned is ever broadcast). ErrSignerUnavailable = errors.New("withdrawals: signer unavailable; withdrawal remains queued") )
Functions ¶
func VerifySignedResponse ¶
func VerifySignedResponse(signDocBytes []byte, resp signer.SigningResponse, expectedSender string) error
VerifySignedResponse is the mandatory adapter-side check before anything is persisted as SIGNED (contracts/signer-interface.md §Adapter-side verification): (a) resp.Signature is a valid 64-byte R||S signature over SHA-256(signDocBytes) for resp.PubKeyCompressed, and (b) that public key derives expectedSender. Either failure means the withdrawal/sweep must be quarantined as REVIEW_REQUIRED and never broadcast.
Types ¶
type Chain ¶
type Chain interface {
Balance(ctx context.Context, addr, denom string) (sdkmath.Int, error)
Simulate(ctx context.Context, txBytes []byte) (*client.SimulateResult, error)
Broadcast(ctx context.Context, txBytes []byte, mode client.BroadcastMode) (*client.BroadcastResult, error)
Tx(ctx context.Context, hash string) (*client.TxInfo, error)
NodeStatus(ctx context.Context) (*client.NodeStatus, error)
}
Chain is the subset of client.Client the workflow needs; *client.Client implementations and the failover wrapper satisfy it.
type Config ¶
type Config struct {
ChainID string
// MinimumWithdrawalUsovr is an integer base-unit string.
MinimumWithdrawalUsovr string
// MaxFeeUsovr is an integer base-unit string bounding every fee under
// both simulate policies.
MaxFeeUsovr string
// GasAdjustment is a decimal string (e.g. "1.3") applied to simulated
// gas with ceiling rounding.
GasAdjustment string
// GasPriceUsovr is a decimal usovr-per-gas string (e.g. "0.025"); it
// must be at or above the live x/globalfee floor.
GasPriceUsovr string
// StaticGasLimit is required when SimulateUnavailable is SimulateStatic.
StaticGasLimit uint64
// BroadcastTimeout is the age after which a BROADCAST withdrawal whose
// transaction cannot be found enters the unknown-after-timeout flow.
BroadcastTimeout time.Duration
// Confirmations is the depth required for CONFIRMED. The adapter passes the
// shared scanner default (deposits.DefaultConfirmations = 2, range 1..12).
Confirmations uint64
// ProhibitedDestinations are strict-validation blocklist entries
// (module accounts, sanctioned addresses).
ProhibitedDestinations []string
// KeyRefForSource maps a source address to the signer's opaque key
// handle. Nil means the source address itself is the key ref.
KeyRefForSource func(sourceAddress string) string
}
Config carries every economic value as a string: nothing here may be hard-coded in transaction logic (FR-040).
type Option ¶
type Option func(*Workflow)
Option configures a Workflow.
func WithMetrics ¶
WithMetrics attaches the adapter metric set.
type Outcome ¶
type Outcome string
Outcome is one of the eight FR-035 broadcaster status distinctions.
const ( // OutcomeEncodingFailed: the transaction could not be locally encoded. OutcomeEncodingFailed Outcome = "LOCAL_ENCODING_FAILED" // OutcomeSignatureFailed: the signature failed verification locally. OutcomeSignatureFailed Outcome = "SIGNATURE_FAILED" // OutcomeCheckTxRejected: node-side pre-inclusion rejection (CheckTx). OutcomeCheckTxRejected Outcome = "CHECKTX_REJECTED" // OutcomeMempoolAccepted: CheckTx passed; the tx is in the mempool. OutcomeMempoolAccepted Outcome = "MEMPOOL_ACCEPTED" // OutcomeIncluded: the tx is in a block; execution result not yet final // for the workflow (confirmation depth pending). OutcomeIncluded Outcome = "BLOCK_INCLUDED" // OutcomeExecSuccess: DeliverTx code 0 at the required depth. OutcomeExecSuccess Outcome = "EXECUTION_SUCCESS" // OutcomeExecFailed: included but DeliverTx code != 0 — reported // accurately, the transfer did NOT happen (fee was still deducted). OutcomeExecFailed Outcome = "EXECUTION_FAILED" // OutcomeUnknownTimeout: broadcast result unknown after timeout and the // original-tx search found nothing — quarantined, never re-signed. OutcomeUnknownTimeout Outcome = "UNKNOWN_AFTER_TIMEOUT" )
type Request ¶
type Request struct {
WithdrawalID string
IdempotencyKey string
SourceAddress string
DestinationAddress string
AmountBaseUnits string
Memo string
}
Request is an exchange-side withdrawal instruction. AmountBaseUnits is an integer usovr string; the idempotency key comes from the exchange's upstream system (FR-033).
type SimulatePolicy ¶
type SimulatePolicy string
SimulatePolicy selects the behaviour when the node cannot serve Simulate (contracts/adapter-config-and-ops.md `simulate_unavailable`).
const ( // SimulateQueue holds withdrawals and alerts (the safe default). SimulateQueue SimulatePolicy = "queue" // SimulateStatic uses the configured static MsgSend gas, still bounded // by max_fee_usovr. Explicit opt-in. SimulateStatic SimulatePolicy = "static" )
type Workflow ¶
type Workflow struct {
// contains filtered or unexported fields
}
Workflow drives withdrawal records through the §5 state machine.
func New ¶
func New(store storage.Store, chain Chain, seq *sequences.Manager, sg signer.TransactionSigner, cfg Config, opts ...Option) (*Workflow, error)
New validates cfg and builds a Workflow.
func (*Workflow) ApproveCompliance ¶
ApproveCompliance records the EXTERNALLY supplied compliance decision (FR-031): the kit blocks until this is called and never decides itself.
func (*Workflow) Broadcast ¶
Broadcast pushes a SIGNED withdrawal's persisted bytes to the chain (FR-035). It never signs: only rec.SignedTxBytes ever reach the wire.
- CheckTx rejection ⇒ SIGNED → REVIEW_REQUIRED with the node's code and log; the sequence reservation is quarantined. Not FAILED: the signed bytes persist and could still be accepted by another node, so the funds must stay committed (SumCommittedBySource counts a REVIEW_REQUIRED record that still holds signed bytes) — never auto-released.
- Acceptance ⇒ BROADCAST (mempool accepted); inclusion and execution are Confirm's job.
- Transport error/timeout ⇒ the unknown flow: search for the original tx by hash; found ⇒ treat as accepted; not found ⇒ withdrawal REVIEW_REQUIRED + reservation RECONCILIATION_REQUIRED. A second signed transaction for this withdrawal is impossible from any path here.
func (*Workflow) Build ¶
Build moves SEQUENCE_RESERVED → TRANSACTION_BUILT after constructing the single-MsgSend transaction (FR-036) from the approved record.
func (*Workflow) Cancel ¶
Cancel moves any pre-SIGNED withdrawal to CANCELLED, releasing its sequence reservation when one is bound and still RESERVED.
func (*Workflow) Confirm ¶
Confirm advances a BROADCAST or INCLUDED withdrawal from chain truth: inclusion ⇒ INCLUDED (height, code, log persisted; reservation CONSUMED); DeliverTx failure ⇒ FAILED with the accurate execution report; success at the configured depth ⇒ CONFIRMED. A BROADCAST withdrawal whose tx cannot be found after BroadcastTimeout enters the unknown flow (REVIEW_REQUIRED + reservation quarantine) — never a re-sign.
func (*Workflow) Recover ¶
Recover drives a REVIEW_REQUIRED withdrawal that holds persisted signed bytes: search-first, then rebroadcast the identical bytes via the sequence manager (never re-sign). On success the withdrawal returns to the chain-truth status; the reservation leaves quarantine only when the transaction is observed on chain.
func (*Workflow) ReserveFunds ¶
ReserveFunds moves COMPLIANCE_APPROVED → FUNDS_RESERVED after the minimum check and the spendable-balance check (FR-032 items 3–4). The balance must cover this withdrawal's amount + max fee PLUS every amount + max fee already committed by other in-flight withdrawals from the same source. The committed-sum read, the balance check, and the status flip run inside a per-source critical section (the same account lock the sequence manager uses), so N individually-affordable concurrent reserves cannot each pass against the same raw balance and be signed for more than the wallet holds. The max-fee cap is the conservative per-withdrawal reservation — the true fee is unknown until simulation.
func (*Workflow) ReserveSequence ¶
ReserveSequence moves FUNDS_RESERVED → SEQUENCE_RESERVED with the single reservation bound to this withdrawal (FR-032 item 5, FR-034).
func (*Workflow) Sign ¶
Sign moves TRANSACTION_SIMULATED → SIGNED. It re-runs the FR-032 checklist tail against the exact sign-doc bytes: the summary derived from those bytes (tx.DeriveSummary inside SignDoc) must match the approved record field-for-field, including the chain ID. The signer's response is verified (signature valid over the doc, public key derives the sender) before anything is persisted; failure quarantines and never broadcasts.
func (*Workflow) Simulate ¶
Simulate moves TRANSACTION_BUILT → TRANSACTION_SIMULATED, persisting gas_wanted (simulated), gas_limit (× adjustment, ceil) and the fee (gas_limit × gas price, ceil), bounded by max_fee_usovr (FR-032 items 6–7, FR-040). When the node cannot simulate, the configured simulate_unavailable policy applies: queue holds the withdrawal (ErrSimulationUnavailable), static uses the configured MsgSend gas.