18. Withdrawal Lifecycle

Outgoing tx 의 customer-visible projection · operation vocabulary · chain-specific quirk

Withdrawal = Outgoing Transaction

Withdrawal 은 transaction state machine 의 customer-visible projection. 13. Transaction State 의 17 status outgoing flow 와 동등 — 본 페이지는 운영 동사 + chain quirk 중심.

Transaction Operations by Status (★ Stage 9)

transaction-lifecycle.md, p.2:

Operation가능 status비고
CancelBroadcasting 이전이후엔 chain 에 broadcast 됨
RetryFAILED 후동일 tx 재시도
Boost / drop (EVM)BroadcastingEVM gas parameter 변경 — Replace-By-Fee (RBF)
Boost UTXO txBroadcastingBitcoin RBF
Rescreen / bypass AMLPENDING_AML_SCREENING / AML rejected재검사 또는 강제 진행
Dismisstransaction card UI dismissal
Edit transaction notesany

Chain-specific Withdrawal Quirk (★ Stage 9)

  • EVM: nonce 가 순차 — 단일 vault stuck 시 전체 queue 정체 → multiple withdrawal vault round-robin
  • Bitcoin: unconfirmed input 25-tx chain limit (Bitcoin Core default) → multiple withdrawal vault
  • Solana: vault account 당 동시 5 tx queue (6번째 이상 Submitted max 2h 대기 후 terminated)
  • Algorand: ~50min signing window
  • Tezos: ~30min + mempool 1 tx/account
  • Polkadot: tx valid 2 hours

Chain-specific 시간 제약과 Approval 경합

Owner / Admin Quorum approval 흐름과 시간 경합 가능

Algorand / Tezos / Polkadot 등의 짧은 signing window 가 Q+O / AG 흐름의 7-day expiry 보다 훨씬 짧음 — approval 받는 동안 chain-side validity expire 가능. DB 측은 chain_validity_expires_at 추적 + alert 필요.

Solana Sign-Only (★ Stage 9)

  • Signed payload client 에 반환 (Fireblocks 가 broadcast 안 함)
  • NOT BROADCAST BY FIREBLOCKS 태그 자동
  • on-chain 감지 시 Completed / Failed transition
  • Timeout 미달성 시 자동 invalidate

Raw Signing Special Path

  • 일반: Pending Signature → (broadcast 없음) → Completed
  • Cached signature (동일 raw data 재서명): Submitted → Completed (전체 ceremony 우회)

DB Schema

CREATE TABLE withdrawals (
  -- transaction 의 sub-aggregate. 1:1 with transaction 의 outgoing 경우.
  id                BINARY(16) PRIMARY KEY,
  transaction_id    BINARY(16) NOT NULL UNIQUE,
  withdrawal_vault_id BINARY(16) NOT NULL,
  destination_type  ENUM('vault', 'exchange', 'whitelisted', 'one-time', 'network'),
  destination_address VARCHAR(128) NOT NULL,
  tag_or_memo       VARCHAR(64),
  chain             VARCHAR(32) NOT NULL,
  chain_validity_expires_at DATETIME(6),              -- chain-specific validity (Algorand/Tezos/Polkadot)
  retry_count       INT NOT NULL DEFAULT 0,
  last_boost_at     DATETIME(6),                       -- RBF
  cancel_attempted_at DATETIME(6),
  cancel_completed_at DATETIME(6),                    -- ★ set-once
  KEY (withdrawal_vault_id),
  KEY (chain_validity_expires_at)
);

-- ★ append-only — broadcast 시도마다 row
CREATE TABLE broadcast_attempts (
  id                BINARY(16) PRIMARY KEY,
  withdrawal_id     BINARY(16) NOT NULL,
  attempt_number    INT NOT NULL,
  raw_payload_cbor  BLOB NOT NULL,                    -- ★ set-once
  signed_payload    BLOB NOT NULL,                    -- ★ set-once
  broadcasted_to_node VARCHAR(128) NOT NULL,
  broadcasted_at    DATETIME(6) NOT NULL,
  tx_hash           VARCHAR(128),                    -- ★ set-once (node 가 echo)
  outcome           ENUM('submitted', 'rejected', 'dropped', 'replaced') NOT NULL,
  UNIQUE KEY (withdrawal_id, attempt_number)
);

-- ★ append-only — confirmation 누적
CREATE TABLE confirmations (
  id                BINARY(16) PRIMARY KEY,
  broadcast_attempt_id BINARY(16) NOT NULL,
  block_hash        VARCHAR(128) NOT NULL,
  block_height      BIGINT NOT NULL,
  confirmed_at      DATETIME(6) NOT NULL,
  reorged_at        DATETIME(6),                      -- reorg 시 set (row 자체는 유지)
  KEY (broadcast_attempt_id, block_height)
);

-- 운영 액션 이력
CREATE TABLE withdrawal_operations (
  -- append-only
  id                BINARY(16) PRIMARY KEY,
  withdrawal_id     BINARY(16) NOT NULL,
  operation         ENUM('cancel', 'retry', 'boost', 'drop',
                          'rescreen-aml', 'bypass-aml', 'dismiss', 'edit-notes') NOT NULL,
  actor_user_id     BINARY(16) NOT NULL,
  parameters_cbor   BLOB,                              -- e.g., new gas params
  occurred_at       DATETIME(6) NOT NULL,
  KEY (withdrawal_id, occurred_at)
);

Round-Robin Withdrawal Vault 운영

EVM / Bitcoin 의 chain-specific 한 sequential bottleneck 회피:

workspace
  └── withdrawal vaults (N 개)
        ├── vault A  →  EVM nonce A0, A1, A2, ...
        ├── vault B  →  EVM nonce B0, B1, B2, ...
        ├── vault C  →  EVM nonce C0, C1, C2, ...

→ application layer 의 vault selector 가 round-robin 으로 vault 할당
→ 한 vault 의 stuck tx 가 전체 queue 막지 않음