Skip to main content

Wallet Management

Wallet Types

Wallet TypePurposeSecurity Level
Developer WalletTesting, deploying to testnetLow (hot wallet OK)
Deployer WalletMainnet contract deploymentMedium (hardware wallet)
Treasury WalletHolding funds, revenueHigh (multisig)
Minter WalletBackend minting operationsMedium (server-side)

Developer Wallet Setup

For local development and testnet:

MetaMask Setup

  1. Install MetaMask
  2. Add Immutable Chain Testnet:
    • Network Name: Immutable Testnet
    • RPC: https://rpc.testnet.immutable.com
    • Chain ID: 13473
    • Symbol: tIMX
  3. 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

  1. Go to Safe
  2. Connect to Immutable Chain
  3. Create a new Safe with:
    • Multiple owner addresses (team members)
    • Threshold (e.g., 2 of 3 signatures required)
  4. 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

OptionProsCons
Environment VariableSimpleLess secure
AWS KMSSecure, auditableMore complex
HashiCorp VaultEnterprise-gradeInfrastructure 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

  • Use separate wallet for testnet
  • Never use mainnet private keys in development
  • Clear browser wallet state between projects

Production

  • Treasury in multisig (Safe)
  • Deployer wallet on hardware device
  • Minting wallet keys in secure storage (KMS/Vault)
  • Rate limiting on minting endpoints
  • Monitoring and alerts for unusual activity
  • Regular key rotation schedule
  • Incident response plan documented

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