Browse Source

update_commit

master
AlexG 2 years ago
parent
commit
71b6aa4220
  1. 2
      package.json
  2. 218
      sources/jetton.deploy.ts
  3. 5
      sources/jetton.spec.ts
  4. 2
      sources/jetton.tact
  5. 37
      sources/utils/jetton-helpers.ts

2
package.json

@ -23,7 +23,7 @@
"ton-crypto": "^3.2.0",
"ton-emulator": "^1.2.0",
"ton-nodejs": "^1.4.3",
"ton-tact": "^0.5.0",
"ton-tact": "^0.8.7",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"

218
sources/jetton.deploy.ts

@ -1,20 +1,204 @@
import { contractAddress, toNano } from "ton";
import { packAdd, SampleTactContract_init } from "./output/sample_SampleTactContract";
import { beginCell, contractAddress, toNano, TonClient, TonClient4, Address, WalletContractV4, internal, fromNano} from "ton";
import {mnemonicToPrivateKey} from "ton-crypto";
import { packAdd, init } from "./output/jetton_SampleJetton";
import { printAddress, printDeploy, printHeader } from "./utils/print";
import { randomAddress } from "./utils/randomAddress";
import {SampleJetton_errors} from "./output/jetton_SampleJetton";
(async () => { //need to changes for jetton
// Parameters
// let owner = randomAddress(0, 'some-owner'); // Replace owner with your address
// let packed = packAdd({ $$type: 'Add', amount: 10n }); // Replace if you want another message used
// let init = await SampleTactContract_init(owner);
// let address = contractAddress({ workchain: 0, initialCode: init.code, initialData: init.data });
// let deployAmount = toNano(10);
// let testnet = true;
//
// // Print basics
// printHeader('SampleTactContract');
// printAddress(address);
// printDeploy(init, deployAmount, packed, testnet);
})();
(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
const client4 = new TonClient4({
endpoint: "https://sandbox-v4.tonhubapi.com"
})
// 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 = "multiply voice predict admit hockey fringe flat bike napkin child quote piano year cloud bundle lunch....";
// 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
let balance: bigint = await contract.getBalance();
// Generate define owner of Jetton contract
let owner = Address.parse('kQDND6yHEzKB82ZGRn58aY9Tt_69Ie_uz73e2VuuJ3fVVcxf');
// Create content Cell
let content = beginCell().storeUint()
// Compute init for deployment
let init = await SampleJetton.init(owner, content);
// send a message on new address contract to deploy it
let seqno: number = await contract.getSeqno();
console.log('🛠Preparing new outgoing massage from deployment wallet. Seqno = ', seqno);
console.log('Current deployment wallet balance = ', fromNano(balance).toString(), '💎TON');
await contract.sendTransfer({
seqno,
secretKey,
messages: [internal({
value: deployAmount,
to: destination_address,
init: {
code : init.code,
data : init.data
},
body: 'Deploy'
})]
});
console.log('======deployment message sent to ', destination_address, ' ======');
})();
export type JettonMetaDataKeys = "name" | "description" | "image" | "symbol";
const jettonOnChainMetadataSpec: {
[key in JettonMetaDataKeys]: "utf8" | "ascii" | undefined;
} = {
name: "utf8",
description: "utf8",
image: "ascii",
symbol: "utf8",
};
const sha256 = (str: string) => {
const sha = new Sha256();
sha.update(str);
return Buffer.from(sha.digestSync());
};
export function buildTokenMetadataCell(data: { [s: string]: string | undefined }): Cell {
const KEYLEN = 256;
const dict = beginDict(KEYLEN);
Object.entries(data).forEach(([k, v]: [string, string | undefined]) => {
if (!jettonOnChainMetadataSpec[k as JettonMetaDataKeys])
throw new Error(`Unsupported onchain key: ${k}`);
if (v === undefined || v === "") return;
let bufferToStore = Buffer.from(v, jettonOnChainMetadataSpec[k as JettonMetaDataKeys]);
const CELL_MAX_SIZE_BYTES = Math.floor((1023 - 8) / 8);
const rootCell = new Cell();
rootCell.bits.writeUint8(SNAKE_PREFIX);
let currentCell = rootCell;
while (bufferToStore.length > 0) {
currentCell.bits.writeBuffer(bufferToStore.slice(0, CELL_MAX_SIZE_BYTES));
bufferToStore = bufferToStore.slice(CELL_MAX_SIZE_BYTES);
if (bufferToStore.length > 0) {
const newCell = new Cell();
currentCell.refs.push(newCell);
currentCell = newCell;
}
}
dict.storeRef(sha256(k), rootCell);
});
return beginCell().storeInt(ONCHAIN_CONTENT_PREFIX, 8).storeDict(dict.endDict()).endCell();
}
export function parseTokenMetadataCell(contentCell: Cell): {
[s in JettonMetaDataKeys]?: string;
} {
// Note that this relies on what is (perhaps) an internal implementation detail:
// "ton" library dict parser converts: key (provided as buffer) => BN(base10)
// and upon parsing, it reads it back to a BN(base10)
// tl;dr if we want to read the map back to a JSON with string keys, we have to convert BN(10) back to hex
const toKey = (str: string) => new BN(str, "hex").toString(10);
const KEYLEN = 256;
const contentSlice = contentCell.beginParse();
if (contentSlice.readUint(8).toNumber() !== ONCHAIN_CONTENT_PREFIX)
throw new Error("Expected onchain content marker");
const dict = contentSlice.readDict(KEYLEN, (s) => {
const buffer = Buffer.from("");
const sliceToVal = (s: Slice, v: Buffer, isFirst: boolean) => {
s.toCell().beginParse();
if (isFirst && s.readUint(8).toNumber() !== SNAKE_PREFIX)
throw new Error("Only snake format is supported");
v = Buffer.concat([v, s.readRemainingBytes()]);
if (s.remainingRefs === 1) {
v = sliceToVal(s.readRef(), v, false);
}
return v;
};
return sliceToVal(s.readRef(), buffer, true);
});
const res: { [s in JettonMetaDataKeys]?: string } = {};
Object.keys(jettonOnChainMetadataSpec).forEach((k) => {
const val = dict
.get(toKey(sha256(k).toString("hex")))
?.toString(jettonOnChainMetadataSpec[k as JettonMetaDataKeys]);
if (val) res[k as JettonMetaDataKeys] = val;
});
return res;
}
export function jettonMinterInitData(
owner: Address,
metadata: { [s in JettonMetaDataKeys]?: string }
): Cell {
return beginCell()
.storeCoins(0)
.storeAddress(owner)
.storeRef(buildTokenMetadataCell(metadata))
.storeRef(JETTON_WALLET_CODE)
.endCell();
}
// return the init Cell of the contract storage (according to load_data() contract method)
export function initData() {
return jettonMinterInitData(jettonParams.owner, {
name: jettonParams.name,
symbol: jettonParams.symbol,
image: jettonParams.image,
description: jettonParams.description,
});
}
// return the op that should be sent to the contract on deployment, can be "null" to send an empty message
export function initMessage() {
return null; // TODO?
}
// optional end-to-end sanity test for the actual on-chain contract to see it is actually working on-chain
export async function postDeployTest(
walletContract: WalletContract,
secretKey: Buffer,
contractAddress: Address
) {
const call = await walletContract.client.callGetMethod(contractAddress, "get_jetton_data");
console.log(
parseTokenMetadataCell(
Cell.fromBoc(Buffer.from(call.stack[3][1].bytes, "base64").toString("hex"))[0]
)
);
}

5
sources/jetton.spec.ts

@ -1,4 +1,4 @@
import { toNano } from "ton-core";
import { toNano, beginCell } from "ton-core";
import { ContractSystem } from "ton-emulator";
import {SampleJetton, SampleJetton_init} from './output/jetton_SampleJetton';
@ -8,6 +8,9 @@ describe('jetton', () => {
// Create jetton
let system = await ContractSystem.create();
let owner = system.treasure('owner');
let content = beginCell().storeUint(1, 16).storeSlice('fff').endCell();
let contract = system.open(await SampleJetton.fromInit(owner.address, null));
let tracker = system.track(contract.address);

2
sources/jetton.tact

@ -6,6 +6,8 @@ message Mint {
contract SampleJetton with Jetton {
// storage#_ balance:Grams owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage
totalSupply: Int as coins;
owner: Address;
content: Cell?;

37
sources/utils/jetton-helpers.ts

@ -0,0 +1,37 @@
import { Cell, beginCell, Address, beginDict, Slice, toNano } from "ton";
let contentSlice2 : Slice;
enum OPS {
ChangeAdmin = 3,
ReplaceMetadata = 4,
Mint = 21,
InternalTransfer = 0x178d4519,
Transfer = 0xf8a7ea5,
Burn = 0x595f07bc,
}
export type JettonMetaDataKeys =
| "name"
| "description"
| "image"
| "symbol"
| "image_data"
| "decimals";
async function parseJettonOffchainMetadata(contentSlice: Slice): Promise<{
metadata: { [s in JettonMetaDataKeys]?: string };
isIpfs: boolean;
}> {
const jsonURI = contentSlice
.loadBits(await () => (contentSlice.remainingBits()))
.toString("ascii")
.replace("ipfs://", "https://ipfs.io/ipfs/");
return {
metadata: (await axios.get(jsonURI)).data,
isIpfs: /(^|\/)ipfs[.:]/.test(jsonURI),
};
}
Loading…
Cancel
Save