;; Tylnyi Coin fixed-emission Jetton master. MIT. ;; TEP-74 + wallet discovery TEP-89. Wallet code is pinned TON reference. #include "imports/stdlib.fc"; #include "imports/params.fc"; #include "imports/op-codes.fc"; #include "imports/jetton-utils.fc"; int fixed_supply() asm "24102026000000000 PUSHINT"; int op::initialize() asm "0x54494e49 PUSHINT"; int op::provide_wallet_address() asm "0x2c76b973 PUSHINT"; int op::take_wallet_address() asm "0xd1735400 PUSHINT"; ;; supply:Coins bootstrap_state:uint2 treasury:MsgAddress init_query_id:uint64 ;; content:^Cell wallet_code:^Cell. States: 0 not issued, 1 in flight, 2 acked. (int, int, slice, int, cell, cell) load_data() inline { slice ds = get_data().begin_parse(); return (ds~load_coins(), ds~load_uint(2), ds~load_msg_addr(), ds~load_uint(64), ds~load_ref(), ds~load_ref()); } () save_data(int supply, int state, slice treasury, int query_id, cell content, cell wallet_code) impure inline { set_data(begin_cell().store_coins(supply).store_uint(state, 2) .store_slice(treasury).store_uint(query_id, 64).store_ref(content) .store_ref(wallet_code).end_cell()); } () recv_internal(int msg_value, cell full_message, slice body) impure { if (body.slice_empty?()) { return (); } slice info = full_message.begin_parse(); int flags = info~load_uint(4); slice sender = info~load_msg_addr(); (int supply, int state, slice treasury, int init_id, cell content, cell wallet_code) = load_data(); slice treasury_wallet = calculate_user_jetton_wallet_address(treasury, my_address(), wallet_code); if (flags & 1) { ;; Only the authentic initial internal_transfer bounce may reset issuance. ;; Normal user-transfer/burn bounces are handled by the reference wallet. body~skip_bits(32); int bounced_op = body~load_uint(32); if ((state == 1) & equal_slices(sender, treasury_wallet) & (bounced_op == op::internal_transfer())) { int bounced_id = body~load_uint(64); int bounced_amount = body~load_coins(); throw_unless(76, (bounced_id == init_id) & (bounced_amount == fixed_supply()) & (supply == fixed_supply())); save_data(0, 0, treasury, 0, content, wallet_code); } return (); } int operation = body~load_uint(32); int query_id = body~load_uint(64); if (operation == op::initialize()) { throw_unless(73, equal_slices(sender, treasury)); throw_unless(75, (state == 0) & (supply == 0)); body.end_parse(); force_chain(treasury); ;; Retain storage/gas reserve; the standard wallet returns excesses as ack. throw_unless(77, msg_value >= 400000000); cell state_init = calculate_jetton_wallet_state_init(treasury, my_address(), wallet_code); cell transfer = begin_cell().store_uint(op::internal_transfer(), 32) .store_uint(query_id, 64).store_coins(fixed_supply()).store_uint(0, 2) .store_slice(my_address()).store_coins(0).store_uint(0, 1).end_cell(); cell message = begin_cell().store_uint(0x18, 6).store_slice(treasury_wallet) .store_coins(200000000).store_uint(7, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1) .store_ref(state_init).store_ref(transfer).end_cell(); ;; Without IGNORE_ERRORS: insufficient action funds revert init state too. send_raw_message(message, 1); save_data(fixed_supply(), 1, treasury, query_id, content, wallet_code); return (); } if (operation == op::excesses()) { if (state == 1) { throw_unless(74, equal_slices(sender, treasury_wallet)); throw_unless(76, query_id == init_id); body.end_parse(); save_data(supply, 2, treasury, init_id, content, wallet_code); } return (); } if (operation == op::burn_notification()) { int amount = body~load_coins(); slice owner = body~load_msg_addr(); slice response = body~load_msg_addr(); body.end_parse(); throw_unless(74, equal_slices(calculate_user_jetton_wallet_address(owner, my_address(), wallet_code), sender)); throw_unless(78, (state == 2) & (amount > 0) & (amount <= supply)); save_data(supply - amount, state, treasury, init_id, content, wallet_code); if (response.preload_uint(2) != 0) { send_raw_message(begin_cell().store_uint(0x10, 6).store_slice(response).store_coins(0) .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1).store_uint(op::excesses(), 32) .store_uint(query_id, 64).end_cell(), 64 + 2); } return (); } if (operation == op::provide_wallet_address()) { throw_unless(77, msg_value >= 10000000); slice owner = body~load_msg_addr(); int include_owner = body~load_uint(1); body.end_parse(); cell included = include_owner ? begin_cell().store_slice(owner).end_cell() : null(); var response = begin_cell().store_uint(0x10, 6).store_slice(sender).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); (int wc, _) = parse_std_addr(owner); if (wc == 0) { response = response.store_slice(calculate_user_jetton_wallet_address(owner, my_address(), wallet_code)); } else { response = response.store_uint(0, 2); } send_raw_message(response.store_maybe_ref(included).end_cell(), 64); return (); } ;; No mint, change-admin, metadata-edit or upgrade opcode exists. throw(65535); } (int, int, slice, cell, cell) get_jetton_data() method_id { (int supply, int state, slice treasury, int init_id, cell content, cell wallet_code) = load_data(); slice admin = begin_cell().store_uint(0, 2).end_cell().begin_parse(); return (supply, 0, admin, content, wallet_code); } slice get_wallet_address(slice owner) method_id { (_, _, _, _, _, cell wallet_code) = load_data(); return calculate_user_jetton_wallet_address(owner, my_address(), wallet_code); } (int, slice, int) get_bootstrap_data() method_id { (_, int state, slice treasury, int init_id, _, _) = load_data(); return (state, treasury, init_id); }