mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
825 lines
20 KiB
Go
825 lines
20 KiB
Go
package lx
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// MultisigWallet represents a multi-signature wallet
|
|
type MultisigWallet struct {
|
|
// Wallet identification
|
|
ID string
|
|
Name string
|
|
Description string
|
|
|
|
// Signers and thresholds
|
|
Owners map[string]*WalletOwner // address -> owner
|
|
RequiredSigs int // Required signatures for execution
|
|
TotalOwners int // Total number of owners
|
|
|
|
// Advanced multisig features
|
|
ThresholdLevels map[TxType]int // Different thresholds for different operations
|
|
TimeLockedOps map[string]*TimeLock // Time-locked operations
|
|
SpendingLimits map[string]*SpendingLimit // Daily/weekly spending limits
|
|
|
|
// Transactions
|
|
PendingTxs map[string]*MultisigTransaction
|
|
ExecutedTxs map[string]*MultisigTransaction
|
|
RejectedTxs map[string]*MultisigTransaction
|
|
|
|
// Wallet state
|
|
Balance map[string]*big.Int // token -> balance
|
|
Nonce uint64 // Transaction nonce
|
|
CreatedAt time.Time
|
|
LastActivity time.Time
|
|
|
|
// Security features
|
|
Frozen bool // Emergency freeze
|
|
WhitelistedAddrs map[string]bool // Whitelisted recipient addresses
|
|
BlacklistedAddrs map[string]bool // Blacklisted addresses
|
|
RequireWhitelist bool // Only send to whitelisted addresses
|
|
|
|
// Recovery mechanism
|
|
RecoveryOwners map[string]*WalletOwner // Recovery signers
|
|
RecoveryThreshold int // Signatures needed for recovery
|
|
RecoveryDelay time.Duration // Delay before recovery takes effect
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// WalletOwner represents an owner of the multisig wallet
|
|
type WalletOwner struct {
|
|
Address string
|
|
Name string
|
|
PublicKey *ecdsa.PublicKey
|
|
Weight int // For weighted multisig
|
|
AddedAt time.Time
|
|
LastActive time.Time
|
|
DailyLimit *big.Int // Individual daily spending limit
|
|
SpentToday *big.Int
|
|
LastResetDate time.Time
|
|
CanAddOwner bool // Permission to add owners
|
|
CanRemoveOwner bool // Permission to remove owners
|
|
CanChangeThreshold bool // Permission to change threshold
|
|
}
|
|
|
|
// MultisigTransaction represents a transaction requiring multiple signatures
|
|
type MultisigTransaction struct {
|
|
ID string
|
|
WalletID string
|
|
Type TxType
|
|
Nonce uint64
|
|
|
|
// Transaction details
|
|
To string
|
|
Value *big.Int
|
|
Token string // Token to transfer (empty for native)
|
|
Data []byte // Additional data/calldata
|
|
|
|
// Signatures
|
|
Signatures map[string]*TxSignature // owner -> signature
|
|
Confirmations int // Current confirmations
|
|
RequiredSigs int // Required signatures
|
|
|
|
// Metadata
|
|
Proposer string
|
|
Description string
|
|
CreatedAt time.Time
|
|
ExecutedAt time.Time
|
|
ExpiresAt time.Time
|
|
|
|
// Status
|
|
Status TxStatus
|
|
ExecutionResult string
|
|
GasUsed uint64
|
|
TxHash string
|
|
}
|
|
|
|
// TxType represents the type of transaction
|
|
type TxType uint8
|
|
|
|
const (
|
|
TxTypeTransfer TxType = iota
|
|
TxTypeAddOwner
|
|
TxTypeRemoveOwner
|
|
TxTypeChangeThreshold
|
|
TxTypeFreeze
|
|
TxTypeUnfreeze
|
|
TxTypeWhitelist
|
|
TxTypeBlacklist
|
|
TxTypeSetLimit
|
|
TxTypeContractCall
|
|
TxTypeRecovery
|
|
TxTypeCancelTx
|
|
)
|
|
|
|
// TxStatus represents transaction status
|
|
type TxStatus uint8
|
|
|
|
const (
|
|
TxStatusPending TxStatus = iota
|
|
TxStatusApproved
|
|
TxStatusExecuting
|
|
TxStatusExecuted
|
|
TxStatusRejected
|
|
TxStatusExpired
|
|
TxStatusCancelled
|
|
)
|
|
|
|
// TxSignature represents a signature on a transaction
|
|
type TxSignature struct {
|
|
Signer string
|
|
Signature []byte
|
|
SignedAt time.Time
|
|
V uint8
|
|
R []byte
|
|
S []byte
|
|
}
|
|
|
|
// TimeLock represents a time-locked operation
|
|
type TimeLock struct {
|
|
OperationType TxType
|
|
UnlockTime time.Time
|
|
MinDelay time.Duration
|
|
MaxDelay time.Duration
|
|
}
|
|
|
|
// SpendingLimit represents spending limits
|
|
type SpendingLimit struct {
|
|
Period LimitPeriod
|
|
Amount *big.Int
|
|
Spent *big.Int
|
|
LastReset time.Time
|
|
Token string // Token this limit applies to
|
|
}
|
|
|
|
// LimitPeriod represents the period for spending limits
|
|
type LimitPeriod uint8
|
|
|
|
const (
|
|
LimitPeriodDaily LimitPeriod = iota
|
|
LimitPeriodWeekly
|
|
LimitPeriodMonthly
|
|
)
|
|
|
|
// MultisigManager manages all multisig wallets
|
|
type MultisigManager struct {
|
|
Wallets map[string]*MultisigWallet
|
|
UserWallets map[string][]*MultisigWallet // user -> wallets they're part of
|
|
|
|
// Global settings
|
|
MaxOwnersPerWallet int
|
|
MinThreshold int
|
|
MaxPendingTxs int
|
|
TxExpiryDuration time.Duration
|
|
|
|
// Security
|
|
GlobalFreeze bool
|
|
EmergencyContacts []string
|
|
|
|
// Analytics
|
|
TotalWallets int
|
|
TotalTransactions uint64
|
|
TotalVolume map[string]*big.Int // token -> volume
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewMultisigManager creates a new multisig manager
|
|
func NewMultisigManager() *MultisigManager {
|
|
return &MultisigManager{
|
|
Wallets: make(map[string]*MultisigWallet),
|
|
UserWallets: make(map[string][]*MultisigWallet),
|
|
MaxOwnersPerWallet: 20,
|
|
MinThreshold: 1,
|
|
MaxPendingTxs: 100,
|
|
TxExpiryDuration: 7 * 24 * time.Hour, // 7 days
|
|
TotalVolume: make(map[string]*big.Int),
|
|
}
|
|
}
|
|
|
|
// CreateWallet creates a new multisig wallet
|
|
func (mm *MultisigManager) CreateWallet(
|
|
name string,
|
|
owners []string,
|
|
threshold int,
|
|
) (*MultisigWallet, error) {
|
|
mm.mu.Lock()
|
|
defer mm.mu.Unlock()
|
|
|
|
// Validation
|
|
if len(owners) == 0 {
|
|
return nil, errors.New("at least one owner required")
|
|
}
|
|
|
|
if len(owners) > mm.MaxOwnersPerWallet {
|
|
return nil, fmt.Errorf("maximum %d owners allowed", mm.MaxOwnersPerWallet)
|
|
}
|
|
|
|
if threshold < mm.MinThreshold || threshold > len(owners) {
|
|
return nil, fmt.Errorf("threshold must be between %d and %d", mm.MinThreshold, len(owners))
|
|
}
|
|
|
|
// Check for duplicate owners
|
|
ownerMap := make(map[string]bool)
|
|
for _, owner := range owners {
|
|
if ownerMap[owner] {
|
|
return nil, errors.New("duplicate owner address")
|
|
}
|
|
ownerMap[owner] = true
|
|
}
|
|
|
|
walletID := mm.generateWalletID()
|
|
|
|
// Create wallet
|
|
wallet := &MultisigWallet{
|
|
ID: walletID,
|
|
Name: name,
|
|
Owners: make(map[string]*WalletOwner),
|
|
RequiredSigs: threshold,
|
|
TotalOwners: len(owners),
|
|
ThresholdLevels: make(map[TxType]int),
|
|
TimeLockedOps: make(map[string]*TimeLock),
|
|
SpendingLimits: make(map[string]*SpendingLimit),
|
|
PendingTxs: make(map[string]*MultisigTransaction),
|
|
ExecutedTxs: make(map[string]*MultisigTransaction),
|
|
RejectedTxs: make(map[string]*MultisigTransaction),
|
|
Balance: make(map[string]*big.Int),
|
|
WhitelistedAddrs: make(map[string]bool),
|
|
BlacklistedAddrs: make(map[string]bool),
|
|
RecoveryOwners: make(map[string]*WalletOwner),
|
|
CreatedAt: time.Now(),
|
|
LastActivity: time.Now(),
|
|
}
|
|
|
|
// Set default threshold levels for different operations
|
|
wallet.ThresholdLevels[TxTypeTransfer] = threshold
|
|
wallet.ThresholdLevels[TxTypeAddOwner] = min(threshold+1, len(owners))
|
|
wallet.ThresholdLevels[TxTypeRemoveOwner] = min(threshold+1, len(owners))
|
|
wallet.ThresholdLevels[TxTypeChangeThreshold] = min(threshold+1, len(owners))
|
|
wallet.ThresholdLevels[TxTypeFreeze] = max(2, threshold/2)
|
|
wallet.ThresholdLevels[TxTypeRecovery] = len(owners) // All owners for recovery
|
|
|
|
// Add owners
|
|
for _, ownerAddr := range owners {
|
|
owner := &WalletOwner{
|
|
Address: ownerAddr,
|
|
Weight: 1, // Equal weight by default
|
|
AddedAt: time.Now(),
|
|
DailyLimit: big.NewInt(0), // No individual limit by default
|
|
SpentToday: big.NewInt(0),
|
|
LastResetDate: time.Now(),
|
|
CanAddOwner: true,
|
|
CanRemoveOwner: true,
|
|
CanChangeThreshold: true,
|
|
}
|
|
wallet.Owners[ownerAddr] = owner
|
|
|
|
// Track user wallets
|
|
mm.UserWallets[ownerAddr] = append(mm.UserWallets[ownerAddr], wallet)
|
|
}
|
|
|
|
mm.Wallets[walletID] = wallet
|
|
mm.TotalWallets++
|
|
|
|
return wallet, nil
|
|
}
|
|
|
|
// ProposeTransaction proposes a new transaction
|
|
func (mm *MultisigManager) ProposeTransaction(
|
|
walletID string,
|
|
proposer string,
|
|
txType TxType,
|
|
to string,
|
|
value *big.Int,
|
|
token string,
|
|
data []byte,
|
|
description string,
|
|
) (*MultisigTransaction, error) {
|
|
mm.mu.Lock()
|
|
defer mm.mu.Unlock()
|
|
|
|
wallet, exists := mm.Wallets[walletID]
|
|
if !exists {
|
|
return nil, errors.New("wallet not found")
|
|
}
|
|
|
|
wallet.mu.Lock()
|
|
defer wallet.mu.Unlock()
|
|
|
|
// Check if proposer is owner
|
|
if _, isOwner := wallet.Owners[proposer]; !isOwner {
|
|
return nil, errors.New("proposer is not an owner")
|
|
}
|
|
|
|
// Check if wallet is frozen
|
|
if wallet.Frozen && txType != TxTypeUnfreeze {
|
|
return nil, errors.New("wallet is frozen")
|
|
}
|
|
|
|
// Check pending transaction limit
|
|
if len(wallet.PendingTxs) >= mm.MaxPendingTxs {
|
|
return nil, errors.New("too many pending transactions")
|
|
}
|
|
|
|
// Validate recipient for transfers
|
|
if txType == TxTypeTransfer {
|
|
if wallet.RequireWhitelist && !wallet.WhitelistedAddrs[to] {
|
|
return nil, errors.New("recipient not whitelisted")
|
|
}
|
|
if wallet.BlacklistedAddrs[to] {
|
|
return nil, errors.New("recipient is blacklisted")
|
|
}
|
|
|
|
// Check spending limits
|
|
if err := mm.checkSpendingLimits(wallet, token, value); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Get required signatures for this transaction type
|
|
requiredSigs := wallet.ThresholdLevels[txType]
|
|
if requiredSigs == 0 {
|
|
requiredSigs = wallet.RequiredSigs
|
|
}
|
|
|
|
// Create transaction
|
|
tx := &MultisigTransaction{
|
|
ID: mm.generateTxID(),
|
|
WalletID: walletID,
|
|
Type: txType,
|
|
Nonce: wallet.Nonce,
|
|
To: to,
|
|
Value: value,
|
|
Token: token,
|
|
Data: data,
|
|
Signatures: make(map[string]*TxSignature),
|
|
RequiredSigs: requiredSigs,
|
|
Proposer: proposer,
|
|
Description: description,
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(mm.TxExpiryDuration),
|
|
Status: TxStatusPending,
|
|
}
|
|
|
|
// Automatically sign by proposer
|
|
signature := mm.signTransaction(tx, proposer)
|
|
tx.Signatures[proposer] = signature
|
|
tx.Confirmations = 1
|
|
|
|
// Store transaction
|
|
wallet.PendingTxs[tx.ID] = tx
|
|
wallet.Nonce++
|
|
wallet.LastActivity = time.Now()
|
|
|
|
// Check if immediately executable
|
|
if tx.Confirmations >= tx.RequiredSigs {
|
|
go mm.executeTransaction(tx)
|
|
}
|
|
|
|
return tx, nil
|
|
}
|
|
|
|
// ConfirmTransaction adds a signature to a pending transaction
|
|
func (mm *MultisigManager) ConfirmTransaction(
|
|
walletID string,
|
|
txID string,
|
|
signer string,
|
|
) error {
|
|
mm.mu.Lock()
|
|
defer mm.mu.Unlock()
|
|
|
|
wallet, exists := mm.Wallets[walletID]
|
|
if !exists {
|
|
return errors.New("wallet not found")
|
|
}
|
|
|
|
wallet.mu.Lock()
|
|
defer wallet.mu.Unlock()
|
|
|
|
// Check if signer is owner
|
|
owner, isOwner := wallet.Owners[signer]
|
|
if !isOwner {
|
|
return errors.New("signer is not an owner")
|
|
}
|
|
|
|
// Get transaction
|
|
tx, exists := wallet.PendingTxs[txID]
|
|
if !exists {
|
|
return errors.New("transaction not found")
|
|
}
|
|
|
|
// Check if already signed
|
|
if _, signed := tx.Signatures[signer]; signed {
|
|
return errors.New("already signed by this owner")
|
|
}
|
|
|
|
// Check expiry
|
|
if time.Now().After(tx.ExpiresAt) {
|
|
tx.Status = TxStatusExpired
|
|
delete(wallet.PendingTxs, txID)
|
|
wallet.RejectedTxs[txID] = tx
|
|
return errors.New("transaction expired")
|
|
}
|
|
|
|
// Add signature
|
|
signature := mm.signTransaction(tx, signer)
|
|
tx.Signatures[signer] = signature
|
|
tx.Confirmations++
|
|
|
|
// Update owner activity
|
|
owner.LastActive = time.Now()
|
|
|
|
// Check if ready to execute
|
|
if tx.Confirmations >= tx.RequiredSigs {
|
|
tx.Status = TxStatusApproved
|
|
go mm.executeTransaction(tx)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokeConfirmation revokes a signature from a transaction
|
|
func (mm *MultisigManager) RevokeConfirmation(
|
|
walletID string,
|
|
txID string,
|
|
signer string,
|
|
) error {
|
|
mm.mu.Lock()
|
|
defer mm.mu.Unlock()
|
|
|
|
wallet, exists := mm.Wallets[walletID]
|
|
if !exists {
|
|
return errors.New("wallet not found")
|
|
}
|
|
|
|
wallet.mu.Lock()
|
|
defer wallet.mu.Unlock()
|
|
|
|
tx, exists := wallet.PendingTxs[txID]
|
|
if !exists {
|
|
return errors.New("transaction not found")
|
|
}
|
|
|
|
// Check if signed by this owner
|
|
if _, signed := tx.Signatures[signer]; !signed {
|
|
return errors.New("not signed by this owner")
|
|
}
|
|
|
|
// Remove signature
|
|
delete(tx.Signatures, signer)
|
|
tx.Confirmations--
|
|
|
|
// Update status if needed
|
|
if tx.Status == TxStatusApproved && tx.Confirmations < tx.RequiredSigs {
|
|
tx.Status = TxStatusPending
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// executeTransaction executes an approved transaction
|
|
func (mm *MultisigManager) executeTransaction(tx *MultisigTransaction) error {
|
|
mm.mu.Lock()
|
|
wallet := mm.Wallets[tx.WalletID]
|
|
mm.mu.Unlock()
|
|
|
|
if wallet == nil {
|
|
return errors.New("wallet not found")
|
|
}
|
|
|
|
wallet.mu.Lock()
|
|
defer wallet.mu.Unlock()
|
|
|
|
// Double-check confirmations
|
|
if tx.Confirmations < tx.RequiredSigs {
|
|
return errors.New("insufficient confirmations")
|
|
}
|
|
|
|
tx.Status = TxStatusExecuting
|
|
|
|
// Execute based on transaction type
|
|
var err error
|
|
switch tx.Type {
|
|
case TxTypeTransfer:
|
|
err = mm.executeTransfer(wallet, tx)
|
|
|
|
case TxTypeAddOwner:
|
|
err = mm.executeAddOwner(wallet, tx)
|
|
|
|
case TxTypeRemoveOwner:
|
|
err = mm.executeRemoveOwner(wallet, tx)
|
|
|
|
case TxTypeChangeThreshold:
|
|
err = mm.executeChangeThreshold(wallet, tx)
|
|
|
|
case TxTypeFreeze:
|
|
wallet.Frozen = true
|
|
|
|
case TxTypeUnfreeze:
|
|
wallet.Frozen = false
|
|
|
|
case TxTypeWhitelist:
|
|
wallet.WhitelistedAddrs[tx.To] = true
|
|
|
|
case TxTypeBlacklist:
|
|
wallet.BlacklistedAddrs[tx.To] = true
|
|
|
|
default:
|
|
err = errors.New("unsupported transaction type")
|
|
}
|
|
|
|
if err != nil {
|
|
tx.Status = TxStatusRejected
|
|
tx.ExecutionResult = err.Error()
|
|
wallet.RejectedTxs[tx.ID] = tx
|
|
} else {
|
|
tx.Status = TxStatusExecuted
|
|
tx.ExecutedAt = time.Now()
|
|
tx.ExecutionResult = "Success"
|
|
wallet.ExecutedTxs[tx.ID] = tx
|
|
|
|
// Update metrics
|
|
mm.TotalTransactions++
|
|
if tx.Type == TxTypeTransfer && tx.Value != nil {
|
|
if mm.TotalVolume[tx.Token] == nil {
|
|
mm.TotalVolume[tx.Token] = big.NewInt(0)
|
|
}
|
|
mm.TotalVolume[tx.Token].Add(mm.TotalVolume[tx.Token], tx.Value)
|
|
}
|
|
}
|
|
|
|
// Remove from pending
|
|
delete(wallet.PendingTxs, tx.ID)
|
|
wallet.LastActivity = time.Now()
|
|
|
|
return err
|
|
}
|
|
|
|
// executeTransfer executes a transfer transaction
|
|
func (mm *MultisigManager) executeTransfer(wallet *MultisigWallet, tx *MultisigTransaction) error {
|
|
// Check balance
|
|
balance := wallet.Balance[tx.Token]
|
|
if balance == nil || balance.Cmp(tx.Value) < 0 {
|
|
return errors.New("insufficient balance")
|
|
}
|
|
|
|
// Update spending for daily limits
|
|
for _, owner := range wallet.Owners {
|
|
if owner.DailyLimit != nil && owner.DailyLimit.Sign() > 0 {
|
|
// Reset daily spent if needed
|
|
if time.Since(owner.LastResetDate) > 24*time.Hour {
|
|
owner.SpentToday = big.NewInt(0)
|
|
owner.LastResetDate = time.Now()
|
|
}
|
|
|
|
// Check individual limit
|
|
newSpent := new(big.Int).Add(owner.SpentToday, tx.Value)
|
|
if newSpent.Cmp(owner.DailyLimit) > 0 {
|
|
// This owner has exceeded their limit
|
|
// In a real implementation, we might handle this differently
|
|
}
|
|
|
|
owner.SpentToday = newSpent
|
|
}
|
|
}
|
|
|
|
// Deduct balance
|
|
wallet.Balance[tx.Token].Sub(wallet.Balance[tx.Token], tx.Value)
|
|
|
|
// In a real implementation, this would interact with the blockchain
|
|
tx.TxHash = fmt.Sprintf("0x%x", time.Now().Unix())
|
|
|
|
return nil
|
|
}
|
|
|
|
// executeAddOwner adds a new owner to the wallet
|
|
func (mm *MultisigManager) executeAddOwner(wallet *MultisigWallet, tx *MultisigTransaction) error {
|
|
if wallet.TotalOwners >= mm.MaxOwnersPerWallet {
|
|
return errors.New("maximum owners reached")
|
|
}
|
|
|
|
if _, exists := wallet.Owners[tx.To]; exists {
|
|
return errors.New("already an owner")
|
|
}
|
|
|
|
owner := &WalletOwner{
|
|
Address: tx.To,
|
|
Weight: 1,
|
|
AddedAt: time.Now(),
|
|
DailyLimit: big.NewInt(0),
|
|
SpentToday: big.NewInt(0),
|
|
LastResetDate: time.Now(),
|
|
}
|
|
|
|
wallet.Owners[tx.To] = owner
|
|
wallet.TotalOwners++
|
|
|
|
// Update user wallets
|
|
mm.UserWallets[tx.To] = append(mm.UserWallets[tx.To], wallet)
|
|
|
|
return nil
|
|
}
|
|
|
|
// executeRemoveOwner removes an owner from the wallet
|
|
func (mm *MultisigManager) executeRemoveOwner(wallet *MultisigWallet, tx *MultisigTransaction) error {
|
|
if wallet.TotalOwners <= wallet.RequiredSigs {
|
|
return errors.New("cannot remove owner: would break threshold")
|
|
}
|
|
|
|
if _, exists := wallet.Owners[tx.To]; !exists {
|
|
return errors.New("not an owner")
|
|
}
|
|
|
|
delete(wallet.Owners, tx.To)
|
|
wallet.TotalOwners--
|
|
|
|
// Update user wallets
|
|
userWallets := mm.UserWallets[tx.To]
|
|
for i, w := range userWallets {
|
|
if w.ID == wallet.ID {
|
|
mm.UserWallets[tx.To] = append(userWallets[:i], userWallets[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// executeChangeThreshold changes the signature threshold
|
|
func (mm *MultisigManager) executeChangeThreshold(wallet *MultisigWallet, tx *MultisigTransaction) error {
|
|
newThreshold := int(tx.Value.Int64())
|
|
|
|
if newThreshold < mm.MinThreshold || newThreshold > wallet.TotalOwners {
|
|
return fmt.Errorf("invalid threshold: must be between %d and %d",
|
|
mm.MinThreshold, wallet.TotalOwners)
|
|
}
|
|
|
|
wallet.RequiredSigs = newThreshold
|
|
|
|
// Update threshold levels proportionally
|
|
for txType := range wallet.ThresholdLevels {
|
|
if txType == TxTypeTransfer {
|
|
wallet.ThresholdLevels[txType] = newThreshold
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetSpendingLimit sets a spending limit for the wallet
|
|
func (mm *MultisigManager) SetSpendingLimit(
|
|
walletID string,
|
|
token string,
|
|
period LimitPeriod,
|
|
amount *big.Int,
|
|
) error {
|
|
mm.mu.Lock()
|
|
defer mm.mu.Unlock()
|
|
|
|
wallet, exists := mm.Wallets[walletID]
|
|
if !exists {
|
|
return errors.New("wallet not found")
|
|
}
|
|
|
|
wallet.mu.Lock()
|
|
defer wallet.mu.Unlock()
|
|
|
|
limitKey := fmt.Sprintf("%s_%d", token, period)
|
|
wallet.SpendingLimits[limitKey] = &SpendingLimit{
|
|
Period: period,
|
|
Amount: amount,
|
|
Spent: big.NewInt(0),
|
|
LastReset: time.Now(),
|
|
Token: token,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
func (mm *MultisigManager) generateWalletID() string {
|
|
data := fmt.Sprintf("wallet_%d_%d", time.Now().UnixNano(), mm.TotalWallets)
|
|
hash := sha256.Sum256([]byte(data))
|
|
return hex.EncodeToString(hash[:])[:16]
|
|
}
|
|
|
|
func (mm *MultisigManager) generateTxID() string {
|
|
data := fmt.Sprintf("tx_%d_%d", time.Now().UnixNano(), mm.TotalTransactions)
|
|
hash := sha256.Sum256([]byte(data))
|
|
return hex.EncodeToString(hash[:])[:16]
|
|
}
|
|
|
|
func (mm *MultisigManager) signTransaction(tx *MultisigTransaction, signer string) *TxSignature {
|
|
// In production, this would use proper cryptographic signing
|
|
data := fmt.Sprintf("%s_%s_%s_%s", tx.ID, tx.To, tx.Value, signer)
|
|
hash := sha256.Sum256([]byte(data))
|
|
|
|
return &TxSignature{
|
|
Signer: signer,
|
|
Signature: hash[:],
|
|
SignedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (mm *MultisigManager) checkSpendingLimits(
|
|
wallet *MultisigWallet,
|
|
token string,
|
|
amount *big.Int,
|
|
) error {
|
|
now := time.Now()
|
|
|
|
// Check daily limit
|
|
dailyKey := fmt.Sprintf("%s_%d", token, LimitPeriodDaily)
|
|
if limit, exists := wallet.SpendingLimits[dailyKey]; exists {
|
|
// Reset if needed
|
|
if now.Sub(limit.LastReset) > 24*time.Hour {
|
|
limit.Spent = big.NewInt(0)
|
|
limit.LastReset = now
|
|
}
|
|
|
|
newSpent := new(big.Int).Add(limit.Spent, amount)
|
|
if newSpent.Cmp(limit.Amount) > 0 {
|
|
return fmt.Errorf("exceeds daily limit: %s", limit.Amount)
|
|
}
|
|
}
|
|
|
|
// Check weekly limit
|
|
weeklyKey := fmt.Sprintf("%s_%d", token, LimitPeriodWeekly)
|
|
if limit, exists := wallet.SpendingLimits[weeklyKey]; exists {
|
|
if now.Sub(limit.LastReset) > 7*24*time.Hour {
|
|
limit.Spent = big.NewInt(0)
|
|
limit.LastReset = now
|
|
}
|
|
|
|
newSpent := new(big.Int).Add(limit.Spent, amount)
|
|
if newSpent.Cmp(limit.Amount) > 0 {
|
|
return fmt.Errorf("exceeds weekly limit: %s", limit.Amount)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
// GetWalletInfo returns wallet information
|
|
func (mm *MultisigManager) GetWalletInfo(walletID string) (map[string]interface{}, error) {
|
|
mm.mu.RLock()
|
|
defer mm.mu.RUnlock()
|
|
|
|
wallet, exists := mm.Wallets[walletID]
|
|
if !exists {
|
|
return nil, errors.New("wallet not found")
|
|
}
|
|
|
|
wallet.mu.RLock()
|
|
defer wallet.mu.RUnlock()
|
|
|
|
owners := make([]string, 0, len(wallet.Owners))
|
|
for addr := range wallet.Owners {
|
|
owners = append(owners, addr)
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"id": wallet.ID,
|
|
"name": wallet.Name,
|
|
"owners": owners,
|
|
"required_sigs": wallet.RequiredSigs,
|
|
"total_owners": wallet.TotalOwners,
|
|
"pending_txs": len(wallet.PendingTxs),
|
|
"executed_txs": len(wallet.ExecutedTxs),
|
|
"balance": wallet.Balance,
|
|
"frozen": wallet.Frozen,
|
|
"created_at": wallet.CreatedAt,
|
|
"last_activity": wallet.LastActivity,
|
|
}, nil
|
|
}
|
|
|
|
// GetUserWallets returns all wallets a user is part of
|
|
func (mm *MultisigManager) GetUserWallets(user string) []*MultisigWallet {
|
|
mm.mu.RLock()
|
|
defer mm.mu.RUnlock()
|
|
|
|
return mm.UserWallets[user]
|
|
}
|