Wallet Management
Wallet Types
| Wallet Type | Purpose | Security Level |
|---|
| Developer Wallet | Testing, deploying to testnet | Low (hot wallet OK) |
| Deployer Wallet | Mainnet contract deployment | Medium (hardware wallet) |
| Treasury Wallet | Holding funds, revenue | High (multisig) |
| Minter Wallet | Backend minting operations | Medium (server-side) |
Developer Wallet Setup
For local development and testnet:
- Install MetaMask
- Add Immutable Chain Testnet:
- Network Name:
Immutable Testnet
- RPC:
https://rpc.testnet.immutable.com
- Chain ID:
13473
- Symbol:
tIMX
- Get test tokens from the Faucet
Hardhat Configuration
// hardhat.config.ts
import { HardhatUserConfig } from 'hardhat/config';
const config: HardhatUserConfig = {
networks: {
'immutable-testnet': {
url: 'https://rpc.testnet.immutable.com',
accounts: [process.env.PRIVATE_KEY!],
chainId: 13473,
},
'immutable-mainnet': {
url: 'https://rpc.immutable.com',
accounts: [process.env.PRIVATE_KEY!],
chainId: 13371,
},
},
};
export default config;
Never commit private keys to version control. Use environment variables or a secrets manager.
Production Treasury: Multisig
For production funds, use a multisig wallet like Safe.
Why Multisig?
- No single point of failure: Multiple signatures required
- Team accountability: Track who approved transactions
- Recovery: Lost keys don’t mean lost funds
Setting Up Safe
- Go to Safe
- Connect to Immutable Chain
- Create a new Safe with:
- Multiple owner addresses (team members)
- Threshold (e.g., 2 of 3 signatures required)
- Fund the Safe with IMX for gas
Transaction Flow
Team Member A proposes transaction
↓
Team Member B reviews and signs
↓
Threshold reached → Transaction executes
Backend Minting Wallet
Your backend needs a wallet for minting operations.
Key Management Options
| Option | Pros | Cons |
|---|
| Environment Variable | Simple | Less secure |
| AWS KMS | Secure, auditable | More complex |
| HashiCorp Vault | Enterprise-grade | Infrastructure overhead |
Using AWS KMS with viem
import { KMSClient, SignCommand } from '@aws-sdk/client-kms';
import { createWalletClient, http, type Account } from 'viem';
import { immutableZkEvm } from 'viem/chains';
// Create a KMS-backed account for viem
async function createKMSAccount(keyId: string): Promise<Account> {
const kms = new KMSClient({ region: 'us-east-1' });
return {
address: '0x...', // Derive from KMS public key
type: 'local',
signMessage: async ({ message }) => {
const command = new SignCommand({
KeyId: keyId,
Message: Buffer.from(message as string),
SigningAlgorithm: 'ECDSA_SHA_256',
MessageType: 'DIGEST',
});
const response = await kms.send(command);
// Process and return signature
return '0x...' as `0x${string}`;
},
signTransaction: async (tx) => {
// Sign transaction with KMS
return '0x...' as `0x${string}`;
},
signTypedData: async (data) => {
// Sign typed data with KMS
return '0x...' as `0x${string}`;
},
};
}
// Use with viem
const account = await createKMSAccount(process.env.KMS_KEY_ID!);
const walletClient = createWalletClient({
account,
chain: immutableZkEvm,
transport: http(),
});
Rate Limiting & Monitoring
Protect your minting wallet:
import rateLimit from 'express-rate-limit';
const mintLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: 'Too many mint requests',
});
app.post('/api/mint', mintLimiter, async (req, res) => {
// Log all mint operations
logger.info('Mint request', {
recipient: req.body.address,
tokenId: req.body.tokenId,
timestamp: new Date(),
});
// Process mint...
});
Security Checklist
Development
Production
Gas Management
Estimating Costs
import { createPublicClient, http, formatEther } from 'viem';
import { immutableZkEvm } from 'viem/chains';
const publicClient = createPublicClient({
chain: immutableZkEvm,
transport: http(),
});
async function estimateGas(to: `0x${string}`, data: `0x${string}`) {
const gasEstimate = await publicClient.estimateGas({ to, data });
const gasPrice = await publicClient.getGasPrice();
const maxCost = gasEstimate * gasPrice;
console.log(`Max cost: ${formatEther(maxCost)} IMX`);
}
Maintaining Balance
Set up alerts when wallet balance drops:
import { createPublicClient, http, formatEther, parseEther } from 'viem';
import { immutableZkEvm } from 'viem/chains';
const publicClient = createPublicClient({
chain: immutableZkEvm,
transport: http(),
});
async function checkBalance(address: `0x${string}`, threshold: bigint) {
const balance = await publicClient.getBalance({ address });
if (balance < threshold) {
await sendAlert(`Wallet ${address} balance low: ${formatEther(balance)} IMX`);
}
}
// Run periodically
setInterval(() => checkBalance(MINTER_WALLET, parseEther('10')), 60000);
Funds Recovery
From Passport Wallet
Players control their Passport wallets. Recovery is through their authentication provider (Google, Apple, etc.).
From Studio Wallets
- Multisig: Recover with threshold of remaining signers
- Single-sig: Depends on backup strategy
- KMS: AWS manages key durability
Always test your recovery procedures before you need them.
Next Steps