Skip to main content

Deploy Your First Smart Contract on Nexis

This comprehensive tutorial walks you through deploying a smart contract on Nexis Appchain from start to finish. You’ll learn how to set up your development environment, write a contract, deploy it to the testnet, and verify it on the block explorer.

Prerequisites

Before starting this tutorial, ensure you have:

Node.js v16+

Download from nodejs.org

MetaMask Wallet

Install from metamask.io

Testnet NZT

Get tokens from the faucet

Code Editor

VS Code, Sublime, or your preferred editor
Important: Never share your private keys or commit them to version control. Always use environment variables for sensitive data.

Part 1: Network Setup

Step 1: Add Nexis Network to MetaMask

First, configure your wallet to connect to Nexis Appchain Testnet.
  • Manual Configuration
  • Programmatic Addition
Open MetaMask and navigate to Settings → Networks → Add Network. Enter these parameters:
FieldValue
Network NameNexis Appchain Testnet
RPC URLhttps://rpc.testnet.nexis.network
Chain ID84532
Currency SymbolNZT
Block Explorerhttps://explorer.testnet.nexis.network
Click “Save” to add the network.

Step 2: Verify Network Connection

Test your RPC connection:
# Check if RPC is responding
curl https://rpc.testnet.nexis.network \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'

# Expected output:
# {"jsonrpc":"2.0","id":1,"result":"0x1a2b3c"}
Success! If you see a hex result, your RPC connection is working correctly.

Part 2: Project Setup with Hardhat

Step 1: Initialize a New Project

Create a new directory and initialize your project:
# Create project directory
mkdir nexis-contract-demo
cd nexis-contract-demo

# Initialize npm project
npm init -y

# Install Hardhat and dependencies
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts dotenv

Step 2: Initialize Hardhat

Run the Hardhat initialization wizard:
npx hardhat init
Select these options:
  • What do you want to do? → Create a JavaScript project
  • Hardhat project root → Press Enter (use current directory)
  • Add a .gitignore? → Yes
  • Install dependencies? → Yes
Hardhat will create a basic project structure with sample contracts, tests, and scripts.

Step 3: Configure Hardhat for Nexis

Edit hardhat.config.js to add Nexis network configuration:
hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    // Nexis Testnet
    nexis: {
      url: process.env.NEXIS_RPC_URL || "https://rpc.testnet.nexis.network",
      chainId: 84532,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      gasPrice: 1000000000, // 1 gwei
    },
    // For local testing
    localhost: {
      url: "http://127.0.0.1:8545"
    }
  },
  etherscan: {
    apiKey: {
      nexis: "no-api-key-needed" // Nexis doesn't require API key for verification
    },
    customChains: [
      {
        network: "nexis",
        chainId: 84532,
        urls: {
          apiURL: "https://explorer.testnet.nexis.network/api",
          browserURL: "https://explorer.testnet.nexis.network"
        }
      }
    ]
  },
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts"
  }
};

Step 4: Create Environment Variables

Create a .env file in your project root:
.env
# Your MetaMask private key (keep this secret!)
PRIVATE_KEY=your_private_key_here

# RPC URL (optional, uses default if not set)
NEXIS_RPC_URL=https://rpc.testnet.nexis.network
Security Best Practice: Add .env to your .gitignore file to prevent committing secrets:
echo ".env" >> .gitignore

Step 5: Get Your Private Key

To export your private key from MetaMask:
  1. Open MetaMask
  2. Click the three dots menu
  3. Select Account details
  4. Click Show private key
  5. Enter your MetaMask password
  6. Copy the private key and paste it in your .env file

Part 3: Write Your Smart Contract

Step 1: Create the Contract File

Create a new file contracts/AITaskRequester.sol:
contracts/AITaskRequester.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title AITaskRequester
 * @dev A contract that creates AI inference tasks on Nexis Appchain
 */
contract AITaskRequester is Ownable, ReentrancyGuard {
    // Interface for Nexis Tasks contract
    interface ITasks {
        function createTask(
            string calldata description,
            uint256 reward,
            uint256 deadline,
            bytes calldata requirements
        ) external payable returns (uint256 taskId);

        function getTaskStatus(uint256 taskId)
            external
            view
            returns (uint8 status, address agent, bytes32 resultHash);
    }

    // Events
    event TaskCreated(
        uint256 indexed taskId,
        address indexed requester,
        string description,
        uint256 reward,
        uint256 deadline
    );

    event TaskCompleted(
        uint256 indexed taskId,
        address indexed agent,
        bytes32 resultHash
    );

    // State variables
    ITasks public immutable tasksContract;
    mapping(uint256 => address) public taskRequesters;
    mapping(address => uint256[]) public userTasks;
    uint256 public totalTasksCreated;

    /**
     * @dev Constructor sets the Tasks contract address
     * @param _tasksContract Address of the Nexis Tasks.sol contract
     */
    constructor(address _tasksContract) {
        require(_tasksContract != address(0), "Invalid tasks contract");
        tasksContract = ITasks(_tasksContract);
    }

    /**
     * @dev Request an AI inference by creating a task
     * @param prompt The input prompt for the AI
     * @param reward Amount to reward the agent (in wei)
     * @param duration How long until task deadline (in seconds)
     * @param modelRequirements Encoded requirements (model, parameters, etc.)
     * @return taskId The ID of the created task
     */
    function requestInference(
        string calldata prompt,
        uint256 reward,
        uint256 duration,
        bytes calldata modelRequirements
    ) external payable nonReentrant returns (uint256) {
        require(bytes(prompt).length > 0, "Empty prompt");
        require(bytes(prompt).length <= 5000, "Prompt too long");
        require(reward > 0, "Zero reward");
        require(msg.value >= reward, "Insufficient payment");
        require(duration >= 300 && duration <= 86400, "Invalid duration");

        // Calculate deadline
        uint256 deadline = block.timestamp + duration;

        // Create task on Nexis
        uint256 taskId = tasksContract.createTask{value: reward}(
            prompt,
            reward,
            deadline,
            modelRequirements
        );

        // Track the task
        taskRequesters[taskId] = msg.sender;
        userTasks[msg.sender].push(taskId);
        totalTasksCreated++;

        emit TaskCreated(taskId, msg.sender, prompt, reward, deadline);

        // Refund excess payment
        if (msg.value > reward) {
            payable(msg.sender).transfer(msg.value - reward);
        }

        return taskId;
    }

    /**
     * @dev Get tasks created by a user
     * @param user Address of the user
     * @return Array of task IDs
     */
    function getUserTasks(address user) external view returns (uint256[] memory) {
        return userTasks[user];
    }

    /**
     * @dev Check if a task is complete
     * @param taskId The task ID to check
     * @return completed True if task is completed
     * @return agent Address of agent who completed it
     * @return resultHash Hash of the result
     */
    function checkTaskStatus(uint256 taskId)
        external
        view
        returns (bool completed, address agent, bytes32 resultHash)
    {
        (uint8 status, address taskAgent, bytes32 hash) = tasksContract.getTaskStatus(taskId);

        // Status: 0=Open, 1=Claimed, 2=Completed, 3=Disputed, 4=Resolved
        completed = (status == 2 || status == 4);
        agent = taskAgent;
        resultHash = hash;
    }

    /**
     * @dev Emergency withdraw function (only owner)
     */
    function emergencyWithdraw() external onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    /**
     * @dev Allow contract to receive ETH
     */
    receive() external payable {}
}
This contract demonstrates best practices:
  • Uses OpenZeppelin’s secure contracts
  • Implements proper access control
  • Includes reentrancy protection
  • Has comprehensive error handling
  • Emits events for tracking
  • Includes detailed NatSpec documentation

Step 2: Compile the Contract

Compile your contract to check for errors:
npx hardhat compile
Expected output:
Compiled 1 Solidity file successfully
If you see compilation errors, check:
  • Solidity version matches your config
  • All imports are correctly installed
  • No syntax errors in the code

Part 4: Deploy to Nexis Testnet

Step 1: Create Deployment Script

Create scripts/deploy.js:
scripts/deploy.js
const hre = require("hardhat");

async function main() {
  console.log("Deploying AITaskRequester to Nexis Appchain...");

  // Address of Tasks.sol on Nexis Testnet
  const TASKS_CONTRACT = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb";

  // Get the deployer account
  const [deployer] = await hre.ethers.getSigners();
  const balance = await hre.ethers.provider.getBalance(deployer.address);

  console.log("Deploying with account:", deployer.address);
  console.log("Account balance:", hre.ethers.formatEther(balance), "NZT");

  // Check if we have enough balance
  if (balance < hre.ethers.parseEther("0.01")) {
    throw new Error("Insufficient balance. Get testnet NZT from the faucet.");
  }

  // Deploy the contract
  const AITaskRequester = await hre.ethers.getContractFactory("AITaskRequester");
  const contract = await AITaskRequester.deploy(TASKS_CONTRACT);

  await contract.waitForDeployment();

  const contractAddress = await contract.getAddress();
  console.log("✅ AITaskRequester deployed to:", contractAddress);

  // Wait for a few blocks before verification
  console.log("Waiting for 5 block confirmations...");
  await contract.deploymentTransaction().wait(5);

  console.log("\nDeployment Summary:");
  console.log("===================");
  console.log("Contract Address:", contractAddress);
  console.log("Deployer:", deployer.address);
  console.log("Tasks Contract:", TASKS_CONTRACT);
  console.log("Network:", hre.network.name);
  console.log("Chain ID:", (await hre.ethers.provider.getNetwork()).chainId.toString());

  console.log("\n📝 To verify on explorer, run:");
  console.log(`npx hardhat verify --network nexis ${contractAddress} ${TASKS_CONTRACT}`);

  console.log("\n🔗 View on explorer:");
  console.log(`https://explorer.testnet.nexis.network/address/${contractAddress}`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error("❌ Deployment failed:", error);
    process.exit(1);
  });

Step 2: Deploy the Contract

Run the deployment script:
npx hardhat run scripts/deploy.js --network nexis
Deployment typically takes 5-15 seconds on Nexis due to 2-second block times.
Expected output:
Deploying AITaskRequester to Nexis Appchain...
Deploying with account: 0x1234...5678
Account balance: 10.5 NZT
✅ AITaskRequester deployed to: 0xAbC1...23dE
Waiting for 5 block confirmations...

Deployment Summary:
===================
Contract Address: 0xAbC1...23dE
Deployer: 0x1234...5678
Tasks Contract: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
Network: nexis
Chain ID: 84532
Congratulations! Your contract is now deployed on Nexis Appchain.

Step 3: Verify the Contract

Verify your contract on the block explorer:
npx hardhat verify --network nexis DEPLOYED_ADDRESS "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
Replace DEPLOYED_ADDRESS with your actual contract address. Expected output:
Successfully verified contract AITaskRequester on Nexis Explorer.
https://explorer.testnet.nexis.network/address/0xAbC1...23dE#code

Part 5: Deploy with Foundry (Alternative)

If you prefer Foundry over Hardhat, follow these steps:

Step 1: Install Foundry

# Install Foundry
curl -L https://foundry.paradigm.xyz | bash

# Update to latest version
foundryup

Step 2: Create Foundry Project

# Initialize project
forge init nexis-foundry-demo
cd nexis-foundry-demo

# Install dependencies
forge install OpenZeppelin/openzeppelin-contracts

Step 3: Configure Foundry

Edit foundry.toml:
foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.20"
optimizer = true
optimizer_runs = 200

[rpc_endpoints]
nexis = "https://rpc.testnet.nexis.network"

[etherscan]
nexis = { key = "no-api-key-needed", url = "https://explorer.testnet.nexis.network/api" }

Step 4: Create Contract

Move your contract to src/AITaskRequester.sol (use the same code as above).

Step 5: Deploy with Forge

# Set your private key
export PRIVATE_KEY=your_private_key_here

# Deploy
forge create src/AITaskRequester.sol:AITaskRequester \
  --rpc-url nexis \
  --private-key $PRIVATE_KEY \
  --constructor-args 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb \
  --verify

# Or use a script for more control
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url nexis \
  --private-key $PRIVATE_KEY \
  --broadcast \
  --verify

Part 6: Testing Your Deployment

Step 1: Interact via Hardhat Console

Test your deployed contract:
npx hardhat console --network nexis
In the console:
// Get contract instance
const AITaskRequester = await ethers.getContractFactory("AITaskRequester");
const contract = await AITaskRequester.attach("YOUR_CONTRACT_ADDRESS");

// Check tasks contract address
const tasksAddress = await contract.tasksContract();
console.log("Tasks contract:", tasksAddress);

// Create a test task
const tx = await contract.requestInference(
  "Explain quantum computing in simple terms",
  ethers.parseEther("0.1"), // 0.1 NZT reward
  3600, // 1 hour deadline
  ethers.AbiCoder.defaultAbiCoder().encode(
    ['string', 'uint256'],
    ['gpt-4', 1000] // model and max tokens
  ),
  { value: ethers.parseEther("0.1") }
);

await tx.wait();
console.log("Task created! Tx:", tx.hash);

Step 2: View on Block Explorer

Visit the explorer to see your contract:
https://explorer.testnet.nexis.network/address/YOUR_CONTRACT_ADDRESS
You should see:
  • Contract code (if verified)
  • Recent transactions
  • Events emitted
  • Current balance

Troubleshooting Common Issues

Problem: Your account doesn’t have enough NZT for gas fees.Solution:
  1. Visit the faucet
  2. Request testnet NZT
  3. Wait for transaction to confirm
  4. Check balance: await ethers.provider.getBalance(YOUR_ADDRESS)
Problem: Your local nonce is out of sync with the network.Solution:
# Reset Hardhat cache
rm -rf cache artifacts

# Or specify nonce manually
await contract.deploy({
  nonce: await ethers.provider.getTransactionCount(deployer.address)
})
Problem: Verification on explorer is failing.Solution:
  1. Wait 1-2 minutes after deployment
  2. Make sure constructor arguments are correct
  3. Check Solidity version matches (0.8.20)
  4. Try manual verification on explorer UI
Problem: Cannot connect to Nexis RPC.Solution:
# Test RPC connectivity
curl https://rpc.testnet.nexis.network \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}'

# Try alternative RPC if main is down
url: "https://rpc-backup.testnet.nexis.network"
Problem: Transaction gas estimation is failing.Solution:
// Specify gas limit manually
await contract.deploy({
  gasLimit: 5000000
});

// Or increase gas price
await contract.deploy({
  gasPrice: ethers.parseUnits("2", "gwei")
});

Best Practices Checklist

1

Security

  • Never commit private keys to git
  • Use environment variables for secrets
  • Add .env to .gitignore
  • Test on testnet before mainnet
  • Get contracts audited for production
2

Gas Optimization

  • Enable Solidity optimizer
  • Use immutable for constants set in constructor
  • Batch operations when possible
  • Emit events instead of storing strings
  • Use calldata instead of memory for external function parameters
3

Testing

  • Write comprehensive unit tests
  • Test edge cases and error conditions
  • Use Hardhat’s local network for rapid testing
  • Test with actual testnet before production
  • Monitor gas usage in tests
4

Documentation

  • Add NatSpec comments to all functions
  • Document constructor parameters
  • Explain complex logic
  • Keep README up to date
  • Document deployment process

Next Steps

Additional Resources


Pro Tip: Save your deployment addresses in a deployments.json file to track contracts across networks. This makes it easier to interact with them later.