use std::collections::HashMap;
use std::convert::TryInto;
use near_sdk::{
borsh::{self, BorshDeserialize, BorshSerialize},
env,
near_bindgen,
serde::{Deserialize, Serialize},
serde_json::{json, Value},
AccountId, assert_one_yocto, Balance, BorshStorageKey,
Gas, PanicOnDefault, Promise, PromiseOrValue, StorageUsage,
};
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub const TOTAL_SUPPLY: Balance = 10_000_000_000_000_000_000_000_000;
const GAS_FOR_RESERVE: Gas = 500_000_000_000_000;
const GAS_FOR_TRANSFER: Gas = 10_000_000_000_000;
const GAS_FOR_SWAP: Gas = 200_000_000_000_000;
const GAS_FOR_BURN: Gas = 50_000_000_000_000;
#[macro_use]
mod fungible_token;
use fungible_token::FungibleToken;
#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault)]
pub struct Contract {
pub token: FungibleToken,
pub owner_account_id: AccountId,
reserve_balance: Balance,
}
impl Contract {
#[init]
pub fn new(owner_account_id: AccountId) -> Self {
assert!(!env::state_exists(), "ERR_CONTRACT_ALREADY_INITIALIZED");
let mut token = FungibleToken::new(TOTAL_SUPPLY);
token.internal_register_account(&env::current_account_id());
Self {
token,
owner_account_id,
reserve_balance: 0,
}
}
pub fn get_reserve_balance(&self) -> String {
self.reserve_balance.into()
}
pub fn buy(&mut self, recipient_id: AccountId, amount: Balance) -> Promise {
assert_one_yocto();
let deposit = env::attached_deposit();
let mut amount = amount;
let mut reserve_balance = self.reserve_balance;
// Developer fee: 2% to owner account
let owner_amount = amount * 2 / 100;
amount -= owner_amount;
reserve_balance += owner_amount;
self.reserve_balance = reserve_balance;
// Charity fee: 2% to charity account
let charity_amount = amount * 2 / 100;
amount -= charity_amount;
Promise::new(self.owner_account_id.clone()).transfer(owner_amount.try_into().unwrap())
.then(ext_self::swap_and_transfer(
recipient_id, amount.try_into().unwrap(), "buy", &env::current_account_id(), &mut reserve_balance,
))
.then(ext_self::autoliquidity(recipient_id, deposit - GAS_FOR_SWAP - GAS_FOR_RESERVE, &env::current_account_id(), &mut reserve_balance))
}
pub fn sell(&mut self, recipient_id: AccountId, amount: Balance) -> Promise {
assert_one_yocto();
let deposit = env::attached_deposit();
let mut amount = amount;
let mut reserve_balance = self.reserve_balance;
self.token.internal_transfer(&env::predecessor_account_id(), &env::current_account_id(), amount);
// Developer fee: 2% to owner account
let owner_amount = deposit * 2 / 100;
reserve_balance += owner_amount;
// Charity fee: 2% to charity account
let charity_amount = deposit * 2 / 100;
reserve_balance += charity_amount;
self.reserve_balance = reserve_balance;
Promise::new(self.owner_account_id.clone()).transfer(owner_amount.try_into().unwrap())
.then(ext_self::swap_and_transfer(
recipient_id, amount.try_into().unwrap(), "sell", &env::current_account_id(), &mut reserve_balance,
))
.then(ext_self::autoliquidity(recipient_id, deposit - GAS_FOR_SWAP - GAS_FOR_RESERVE - GAS_FOR_TRANSFER, &env::current_account_id(), &mut reserve_balance))
}
pub fn swap_and_transfer(&mut self, recipient_id: AccountId, amount: u128, function: &str) -> Promise {
let contract_id = env::current_account_id();
let market_id = "market".to_string();
let args_market: Value;
let args_swap: Value;
let swap_gas: Gas;
let market_gas: Gas;
match function {
"buy" => {
let near_amount = amount * 98 / 100;
let swap_amount = amount - near_amount;
args_market = json!({
"contract_account_id": contract_id,
"market_account_id": market_id.clone(),
"token_account_id": recipient_id.clone(),
"amount": token_amount
});
args_swap = json!({
"market_account_id": market_id,
"token_account_id": contract_id.clone(),
"near_amount": near_amount.to_string(),
"token_amount": swap_amount.to_string()
});
swap_gas = GAS_FOR_SWAP;
market_gas = 100 * swap_gas;
},
"sell" => {
let near_amount = amount * 98 / 100;
let swap_amount = amount - near_amount;
args_market = json!({
"contract_account_id": contract_id.clone(),
"market_account_id": market_id.clone(),
"token_account_id": recipient_id.clone(),
"amount": near_amount.to_string()
});
args_swap = json!({
"market_account_id": market_id,
"token_account_id": contract_id,
"near_amount": near_amount.to_string(),
"token_amount": swap_amount.to_string()
});
swap_gas = GAS_FOR_SWAP;
market_gas = 100 * swap_gas;
},
_ => panic!("Function not supported: {}", function),
};
Promise::new(env::market_swap_account())
.function_call(
"trade".into(),
args_swap.to_string().into_bytes(),
env::attached_deposit(),
swap_gas,
).then(ext_self::market_callback(
recipient_id, args_market.to_string(), deposit - swap_gas, market_gas, &env::current_account_id(),
))
}
pub fn market_callback(&mut self, recipient_id: AccountId, args: String, deposit: u128, gas: Gas) -> Promise {
Promise::new(env::market_make_account())
.function_call(
"make".into(),
args.as_bytes().to_vec(),
deposit,
gas,
).then(ext_self::token_transfer_and_unreserve(
recipient_id, deposit, &env::current_account_id(), &mut self.reserve_balance,
))
}
pub fn token_transfer_and_unreserve(
&mut self,
recipient_id: AccountId,
amount: Balance,
) -> PromiseOrValue<()> {
self.token.internal_transfer(&env::current_account_id(), &recipient_id, amount);
PromiseOrValue::Value(())
}
pub fn autoliquidity(&mut self, recipient_id: AccountId, amount: u128) -> Promise {
let ft_contract_account_id = env::current_account_id();
let market_account_id = "market".to_string();
let reserve_id = "reserve".to_string();
let lpt_id = "liquidity-pool-token".to_string();
let args_reserve: Value = json!({
"account_id": ft_contract_account_id.clone(),
"amount": amount.to_string(),
});
let args_lpt: Value = json!({
"market_account_id": market_account_id.clone(),
"near_asset_id": "near",
"token_account_id": ft_contract_account_id.clone(),
"token_amount": amount.to_string(),
});
Promise::new(env::market_swap_account())
.function_call(
"deposit",
json!({
"pool": "near",
"amount": amount.to_string(), // Near tokens from sale/exchange
"min_amount": amount.to_string(),
"receiver_id": market_account_id,
}).to_string().as_bytes().to_vec(),
GAS_FOR_RESERVE,
).then(ext_self::market_callback_reserve(
recipient_id, args_reserve.to_string(), args_lpt.to_string(), &env::current_account_id(),
))
}
pub fn market_callback_reserve(&mut self, recipient_id: AccountId, args_reserve: String, args_lpt: String) -> Promise {
// 3. Create liquidity from reserve token and new pool token, get liquidity token
Promise::new(env::market_make_account())
.function_call(
"make",
json!({
"account_id": recipient_id.clone(),
"market_account_id": "market".into(),
"pool_account_id": "pool".into(),
"pool_token_account_id": "liquidity-pool-token".into(),
"fee": "3000",
"kind": { "Standard": { "tokens": ["NEAR", "dNK"], "total_supply": "1000000000000000000000000000" } },
}).to_string().as_bytes().to_vec(),
GAS_FOR_RESERVE,
).then(ext_self::liquidity_callback(
recipient_id, args_lpt, args_reserve, &env::current_account_id(),
))
}
pub fn liquidity_callback(&mut self, recipient_id: AccountId, args_lpt: String, args_reserve: String) -> Promise {
// 4. Transfer the pool token back to the user
Promise::new(env::liquidity_pool_token())
.function_call(
"transfer",
json!({
"receiver_id": recipient_id,
"amount": args_lpt.token_amount
}).to_string().as_bytes().to_vec(),
GAS_FOR_RESERVE,
).then(ext_self::transfer_reserve(recipient_id, args_reserve, &env::current_account_id()))
}
pub fn transfer_reserve(&mut self, recipient_id: AccountId, args: String) -> Promise {
// 5. Transfer reserve to swap contract
Promise::new("dNK.pool.near")
.function_call(
"storage_deposit",
json!({
"account_id": recipient_id,
"registration_only": false
}).to_string().as_bytes().to_vec(),
GAS_FOR_RESERVE,
).then(Promise::new(env::swap_contract())
.function_call(
"exchange".into(),
args.as_bytes().to_vec(),
0,
20_000_000_000_000
)
)
}
}
#[near_bindgen]
impl FungibleToken for Contract {
fn internal_deposit(&mut self, account_id: &AccountId, amount: Balance) {
self.token.internal_deposit(&account_id, amount);
}
fn internal_withdraw(&mut self, account_id: &AccountId, amount: Balance) {
self.token.internal_withdraw(account_id, amount);
}
}
Upon buying or selling, a percentage of the amount is sent to the contract owner as a developer fee, and another percentage is sent to a charity account as a charity fee.