NEP-20 Token
Full ERC-20 compatible token with owner-only minting.Copy
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.Copy
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.Copy
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.Copy
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.Copy
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
Copy
# 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