Browse Source

We can now deploy items and have a cli

master
Lev 2 years ago
parent
commit
a028b5c7d3
  1. 63
      bin/cli.js
  2. 8
      build/_build.ts
  3. 12
      build/_deploy.ts
  4. 7
      build/nft-collection.deploy.ts
  5. 2
      contracts/imports/dns-utils.fc
  6. 32
      contracts/main.ts
  7. 27
      package-lock.json
  8. 1
      package.json
  9. 3
      test/creation.spec.ts
  10. 3
      test/signing.spec.ts

63
bin/cli.js

@ -1,13 +1,15 @@
const main = require('../contracts/main');
const subcommand = require('subcommand');
const {Address} = require("ton");
const {keyPairFromSeed, getSecureRandomBytes} = require("ton-crypto");
const {keyPairFromSeed, getSecureRandomBytes, keyPairFromSecretKey} = require("ton-crypto");
const {Base64} = require('@tonconnect/protocol');
const BN = require("bn.js");
const argv = process.argv.slice(2);
let commands = [
{
name: 'sign-message',
name: 'buy-message',
options: [ // cliclopts options
// {
// name: 'loud',
@ -31,12 +33,18 @@ let commands = [
help: 'Secret key, hex-encoded'
}
],
command: function sign_message(args) {
let key = Buffer.from(args.key, 'hex');
let collection = Address.parse(args.collection);
let buyer = Address.parse(args.buyer);
let signature = main.signBuy(args.domain, collection, buyer, key);
console.log(signature);
command: function message(args) {
let collection = Address.parse(args._[1]);
if (args._[2] !== '') {
let buyer = Address.parse(args._[2]);
let key = Buffer.from(args._[3], 'hex');
let signature = main.signBuy(args._[0], collection, buyer, key);
let msg = main.createItem({domain: args._[0], signature})
console.log(Base64.encode(msg.toBoc()) + ' ' + collection.toString());
} else {
let msg = main.createItem({domain: args._[0], signature: '0'})
console.log(Base64.encode(msg.toBoc()) + ' ' + collection.toString());
}
}
},
{
@ -45,6 +53,45 @@ let commands = [
let keypair = keyPairFromSeed(await getSecureRandomBytes(32));
console.log(keypair.secretKey.toString('hex'));
}
},
{
name: 'getpub', // get public key as BN from b64 secret key
options: [
{
name: 'key',
help: 'Secret key, hex-encoded'
}
],
command: function getpub(args) {
let keypair = keyPairFromSecretKey(Buffer.from(args._[0], 'base64'));
console.log(new BN(keypair.publicKey))
}
},
{
name: 'content-message',
options: [
{
name: 'domain',
help: 'domain name to set content for (test for test.example.ton)'
},
{
name: 'zone',
help: 'zone name to set content for (example.ton for test.example.ton)'
},
{
name: 'address',
help: 'address of the collection contract'
},
{
name: 'workchain',
help: '0 for mainnet, 1 for testnet'
}
],
command: function set_content(args) {
let msg = main.setContent({domain: args._[0], zone: args._[1]});
let addr = main.getItemAddr(Address.parse(args._[2]), args._[0], args._[3] === '1' ? -1 : 0);
console.log(Base64.encode(msg.toBoc()) + ' ' + addr.toString());
}
}
]

8
build/_build.ts

@ -19,10 +19,10 @@ async function main() {
console.log("Build script running, let's find some FunC contracts to compile..");
// if we have an explicit bin directory, use the executables there (needed for glitch.com)
if (fs.existsSync("bin")) {
process.env.PATH = path.join(__dirname, "..", "bin") + path.delimiter + process.env.PATH;
process.env.FIFTPATH = path.join(__dirname, "..", "bin", "fiftlib");
}
// if (fs.existsSync("bin")) {
// process.env.PATH = path.join(__dirname, "..", "bin") + path.delimiter + process.env.PATH;
// process.env.FIFTPATH = path.join(__dirname, "..", "bin", "fiftlib");
// }
// make sure func compiler is available
const minSupportFunc = "0.2.0";

12
build/_deploy.ts

@ -6,9 +6,11 @@
import axios from "axios";
import axiosThrottle from "axios-request-throttle";
axiosThrottle.use(axios, {requestsPerSecond: 0.5}); // required since toncenter jsonRPC limits to 1 req/sec without API key
import dotenv from "dotenv";
dotenv.config();
import fs from "fs";
@ -32,7 +34,10 @@ async function main() {
}
// initialize globals
const client = new TonClient({ endpoint: `https://${isTestnet ? "testnet." : ""}toncenter.com/api/v2/jsonRPC` });
const client = new TonClient({
endpoint: `https://${isTestnet ? "testnet." : ""}toncenter.com/api/v2/jsonRPC`,
apiKey: isTestnet ? 'bed2b4589201ff3c575be653593f912a337c08eed416b60b02345763b9ee9c36' : 'a1e8a1055a387515158589dc7e9bad3035e7db2b9f9ea5cdad6b727f71e328db'
});
const deployerWalletType = "org.ton.wallets.v3.r2"; // also see WalletV3R2Source class used below
const newContractFunding = toNano(0.02); // this will be (almost in full) the balance of a new deployed contract and allow it to pay rent
const workchain = 0; // normally 0, only special contracts should be deployed to masterchain (-1)
@ -53,7 +58,10 @@ async function main() {
// open the wallet and make sure it has enough TON
const walletKey = await mnemonicToWalletKey(deployerMnemonic.split(" "));
const walletContract = WalletContract.create(client, WalletV3R2Source.create({ publicKey: walletKey.publicKey, workchain }));
const walletContract = WalletContract.create(client, WalletV3R2Source.create({
publicKey: walletKey.publicKey,
workchain
}));
console.log(` - Wallet address used to deploy from is: ${walletContract.address.toFriendly()}`);
const walletBalance = await client.getBalance(walletContract.address);
if (walletBalance.lt(toNano(0.2))) {

7
build/nft-collection.deploy.ts

@ -1,15 +1,16 @@
import * as main from "../contracts/main";
import { Address, toNano, TupleSlice, WalletContract } from "ton";
// import { sendInternalMessageWithWallet } from "../test/helpers";
import { hex } from "../build/nft-item.compiled.json";
import {hex as item_code} from "../build/nft-item.compiled.json";
import { Builder, Cell, Slice } from "ton";
import BN from "bn.js";
// return the init Cell of the contract storage (according to load_data() contract method)
export function initData() {
return main.collectionData({
ownerAddress: Address.parseFriendly("kQBw4_jZTQVbOSDbUjAMibTHWbstrCqjOnzvUTCphGpTFDrK").address,
code: Cell.fromBoc(hex)[0],
ownerKey: 0 // 63181357919630091755807889549433422416741950993093777020964723182484811889834
code: Cell.fromBoc(item_code)[0],
ownerKey: new BN(0) // 63181357919630091755807889549433422416741950993093777020964723182484811889834
});
}

2
contracts/imports/dns-utils.fc

@ -200,7 +200,7 @@ int price_function(int length, int multiplierx10, int steepness) {
if (length >= 9) {
price = 10;
}
price = (steepness * price + (10 - steepness) * 50) / 10;
price = (steepness * price + (10 - steepness) * 50) / 50;
return price * one_ton * multiplierx10 / 10;
}

32
contracts/main.ts

@ -4,6 +4,7 @@ import {SmartContract} from "ton-contract-executor";
import {encodeOffChainContent, makeSnakeCell} from "./utils";
import {randomBytes} from "crypto";
import {keyPairFromSeed, KeyPair, sign, keyPairFromSecretKey} from "ton-crypto";
import { hex as item_code } from "../build/nft-item.compiled.json";
import {randomAddress} from "../test/helpers";
import {hashCell} from "ton/dist/boc/boc";
@ -29,7 +30,7 @@ export function data(params: { ownerAddress: Address; collectionAddress: Address
.storeUint(0, 256)
.storeAddress(params.collectionAddress)
.storeAddress(params.ownerAddress)
.storeRef(encodeOffChainContent("https://agorata.io/collection.json")) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md
.storeRef(encodeOffChainContent(`https://api.agorata.io/collection/${params.domain}.json`)) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md
.storeRef(makeSnakeCell(Buffer.from(params.domain)))
.storeDict(null)
.storeUint(0, 64).endCell();
@ -39,16 +40,19 @@ export function data(params: { ownerAddress: Address; collectionAddress: Address
export function collectionData(params: {
code: Cell, ownerAddress: Address, ownerKey: BN,
price_multiplier?: number, price_steepness?: number
price_multiplier?: number, price_steepness?: number, zone?: string
}): Cell {
if (params.price_multiplier == undefined) {
params.price_multiplier = 10;
params.price_multiplier = 1;
}
if (params.price_steepness == undefined) {
params.price_steepness = 5;
params.price_steepness = 1;
}
if (params.zone == undefined) {
params.zone = "example";
}
return beginCell()
.storeRef(encodeOffChainContent("https://agorata.io/collection.json"))
.storeRef(encodeOffChainContent(`https://api.agorata.io/data/${params.zone}.json`)) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md
.storeRef(params.code)
.storeRef(beginCell().storeUint(params.price_multiplier, 8).storeUint(params.price_steepness, 4).endCell())
.storeUint(params.ownerKey, 256)
@ -99,6 +103,17 @@ export function signBuy(domain: string, collectionAddress: Address, buyerAddress
return asciiEncode(signature);
}
export function getItemAddr(collectionAddress: Address, domain: string, workchain: number): Address {
let item_data_init = beginCell()
.storeUint(new BN(hashCell(beginCell().storeBuffer(Buffer.from(domain)).endCell())), 256)
.storeAddress(collectionAddress).endCell();
let item_state_init = beginCell().storeUint(0, 2)
.storeDict(Cell.fromBoc(item_code)[0])
.storeDict(item_data_init)
.storeUint(0, 1).endCell();
return new Address(workchain, hashCell(item_state_init));
}
export function TON(): number {
return 1000000000;
}
@ -119,6 +134,13 @@ export function createItem(params: { domain: String, signature?: String }): Cell
.endCell();
}
export function setContent(params: { domain: string, zone: string }) {
return beginCell()
.storeUint(0x1a0b9d51 , 32)
.storeRef(encodeOffChainContent(`https://api.agorata.io/data/${params.zone}/${params.domain}.json`))
.endCell();
}
export function instantBuySignature(receiverAddress: Address, issuedCollectionAddr: Address, amount: number, domain: Cell, privateKey: Buffer): Buffer {
let messageToSign = beginCell().storeAddress(receiverAddress).storeAddress(issuedCollectionAddr).storeUint(amount, 256).storeRef(domain).endCell();
let hash = messageToSign.hash();

27
package-lock.json generated

@ -10,6 +10,7 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@tonconnect/protocol": "^2.0.1",
"semver": "^7.3.7",
"subcommand": "^2.1.1",
"ton-compiler": "^2.0.0"
@ -303,6 +304,14 @@
"node": ">=10"
}
},
"node_modules/@tonconnect/protocol": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.0.1.tgz",
"integrity": "sha512-jkSj6EKjIlHnJxrtxdlO7KqVJe41yrIgqamGZiqziKH6iwx0m9YyKvuIREd6CmWY2jbsev3BvBWqPp9KH6HrRw==",
"dependencies": {
"tweetnacl-util": "^0.15.1"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@ -1962,6 +1971,11 @@
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"dev": true
},
"node_modules/tweetnacl-util": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw=="
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -2263,6 +2277,14 @@
"dev": true,
"optional": true
},
"@tonconnect/protocol": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.0.1.tgz",
"integrity": "sha512-jkSj6EKjIlHnJxrtxdlO7KqVJe41yrIgqamGZiqziKH6iwx0m9YyKvuIREd6CmWY2jbsev3BvBWqPp9KH6HrRw==",
"requires": {
"tweetnacl-util": "^0.15.1"
}
},
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@ -3497,6 +3519,11 @@
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"dev": true
},
"tweetnacl-util": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw=="
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",

1
package.json

@ -47,6 +47,7 @@
"node": ">=16.15.0"
},
"dependencies": {
"@tonconnect/protocol": "^2.0.1",
"semver": "^7.3.7",
"subcommand": "^2.1.1",
"ton-compiler": "^2.0.0"

3
test/creation.spec.ts

@ -9,6 +9,7 @@ import * as main from "../contracts/main";
import { internalMessage, randomAddress } from "./helpers";
import { hex } from "../build/nft-collection.compiled.json";
import {hex as item_code} from "../build/nft-item.compiled.json";
import {makeSnakeCell} from "../contracts/utils";
describe("Creating items tests", () => {
@ -20,7 +21,7 @@ describe("Creating items tests", () => {
Cell.fromBoc(hex)[0],
main.collectionData({
ownerAddress: randomAddress("owner"),
code: Cell.fromBoc(hex)[0],
code: Cell.fromBoc(item_code)[0],
ownerKey: 0,
}),
{ debug: debug }

3
test/signing.spec.ts

@ -10,6 +10,7 @@ import * as main from "../contracts/main";
import {internalMessage, randomAddress} from "./helpers";
import {hex} from "../build/nft-collection.compiled.json";
import {hex as item_code} from "../build/nft-item.compiled.json";
import {makeSnakeCell} from "../contracts/utils";
import {keyPairFromSeed, KeyPair, sign, keyPairFromSecretKey} from "ton-crypto";
import {signBuy} from "../contracts/main";
@ -18,7 +19,7 @@ let ownerKeys = keyPairFromSeed(Buffer.from("00000000000000000000000000000000000
let ownerPubNum = new BN(ownerKeys.publicKey);
let data = main.collectionData({
ownerAddress: randomAddress("owner"),
code: Cell.fromBoc(hex)[0],
code: Cell.fromBoc(item_code)[0],
ownerKey: ownerPubNum,
});

Loading…
Cancel
Save