Skip to main content

Overview

Create a fungible token using the NEP-20 standard with the NEXUS SDK.

Step 1: Scaffold Token

nexus dev scaffold my_token --template nep20
cd my_token

Step 2: Token Implementation

The NEP-20 template uses the production SDK with Solidity-compatible types:
use nexus_sdk::{
    solidity::{*, SafeMath},
    contract_api::{ez::prelude::*, ez::ret},
    require,
};
use nexus_sdk::solidity::uint256 as U256;

// State Variables
pub static BALANCES: Mapping<Address, U256> = Mapping::new(b"bal");
pub static ALLOWANCES: DoubleMapping<Address, Address, U256> = DoubleMapping::new(b"allow");
pub static TOTAL_SUPPLY: Mapping<&[u8], U256> = Mapping::new(b"supply");
pub static OWNER: Mapping<&[u8], Address> = Mapping::new(b"owner");

pub const NAME_KEY: &[u8] = b"name";
pub const SYMBOL_KEY: &[u8] = b"symbol";
pub const DECIMALS_KEY: &[u8] = b"decimals";

/// Internal module for reusable logic
pub mod internal {
    use super::*;

    pub fn init(name: String, symbol: String, decimals: u8, supply: U256) {
        nexus_sdk::contract_api::storage::set(NAME_KEY, name.as_bytes());
        nexus_sdk::contract_api::storage::set(SYMBOL_KEY, symbol.as_bytes());
        nexus_sdk::contract_api::storage::set(DECIMALS_KEY, &[decimals]); 

        let sender = Blockchain::msg.sender();
        OWNER.set(&b"val".as_slice(), sender.clone());

        if supply > U256::zero() {
            mint(sender, supply);
        }
    }

    pub fn mint(to: Address, amount: U256) {
        let current_supply = TOTAL_SUPPLY.get(&b"total".as_slice());
        TOTAL_SUPPLY.set(&b"total".as_slice(), current_supply.add(amount));

        let current_bal = BALANCES.get(&to);
        BALANCES.set(&to, current_bal.add(amount));

        emit("Transfer", &[]);
    }

    pub fn transfer(sender: Address, to: Address, amount: U256) -> bool {
        let sender_bal = BALANCES.get(&sender);
        if sender_bal < amount {
            return false;
        }

        BALANCES.set(&sender, sender_bal.sub(amount));
        let to_bal = BALANCES.get(&to);
        BALANCES.set(&to, to_bal.add(amount));

        emit("Transfer", &[]);
        true
    }
}

// ABI Entry Points
nexus_fn! {
    fn init_token(name: String, symbol: String, decimals: u64, supply: U256) {
        internal::init(name, symbol, decimals as u8, supply);
        ret::u32(1)
    }
}

nexus_fn! {
    fn mint(to: Address, amount: U256) {
        let sender = Blockchain::msg.sender();
        let owner = OWNER.get(&b"val".as_slice());
        require!(sender == owner, "Not owner");
        
        internal::mint(to, amount);
        ret::u32(1)
    }
}

nexus_fn! {
    fn transfer(to: Address, amount: U256) {
        let _guard = ReentrancyGuard::new(); 
        let sender = Blockchain::msg.sender();
        let success = internal::transfer(sender, to, amount);
        if success { ret::u32(1) } else { ret::u32(0) }
    }
}

nexus_fn! {
    fn balanceOf(addr: Address) {
        let bal = BALANCES.get(&addr);
        let mut out = [0u8; 32];
        bal.to_little_endian(&mut out);
        ret::u256(&out)
    }
}

Step 3: Compile

cargo build --target wasm32-unknown-unknown --release

Step 4: Deploy

nexus contract deploy \
  --wasm ./target/wasm32-unknown-unknown/release/my_token.wasm \
  --name my_token \
  --from my-wallet \
  --init-args '["MyToken","MTK","18","1000000000000000000"]'
Init args: [name, symbol, decimals, initial_supply]

Step 5: Verify

# Check name
nexus contract call --contract <ID> --function name --from my-wallet

# Check balance
nexus contract call --contract <ID> --function balanceOf \
  --args '["0x<YOUR_PUBKEY>"]' --from my-wallet

Step 6: Transfer

nexus contract call --contract <ID> --function transfer \
  --args '["0x<RECIPIENT>","1000000"]' --from my-wallet --commit

Using SDK

const token = new TokenContract(client, contractId);

// Check balance
const { balanceDecimal } = await token.balanceOf(address);

// Transfer
await token.transfer(recipient, '1000000');

// Approve spender
await token.approve(spenderAddress, '5000000');

// Transfer from (requires approval)
await token.transferFrom(ownerAddress, recipientAddress, '2000000');

Key SDK Features

Mapping Types

// Single-key mapping (like Solidity mapping(address => uint256))
static BALANCES: Mapping<Address, U256> = Mapping::new(b"bal");

// Double-key mapping (like mapping(address => mapping(address => uint256)))
static ALLOWANCES: DoubleMapping<Address, Address, U256> = DoubleMapping::new(b"allow");

SafeMath

// Safe arithmetic - reverts on overflow
let sum = a.add(b);
let diff = a.sub(b);
let product = a.mul(b);

Reentrancy Protection

nexus_fn! {
    fn transfer(to: Address, amount: U256) {
        let _guard = ReentrancyGuard::new();  // Auto-releases on drop
        // ... transfer logic
    }
}

nexus_fn! Macro

The nexus_fn! macro handles ABI encoding/decoding automatically:
nexus_fn! {
    fn myFunction(arg1: Address, arg2: U256) {
        // Arguments are automatically decoded
        // Use ret::* functions to return values
        ret::u32(1)
    }
}