Browse Source

Started the contract, wrote deploy and interactions, tests (do not pass yet)

master
Lev 2 years ago
parent
commit
a19790a8de
  1. 54
      sources/jetton.deploy.ts
  2. 27
      sources/jetton.spec.ts
  3. 20
      sources/jetton.tact
  4. 4
      sources/output/jetton_JettonDefaultWallet.ts
  5. 29
      sources/tests/__snapshots__/jetton.spec.ts.snap
  6. 36
      sources/tests/jetton.spec.ts
  7. 10
      sources/tests/scenario.ts
  8. 42
      sources/utils/config.ts
  9. 19
      sources/utils/helpers.ts
  10. 83
      sources/utils/interactions.ts
  11. 3
      sources/utils/rmlogs.sh
  12. 2
      sources/wallet.tact
  13. 2
      yarn.lock

54
sources/jetton.deploy.ts

@ -1,57 +1,22 @@
import { beginCell, contractAddress, toNano, TonClient, TonClient4, Address, WalletContractV4, internal, fromNano, Cell} from "ton"; import { beginCell, contractAddress, toNano, TonClient, TonClient4, Address, WalletContractV4, internal, fromNano, Cell} from "ton";
import {mnemonicToPrivateKey} from "ton-crypto"; import {mnemonicToPrivateKey} from "ton-crypto";
import {buildOnchainMetadata} from "./utils/jetton-helpers"; import {buildOnchainMetadata} from "./utils/helpers";
import {TONB} from "./output/jetton_TONB"; import {TONB} from "./output/jetton_TONB";
import {client, wallet_data, workchain, owner, jettonParams, default_content} from "./utils/config";
(async () => { //need changes for jetton (async () => { //need changes for jetton
//create client for testnet Toncenter API
const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
apiKey: 'bb38df0c2756c66e2ab49f064e2484ec444b01244d2bd49793bd5b58f61ae3d2'
})
//create client for testnet sandboxv4 API - alternative endpoint //create client for testnet sandboxv4 API - alternative endpoint
const client4 = new TonClient4({ const client4 = new TonClient4({
endpoint: "https://sandbox-v4.tonhubapi.com" endpoint: "https://sandbox-v4.tonhubapi.com"
}) })
let {my_wallet, secretKey} = await wallet_data();
// Insert your test wallet's 24 words, make sure you have some test Toncoins on its balance. Every deployment spent 0.5 test toncoin.
let mnemonics = "basic security merge opera inject core melody polar become force cool glance history order warfare consider company slim twice balcony scare shoot winner rude";
// read more about wallet apps https://ton.org/docs/participate/wallets/apps#tonhub-test-environment
let keyPair = await mnemonicToPrivateKey(mnemonics.split(" "));
let secretKey = keyPair.secretKey;
//workchain = 1 - masterchain (expensive operation cost, validator's election contract works here)
//workchain = 0 - basechain (normal operation cost, user's contracts works here)
let workchain = 0; //we are working in basechain.
//Create deployment wallet contract
let wallet = WalletContractV4.create({ workchain, publicKey: keyPair.publicKey});
let contract = client.open(wallet);
// Get deployment wallet balance // Get deployment wallet balance
let balance: bigint = await contract.getBalance(); let balance: bigint = await my_wallet.getBalance();
// This is example data - Modify these params for your own jetton
// - Data is stored on-chain (except for the image data itself)
const jettonParams = {
name: "TactJet",
description: "This is description of Test tact jetton",
image: "https://ipfs.io/ipfs/QmbPZjC1tuP6ickCCBtoTCQ9gc3RpkbKx7C1LMYQdcLwti" // Image url
};
// Owner should usually be the deploying wallet's address.
let owner = Address.parse('EQAuAiFGgkxoQvBWXjXQcLYb8BW4fO6UJkt6_uCONJ2y5VUk');
// Create content Cell
let content = buildOnchainMetadata(jettonParams);
// Compute init data for deployment // Compute init data for deployment
let init = await TONB.init(owner, content); let init = await TONB.init(owner, default_content);
let destination_address = contractAddress(workchain, init); let destination_address = contractAddress(workchain, init);
@ -61,15 +26,13 @@ import {TONB} from "./output/jetton_TONB";
// send a message on new address contract to deploy it // send a message on new address contract to deploy it
let seqno: number = await contract.getSeqno(); let seqno: number = await my_wallet.getSeqno();
//TL-B mint#01fb345b amount:int257 = Mint
let msg = beginCell().storeBuffer(Buffer.from("01fb345b", "hex")).storeInt(amount, 257).endCell();
console.log('🛠Preparing new outgoing massage from deployment wallet. Seqno = ', seqno); console.log('🛠Preparing new outgoing massage from deployment wallet. Seqno = ', seqno);
console.log('Current deployment wallet balance = ', fromNano(balance).toString(), '💎TON'); console.log('Current deployment wallet balance = ', fromNano(balance).toString(), '💎TON');
console.log('Totally supply for deployed Token = ', supply, ', amount = ', amount.toString()); console.log('Totally supply for deployed Token = ', supply, ', amount = ', amount.toString());
await contract.sendTransfer({ await my_wallet.sendTransfer({
seqno, seqno,
secretKey, secretKey,
messages: [internal({ messages: [internal({
@ -78,8 +41,7 @@ import {TONB} from "./output/jetton_TONB";
init: { init: {
code : init.code, code : init.code,
data : init.data data : init.data
}, }
body: msg
})] })]
}); });
console.log('======deployment message sent to ', destination_address, ' ======'); console.log('======deployment message sent to ', destination_address, ' ======');

27
sources/jetton.spec.ts

@ -1,27 +0,0 @@
import { toNano, beginCell } from "ton";
import { ContractSystem } from "ton-emulator";
import {TONB} from './output/jetton_TONB';
describe('jetton', () => {
it('should deploy', async () => {
// Create jetton
let system = await ContractSystem.create();
let owner = system.treasure('owner');
let contract = system.open(await TONB.fromInit(owner.address, null));
let tracker = system.track(contract.address);
// Mint
await contract.send(owner, { value: toNano(1) }, { $$type: 'Mint', amount: toNano(1000000) });
await system.run();
expect(tracker.events()).toMatchSnapshot();
// Check owner
expect((await contract.getOwner()).toString()).toEqual(owner.address.toString());
// Data
let data = await contract.getGetJettonData();
// console.warn(data);
});
});

20
sources/jetton.tact

@ -4,10 +4,20 @@ import "./wallet";
import "./linker"; import "./linker";
import "./jetton_trait"; import "./jetton_trait";
message Mint { message Deposit {
amount: Int; amount: Int;
} }
message Withdraw {
amount: Int;
}
struct WithdrawalRequests {
addresses: map[Int]Address;
amounts: map[Int]Int;
n_requests: Int;
}
const gas_consumption: Int = ton("0.01"); const gas_consumption: Int = ton("0.01");
const withdraw_gas_consumption: Int = ton("0.05"); const withdraw_gas_consumption: Int = ton("0.05");
@ -32,9 +42,15 @@ contract TONB with Jetton {
self.content = content; self.content = content;
} }
receive(msg: Mint) { receive(msg: Deposit) {
let ctx: Context = context(); let ctx: Context = context();
require(ctx.value >= deposit_gas_consumption, "not enough money for deposit"); require(ctx.value >= deposit_gas_consumption, "not enough money for deposit");
self.mint(ctx.sender, msg.amount, ctx.sender); self.mint(ctx.sender, msg.amount, ctx.sender);
} }
receive(msg: Withdraw) {
let ctx: Context = context();
require(ctx.value >= withdraw_gas_consumption, "not enough money for withdraw");
self.burn(ctx.sender, msg.amount, ctx.sender);
}
} }

4
sources/output/jetton_JettonDefaultWallet.ts

@ -768,9 +768,9 @@ async function JettonDefaultWallet_init(master: Address, owner: Address) {
if (!res.success) { throw Error(res.error); } if (!res.success) { throw Error(res.error); }
if (res.exitCode !== 0 && res.exitCode !== 1) { if (res.exitCode !== 0 && res.exitCode !== 1) {
if (JettonDefaultWallet_errors[res.exitCode]) { if (JettonDefaultWallet_errors[res.exitCode]) {
throw new ComputeError(JettonDefaultWallet_errors[res.exitCode].message, res.exitCode, { logs: res.vmLogs }); throw new ComputeError(JettonDefaultWallet_errors[res.exitCode].message, res.exitCode);
} else { } else {
throw new ComputeError('Exit code: ' + res.exitCode, res.exitCode, { logs: res.vmLogs }); throw new ComputeError('Exit code: ' + res.exitCode, res.exitCode);
} }
} }

29
sources/tests/__snapshots__/jetton.spec.ts.snap

@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`jetton should deploy 1`] = `
[
{
"type": "deploy",
},
{
"message": {
"body": {
"cell": "x{8999B1640000000000000000000000000000000000000000000000000001C6BF526340004_}",
"type": "cell",
},
"bounce": true,
"from": "kQAI-3FJVc_ywSuY4vq0bYrzR7S4Och4y7bTU_i5yLOB3A6P",
"to": "kQCrEWck7tGRuwnumilB-HRzp2v-IxRzWG4Lw0mP_VYl72fg",
"type": "internal",
"value": 1000000000n,
},
"type": "received",
},
{
"gasUsed": 22165n,
"type": "processed",
},
]
`;
exports[`jetton should deploy 2`] = `[]`;

36
sources/tests/jetton.spec.ts

@ -0,0 +1,36 @@
import { toNano, beginCell } from "ton";
import { ContractSystem } from "ton-emulator";
import {TONB} from '../output/jetton_TONB';
import { default_content } from '../utils/config';
import { TONBWallet } from '../output/jetton_TONBWallet';
describe('jetton', () => {
it('should deploy', async () => {
// Create jetton
let system = await ContractSystem.create();
let owner = system.treasure('owner');
let contract = system.open(await TONB.fromInit(owner.address, default_content));
let tracker = system.track(contract.address);
let wallet_contract = system.open(await TONBWallet.fromInit(contract.address, owner.address));
let wallet_tracker = system.track(wallet_contract.address);
// Mint
let res1 = await contract.send(owner, { value: toNano(1) }, { $$type: 'Deposit', amount: toNano(1000000) });
let res = await system.run();
console.log(res1)
console.log(res)
expect(tracker.events()).toMatchSnapshot();
expect(wallet_tracker.events()).toMatchSnapshot();
console.log(await wallet_contract.getGetWalletData());
// Check owner
expect((await contract.getOwner()).toString()).toEqual(owner.address.toString());
// Data
let data = await contract.getGetJettonData();
// console.warn(data);
console.log(tracker.events())
});
});

10
sources/tests/scenario.ts

@ -0,0 +1,10 @@
import { deployTONB, deposit, withdraw } from "../utils/interactions";
import { wallet_data } from '../utils/config';
import { randomAddress, TON } from '../utils/helpers';
(async () => {
let {my_wallet, secretKey} = await wallet_data();
let tonb_addr = await deployTONB();
await deposit(my_wallet, secretKey, 1 * TON(), tonb_addr);
await withdraw(my_wallet, secretKey, 0.5 * TON(), tonb_addr);
})();

42
sources/utils/config.ts

@ -0,0 +1,42 @@
import { TonClient, Address, WalletContractV3R2 } from "ton";
import { mnemonicToPrivateKey } from "ton-crypto";
import { buildOnchainMetadata } from "./helpers";
export const client = new TonClient({
endpoint: 'https://testnet.toncenter.com/api/v2/jsonRPC',
apiKey: 'bb38df0c2756c66e2ab49f064e2484ec444b01244d2bd49793bd5b58f61ae3d2'
})
let mnemonics = "basic security merge opera inject core melody polar become force cool glance history order warfare consider company slim twice balcony scare shoot winner rude";
// read more about wallet apps https://ton.org/docs/participate/wallets/apps#tonhub-test-environment
export async function wallet_data() {
let keyPair = await mnemonicToPrivateKey(mnemonics.split(" "));
let secretKey = keyPair.secretKey;
//Create deployment wallet contract
let wallet_contract = WalletContractV3R2.create({ workchain, publicKey: keyPair.publicKey });
let my_wallet = client.open(wallet_contract);
return { my_wallet, secretKey, keyPair };
}
//workchain = 1 - masterchain (expensive operation cost, validator's election contract works here)
//workchain = 0 - basechain (normal operation cost, user's contracts works here)
export let workchain = 0; //we are working in basechain.
// This is example data - Modify these params for your own jetton
// - Data is stored on-chain (except for the image data itself)
export const jettonParams = {
name: "TactJet",
description: "This is description of Test tact jetton",
image: "https://ipfs.io/ipfs/QmbPZjC1tuP6ickCCBtoTCQ9gc3RpkbKx7C1LMYQdcLwti" // Image url
};
// Owner should usually be the deploying wallet's address.
export let owner = Address.parse('EQAuAiFGgkxoQvBWXjXQcLYb8BW4fO6UJkt6_uCONJ2y5VUk');
// Create content Cell
export let default_content = buildOnchainMetadata(jettonParams);

19
sources/utils/jetton-helpers.ts → sources/utils/helpers.ts

@ -1,6 +1,8 @@
import { Sha256 } from "@aws-crypto/sha256-js"; import { Sha256 } from "@aws-crypto/sha256-js";
import { beginCell, Cell } from "ton"; import { beginCell, Cell, Address } from "ton";
import { Dictionary } from "ton-core"; import { Dictionary } from "ton-core";
import Prando from "prando";
const ONCHAIN_CONTENT_PREFIX = 0x00; const ONCHAIN_CONTENT_PREFIX = 0x00;
const SNAKE_PREFIX = 0x00; const SNAKE_PREFIX = 0x00;
@ -59,4 +61,17 @@ export function buildOnchainMetadata(data: {
.storeInt(ONCHAIN_CONTENT_PREFIX, 8) .storeInt(ONCHAIN_CONTENT_PREFIX, 8)
.storeDict(dict) .storeDict(dict)
.endCell(); .endCell();
} }
export function TON(): number {
return 1000000000;
}
export function randomAddress(seed: string, workchain?: number) {
const random = new Prando(seed);
const hash = Buffer.alloc(32);
for (let i = 0; i < hash.length; i++) {
hash[i] = random.nextInt(0, 255);
}
return new Address(workchain ?? 0, hash);
}

83
sources/utils/interactions.ts

@ -0,0 +1,83 @@
import { Cell, Address, internal, beginCell, contractAddress, toNano, fromNano } from "ton";
import { storeDeposit, storeWithdraw } from "../output/jetton_TONB";
import { TON } from "./helpers";
import { wallet_data, owner, default_content, workchain } from './config';
import {TONB} from "../output/jetton_TONB";
import { TONBWallet } from '../output/jetton_TONBWallet';
export async function sendMessage(wallet: any, secretKey: Buffer, msg: {value: string | bigint, to: Address, bounce?: boolean, init?: {code?: Cell, data?: Cell}, body?: any}) {
let seqno: number = await wallet.getSeqno();
return await wallet.sendTransfer({
seqno,
secretKey,
messages: [internal(msg)]
});
}
export async function deposit(wallet: any, secretKey: Buffer, value_: bigint | number, tonb: Address) {
console.log('📤Sending deposit message to ', tonb, ' with value ', value_, '💎TON');
let value = BigInt(value_);
let msg_body_b = beginCell();
storeDeposit({amount: value as bigint, $$type: 'Deposit'})(msg_body_b);
let msg_body = msg_body_b.endCell();
let msg_value = value as bigint + BigInt(0.1 * TON());
let res = await sendMessage(wallet, secretKey, {value: msg_value, to: tonb, body: msg_body});
console.log(res);
console.log('======deposit message sent ======');
}
export async function withdraw(wallet: any, secretKey: Buffer, value_: bigint | number, tonb: Address) {
let value = BigInt(value_);
let msg_body_b = beginCell();
storeWithdraw({amount: value as bigint, $$type: 'Withdraw'})(msg_body_b);
let msg_body = msg_body_b.endCell();
let msg_value = BigInt(0.1 * TON());
await sendMessage(wallet, secretKey, {value: msg_value, to: tonb, body: msg_body});
}
export async function deployTONB(tonb_content?: any) {
if (!tonb_content) {
tonb_content = default_content;
}
let {my_wallet, secretKey, keyPair} = await wallet_data();
// Get deployment wallet balance
let balance: bigint = await my_wallet.getBalance();
// Compute init data for deployment
let init = await TONB.init(owner, tonb_content);
let destination_address = contractAddress(workchain, init);
let deployAmount = toNano('0.5');
let supply = 500;
let amount = BigInt(supply * Math.pow(10, 9));
// send a message on new address contract to deploy it
let seqno: number = await my_wallet.getSeqno();
console.log('🛠Preparing new outgoing massage from deployment wallet. Seqno = ', seqno, ', wallet = ', my_wallet.address);
console.log('Current deployment wallet balance = ', fromNano(balance).toString(), '💎TON');
// console.log('Totally supply for deployed Token = ', supply, ', amount = ', amount.toString());
let res = await my_wallet.sendTransfer({
seqno,
secretKey,
messages: [internal({
value: deployAmount,
to: destination_address,
init: {
code : init.code,
data : init.data
}
})]
});
console.log('======deployment message sent to ', destination_address, ' ======');
console.log(res);
return destination_address;
}
export async function getAddress(master_: string | Address, owner_: string | Address) {
let master = typeof master_ === 'string' ? Address.parse(master_) : master_;
let owner = typeof owner_ === 'string' ? Address.parse(owner_) : owner_;
return contractAddress(workchain, await TONBWallet.init(master, owner));
}

3
sources/utils/rmlogs.sh

@ -0,0 +1,3 @@
#!/bin/hash
for file in $(find sources/output -type f); do sed -i 's/,\ {\ logs:\ res.vmLogs\ }//g' $file; done
for file in $(find sources/output -type f); do sed -i 's/stringtoreplace//g' $file; done

2
sources/wallet.tact

@ -109,7 +109,7 @@ contract TONBWallet {
// Check sender // Check sender
let ctx: Context = context(); let ctx: Context = context();
require(ctx.sender == self.owner, "Invalid sender"); require(ctx.sender == self.owner || ctx.sender == self.master, "Invalid sender");
// Update balance // Update balance
self.balance = self.balance - msg.amount; self.balance = self.balance - msg.amount;

2
yarn.lock

@ -2618,7 +2618,7 @@ pkg-dir@^4.2.0:
prando@^6.0.1: prando@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.npmjs.org/prando/-/prando-6.0.1.tgz" resolved "https://registry.yarnpkg.com/prando/-/prando-6.0.1.tgz#ffa8de84c2adc4975dd9df37ae4ada0458face53"
integrity sha512-ghUWxQ1T9IJmPu6eshc3VU0OwveUtXQ33ZLXYUcz1Oc5ppKLDXKp0TBDj6b0epwhEctzcQSNGR2iHyvQSn4W5A== integrity sha512-ghUWxQ1T9IJmPu6eshc3VU0OwveUtXQ33ZLXYUcz1Oc5ppKLDXKp0TBDj6b0epwhEctzcQSNGR2iHyvQSn4W5A==
pretty-format@^29.0.0, pretty-format@^29.3.1: pretty-format@^29.0.0, pretty-format@^29.3.1:

Loading…
Cancel
Save