Skip to content

Commit

Permalink
Merge pull request #11 from 0kenx/main
Browse files Browse the repository at this point in the history
Wallet discovery
  • Loading branch information
howardpen9 authored Oct 18, 2023
2 parents 5a770b7 + d650459 commit 7b8d9d0
Show file tree
Hide file tree
Showing 28 changed files with 7,285 additions and 1,856 deletions.
4,665 changes: 4,665 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,26 @@
"@ton-community/test-utils": "^0.3.0",
"ton-emulator": "^2.1.1",

"ton-core": "^0.49.0",
"ton": "^13.4.1",
"ton-core": "^0.51.0",
"ton": "^13.6.0",

"dotenv": "^16.3.1",
"@aws-crypto/sha256-js": "^4.0.0",
"@tact-lang/compiler": "^1.1.0",
"@tact-lang/compiler": "^1.1.3",
"@tact-lang/emulator": "^3.2.2",
"@types/jest": "^29.2.4",
"@types/node": "^18.11.14",
"@types/jest": "^29.5.3",
"@types/node": "^20.2.5",
"@types/qs": "^6.9.7",
"base64url": "^3.0.1",
"enquirer": "^2.3.6",
"jest": "^29.3.1",
"jest": "^29.6.2",
"open": "^8.4.0",
"prando": "^6.0.1",
"prettier": "^2.5.1",
"prettier": "^2.8.8",
"qs": "^6.11.0",
"ton-crypto": "^3.2.0",
"ts-jest": "^29.0.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
"typescript": "^5.1.6"
}
}
8 changes: 4 additions & 4 deletions sources/contract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe("contract", () => {

it("should mint successfully", async () => {
const player = await blockchain.treasury("player");
const totalSupplyBefore = (await token.getGetJettonData()).totalSupply;
const totalSupplyBefore = (await token.getGetJettonData()).total_supply;
const mintAmount = toNano(100);
const Mint: Mint = {
$$type: "Mint",
Expand All @@ -65,7 +65,7 @@ describe("contract", () => {
success: true,
});

const totalSupplyAfter = (await token.getGetJettonData()).totalSupply;
const totalSupplyAfter = (await token.getGetJettonData()).total_supply;
expect(totalSupplyBefore + mintAmount).toEqual(totalSupplyAfter);

const playerWallet = await token.getGetWalletAddress(player.address);
Expand Down Expand Up @@ -94,7 +94,7 @@ describe("contract", () => {
// Transfer tokens from sender's wallet to receiver's wallet
const transferMessage: TokenTransfer = {
$$type: "TokenTransfer",
queryId: 1n,
query_id: 1n,
amount: transferAmount,
destination: receiver.address,
response_destination: sender.address,
Expand Down Expand Up @@ -136,7 +136,7 @@ describe("contract", () => {
let burnAmount = toNano(10);
const burnMessage: TokenBurn = {
$$type: "TokenBurn",
queryId: 0n,
query_id: 0n,
amount: burnAmount,
response_destination: deployer.address,
custom_payload: beginCell().endCell(),
Expand Down
266 changes: 13 additions & 253 deletions sources/contract.tact
Original file line number Diff line number Diff line change
@@ -1,284 +1,44 @@
import "@stdlib/ownable";
import "./message";
import "./jetton";

message Mint {
amount: Int;
receiver: Address;
}

contract SampleJetton with Jetton {
totalSupply: Int as coins;
total_supply: Int as coins;
owner: Address;
content: Cell;
mintable: Bool;

maxSupply: Int as coins; // Extract parameter we set here. The Jetton Standards doesn't have this parameter.
max_supply: Int as coins; // Extract parameter we set here. The Jetton Standards doesn't have this parameter.

init(owner: Address, content: Cell, maxSupply: Int) {
self.totalSupply = 0;
init(owner: Address, content: Cell, max_supply: Int) {
self.total_supply = 0;
self.owner = owner;
self.mintable = true;
self.content = content;
self.maxSupply = maxSupply;
self.max_supply = max_supply;
}

receive(msg: Mint) {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not Owner");
require(self.mintable, "Can't Mint Anymore");
require(ctx.sender == self.owner, "Not owner");
require(self.mintable, "Not mintable");
require(self.total_supply + msg.amount <= self.max_supply, "Max supply exceeded");
self.mint(msg.receiver, msg.amount, self.owner); // (to, amount, response_destination)
}

receive("Mint: 100") { // Public Minting
let ctx: Context = context();
require(self.mintable, "Can't Mint Anymore");
require(self.mintable, "Not mintable");
require(self.total_supply + 100 <= self.max_supply, "Max supply exceeded");
self.mint(ctx.sender, 100, self.owner); // 🔴
}

receive("Owner: MintClose") {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not Owner");
require(ctx.sender == self.owner, "Not owner");
self.mintable = false;
}
}

// ============================================================================================================ //
@interface("org.ton.jetton.master")
trait Jetton with Ownable {

totalSupply: Int; // Already set initially
mintable: Bool;
owner: Address;
content: Cell;

maxSupply: Int;

receive(msg: TokenUpdateContent) {
self.requireOwner(); // Allow changing content only by owner
self.content = msg.content; // Update content
}

receive(msg: TokenBurnNotification) {
self.requireSenderAsWalletOwner(msg.response_destination); // Check wallet
self.totalSupply = self.totalSupply - msg.amount; // Update supply
if (msg.response_destination != null) { // Cashback
send(SendParameters{
to: msg.response_destination,
value: 0,
bounce: false,
mode: SendRemainingValue + SendIgnoreErrors,
body: TokenExcesses{ queryId: msg.queryId }.toCell()
});
}
}

// Private Methods //
// @to The Address receive the Jetton token after minting
// @amount The amount of Jetton token being minted
// @response_destination The previous owner address
fun mint(to: Address, amount: Int, response_destination: Address) {
require(self.totalSupply + amount <= self.maxSupply, "The total supply will be overlapping.");
self.totalSupply = self.totalSupply + amount; // Update total supply

let winit: StateInit = self.getJettonWalletInit(to); // Create message
send(SendParameters{
to: contractAddress(winit),
value: 0,
bounce: true,
mode: SendRemainingValue,
body: TokenTransferInternal{
queryId: 0,
amount: amount,
from: myAddress(),
response_destination: response_destination,
forward_ton_amount: 0,
forward_payload: emptySlice()
}.toCell(),
code: winit.code,
data: winit.data
});
}

fun requireSenderAsWalletOwner(owner: Address) {
let ctx: Context = context();
let winit: StateInit = self.getJettonWalletInit(owner);
require(contractAddress(winit) == ctx.sender, "Invalid sender");
}

virtual fun getJettonWalletInit(address: Address): StateInit {
return initOf JettonDefaultWallet(myAddress(), address);
}

// ====== Get Methods ====== //
get fun get_jetton_data(): JettonData {
let code: Cell = self.getJettonWalletInit(myAddress()).code;
return JettonData{
totalSupply: self.totalSupply,
mintable: self.mintable,
owner: self.owner,
content: self.content,
walletCode: code
};
}

get fun get_wallet_address(owner: Address): Address {
let winit: StateInit = self.getJettonWalletInit(owner);
return contractAddress(winit);
}
}

// ============================================================ //
@interface("org.ton.jetton.wallet")
contract JettonDefaultWallet {
const minTonsForStorage: Int = ton("0.01");
const gasConsumption: Int = ton("0.01");

balance: Int;
owner: Address;
master: Address;

init(master: Address, owner: Address) {
self.balance = 0;
self.owner = owner;
self.master = master;
}

receive(msg: TokenTransfer) { // 0xf8a7ea5
let ctx: Context = context(); // Check sender
require(ctx.sender == self.owner, "Invalid sender");

// Gas checks
let fwdFee: Int = ctx.readForwardFee() + ctx.readForwardFee();
let final: Int = 2 * self.gasConsumption + self.minTonsForStorage + fwdFee;
require(ctx.value > min(final, ton("0.01")), "Invalid value!!");

// Update balance
self.balance = self.balance - msg.amount;
require(self.balance >= 0, "Invalid balance");

let init: StateInit = initOf JettonDefaultWallet(self.master, msg.destination);
let walletAddress: Address = contractAddress(init);
send(SendParameters{
to: walletAddress,
value: 0,
mode: SendRemainingValue,
bounce: true,
body: TokenTransferInternal{
queryId: msg.queryId,
amount: msg.amount,
from: self.owner,
response_destination: msg.response_destination,
forward_ton_amount: msg.forward_ton_amount,
forward_payload: msg.forward_payload
}.toCell(),
code: init.code,
data: init.data
});
}

receive(msg: TokenTransferInternal) { // 0x178d4519
let ctx: Context = context();

if (ctx.sender != self.master) {
let sinit: StateInit = initOf JettonDefaultWallet(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");

// Get value for gas
let msgValue: Int = self.msgValue(ctx.value);
let fwdFee: Int = ctx.readForwardFee();
msgValue = msgValue - msg.forward_ton_amount - fwdFee;

// 0x7362d09c - notify the new owner of JettonToken that the transfer is complete
if (msg.forward_ton_amount > 0) {
send(SendParameters{
to: self.owner,
value: msg.forward_ton_amount,
mode: SendPayGasSeparately + SendIgnoreErrors,
bounce: false,
body: TokenNotification {
queryId: msg.queryId,
amount: msg.amount,
from: msg.from,
forward_payload: msg.forward_payload
}.toCell()
});
}

// 0xd53276db -- Cashback to the original Sender
if (msg.response_destination != null) {
send(SendParameters {
to: msg.response_destination,
value: msgValue,
bounce: false,
body: TokenExcesses {
queryId: msg.queryId
}.toCell(),
mode: SendIgnoreErrors
});
}
}

receive(msg: TokenBurn) {
let ctx: Context = context();
require(ctx.sender == self.owner, "Invalid sender"); // Check sender

self.balance = self.balance - msg.amount; // Update balance
require(self.balance >= 0, "Invalid balance");

let fwdFee: Int = ctx.readForwardFee(); // Gas checks
require(ctx.value > fwdFee + 2 * self.gasConsumption + self.minTonsForStorage, "Invalid value - Burn");

// Burn tokens
send(SendParameters{
to: self.master,
value: 0,
mode: SendRemainingValue,
bounce: true,
body: TokenBurnNotification{
queryId: msg.queryId,
amount: msg.amount,
response_destination: self.owner
}.toCell()
});
}

get fun msgValue(value: Int): Int {
let msgValue: Int = value;
let tonBalanceBeforeMsg: Int = myBalance() - msgValue;
let storageFee: Int = self.minTonsForStorage - min(tonBalanceBeforeMsg, self.minTonsForStorage);
msgValue = msgValue - (storageFee + self.gasConsumption);
return msgValue;
}

bounced(src: bounced<TokenTransferInternal>) {
self.balance = self.balance + src.amount;
}

bounced(src: bounced<TokenBurnNotification>) {
self.balance = self.balance + src.amount;
}

get fun get_wallet_data(): JettonWalletData {
return JettonWalletData{
balance: self.balance,
owner: self.owner,
master: self.master,
walletCode: (initOf JettonDefaultWallet(self.master, self.owner)).code
};
}

// 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");
// self.balance = self.balance + jettonAmount; // Update balance
// }
}
}
Loading

0 comments on commit 7b8d9d0

Please sign in to comment.