Skip to main content

NEP-20 Token

Full ERC-20 compatible token with owner-only minting.
use nexus_sdk::{
    solidity::{*, SafeMath},
    contract_api::{ez::prelude::*, ez::ret},
    require,
};
use nexus_sdk::solidity::uint256 as U256;

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

nexus_fn! {
    fn init_token(name: String, symbol: String, decimals: u64, supply: U256) {
        storage::set(b"name", name.as_bytes());
        storage::set(b"symbol", symbol.as_bytes());
        storage::set(b"decimals", &[decimals as u8]);
        
        let sender = Blockchain::msg.sender();
        OWNER.set(&b"val".as_slice(), sender.clone());
        
        if supply > U256::zero() {
            BALANCES.set(&sender, supply);
            TOTAL_SUPPLY.set(&b"total".as_slice(), supply);
        }
        ret::u32(1)
    }
}

nexus_fn! {
    fn transfer(to: Address, amount: U256) {
        let _guard = ReentrancyGuard::new();
        let sender = Blockchain::msg.sender();
        
        let sender_bal = BALANCES.get(&sender);
        require!(sender_bal >= amount, "Insufficient balance");
        
        BALANCES.set(&sender, sender_bal.sub(amount));
        let to_bal = BALANCES.get(&to);
        BALANCES.set(&to, to_bal.add(amount));
        
        emit("Transfer", &[]);
        ret::u32(1)
    }
}

Wrapped vSAT (WVSAT)

Wraps native vSAT into an NEP-20 token for AMM compatibility.
use nexus_sdk::{
    solidity::{*, SafeMath},
    contract_api::{ez::prelude::*, ez::ret, ez::env},
    require,
};
use nexus_sdk::solidity::uint256 as U256;

static BALANCES: Mapping<Address, U256> = Mapping::new(b"wvsat:bal");

nexus_fn! {
    fn deposit() {
        let sender = Blockchain::msg.sender();
        let value = Blockchain::msg.value();
        
        require!(value > U256::zero(), "No value sent");
        
        let current = BALANCES.get(&sender);
        BALANCES.set(&sender, current.add(value));
        
        emit("Deposit", &sender.0);
        ret::u32(1)
    }
}

nexus_fn! {
    fn withdraw(amount: U256) {
        let sender = Blockchain::msg.sender();
        let balance = BALANCES.get(&sender);
        
        require!(balance >= amount, "Insufficient balance");
        
        BALANCES.set(&sender, balance.sub(amount));
        
        // Transfer native vSAT back to sender
        sender.transfer(amount);
        
        emit("Withdrawal", &sender.0);
        ret::u32(1)
    }
}

nexus_fn! {
    fn transfer(to: Address, amount: U256) {
        let sender = Blockchain::msg.sender();
        let sender_bal = BALANCES.get(&sender);
        
        require!(sender_bal >= amount, "Insufficient balance");
        
        BALANCES.set(&sender, sender_bal.sub(amount));
        let to_bal = BALANCES.get(&to);
        BALANCES.set(&to, to_bal.add(amount));
        
        emit("Transfer", &[]);
        ret::u32(1)
    }
}

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

nexus_fn! {
    fn name() { ret::string("Wrapped vSAT") }
}

nexus_fn! {
    fn symbol() { ret::string("WVSAT") }
}

nexus_fn! {
    fn decimals() { ret::u8(8) }
}

AMM Pair (Simplified)

Core swap logic from the production AMM.
use nexus_sdk::{
    solidity::{*, SafeMath},
    contract_api::{ez::prelude::*, ez::ret, xcc},
    require,
};
use nexus_sdk::solidity::uint256 as U256;

const MINIMUM_LIQUIDITY: u64 = 1000;

static TOKEN0: Mapping<&[u8], Address> = Mapping::new(b"p:t0");
static TOKEN1: Mapping<&[u8], Address> = Mapping::new(b"p:t1");
static RES0: Mapping<&[u8], U256> = Mapping::new(b"p:r0");
static RES1: Mapping<&[u8], U256> = Mapping::new(b"p:r1");
static LP_SUPPLY: Mapping<&[u8], U256> = Mapping::new(b"p:lp:s");
static LP_BALANCES: Mapping<Address, U256> = Mapping::new(b"p:lp:bal");

fn sqrt_u256(x: U256) -> U256 {
    if x == U256::zero() { return U256::zero(); }
    let mut z = x;
    let mut y = x.div(U256::from(2)).add(U256::one());
    while y < z {
        z = y;
        y = x.div(y).add(y).div(U256::from(2));
    }
    z
}

nexus_fn! {
    fn swap(amount0_out: U256, amount1_out: U256, to: Address) {
        let t0 = TOKEN0.get(&b"val".as_slice());
        let t1 = TOKEN1.get(&b"val".as_slice());
        let r0 = RES0.get(&b"val".as_slice());
        let r1 = RES1.get(&b"val".as_slice());
        
        require!(amount0_out > U256::zero() || amount1_out > U256::zero());
        require!(amount0_out < r0 && amount1_out < r1);
        
        // Transfer outputs
        if amount0_out > U256::zero() {
            xcc::call(&t0.0, "transfer", &json_encode_addr_u256(&to, &amount0_out));
        }
        if amount1_out > U256::zero() {
            xcc::call(&t1.0, "transfer", &json_encode_addr_u256(&to, &amount1_out));
        }
        
        // Get new balances
        let balance0 = get_token_balance(&t0, &this_addr);
        let balance1 = get_token_balance(&t1, &this_addr);
        
        // Calculate input amounts
        let amount0_in = if balance0 > r0.sub(amount0_out) {
            balance0.sub(r0.sub(amount0_out))
        } else { U256::zero() };
        let amount1_in = if balance1 > r1.sub(amount1_out) {
            balance1.sub(r1.sub(amount1_out))
        } else { U256::zero() };
        
        require!(amount0_in > U256::zero() || amount1_in > U256::zero());
        
        // Verify K invariant with 0.3% fee
        let balance0_adj = balance0.mul(1000).sub(amount0_in.mul(3));
        let balance1_adj = balance1.mul(1000).sub(amount1_in.mul(3));
        require!(balance0_adj.mul(balance1_adj) >= r0.mul(r1).mul(1000000));
        
        // Update reserves
        RES0.set(&b"val".as_slice(), balance0);
        RES1.set(&b"val".as_slice(), balance1);
        
        emit("Swap", &[]);
        ret::u32(1)
    }
}

Cross-Contract Calls

Example of calling other contracts.
use nexus_sdk::contract_api::xcc;

// Call another contract
fn get_token_balance(token: &Address, account: &Address) -> U256 {
    let args = json_encode_addr(account);
    let result = xcc::call(&token.0, "balanceOf", &args);
    
    if result.len() >= 32 {
        U256::from_little_endian(&result[0..32])
    } else {
        U256::zero()
    }
}

// Transfer tokens
fn transfer_tokens(token: &Address, to: &Address, amount: &U256) -> bool {
    let args = json_encode_addr_u256(to, amount);
    let result = xcc::call(&token.0, "transfer", &args);
    check_result(&result)
}

// Deploy a new contract
fn deploy_child(code: &[u8], init_args: &[u8], salt: &[u8]) -> Address {
    let result = xcc::deploy(code, init_args, salt);
    Address(result)
}

JSON Argument Encoding

Helper functions for cross-contract calls.
use alloc::string::String;
use alloc::vec::Vec;

fn addr_to_hex(addr: &Address) -> String {
    let mut s = String::with_capacity(66);
    s.push_str("\"0x");
    for b in &addr.0 {
        let hi = (b >> 4) & 0xf;
        let lo = b & 0xf;
        s.push(if hi < 10 { (b'0' + hi) as char } else { (b'a' + hi - 10) as char });
        s.push(if lo < 10 { (b'0' + lo) as char } else { (b'a' + lo - 10) as char });
    }
    s.push('"');
    s
}

fn json_encode_addr(a: &Address) -> Vec<u8> {
    let mut s = String::from("[");
    s.push_str(&addr_to_hex(a));
    s.push(']');
    s.into_bytes()
}

fn json_encode_addr_u256(a: &Address, v: &U256) -> Vec<u8> {
    let mut s = String::from("[");
    s.push_str(&addr_to_hex(a));
    s.push(',');
    s.push_str(&u256_to_str(v));
    s.push(']');
    s.into_bytes()
}

Build & Deploy

# Build contract
cargo build --target wasm32-unknown-unknown --release

# Validate
nexus dev validate ./target/wasm32-unknown-unknown/release/my_contract.wasm

# Deploy
nexus contract deploy \
  --wasm ./target/wasm32-unknown-unknown/release/my_contract.wasm \
  --name my_contract \
  --from my-wallet