diff --git a/build/jetton-wallet.deploy.ts b/build/jetton-wallet.deploy.ts new file mode 100644 index 0000000..e097b7c --- /dev/null +++ b/build/jetton-wallet.deploy.ts @@ -0,0 +1,31 @@ +import * as main from "../contracts/main"; +import { Address, toNano, TupleSlice, WalletContract } from "ton"; +import { sendInternalMessageWithWallet } from "../test/helpers"; + +// return the init Cell of the contract storage (according to load_data() contract method) +export function initData() { + return main.data({ + ownerAddress: Address.parseFriendly("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N").address, + counter: 10, + }); +} + +// return the op that should be sent to the contract on deployment, can be "null" to send an empty message +export function initMessage() { + return main.increment(); +} + +// optional end-to-end sanity test for the actual on-chain contract to see it is actually working on-chain +export async function postDeployTest(walletContract: WalletContract, secretKey: Buffer, contractAddress: Address) { + const call = await walletContract.client.callGetMethod(contractAddress, "counter"); + const counter = new TupleSlice(call.stack).readBigNumber(); + console.log(` # Getter 'counter' = ${counter.toString()}`); + + const message = main.increment(); + await sendInternalMessageWithWallet({ walletContract, secretKey, to: contractAddress, value: toNano(0.02), body: message }); + console.log(` # Sent 'increment' op message`); + + const call2 = await walletContract.client.callGetMethod(contractAddress, "counter"); + const counter2 = new TupleSlice(call2.stack).readBigNumber(); + console.log(` # Getter 'counter' = ${counter2.toString()}`); +} diff --git a/build/minter.deploy.ts b/build/minter.deploy.ts new file mode 100644 index 0000000..e097b7c --- /dev/null +++ b/build/minter.deploy.ts @@ -0,0 +1,31 @@ +import * as main from "../contracts/main"; +import { Address, toNano, TupleSlice, WalletContract } from "ton"; +import { sendInternalMessageWithWallet } from "../test/helpers"; + +// return the init Cell of the contract storage (according to load_data() contract method) +export function initData() { + return main.data({ + ownerAddress: Address.parseFriendly("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N").address, + counter: 10, + }); +} + +// return the op that should be sent to the contract on deployment, can be "null" to send an empty message +export function initMessage() { + return main.increment(); +} + +// optional end-to-end sanity test for the actual on-chain contract to see it is actually working on-chain +export async function postDeployTest(walletContract: WalletContract, secretKey: Buffer, contractAddress: Address) { + const call = await walletContract.client.callGetMethod(contractAddress, "counter"); + const counter = new TupleSlice(call.stack).readBigNumber(); + console.log(` # Getter 'counter' = ${counter.toString()}`); + + const message = main.increment(); + await sendInternalMessageWithWallet({ walletContract, secretKey, to: contractAddress, value: toNano(0.02), body: message }); + console.log(` # Sent 'increment' op message`); + + const call2 = await walletContract.client.callGetMethod(contractAddress, "counter"); + const counter2 = new TupleSlice(call2.stack).readBigNumber(); + console.log(` # Getter 'counter' = ${counter2.toString()}`); +} diff --git a/contracts/imports/discovery-params.fc b/contracts/imports/discovery-params.fc new file mode 100644 index 0000000..0d476d6 --- /dev/null +++ b/contracts/imports/discovery-params.fc @@ -0,0 +1,10 @@ +;; moved to the separate file to keep hex of the previous codes unchanged + +const op::provide_wallet_address = 0x2c76b973; +const op::take_wallet_address = 0xd1735400; + +int is_resolvable?(slice addr) inline { + (int wc, _) = parse_std_addr(addr); + + return wc == workchain(); +} \ No newline at end of file diff --git a/contracts/imports/forward-fee-calc.fc b/contracts/imports/forward-fee-calc.fc new file mode 100644 index 0000000..d93f7c7 --- /dev/null +++ b/contracts/imports/forward-fee-calc.fc @@ -0,0 +1,23 @@ +{- +Forward fee calculation supporting different workchains +-} + +;; See crypto/block/transaction.cpp:L1499 +int msg_fwd_fee(slice destination, cell message_body, cell init_state, int max_viewed_cells) inline { + (int wc, _) = parse_std_addr(destination); + throw_unless(107, (workchain == -1) | (workchain == 0) ); + int config_index = 25 + workchain; + slice cfg = config_param(config_index).begin_parse().skip_bits(8); + int lump_price = cfg~load_uint(64); + int bit_price = cfg~load_uint(64); + int cell_price = cfg~load_uint(64); + (int cells, int bits, _) = compute_data_size(message_body, max_viewed_cells); + cells -= 1; + bits -= message_body.slice_bits(); + + (int is_cells, int is_bits, _) = compute_data_size(init_state, max_viewed_cells); + is_cells -= 1; + is_bits -= init_state.slice_bits(); + return lump_price + (((bits + is_bits) * bit_price + (cells + is_cells) * cell_price + 65535) >> 16 ); +} + diff --git a/contracts/imports/op-codes.fc b/contracts/imports/op-codes.fc new file mode 100644 index 0000000..ea5906d --- /dev/null +++ b/contracts/imports/op-codes.fc @@ -0,0 +1,16 @@ +;; operations (constant values taken from crc32 on op message in the companion .tlb files and appear during build) + +;; Wton +const op::deposit = 0x77a33521; +const op::withdraw = 0x47d1895f; +const op::change_next_admin = 0x62dd86f1; +const op::change_admin = 0x66447dad; +const op::change_content = 0x743f8e58; + +;; Wallet +const op::transfer = 0xf8a7ea5; +const op::transfer_notification = 0x7362d09c; +const op::excesses = 0xd53276db; +const op::burn = 0x595f07bc; +const op::internal_transfer = 0x178d4519; +const op::burn_notification = 0x7bdd97de; diff --git a/contracts/imports/utils.fc b/contracts/imports/utils.fc index 230a8f7..3b39045 100644 --- a/contracts/imports/utils.fc +++ b/contracts/imports/utils.fc @@ -1,3 +1,7 @@ +#pragma version >=0.2.0; + +#include "stdlib.fc"; + () send_grams(slice address, int amount) impure { cell msg = begin_cell() .store_uint (0x18, 6) ;; bounce @@ -7,3 +11,41 @@ .end_cell(); send_raw_message(msg, 3); ;; mode, 2 for ignoring errors, 1 for sender pays fees, 64 for returning inbound message value } + +int workchain() asm "0 PUSHINT"; + +() force_chain(slice addr) impure { + (int wc, _) = parse_std_addr(addr); + throw_unless(333, wc == workchain()); +} + +cell pack_jetton_wallet_data(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_coins(balance) + .store_slice(owner_address) + .store_slice(jetton_master_address) + .store_ref(jetton_wallet_code) + .end_cell(); +} + +cell calculate_jetton_wallet_state_init(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return begin_cell() + .store_uint(0, 2) + .store_dict(jetton_wallet_code) + .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) + .store_uint(0, 1) + .end_cell(); +} + +slice calculate_jetton_wallet_address(cell state_init) inline { + return begin_cell() + .store_uint(4, 3) + .store_int(workchain(), 8) + .store_uint(cell_hash(state_init), 256) + .end_cell() + .begin_parse(); +} + +slice calculate_user_jetton_wallet_address(slice owner_address, slice jetton_master_address, cell jetton_wallet_code) inline { + return calculate_jetton_wallet_address(calculate_jetton_wallet_state_init(owner_address, jetton_master_address, jetton_wallet_code)); +} diff --git a/contracts/jetton-wallet.fc b/contracts/jetton-wallet.fc new file mode 100644 index 0000000..5a353cd --- /dev/null +++ b/contracts/jetton-wallet.fc @@ -0,0 +1,243 @@ +;; Jetton Wallet Smart Contract + +{- + +NOTE that this tokens can be transferred within the same workchain. + +This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions: + +1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`) + +2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain) + +-} + +#pragma version >=0.2.0; + +#include "imports/stdlib.fc"; +#include "imports/utils.fc"; +#include "imports/op-codes.fc"; + +int min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON +int gas_consumption() asm "10000000 PUSHINT"; ;; 0.01 TON + +{- + Storage + storage#_ balance:(VarUInteger 16) owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage; +-} + +(int, slice, slice, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref()); +} + +() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline { + set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code)); +} + +{- + transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress + response_destination:MsgAddress custom_payload:(Maybe ^Cell) + forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice to_owner_address = in_msg_body~load_msg_addr(); + force_chain(to_owner_address); + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + balance -= jetton_amount; + + throw_unless(705, equal_slices(owner_address, sender_address)); + throw_unless(706, balance >= 0); + + cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + slice response_address = in_msg_body~load_msg_addr(); + cell custom_payload = in_msg_body~load_dict(); + int forward_ton_amount = in_msg_body~load_coins(); + throw_unless(708, slice_bits(in_msg_body) >= 1); + slice either_forward_payload = in_msg_body; + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(0) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init); + var msg_body = begin_cell() + .store_uint(op::internal_transfer, 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .store_coins(forward_ton_amount) + .store_slice(either_forward_payload) + .end_cell(); + + msg = msg.store_ref(msg_body); + int fwd_count = forward_ton_amount ? 2 : 1; + throw_unless(709, msg_value > + forward_ton_amount + + ;; 3 messages: wal1->wal2, wal2->owner, wal2->response + ;; but last one is optional (it is ok if it fails) + fwd_count * fwd_fee + + (2 * gas_consumption() + min_tons_for_storage())); + ;; universal message send fee calculation may be activated here + ;; by using this instead of fwd_fee + ;; msg_fwd_fee(to_wallet, msg_body, state_init, 15) + + send_raw_message(msg.end_cell(), 64); ;; revert on errors + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +{- + internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress + response_address:MsgAddress + forward_ton_amount:(VarUInteger 16) + forward_payload:(Either Cell ^Cell) + = InternalMsgBody; +-} + +() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + slice from_address = in_msg_body~load_msg_addr(); + slice response_address = in_msg_body~load_msg_addr(); + throw_unless(707, + equal_slices(jetton_master_address, sender_address) + | + equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address) + ); + int forward_ton_amount = in_msg_body~load_coins(); + + int ton_balance_before_msg = my_ton_balance - msg_value; + int storage_fee = min_tons_for_storage() - min(ton_balance_before_msg, min_tons_for_storage()); + msg_value -= (storage_fee + gas_consumption()); + if(forward_ton_amount) { + msg_value -= (forward_ton_amount + fwd_fee); + slice either_forward_payload = in_msg_body; + + var msg_body = begin_cell() + .store_uint(op::transfer_notification, 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(from_address) + .store_slice(either_forward_payload) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract + .store_slice(owner_address) + .store_coins(forward_ton_amount) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 1); + } + + if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(response_address) + .store_coins(msg_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2); + } + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure { + ;; NOTE we can not allow fails in action phase since in that case there will be + ;; no bounce. Thus check and throw in computation phase. + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + slice response_address = in_msg_body~load_msg_addr(); + cell custom_payload = in_msg_body~load_dict(); + balance -= jetton_amount; + throw_unless(705, equal_slices(jetton_master_address, sender_address)); + throw_unless(706, balance >= 0); + throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption()); + + var msg_body = begin_cell() + .store_uint(op::burn_notification, 32) + .store_uint(query_id, 64) + .store_coins(jetton_amount) + .store_slice(owner_address) + .store_slice(response_address) + .store_dict(custom_payload) + .end_cell(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(jetton_master_address) + .store_coins(0) + .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_ref(msg_body); + + send_raw_message(msg.end_cell(), 64); + + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() on_bounce (slice in_msg_body) impure { + in_msg_body~skip_bits(32); ;; 0xFFFFFFFF + (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data(); + int op = in_msg_body~load_uint(32); + throw_unless(709, (op == op::internal_transfer) | (op == op::burn_notification)); + int query_id = in_msg_body~load_uint(64); + int jetton_amount = in_msg_body~load_coins(); + balance += jetton_amount; + save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + if (flags & 1) { + on_bounce(in_msg_body); + return (); + } + slice sender_address = cs~load_msg_addr(); + cs~load_msg_addr(); ;; skip dst + cs~load_coins(); ;; skip value + cs~skip_bits(1); ;; skip extracurrency collection + cs~load_coins(); ;; skip ihr_fee + int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of forward_payload costs + + int op = in_msg_body~load_uint(32); + + if (op == op::transfer) { ;; outgoing transfer + send_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + if (op == op::internal_transfer) { ;; incoming transfer + receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value); + return (); + } + + if (op == op::burn) { ;; burn + burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee); + return (); + } + + throw(0xffff); +} + +(int, slice, slice, cell) get_wallet_data() method_id { + return load_data(); +} \ No newline at end of file diff --git a/contracts/jetton-wallet.tlb b/contracts/jetton-wallet.tlb new file mode 100644 index 0000000..4914461 --- /dev/null +++ b/contracts/jetton-wallet.tlb @@ -0,0 +1,19 @@ +// base types defined in https://github.com/newton-blockchain/ton/blob/master/crypto/block/block.tlb + +// storage (according to save_data() contract method) + +storage#_ balance:(VarUInteger 16) owner_address:MsgAddress jetton_master_address:MsgAddress jetton_wallet_code:^Cell = Storage + +// ops + +transfer query_id:uint64 amount:VarUInteger 16 destination:MsgAddress response_destination:MsgAddress custom_payload:Maybe ^Cell forward_ton_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody + +transfer_notification query_id:uint64 amount:VarUInteger 16 sender:MsgAddress forward_payload:Either Cell ^Cell = InternalMsgBody + +excesses query_id:uint64 = InternalMsgBody + +burn query_id:uint64 amount:VarUInteger 16 response_destination:MsgAddress custom_payload:Maybe ^Cell = InternalMsgBody + +internal_transfer query_id:uint64 amount:VarUInteger 16 from:MsgAddress response_address:MsgAddress forward_ton_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody + +burn_notification#0x7bdd97de query_id:uint64 amount:VarUInteger 16 sender:MsgAddress response_destination:MsgAddress custom_payload:Maybe ^Cell = InternalMsgBody diff --git a/contracts/minter.fc b/contracts/minter.fc new file mode 100644 index 0000000..d8c3448 --- /dev/null +++ b/contracts/minter.fc @@ -0,0 +1,239 @@ +;; Wrapped TON minter smart contract + +;; storage scheme +;; storage#_ total_supply:Coins admin_address:MsgAddress next_admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage; + +#pragma version >=0.2.0; + +#include "imports/stdlib.fc"; +#include "imports/op-codes.fc"; +#include "imports/utils.fc"; +#include "imports/discovery-params.fc"; + +int gas_consumption() asm "10000000 PUSHINT"; ;; 0.01 TON +int withdraw_gas_consumption() asm "10000000 PUSHINT"; ;; 0.01 TON +int deposit_gas_consumption() asm "35000000 PUSHINT"; ;; 0.035 TON + +(int, slice, slice, cell, cell) load_data() inline { + slice ds = get_data().begin_parse(); + return ( + ds~load_coins(), ;; total_supply + ds~load_msg_addr(), ;; admin_address + ds~load_msg_addr(), ;; next_admin_address + ds~load_ref(), ;; content + ds~load_ref() ;; jetton_wallet_code + ); +} + +() save_data(int total_supply, slice admin_address, slice next_admin_address, cell content, cell jetton_wallet_code) impure inline { + set_data(begin_cell() + .store_coins(total_supply) + .store_slice(admin_address) + .store_slice(next_admin_address) + .store_ref(content) + .store_ref(jetton_wallet_code) + .end_cell() + ); +} + +() mint_tokens(slice to_address, cell jetton_wallet_code, int amount) impure { + cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + var master_msg = begin_cell() + .store_uint(op::internal_transfer, 32) + ;; query_id + .store_uint(0, 64) + ;; jetton_amount + .store_coins(amount) + ;; from_address == to_address + .store_slice(to_address) + ;; response_address + .store_slice(my_address()) + ;; forward ton amount + .store_coins(0) + .end_cell(); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(gas_consumption()) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(master_msg); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors +} + +() burn_tokens(slice to_address, cell jetton_wallet_code, int amount) impure { + cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code); + slice to_wallet_address = calculate_jetton_wallet_address(state_init); + var master_msg = begin_cell() + .store_uint(op::burn, 32) + ;; query_id + .store_uint(0, 64) + ;; jetton_amount + .store_coins(amount) + ;; response_address + .store_slice(my_address()) + ;; custom_payload (currently not used) + .store_dict(new_dict()) + .end_cell(); + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(to_wallet_address) + .store_coins(gas_consumption() * 3) + .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) + .store_ref(state_init) + .store_ref(master_msg); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors +} + +() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure { + if (in_msg_body.slice_empty?()) { ;; ignore empty messages + return (); + } + slice cs = in_msg_full.begin_parse(); + int flags = cs~load_uint(4); + + if (flags & 1) { ;; ignore all bounced messages + return (); + } + slice sender_address = cs~load_msg_addr(); + cs~load_msg_addr(); ;; skip dst + cs~load_coins(); ;; skip value + cs~skip_bits(1); ;; skip extracurrency collection + cs~load_coins(); ;; skip ihr_fee + int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of provide_wallet_address cost + + int op = in_msg_body~load_uint(32); + int query_id = in_msg_body~load_uint(64); + + (int total_supply, slice admin_address, slice next_admin_address, cell content, cell jetton_wallet_code) = load_data(); + + if (op == op::deposit) { + msg_value -= deposit_gas_consumption(); + throw_if(74, msg_value < 0); + + int ton_amount = msg_value; + int jetton_amount = in_msg_body~load_coins(); + throw_if(75, ton_amount < jetton_amount); + mint_tokens(sender_address, jetton_wallet_code, jetton_amount); + save_data(total_supply + jetton_amount, admin_address, next_admin_address, content, jetton_wallet_code); + + msg_value -= jetton_amount + gas_consumption(); + if (msg_value > 0) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(sender_address) + .store_coins(msg_value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::excesses, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 2); ;; pay transfer fees together, ignore errors + } + + return (); + } + + if (op == op::withdraw) { + msg_value -= withdraw_gas_consumption(); + throw_if(74, msg_value < 0); + + int jetton_amount = in_msg_body~load_coins(); + throw_if(75, total_supply < jetton_amount); + burn_tokens(sender_address, jetton_wallet_code, jetton_amount); + save_data(total_supply, admin_address, next_admin_address, content, jetton_wallet_code); + + return (); + } + + if (op == op::burn_notification) { + int jetton_amount = in_msg_body~load_coins(); + slice from_address = in_msg_body~load_msg_addr(); + throw_unless(74, + equal_slices(calculate_user_jetton_wallet_address(from_address, my_address(), jetton_wallet_code), sender_address) + ); + + if (jetton_amount > 0) { + var msg = begin_cell() + .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000 + .store_slice(from_address) + .store_coins(jetton_amount) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + ;; op code = 0 + .store_uint(0, 32) + .store_uint(query_id, 64); + send_raw_message(msg.end_cell(), 1); ;; pay transfer fees seperatly, revert on errors + } + + save_data(total_supply - jetton_amount, admin_address, next_admin_address, content, jetton_wallet_code); + + return (); + } + + if (op == op::provide_wallet_address) { + throw_unless(75, msg_value > fwd_fee + const::provide_address_gas_consumption); + + slice owner_address = in_msg_body~load_msg_addr(); + int include_address? = in_msg_body~load_uint(1); + + cell included_address = include_address? + ? begin_cell().store_slice(owner_address).end_cell() + : null(); + + var msg = begin_cell() + .store_uint(0x18, 6) + .store_slice(sender_address) + .store_coins(0) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(op::take_wallet_address, 32) + .store_uint(query_id, 64); + + if (is_resolvable?(owner_address)) { + msg = msg.store_slice(calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code)); + } else { + msg = msg.store_uint(0, 2); ;; addr_none + } + send_raw_message(msg.store_maybe_ref(included_address).end_cell(), 64); + return (); + } + + if (op == op::change_next_admin) { ;; change next admin + throw_unless(73, equal_slices(sender_address, admin_address)); + slice new_next_admin_address = in_msg_body~load_msg_addr(); + save_data(total_supply, admin_address, new_next_admin_address, content, jetton_wallet_code); + return (); + } + + if (op == op::change_admin) { ;; change admin (only next admin can get an admin permission) + throw_unless(73, equal_slices(sender_address, next_admin_address)); + slice addr_none = begin_cell().store_uint(0, 2).end_cell().begin_parse(); + save_data(total_supply, next_admin_address, addr_none, content, jetton_wallet_code); + return (); + } + + if (op == op::change_content) { ;; change content, delete this for immutable tokens + throw_unless(73, equal_slices(sender_address, admin_address)); + save_data(total_supply, admin_address, next_admin_address, in_msg_body~load_ref(), jetton_wallet_code); + return (); + } + + if (op == op::excesses) { + return (); + } + + throw(0xffff); +} + +(int, int, slice, cell, cell) get_jetton_data() method_id { + (int total_supply, slice admin_address, _, cell content, cell jetton_wallet_code) = load_data(); + return (total_supply, -1, admin_address, content, jetton_wallet_code); +} + +(slice) get_jetton_extra_data() method_id { + (_, _, slice next_admin_address, _, _) = load_data(); + return (next_admin_address); +} + +slice get_wallet_address(slice owner_address) method_id { + (_, _, _, _, cell jetton_wallet_code) = load_data(); + return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code); +} diff --git a/contracts/minter.tlb b/contracts/minter.tlb new file mode 100644 index 0000000..c9a6e76 --- /dev/null +++ b/contracts/minter.tlb @@ -0,0 +1,21 @@ +// base types defined in https://github.com/newton-blockchain/ton/blob/master/crypto/block/block.tlb + +// storage (according to save_data() contract method) + +storage#_ total_supply:Coins admin_address:MsgAddress next_admin_address:MsgAddress content:^Cell jetton_wallet_code:^Cell = Storage + +// ops + +deposit query_id:uint64 amount:VarUInteger 16 = InternalMsgBody + +withdraw query_id:uint64 amount:VarUInteger 16 = InternalMsgBody + +change_next_admin query_id:uint64 new_next_admin_address:MsgAddress = InternalMsgBody + +change_admin query_id:uint64 = InternalMsgBody + +change_content query_id:uint64 new_content:^Cell = InternalMsgBody + +provide_wallet_address query_id:uint64 owner_address:MsgAddress include_owner_address:Bool = InternalMsgBody + +excesses query_id:uint64 = InternalMsgBody diff --git a/package.json b/package.json index 4a99708..5367229 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "tonstarter-contracts", + "name": "tonb", "description": "", "version": "0.0.0", "license": "MIT",