> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yattacorp.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# NEP-20 Token Standard

> The NEXUS fungible token standard — interface, production implementation, and usage

NEP-20 is the fungible token standard for NEXUS, equivalent to ERC-20 on Ethereum. Every token deployed on NEXUS follows this interface, enabling wallets, DEXes, and other contracts to interact with any token in a uniform way.

***

## Interface

### Required Functions

| Function            | Arguments                      | Returns   | Description                                   |
| ------------------- | ------------------------------ | --------- | --------------------------------------------- |
| `init_token`        | name, symbol, decimals, supply | -         | Initialize (one-time, owner becomes deployer) |
| `name`              | —                              | bytes     | Token name                                    |
| `symbol`            | —                              | bytes     | Token symbol                                  |
| `decimals`          | —                              | u32       | Decimal places (default: 18)                  |
| `totalSupply`       | —                              | U256      | Total tokens in existence                     |
| `balanceOf`         | address                        | U256      | Balance of a specific address                 |
| `transfer`          | to, amount                     | u32 (1/0) | Send tokens to an address                     |
| `approve`           | spender, amount                | u32 (1)   | Allow spender to use your tokens              |
| `allowance`         | owner, spender                 | U256      | How much spender can use                      |
| `transferFrom`      | from, to, amount               | u32 (1/0) | Transfer using an allowance                   |
| `increaseAllowance` | spender, amount                | u32 (1)   | Safely increase allowance                     |
| `decreaseAllowance` | spender, amount                | u32 (1)   | Safely decrease allowance                     |
| `mint`              | to, amount                     | u32 (1)   | Mint new tokens (owner only)                  |
| `burn`              | from, amount                   | u32 (1)   | Burn tokens (owner only)                      |
| `owner`             | —                              | bytes     | Get current owner address                     |
| `transferOwnership` | new\_owner                     | u32 (1)   | Transfer ownership                            |
| `renounceOwnership` | —                              | u32 (1)   | Renounce ownership (irreversible)             |

### Events

| Event                  | When                                             |
| ---------------------- | ------------------------------------------------ |
| `Transfer`             | On any token transfer, including mints and burns |
| `Approval`             | When an allowance is set                         |
| `TokenInitialized`     | When `init_token` is called                      |
| `OwnershipTransferred` | When ownership changes                           |
| `OwnershipRenounced`   | When ownership is renounced                      |

***

## Storage Layout

```rust theme={null}
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 static INITIALIZED:  Mapping<&[u8], bool>                  = Mapping::new(b"init");
```

***

## Production Source

The full production implementation (`apps/contracts/nep20/src/lib.rs`):

```rust theme={null}
//! NEP-20 Token Standard - Production Implementation

#![no_std]
extern crate alloc;
use alloc::string::String;
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 static INITIALIZED: Mapping<&[u8], bool> = Mapping::new(b"init");

// Constants
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::*;

    /// Manual U256 serialization to avoid WASM intrinsics
    pub fn u256_to_bytes(value: U256) -> [u8; 32] {
        value.to_little_endian()
    }

    /// Returns true if a < b
    pub fn u256_lt(a: &U256, b: &U256) -> bool {
        if a.0[3] < b.0[3] { return true; }
        if a.0[3] > b.0[3] { return false; }
        if a.0[2] < b.0[2] { return true; }
        if a.0[2] > b.0[2] { return false; }
        if a.0[1] < b.0[1] { return true; }
        if a.0[1] > b.0[1] { return false; }
        if a.0[0] < b.0[0] { return true; }
        false
    }

    /// Returns true if a > b
    pub fn u256_gt(a: &U256, b: &U256) -> bool {
        if a.0[3] > b.0[3] { return true; }
        if a.0[3] < b.0[3] { return false; }
        if a.0[2] > b.0[2] { return true; }
        if a.0[2] < b.0[2] { return false; }
        if a.0[1] > b.0[1] { return true; }
        if a.0[1] < b.0[1] { return false; }
        if a.0[0] > b.0[0] { return true; }
        false
    }

    /// Returns true if a == b
    pub fn u256_eq(a: &U256, b: &U256) -> bool {
        for i in 0..4 {
            if a.0[i] != b.0[i] { return false; }
        }
        true
    }

    /// Returns true if a != b
    pub fn u256_ne(a: &U256, b: &U256) -> bool {
        !u256_eq(a, b)
    }

    /// Manual U256 addition — no WASM compiler intrinsics
    pub fn u256_add(a: &U256, b: &U256) -> U256 {
        let mut result = U256::zero();
        let mut carry: u64 = 0;

        let sum0 = a.0[0].wrapping_add(b.0[0]);
        let c0 = if sum0 < a.0[0] { 1u64 } else { 0u64 };
        let sum0_c = sum0.wrapping_add(carry);
        let c0_2 = if sum0_c < sum0 { 1u64 } else { 0u64 };
        result.0[0] = sum0_c;
        carry = c0 + c0_2;

        let sum1 = a.0[1].wrapping_add(b.0[1]);
        let c1 = if sum1 < a.0[1] { 1u64 } else { 0u64 };
        let sum1_c = sum1.wrapping_add(carry);
        let c1_2 = if sum1_c < sum1 { 1u64 } else { 0u64 };
        result.0[1] = sum1_c;
        carry = c1 + c1_2;

        let sum2 = a.0[2].wrapping_add(b.0[2]);
        let c2 = if sum2 < a.0[2] { 1u64 } else { 0u64 };
        let sum2_c = sum2.wrapping_add(carry);
        let c2_2 = if sum2_c < sum2 { 1u64 } else { 0u64 };
        result.0[2] = sum2_c;
        carry = c2 + c2_2;

        let sum3 = a.0[3].wrapping_add(b.0[3]);
        let c3 = if sum3 < a.0[3] { 1u64 } else { 0u64 };
        let sum3_c = sum3.wrapping_add(carry);
        let c3_2 = if sum3_c < sum3 { 1u64 } else { 0u64 };
        result.0[3] = sum3_c;
        carry = c3 + c3_2;
        let _ = carry;

        result
    }

    /// Checked U256 addition — returns None on overflow
    #[inline]
    pub fn u256_checked_add(a: &U256, b: &U256) -> Option<U256> {
        let result = u256_add(a, b);
        if u256_lt(&result, a) || u256_lt(&result, b) {
            None
        } else {
            Some(result)
        }
    }

    /// Manual U256 subtraction — no WASM compiler intrinsics
    pub fn u256_sub(a: &U256, b: &U256) -> U256 {
        let mut result = U256::zero();
        let mut borrow = 0u64;

        for i in 0..4 {
            let diff = a.0[i].wrapping_sub(b.0[i]).wrapping_sub(borrow);
            result.0[i] = diff;
            borrow = if (a.0[i] < b.0[i]) || (diff > a.0[i]) { 1 } else { 0 };
        }

        result
    }

    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 u256_gt(&supply, &U256::zero()) {
            mint(sender, supply);
        }
    }

    pub fn mint(to: Address, amount: U256) {
        let current_supply = TOTAL_SUPPLY.get(&b"total".as_slice());
        let new_supply = u256_checked_add(&current_supply, &amount)
            .expect("Mint: total supply overflow");
        TOTAL_SUPPLY.set(&b"total".as_slice(), new_supply);

        let current_bal = BALANCES.get(&to);
        let new_bal = u256_checked_add(&current_bal, &amount)
            .expect("Mint: balance overflow");
        BALANCES.set(&to, new_bal);

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

    nexus_fn! {
        fn burn(from: Address, amount: U256) {
            let sender = Blockchain::msg.sender();
            let owner = OWNER.get(&b"val".as_slice());
            require!(sender == owner, "Not owner");

            let bal = BALANCES.get(&from);
            require!(!internal::u256_lt(&bal, &amount), "Burn exceeds balance");
            BALANCES.set(&from, internal::u256_sub(&bal, &amount));

            let supply = TOTAL_SUPPLY.get(&b"total".as_slice());
            require!(!internal::u256_lt(&supply, &amount), "Burn exceeds supply");
            TOTAL_SUPPLY.set(&b"total".as_slice(), internal::u256_sub(&supply, &amount));

            emit("Transfer", &[]);
            ret::u32(1)
        }
    }

    pub fn transfer(sender: Address, to: Address, amount: U256) -> bool {
        if to.is_zero() { return false; }
        if amount == U256::zero() { return false; }

        let sender_bal = BALANCES.get(&sender);
        if u256_lt(&sender_bal, &amount) { return false; }

        BALANCES.set(&sender, u256_sub(&sender_bal, &amount));

        let to_bal = BALANCES.get(&to);
        BALANCES.set(&to, u256_add(&to_bal, &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 {
        if amount == U256::zero() { return false; }

        let allowed = ALLOWANCES.get(&from, &spender);
        if u256_lt(&allowed, &amount) { return false; }

        // Reduce allowance unless it's a max (unlimited) approval
        if u256_ne(&allowed, &U256::max_value()) {
            ALLOWANCES.set(&from, &spender, u256_sub(&allowed, &amount));
        }

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

    pub fn balance_of(addr: Address) -> U256 {
        BALANCES.get(&addr)
    }
}

// ABI Entry Points

nexus_fn! {
    fn init_token(name: String, symbol: String, decimals: u64, supply: U256) {
        let is_initialized = INITIALIZED.get(&b"val".as_slice());
        require!(!is_initialized, "AI"); // Already Initialized

        INITIALIZED.set(&b"val".as_slice(), true);
        internal::init(name, symbol, decimals as u8, supply);
        emit("TokenInitialized", &[]);

        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 transferOwnership(new_owner: Address) {
        let sender = Blockchain::msg.sender();
        let owner = OWNER.get(&b"val".as_slice());
        require!(sender == owner, "Not owner");
        require!(!new_owner.is_zero(), "Zero address");

        OWNER.set(&b"val".as_slice(), new_owner.clone());
        emit("OwnershipTransferred", &new_owner.0);
        ret::u32(1)
    }
}

nexus_fn! {
    fn renounceOwnership() {
        let sender = Blockchain::msg.sender();
        let owner = OWNER.get(&b"val".as_slice());
        require!(sender == owner, "Not owner");

        OWNER.set(&b"val".as_slice(), Address::zero());
        emit("OwnershipRenounced", &[]);
        ret::u32(1)
    }
}

nexus_fn! {
    fn owner() {
        let owner = OWNER.get(&b"val".as_slice());
        ret::bytes(&owner.0)
    }
}

nexus_fn! {
    fn balanceOf(addr: Address) {
        let bal = internal::balance_of(addr);
        let out = internal::u256_to_bytes(bal);
        ret::u256(&out)
    }
}

nexus_fn! {
    fn totalSupply() {
        let supply = TOTAL_SUPPLY.get(&b"total".as_slice());
        let out = internal::u256_to_bytes(supply);
        ret::u256(&out)
    }
}

nexus_fn! {
    fn name() {
        let name_bytes = nexus_sdk::contract_api::storage::get(NAME_KEY);
        ret::bytes(&name_bytes)
    }
}

nexus_fn! {
    fn symbol() {
        let symbol_bytes = nexus_sdk::contract_api::storage::get(SYMBOL_KEY);
        ret::bytes(&symbol_bytes)
    }
}

nexus_fn! {
    fn decimals() {
        let dec_bytes = nexus_sdk::contract_api::storage::get(DECIMALS_KEY);
        let decimals = if dec_bytes.is_empty() { 18u8 } else { dec_bytes[0] };
        ret::u32(decimals as u32)
    }
}

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 increaseAllowance(spender: Address, added_value: U256) {
        let sender = Blockchain::msg.sender();
        let current = ALLOWANCES.get(&sender, &spender);
        let new_allowance = internal::u256_add(&current, &added_value);
        internal::approve(sender, spender, new_allowance);
        ret::u32(1)
    }
}

nexus_fn! {
    fn decreaseAllowance(spender: Address, subtracted_value: U256) {
        let sender = Blockchain::msg.sender();
        let current = ALLOWANCES.get(&sender, &spender);
        if internal::u256_lt(&current, &subtracted_value) {
            panic!("Allowance underflow");
        }
        let new_allowance = internal::u256_sub(&current, &subtracted_value);
        internal::approve(sender, spender, new_allowance);
        ret::u32(1)
    }
}

nexus_fn! {
    fn allowance(owner: Address, spender: Address) {
        let amount = ALLOWANCES.get(&owner, &spender);
        let out = internal::u256_to_bytes(amount);
        ret::u256(&out)
    }
}

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

<Info>
  U256 arithmetic is implemented manually (word-by-word carry/borrow) to avoid WASM compiler intrinsics that are not available in `no_std` environments. This is a hard requirement for NEXUS contracts.
</Info>

<Info>
  `ReentrancyGuard::new()` is a built-in NEXUS primitive. It prevents reentrancy attacks on any function that modifies balances or makes external calls.
</Info>

***

## Deploying a Token

```typescript theme={null}
import { NexusClient, Wallet } from '@yattacorp/nexus-sdk';
import { readFileSync } from 'fs';

// Wallet must be mnemonic-based — required for vault creation and renewal key
const wallet = Wallet.fromMnemonic(process.env.MNEMONIC!, 'mainnet', undefined, 'zcash');
const client = new NexusClient(
  { rpcUrl: 'https://api.yattacorp.xyz', network: 'mainnet', chain: 'zcash' },
  wallet
);

// 1. Build (from nexus-contract-template)
//    cargo build --target wasm32-unknown-unknown --release

// 2. Deploy the NEP-20 WASM
const wasm = readFileSync(
  './target/wasm32-unknown-unknown/release/my_nexus_contract.wasm'
);
const { contractId } = await client.deployContract(wasm);

// 3. Initialize (one-time — sets name, symbol, supply, owner = deployer)
await client.callContract(contractId, 'init_token', [
  'My Token',                   // name
  'MTK',                        // symbol
  18,                           // decimals
  '1000000000000000000000000',  // initial supply (1M tokens at 18 decimals)
]);

console.log('Token deployed:', contractId);
```

***

## Interacting with a Token

```typescript theme={null}
// Read balance (no gas)
const balance = await client.queryContract(contractId, 'balanceOf', [userAddress]);

// Transfer tokens
await client.callContract(contractId, 'transfer', [
  recipientAddress,
  '1000000000000000000',  // 1 token (18 decimals)
]);

// Approve a spender
await client.callContract(contractId, 'approve', [
  spenderAddress,
  '500000000000000000',
]);

// Check allowance
const allowance = await client.queryContract(contractId, 'allowance', [
  ownerAddress,
  spenderAddress,
]);

// Transfer on behalf of (requires prior approval)
await client.callContract(contractId, 'transferFrom', [
  ownerAddress,
  recipientAddress,
  '500000000000000000',
]);

// Mint new tokens (owner only)
await client.callContract(contractId, 'mint', [
  recipientAddress,
  '1000000000000000000000',
]);

// Burn tokens (owner only)
await client.callContract(contractId, 'burn', [
  holderAddress,
  '1000000000000000000',
]);
```

***

## Security Notes

* `init_token` can only be called once — it checks the `INITIALIZED` flag (`"AI"` error = Already Initialized) and reverts if already set
* `mint` and `burn` are owner-only — after `renounceOwnership()`, no one can ever mint or burn again
* `transfer` and `transferFrom` both use `ReentrancyGuard` — reentrancy is blocked at the SDK level
* `increaseAllowance`/`decreaseAllowance` are safer than calling `approve` directly — they prevent the ERC-20 approval race condition
* `transferFrom` does not reduce allowance if the allowance is `U256::max_value()` (unlimited approval)
* All U256 arithmetic uses manual word-by-word operations — no floating point, no compiler intrinsics, full WASM compatibility
