Production Deployment Guide
Complete guide for deploying Hoosat-mo to Internet Computer mainnet.
Overview
This guide covers security considerations, deployment steps, monitoring, and best practices for production Hoosat applications on IC.
Security Checklist
Critical Requirements
- Never use
dfx_test_keyin production - Use production threshold ECDSA keys - Security audit completed
- Key management procedures documented
- Rate limiting implemented
- Error handling comprehensive
- Input validation enabled
- Monitoring and alerting configured
- Backup and recovery plan created
Key Management
// ❌ DON'T: Use test key in production
let wallet = Wallet.createMainnetWallet("dfx_test_key", ?"hoosat");
// ✅ DO: Use production key
let wallet = Wallet.createMainnetWallet("production_key_1", ?"hoosat");
Production keys require IC mainnet permissions. Contact DFINITY for access.
Pre-Production Setup
1. Environment Configuration
Create production configuration:
module {
public let PRODUCTION_CONFIG = {
keyName = "production_hoosat_key";
apiHost = "api.network.hoosat.fi";
network = "mainnet";
maxFee = 1_000_000; // 0.01 HTN max fee
defaultFeeRate = 1000; // sompi/byte
dustThreshold = 1000; // minimum output
};
};
2. Canister Configuration
Update dfx.json for production:
{
"canisters": {
"hoosat_wallet": {
"main": "src/hoosat_wallet/main.mo",
"type": "motoko",
"declarations": {
"output": "src/declarations/hoosat_wallet",
"bindings": ["js", "ts", "did", "mo"]
}
}
},
"defaults": {
"build": {
"packtool": "mops sources"
}
},
"networks": {
"ic": {
"providers": ["https://icp-api.io"],
"type": "persistent"
}
}
}
Deployment Steps
1. Prepare Canister
# Install dependencies
mops install
# Build canister
dfx build --network ic
# Check canister size
dfx canister --network ic info hoosat_wallet
2. Deploy to IC Mainnet
# Create production identity
dfx identity new production
dfx identity use production
# Deploy with sufficient cycles (10T for production)
dfx deploy --network ic --with-cycles 10000000000000
3. Verify Deployment
# Test address generation
dfx canister --network ic call hoosat_wallet generateAddress '(null, null)'
# Check canister status
dfx canister --network ic status hoosat_wallet
Security Implementation
Input Validation
Always validate all inputs:
import Validation "mo:hoosat-mo/validation";
import Errors "mo:hoosat-mo/errors";
public func safeSend(to: Text, amount: Nat64) : async Result.Result<Text, Errors.HoosatError> {
// Validate address
let addrValidation = Validation.validateAddress(to);
switch (addrValidation) {
case (#err(e)) { return #err(e); };
case (#ok(_)) {};
};
// Validate amount
let amountValidation = Validation.validateAmount(amount);
switch (amountValidation) {
case (#err(e)) { return #err(e); };
case (#ok(_)) {};
};
// Proceed with transaction
// ...
};
Rate Limiting
Implement per-caller rate limiting:
import HashMap "mo:base/HashMap";
import Principal "mo:base/Principal";
import Time "mo:base/Time";
private var rateLimits = HashMap.HashMap<Principal, [Int]>(
10,
Principal.equal,
Principal.hash
);
private func checkRateLimit(caller: Principal) : Bool {
let now = Time.now();
let window = 60_000_000_000; // 60 seconds in nanoseconds
let maxRequests = 10;
let timestamps = switch (rateLimits.get(caller)) {
case (?ts) { ts };
case (null) { [] };
};
// Filter recent timestamps
let recent = Array.filter(timestamps, func(t: Int) : Bool {
now - t < window
});
if (recent.size() >= maxRequests) {
return false; // Rate limit exceeded
};
// Add current timestamp
rateLimits.put(caller, Array.append(recent, [now]));
return true;
};
public shared(msg) func rateLimitedSend(
to: Text,
amount: Nat64
) : async Result.Result<Text, Errors.HoosatError> {
if (not checkRateLimit(msg.caller)) {
return #err(#ValidationError("Rate limit exceeded"));
};
// Proceed with transaction
// ...
};
Error Handling
Comprehensive error handling:
public func robustSend(
from: Text,
to: Text,
amount: Nat64
) : async Result.Result<TransactionResult, Errors.HoosatError> {
// Validate inputs
let _ = switch (Validation.validateAddress(to)) {
case (#err(e)) { return #err(e); };
case (#ok(_)) {};
};
// Send with error handling
let result = await wallet.sendTransaction(from, to, amount, null, null);
switch (result) {
case (#err(#NetworkError(msg))) {
// Log network error for monitoring
Debug.print("Network error: " # msg);
// Retry logic here if needed
return #err(#NetworkError(msg));
};
case (#err(#InsufficientFunds(info))) {
Debug.print("Insufficient funds: need " # debug_show(info.required));
return #err(#InsufficientFunds(info));
};
case (#err(e)) {
Debug.print("Error: " # Errors.errorToText(e));
return #err(e);
};
case (#ok(tx)) {
return #ok(tx);
};
};
};
Monitoring
Cycle Management
Monitor canister cycles:
import Cycles "mo:base/ExperimentalCycles";
public query func getCyclesBalance() : async Nat {
return Cycles.balance();
};
public func checkCyclesAndAlert() : async () {
let balance = Cycles.balance();
let threshold = 1_000_000_000_000; // 1T cycles
if (balance < threshold) {
// Alert mechanism (log, notify, etc.)
Debug.print("⚠️ Low cycles: " # debug_show(balance));
};
};
Transaction Logging
Log all transactions:
import Array "mo:base/Array";
private stable var txLog : [(Int, Text, Text, Nat64, Bool)] = [];
private func logTransaction(
from: Text,
to: Text,
amount: Nat64,
success: Bool
) {
let timestamp = Time.now();
txLog := Array.append(
txLog,
[(timestamp, from, to, amount, success)]
);
// Trim old logs if needed
if (txLog.size() > 10000) {
txLog := Array.subArray(txLog, txLog.size() - 10000, 10000);
};
};
public query func getTransactionLog() : async [(Int, Text, Text, Nat64, Bool)] {
return txLog;
};
Performance Optimization
Cache Frequently Accessed Data
private var addressCache : [(Text, (Text, Int))] = [];
public func getCachedAddress(
derivation: Text,
ttl: Int
) : async Text {
let now = Time.now();
// Check cache
switch (Array.find(addressCache, func((d, _)) : Bool { d == derivation })) {
case (?(_, (addr, timestamp))) {
if (now - timestamp < ttl) {
return addr;
};
};
case (null) {};
};
// Generate new
let result = await wallet.generateAddress(null, ?derivation);
switch (result) {
case (#ok(info)) {
addressCache := Array.append(
addressCache,
[(derivation, (info.address, now))]
);
return info.address;
};
case (#err(_)) { return ""; };
};
};
Emergency Procedures
Circuit Breaker
private stable var emergencyMode = false;
private stable var emergencyReason = "";
public shared(msg) func enableEmergencyMode(reason: Text) : async () {
// Only admin can enable
assert(msg.caller == adminPrincipal);
emergencyMode := true;
emergencyReason := reason;
Debug.print("🚨 Emergency mode enabled: " # reason);
};
public shared(msg) func disableEmergencyMode() : async () {
assert(msg.caller == adminPrincipal);
emergencyMode := false;
emergencyReason := "";
Debug.print("✅ Emergency mode disabled");
};
private func checkEmergencyMode() : Result.Result<(), Errors.HoosatError> {
if (emergencyMode) {
return #err(#InternalError("System in emergency mode: " # emergencyReason));
};
#ok(())
};
Testing Before Production
Test on Testnet
// Use testnet wallet for testing
let testWallet = Wallet.createTestnetWallet("dfx_test_key", ?"hoosat");
// Test all operations
let addrResult = await testWallet.generateAddress(null, null);
let balResult = await testWallet.getBalance("hoosat:qp...");
let txResult = await testWallet.sendTransaction(from, to, 100000000, null, null);
Load Testing
Test with concurrent requests:
# Simulate load
for i in {1..100}; do
dfx canister --network ic call hoosat_wallet getBalance '("hoosat:qp...")' &
done
wait
Deployment Checklist
- Security audit completed
- All tests passing
- Production keys configured
- Rate limiting implemented
- Error handling comprehensive
- Monitoring setup
- Emergency procedures documented
- Cycle management automated
- Backup strategy in place
- Documentation updated
Resources
Support
For production support:
- Discord: discord.gg/mFBfNpNA
- Telegram: t.me/HoosatNetwork
- GitHub Issues: Hoosat-Oy/Hoosat-mo