5. Wallets · Addresses · Asset Wallet

정식 매핑 — 3 asset 패턴 · whitelisted address · chain 별 특이 제약
graph TB
  VA["🏦 Vault Account
(workspace 안 자산 보관 단위)"] VA --> AW1["💰 Asset Wallet — BTC
(UTXO 패턴)"] VA --> AW2["💰 Asset Wallet — ETH
(Account no-tag 패턴)"] VA --> AW3["💰 Asset Wallet — XRP
(Account tag 패턴)"] subgraph UTXO_BOX["① UTXO: 1 wallet → N addresses"] AW1 --> A1a["📍 address #1"] AW1 --> A1b["📍 address #2"] AW1 --> A1c["📍 address #N …"] end subgraph ACCT_BOX["② Account no-tag: 1 wallet → 1 address (강제)"] AW2 --> A2["📍 단일 address
(ERC-20 token 도 동일 address 공유)"] end subgraph TAG_BOX["③ Account tag/memo: 1 address + N tags"] AW3 --> A3["📍 단일 on-chain address"] A3 -. tag .-> T1["🏷️ tag/memo #1"] A3 -. tag .-> T2["🏷️ tag/memo #2"] A3 -. tag .-> T3["🏷️ tag/memo #N"] end classDef vault fill:#fef3c7,stroke:#d97706,stroke-width:2px; classDef wallet fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px; classDef addr fill:#dcfce7,stroke:#16a34a; classDef tag fill:#fce7f3,stroke:#db2777; class VA vault; class AW1,AW2,AW3 wallet; class A1a,A1b,A1c,A2,A3 addr; class T1,T2,T3 tag;
Vault Account — Asset Wallet — Address 관계. 같은 vault 안에 chain × asset 단위로 asset wallet 이 생성되고, address 분기는 chain 의 잔액 모델 (UTXO / account-based) 에 따라 3 패턴으로 갈린다.

3 Asset Pattern

Asset 패턴예시Permanent AddressDeposit Address 개수분기자
UTXO(?)-basedBTC, BCH, LTC, DOGE1Naddress 그 자체
Account-based (no tag)ETH, EVM(?) 전반1 (단일 강제)
Account-based (tag/memo)XRP, XLM1 (on-chain 동일)N tags / memos
EVM 의 "1 vault account(?) = 1 address" 제약

End-client 별 unique address 가 필요한 경우 individual vault account 필요 → intermediate vault 패턴 사용 (omnibus + intermediate per client).

DB Schema

CREATE TABLE asset_wallets (
  id                BINARY(16) PRIMARY KEY,
  vault_account_id  BINARY(16) NOT NULL,
  chain             VARCHAR(32) NOT NULL,            -- 'BTC', 'ETH', 'SOL', ...
  asset_id          VARCHAR(64) NOT NULL,            -- 'BTC', 'ETH', 'USDC_ETH', ...
  asset_pattern     ENUM('utxo', 'account-no-tag', 'account-tag') NOT NULL,
  permanent_address VARCHAR(128),                    -- UTXO 만 set
  created_at        DATETIME(6) NOT NULL,
  UNIQUE KEY (vault_account_id, asset_id),
  KEY (chain)
);

CREATE TABLE addresses (
  id              BINARY(16) PRIMARY KEY,
  asset_wallet_id BINARY(16) NOT NULL,
  address_string  VARCHAR(128) NOT NULL,
  tag_or_memo     VARCHAR(64),                       -- XRP/XLM 의 분기자
  address_type    ENUM('deposit', 'change', 'permanent') NOT NULL,
  derivation_index INT,                              -- UTXO 의 deposit address index
  deactivated_at  DATETIME(6),
  created_at      DATETIME(6) NOT NULL,
  KEY (asset_wallet_id, address_type),
  UNIQUE KEY (address_string, tag_or_memo)
);

Whitelisted Address — Vault 외부 송금 대상 목록

Vault address 는 workspace 안의 vault 가 보유한 address (입금·출금·잔액 보관·on-chain 식별 모두 거기), whitelisted address 는 외부로 송금할 목적지 를 Admin Quorum(?) 승인으로 사전 등록해둔 화이트리스트. 두 개념은 가리키는 대상이 다름.

Type의미workspace(?) 잔액 표시
Internal같은 workspace 의 다른 user/team 의 외부 wallet표시
External외부 organization 의 wallet표시 안 됨
Contract / ProgramEVM smart contract 주소 또는 Solana Program ID (base58). 자산 수령 wallet 이 아닌 callable 대상 — DApp interaction (DEX swap / DeFi staking / NFT mint 등) 출금에 사용. Solana 의 user wallet / token account 는 Contract 아님 (Internal/External 로 등록)
CREATE TABLE whitelisted_addresses (
  id              BINARY(16) PRIMARY KEY,
  workspace_id    BINARY(16) NOT NULL,
  type            ENUM('internal', 'external', 'contract') NOT NULL,
  asset_id        VARCHAR(64) NOT NULL,
  address_string  VARCHAR(128) NOT NULL,
  tag_or_memo     VARCHAR(64),
  label           VARCHAR(128),
  approved_by_quorum_id BINARY(16) NOT NULL,         -- Admin Quorum approval 의무
  approved_at     DATETIME(6) NOT NULL,
  KEY (workspace_id, type)
);

CREATE TABLE one_time_addresses (
  -- OTA = whitelist 우회 path. Address-level quorum 없음, Policy 가 유일한 방어선
  id              BINARY(16) PRIMARY KEY,
  workspace_id    BINARY(16) NOT NULL,
  used_in_tx_id   BINARY(16) NOT NULL,
  address_string  VARCHAR(128) NOT NULL,
  used_at         DATETIME(6) NOT NULL
);
본 schema 의 범위 밖 — 후속 검토 위치

아래 두 항목은 whitelisted_addresses 단독으로는 다루지 않고, 다른 평면에서 처리하도록 분리합니다. 이 페이지의 schema 를 단순하게 유지하기 위함.

  • Method whitelist (Contract type 의 함수 호출 제어) — Contract address 만 등록하면 "어떤 contract" 는 막아도 "그 contract 의 어떤 함수를 어떤 인자로" 는 못 막음. DApp interaction 출금 운영 시 method/function selector + argument constraint whitelist 가 한 layer 더 필요. → 10. Policy 의 Policy rule 평면
  • Approver-level audit trail (N-of-M 의 specific signatures) — 본 schema 의 approved_by_quorum_id 는 quorum 자체의 id 만 가리키므로 "그 시점에 누가 sign 했는지" 의 trail 이 없음. Quorum membership 이 시간에 따라 바뀌므로 historical signer trail 은 별도 audit 평면에서 보존 필요. → 20. Cross-DB Auditapproval_decisions (approver 별 mobile_device_sig) + audit_events hash chain 패턴

Chain 별 특이 제약 — DB schema 에 어떻게 흡수되는가

Asset 패턴 (UTXO / account / account+tag) 위에 한 layer 더. 각 chain 마다 다른 디테일 제약 — 최소 잔액, 최소 송금 금액, account 활성화 보증금, trust line, token wallet 권한 등 — 을 Fireblocks 가 어떻게 schema 컬럼으로 흡수하는지 정리. 마지막 컬럼 "DB 의 함의" 가 핵심.

제약예시DB 의 함의
Minimum balance (Base Reserve)ALGO / KSM / NEAR / DOT / XRP / SOL / XLM / TON 등asset_wallets.min_balance_reserved 컬럼
Minimum transaction amountBTC 0.00000582 / ALGO 1e-6 / ADA 1 / DOGE 0.01 / TON 1e-6Submit 시 validation; 미만 시 Amount Too Small status
Account 활성화Stellar (1 XLM 필수), Polkadot (0.01 DOT 이상), Near (token contract pre-funding)asset_wallets.activated_at 컬럼
Trust line (XRP)Reserve 미만 시 on-chain 자동 삭제주기적 reconciliation + 상태 동기화
Token wallet 제약 (TL)ALGO / XRP / SOL / XLM token walletRole 별 생성 권한 차이 — role_permissions 테이블에 'TL' label
Chain type 분류EVM account-based / non-EVM account-based / UTXO / Cosmos SDKchains lookup 테이블의 type 컬럼
Internal transactions (EVM)smart contract 내부 native transfer — Ethereum / Optimism / Arbitrum / Avalanche / Base / Celo 등관찰 메커니즘은 본 자료 미명시 → 18. Deposit Lifecycle

chains Lookup Table — chain-level static config 의 single source-of-truth

위의 "Chain 별 특이 제약" 표의 거의 모든 행이 가리키던 home. 각 chain 의 type/모델/최소값/플래그/제약값을 한 row 로 모아둠. asset_wallets 등 다른 테이블이 chain 단위 설정을 읽을 때 JOIN/lookup 대상.

CREATE TABLE chains (
  id                       VARCHAR(32) PRIMARY KEY,    -- 'BTC','ETH','XRP','XLM','SOL',...
  display_name             VARCHAR(128) NOT NULL,
  type                     ENUM('utxo','evm','non_evm_account','cosmos_sdk') NOT NULL,
  account_model            ENUM('utxo','account_no_tag','account_with_tag') NOT NULL,
  min_tx_amount            DECIMAL(40,20),             -- chain 의 최소 송금 금액 default
  base_reserve_minimum     DECIMAL(40,20),             -- 활성 유지를 위한 최소 잔액
  requires_activation      BOOLEAN NOT NULL DEFAULT FALSE,   -- Stellar / Polkadot / Near 등
  activation_minimum       DECIMAL(40,20),             -- 활성화 보증금 (예: 1 XLM)
  requires_trust_line      BOOLEAN NOT NULL DEFAULT FALSE,   -- XRP token holding 시 trust line
  supports_internal_tx     BOOLEAN NOT NULL DEFAULT FALSE,   -- EVM internal transfers
  supports_token_wallet    BOOLEAN NOT NULL DEFAULT FALSE,   -- ALGO / XRP / SOL / XLM
  unconfirmed_chain_limit  INT,                        -- BTC 25 등 — mempool descendant cap
  concurrent_tx_limit_per_vault INT,                   -- Solana per-vault 5 등 (soft throttle)
  concurrent_tx_limit_per_workspace INT,               -- Solana workspace-wide 600 등
  signing_window_seconds   INT,                        -- Algorand ~50min 등
  is_active                BOOLEAN NOT NULL DEFAULT TRUE,    -- 서비스가 지원 중인지
  modified_by              BINARY(16) NOT NULL,
  modified_at              DATETIME(6) NOT NULL
);
컬럼자료형역할
idVARCHAR(32) PKchain ticker 또는 short code. asset_wallets.chain_id 가 FK 로 가리킴
display_nameVARCHAR(128)UI 표시용 정식 이름 ("Bitcoin", "Ethereum")
typeENUM (4 값)큰 분류: utxo / evm / non_evm_account / cosmos_sdk. 위 표의 "Chain type 분류" 행 대응
account_modelENUM (3 값)address 분기 모델 — 본 페이지 "3 Asset Pattern" 절의 3 패턴과 1:1 매핑
min_tx_amountDECIMAL(40,20)chain default 최소 송금. submit 시 validation. BTC 0.00000582 / TON 1e-6 등. 더 정밀한 per-asset 값은 별도 자산 테이블에서 override 가능
base_reserve_minimumDECIMAL(40,20)활성 유지에 묶여 있어야 하는 최소 잔액. per-wallet 현재 reserve 는 asset_wallets.min_balance_reserved
requires_activationBOOLEANaccount 활성화가 필요한 chain 인지. asset_wallets.activated_at 의 작성 조건
activation_minimumDECIMAL(40,20) nullable활성화 보증금. Stellar 1 XLM / Polkadot 0.01 DOT 등. requires_activation=FALSE 이면 NULL
requires_trust_lineBOOLEANXRP 등에서 token 보유 시 trust line 필요 여부. on-chain 자동 삭제 가능성 알림
supports_internal_txBOOLEANEVM 의 smart contract 내부 native transfer 같은 deposit path 관찰이 필요한 chain 인지
supports_token_walletBOOLEANchain 위에 token wallet (ALGO/XRP/SOL/XLM 의 SPL/IOU/TL) 을 만들 수 있는지. role 'TL' label 의 트리거 조건
unconfirmed_chain_limitINT nullableUTXO 의 mempool descendant cap. BTC = 25. withdrawal vault round-robin 산정 기준
concurrent_tx_limit_per_vaultINT nullableper-vault 동시 tx 한도. Solana = 5. soft throttle — 초과 시 Submitted 대기, hard stuck 아님
concurrent_tx_limit_per_workspaceINT nullableworkspace-wide 동시 tx 한도. Solana = 600. per-vault 5 와 별도 scope
signing_window_secondsINT nullable서명 후 broadcast 까지 허용 시간. Algorand ~3000s
is_activeBOOLEAN서비스가 현재 그 chain 을 지원하는지. FALSE 면 신규 vault wallet 생성 불가, 기존 잔액은 유지
modified_by · modified_atBINARY(16) · DATETIME(6)이 row 의 마지막 변경자/시각. config 변경 이력 자체는 별도 audit 평면
운영 노트 — chain config 의 변경 경로
  • chain seed 추가 / 신규 chain 지원: 보통 migration 또는 deployment-time seed 로 INSERT. 실시간 UI 추가 시 audit 평면에 event 영속화 권장
  • 제약 값 변경 (예: BTC 가 mempool default 를 25 → N 으로 변경) : UPDATE + change_events 1 row + 20. Cross-DB Audit 의 hash chain
  • chain 지원 중단: is_active=FALSE 로 soft-disable. row 삭제하면 기존 wallet 의 FK 가 깨짐 — append-only/set-once 의 spine

Withdrawal Vault Round-Robin

flowchart TB
  subgraph SINGLE["❌ 단일 vault — sequential bottleneck"]
    direction LR
    SQ[("tx_1 · tx_2 · tx_3 · tx_4 …")] --> S_V["Vault A"]
    S_V --> S_STUCK["⚠️ hard stuck
EVM: nonce gap
BTC: 25-tx chain limit"] S_STUCK -.blocks all.-> S_WAIT["뒤따르는 tx
전부 대기"] end subgraph RR["✅ Round-Robin — N vaults"] direction LR RQ[("tx_1 · tx_2 · tx_3 · tx_4 · tx_5 …")] --> DISP{{"dispatcher
(round-robin)"}} DISP -->|"tx_1, tx_4 …"| VA["Vault A"] DISP -->|"tx_2, tx_5 …"| VB["Vault B"] DISP -->|"tx_3, tx_6 …"| VC["Vault C"] VA -.stuck.-> ISO_A["A 만 정체"] VB --> OK_B["B 정상 진행"] VC --> OK_C["C 정상 진행"] end classDef bad fill:#fee2e2,stroke:#dc2626,stroke-width:2px; classDef good fill:#dcfce7,stroke:#16a34a,stroke-width:2px; classDef vault fill:#dbeafe,stroke:#2563eb; classDef stuck fill:#fef3c7,stroke:#d97706; classDef okstate fill:#dcfce7,stroke:#16a34a; class SINGLE bad; class RR good; class S_V,VA,VB,VC vault; class S_STUCK,ISO_A stuck; class OK_B,OK_C okstate;
위 — 단일 vault 의 sequential bottleneck (EVM nonce gap · BTC 25-chain). 한 tx 가 막히면 뒤가 다 멈춤. 아래 — 같은 출금 흐름을 N 개의 독립 vault 로 round-robin 분산. 한 vault 가 정체돼도 나머지는 정상 진행. Solana 는 다름 — per-vault 5 는 hard stuck 이 아니라 soft throttle (Submitted 대기). 1 vault 로도 운영 가능, burst > 5/vault 일 때만 보조용 round-robin.
EVM (Ethereum · Polygon · Arbitrum 등)
같은 주소에서 출금하는 tx 는 0, 1, 2, 3 … 순서 번호 (nonce(?)) 를 반드시 순서대로 처리합니다. 한 tx 가 멈추면 (수수료 부족으로 채굴자가 안 집어감 등) 그 뒤 번호의 tx 가 전부 대기 상태로 묶입니다. 출금 vault 가 하나라면 그 vault 의 출금 전체가 멈춥니다.
출금 전용 vault 를 여러 개 두고 돌아가며 사용하면 (round-robin), 한 vault 가 막혀도 나머지로 출금이 계속 흐릅니다.
Bitcoin
아직 block 에 확정되지 않은 tx 는 Bitcoin 네트워크의 임시 보관소 (mempool) 에 쌓입니다. 한 tx 의 잔돈 (change) 을 다음 tx 의 입력으로 이어 쓰는 chain 이 25 개에 도달하면, 26 번째 tx 는 네트워크가 거부합니다 (Bitcoin Core 기본 설정(?)). block 확정까지 평균 10 분, 그 사이 빠르게 연속 출금하면 한 vault 가 25 limit 에 부딪혀 출금이 막힙니다.
출금 vault 를 여러 개로 분산해서 각각의 chain 이 25 에 부딪히지 않도록.
Solana
제약이 두 단계입니다.
  • vault 한 개당 동시 5 tx: 6 번째 출금 요청은 "Submitted" 상태로 줄을 서고, 최대 2 시간 안에 처리되지 않으면 자동 취소됩니다.
  • workspace 전체에서 동시 600 tx: 한 workspace 안의 모든 vault 의 진행 중 tx 합이 600 개를 넘으면 추가 요청을 안 받습니다.
EVM/BTC 와 결정적으로 다른 점 — 5 를 초과해도 tx 가 막히는 게 아니라 "기다리는" 상태일 뿐입니다 (앞 tx 가 끝나면 자동으로 다음 차례로). 즉 출금 vault 1 개로도 충분히 운영 가능합니다.
→ vault 여러 개로 나누는 것은 짧은 시간에 출금 요청이 갑자기 한꺼번에 몰릴 때 (예: 정각마다 일괄 자동 출금이 도는 운영에서 한 vault 의 5 한도를 넘길 가능성이 있는 경우) 에만 보조적으로 의미가 있습니다.