Institutional Multi-approval Wallet System Development
Institutional clients — funds, DAO treasuries, hedge funds — cannot afford single point of failure in asset management. One compromised key shouldn't give full control over funds. Multi-approval system isn't just multisig on smart contract, it's complex infrastructure: signing policies, participant roles, audit trail, hardware security module integrations, and recovery processes.
Architectural Options
Several implementation levels exist, choice depends on client requirements.
On-chain Multisig
Gnosis Safe (now Safe{Core}) — de facto standard for institutional wallets. Over $100 billion in assets under management, integrated in most large DAOs and corporate treasuries. Core: smart contract wallet with M-of-N signatures, transaction executes only with threshold signatures from owners.
Safe architecture:
- GnosisSafe.sol — main contract, stores owners mapping, threshold, nonce
- SafeProxy — minimalist proxy delegating calls to singleton implementation
- GnosisSafeProxyFactory — creates new Safe wallets via CREATE2 (deterministic address)
- Modules — functionality extensions: Recovery Module, Allowance Module, Guard Module
Instead of direct Safe SDK use, can build own management system. Safe transaction requires accumulating signatures off-chain and final execute call:
// Safe transaction structure
struct SafeTx {
address to;
uint256 value;
bytes data;
Enum.Operation operation; // Call or DelegateCall
uint256 safeTxGas;
uint256 baseGas;
uint256 gasPrice;
address gasToken;
address refundReceiver;
uint256 nonce;
}
Off-chain Policies with On-chain Execution
For complex scenarios — time-locks, spending limits, whitelist addresses — use combination: off-chain policy engine checks transaction before signature collection, then collected signatures passed to Safe for execution.
Fireblocks, BitGo implement exactly this model: transaction passes through policy engine (which can require approval from specific people, specific time windows, with amount limits) before reaching on-chain.
Detailed System Architecture
Components
Policy Engine — service storing and applying rules:
- Recipient address whitelist
- Spending limits (daily, weekly limit per token)
- Time-based rules (transactions only business hours UTC)
- Amount thresholds (under $10k — 2 signatures, over $100k — 5 signatures)
- Asset-specific rules (certain token operations require CFO approval)
Approval Workflow Engine — manages transaction request states:
PENDING_APPROVAL → COLLECTING_SIGNATURES → READY_TO_EXECUTE → EXECUTED
↘ REJECTED ↙
Each state transition logged with timestamp and actor_id. Mandatory for compliance.
Signature Aggregator — collects signatures from approvers. EIP-712 signatures (or EIP-1271 for smart contract signers). Stores partial signatures until threshold reached.
Notification Service — notifies approvers (email, Telegram, Slack) when transaction appears for signing. Includes deep-link for quick approval.
Hardware Security Module (HSM) Integration — for large institutions keys stored in HSM (AWS CloudHSM, Azure Dedicated HSM, Thales). Signing happens inside HSM without private key export.
Database
CREATE TABLE approval_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
safe_address VARCHAR(42) NOT NULL,
to_address VARCHAR(42) NOT NULL,
value NUMERIC(78, 0),
data TEXT,
operation SMALLINT DEFAULT 0,
nonce BIGINT NOT NULL,
safe_tx_hash VARCHAR(66) UNIQUE NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
metadata JSONB
);
CREATE TABLE approvals (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
request_id UUID REFERENCES approval_requests(id),
approver_id UUID REFERENCES users(id),
signature VARCHAR(132) NOT NULL,
signed_at TIMESTAMPTZ DEFAULT NOW(),
device_type VARCHAR(32), -- hardware_wallet, hsm, software
ip_address INET
);
CREATE TABLE policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
rules JSONB NOT NULL,
threshold INTEGER NOT NULL,
owners JSONB NOT NULL, -- approver list
is_active BOOLEAN DEFAULT TRUE
);
Transaction Creation Process
- Initiator creates request via API or UI
- Policy Engine checks transaction against active policies
- If policy violated — reject with explanation
- If OK — create
ApprovalRequestwithpendingstatus - Calculate
safeTxHash(deterministic Safe transaction hash) - Approvers receive notifications
- Each approver signs
safeTxHashwith their key - On threshold reached — status changes to
ready_to_execute - Relayer sends transaction on-chain with aggregated signatures
// Calculate safeTxHash
async function computeSafeTxHash(
safeAddress: string,
tx: SafeTx,
chainId: number
): Promise<string> {
const domain = {
chainId,
verifyingContract: safeAddress,
};
const types = {
SafeTx: [
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "data", type: "bytes" },
{ name: "operation", type: "uint8" },
{ name: "safeTxGas", type: "uint256" },
{ name: "baseGas", type: "uint256" },
{ name: "gasPrice", type: "uint256" },
{ name: "gasToken", type: "address" },
{ name: "refundReceiver", type: "address" },
{ name: "nonce", type: "uint256" },
],
};
return ethers.TypedDataEncoder.hash(domain, types, tx);
}
Roles and Access Rights
Typical role model for institutional wallet:
| Role | Rights |
|---|---|
| Admin | Manage policies, add/remove owners |
| Initiator | Create transaction requests |
| Approver | Sign transactions |
| Executor | Send on-chain after signature collection |
| Auditor | Read-only access to complete audit trail |
| Observer | View balances and statuses |
Roles can be combined. For large organizations — Initiator/Approver separation mandatory (four eyes).
Time-lock and Emergency
Time-lock. For transactions above threshold — mandatory delay before execution (e.g., 24 hours). Other owners can cancel during this period. Implemented via TimelockController OpenZeppelin or custom Safe Module.
Emergency Pause. If compromise detected — ability to freeze all outgoing transactions. Requires N-of-M signatures from Guardian keys (stored separately with independent custodians).
Social Recovery. If quorum owners unavailable (key loss, death) — recovery procedure via Recovery owners. Safe Recovery Module supports this out of the box.
HSM Integration
For AWS CloudHSM:
import * as pkcs11js from "pkcs11js";
class HSMSigner {
private pkcs11: pkcs11js.PKCS11;
async sign(txHash: Buffer, keyLabel: string): Promise<Buffer> {
const session = this.pkcs11.C_OpenSession(this.slotId, pkcs11js.CKF_SERIAL_SESSION);
this.pkcs11.C_Login(session, pkcs11js.CKU_USER, this.pin);
const privateKey = this.findKeyByLabel(session, keyLabel);
this.pkcs11.C_SignInit(session, { mechanism: pkcs11js.CKM_ECDSA }, privateKey);
const signature = this.pkcs11.C_Sign(session, txHash, Buffer.alloc(64));
this.pkcs11.C_Logout(session);
this.pkcs11.C_CloseSession(session);
return this.convertToEthSignature(signature);
}
}
Critical: private key never leaves HSM. Signing operation executes inside protected hardware with physical tamper-protection.
Audit and Compliance
Every action logged immutably:
- Request creation (who, when, from where)
- Each approval/rejection (who, when, from what device)
- On-chain execution (txHash)
- Policy changes
For financial organizations — export in auditor-compatible formats (CSV, PDF with digital signature). SIEM system integration (Splunk, Datadog) via webhook.
Development Stack
Backend: Node.js + TypeScript, PostgreSQL, Redis (queues), Bull (job processing) Smart contracts: Safe{Core} SDK, custom Safe Modules in Solidity HSM: AWS CloudHSM SDK, PKCS#11 Frontend: React + wagmi, Safe{Core} Protocol Kit Notifications: SendGrid, Telegram Bot API, Slack Webhooks Infrastructure: AWS ECS, RDS, CloudHSM cluster
Timeline
- Basic system (Safe + workflow + UI): 8-10 weeks
- With HSM integration: +3-4 weeks
- With compliance/audit export: +2 weeks
- Security audit: mandatory, +3-6 weeks
Total production-ready system with HSM and audit: 4-5 months.







