Skip to content

BHIIKTOR/cosmwasm-deployor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CosmWasm Deployment Framework

A comprehensive TypeScript framework for deploying CosmWasm smart contracts with support for multiple deployment strategies, dependency management, and storage tracking.

Note

it works, but it can use a nice overhaul here and there, it does a lot of what is needed and a could be easily extended to automate testing after deploy (kind of on my TODO)

🎯 Overview

This framework provides a unified, efficient system for deploying CosmWasm contracts with features like:

  • Multiple Deployment Strategies: Simple, waterfall, strategy-based, and parallel deployments
  • Dependency Management: Automatic resolution and injection of contract dependencies
  • Storage Tracking: Individual contract storage with deployment phase tracking
  • Wallet Management: Unified wallet and client creation with validation
  • WASM Management: Centralized WASM file loading with validation and caching
  • Type Safety: Comprehensive TypeScript types for all operations
  • Backward Compatibility: Existing deployment scripts continue to work unchanged

πŸ—οΈ Architecture

Core System

The framework has been refactored around a unified core system that eliminates code duplication:

src/core/
β”œβ”€β”€ types.ts           # Unified type definitions
β”œβ”€β”€ wasm-loader.ts     # WASM file loading and validation
β”œβ”€β”€ wallet-manager.ts  # Wallet and client management
β”œβ”€β”€ deployment-engine.ts # Core deployment operations
β”œβ”€β”€ storage-manager.ts # Unified storage interface
└── index.ts          # Central export point

Legacy Systems (Refactored)

All existing deployment systems now use the core modules internally:

  • deployment-manager.ts - Basic deployment operations
  • waterfall-deploy.ts - Dependency-aware deployments
  • deploy-strategy.ts - Strategy-based deployments
  • deploy.ts - Simple deployment scripts

πŸš€ Quick Start

1. Environment Setup

Create your environment file (.env.dev, .env.prod, etc.):

# Network Configuration
CHAIN_ID=thorchain-mainnet-v1
RPC_ENDPOINT=https://rpc.thorchain.info
API_ENDPOINT=https://thornode.ninerealms.com
CHAIN_EXPLORER=https://viewblock.io/thorchain

# Wallet Configuration
MNEMONIC="your twelve word mnemonic phrase here"
DEFAULT_ADDRESS=thor1...

# Paths
ARTIFACTS_PATH=./artifacts
CONFIGS_PATH=./configs
DEPLOYMENTS_PATH=./deployed
CONTRACTS_PATH=./contracts

# Gas Settings
GAS_PRICE=0.025uthor
GAS_LIMIT=2000000

2. Contract Configuration

Contract configurations support all deployment strategies with a unified format. Here's a comprehensive example showing all available options:

// configs/example_contract.json
{
  // Basic Required Fields
  "name": "MyContract",                           // Contract identifier name
  "wasmFile": "my_contract.wasm",                // WASM filename (required)
  "initMsg": {                                   // Contract instantiation message (required)
    "admin": "thor1admin123...",                 // Contract admin address
    "owner": "thor1owner456...",                 // Contract owner
    "config": {
      "fee": "0.003",                           // Fee configuration
      "max_slippage": "0.05",                   // Maximum slippage allowed
      "registry_address": "{rujira_registry.wasm}",  // Template placeholder for dependency
      "vault_address": "{rujira_vault.wasm}",        // Another dependency placeholder
      "admin": "{DEPLOYER_ADDRESS}"                  // Deployer's wallet address
    }
  },

  // Optional Basic Fields
  "label": "My Contract v2.0",                   // Human-readable contract label
  "admin": "thor1admin123...",                   // Admin override (null = deployer becomes admin)
  "wasmPath": "../custom/path/my_contract.wasm", // Custom path override (default: artifacts/)
  "gasLimit": 3000000,                          // Custom gas limit override
  "uploadOnly": false,                          // Set to true for library/template contracts

  // Dependency Configuration (for complex deployments)
  "dependencies": [
    // Simple string format (basic deployments)
    "registry_contract.wasm",
    "utility_contract.wasm",

    // Advanced object format (waterfall deployments)
    {
      "contractName": "rujira_registry",         // Dependency contract name
      "paramPath": "config.registry_address",    // JSON path for address injection
      "isOptional": false                        // Whether dependency is optional
    },
    {
      "contractName": "rujira_vault",
      "paramPath": "config.vault_address",
      "isOptional": true
    }
  ],

  // Waterfall-Specific Fields
  "priority": 5                                 // Deployment order priority (lower = earlier)
}

This single configuration format works for all deployment strategies:

  • Simple deployments: Use basic fields (name, wasmFile, initMsg, label)
  • Library contracts: Add "uploadOnly": true to skip instantiation
  • Custom paths: Use wasmPath for non-standard WASM locations
  • Dependencies: Use string array for simple deps, object array for advanced resolution
  • Waterfall deployments: Add priority and advanced dependency objects

Configuration Options Reference

Field Type Required Description
name string βœ… Contract identifier name
wasmFile string βœ… WASM filename (e.g., "contract.wasm")
wasmPath string ❌ Custom path to WASM file (overrides default artifacts path)
label string ❌ Human-readable contract label
admin string | null ❌ Admin address (null = deployer address)
initMsg object βœ… Contract instantiation message
dependencies string[] | ContractDependency[] ❌ Contract dependencies
gasLimit number ❌ Custom gas limit override
uploadOnly boolean ❌ Upload contract code only, skip instantiation
priority number ❌ Deployment priority (waterfall only)

Advanced Features

Dependencies: Use string arrays for simple dependencies or object arrays for advanced resolution with specific JSON paths and optional flags.

Template Placeholders: In waterfall deployments, use placeholders for automatic resolution:

  • {contract_name.wasm} - Resolves to deployed contract address (for dependencies)
  • {CODE_ID.contract_name.wasm} - Resolves to contract code ID
  • {CHECKSUM.contract_name.wasm} - Resolves to WASM file checksum
  • {DEPLOYER_ADDRESS} - Resolves to the deployer's wallet address

Example usage:

{
  "initMsg": {
    "admin": "{DEPLOYER_ADDRESS}",                    // Admin set to deployer
    "registry_address": "{rujira_registry.wasm}",     // Dependency address
    "code_id": "{CODE_ID.rujira_vault.wasm}",        // Dependency code ID
    "checksum": "{CHECKSUM.rujira_vault.wasm}"        // Dependency checksum
  }
}

Best Practices: Use descriptive names, set appropriate admins (null = deployer), configure custom gas limits for complex contracts, and organize paths consistently.

3. Basic Deployment

import { createWallet, DeploymentEngine, getStorageManager } from "./src/core/index.js";
import { DEPLOYMENT_CONFIG, mnemonic } from "./src/config.js";

async function deployContract() {
  // Create wallet and client
  const walletInfo = await createWallet(
    DEPLOYMENT_CONFIG,
    mnemonic
  );

  // Create deployment engine
  const engine = new DeploymentEngine(walletInfo);

  // Storage configuration
  const storageConfig = {
    network: "thorchain",
    baseDir: "./deployed"
  };

  // Deploy contract
  const result = await engine.deployContract({
    name: "MyContract",
    wasmFile: "my_contract.wasm",
    initMsg: { admin: walletInfo.address },
    label: "My Contract v1.0"
  }, {
    storageConfig,
    skipExisting: true
  });

  console.log("Deployment result:", result);
}

πŸ“‹ Deployment Strategies

1. Simple Deployment

Deploy individual contracts:

npm run deploy:simple -- my_contract.json

2. Waterfall Deployment

Deploy contracts with dependency resolution:

npm run deploy:waterfall -- --config=waterfall-config.json

Example waterfall config:

{
  "contracts": [
    {
      "contractName": "Registry",
      "wasmFileName": "registry.wasm",
      "priority": 1,
      "dependencies": [],
      "initConfigPath": "registry_init.json"
    },
    {
      "contractName": "Vault",
      "wasmFileName": "vault.wasm",
      "priority": 2,
      "dependencies": [
        {
          "contractName": "Registry",
          "paramPath": "registry_address",
          "isOptional": false
        }
      ],
      "initConfigPath": "vault_init.json"
    }
  ],
  "artifactsDir": "./artifacts",
  "configsDir": "./configs",
  "storageConfig": {
    "network": "thorchain",
    "baseDir": "./deployed"
  }
}

3. Strategy-Based Deployment

Deploy using predefined strategies:

npm run deploy:strategy -- --strategy=minimal
npm run deploy:strategy -- --strategy=full-ecosystem

4. Batch Deployment

Deploy multiple contracts in sequence:

const configs = [
  { name: "Contract1", wasmFile: "contract1.wasm", initMsg: {...} },
  { name: "Contract2", wasmFile: "contract2.wasm", initMsg: {...} },
];

const result = await engine.deployContracts(configs, {
  storageConfig,
  continueOnError: true
});

πŸ”§ Core API Reference

DeploymentEngine

Main deployment orchestration class:

class CoreDeploymentEngine {
  constructor(walletInfo: WalletInfo, gasPrice?: string, gasLimit?: number)

  // Deploy single contract (upload + instantiate)
  async deployContract(config: ContractConfig, options: DeploymentOptions): Promise<ContractDeploymentResult>

  // Deploy multiple contracts
  async deployContracts(configs: ContractConfig[], options: DeploymentOptions): Promise<BatchDeploymentResult>

  // Upload contract code only
  async uploadContract(config: UploadConfig): Promise<UploadOperationResult>

  // Instantiate uploaded contract
  async instantiateContract(config: InstantiateConfig): Promise<InstantiateOperationResult>

  // Validate deployment environment
  async validateDeployment(configs: ContractConfig[], options: DeploymentOptions): Promise<ValidationResult>
}

WalletManager

Wallet and client management:

// Create wallet and client
async function createWalletAndClient(config: NetworkConfig, mnemonic: string, options?: WalletCreationOptions): Promise<WalletInfo>

// Validate wallet connection
async function validateWalletConnection(walletInfo: WalletInfo, config: NetworkConfig): Promise<WalletValidationResult>

// Get account information
async function getAccountInfo(walletInfo: WalletInfo): Promise<AccountInfo>

StorageManager

Contract storage management:

class UnifiedStorageManager {
  // Load contract data
  async loadContract(contractName: string): Promise<ContractStorage | null>

  // Save contract data
  async saveContract(contract: ContractStorage): Promise<void>

  // List all contracts
  async listAllContracts(): Promise<ContractSummary[]>

  // Get contracts by deployment phase
  async getContractsByPhase(phase: DeploymentPhase): Promise<ContractStorage[]>

  // Clean storage
  async cleanContracts(options: CleanOptions): Promise<void>
}

WASM Loader

WASM file management:

// Load WASM file
async function loadWasmFile(wasmFile: string, options?: WasmLoadOptions): Promise<WasmLoadResult>

// Load multiple WASM files
async function loadMultipleWasmFiles(wasmFiles: string[], options?: WasmLoadOptions): Promise<Map<string, WasmLoadResult>>

// Get WASM file info without loading
async function getWasmFileInfo(wasmFile: string, customPath?: string): Promise<WasmFileInfo>

πŸŽ›οΈ Configuration Options

Deployment Options

interface DeploymentOptions {
  storageConfig: StorageConfig;       // Storage configuration
  skipExisting?: boolean;             // Skip already deployed contracts
  forceRedeploy?: boolean;            // Force redeploy existing contracts
  uploadOnly?: boolean;               // Only upload, don't instantiate
  instantiateOnly?: boolean;          // Only instantiate uploaded contracts
  dryRun?: boolean;                   // Simulate deployment without executing
  continueOnError?: boolean;          // Continue on individual contract failures
  gasPrice?: string;                  // Custom gas price
  gasLimit?: number;                  // Custom gas limit
}

Storage Configuration

interface StorageConfig {
  network: string;                    // Network name (e.g., "thorchain")
  baseDir?: string;                   // Storage directory (default: "./deployed")
}

Contract Configuration

interface ContractConfig {
  name: string;                       // Contract name
  wasmFile: string;                   // WASM file name
  wasmPath?: string;                  // Custom WASM file path
  label?: string;                     // Contract label
  admin?: string;                     // Admin address
  initMsg: any;                       // Instantiation message
  gasLimit?: number;                  // Custom gas limit
}

πŸ“Š Deployment Phases

Contracts progress through deployment phases:

  1. UPLOADED: Contract code uploaded to blockchain, has codeId
  2. INSTANTIATED: Contract instantiated, has address

Each phase is tracked in individual storage files:

// deployed/my_contract.json
{
  "contractName": "MyContract",
  "wasmFile": "my_contract.wasm",
  "network": "thorchain",
  "phase": "INSTANTIATED",
  "codeId": 123,
  "address": "thor1abc...",
  "uploadTxHash": "0x123...",
  "instantiateTxHash": "0x456...",
  "uploadedAt": "2024-01-01T00:00:00.000Z",
  "instantiatedAt": "2024-01-01T00:01:00.000Z",
  "metadata": {
    "label": "My Contract v1.0",
    "admin": "thor1def...",
    "initMsg": { "admin": "thor1def..." }
  }
}

πŸ” Available Scripts

Deployment Scripts

# Simple deployment
npm run deploy:simple -- <config-file>

# Waterfall deployment with dependencies
npm run deploy:waterfall -- --config=<waterfall-config>

# Strategy-based deployment
npm run deploy:strategy -- --strategy=<strategy-name>

# Clean deployments
npm run clean -- --all
npm run clean -- --phase=uploaded
npm run clean -- --contract=<contract-name>

Development Scripts

# Build TypeScript
npm run build

# Generate schemas
npm run generate-schema

# Fund wallet for testing
npm run fund-wallet

# System tests
npm run test-system

Network-Specific Scripts

# Testnet deployments
npm run deploy:testnet:simple -- <config>
npm run deploy:testnet:waterfall -- --config=<config>

# Mainnet deployments
npm run deploy:mainnet:simple -- <config>
npm run deploy:mainnet:waterfall -- --config=<config>

πŸ› οΈ Advanced Usage

Custom Deployment Engine

import { CoreDeploymentEngine, createWallet } from "./src/core/index.js";

class CustomDeploymentEngine extends CoreDeploymentEngine {
  async deployWithRetry(config: ContractConfig, options: DeploymentOptions, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const result = await this.deployContract(config, options);
        if (!result.error) return result;

        console.log(`Attempt ${attempt} failed: ${result.error}`);
        if (attempt < maxRetries) {
          await new Promise(resolve => setTimeout(resolve, 5000));
        }
      } catch (error) {
        if (attempt === maxRetries) throw error;
      }
    }
  }
}

Custom Storage Backend

import { UnifiedStorageManager } from "./src/core/index.js";

class DatabaseStorageManager extends UnifiedStorageManager {
  async saveContract(contract: ContractStorage): Promise<void> {
    // Custom database storage logic
    await database.contracts.save(contract);

    // Also save to file system for backup
    await super.saveContract(contract);
  }
}

Dependency Injection

// Custom dependency resolver
async function customDependencyResolver(initMsg: any, dependencies: string[]): Promise<any> {
  const resolved = { ...initMsg };

  for (const dep of dependencies) {
    const address = await getContractAddress(storageConfig, dep);
    if (address) {
      resolved[`${dep}_address`] = address;
    }
  }

  return resolved;
}

πŸ› Troubleshooting

Common Issues

  1. WASM File Not Found

    # Check artifacts path
    ls -la ./artifacts/
    
    # Verify environment variable
    echo $ARTIFACTS_PATH
  2. Wallet Connection Issues

    // Validate wallet before deployment
    const validation = await validateWalletConnection(walletInfo, networkConfig);
    if (!validation.valid) {
      throw new Error(validation.error);
    }
  3. Storage Permission Issues

    # Check deployed directory permissions
    mkdir -p ./deployed
    chmod 755 ./deployed
  4. Gas Estimation Failures

    // Use manual gas limits
    const result = await engine.deployContract(config, {
      ...options,
      gasLimit: 3000000
    });

Debug Mode

Enable detailed logging:

const result = await engine.deployContract(config, {
  ...options,
  dryRun: true  // Simulate without executing
});

πŸ”’ Security Considerations

  1. Mnemonic Storage: Never commit mnemonics to version control
  2. Environment Files: Use environment-specific files (.env.dev, .env.prod)
  3. Admin Keys: Rotate admin keys regularly
  4. Gas Limits: Set reasonable gas limits to prevent excessive fees
  5. Network Validation: Always validate network configuration

πŸ“ˆ Performance Optimizations

  1. WASM Caching: WASM files are cached automatically during deployment
  2. Concurrent Deployments: Use parallel strategies for independent contracts
  3. Storage Optimization: Individual storage files reduce I/O overhead
  4. Connection Pooling: Wallet connections are reused across deployments

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature-name
  3. Make your changes and add tests
  4. Ensure all tests pass: npm test
  5. Submit a pull request

Code Style

  • Use TypeScript strict mode
  • Follow existing naming conventions
  • Add JSDoc comments for public APIs
  • Include unit tests for new features

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

  • CosmJS team for excellent CosmWasm tooling
  • THORChain team for network infrastructure
  • All contributors who helped improve this framework

πŸ“‹ Migration Guide

If you're upgrading from an older version:

From v1.x to v2.x

The core refactoring maintains backward compatibility, but you can now use the improved core modules:

// Old way (still works)
import { DeploymentManager } from "./src/deployment-manager.js";

// New way (recommended)
import { DeploymentEngine, createWallet, getStorageManager } from "./src/core/index.js";

Storage Migration

Legacy storage files are automatically compatible. No migration required.

Configuration Updates

All existing configuration files continue to work unchanged.

About

CosmWasm contract deployment tool

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published