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 }; } }