4. Vault Account(?)

workspace 안의 자산 보관 단위 — 다중 role · 변경 이력 · vault 구성 패턴 4 종 (Omnibus / Segregated / Treasury / Withdrawal)

Vault Account — workspace 안의 자산 그룹

한 workspace 안에서 client / 팀 / 용도별로 자산을 나누는 단위. vault 의 생성·변경·archive 이력은 1.5 절 의 vault_account_change_events 가 영속화.

1.1 vault_accounts 테이블

CREATE TABLE vault_accounts (
  id                       BINARY(16) PRIMARY KEY,
  workspace_id             BINARY(16) NOT NULL,
  name                     VARCHAR(128) NOT NULL,
  hidden                   BOOLEAN NOT NULL DEFAULT FALSE,
  created_by               BINARY(16) NOT NULL,        -- 생성 요청한 user
  created_at               DATETIME(6) NOT NULL,
  approved_by_quorum_id    BINARY(16) NOT NULL,        -- Admin Quorum 승인 (set-once, 생성용)
  approved_at              DATETIME(6) NOT NULL,       -- threshold 충족 시각 (set-once)
  modified_by              BINARY(16) NOT NULL,
  modified_at              DATETIME(6) NOT NULL,
  archived_at              DATETIME(6),                -- archive 시각 (set-once when set)
  archived_by              BINARY(16),                 -- archive 실행한 user (set-once when set)
  KEY (workspace_id)
);

1.2 필드별 설명

컬럼자료형역할
idBINARY(16) PKvault account 의 고유 식별자
workspace_idBINARY(16)어느 workspace 에 속하는지. FK 검증은 같은 DB 안에서만
nameVARCHAR(128)사람이 읽는 이름표 (예: "Client Acme - USD vault", "Mint authority")
hiddenBOOLEANUI 에서 숨김 처리 여부. soft-hide — 자료는 그대로, 화면에 안 보일 뿐
created_byBINARY(16)이 vault account 를 생성 요청 한 user id (= 요청자). set-once
created_atDATETIME(6)생성 요청 시각. set-once
approved_by_quorum_idBINARY(16)Admin Quorum 의 id (FK → admin_quorums.id). vault 생성에는 quorum 승인이 필수 — 우리 시스템은 Fireblocks 보다 엄격하게 vault 생성을 approval-gated 로 처리. set-once
approved_atDATETIME(6)threshold (N-of-M) 충족 시각. 이 row 는 승인 완료 후에만 INSERT — pending 상태는 별도 approval_requests 평면에서 관리. set-once
modified_byBINARY(16)마지막 변경자의 user id. 변경 발생 시마다 갱신. 자세한 이력은 1.5 change log
modified_atDATETIME(6)마지막 변경 시각. 무엇이 변경되었는지는 1.5 change log
archived_atDATETIME(6) nullablearchive (영구 숨김) 시각. NULL = 활성. hide 와 다르게 archive 는 더 강한 soft-delete. set-once when set
archived_byBINARY(16) nullablearchive 를 실행한 user id. archive 발생 시에만 set, set-once. admin 단독 동사 (quorum-gated 아님)

role 은 별도 junction table 로 — 하나의 vault 가 다중 role (예: treasury + general) 을 동시 보유 가능하므로 vault_accounts 본체에 단일 ENUM 으로 두지 않음. 다음 1.3 절 참조.

Vault lifecycle 의 quorum-gated 분류 (design choice — Fireblocks default 보다 엄격)

vault 의 lifecycle 동사 중 어디까지를 Admin Quorum 승인으로 막을지의 현재 design:

  • quorum-gated: create — vault 자체의 존재가 자산 책임 경계를 정의하므로 단독 admin 으로 못 만듦
  • admin 단독: rename · hide · unhide · archive — 자산 자체에 영향을 주지 않는 UI / 가시성 변경. modified_by 또는 archived_by + change_events 의 actor_user_id
  • 참고: vault_account_roles 의 grant/revoke 도 현재 admin 단독 (1.3 절). role 변경이 운영 위험을 키울 수 있다면 별도 재검토 가능
본 schema 의 범위 밖 — 후속 검토 위치
  • 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 패턴
  • Pending 상태의 approval workflow — vault 생성 요청은 quorum threshold 충족 전까지 pending. 그 상태 (request_id, 누구에게 routed, 누가 sign, 누가 reject, expire 시각) 는 별도 approval_requests 평면 (Add user 의 7-day expiry 와 같은 메커니즘) 에서 영속화. → 9. Admin Quorum

1.3 vault_account_roles — vault 의 다중 role 부여

하나의 vault 가 여러 role 동시 보유 가능 (예: treasury + general). 각 role 독립 grant / revoke + audit trail.

CREATE TABLE vault_account_roles (
  id                BINARY(16) PRIMARY KEY,
  vault_account_id  BINARY(16) NOT NULL,
  role              ENUM('treasury', 'client', 'intermediate', 'withdrawal', 'general') NOT NULL,
  granted_by        BINARY(16) NOT NULL,
  granted_at        DATETIME(6) NOT NULL,
  revoked_by        BINARY(16),
  revoked_at        DATETIME(6),
  KEY (vault_account_id, role),
  KEY (vault_account_id, revoked_at)
);
컬럼자료형역할
idBINARY(16) PKrole grant 의 고유 식별자
vault_account_idBINARY(16)어느 vault account 의 role 부여인지
roleENUM (5 값)vault 의 용도 분류 (다음 1.4 절 표 참조)
granted_byBINARY(16)부여자 user id
granted_atDATETIME(6)부여 시각. set-once
revoked_byBINARY(16) nullable회수자 user id. NULL = 현재 active
revoked_atDATETIME(6) nullable회수 시각. NULL = active. set-once
왜 junction table 인가
  • 1 vault → N roles: 같은 vault 가 treasury + general 등 동시 보유 가능
  • 독립 grant / revoke: 한 role 만 회수 + 나머지 유지 같은 부분 변경 가능
  • 독립 audit trail: 각 role 부여·회수가 자신의 row + timestamp + user 정보 보유
  • set-once row: revoke 후 같은 role 을 재부여할 때 새 row INSERT → role 부여 history 누적
  • 현재 active role 조회: SELECT role FROM vault_account_roles WHERE vault_account_id=? AND revoked_at IS NULL

1.4 role ENUM 5 값 — 각 값의 의미

의미전형적 정책
treasury회사 전체의 메인 자금 vault. 모든 자산의 최상위 저장소가장 엄격 — 큰 금액 송금에 다수 승인 강제
client한 개별 client 의 자산 보관해당 client 의 입출금 한도 + KYC 기준
intermediate중계용 vault. EVM 처럼 "1 vault = 1 address" 제약 chain 에서 client 별로 unique 입금 주소 제공하려고 만듦입금만 받고 즉시 treasury 로 sweep
withdrawal외부로 송금하는 전용 vault. 여러 개 두고 round-robin 으로 사용해서 chain 의 sequential bottleneck 회피EVM 의 nonce 충돌 / Bitcoin 의 25-tx chain limit(?) 회피
general위 분류에 안 맞는 일반 용도기본 정책
⚠️ Scope out — mint / burn / pause / deploy / upgrade 는 본 문서 범위 밖

위 5 종 (token / contract 권한) 은 수탁형 지갑의 본연 기능이 아님:

  • 수탁형 지갑 = 고객 자산 보관 + 송금 이 본질
  • mint / burn = token issuer (Circle, Tether 등) 의 작업
  • pause / deploy / upgrade = protocol operator 의 작업

Fireblocks 가 이를 vault 권한으로 다루는 이유는 plain custodian 이 아니라 digital asset operations platform 이고, 고객 중에 token issuer / protocol team 이 있어서. 순수 custodial wallet 만 구축한다면 위 5 종은 schema 에 포함시키지 않음.

Hybrid (수탁 + token operation) use case 가 필요한 경우: 별도 vault_contract_roles 테이블로 (vault_account_id, chain, contract_address, role) 4-tuple key 의 구조 추가. 본 문서에서는 다루지 않음.

1.5 vault_account_change_events — vault 본체 변경 이력

vault account 의 본체 변경 (rename / hide / unhide / archive) 이력.

CREATE TABLE vault_account_change_events (
  id                    BINARY(16) PRIMARY KEY,
  vault_account_id      BINARY(16) NOT NULL,
  event_type            ENUM('created', 'renamed', 'hidden', 'unhidden', 'archived') NOT NULL,
  field_name            VARCHAR(64) NOT NULL,
  old_value             JSON,
  new_value             JSON,
  actor_user_id         BINARY(16) NOT NULL,
  approved_by_quorum_id BINARY(16),                   -- quorum-gated event (현재 created 만) 에서만 set
  audit_event_id        BINARY(16),
  occurred_at           DATETIME(6) NOT NULL,
  KEY (vault_account_id, occurred_at),
  KEY (event_type)
);
컬럼자료형역할
idBINARY(16) PK이벤트 고유 식별자
vault_account_idBINARY(16)어느 vault account 의 변경인지
event_typeENUM (5 값)created · renamed · hidden · unhidden · archived
field_nameVARCHAR(64)변경된 컬럼명 (예: name, hidden)
old_value · new_valueJSON nullable변경 전/후 값. created event 시 old=NULL
actor_user_idBINARY(16)변경 실행자의 user id (= 변경을 요청 한 user). quorum-gated event 의 경우 요청자가 quorum approver 자신일 필요는 없음
approved_by_quorum_idBINARY(16) nullable이 이벤트가 quorum-gated 일 때만 set — 현재 created 1 종. renamed · hidden · unhidden · archived 는 admin 단독 (NULL)
audit_event_idBINARY(16) nullableauditdb.audit_events 의 cross-DB binding
occurred_atDATETIME(6)변경 발생 시각
왜 별도 change log 가 필요한가
  • 외부 감사 대응: 분기별 SOC 2(?) 감사 시 "이 workspace 의 owner 가 언제 누구에게 transferred 되었나?" / "AML 정책이 언제 변경되었나?" 같은 질문에 즉시 답할 수 있어야 함
  • 법적 책임 추적: 사고 발생 시 "누가 언제 무엇을 바꿨다" 의 cryptographic evidence (auditdb 의 hash chain 으로도 영속화) 필수
  • UPDATE 만으로는 부족: modified_at 은 "마지막 변경 시점" 만 알 뿐, 무엇이 어떻게 변했는지는 별도 log 가 있어야 알 수 있음

Vault Structure 4 가지 패턴 — 실제 운영 방식

출처: vault-structure-best-practices.md

패턴설명 + 언제 쓰나
Omnibus
(통합형)
중앙 treasury vault 1 개 + client 별 intermediate vault. EVM 처럼 1 vault = 1 address 인 chain 에서 client 별 unique 입금 주소를 만들려고 intermediate 를 둠. 입금되면 즉시 treasury 로 sweep.
Segregated
(분리형)
per-client / per-team / per-operation 으로 vault 를 완전히 분리. 규제상 client 자산 분리가 필요하거나 회계상 명확한 attribution 이 필요할 때.
Treasury vault전체 자금의 메인 vault. 가장 엄격한 policy 적용 (다수 승인 + 큰 금액 cap + 외부 주소 whitelist 만).
Smart contract per-op vault
(hybrid use case)
※ 순수 수탁형 지갑 본연 기능 아님 — token issuer / protocol operator 가 custody 와 함께 사용하는 hybrid 패턴. contract 의 특권 작업마다 별도 vault 분리해서 권한 노출 최소화.
Withdrawal vault round-robin외부 송금 vault 를 여러 개 두고 순환 사용. 한 vault 의 tx 가 stuck 되어도 다른 vault 로 진행 가능. EVM 의 nonce 순차성 / Bitcoin 의 25-tx unconfirmed chain limit 같은 chain 의 sequential bottleneck 회피.

참고글