5. Wallets · Addresses · Asset Wallet
정식 매핑 — 3 asset 패턴 · whitelisted address · chain 별 특이 제약graph TB VA["🏦 Vault AccountVault Account — Asset Wallet — Address 관계. 같은 vault 안에 chain × asset 단위로 asset wallet 이 생성되고, address 분기는 chain 의 잔액 모델 (UTXO / account-based) 에 따라 3 패턴으로 갈린다.
(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;
3 Asset Pattern
| Asset 패턴 | 예시 | Permanent Address | Deposit Address 개수 | 분기자 |
|---|---|---|---|---|
| UTXO(?)-based | BTC, BCH, LTC, DOGE | 1 | N | address 그 자체 |
| Account-based (no tag) | ETH, EVM(?) 전반 | — | 1 (단일 강제) | — |
| Account-based (tag/memo) | XRP, XLM | — | 1 (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 / Program | EVM 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 Audit 의approval_decisions(approver 별mobile_device_sig) +audit_eventshash 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 amount | BTC 0.00000582 / ALGO 1e-6 / ADA 1 / DOGE 0.01 / TON 1e-6 | Submit 시 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 wallet | Role 별 생성 권한 차이 — role_permissions 테이블에 'TL' label |
| Chain type 분류 | EVM account-based / non-EVM account-based / UTXO / Cosmos SDK | chains 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
);
| 컬럼 | 자료형 | 역할 |
|---|---|---|
id | VARCHAR(32) PK | chain ticker 또는 short code. asset_wallets.chain_id 가 FK 로 가리킴 |
display_name | VARCHAR(128) | UI 표시용 정식 이름 ("Bitcoin", "Ethereum") |
type | ENUM (4 값) | 큰 분류: utxo / evm / non_evm_account / cosmos_sdk. 위 표의 "Chain type 분류" 행 대응 |
account_model | ENUM (3 값) | address 분기 모델 — 본 페이지 "3 Asset Pattern" 절의 3 패턴과 1:1 매핑 |
min_tx_amount | DECIMAL(40,20) | chain default 최소 송금. submit 시 validation. BTC 0.00000582 / TON 1e-6 등. 더 정밀한 per-asset 값은 별도 자산 테이블에서 override 가능 |
base_reserve_minimum | DECIMAL(40,20) | 활성 유지에 묶여 있어야 하는 최소 잔액. per-wallet 현재 reserve 는 asset_wallets.min_balance_reserved |
requires_activation | BOOLEAN | account 활성화가 필요한 chain 인지. asset_wallets.activated_at 의 작성 조건 |
activation_minimum | DECIMAL(40,20) nullable | 활성화 보증금. Stellar 1 XLM / Polkadot 0.01 DOT 등. requires_activation=FALSE 이면 NULL |
requires_trust_line | BOOLEAN | XRP 등에서 token 보유 시 trust line 필요 여부. on-chain 자동 삭제 가능성 알림 |
supports_internal_tx | BOOLEAN | EVM 의 smart contract 내부 native transfer 같은 deposit path 관찰이 필요한 chain 인지 |
supports_token_wallet | BOOLEAN | chain 위에 token wallet (ALGO/XRP/SOL/XLM 의 SPL/IOU/TL) 을 만들 수 있는지. role 'TL' label 의 트리거 조건 |
unconfirmed_chain_limit | INT nullable | UTXO 의 mempool descendant cap. BTC = 25. withdrawal vault round-robin 산정 기준 |
concurrent_tx_limit_per_vault | INT nullable | per-vault 동시 tx 한도. Solana = 5. soft throttle — 초과 시 Submitted 대기, hard stuck 아님 |
concurrent_tx_limit_per_workspace | INT nullable | workspace-wide 동시 tx 한도. Solana = 600. per-vault 5 와 별도 scope |
signing_window_seconds | INT nullable | 서명 후 broadcast 까지 허용 시간. Algorand ~3000s |
is_active | BOOLEAN | 서비스가 현재 그 chain 을 지원하는지. FALSE 면 신규 vault wallet 생성 불가, 기존 잔액은 유지 |
modified_by · modified_at | BINARY(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 개를 넘으면 추가 요청을 안 받습니다.
→ vault 여러 개로 나누는 것은 짧은 시간에 출금 요청이 갑자기 한꺼번에 몰릴 때 (예: 정각마다 일괄 자동 출금이 도는 운영에서 한 vault 의 5 한도를 넘길 가능성이 있는 경우) 에만 보조적으로 의미가 있습니다.