diff --git a/compilables/ClaimHelper.compile.ts b/compilables/ClaimHelper.compile.ts
new file mode 100644
index 0000000..42b9c4f
--- /dev/null
+++ b/compilables/ClaimHelper.compile.ts
@@ -0,0 +1,6 @@
+import { CompilerConfig } from '@ton/blueprint';
+
+export const compile: CompilerConfig = {
+ lang: 'tact',
+ target: 'contracts/rewards/claim-helper.tact',
+};
diff --git a/compilables/JettonMaster.compile.ts b/compilables/JettonMaster.compile.ts
new file mode 100644
index 0000000..a7c4b39
--- /dev/null
+++ b/compilables/JettonMaster.compile.ts
@@ -0,0 +1,6 @@
+import { CompilerConfig } from '@ton/blueprint';
+
+export const compile: CompilerConfig = {
+ lang: 'func',
+ targets: ['contracts/jettons/standard/jetton_master.fc'],
+};
diff --git a/compilables/JettonVault.compile.ts b/compilables/JettonVault.compile.ts
new file mode 100644
index 0000000..3e08957
--- /dev/null
+++ b/compilables/JettonVault.compile.ts
@@ -0,0 +1,6 @@
+import { CompilerConfig } from '@ton/blueprint';
+
+export const compile: CompilerConfig = {
+ lang: 'tact',
+ target: 'contracts/rewards/jetton-vault.tact',
+};
diff --git a/compilables/JettonWallet.compile.ts b/compilables/JettonWallet.compile.ts
new file mode 100644
index 0000000..73a74c0
--- /dev/null
+++ b/compilables/JettonWallet.compile.ts
@@ -0,0 +1,6 @@
+import { CompilerConfig } from '@ton/blueprint';
+
+export const compile: CompilerConfig = {
+ lang: 'func',
+ targets: ['contracts/jettons/standard/jetton_wallet.fc'],
+};
\ No newline at end of file
diff --git a/compilables/RewardJettonMaster.compile.ts b/compilables/RewardJettonMaster.compile.ts
new file mode 100644
index 0000000..84f87c4
--- /dev/null
+++ b/compilables/RewardJettonMaster.compile.ts
@@ -0,0 +1,6 @@
+import { CompilerConfig } from '@ton/blueprint';
+
+export const compile: CompilerConfig = {
+ lang: 'func',
+ targets: ['contracts/jettons/reward/reward_jetton_master.fc'],
+};
diff --git a/contracts/jetton/messages.tact b/contracts/jetton/messages.tact
index 0b9e000..8b7c174 100644
--- a/contracts/jetton/messages.tact
+++ b/contracts/jetton/messages.tact
@@ -21,6 +21,14 @@ message(0x7362d09c) TokenNotification {
from: Address;
forward_payload: Slice as remaining;
}
+// "op::transfer_notification"c (FunC)
+message(0x4fb8dedc) TransferNotification {
+ queryId: Int as uint64;
+ amount: Int as coins;
+ from: Address;
+ forward_payload: Slice as remaining;
+}
+
message(0x595f07bc) TokenBurn {
queryId: Int as uint64;
amount: Int as coins;
diff --git a/contracts/jettons/common/jetton_utils.fc b/contracts/jettons/common/jetton_utils.fc
new file mode 100644
index 0000000..69ebc5c
--- /dev/null
+++ b/contracts/jettons/common/jetton_utils.fc
@@ -0,0 +1,39 @@
+#include "./params.fc";
+#include "./op_codes.fc";
+
+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();
+}
+
+;; get StateInit
+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) ;; code
+ .store_dict(pack_jetton_wallet_data(0, owner_address, jetton_master_address, jetton_wallet_code)) ;; data
+ .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/jettons/common/op_codes.fc b/contracts/jettons/common/op_codes.fc
new file mode 100644
index 0000000..5aee6f0
--- /dev/null
+++ b/contracts/jettons/common/op_codes.fc
@@ -0,0 +1,17 @@
+const op::transfer = "op::transfer"c;
+const op::transfer_notification = "op::transfer_notification"c;
+const op::internal_transfer = "op::internal_transfer"c;
+const op::excesses = "op::excesses"c;
+const op::burn = "op::burn"c;
+const op::burn_notification = "op::burn_notification"c;
+
+;; Minter
+const op::receiveInit = "op::receiveInit"c;
+const op::mint = "op::mint"c;
+const op::mint = "op::mint"c;
+
+const op::redeemMessage = "op::redeemMessage"c;
+
+;; Discovery params
+const op::provide_wallet_address = "op::provide_wallet_address"c;
+const op::take_wallet_address = "op::take_wallet_address"c;
diff --git a/contracts/jettons/common/params.fc b/contracts/jettons/common/params.fc
new file mode 100644
index 0000000..f561ea4
--- /dev/null
+++ b/contracts/jettons/common/params.fc
@@ -0,0 +1,12 @@
+int workchain() asm "0 PUSHINT";
+
+() force_chain(slice addr) impure {
+ (int wc, _) = parse_std_addr(addr);
+ throw_unless(333, wc == workchain());
+}
+
+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/jettons/reward/reward_jetton_master.fc b/contracts/jettons/reward/reward_jetton_master.fc
new file mode 100644
index 0000000..f3f93b4
--- /dev/null
+++ b/contracts/jettons/reward/reward_jetton_master.fc
@@ -0,0 +1,198 @@
+#include "../../imports/stdlib.fc";
+#include "../common/jetton_utils.fc";
+#include "../common/params.fc";
+#include "../common/op_codes.fc";
+
+const op::batch_mint = "op::batch_mint"c;
+
+;; Jettons discoverable smart contract
+
+(int, 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_ref(), ;; content
+ ds~load_ref() ;; jetton_wallet_code
+ );
+}
+
+() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline {
+ begin_cell()
+ .store_coins(total_supply)
+ .store_slice(admin_address)
+ .store_ref(content)
+ .store_ref(jetton_wallet_code)
+ .end_cell()
+ .set_data();
+}
+
+() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) 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 msg = begin_cell()
+ .store_uint(0x18, 6) ;; bounceable
+ .store_slice(to_wallet_address)
+ .store_coins(amount)
+ .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
+}
+
+() send_text_message( slice to_addr, int value, int mode, builder content) impure {
+ var body = begin_cell()
+ .store_uint(0, 32)
+ .store_builder(content)
+ .end_cell();
+
+ var msg = begin_cell()
+ .store_uint(0x10, 6) ;; nobounce
+ .store_slice(to_addr)
+ .store_coins(value)
+ .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ .store_ref(body)
+ .end_cell();
+
+ send_raw_message(msg, mode);
+}
+
+
+;; =========================== Main Entry Points ===========================
+() recv_internal(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 = muldiv(cs~load_coins(), 3, 2);
+ ;; we use message fwd_fee for estimation of forward_payload costs
+
+ ;; check in_msg_body message
+ int op = in_msg_body~load_uint(32);
+ int query_id = in_msg_body~load_uint(64);
+
+ (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
+
+ if (op == op::receiveInit) { ;; 0x0000015
+ slice to_address = in_msg_body~load_msg_addr();
+ builder msg_body = begin_cell().store_uint(0, 32).store_slice("Hello world!");
+ send_text_message(to_address, 0, 64, msg_body);
+ return ();
+ }
+
+ if (op == op::mint) {
+ throw_unless(73, equal_slices(sender_address, admin_address)); ;; only admin can mint - Wrapper Contract
+
+ slice to_address = in_msg_body~load_msg_addr();
+ int amount = in_msg_body~load_coins();
+ cell master_msg = in_msg_body~load_ref(); ;; load a reference message
+
+ slice master_msg_cs = master_msg.begin_parse();
+ master_msg_cs~skip_bits(32 + 64); ;; op + query_id
+ int jetton_amount = master_msg_cs~load_coins();
+
+ mint_tokens(to_address, jetton_wallet_code, amount, master_msg);
+ save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code);
+ return ();
+ }
+ if (op == op::batch_mint) {
+ ;; TODO: implement batch mint
+ ;; throw_unless(73, equal_slices(sender_address, admin_address));
+ ;; cell master_msg = in_msg_body~load_ref(); ;; load a reference message
+ ;; slice master_msg_cs = master_msg.begin_parse();
+ ;; master_msg_cs~skip_bits(32 + 64); ;; op + query_id
+ ;; int dict_size = master_msg_cs~load_uint(16);
+ ;; (slice s, cell dictionary_cell)= master_msg_cs~load_dict();
+ ;; int added_jettons = 0;
+ ;; (int key, slice val, int flag) = dictionary_cell.udict_get_min?(32);
+ ;; while (flag) {
+ ;; slice to_addr = begin_cell().store_uint(key, 267).end_cell().begin_parse();
+ ;; int val_as_int = val~load_coins();
+ ;; mint_tokens(to_addr, jetton_wallet_code, val_as_int, master_msg);
+ ;; added_jettons += val_as_int;
+ ;; (key, val, flag) = dictionary_cell.udict_get_next?(32, key);
+ ;; }
+ ;; save_data(total_supply + added_jettons, 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)
+ );
+ save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code);
+
+ slice response_address = in_msg_body~load_msg_addr();
+ if (response_address.preload_uint(2) != 0) {
+ var msg = begin_cell()
+ .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
+ .store_slice(response_address)
+ .store_coins(88)
+ .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ ;; ------ Op Code & Message body --------
+ .store_uint(op::excesses, 32)
+ .store_uint(query_id, 64);
+ send_raw_message(msg.end_cell(), 1);
+
+ var returnWrapper_msg = begin_cell()
+ .store_uint(0x10, 6)
+ .store_slice(admin_address)
+ .store_coins(0)
+ .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ ;; ------ Op Code & Message body --------
+ .store_uint(3852451109, 32) ;; 0xe59fbd25
+ .store_uint(query_id, 64)
+ .store_coins(jetton_amount)
+ .store_slice(response_address);
+ send_raw_message(returnWrapper_msg.end_cell(), 64);
+ }
+ return ();
+ }
+
+ if (op == 3) { ;; change admin
+ throw_unless(73, equal_slices(sender_address, admin_address));
+ slice new_admin_address = in_msg_body~load_msg_addr();
+ save_data(total_supply, new_admin_address, content, jetton_wallet_code);
+ return ();
+ }
+
+ if (op == 4) { ;; change content, delete this for immutable tokens
+ throw_unless(73, equal_slices(sender_address, admin_address));
+ save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code);
+ return ();
+ }
+
+ throw(0xffff);
+}
+
+
+;; ------ Get Method ------
+(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_wallet_address(slice owner_address) method_id {
+ (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
+ return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code);
+}
diff --git a/contracts/jettons/standard/jetton_master.fc b/contracts/jettons/standard/jetton_master.fc
new file mode 100644
index 0000000..4e51e42
--- /dev/null
+++ b/contracts/jettons/standard/jetton_master.fc
@@ -0,0 +1,208 @@
+#include "../../imports/stdlib.fc";
+#include "../common/jetton_utils.fc";
+#include "../common/op_codes.fc";
+
+;; Jettons discoverable smart contract
+
+;; 6905(computational_gas_price) * 1000(cur_gas_price) = 6905000
+;; ceil(6905000) = 10000000 ~= 0.01 TON
+int provide_address_gas_consumption() asm "10000000 PUSHINT";
+
+
+(int, 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_ref(), ;; content
+ ds~load_ref() ;; jetton_wallet_code
+ );
+}
+
+() save_data(int total_supply, slice admin_address, cell content, cell jetton_wallet_code) impure inline {
+ begin_cell()
+ .store_coins(total_supply)
+ .store_slice(admin_address)
+ .store_ref(content)
+ .store_ref(jetton_wallet_code)
+ .end_cell()
+ .set_data();
+}
+
+() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) 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 msg = begin_cell()
+ .store_uint(0x18, 6) ;; bounceable
+ .store_slice(to_wallet_address)
+ .store_coins(amount)
+ .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
+}
+
+() send_text_message( slice to_addr, int value, int mode, builder content) impure {
+ var body = begin_cell()
+ .store_uint(0, 32)
+ .store_builder(content)
+ .end_cell();
+
+ var msg = begin_cell()
+ .store_uint(0x10, 6) ;; nobounce
+ .store_slice(to_addr)
+ .store_coins(value)
+ .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ .store_ref(body)
+ .end_cell();
+
+ send_raw_message(msg, mode);
+}
+
+
+;; =========================== Main Entry Points ===========================
+() recv_internal(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 = muldiv(cs~load_coins(), 3, 2);
+ ;; we use message fwd_fee for estimation of forward_payload costs
+
+ ;; check in_msg_body message
+ int op = in_msg_body~load_uint(32);
+ int query_id = in_msg_body~load_uint(64);
+
+ (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
+
+ if (op == op::receiveInit) { ;; 0x0000015
+ slice to_address = in_msg_body~load_msg_addr();
+ builder msg_body = begin_cell().store_uint(0, 32).store_slice("Hello world!");
+ send_text_message(to_address, 0, 64, msg_body);
+ return ();
+ }
+
+ if (op == op::mint) {
+ throw_unless(73, equal_slices(sender_address, admin_address)); ;; only admin can mint - Wrapper Contract
+
+ slice to_address = in_msg_body~load_msg_addr();
+ int amount = in_msg_body~load_coins();
+ cell master_msg = in_msg_body~load_ref(); ;; load a reference message
+
+ slice master_msg_cs = master_msg.begin_parse();
+ master_msg_cs~skip_bits(32 + 64); ;; op + query_id
+ int jetton_amount = master_msg_cs~load_coins();
+
+ mint_tokens(to_address, jetton_wallet_code, amount, master_msg);
+ save_data(total_supply + jetton_amount, 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)
+ );
+ save_data(total_supply - jetton_amount, admin_address, content, jetton_wallet_code);
+
+ slice response_address = in_msg_body~load_msg_addr();
+ if (response_address.preload_uint(2) != 0) {
+ var msg = begin_cell()
+ .store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
+ .store_slice(response_address)
+ .store_coins(88)
+ .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ ;; ------ Op Code & Message body --------
+ .store_uint(op::excesses, 32)
+ .store_uint(query_id, 64);
+ send_raw_message(msg.end_cell(), 1);
+
+ var returnWrapper_msg = begin_cell()
+ .store_uint(0x10, 6)
+ .store_slice(admin_address)
+ .store_coins(0)
+ .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
+ ;; ------ Op Code & Message body --------
+ .store_uint(3852451109, 32) ;; 0xe59fbd25
+ .store_uint(query_id, 64)
+ .store_coins(jetton_amount)
+ .store_slice(response_address);
+ send_raw_message(returnWrapper_msg.end_cell(), 64);
+ }
+ return ();
+ }
+
+ if (op == op::provide_wallet_address) {
+ throw_unless(75, msg_value > fwd_fee + 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)
+ ;; ------ Op Code & Message body --------
+ .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 == 3) { ;; change admin
+ throw_unless(73, equal_slices(sender_address, admin_address));
+ slice new_admin_address = in_msg_body~load_msg_addr();
+ save_data(total_supply, new_admin_address, content, jetton_wallet_code);
+ return ();
+ }
+
+ if (op == 4) { ;; change content, delete this for immutable tokens
+ throw_unless(73, equal_slices(sender_address, admin_address));
+ save_data(total_supply, admin_address, in_msg_body~load_ref(), jetton_wallet_code);
+ return ();
+ }
+
+ throw(0xffff);
+}
+
+
+;; ------ Get Method ------
+(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_wallet_address(slice owner_address) method_id {
+ (int total_supply, slice admin_address, cell content, cell jetton_wallet_code) = load_data();
+ return calculate_user_jetton_wallet_address(owner_address, my_address(), jetton_wallet_code);
+}
diff --git a/contracts/jettons/standard/jetton_wallet.fc b/contracts/jettons/standard/jetton_wallet.fc
new file mode 100644
index 0000000..3cb036d
--- /dev/null
+++ b/contracts/jettons/standard/jetton_wallet.fc
@@ -0,0 +1,284 @@
+#include "../../imports/stdlib.fc";
+#include "../common/jetton_utils.fc";
+#include "../common/op_codes.fc";
+
+;; 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)
+
+-}
+
+int min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON
+;; Note that 2 * gas_consumptions is expected to be able to cover fees on both wallets (sender and receiver)
+;; and also constant fees on inter-wallet interaction, in particular fwd fee on state_init transfer
+;; that means that you need to reconsider this fee when:
+;; a) jetton logic become more gas-heavy
+;; b) jetton-wallet code (sent with inter-wallet message) become larger or smaller
+;; c) global fee changes / different workchain
+int gas_consumption() asm "15000000 PUSHINT"; ;; 0.015 TON
+
+{-
+ Storage
+ storage#_ balance:Coins 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(), ;; balance
+ ds~load_msg_addr(), ;; owner_address
+ ds~load_msg_addr(), ;; jetton_master_address
+ ds~load_ref() ;; jetton_wallet_code
+ );
+}
+
+() 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;
+
+ internal_transfer query_id:uint64 jetton_amount:(VarUInteger 16) from:MsgAddress
+ response_address:MsgAddress
+ 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) ;; from
+ .store_slice(response_address) ;; response_address
+ .store_coins(forward_ton_amount) ;; forward_ton_amount
+ .store_slice(either_forward_payload) ;; 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();
+ ;; ignore custom payload
+ ;; slice custom_payload = in_msg_body~load_dict();
+ balance -= jetton_amount;
+ throw_unless(705, equal_slices(owner_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)
+ .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 = muldiv(cs~load_coins(), 3, 2); ;; 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();
+}
diff --git a/contracts/pool.tact b/contracts/pool.tact
index ba69b1a..f0e958b 100644
--- a/contracts/pool.tact
+++ b/contracts/pool.tact
@@ -2,10 +2,6 @@ import "@stdlib/deploy";
import "@stdlib/ownable";
import "@stdlib/stoppable";
import "./user-account";
-import "./jetton/assetToken/atoken";
-import "./jetton/debtToken/dtoken";
-import "./jetton/assetToken/atoken-wallet";
-import "./jetton/debtToken/dtoken-wallet";
import "./types/struct";
import "./types/message";
import "./libraries/logic/reserve-logic";
@@ -44,7 +40,7 @@ contract Pool with Deployable, Ownable, Resumable, PoolView, PoolConfigurator, P
// baseTokenAddress -> reserveConfiguration
reservesConfiguration: map
;
reservesInterestRateStrategy: map;
-
+
// oracle provider address
oracleProvider: Address?;
// ACL admins
@@ -1131,10 +1127,10 @@ contract Pool with Deployable, Ownable, Resumable, PoolView, PoolConfigurator, P
user: ownerAddress,
msg: msg
};
-
+
self.updatePositionMsg.set(queryId, updatePositionBounce);
self.queryId += 1;
-
+
// TODO: more gas check
send(SendParameters{
to: userAccountAddress,
diff --git a/contracts/rewards/claim-helper.tact b/contracts/rewards/claim-helper.tact
new file mode 100644
index 0000000..6135014
--- /dev/null
+++ b/contracts/rewards/claim-helper.tact
@@ -0,0 +1,78 @@
+import "@stdlib/ownable";
+import "@stdlib/deploy";
+import "@stdlib/stoppable";
+import "../jetton/messages";
+import "../types/message";
+import "./message";
+
+contract ClaimHelper with Ownable, Deployable, Resumable {
+ const MIN_TONS_FOR_STORAGE: Int = ton("0.03");
+ const TOKEN_CLAIM_GAS_CONSUMPTION: Int = ton("0.05");
+ const PROCESS_NOTIFICATION_GAS_CONSUMPTION: Int = ton("0.05");
+ owner: Address;
+ stopped: Bool;
+ jettonMasterAddess: Address;
+ jettonWalletAddess: Address;
+ vaultAddress: Address;
+
+ init(jettonMasterAddess: Address, vaultAddress: Address){
+ self.jettonMasterAddess = jettonMasterAddess;
+ self.vaultAddress = vaultAddress;
+ self.stopped = true;
+ // should be reset after deployment
+ self.jettonWalletAddess = myAddress();
+ self.owner = sender();
+ }
+
+ receive(msg: SetJettonWalletAddress) {
+ self.requireOwner();
+ self.jettonWalletAddess = msg.newAddress;
+ // could also be reset by calling 'Resume'
+ self.stopped = false;
+ }
+
+ receive(msg: TransferNotification){
+ let ctx: Context = context();
+ require(ctx.sender == self.jettonWalletAddess, "Invalid sender");
+ let fwdFee: Int = ctx.readForwardFee() * 5;
+ let storageFee: Int = self.MIN_TONS_FOR_STORAGE;
+ let computationAndActionFee: Int = self.TOKEN_CLAIM_GAS_CONSUMPTION + self.PROCESS_NOTIFICATION_GAS_CONSUMPTION;
+ let totalFee: Int = (fwdFee + storageFee) + computationAndActionFee;
+ require(ctx.value > totalFee, "Insufficient fee");
+
+ // let forwardPayload: Slice = msg.forward_payload;
+ // let opCode: Int = forwardPayload.loadUint(32);
+ // "ClaimReward"c
+ // if (opCode == 0x7994ff68) {
+ let claimAmount: Int = msg.amount;
+ require(claimAmount > 0, "claim zero amount");
+ let selfConsumption: Int = self.PROCESS_NOTIFICATION_GAS_CONSUMPTION + ctx.readForwardFee();
+ let remainingMsgValue: Int = self.remainingValue(ctx.value, selfConsumption);
+ send(SendParameters{
+ to: self.vaultAddress,
+ value: remainingMsgValue,
+ bounce: true,
+ mode: SendPayGasSeparately,
+ body: ClaimTargetJetton{
+ jettonAddress: self.jettonMasterAddess,
+ owner: msg.from,
+ amount: claimAmount
+ }.toCell()
+ }
+ );
+ // }
+ return;
+ }
+
+ fun remainingValue(value: Int, selfConsumption: Int): Int {
+ let msgValue: Int = value;
+ let tonBalanceBeforeMsg: Int = myBalance() - msgValue;
+ let storageFee: Int = self.MIN_TONS_FOR_STORAGE - min(tonBalanceBeforeMsg, self.MIN_TONS_FOR_STORAGE);
+ msgValue = msgValue - (storageFee + selfConsumption);
+ return msgValue;
+ }
+
+ // fun getJettonWalletInit(address: Address): StateInit {
+ // FunC implemented jetton wallet could not invoke initOf() func
+ // }
+}
\ No newline at end of file
diff --git a/contracts/rewards/jetton-vault.tact b/contracts/rewards/jetton-vault.tact
new file mode 100644
index 0000000..ee70759
--- /dev/null
+++ b/contracts/rewards/jetton-vault.tact
@@ -0,0 +1,140 @@
+import "@stdlib/ownable";
+import "@stdlib/deploy";
+import "@stdlib/stoppable";
+import "../jetton/messages";
+import "../types/message";
+import "./message";
+import "./struct.tact";
+
+contract JettonVault with Ownable, Deployable, Resumable {
+ const MIN_TONS_FOR_STORAGE: Int = ton("0.03");
+ const TOKEN_TRANSFER_GAS_CONSUMPTION: Int = ton("0.05");
+ const TOKEN_CLAIM_GAS_CONSUMPTION: Int = ton("0.05");
+ const PROCESS_NOTIFICATION_GAS_CONSUMPTION: Int = ton("0.05");
+ const INITIATE_TIME_VESTING_GAS_CONSUMPTION: Int = ton("0.04");
+ owner: Address;
+ stopped: Bool;
+ jettonWalletAddess: Address;
+ claimableConfigurations: map;
+ claimableConfigurationLength: Int = 0;
+ queryId: Int = 0;
+ init(){
+ self.owner = sender();
+ self.stopped = true;
+ // should be reset after deployment
+ self.jettonWalletAddess = myAddress();
+ }
+
+ receive(msg: SetJettonWalletAddress) {
+ self.requireOwner();
+ self.jettonWalletAddess = msg.newAddress;
+ // could also be reset by calling 'Resume'
+ self.stopped = false;
+ }
+
+ receive(msg: ConfigureClaimableConfiguration){
+ self.requireOwner();
+ let existedClaimableAddress: ClaimableConfiguration? = self.claimableConfigurations.get(msg.originJettonAddress);
+ // require(existedClaimableAddress == null, "Claimable Jetton already configured");
+ self.claimableConfigurations.set(msg.originJettonAddress, ClaimableConfiguration {
+ jettonWalletAddress: msg.jettonWalletAddress,
+ targetBeneficiary: msg.targetBeneficiary,
+ claimType: msg.claimType,
+ claimHelper: msg.claimHelper,
+ });
+ self.claimableConfigurationLength += 1;
+ self.reply("ClaimableConfiguration Added".asComment());
+ }
+
+ receive(msg: DropJettonMapping){
+ self.requireOwner();
+ let existedClaimableConfiguration: ClaimableConfiguration? = self.claimableConfigurations.get(msg.originJettonAddress);
+ require(existedClaimableConfiguration != null, "Claimable Jetton not configured");
+ self.claimableConfigurations.del(msg.originJettonAddress);
+ self.claimableConfigurationLength -= 1;
+ self.reply("ClaimableConfiguration Dropped".asComment());
+ }
+
+ receive(msg: ClaimTargetJetton){
+ let ctx: Context = context();
+ let config: ClaimableConfiguration? = self.claimableConfigurations.get(msg.jettonAddress);
+ require(config != null, "Invalid jetton address");
+ let jettonWalletAddress: Address = config!!.jettonWalletAddress;
+ require(ctx.sender == config!!.claimHelper, "Invalid sender");
+
+ let fwdFee: Int = ctx.readForwardFee() * 5;
+ let storageFee: Int = self.MIN_TONS_FOR_STORAGE;
+ let computationAndActionFee: Int = self.TOKEN_CLAIM_GAS_CONSUMPTION;
+ let totalFee: Int = (fwdFee + storageFee) + computationAndActionFee;
+ require(ctx.value > totalFee, "Insufficient fee");
+
+ let selfConsumption: Int = self.PROCESS_NOTIFICATION_GAS_CONSUMPTION + ctx.readForwardFee();
+ let remainingMsgValue: Int = self.remainingValue(ctx.value, selfConsumption);
+
+ let claimType: Int = config!!.claimType;
+ if (claimType == 0) {
+ self.sendJettonTransferViaVault(jettonWalletAddress, msg.owner, msg.amount);
+ }
+ if (claimType == 1) {
+ self.initiateTimeVestingViaVault(jettonWalletAddress, config!!.targetBeneficiary, msg.owner, msg.amount);
+ }
+ }
+
+ fun sendJettonTransferViaVault(jettonWalletAddress: Address, toAddress: Address, amount: Int) {
+ let tokenTransferMsg: TokenTransfer = TokenTransfer{
+ queryId: self.queryId,
+ amount: amount,
+ destination: toAddress,
+ response_destination: toAddress,
+ custom_payload: null,
+ forward_ton_amount: 0,
+ forward_payload: emptySlice()
+ };
+ self.queryId += 1;
+
+ send(SendParameters{
+ to: jettonWalletAddress,
+ value: 0,
+ bounce: true,
+ mode: SendRemainingValue,
+ body: tokenTransferMsg.toCell()
+ });
+ }
+
+ fun initiateTimeVestingViaVault(jettonWalletAddress: Address, toAddress: Address, owner: Address, amount: Int) {
+ let tokenTransferMsg: TokenTransfer = TokenTransfer{
+ queryId: self.queryId,
+ amount: amount,
+ destination: toAddress,
+ response_destination: owner,
+ custom_payload: null,
+ forward_ton_amount: self.INITIATE_TIME_VESTING_GAS_CONSUMPTION,
+ forward_payload: beginCell()
+ .storeUint(0x63ed65e, 32) // opcode: AddLock
+ .storeAddress(owner)
+ .endCell()
+ .asSlice()
+ };
+ self.queryId += 1;
+
+ send(SendParameters{
+ to: jettonWalletAddress,
+ value: 0,
+ bounce: true,
+ mode: SendRemainingValue,
+ body: tokenTransferMsg.toCell()
+ });
+ }
+
+ get fun allClaimableJettonMapping(): map {
+ return self.claimableConfigurations;
+ }
+
+ fun remainingValue(value: Int, selfConsumption: Int): Int {
+ let msgValue: Int = value;
+ let tonBalanceBeforeMsg: Int = myBalance() - msgValue;
+ let storageFee: Int = self.MIN_TONS_FOR_STORAGE - min(tonBalanceBeforeMsg, self.MIN_TONS_FOR_STORAGE);
+ msgValue = msgValue - (storageFee + selfConsumption);
+ return msgValue;
+ }
+}
\ No newline at end of file
diff --git a/contracts/rewards/message.tact b/contracts/rewards/message.tact
new file mode 100644
index 0000000..951d9dd
--- /dev/null
+++ b/contracts/rewards/message.tact
@@ -0,0 +1,21 @@
+message(0x2daf1323) ClaimTargetJetton {
+ jettonAddress: Address;
+ owner: Address;
+ amount: Int as coins;
+}
+
+message(0x8aed76c1) ConfigureClaimableConfiguration {
+ originJettonAddress: Address;
+ jettonWalletAddress: Address;
+ targetBeneficiary: Address;
+ claimType: Int;
+ claimHelper: Address;
+}
+
+message(0x5891a820) DropJettonMapping {
+ originJettonAddress: Address;
+}
+
+message(0x4b6be393) SetJettonWalletAddress {
+ newAddress: Address
+}
\ No newline at end of file
diff --git a/contracts/rewards/struct.tact b/contracts/rewards/struct.tact
new file mode 100644
index 0000000..31bc6c3
--- /dev/null
+++ b/contracts/rewards/struct.tact
@@ -0,0 +1,8 @@
+struct ClaimableConfiguration {
+ claimHelper: Address;
+ jettonWalletAddress: Address;
+ targetBeneficiary: Address;
+ claimType: Int;
+ // - 0: Claim via transfer
+ // - 1; Claim via time vesting
+}
\ No newline at end of file
diff --git a/scripts/deployPool.ts b/scripts/deployPool.ts
index 5da306f..c1a18a0 100644
--- a/scripts/deployPool.ts
+++ b/scripts/deployPool.ts
@@ -1,40 +1,54 @@
-import { toNano } from '@ton/core';
+import { OpenedContract, Sender, toNano } from '@ton/core';
import { Pool } from '../wrappers/Pool';
import { NetworkProvider, sleep } from '@ton/blueprint';
import { ACL } from '../helpers/constant';
-import { waitNextSeqno } from './utils';
import { waitForTx } from '../helpers/address';
+async function sendWithRetry(pool: OpenedContract, sender: Sender, value: bigint, message: any, retries = 10, delay = 5000) {
+ for (let i = 0; i < retries; i++) {
+ try {
+ await pool.send(sender, { value }, message);
+ return;
+ } catch (error: any) {
+ if (error.response && error.response.status === 429) {
+ console.log(`429 error encountered. Retrying in ${delay}ms...`);
+ console.log(error.response.data);
+ await sleep(delay);
+ } else {
+ throw error;
+ }
+ }
+ }
+ throw new Error('Max retries reached');
+}
+
export async function run(provider: NetworkProvider) {
- console.log('Deploying pool...');
const pool = provider.open(await Pool.fromInit());
- await sleep(2000);
+ console.log(`[${provider.network()}] Deploying pool`)
- await pool.send(
+ await sendWithRetry(
+ pool,
provider.sender(),
- {
- value: toNano('0.05'),
- },
+ toNano('0.05'),
{
$$type: 'Deploy',
queryId: 0n,
- },
+ }
);
await sleep(2000);
// latest: EQDlTidB1AZqnPwrtgYoai88pgr_rA1ATzB0pKke2cuQR2rI
await provider.waitForDeploy(pool.address);
console.log(`Deployed at ${pool.address.toString()}`);
- await pool.send(
+ await sendWithRetry(
+ pool,
provider.sender(),
- {
- value: toNano('0.1'),
- },
+ toNano('0.1'),
{
$$type: 'GrantRole',
role: ACL.ASSET_LISTING_ADMIN_ROLE,
admin: provider.sender().address!!,
- },
+ }
);
await waitForTx(provider, pool.address);
console.log(
diff --git a/tests/ClaimRewards-FunCJetton.spec.ts b/tests/ClaimRewards-FunCJetton.spec.ts
new file mode 100644
index 0000000..63c6e4d
--- /dev/null
+++ b/tests/ClaimRewards-FunCJetton.spec.ts
@@ -0,0 +1,788 @@
+import { Blockchain, printTransactionFees, SandboxContract, TreasuryContract } from '@ton/sandbox';
+import { beginCell, Cell, toNano, Address } from '@ton/core';
+import '@ton/test-utils';
+import { SampleJetton } from '../build/SampleJetton/tact_SampleJetton';
+import { buildOnchainMetadata } from '../scripts/utils';
+import { JettonDefaultWallet } from '../build/SampleJetton/tact_JettonDefaultWallet';
+import { ClaimHelper } from '../wrappers/ClaimHelper';
+import { JettonVault } from '../wrappers/JettonVault';
+import { RewardJettonMaster } from '../wrappers/RewardJettonMaster';
+import { compile } from '@ton/blueprint';
+import { JettonWallet } from '../wrappers/JettonWallet';
+import { sumTransactionsFee } from '../jest.setup';
+import { MockTay } from '../wrappers/MockTay';
+import { TimeVestingMaster } from '../wrappers/TimeVestingMaster';
+import { TimeVesting } from '../wrappers/TimeVesting';
+
+describe('ClaimRewards', () => {
+ let blockchain: Blockchain;
+ let deployer: SandboxContract;
+ let timeVestingMaster: SandboxContract;
+ let tay: SandboxContract;
+ let usdt: SandboxContract;
+
+ // FunC implemented jetton contract
+ let rewardJettonMasterCode: Cell;
+ let rewardJettonWalletCode: Cell;
+ let usdtRewardJettonMaster: SandboxContract;
+ let usdtRewardJettonWallet: SandboxContract;
+ let tayRewardJettonMaster: SandboxContract;
+ let tayRewardJettonWallet: SandboxContract;
+
+ let jettonVault: SandboxContract;
+ let usdtClaimHelper: SandboxContract;
+ let tayClaimHelper: SandboxContract;
+ let constructJettonWalletConfig: any;
+ let constructRewardJettonMasterConfig: any;
+
+ let mintMockTay: any;
+
+ beforeEach(async () => {
+ blockchain = await Blockchain.create();
+ deployer = await blockchain.treasury('deployer');
+ const jettonParams = {
+ name: 'USDT-Jetton',
+ description: 'Sample USDT Jetton for testing purposes',
+ decimals: '6',
+ image: 'https://ipfs.io/ipfs/bafybeicn7i3soqdgr7dwnrwytgq4zxy7a5jpkizrvhm5mv6bgjd32wm3q4/welcome-to-IPFS.jpg',
+ symbol: 'USDT',
+ };
+ const usdtRewardJettonParams = {
+ ...jettonParams,
+ name: 'USDT-Reward-Jetton',
+ symbol: "T-USDT",
+ }
+ const tayJettonParams = {
+ ...jettonParams,
+ name: 'TonLayer Token',
+ description: 'TonLayer Token',
+ symbol: "TAY",
+ decimal: "9",
+ }
+ const tayRewardJettonParams = {
+ ...jettonParams,
+ name: 'TAY-Reward-Jetton',
+ description: 'TonLayer Reward Token',
+ symbol: "T-TAY",
+ decimal: "9",
+ }
+
+ // It's the largest value I can use for max_supply in the tests
+ let max_supply = (1n << 120n) - 1n;
+ // let max_supply = toNano(1000000n); // ๐ด Set the specific total supply in nano
+ let content = buildOnchainMetadata(jettonParams);
+ let tayContent = buildOnchainMetadata(tayJettonParams);
+ let usdtRewardJettonContent = buildOnchainMetadata(usdtRewardJettonParams);
+ let tayRewardJettonContent = buildOnchainMetadata(tayRewardJettonParams);
+
+ usdt = blockchain.openContract(await SampleJetton.fromInit(deployer.address, content, max_supply));
+ tay = blockchain.openContract(await MockTay.fromInit(deployer.address, tayContent, max_supply));
+ timeVestingMaster = blockchain.openContract(await TimeVestingMaster.fromInit());
+
+ rewardJettonWalletCode = await compile('JettonWallet');
+ rewardJettonMasterCode = await compile('RewardJettonMaster');
+
+ constructRewardJettonMasterConfig = (ownerAddress: Address, contentData: any) =>
+ blockchain.openContract(RewardJettonMaster.createFromConfig(
+ {
+ admin: ownerAddress,
+ content: contentData,
+ walletCode: rewardJettonWalletCode,
+ },
+ rewardJettonMasterCode
+ ));
+
+ constructJettonWalletConfig = (ownerAddress: Address, minterAddress: Address) =>
+ blockchain.openContract(
+ JettonWallet.createFromConfig(
+ {
+ owner: ownerAddress,
+ minter: minterAddress,
+ walletCode: rewardJettonWalletCode,
+ },
+ rewardJettonWalletCode
+ ));
+
+ usdtRewardJettonMaster = constructRewardJettonMasterConfig(deployer.address, usdtRewardJettonContent);
+ usdtRewardJettonWallet = constructJettonWalletConfig(deployer.address, usdtRewardJettonMaster.address)
+ tayRewardJettonMaster = constructRewardJettonMasterConfig(deployer.address, tayRewardJettonContent);
+ tayRewardJettonWallet = constructJettonWalletConfig(deployer.address, tayRewardJettonMaster.address)
+
+ jettonVault = blockchain.openContract(await JettonVault.fromInit());
+ const usdtDeployResult = await usdt.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Deploy',
+ queryId: 0n,
+ },
+ );
+ expect(usdtDeployResult.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: usdt.address,
+ deploy: true,
+ success: true,
+ });
+
+ const tayDeployResult = await tay.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Deploy',
+ queryId: 0n,
+ },
+ );
+ expect(tayDeployResult.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: tay.address,
+ deploy: true,
+ success: true,
+ });
+ await timeVestingMaster.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Deploy',
+ queryId: 0n,
+ },
+ );
+ const timeVestingMasterTayWallet = await tay.getGetWalletAddress(timeVestingMaster.address);
+ await timeVestingMaster.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.5'),
+ },
+ {
+ $$type: 'SetTayWallet',
+ tayWallet: timeVestingMasterTayWallet,
+ },
+ );
+ mintMockTay = async (jetton: SandboxContract, receiver: Address, amount: bigint) => {
+ await jetton.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Mint',
+ queryId: 0n,
+ amount,
+ receiver,
+ },
+ );
+ };
+
+ const deployUsdtRewardJetton = await usdtRewardJettonMaster.sendDeploy(
+ deployer.getSender(),
+ toNano('0.05'),
+ );
+ expect(deployUsdtRewardJetton.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: usdtRewardJettonMaster.address,
+ deploy: true,
+ success: true,
+ })
+ const deployTayRewardJetton = await tayRewardJettonMaster.sendDeploy(
+ deployer.getSender(),
+ toNano('0.05'),
+ );
+ expect(deployTayRewardJetton.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: tayRewardJettonMaster.address,
+ deploy: true,
+ success: true,
+ })
+
+ const deployJettonVault = await jettonVault.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Deploy',
+ queryId: 0n,
+ },
+ );
+ expect(deployJettonVault.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: jettonVault.address,
+ deploy: true,
+ success: true,
+ })
+ const jettonVaultUsdtWalletAddress = await usdt.getGetWalletAddress(jettonVault.address);
+ await jettonVault.send(
+ deployer.getSender(),
+ {
+ value: toNano("0.05"),
+ },
+ {
+
+ $$type: 'SetJettonWalletAddress',
+ newAddress: jettonVaultUsdtWalletAddress,
+ },
+ )
+
+ const jettonVaultTayWalletAddress = await tay.getGetWalletAddress(jettonVault.address);
+ await jettonVault.send(
+ deployer.getSender(),
+ {
+ value: toNano("0.05"),
+ },
+ {
+
+ $$type: 'SetJettonWalletAddress',
+ newAddress: jettonVaultTayWalletAddress,
+ },
+ )
+
+ usdtClaimHelper = blockchain.openContract(await ClaimHelper.fromInit(usdtRewardJettonMaster.address, jettonVault.address));
+ const deployUsdtClaimHelper = await usdtClaimHelper.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Deploy',
+ queryId: 0n,
+ },
+ )
+ expect(deployUsdtClaimHelper.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: usdtClaimHelper.address,
+ deploy: true,
+ success: true,
+ })
+ tayClaimHelper = blockchain.openContract(await ClaimHelper.fromInit(tayRewardJettonMaster.address, jettonVault.address));
+ await tayClaimHelper.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Deploy',
+ queryId: 0n,
+ },
+ )
+
+ const usdtClaimHelperUsdtRewardJettonWalletAddress = await usdtRewardJettonMaster.getWalletAddress(usdtClaimHelper.address);
+ await usdtClaimHelper.send(
+ deployer.getSender(),
+ {
+ value: toNano("0.05"),
+ },
+ {
+
+ $$type: 'SetJettonWalletAddress',
+ newAddress: usdtClaimHelperUsdtRewardJettonWalletAddress,
+ },
+ )
+ const tayClaimHelperTayRewardJettonWalletAddress = await tayRewardJettonMaster.getWalletAddress(tayClaimHelper.address);
+ await tayClaimHelper.send(
+ deployer.getSender(),
+ {
+ value: toNano("0.05"),
+ },
+ {
+
+ $$type: 'SetJettonWalletAddress',
+ newAddress: tayClaimHelperTayRewardJettonWalletAddress,
+ },
+ )
+
+ const setUsdtClaimable = await jettonVault.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'ConfigureClaimableConfiguration',
+ originJettonAddress: usdtRewardJettonMaster.address,
+ jettonWalletAddress: jettonVaultUsdtWalletAddress,
+ targetBeneficiary: jettonVaultUsdtWalletAddress,
+ claimType: 0n,
+ claimHelper: usdtClaimHelper.address,
+ },
+ );
+ expect(setUsdtClaimable.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: jettonVault.address,
+ success: true,
+ });
+
+ const setTayClaimable = await jettonVault.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'ConfigureClaimableConfiguration',
+ originJettonAddress: tayRewardJettonMaster.address,
+ jettonWalletAddress: jettonVaultTayWalletAddress,
+ targetBeneficiary: timeVestingMaster.address,
+ claimType: 1n,
+ claimHelper: tayClaimHelper.address,
+ },
+ );
+ expect(setTayClaimable.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: jettonVault.address,
+ success: true,
+ });
+
+ // const mapping = await jettonVault.getAllClaimableJettonMapping();
+ // console.log(mapping);
+ });
+
+ describe('claim USDT & TAY rewards', () => {
+ it('should claim USDT successfully', async () => {
+ await usdt.send(
+ deployer.getSender(),
+ {
+ value: toNano('0.05'),
+ },
+ {
+ $$type: 'Mint',
+ queryId: 0n,
+ amount: toNano("1"),
+ receiver: jettonVault.address,
+ },
+ );
+
+ const sender = deployer.getSender();
+ const senderAddress = sender.address;
+ const mintAmount = toNano("1")
+ const mintUsdtRewardJettonResult = await usdtRewardJettonMaster.sendMint(
+ sender,
+ senderAddress,
+ mintAmount,
+ toNano('0.05'),
+ toNano('0.06'),
+ );
+ expect(mintUsdtRewardJettonResult.transactions).toHaveTransaction({
+ from: undefined,
+ oldStatus: "active",
+ endStatus: "active",
+ success: true,
+ });
+
+ const jettonVaultUsdtRewardJettonWalletAddress = await usdtRewardJettonMaster.getWalletAddress(jettonVault.address);
+ const senderUsdtRewardJettonWalletAddress = await usdtRewardJettonMaster.getWalletAddress(senderAddress);
+ const usdtClaimHelperUsdtRewardJettonWalletAddress = await usdtRewardJettonMaster.getWalletAddress(usdtClaimHelper.address);
+ const jettonVaultUsdtWalletAddress = await usdt.getGetWalletAddress(jettonVault.address);
+ const senderUsdtWalletAddress = await usdt.getGetWalletAddress(senderAddress);
+ const usdtClaimHelperWalletAddress = await usdt.getGetWalletAddress(usdtClaimHelper.address);
+
+ const jettonVaultUsdtRewardJettonWallet = constructJettonWalletConfig(jettonVault.address, usdtRewardJettonMaster.address)
+ const usdtClaimHelperUsdtRewardJettonWallet = constructJettonWalletConfig(usdtClaimHelper.address, usdtRewardJettonMaster.address)
+ const senderUsdtRewardJettonWallet = constructJettonWalletConfig(senderAddress, usdtRewardJettonMaster.address)
+
+ const jettonVaultUsdtWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(jettonVaultUsdtWalletAddress));
+ const senderUsdtWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(senderUsdtWalletAddress));
+ const usdtClaimHelperWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(usdtClaimHelperWalletAddress));
+
+ const senderUsdtRewardJettonWalletBalanceBefore = await senderUsdtRewardJettonWallet.getJettonBalance();
+ const usdtClaimHelperUsdtRewardJettonWalletBalanceBefore = await usdtClaimHelperUsdtRewardJettonWallet.getJettonBalance();
+ const jettonVaultUsdtRewardJettonWalletBalanceBefore = await jettonVaultUsdtRewardJettonWallet.getJettonBalance();
+ // let senderUsdtWalletBalanceBefore = 0, usdtClaimHelperWalletBalanceBefore = 0, jettonVaultUsdtWalletBalanceBefore = 0;
+ // const senderUsdtWalletBalanceBefore = (await senderUsdtWallet.getGetWalletData()).balance;
+ // const usdtClaimHelperWalletBalanceBefore = (await usdtClaimHelperWallet.getGetWalletData()).balance;
+ const jettonVaultUsdtWalletBalanceBefore = (await jettonVaultUsdtWallet.getGetWalletData()).balance;
+
+ const claimAmount = toNano("0.3");
+ const claimUsdtRewardResult = await usdtRewardJettonWallet.sendTransfer(
+ sender,
+ toNano('0.35'),
+ toNano('0.35'),
+ usdtClaimHelper.address,
+ claimAmount,
+ beginCell().storeUint(0x7994ff68, 32).endCell(), // opcode: ClaimReward
+ );
+
+ // console.log({
+ // senderAddress: deployer.getSender().address.toString(),
+ // usdt: usdt.address.toString(),
+ // usdtRewardJettonMaster: usdtRewardJettonMaster.address.toString(),
+ // usdtClaimHelper: usdtClaimHelper.address.toString(),
+ // jettonVault: jettonVault.address.toString(),
+ // jettonVaultUsdtRewardJettonWalletAddress: jettonVaultUsdtRewardJettonWalletAddress.toString(),
+ // senderUsdtRewardJettonWalletAddress: senderUsdtRewardJettonWalletAddress.toString(),
+ // usdtClaimHelperUsdtRewardJettonWalletAddress: usdtClaimHelperUsdtRewardJettonWallet.address.toString(),
+ // jettonVaultUsdtWalletAddress: jettonVaultUsdtWalletAddress.toString(),
+ // senderUsdtWalletAddress: senderUsdtWalletAddress.toString(),
+ // usdtClaimHelperWalletAddress: usdtClaimHelperWalletAddress.toString(),
+ // })
+ // external message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: undefined,
+ to: senderAddress,
+ outMessagesCount: 1,
+ success: true,
+ });
+ // op::transfer message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: senderAddress,
+ to: senderUsdtRewardJettonWalletAddress,
+ outMessagesCount: 1,
+ success: true,
+ });
+ // op::internal_transfer message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: senderUsdtRewardJettonWalletAddress,
+ to: usdtClaimHelperUsdtRewardJettonWalletAddress,
+ outMessagesCount: 2,
+ oldStatus: "uninitialized",
+ endStatus: "active",
+ success: true,
+ });
+ // op::transfer_notification message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: usdtClaimHelperUsdtRewardJettonWalletAddress,
+ to: usdtClaimHelper.address,
+ outMessagesCount: 1,
+ success: true,
+ });
+ // op::excesses message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: usdtClaimHelperUsdtRewardJettonWalletAddress,
+ to: senderAddress,
+ outMessagesCount: 0,
+ success: true,
+ })
+ // ClaimReward message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: usdtClaimHelper.address,
+ to: jettonVault.address,
+ outMessagesCount: 1,
+ success: true,
+ })
+ // TokenTransfer message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: jettonVault.address,
+ to: jettonVaultUsdtWalletAddress,
+ outMessagesCount: 1,
+ success: true,
+ })
+ // op::internal_transfer message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: jettonVaultUsdtWalletAddress,
+ to: senderUsdtWalletAddress,
+ oldStatus: "uninitialized",
+ endStatus: "active",
+ outMessagesCount: 1,
+ success: true,
+ })
+ // op::transfer_notification message
+ expect(claimUsdtRewardResult.transactions).toHaveTransaction({
+ from: senderUsdtWalletAddress,
+ to: senderAddress,
+ outMessagesCount: 0,
+ success: true,
+ })
+
+ const jettonVaultUsdtRewardJettonWalletBalance = await jettonVaultUsdtRewardJettonWallet.getJettonBalance();
+ const usdtClaimHelperUsdtRewardJettonWalletBalance = await usdtClaimHelperUsdtRewardJettonWallet.getJettonBalance();
+ const senderUsdtRewardJettonWalletBalance = await senderUsdtRewardJettonWallet.getJettonBalance();
+
+ const senderUsdtWalletBalance = (await senderUsdtWallet.getGetWalletData()).balance;
+ // const usdtClaimHelperWalletBalance = (await usdtClaimHelperWallet.getGetWalletData()).balance;
+ const jettonVaultUsdtWalletBalance = (await jettonVaultUsdtWallet.getGetWalletData()).balance;
+
+ expect(usdtClaimHelperUsdtRewardJettonWalletBalance - usdtClaimHelperUsdtRewardJettonWalletBalanceBefore).toEqual(claimAmount);
+ expect(jettonVaultUsdtRewardJettonWalletBalance - jettonVaultUsdtRewardJettonWalletBalanceBefore).toEqual(toNano("0"));
+ expect(senderUsdtRewardJettonWalletBalanceBefore - senderUsdtRewardJettonWalletBalance).toEqual(claimAmount);
+
+ expect(jettonVaultUsdtWalletBalanceBefore - jettonVaultUsdtWalletBalance).toEqual(claimAmount);
+ expect(senderUsdtWalletBalance).toEqual(claimAmount);
+
+ console.table([
+ {
+ "JettonVault-T-USDT": jettonVaultUsdtRewardJettonWalletBalanceBefore,
+ "ClaimHelper-T-USDT": usdtClaimHelperUsdtRewardJettonWalletBalanceBefore,
+ "Sender-T-USDT": senderUsdtRewardJettonWalletBalanceBefore,
+ "JettonVault-USDT": jettonVaultUsdtWalletBalanceBefore,
+ "ClaimHelper-USDT": 0,
+ "Sender-USDT": 0,
+ },
+ {
+ "JettonVault-T-USDT": jettonVaultUsdtRewardJettonWalletBalance,
+ "ClaimHelper-T-USDT": usdtClaimHelperUsdtRewardJettonWalletBalance,
+ "Sender-T-USDT": senderUsdtRewardJettonWalletBalance,
+ "JettonVault-USDT": jettonVaultUsdtWalletBalance,
+ "ClaimHelper-USDT": 0,
+ "Sender-USDT": senderUsdtWalletBalance,
+ }
+ ])
+ // โโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
+ // โ (index) โ JettonVault-T-USDT โ ClaimHelper-T-USDT โ Sender-T-USDT โ JettonVault-USDT โ ClaimHelper-USDT โ Sender-USDT โ
+ // โโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโค
+ // โ 0 โ 0n โ 0n โ 1000000000n โ 1000000000n โ 0 โ 0 โ
+ // โ 1 โ 0n โ 300000000n โ 700000000n โ 700000000n โ 0 โ 300000000n โ
+ // โโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
+
+ // printTransactionFees(claimUsdtRewardResult.transactions)
+ // โโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโ
+ // โ (index) โ op โ valueIn โ valueOut โ totalFees โ inForwardFee โ outForwardFee โ outActions โ computeFee โ exitCode โ actionCode โ
+ // โโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโค
+ // โ 0 โ 'N/A' โ 'N/A' โ '0.7 TON' โ '0.002061 TON' โ 'N/A' โ '0.000775 TON' โ 1 โ '0.000775 TON' โ 0 โ 0 โ
+ // โ 1 โ '0x3ee943f1' โ '0.7 TON' โ '0.69228 TON' โ '0.004911 TON' โ '0.000517 TON' โ '0.004214 TON' โ 1 โ '0.003506 TON' โ 0 โ 0 โ
+ // โ 2 โ '0xce30d1dc' โ '0.69228 TON' โ '0.662666 TON' โ '0.004357 TON' โ '0.00281 TON' โ '0.001053 TON' โ 2 โ '0.004006 TON' โ 0 โ 0 โ
+ // โ 3 โ '0x4fb8dedc' โ '0.35 TON' โ '0.299348 TON' โ '0.006515 TON' โ '0.000436 TON' โ '0.000681 TON' โ 1 โ '0.006288 TON' โ 0 โ 0 โ
+ // โ 4 โ '0x7d7aec1d' โ '0.312666 TON' โ '0 TON' โ '0.000124 TON' โ '0.000267 TON' โ 'N/A' โ 0 โ '0.000124 TON' โ 0 โ 0 โ
+ // โ 5 โ '0x2daf1323' โ '0.299348 TON' โ '0.05 TON' โ '0.007443 TON' โ '0.000454 TON' โ '0.000709 TON' โ 1 โ '0.007206 TON' โ 0 โ 0 โ
+ // โ 6 โ '0xf8a7ea5' โ '0.05 TON' โ '0.033059 TON' โ '0.011073 TON' โ '0.000473 TON' โ '0.008804 TON' โ 1 โ '0.008138 TON' โ 0 โ 0 โ
+ // โ 7 โ '0x178d4519' โ '0.033059 TON' โ '0.003777 TON' โ '0.007272 TON' โ '0.00587 TON' โ '0.000479 TON' โ 1 โ '0.007112 TON' โ 0 โ 0 โ
+ // โ 8 โ '0xd53276db' โ '0.003777 TON' โ '0 TON' โ '0.000124 TON' โ '0.000319 TON' โ 'N/A' โ 0 โ '0.000124 TON' โ 0 โ 0 โ
+ // โโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโ
+ const totalTransactionFee = sumTransactionsFee(claimUsdtRewardResult.transactions);
+ expect(totalTransactionFee).toBeLessThanOrEqual(0.045076311); // real: 0.044076311
+ });
+
+ it('should claim TAY successfully', async () => {
+ await mintMockTay(tay, jettonVault.address, toNano("1"));
+ const sender = deployer.getSender();
+ const senderAddress = sender.address;
+
+ const mintAmount = toNano("1")
+ const mintTayRewardJettonResult = await tayRewardJettonMaster.sendMint(
+ sender,
+ senderAddress,
+ mintAmount,
+ toNano('0.05'),
+ toNano('0.06'),
+ );
+ expect(mintTayRewardJettonResult.transactions).toHaveTransaction({
+ from: await tayRewardJettonMaster.getWalletAddress(senderAddress),
+ to: senderAddress,
+ oldStatus: "active",
+ endStatus: "active",
+ success: true,
+ });
+
+ const jettonVaultTayRewardJettonWalletAddress = await tayRewardJettonMaster.getWalletAddress(jettonVault.address);
+ const senderTayRewardJettonWalletAddress = await tayRewardJettonMaster.getWalletAddress(senderAddress);
+ const tayClaimHelperTayRewardJettonWalletAddress = await tayRewardJettonMaster.getWalletAddress(tayClaimHelper.address);
+ const jettonVaultTayWalletAddress = await tay.getGetWalletAddress(jettonVault.address);
+ const senderTayWalletAddress = await tay.getGetWalletAddress(senderAddress);
+ const tayClaimHelperWalletAddress = await tay.getGetWalletAddress(tayClaimHelper.address);
+ const timeVestingMasterTayWalletAddress = await tay.getGetWalletAddress(timeVestingMaster.address);
+
+ const jettonVaultTayRewardJettonWallet = constructJettonWalletConfig(jettonVault.address, tayRewardJettonMaster.address)
+ const tayClaimHelperTayRewardJettonWallet = constructJettonWalletConfig(tayClaimHelper.address, tayRewardJettonMaster.address)
+ const senderTayRewardJettonWallet = constructJettonWalletConfig(senderAddress, tayRewardJettonMaster.address)
+ const timeVestingRewardJettonWallet = constructJettonWalletConfig(timeVestingMaster.address, tayRewardJettonMaster.address)
+ const timeVestingMasterTayWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(timeVestingMasterTayWalletAddress));
+
+ const jettonVaultTayWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(jettonVaultTayWalletAddress));
+ // const senderTayWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(senderTayWalletAddress));
+ // const tayClaimHelperWallet = blockchain.openContract(JettonDefaultWallet.fromAddress(tayClaimHelperWalletAddress));
+
+ const senderTayRewardJettonWalletBalanceBefore = await senderTayRewardJettonWallet.getJettonBalance();
+ const tayClaimHelperTayRewardJettonWalletBalanceBefore = await tayClaimHelperTayRewardJettonWallet.getJettonBalance();
+ const jettonVaultTayRewardJettonWalletBalanceBefore = await jettonVaultTayRewardJettonWallet.getJettonBalance();
+
+ // const timeVestingMasterTayWalletBalanceBefore = (await timeVestingMasterTayWallet.getGetWalletData()).balance;
+ const jettonVaultTayWalletBalanceBefore = (await jettonVaultTayWallet.getGetWalletData()).balance;
+
+ const claimAmount = toNano("0.3");
+ // MessageFlow
+ // 1. (external) undefined => senderAddress
+ // 2. (op::transfer) senderAddress => senderTayRewardJettonWalletAddress
+ // 3. (op::internal_transfer) senderTayRewardJettonWalletAddress => tayClaimHelperTayRewardJettonWalletAddress
+ // 4. (op::transfer_notification) tayClaimHelperTayRewardJettonWalletAddress => tayClaimHelper
+ // 5. (op::excesses) tayClaimHelperTayRewardJettonWalletAddress => senderAddress
+ // 6. (ClaimReward) tayClaimHelper => jettonVault
+ // 7. (TokenTransfer) jettonVault => jettonVaultTayWalletAddress
+ // 8. (InternalTransfer) jettonVaultTayWalletAddress => timeVestingMasterTayWallet
+ // 9. (TokenNotification) timeVestingMasterTayWallet => timeVestingMaster
+ // 10.(Excesses) timeVestingMasterTayWallet => senderAddress
+ // 11.(AddLock) timeVestingMaster => senderTimeVestingMasterWalletAddress
+ // 12.(SelfReply) senderTimeVestingMasterWalletAddress => senderAddress
+ const claimTayRewardResult = await tayRewardJettonWallet.sendTransfer(
+ sender,
+ toNano('0.2'),
+ toNano('0.3'),
+ tayClaimHelper.address,
+ claimAmount,
+ beginCell().storeUint(0x7994ff68, 32).endCell(), // opcode: ClaimReward
+ );
+
+ const senderTimeVestingMasterWalletAddress = await timeVestingMaster.getUserTimeVestingAddress(deployer.address);
+ // console.log({
+ // senderAddress: deployer.getSender().address.toString(),
+ // tay: tay.address.toString(),
+ // tayRewardJettonMaster: tayRewardJettonMaster.address.toString(),
+ // tayClaimHelper: tayClaimHelper.address.toString(),
+ // jettonVault: jettonVault.address.toString(),
+ // jettonVaultTayRewardJettonWalletAddress: jettonVaultTayRewardJettonWalletAddress.toString(),
+ // senderTayRewardJettonWalletAddress: senderTayRewardJettonWalletAddress.toString(),
+ // tayClaimHelperTayRewardJettonWalletAddress: tayClaimHelperTayRewardJettonWallet.address.toString(),
+ // jettonVaultTayWalletAddress: jettonVaultTayWalletAddress.toString(),
+ // senderTayWalletAddress: senderTayWalletAddress.toString(),
+ // tayClaimHelperWalletAddress: tayClaimHelperWalletAddress.toString(),
+ // timeVestingMaster: timeVestingMaster.address.toString(),
+ // senderTimeVestingMasterWalletAddress: senderTimeVestingMasterWalletAddress.toString(),
+ // timeVestingMasterTayWalletAddress: timeVestingMasterTayWalletAddress.toString(),
+ // })
+
+ // 1. external message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: undefined,
+ to: senderAddress,
+ outMessagesCount: 1,
+ success: true,
+ });
+ // 2. op::transfer message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: senderAddress,
+ to: senderTayRewardJettonWalletAddress,
+ outMessagesCount: 1,
+ success: true,
+ });
+ // 3. op::internal_transfer message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: senderTayRewardJettonWalletAddress,
+ to: tayClaimHelperTayRewardJettonWalletAddress,
+ outMessagesCount: 2,
+ oldStatus: "uninitialized",
+ endStatus: "active",
+ success: true,
+ });
+ // 4. op::transfer_notification message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: tayClaimHelperTayRewardJettonWalletAddress,
+ to: tayClaimHelper.address,
+ outMessagesCount: 1,
+ success: true,
+ });
+ // 5. op::excesses message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: tayClaimHelperTayRewardJettonWalletAddress,
+ to: senderAddress,
+ outMessagesCount: 0,
+ success: true,
+ })
+ // 6. ClaimReward message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: tayClaimHelper.address,
+ to: jettonVault.address,
+ outMessagesCount: 1,
+ success: true,
+ })
+ // 7. TokenTransfer message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: jettonVault.address,
+ to: jettonVaultTayWalletAddress,
+ outMessagesCount: 1,
+ success: true,
+ })
+ // 8. InternalTransfer message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: jettonVaultTayWalletAddress,
+ to: timeVestingMasterTayWalletAddress,
+ oldStatus: "uninitialized",
+ endStatus: "active",
+ outMessagesCount: 2,
+ success: true,
+ })
+ // 9. TokenNotification message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: timeVestingMasterTayWalletAddress,
+ to: timeVestingMaster.address,
+ success: true,
+ });
+ // 10. Excesses message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: timeVestingMasterTayWalletAddress,
+ to: senderAddress,
+ success: true,
+ });
+ // 11. AddLock message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: timeVestingMaster.address,
+ to: senderTimeVestingMasterWalletAddress,
+ success: true,
+ });
+ // 12. SelfReply message
+ expect(claimTayRewardResult.transactions).toHaveTransaction({
+ from: senderTimeVestingMasterWalletAddress,
+ to: senderAddress,
+ success: true,
+ });
+
+ const deployerTimeVesting = blockchain.openContract(
+ TimeVesting.fromAddress(await timeVestingMaster.getUserTimeVestingAddress(senderAddress)),
+ );
+ const lockedTAY = await deployerTimeVesting.getTimeVestingData();
+ console.log(lockedTAY);
+
+ // const jettonVaultTayRewardJettonWalletBalance = await jettonVaultTayRewardJettonWallet.getJettonBalance();
+ const tayClaimHelperTayRewardJettonWalletBalance = await tayClaimHelperTayRewardJettonWallet.getJettonBalance();
+ const senderTayRewardJettonWalletBalance = await senderTayRewardJettonWallet.getJettonBalance();
+
+ // const senderTayWalletBalance = (await senderTayWallet.getGetWalletData()).balance;
+ // const tayClaimHelperWalletBalance = (await tayClaimHelperWallet.getGetWalletData()).balance;
+ const jettonVaultTayWalletBalance = (await jettonVaultTayWallet.getGetWalletData()).balance;
+ // const timeVestingMasterTayWalletBalance = (await tay.getGetWalletAddress(timeVestingMasterTayWallet))
+ const timeVestingMasterTayWalletBalance = (await timeVestingMasterTayWallet.getGetWalletData()).balance
+ // (await tay.getGetWalletData(timeVestingMasterTayWallet)).balance;
+
+ expect(tayClaimHelperTayRewardJettonWalletBalance - tayClaimHelperTayRewardJettonWalletBalanceBefore).toEqual(claimAmount);
+ expect(jettonVaultTayWalletBalanceBefore - jettonVaultTayWalletBalance).toEqual(claimAmount);
+ expect(senderTayRewardJettonWalletBalanceBefore - senderTayRewardJettonWalletBalance).toEqual(claimAmount);
+ expect(timeVestingMasterTayWalletBalance).toEqual(claimAmount);
+
+ console.table([
+ {
+ "JettonVault-T-TAY": jettonVaultTayRewardJettonWalletBalanceBefore,
+ "ClaimHelper-T-TAY": tayClaimHelperTayRewardJettonWalletBalanceBefore,
+ "Sender-T-TAY": senderTayRewardJettonWalletBalanceBefore,
+ "JettonVault-TAY": jettonVaultTayWalletBalanceBefore,
+ "ClaimHelper-TAY": 0,
+ "Sender-TAY": 0,
+ "TimeVesting-TAY": 0,
+ },
+ {
+ "JettonVault-T-TAY": 0,
+ "ClaimHelper-T-TAY": tayClaimHelperTayRewardJettonWalletBalance,
+ "Sender-T-TAY": senderTayRewardJettonWalletBalance,
+ "JettonVault-TAY": jettonVaultTayWalletBalance,
+ "ClaimHelper-TAY": 0,
+ "Sender-TAY": 0,
+ "TimeVesting-TAY": timeVestingMasterTayWalletBalance,
+ }
+ ])
+ // โโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
+ // โ (index) โ JettonVault-T-TAY โ ClaimHelper-T-TAY โ Sender-T-TAY โ JettonVault-TAY โ ClaimHelper-TAY โ Sender-TAY โ TimeVesting-TAY โ
+ // โโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโค
+ // โ 0 โ 0n โ 0n โ 1000000000n โ 1000000000n โ 0 โ 0 โ 0 โ
+ // โ 1 โ 0 โ 300000000n โ 700000000n โ 700000000n โ 0 โ 0 โ 300000000n โ
+ // โโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ
+
+ // printTransactionFees(claimTayRewardResult.transactions)
+ // โโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโ
+ // โ (index) โ op โ valueIn โ valueOut โ totalFees โ inForwardFee โ outForwardFee โ outActions โ computeFee โ exitCode โ actionCode โ
+ // โโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโค
+ // โ 0 โ 'N/A' โ 'N/A' โ '0.5 TON' โ '0.002061 TON' โ 'N/A' โ '0.000775 TON' โ 1 โ '0.000775 TON' โ 0 โ 0 โ
+ // โ 1 โ '0x3ee943f1' โ '0.5 TON' โ '0.49228 TON' โ '0.004911 TON' โ '0.000517 TON' โ '0.004214 TON' โ 1 โ '0.003506 TON' โ 0 โ 0 โ
+ // โ 2 โ '0xce30d1dc' โ '0.49228 TON' โ '0.462666 TON' โ '0.004357 TON' โ '0.00281 TON' โ '0.001053 TON' โ 2 โ '0.004006 TON' โ 0 โ 0 โ
+ // โ 3 โ '0x4fb8dedc' โ '0.3 TON' โ '0.249348 TON' โ '0.006334 TON' โ '0.000436 TON' โ '0.000681 TON' โ 1 โ '0.006107 TON' โ 0 โ 0 โ
+ // โ 4 โ '0x7d7aec1d' โ '0.162666 TON' โ '0 TON' โ '0.000124 TON' โ '0.000267 TON' โ 'N/A' โ 0 โ '0.000124 TON' โ 0 โ 0 โ
+ // โ 5 โ '0x2daf1323' โ '0.249348 TON' โ '0.240457 TON' โ '0.00833 TON' โ '0.000454 TON' โ '0.000841 TON' โ 1 โ '0.00805 TON' โ 0 โ 0 โ
+ // โ 6 โ '0xf8a7ea5' โ '0.240457 TON' โ '0.223441 TON' โ '0.011098 TON' โ '0.000561 TON' โ '0.008879 TON' โ 1 โ '0.008138 TON' โ 0 โ 0 โ
+ // โ 7 โ '0x178d4519' โ '0.223441 TON' โ '0.194084 TON' โ '0.009068 TON' โ '0.005919 TON' โ '0.001198 TON' โ 2 โ '0.008668 TON' โ 0 โ 0 โ
+ // โ 8 โ '0x7362d09c' โ '0.04 TON' โ '0.02627 TON' โ '0.009667 TON' โ '0.00048 TON' โ '0.006097 TON' โ 1 โ '0.007634 TON' โ 0 โ 0 โ
+ // โ 9 โ '0xd53276db' โ '0.154084 TON' โ '0 TON' โ '0.000124 TON' โ '0.000319 TON' โ 'N/A' โ 0 โ '0.000124 TON' โ 0 โ 0 โ
+ // โ 10 โ '0xf0e97869' โ '0.02627 TON' โ '0.001106 TON' โ '0.004819 TON' โ '0.004065 TON' โ '0.000517 TON' โ 2 โ '0.004647 TON' โ 0 โ 0 โ
+ // โ 11 โ '0x0' โ '0.001106 TON' โ '0 TON' โ '0.000124 TON' โ '0.000345 TON' โ 'N/A' โ 0 โ '0.000124 TON' โ 0 โ 0 โ
+ // โโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโ
+ const totalTransactionFee = sumTransactionsFee(claimTayRewardResult.transactions);
+ expect(totalTransactionFee).toBeLessThanOrEqual(0.062); // real: 0.06101040500000001
+ });
+ });
+});
diff --git a/tests/Pool.Borrow.spec.ts b/tests/Pool.Borrow.spec.ts
index 8d242a7..7a8c8d8 100644
--- a/tests/Pool.Borrow.spec.ts
+++ b/tests/Pool.Borrow.spec.ts
@@ -1,4 +1,4 @@
-import { Blockchain, printTransactionFees, SandboxContract, TreasuryContract } from '@ton/sandbox';
+import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { Address, beginCell, Dictionary, fromNano, toNano } from '@ton/core';
import { Pool, storeBorrowToken, storeBorrowTon } from '../wrappers/Pool';
import '@ton/test-utils';
@@ -143,7 +143,7 @@ describe('Pool', () => {
});
const totalTransactionFee = sumTransactionsFee(result.transactions);
- expect(totalTransactionFee).toBeLessThanOrEqual(0.11); // real: 0.10973918099999999
+ expect(totalTransactionFee).toBeLessThanOrEqual(0.1105); // real: 0.11042544299999998
const userAccountContract = blockchain.openContract(userAccountAddress);
const accountData = await userAccountContract.getAccount();
diff --git a/tests/Pool.spec.ts b/tests/Pool.spec.ts
index 2e0e8b1..081f56a 100644
--- a/tests/Pool.spec.ts
+++ b/tests/Pool.spec.ts
@@ -111,9 +111,9 @@ describe('Pool', () => {
success: true,
});
- // TODO: the aToken calculated from Atoken.fromInit and pool.getCalculateATokenAddress is different!!! why?
- // const aToken = blockchain.openContract(await AToken.fromInit(pool.address, contents.aTokenContent, reserveAddress))
+ const aTokenCalculated = blockchain.openContract(await AToken.fromInit(pool.address, contents.aTokenContent, reserveAddress))
const aToken = blockchain.openContract(AToken.fromAddress(reserveConfiguration.aTokenAddress));
+ expect(aTokenCalculated.address.toString()).toEqual(aToken.address.toString());
expect((await aToken.getOwner()).toString()).toEqual(pool.address.toString());
expect((await aToken.getGetPoolData()).pool.toString()).toEqual(pool.address.toString());
expect((await aToken.getGetPoolData()).asset.toString()).toEqual(reserveAddress.toString());
diff --git a/tests/RewardJetton.spec.ts b/tests/RewardJetton.spec.ts
new file mode 100644
index 0000000..254bfe2
--- /dev/null
+++ b/tests/RewardJetton.spec.ts
@@ -0,0 +1,283 @@
+import '@ton/test-utils';
+import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
+import { Cell, toNano } from '@ton/core';
+import { buildOnchainMetadata } from '../scripts/utils';
+import { JettonWallet } from '../wrappers/JettonWallet';
+import { RewardJettonMaster } from '../wrappers/RewardJettonMaster';
+import { compile } from '@ton/blueprint';
+
+describe('RewardJetton', () => {
+ let blockchain: Blockchain;
+ let deployer: SandboxContract;
+ let sampleRewardJettonMaster: SandboxContract;
+ let sampleJettonWallet: SandboxContract;
+
+ beforeEach(async () => {
+ blockchain = await Blockchain.create();
+ deployer = await blockchain.treasury('deployer');
+ const jettonParams = {
+ name: 'SampleJetton',
+ description: 'Sample Jetton for testing purposes',
+ decimals: '9',
+ image: 'https://ipfs.io/ipfs/bafybeicn7i3soqdgr7dwnrwytgq4zxy7a5jpkizrvhm5mv6bgjd32wm3q4/welcome-to-IPFS.jpg',
+ symbol: 'SAM',
+ };
+
+ // It's the largest value I can use for max_supply in the tests
+ let content = buildOnchainMetadata(jettonParams);
+
+ const walletCode = await compile('JettonWallet');
+ const masterCode = await compile('RewardJettonMaster');
+ sampleRewardJettonMaster = blockchain.openContract(RewardJettonMaster.createFromConfig(
+ {
+ admin: deployer.address,
+ content,
+ walletCode,
+ },
+ masterCode
+ ));
+ sampleJettonWallet = blockchain.openContract(JettonWallet.createFromConfig(
+ {
+ owner: deployer.address,
+ minter: sampleRewardJettonMaster.address,
+ walletCode,
+ },
+ walletCode
+ ));
+
+ const deployResult = await sampleRewardJettonMaster.sendDeploy(
+ deployer.getSender(),
+ toNano('0.05'),
+ );
+
+ expect(deployResult.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: sampleRewardJettonMaster.address,
+ deploy: true,
+ success: true,
+ });
+ });
+
+ describe("should run", () => {
+ it("should deploy jetton master and jetton wallet successfully", async () => {
+ const jettonData = await sampleRewardJettonMaster.getJettonData();
+ expect(jettonData.totalSupply).toEqual(0n);
+ expect(jettonData.mintable).toEqual(true);
+ expect(jettonData.adminAddress.toString()).toEqual(deployer.getSender().address.toString());
+ expect(jettonData.content).toEqual(jettonData.content);
+
+ const calculatedWalletAddress = await sampleRewardJettonMaster.getWalletAddress(deployer.address);
+ expect(calculatedWalletAddress.toString()).toEqual(sampleJettonWallet.address.toString());
+ })
+ })
+
+ // NOTE: discovery is disabled in RewardJettonMaster
+ // describe('discover', () => {
+ // it('should discover wallet address successfully', async () => {
+ // const receiverAddress = (await blockchain.createWallets(1))[0].address;
+
+ // // should be failed due to insufficient funds
+ // const result = await sampleRewardJettonMaster.sendDiscovery(
+ // deployer.getSender(),
+ // receiverAddress,
+ // true,
+ // toNano('0.01'),
+ // );
+ // expect(result.transactions).toHaveTransaction({
+ // to: sampleRewardJettonMaster.address,
+ // inMessageBounced: false,
+ // inMessageBounceable: true,
+ // success: false,
+ // exitCode: Errors.insufficient_discovery_fee,
+ // });
+ // expect(result.transactions).toHaveTransaction({
+ // from: sampleRewardJettonMaster.address,
+ // inMessageBounced: true,
+ // success: true,
+ // });
+
+ // // should be successful now due to sufficient funds
+ // const result2 = await sampleRewardJettonMaster.sendDiscovery(
+ // deployer.getSender(),
+ // receiverAddress,
+ // true,
+ // toNano('0.1'),
+ // );
+
+ // // provide_wallet_address message
+ // expect(result2.transactions).toHaveTransaction({
+ // to: sampleRewardJettonMaster.address,
+ // success: true,
+ // })
+ // // take_wallet_address message
+ // expect(result2.transactions).toHaveTransaction({
+ // from: sampleRewardJettonMaster.address,
+ // success: true,
+ // });
+ // });
+ // });
+
+ describe('mint', () => {
+ it('should mint token successfully (mint -> internal_transfer -> transfer_notification)', async () => {
+ const receiverAddress = (await blockchain.createWallets(1))[0].address;
+ const result = await sampleRewardJettonMaster.sendMint(
+ deployer.getSender(),
+ receiverAddress,
+ toNano('0.05'),
+ toNano('0.05'),
+ toNano('0.06'),
+ );
+ const receiverWalletAddress = await sampleRewardJettonMaster.getWalletAddress(receiverAddress);
+ const balance = await blockchain.openContract(
+ JettonWallet.createFromAddress(receiverWalletAddress),
+ ).getJettonBalance();
+ expect(balance).toEqual(toNano('0.05'));
+
+ // mint message
+ expect(result.transactions).toHaveTransaction({
+ from: undefined,
+ oldStatus: "active",
+ endStatus: "active",
+ success: true,
+ });
+
+ // internal transfer message
+ expect(result.transactions).toHaveTransaction({
+ from: sampleRewardJettonMaster.address,
+ to: receiverWalletAddress,
+ oldStatus: "uninitialized",
+ endStatus: "active",
+ inMessageBounceable: true,
+ success: true,
+ });
+
+ // transfer_notification Message
+ expect(result.transactions).toHaveTransaction({
+ from: receiverWalletAddress,
+ to: receiverAddress,
+ oldStatus: "active",
+ endStatus: "active",
+ inMessageBounceable: false,
+ success: true,
+ });
+ });
+
+ // TODO: Implement mint_batch in FunC contract
+ // it('should mint batch of tokens successfully', async () => {
+ // blockchain.verbosity = {
+ // print: true,
+ // blockchainLogs: true,
+ // vmLogs: 'vm_logs_full',
+ // debugLogs: true,
+ // }
+ // const wallets = await blockchain.createWallets(1);
+ // const records = Dictionary.empty(Dictionary.Keys.Address(), Dictionary.Values.BigUint(256));
+ // wallets.forEach(wallet => records.set(wallet.address, toNano('0.66')));
+
+ // const result = await sampleRewardJettonMaster.sendMintBatch(
+ // deployer.getSender(),
+ // records,
+ // );
+
+ // const supply = await sampleRewardJettonMaster.getJettonData();
+ // expect(supply.totalSupply).toEqual(toNano('0.1'));
+
+ // expect(result.transactions).toHaveTransaction({
+ // from: sampleRewardJettonMaster.address,
+ // success: false,
+ // });
+ // })
+ })
+
+
+ describe('transfer', () => {
+ it("should transfer token successfully", async () => {
+ const sender = deployer.getSender();
+ const senderAddress = sender.address;
+ const amount = toNano("1")
+ const result = await sampleRewardJettonMaster.sendMint(
+ sender,
+ senderAddress,
+ amount,
+ toNano('0.05'),
+ toNano('0.06'),
+ );
+
+ const senderWalletAddress = await sampleRewardJettonMaster.getWalletAddress(senderAddress);
+ const balance = await blockchain.openContract(JettonWallet.createFromAddress(senderWalletAddress),).getJettonBalance();
+ expect(balance).toEqual(toNano('1'));
+
+ expect(result.transactions).toHaveTransaction({
+ from: deployer.address,
+ to: sampleRewardJettonMaster.address,
+ success: true,
+ });
+
+ const receiverAddress = (await blockchain.createWallets(1))[0].address;
+ const receiverWalletAddress = await sampleRewardJettonMaster.getWalletAddress(receiverAddress);
+ const receiverWallet = blockchain.openContract(JettonWallet.createFromAddress(receiverWalletAddress))
+ const transferAmount = toNano("0.3")
+ const transferResult = await sampleJettonWallet.sendTransfer(
+ sender,
+ toNano('0.05'),
+ toNano('0.05'),
+ receiverAddress,
+ transferAmount,
+ Cell.EMPTY,
+ );
+
+ // console.log({
+ // senderAddress: senderAddress.toString(),
+ // receiverAddress: receiverAddress.toString(),
+ // senderWalletAddress: senderWalletAddress.toString(),
+ // receiverWalletAddress: receiverWalletAddress.toString(),
+ // sampleRewardJettonMaster: sampleRewardJettonMaster.address.toString(),
+ // })
+
+ // external message
+ expect(transferResult.transactions).toHaveTransaction({
+ from: undefined,
+ to: senderAddress,
+ oldStatus: "active",
+ endStatus: "active",
+ outMessagesCount: 1,
+ success: true,
+ });
+
+ // op::transfer message
+ expect(transferResult.transactions).toHaveTransaction({
+ from: senderAddress,
+ to: senderWalletAddress,
+ outMessagesCount: 1,
+ success: true,
+ });
+
+ // op::transfer message
+ expect(transferResult.transactions).toHaveTransaction({
+ from: senderWalletAddress,
+ to: receiverWalletAddress,
+ outMessagesCount: 2, // op::transfer_notification & op::excesses
+ oldStatus: "uninitialized",
+ endStatus: "active",
+ success: true,
+ });
+
+ // op::transfer_notification message
+ expect(transferResult.transactions).toHaveTransaction({
+ from: receiverWalletAddress,
+ to: receiverAddress,
+ success: true,
+ });
+
+ // op::excesses message
+ expect(transferResult.transactions).toHaveTransaction({
+ from: receiverWalletAddress,
+ to: senderAddress,
+ success: true,
+ });
+
+ const newWalletData = await receiverWallet.getJettonBalance();
+ expect(newWalletData).toEqual(transferAmount);
+ });
+ })
+});
diff --git a/tests/SampleJetton.spec.ts b/tests/SampleJetton.spec.ts
index 95a5b72..e940839 100644
--- a/tests/SampleJetton.spec.ts
+++ b/tests/SampleJetton.spec.ts
@@ -1,5 +1,5 @@
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
-import { toNano } from '@ton/core';
+import { Cell, toNano } from '@ton/core';
import '@ton/test-utils';
import { SampleJetton } from '../build/SampleJetton/tact_SampleJetton';
import { buildOnchainMetadata } from '../scripts/utils';
@@ -92,4 +92,67 @@ describe('SampleJetton', () => {
expect(walletData.balance).toEqual(1000000000n);
});
});
+
+ // TODO: Fix Exit Code `5` (Integer out of expected range.)
+ // describe('transfer', () => {
+ // it("should transfer token successfully", async () => {
+ // const sender = (await blockchain.createWallets(1))[0];
+ // const senderAddress = sender.address;
+ // const amount = toNano("1")
+ // const result = await sampleJetton.send(
+ // deployer.getSender(),
+ // {
+ // value: toNano('0.05'),
+ // },
+ // {
+ // $$type: 'Mint',
+ // queryId: 0n,
+ // amount: amount,
+ // receiver: senderAddress,
+ // },
+ // );
+ // expect(result.transactions).toHaveTransaction({
+ // from: deployer.address,
+ // to: sampleJetton.address,
+ // success: true,
+ // });
+
+ // const senderSampleJettonWalletAddress = await sampleJetton.getGetWalletAddress(senderAddress);
+ // const senderSampleJettonWallet = blockchain.openContract(
+ // JettonDefaultWallet.fromAddress(senderSampleJettonWalletAddress),
+ // );
+ // const walletData = await senderSampleJettonWallet.getGetWalletData();
+ // expect(walletData.balance).toEqual(amount);
+
+ // const receiver = (await blockchain.createWallets(2))[1];
+ // const receiverAddress = receiver.address;
+ // const transferAmount = toNano("0.3")
+ // const transferResult = await senderSampleJettonWallet.send(
+ // sender.getSender(),
+ // {
+ // value: toNano('0.05'),
+ // },
+ // {
+ // $$type: 'TokenTransfer',
+ // queryId: 0n,
+ // amount: transferAmount,
+ // destination: receiverAddress,
+ // response_destination: receiverAddress,
+ // custom_payload: null,
+ // forward_ton_amount: toNano('0.15'),
+ // forward_payload: Cell.EMPTY,
+ // },
+ // );
+
+ // expect(transferResult.transactions).toHaveTransaction({
+ // from: senderSampleJettonWalletAddress,
+ // to: senderAddress,
+ // success: true,
+ // });
+
+ // const newWalletData = await senderSampleJettonWallet.getGetWalletData();
+ // expect(newWalletData.balance).toEqual(amount - transferAmount - BigInt(1));
+
+ // });
+ // })
});
diff --git a/wrappers/ClaimHelper.ts b/wrappers/ClaimHelper.ts
new file mode 100644
index 0000000..bb82c9a
--- /dev/null
+++ b/wrappers/ClaimHelper.ts
@@ -0,0 +1 @@
+export * from '../build/ClaimHelper/tact_ClaimHelper';
diff --git a/wrappers/JettonConstants.ts b/wrappers/JettonConstants.ts
new file mode 100644
index 0000000..86751da
--- /dev/null
+++ b/wrappers/JettonConstants.ts
@@ -0,0 +1,30 @@
+export abstract class Op {
+ static transfer = 0x3ee943f1;
+ static transfer_notification = 0x0626b4be;
+ static internal_transfer = 0xce30d1dc;
+ static excesses = 0x7d7aec1d;
+ static burn = 0xbae7fba1;
+ static burn_notification = 0x894844ca;
+
+ static mint = 0xecad15c4;
+ static mint_batch = 0x4fb31204;
+ static change_admin = 3;
+ static change_content = 4;
+
+ static provide_wallet_address = 0xe450e86a;
+ static take_wallet_address = 0x3331a011;
+}
+
+export abstract class Errors {
+ static invalid_op = 709;
+ static not_admin = 73;
+ static unouthorized_burn = 74;
+ static insufficient_discovery_fee = 75;
+ static wrong_op = 0xffff;
+ static not_owner = 705;
+ static not_enough_ton = 709;
+ static not_enough_gas = 707;
+ static not_valid_wallet = 707;
+ static wrong_workchain = 333;
+ static balance_error = 706;
+}
\ No newline at end of file
diff --git a/wrappers/JettonMaster.ts b/wrappers/JettonMaster.ts
new file mode 100644
index 0000000..7125071
--- /dev/null
+++ b/wrappers/JettonMaster.ts
@@ -0,0 +1,244 @@
+import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode, toNano, internal as internal_relaxed, storeMessageRelaxed, Dictionary } from '@ton/core';
+
+import { Op } from './JettonConstants';
+
+export type JettonMasterContent = {
+ type: 0 | 1,
+ uri: string
+};
+
+export type JettonMasterConfig = { admin: Address; content: Cell; walletCode: Cell };
+
+export function jettonMasterConfigToCell(config: JettonMasterConfig): Cell {
+ return beginCell()
+ .storeCoins(0)
+ .storeAddress(config.admin)
+ .storeRef(config.content)
+ .storeRef(config.walletCode)
+ .endCell();
+}
+
+export function jettonContentToCell(content: JettonMasterContent) {
+ return beginCell()
+ .storeUint(content.type, 8)
+ .storeStringTail(content.uri) //Snake logic under the hood
+ .endCell();
+}
+
+export class JettonMaster implements Contract {
+ constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) { }
+
+ static createFromAddress(address: Address) {
+ return new JettonMaster(address);
+ }
+
+ static createFromConfig(config: JettonMasterConfig, code: Cell, workchain = 0) {
+ const data = jettonMasterConfigToCell(config);
+ const init = { code, data };
+ return new JettonMaster(contractAddress(workchain, init), init);
+ }
+
+ async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
+ await provider.internal(via, {
+ value,
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: beginCell().endCell(),
+ });
+ }
+
+ protected static jettonInternalTransfer(jetton_amount: bigint,
+ forward_ton_amount: bigint,
+ response_addr?: Address,
+ query_id: number | bigint = 0) {
+ return beginCell()
+ .storeUint(Op.internal_transfer, 32)
+ .storeUint(query_id, 64)
+ .storeCoins(jetton_amount)
+ .storeAddress(null)
+ .storeAddress(response_addr)
+ .storeCoins(forward_ton_amount)
+ .storeBit(false)
+ .endCell();
+
+ }
+
+ // if (op == op::mint) {
+ // throw_unless(73, equal_slices(sender_address, admin_address)); ;; only admin can mint - Wrapper Contract
+
+ // slice to_address = in_msg_body~load_msg_addr();
+ // int amount = in_msg_body~load_coins();
+ // cell master_msg = in_msg_body~load_ref(); ;; load a reference message
+
+ // slice master_msg_cs = master_msg.begin_parse();
+ // master_msg_cs~skip_bits(32 + 64); ;; op + query_id
+ // int jetton_amount = master_msg_cs~load_coins();
+
+ // mint_tokens(to_address, jetton_wallet_code, amount, master_msg);
+ // save_data(total_supply + jetton_amount, admin_address, content, jetton_wallet_code);
+ // return ();
+ // }
+ static mintMessage(from: Address, to: Address, jetton_amount: bigint, forward_ton_amount: bigint, total_ton_amount: bigint, query_id: number | bigint = 0) {
+ const mintMsg = beginCell().storeUint(Op.internal_transfer, 32)
+ .storeUint(0, 64)
+ .storeCoins(jetton_amount)
+ .storeAddress(null)
+ .storeAddress(from) // Response addr
+ .storeCoins(forward_ton_amount)
+ .storeMaybeRef(null)
+ .endCell();
+
+ return beginCell().storeUint(Op.mint, 32).storeUint(query_id, 64) // op, queryId
+ .storeAddress(to)
+ .storeCoins(total_ton_amount)
+ .storeCoins(jetton_amount)
+ .storeRef(mintMsg)
+ .endCell();
+ }
+ async sendMint(provider: ContractProvider, via: Sender, to: Address, jetton_amount: bigint, forward_ton_amount: bigint, total_ton_amount: bigint) {
+ if (total_ton_amount <= forward_ton_amount) {
+ throw new Error("Total ton amount should be > forward amount");
+ }
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: JettonMaster.mintMessage(this.address, to, jetton_amount, forward_ton_amount, total_ton_amount),
+ value: total_ton_amount + toNano('0.015'),
+ });
+ }
+
+ // if (op == op::batch_mint) {
+ // throw_unless(73, equal_slices(sender_address, admin_address)); ;; ๅชๆ็ฎก็ๅๅฏไปฅๆน้ mint
+
+ // cell batch_data = in_msg_body~load_ref();
+ // int mint_count = in_msg_body~load_uint(16);
+
+ // int i = 0;
+ // while (i < mint_count) {
+ // slice batch_slice = batch_data.begin_parse();
+
+ // while (~ batch_slice.slice_empty?()) {
+ // slice to_address = batch_slice~load_msg_addr();
+ // int amount = batch_slice~load_coins();
+ // cell master_msg = batch_slice~load_ref();
+
+ // slice master_msg_cs = master_msg.begin_parse();
+ // master_msg_cs~skip_bits(32 + 64); ;; op + query_id
+ // int jetton_amount = master_msg_cs~load_coins();
+
+ // mint_tokens(to_address, jetton_wallet_code, amount, master_msg);
+ // total_supply += jetton_amount;
+
+ // i += 1;
+ // if (i >= mint_count) {
+ // break;
+ // }
+ // }
+
+ // if (batch_slice.slice_refs_empty?()) {
+ // break;
+ // }
+ // batch_data = batch_slice~load_ref();
+ // }
+
+ // save_data(total_supply, admin_address, content, jetton_wallet_code);
+ // return ();
+ // }
+
+ static mintBatchMessage(
+ batch_data: Dictionary,
+ query_id: number | bigint = 0
+ ) {
+ return beginCell().storeUint(Op.mint_batch, 32).storeUint(query_id, 64) // op, queryId
+ .storeDict(batch_data, Dictionary.Keys.Address(), Dictionary.Values.BigInt(257))
+ .storeUint(batch_data.size, 16)
+ .endCell();
+ }
+
+ async sendMintBatch(
+ provider: ContractProvider,
+ via: Sender,
+ batch_data: Dictionary
+ ) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: JettonMaster.mintBatchMessage(batch_data),
+ value: toNano('0.015') * BigInt(batch_data.size),
+ });
+ }
+
+
+ /* provide_wallet_address#e450e86a query_id:uint64 owner_address:MsgAddress include_address:Bool = InternalMsgBody;
+ */
+ static discoveryMessage(owner: Address, include_address: boolean) {
+ return beginCell().storeUint(Op.provide_wallet_address, 32).storeUint(0, 64) // op, queryId
+ .storeAddress(owner).storeBit(include_address)
+ .endCell();
+ }
+
+ async sendDiscovery(provider: ContractProvider, via: Sender, owner: Address, include_address: boolean, value: bigint = toNano('0.1')) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: JettonMaster.discoveryMessage(owner, include_address),
+ value: value,
+ });
+ }
+
+ static changeAdminMessage(newOwner: Address) {
+ return beginCell().storeUint(Op.change_admin, 32).storeUint(0, 64) // op, queryId
+ .storeAddress(newOwner)
+ .endCell();
+ }
+
+ async sendChangeAdmin(provider: ContractProvider, via: Sender, newOwner: Address) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: JettonMaster.changeAdminMessage(newOwner),
+ value: toNano("0.05"),
+ });
+ }
+ static changeContentMessage(content: Cell) {
+ return beginCell().storeUint(Op.change_content, 32).storeUint(0, 64) // op, queryId
+ .storeRef(content)
+ .endCell();
+ }
+
+ async sendChangeContent(provider: ContractProvider, via: Sender, content: Cell) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: JettonMaster.changeContentMessage(content),
+ value: toNano("0.05"),
+ });
+ }
+ async getWalletAddress(provider: ContractProvider, owner: Address): Promise {
+ const res = await provider.get('get_wallet_address', [{ type: 'slice', cell: beginCell().storeAddress(owner).endCell() }])
+ return res.stack.readAddress()
+ }
+
+ async getJettonData(provider: ContractProvider) {
+ let res = await provider.get('get_jetton_data', []);
+ let totalSupply = res.stack.readBigNumber();
+ let mintable = res.stack.readBoolean();
+ let adminAddress = res.stack.readAddress();
+ let content = res.stack.readCell();
+ let walletCode = res.stack.readCell();
+ return {
+ totalSupply,
+ mintable,
+ adminAddress,
+ content,
+ walletCode
+ };
+ }
+
+ async getTotalSupply(provider: ContractProvider) {
+ let res = await this.getJettonData(provider);
+ return res.totalSupply;
+ }
+ async getAdminAddress(provider: ContractProvider) {
+ let res = await this.getJettonData(provider);
+ return res.adminAddress;
+ }
+ async getContent(provider: ContractProvider) {
+ let res = await this.getJettonData(provider);
+ return res.content;
+ }
+}
\ No newline at end of file
diff --git a/wrappers/JettonVault.ts b/wrappers/JettonVault.ts
new file mode 100644
index 0000000..af182c8
--- /dev/null
+++ b/wrappers/JettonVault.ts
@@ -0,0 +1 @@
+export * from '../build/JettonVault/tact_JettonVault';
diff --git a/wrappers/JettonWallet.ts b/wrappers/JettonWallet.ts
new file mode 100644
index 0000000..0d9db2f
--- /dev/null
+++ b/wrappers/JettonWallet.ts
@@ -0,0 +1,73 @@
+import { Address, beginCell, Builder, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
+
+export type JettonWalletConfig = {
+ owner: Address;
+ minter: Address;
+ walletCode: Cell;
+};
+
+export function jettonWalletConfigToCell(config: JettonWalletConfig): Cell {
+ return beginCell()
+ .storeCoins(0)
+ .storeAddress(config.owner)
+ .storeAddress(config.minter)
+ .storeRef(config.walletCode)
+ .endCell();
+}
+
+export class JettonWallet implements Contract {
+ constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) { }
+
+ static createFromAddress(address: Address) {
+ return new JettonWallet(address);
+ }
+
+ static createFromConfig(config: JettonWalletConfig, code: Cell, workchain = 0) {
+ const data = jettonWalletConfigToCell(config);
+ const init = { code, data };
+ return new JettonWallet(contractAddress(workchain, init), init);
+ }
+
+ async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
+ await provider.internal(via, {
+ value,
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: beginCell().endCell(),
+ });
+ }
+
+ async sendTransfer(
+ provider: ContractProvider,
+ via: Sender,
+ value: bigint,
+ forwardValue: bigint,
+ recipient: Address,
+ amount: bigint,
+ forwardPayload: Cell
+ ) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: beginCell()
+ .storeUint(0x3ee943f1, 32)
+ .storeUint(0, 64)
+ .storeCoins(amount)
+ .storeAddress(recipient)
+ .storeAddress(via.address)
+ .storeUint(0, 1)
+ .storeCoins(forwardValue)
+ .storeUint(1, 1)
+ .storeRef(forwardPayload)
+ .endCell(),
+ value: value + forwardValue,
+ });
+ }
+
+ async getJettonBalance(provider: ContractProvider) {
+ let state = await provider.getState();
+ if (state.state.type !== 'active') {
+ return 0n;
+ }
+ let res = await provider.get('get_wallet_data', []);
+ return res.stack.readBigNumber();
+ }
+}
diff --git a/wrappers/RewardJettonMaster.ts b/wrappers/RewardJettonMaster.ts
new file mode 100644
index 0000000..a2c919b
--- /dev/null
+++ b/wrappers/RewardJettonMaster.ts
@@ -0,0 +1,190 @@
+import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode, toNano, internal as internal_relaxed, storeMessageRelaxed, Dictionary } from '@ton/core';
+
+import { Op } from './JettonConstants';
+
+export type RewardJettonMasterContent = {
+ type: 0 | 1,
+ uri: string
+};
+
+export type RewardJettonMasterConfig = { admin: Address; content: Cell; walletCode: Cell };
+
+export function RewardJettonMasterConfigToCell(config: RewardJettonMasterConfig): Cell {
+ return beginCell()
+ .storeCoins(0)
+ .storeAddress(config.admin)
+ .storeRef(config.content)
+ .storeRef(config.walletCode)
+ .endCell();
+}
+
+export function jettonContentToCell(content: RewardJettonMasterContent) {
+ return beginCell()
+ .storeUint(content.type, 8)
+ .storeStringTail(content.uri) //Snake logic under the hood
+ .endCell();
+}
+
+export class RewardJettonMaster implements Contract {
+ constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) { }
+
+ static createFromAddress(address: Address) {
+ return new RewardJettonMaster(address);
+ }
+
+ static createFromConfig(config: RewardJettonMasterConfig, code: Cell, workchain = 0) {
+ const data = RewardJettonMasterConfigToCell(config);
+ const init = { code, data };
+ return new RewardJettonMaster(contractAddress(workchain, init), init);
+ }
+
+ async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
+ await provider.internal(via, {
+ value,
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: beginCell().endCell(),
+ });
+ }
+
+ protected static jettonInternalTransfer(jetton_amount: bigint,
+ forward_ton_amount: bigint,
+ response_addr?: Address,
+ query_id: number | bigint = 0) {
+ return beginCell()
+ .storeUint(Op.internal_transfer, 32)
+ .storeUint(query_id, 64)
+ .storeCoins(jetton_amount)
+ .storeAddress(null)
+ .storeAddress(response_addr)
+ .storeCoins(forward_ton_amount)
+ .storeBit(false)
+ .endCell();
+ }
+
+ static mintMessage(from: Address, to: Address, jetton_amount: bigint, forward_ton_amount: bigint, total_ton_amount: bigint, query_id: number | bigint = 0) {
+ const mintMsg = beginCell().storeUint(Op.internal_transfer, 32)
+ .storeUint(0, 64)
+ .storeCoins(jetton_amount)
+ .storeAddress(null)
+ .storeAddress(from) // Response addr
+ .storeCoins(forward_ton_amount)
+ .storeMaybeRef(null)
+ .endCell();
+
+ return beginCell().storeUint(Op.mint, 32).storeUint(query_id, 64) // op, queryId
+ .storeAddress(to)
+ .storeCoins(total_ton_amount)
+ .storeCoins(jetton_amount)
+ .storeRef(mintMsg)
+ .endCell();
+ }
+ async sendMint(provider: ContractProvider, via: Sender, to: Address, jetton_amount: bigint, forward_ton_amount: bigint, total_ton_amount: bigint) {
+ if (total_ton_amount <= forward_ton_amount) {
+ throw new Error("Total ton amount should be > forward amount");
+ }
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: RewardJettonMaster.mintMessage(this.address, to, jetton_amount, forward_ton_amount, total_ton_amount),
+ value: total_ton_amount + toNano('0.015'),
+ });
+ }
+
+ static mintBatchMessage(
+ batch_data: Dictionary,
+ query_id: number | bigint = 0
+ ) {
+ return beginCell().storeUint(Op.mint_batch, 32).storeUint(query_id, 64) // op, queryId
+ .storeUint(batch_data.size, 16)
+ .storeDict(batch_data, Dictionary.Keys.Address(), Dictionary.Values.BigInt(257))
+ .endCell();
+ }
+
+ async sendMintBatch(
+ provider: ContractProvider,
+ via: Sender,
+ batch_data: Dictionary
+ ) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: RewardJettonMaster.mintBatchMessage(batch_data),
+ value: toNano('0.015') * BigInt(batch_data.size),
+ });
+ }
+
+
+ /* provide_wallet_address#e450e86a query_id:uint64 owner_address:MsgAddress include_address:Bool = InternalMsgBody;
+ */
+ static discoveryMessage(owner: Address, include_address: boolean) {
+ return beginCell().storeUint(Op.provide_wallet_address, 32).storeUint(0, 64) // op, queryId
+ .storeAddress(owner).storeBit(include_address)
+ .endCell();
+ }
+
+ async sendDiscovery(provider: ContractProvider, via: Sender, owner: Address, include_address: boolean, value: bigint = toNano('0.1')) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: RewardJettonMaster.discoveryMessage(owner, include_address),
+ value: value,
+ });
+ }
+
+ static changeAdminMessage(newOwner: Address) {
+ return beginCell().storeUint(Op.change_admin, 32).storeUint(0, 64) // op, queryId
+ .storeAddress(newOwner)
+ .endCell();
+ }
+
+ async sendChangeAdmin(provider: ContractProvider, via: Sender, newOwner: Address) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: RewardJettonMaster.changeAdminMessage(newOwner),
+ value: toNano("0.05"),
+ });
+ }
+ static changeContentMessage(content: Cell) {
+ return beginCell().storeUint(Op.change_content, 32).storeUint(0, 64) // op, queryId
+ .storeRef(content)
+ .endCell();
+ }
+
+ async sendChangeContent(provider: ContractProvider, via: Sender, content: Cell) {
+ await provider.internal(via, {
+ sendMode: SendMode.PAY_GAS_SEPARATELY,
+ body: RewardJettonMaster.changeContentMessage(content),
+ value: toNano("0.05"),
+ });
+ }
+ async getWalletAddress(provider: ContractProvider, owner: Address): Promise {
+ const res = await provider.get('get_wallet_address', [{ type: 'slice', cell: beginCell().storeAddress(owner).endCell() }])
+ return res.stack.readAddress()
+ }
+
+ async getJettonData(provider: ContractProvider) {
+ let res = await provider.get('get_jetton_data', []);
+ let totalSupply = res.stack.readBigNumber();
+ let mintable = res.stack.readBoolean();
+ let adminAddress = res.stack.readAddress();
+ let content = res.stack.readCell();
+ let walletCode = res.stack.readCell();
+ return {
+ totalSupply,
+ mintable,
+ adminAddress,
+ content,
+ walletCode
+ };
+ }
+
+ async getTotalSupply(provider: ContractProvider) {
+ let res = await this.getJettonData(provider);
+ return res.totalSupply;
+ }
+ async getAdminAddress(provider: ContractProvider) {
+ let res = await this.getJettonData(provider);
+ return res.adminAddress;
+ }
+ async getContent(provider: ContractProvider) {
+ let res = await this.getJettonData(provider);
+ return res.content;
+ }
+}
\ No newline at end of file