Skip to main content

Overview

NEP-20 is the fungible token standard for NEXUS, fully compatible with ERC-20 for cross-contract calls.

Interface

Required Functions

FunctionArgumentsReturnsDescription
name-stringToken name
symbol-stringToken symbol
decimals-u32Decimal places
totalSupply-U256Total supply
balanceOfaddressU256Balance of address
transferto, amountboolTransfer tokens
approvespender, amountboolApprove spending
allowanceowner, spenderU256Get allowance
transferFromfrom, to, amountboolTransfer from

Events

EventDescription
TransferEmitted on token transfer
ApprovalEmitted when allowance is set

Production Implementation

//! NEP-20 Token Standard - Production Implementation

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 containing 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
    }

    pub fn approve(owner: Address, spender: Address, amount: U256) {
        ALLOWANCES.set(&owner, &spender, amount);
        emit("Approval", &[]);
    }

    pub fn transfer_from(spender: Address, from: Address, to: Address, amount: U256) -> bool {
        let allowed = ALLOWANCES.get(&from, &spender);
        if allowed < amount {
            return false;
        }

        // Update Allowance (unless max approval)
        if allowed != U256::max_value() {
            ALLOWANCES.set(&from, &spender, allowed.sub(amount));
        }

        internal::transfer(from, to, amount)
    }
}

// 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 approve(spender: Address, amount: U256) {
        let sender = Blockchain::msg.sender();
        internal::approve(sender, spender, amount);
        ret::u32(1)
    }
}

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

Key Features

Solidity-Compatible Types

use nexus_sdk::solidity::{*, SafeMath};
use nexus_sdk::solidity::uint256 as U256;

// Mappings work like Solidity
static BALANCES: Mapping<Address, U256> = Mapping::new(b"bal");
static ALLOWANCES: DoubleMapping<Address, Address, U256> = DoubleMapping::new(b"allow");

SafeMath Operations

// Safe arithmetic that reverts on overflow
let new_balance = current_balance.add(amount);
let remaining = balance.sub(amount);
let product = a.mul(b);
let quotient = a.div(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
  • Function export
  • Error handling
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)
    }
}

Deployment

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

Usage

const token = new TokenContract(client, contractId);

// Query
const balance = await token.balanceOf(address);
const name = await token.name();

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

// Approve and transferFrom
await token.approve(spender, '1000000');
await token.transferFrom(owner, recipient, '500000');