Custody Integration Guide¶
Custody Solutions Overview¶
Supported Custody Types:¶
1. Self-Custody (Current) - User controls private keys через MetaMask - Best for: Retail users, DeFi enthusiasts - Security: User responsibility
2. Custodial Wallets (Planned) - Saga manages private keys - Best for: Less technical users - Security: Platform responsibility
3. Institutional Custody (Future) - Third-party professional custody (Fireblocks, Copper, BitGo) - Best for: Institutions, large accounts - Security: Multi-signature, insurance
Self-Custody Architecture (Current)¶
Web3 Wallet Integration:¶
MetaMask Connection:
// frontend/user-app/src/lib/web3-auth-context.tsx
import { useConnect, useAccount } from 'wagmi';
export function connectWallet() {
const { connect, connectors } = useConnect();
const { address } = useAccount();
// Connect to MetaMask
const metaMask = connectors.find(c => c.id === 'metaMask');
if (metaMask) {
connect({ connector: metaMask });
}
return address;
}
Signature-based Authentication:
// User signs message with private key
const message = `Authenticate to Saga Platform\nNonce: ${nonce}`;
const signature = await wallet.signMessage(message);
// Backend verifies signature
const recoveredAddress = ethers.verifyMessage(message, signature);
if (recoveredAddress.toLowerCase() === walletAddress.toLowerCase()) {
// Authentication successful
}
Security Considerations:
- ✅ User retains full control of assets
- ✅ No private keys stored on Saga servers
- ✅ Non-custodial architecture (regulatory advantage)
- ⚠️ User responsible for key security
🏢 Institutional Custody Integration¶
Fireblocks Integration (Planned)¶
Architecture:
User → Saga Platform → Fireblocks API → Fireblocks Vault
↓
Multi-Signature
Approval Workflow
↓
Blockchain Transaction
Implementation Example:
// backend/custody/fireblocks/client.go
type FireblocksClient struct {
apiKey string
apiSecret string
baseURL string
}
func (c *FireblocksClient) CreateVaultAccount(userID int64) (*VaultAccount, error) {
// Create vault account for user
req := &CreateVaultRequest{
Name: fmt.Sprintf("saga-user-%d", userID),
HiddenOnUI: false,
AutoFuel: true,
}
resp, err := c.post("/v1/vault/accounts", req)
if err != nil {
return nil, err
}
return parseVaultAccount(resp), nil
}
func (c *FireblocksClient) InitiateWithdrawal(
vaultID string,
asset string,
amount *big.Int,
destination string,
) (*Transaction, error) {
req := &TransactionRequest{
AssetID: asset,
Source: VaultAccountSource{Type: "VAULT_ACCOUNT", ID: vaultID},
Destination: ExternalWalletDestination{Type: "EXTERNAL_WALLET", Address: destination},
Amount: amount.String(),
}
// Initiate transaction (requires approval)
txResp, err := c.post("/v1/transactions", req)
if err != nil {
return nil, err
}
return parseTransaction(txResp), nil
}
Multi-Signature Approval:
type ApprovalWorkflow struct {
MinApprovers int
Approvers []string
Policy *Policy
}
func (w *ApprovalWorkflow) RequiresApproval(amount *big.Int) bool {
// Amounts > $10,000 require 2-of-3 approval
threshold := big.NewInt(10000)
return amount.Cmp(threshold) > 0
}
func (w *ApprovalWorkflow) GetApprovers(amount *big.Int) []string {
if w.RequiresApproval(amount) {
return w.Approvers // Return all approvers for 2-of-3
}
return []string{} // Auto-approve small amounts
}
Copper Integration (Alternative)¶
Copper ClearLoop API:
// backend/custody/copper/client.go
type CopperClient struct {
apiKey string
apiSecret string
networkID string
}
func (c *CopperClient) CreateWallet(userID int64, currency string) (*Wallet, error) {
req := &CreateWalletRequest{
UserID: userID,
Currency: currency,
NetworkID: c.networkID,
}
resp, err := c.post("/wallets", req)
if err != nil {
return nil, err
}
return parseWallet(resp), nil
}
func (c *CopperClient) RequestWithdrawal(
walletID string,
amount string,
destination string,
) (*WithdrawalRequest, error) {
req := &WithdrawalRequest{
WalletID: walletID,
Amount: amount,
Destination: destination,
Priority: "NORMAL",
}
// Submit for approval
resp, err := c.post("/withdrawals", req)
if err != nil {
return nil, err
}
return parseWithdrawalRequest(resp), nil
}
💼 Custodial Wallet Architecture¶
Saga-Managed Wallets (Planned)¶
Key Management:
// backend/custody/internal/key_manager.go
type KeyManager struct {
kms *awskms.Client // AWS KMS for key storage
region string
}
func (km *KeyManager) GenerateWallet(userID int64) (*Wallet, error) {
// Generate key pair in AWS KMS
keyResp, err := km.kms.CreateKey(context.Background(), &awskms.CreateKeyInput{
Description: aws.String(fmt.Sprintf("saga-user-%d", userID)),
KeyUsage: types.KeyUsageTypeSignVerify,
KeySpec: types.KeySpecEccSecgP256k1, // Ethereum compatible
})
if err != nil {
return nil, err
}
// Derive Ethereum address from public key
publicKey, err := km.getPublicKey(keyResp.KeyMetadata.KeyId)
if err != nil {
return nil, err
}
address := crypto.PubkeyToAddress(*publicKey).Hex()
return &Wallet{
UserID: userID,
Address: address,
KeyID: *keyResp.KeyMetadata.KeyId,
}, nil
}
func (km *KeyManager) SignTransaction(keyID string, txHash []byte) ([]byte, error) {
// Sign transaction using KMS
signResp, err := km.kms.Sign(context.Background(), &awskms.SignInput{
KeyId: aws.String(keyID),
Message: txHash,
MessageType: types.MessageTypeDigest,
SigningAlgorithm: types.SigningAlgorithmSpecEcdsaSha256,
})
if err != nil {
return nil, err
}
return signResp.Signature, nil
}
Security Architecture:
- ✅ Private keys NEVER leave AWS KMS
- ✅ Multi-region replication
- ✅ Audit logging (CloudTrail)
- ✅ Role-based access control
- ✅ Hardware security modules (HSM)
Withdrawal Flow with Custodial Wallets:¶
1. User Request → Saga Backend
2. Saga Backend → AWS KMS (sign transaction)
3. AWS KMS → Signed Transaction
4. Saga Backend → Blockchain
5. Blockchain → Confirmation
6. Saga Backend → User Notification
Migration Path¶
Phase 1: Self-Custody (Current)¶
- ✅ MetaMask integration
- ✅ Signature-based auth
- ✅ User controls keys
Phase 2: Custodial Option (Q2 2025)¶
- 🔄 AWS KMS integration
- 🔄 Saga-managed wallets
- 🔄 User choice: self-custody vs custodial
Phase 3: Institutional Custody (Q3 2025)¶
- 📅 Fireblocks/Copper integration
- 📅 Multi-signature workflows
- 📅 Insurance coverage
- 📅 Institutional grade security
API Integration Examples¶
Create Custodial Account:¶
POST /api/custody/accounts
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"custody_type": "fireblocks",
"user_id": 123,
"asset": "USDC"
}
Response:
{
"account_id": "fb-vault-456",
"address": "0x742d35Cc9Cf3C4C3a3F5d7B5f",
"custody_provider": "fireblocks",
"created_at": "2025-10-06T12:00:00Z"
}
Initiate Custodial Withdrawal:¶
POST /api/custody/withdrawals
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"account_id": "fb-vault-456",
"amount": "1000.00",
"asset": "USDC",
"destination": "0xE4B5F7D8C9A2B1C3D4E5F6G7H8"
}
Response:
{
"withdrawal_id": "wd-789",
"status": "pending_approval",
"approvers_required": 2,
"approved_by": [],
"estimated_completion": "2025-10-06T14:00:00Z"
}
Check Approval Status:¶
GET /api/custody/withdrawals/wd-789/status
Authorization: Bearer <jwt_token>
Response:
{
"withdrawal_id": "wd-789",
"status": "approved",
"approved_by": ["admin1@saga.com", "admin2@saga.com"],
"transaction_hash": "0xabc123...",
"blockchain_confirmations": 12
}
Compliance and Reporting¶
Audit Trail:¶
type CustodyAuditLog struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Action string `json:"action"`
CustodyType string `json:"custody_type"`
Amount SafeDecimal `json:"amount"`
Status string `json:"status"`
Approvers []string `json:"approvers,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
func (s *CustodyService) LogAction(log *CustodyAuditLog) error {
// Insert audit log
_, err := s.db.Exec(`
INSERT INTO custody_audit_logs (
user_id, action, custody_type, amount, status, approvers, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7)
`, log.UserID, log.Action, log.CustodyType, log.Amount, log.Status, pq.Array(log.Approvers), log.CreatedAt)
return err
}
Regulatory Reporting:¶
func (s *CustodyService) GenerateComplianceReport(startDate, endDate time.Time) (*ComplianceReport, error) {
rows, err := s.db.Query(`
SELECT
custody_type,
COUNT(*) as transaction_count,
SUM(amount) as total_volume,
AVG(amount) as average_amount
FROM custody_audit_logs
WHERE created_at BETWEEN $1 AND $2
GROUP BY custody_type
`, startDate, endDate)
if err != nil {
return nil, err
}
defer rows.Close()
report := &ComplianceReport{
Period: fmt.Sprintf("%s to %s", startDate.Format("2006-01-02"), endDate.Format("2006-01-02")),
Providers: make([]*ProviderReport, 0),
}
for rows.Next() {
var pr ProviderReport
if err := rows.Scan(&pr.CustodyType, &pr.TransactionCount, &pr.TotalVolume, &pr.AverageAmount); err != nil {
return nil, err
}
report.Providers = append(report.Providers, &pr)
}
return report, nil
}
Related Documentation¶
- Security Architecture - Overall security design
- Withdrawal Flow - Withdrawal process
- Admin Approval - Admin workflow
📋 Метаданные¶
Версия: 2.4.82
Обновлено: 2025-10-21
Статус: Published