|
|
|
import "@stdlib/ownable";
|
|
|
|
import "./messages";
|
|
|
|
import "./linker";
|
|
|
|
import "./constants";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@interface("org.ton.jetton.wallet")
|
|
|
|
contract TONBWallet {
|
|
|
|
|
|
|
|
const minTonsForStorage: Int = ton("0.01");
|
|
|
|
const gasConsumption: Int = ton("0.01");
|
|
|
|
|
|
|
|
balance: Int;
|
|
|
|
owner: Address;
|
|
|
|
master: Address;
|
|
|
|
blacklisted: Bool = false;
|
|
|
|
linker: Int?;
|
|
|
|
linker_address: Address?;
|
|
|
|
|
|
|
|
init(master: Address, owner: Address) {
|
|
|
|
self.balance = 0;
|
|
|
|
self.owner = owner;
|
|
|
|
self.master = master;
|
|
|
|
}
|
|
|
|
|
|
|
|
receive(msg: TokenTransfer) {
|
|
|
|
require(!self.blacklisted, "Wallet is blacklisted");
|
|
|
|
|
|
|
|
// Check sender
|
|
|
|
let ctx: Context = context();
|
|
|
|
require(ctx.sender == self.owner, "Invalid sender");
|
|
|
|
|
|
|
|
// Update balance
|
|
|
|
self.balance = self.balance - msg.amount;
|
|
|
|
require(self.balance >= 0, "Invalid balance");
|
|
|
|
|
|
|
|
// Gas checks
|
|
|
|
let fwdFee: Int = ctx.readForwardFee();
|
|
|
|
let fwdCount: Int = 1;
|
|
|
|
if (msg.forwardTonAmount > 0) {
|
|
|
|
fwdCount = 2;
|
|
|
|
}
|
|
|
|
require(ctx.value > transfer_gas_consumption, "Invalid value");
|
|
|
|
|
|
|
|
// Send tokens
|
|
|
|
let init: StateInit = initOf TONBWallet(self.master, msg.destination);
|
|
|
|
let walletAddress: Address = contractAddress(init);
|
|
|
|
send(SendParameters{
|
|
|
|
to: walletAddress,
|
|
|
|
value: 0,
|
|
|
|
mode: SendRemainingValue,
|
|
|
|
bounce: true,
|
|
|
|
body: TokenTransferInternal{
|
|
|
|
amount: msg.amount,
|
|
|
|
queryId: msg.queryId,
|
|
|
|
from: self.owner,
|
|
|
|
responseAddress: self.owner,
|
|
|
|
forwardTonAmount: msg.forwardTonAmount,
|
|
|
|
forwardPayload: msg.forwardPayload,
|
|
|
|
setLinker: null,
|
|
|
|
setLinkerAddress: null
|
|
|
|
}.toCell(),
|
|
|
|
code: init.code,
|
|
|
|
data: init.data
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
receive(msg: TokenTransferInternal) {
|
|
|
|
let ctx: Context = context();
|
|
|
|
if(self.blacklisted && ctx.sender != self.master){
|
|
|
|
send(SendParameters{
|
|
|
|
to: self.master,
|
|
|
|
value: 0,
|
|
|
|
mode: SendRemainingValue,
|
|
|
|
body: TokenBurnNotification{
|
|
|
|
queryId: msg.queryId,
|
|
|
|
amount: msg.amount,
|
|
|
|
owner: self.owner,
|
|
|
|
responseAddress: self.owner
|
|
|
|
}.toCell()
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check sender
|
|
|
|
if (self.linker == null) {
|
|
|
|
if (msg.setLinker != null) {
|
|
|
|
self.linker = msg.setLinker;
|
|
|
|
self.linker_address = msg.setLinkerAddress;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ctx.sender != self.master/* && ctx.sender != self.linker_address*/) {
|
|
|
|
let is_from_linker: Bool = false;
|
|
|
|
if (self.linker_address != null) {
|
|
|
|
if (ctx.sender == self.linker_address!!) {
|
|
|
|
is_from_linker = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!is_from_linker) {
|
|
|
|
let sinit: StateInit = initOf TONBWallet(self.master, msg.from);
|
|
|
|
require(contractAddress(sinit) == ctx.sender, "Invalid sender");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update balance
|
|
|
|
self.balance = self.balance + msg.amount;
|
|
|
|
require(self.balance >= 0, "Invalid balance");
|
|
|
|
|
|
|
|
// Adjust value for gas
|
|
|
|
let msgValue: Int = ctx.value;
|
|
|
|
let tonBalanceBeforeMsg: Int = myBalance() - msgValue;
|
|
|
|
let storageFee: Int = self.minTonsForStorage - min(tonBalanceBeforeMsg, self.minTonsForStorage);
|
|
|
|
|
|
|
|
if (self.linker == null) {
|
|
|
|
// request a linker
|
|
|
|
send(SendParameters{
|
|
|
|
to: self.master,
|
|
|
|
value: 0,
|
|
|
|
mode: SendRemainingValue,
|
|
|
|
body: RequestLinker {
|
|
|
|
client: self.owner
|
|
|
|
}.toCell()
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
msgValue = msgValue - (storageFee + self.gasConsumption);
|
|
|
|
|
|
|
|
// Forward ton
|
|
|
|
if (msg.forwardTonAmount > 0) {
|
|
|
|
let fwdFee: Int = ctx.readForwardFee();
|
|
|
|
msgValue = msgValue - (msg.forwardTonAmount + fwdFee);
|
|
|
|
send(SendParameters{
|
|
|
|
to: self.owner,
|
|
|
|
value: msg.forwardTonAmount,
|
|
|
|
bounce: false,
|
|
|
|
body: TokenNotification{
|
|
|
|
queryId: msg.queryId,
|
|
|
|
amount: msg.amount,
|
|
|
|
from: msg.from,
|
|
|
|
forwardPayload: msg.forwardPayload
|
|
|
|
}.toCell()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cashback
|
|
|
|
if (msg.responseAddress != null && msgValue > 0) {
|
|
|
|
send(SendParameters{
|
|
|
|
to: msg.responseAddress!!,
|
|
|
|
value: msgValue,
|
|
|
|
bounce: false,
|
|
|
|
body: TokenExcesses{
|
|
|
|
queryId: msg.queryId
|
|
|
|
}.toCell()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
receive(msg: TokenBurn) {
|
|
|
|
|
|
|
|
// Check sender
|
|
|
|
let ctx: Context = context();
|
|
|
|
require(ctx.sender == self.owner || ctx.sender == self.master || ctx.sender == self.linker_address, "Invalid sender");
|
|
|
|
|
|
|
|
// Update balance
|
|
|
|
self.balance = self.balance - msg.amount;
|
|
|
|
require(self.balance >= 0, "Invalid balance");
|
|
|
|
|
|
|
|
// // Gas checks
|
|
|
|
let fwdFee: Int = ctx.readForwardFee();
|
|
|
|
require(ctx.value > 2 * self.gasConsumption + self.minTonsForStorage, "Invalid value");
|
|
|
|
|
|
|
|
// Burn tokens
|
|
|
|
send(SendParameters{
|
|
|
|
to: self.master,
|
|
|
|
value: 0,
|
|
|
|
mode: SendRemainingValue,
|
|
|
|
bounce: true,
|
|
|
|
body: TokenBurnNotification{
|
|
|
|
queryId: msg.queryId,
|
|
|
|
amount: msg.amount,
|
|
|
|
owner: self.owner,
|
|
|
|
responseAddress: self.owner
|
|
|
|
}.toCell()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
receive(_msg: BlacklistWallet) {
|
|
|
|
// Check sender
|
|
|
|
let ctx: Context = context();
|
|
|
|
require(ctx.sender == self.master || ctx.sender == self.linker_address, "Invalid sender");
|
|
|
|
|
|
|
|
// Blacklist wallet
|
|
|
|
self.blacklisted = true;
|
|
|
|
let amount: Int = self.balance;
|
|
|
|
self.balance = 0;
|
|
|
|
|
|
|
|
send(SendParameters{
|
|
|
|
to: self.master,
|
|
|
|
value: 0,
|
|
|
|
mode: SendRemainingValue,
|
|
|
|
bounce: true,
|
|
|
|
body: TokenBurnNotification{
|
|
|
|
queryId: 0,
|
|
|
|
amount: amount,
|
|
|
|
owner: self.owner,
|
|
|
|
responseAddress: self.owner
|
|
|
|
}.toCell()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
bounced(msg: Slice) {
|
|
|
|
|
|
|
|
// Parse bounced message
|
|
|
|
msg.skipBits(32); // 0xFFFFFFFF
|
|
|
|
let op: Int = msg.loadUint(32);
|
|
|
|
let queryId: Int = msg.loadUint(64);
|
|
|
|
let jettonAmount: Int = msg.loadCoins();
|
|
|
|
require(op == 0x178d4519 || op == 0x7bdd97de, "Invalid bounced message");
|
|
|
|
|
|
|
|
// Update balance
|
|
|
|
self.balance = self.balance + jettonAmount;
|
|
|
|
}
|
|
|
|
|
|
|
|
get fun get_wallet_data(): JettonWalletData {
|
|
|
|
return JettonWalletData{
|
|
|
|
balance: self.balance,
|
|
|
|
owner: self.owner,
|
|
|
|
master: self.master,
|
|
|
|
walletCode: (initOf TONBWallet(self.master, self.owner)).code
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|