Browse Source

Fixes

master
Lev 2 years ago
parent
commit
368825aa1e
  1. 8
      bin/api.js
  2. 4
      build/nft-collection.deploy.ts
  3. 24
      contracts/imports/dns-utils.fc
  4. 38
      contracts/main.ts
  5. 45
      contracts/nft-collection.fc
  6. 60
      contracts/nft-item.fc
  7. 9
      test/item.spec.ts
  8. 2
      test/signing.spec.ts

8
bin/api.js

@ -5,6 +5,8 @@ const {Base64} = require('@tonconnect/protocol');
const express = require('express')
const {get_tonclient, AdnlAddress} = require("../contracts/utils");
const {getRecords} = require("../contracts/main");
const {sha256} = require("ton-crypto");
const {BN} = require("bn.js");
const app = express()
const port = 5171
const debug = process.env.DEBUG === '1';
@ -41,12 +43,14 @@ app.get('/content-message/:address/:zone/:domain', async (req, res) => {
app.get('/address/:collection/:domain', (req, res) => {
let addr = main.getItemAddr(Address.parse(req.params.collection), req.params.domain, 0);
console.log(addr.toFriendly())
res.send(JSON.stringify({"address": addr.toString()}));
})
app.get('/get-records/:address', async (req, res) => {
try {
let records = await getRecords(tonclient, Address.parse(req.params.address));
// console.log(records)
res.send(JSON.stringify(records));
} catch (e) {
console.log(e);
@ -60,6 +64,10 @@ app.get('/set-record/site/:site', async (req, res) => {
res.send(JSON.stringify(Base64.encode(msg.toBoc())));
})
app.get('/hash/:key', async (req, res) => {
res.send(new BN(await sha256(req.params.key)).toString());
})
app.get('/set-record/wallet/:wallet', async (req, res) => {
let wallet = Address.parse(req.params.wallet);
let msg = await main.changeRecordMsg("wallet", await main.WalletRecord(wallet));

4
build/nft-collection.deploy.ts

@ -7,9 +7,11 @@ import BN from "bn.js";
// return the init Cell of the contract storage (according to load_data() contract method)
export function initData() {
let isTestnet = false;
return main.collectionData({
ownerAddress: Address.parseFriendly("kQBw4_jZTQVbOSDbUjAMibTHWbstrCqjOnzvUTCphGpTFDrK").address,
ownerAddress: Address.parseFriendly("EQB_hLB81P0BuBqcitfwoLRd2QX4l4SY-w2fWob9xdWNQgSx").address,
code: Cell.fromBoc(item_code)[0],
zone: isTestnet ? "example.ton" : "nftcoffee.ton",
ownerKey: new BN(0) // 63181357919630091755807889549433422416741950993093777020964723182484811889834
});
}

24
contracts/imports/dns-utils.fc

@ -173,35 +173,35 @@ int check_domain_string(slice domain) {
return (10, 1);
}
int price_function(int length, int multiplierx10, int steepness) {
int price_function(int length, int multiplierx50, int steepness) {
;; length is the length of the domain, the price depends on it. The rest is the price config
;; multiplierx10 is the price multiplier, the default price is multiplied by multiplierx10 / 10
;; multiplierx50 is the price multiplier, the default price is multiplied by multiplierx50 / 50
;; steepness (from 0 to 10) is the steepness of the price function,
;; 10 means the distribution is as in the function from `get_min_price_config` (the second number), 0 means the price is always the same, 50
int price = 50;
int price = 25;
if (length == 3) {
price = 200;
price = 100;
}
if (length == 4) {
price = 100;
price = 50;
}
if (length == 5) {
price = 50;
price = 25;
}
if (length == 6) {
price = 40;
price = 20;
}
if (length == 7) {
price = 30;
price = 15;
}
if (length == 8) {
price = 20;
price = 10;
}
if (length >= 9) {
price = 10;
price = 5;
}
price = (steepness * price + (10 - steepness) * 50) / 50;
return price * one_ton * multiplierx10 / 10;
price = (steepness * price + (10 - steepness) * 25) / 10;
return price * one_ton * multiplierx50 / 50;
}
int get_min_price(int domain_bits_length, int now_time) {

38
contracts/main.ts

@ -46,6 +46,10 @@ export function data(params: { ownerAddress: Address; collectionAddress: Address
return beginCell().storeRef(data_cell).storeBuffer(params.publicKey).endCell();
}
function strCell(str: string): Cell {
return beginCell().storeBuffer(Buffer.from(str)).endCell();
}
export function collectionData(params: {
code: Cell, ownerAddress: Address, ownerKey: BN,
price_multiplier?: number, price_steepness?: number, zone?: string
@ -54,10 +58,10 @@ export function collectionData(params: {
params.price_multiplier = 1;
}
if (params.price_steepness == undefined) {
params.price_steepness = 1;
params.price_steepness = 0;
}
if (params.zone == undefined) {
params.zone = "example";
params.zone = "example.ton";
}
return beginCell()
.storeRef(encodeOffChainContent(`https://api.agorata.io/data/${params.zone}.json`)) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md
@ -67,8 +71,11 @@ export function collectionData(params: {
.storeAddress(params.ownerAddress)
.storeRef(
beginCell()
.storeRef(beginCell().storeBuffer(Buffer.from(`https://api.agorata.io/data/${params.zone}/`)).endCell())
.storeRef(beginCell().storeBuffer(Buffer.from(`.json`)).endCell())
.storeRef(strCell(`https://api.agorata.io/data/${params.zone}/`))
.storeRef(beginCell().storeRef(strCell(`.json`))
.storeRef(strCell(`.png`)).endCell())
.storeRef(strCell('.' + params.zone))
.storeRef(strCell(`A TON DNS domain in the ${params.zone} zone. By agorata.io`))
.endCell())
.endCell();
}
@ -120,9 +127,10 @@ export async function getRecords(tonclient: TonClient, address: Address) {
// print site_data converted from base64
let c = Cell.fromBoc(Buffer.from(site_data, 'base64'))[0].beginParse();
c.skip(16);
site = new AdnlAddress(c.readRemainingBytes().subarray(2, 2 + 32)).toHex();
site = new AdnlAddress(c.readRemainingBytes().subarray(0, 32)).toHex();
}
} catch (e) {
console.log(e)
}
let uri = null;
try {
@ -131,9 +139,12 @@ export async function getRecords(tonclient: TonClient, address: Address) {
["num", new BN(await sha256('uri')).toString()]])).stack[1];
uri = Cell.fromBoc(Buffer.from(ans_uri[1].bytes, "base64"))[0].beginParse().readRemainingBytes().toString();
} catch (e) {
console.log(e);
console.log((await tonclient.callGetMethod(address, 'get_nft_data')).stack[4]);
// console.log((await tonclient.callGetMethod(address, 'get_nft_data')).stack[4]);
}
// let name_uri = (await tonclient.callGetMethod(address, 'dnsresolve',
// [["tvm.Slice", "te6cckEBAQEAAwAAAgDTZ9xB"],
// ["num", new BN(await sha256('image')).toString()]])).stack[1];
// console.log(Cell.fromBoc(Buffer.from(name_uri[1].bytes, "base64"))[0].beginParse().readRemainingBytes().toString());
return {wallet, site, uri};
}
@ -237,7 +248,13 @@ export function initializeItemMsg(params: { domain: String, ownerAddr: Address,
return beginCell()
.storeAddress(params.ownerAddr)
.storeRef(beginCell().storeBuffer(Buffer.from(params.domain)).endCell())
.storeRef(beginCell().storeBuffer(Buffer.from(`https://api.agorata.io/data/${params.zone}/${params.domain}.json`)).endCell())
.storeRef(beginCell()
.storeRef(strCell(`https://api.agorata.io/data/${params.zone}/${params.domain}.json`))
.storeRef(strCell(`https://api.agorata.io/data/${params.zone}/${params.domain}.png`))
.storeRef(strCell(`${params.zone}.${params.domain}`))
.storeRef(strCell(`A TON DNS domain in the ${params.zone} zone`))
.endCell()
)
.endCell();
}
@ -254,11 +271,6 @@ export async function setContent(params: { domain: string, zone: string }) {
return changeRecordMsg("uri", beginCell().storeBuffer(Buffer.from(`https://api.agorata.io/data/${params.zone}/${params.domain}.json`)).endCell());
}
export function loadRecords(map_cell: Cell) {
let map_sl = map_cell.beginParse();
map_sl
}
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();

45
contracts/nft-collection.fc

@ -10,7 +10,11 @@
;; cell pricing
;; uint256(key) owner_key
;; address owner_address
;; cell uri_scheme: prefix slice as a cell, then postfix ('.json') slice as a cell
;; cell data_scheme:
;; uri prefix for image and data slice as a cell
;; cell: uri postfix ('.json') slice as a cell, image uri postfix ('.png')
;; zone
;; description
(cell, cell, cell, int, slice, cell) load_data() inline {
var ds = get_data().begin_parse();
@ -20,18 +24,18 @@
ds~load_ref(), ;; pricing
ds~load_uint(256), ;; owner key
ds~load_msg_addr(), ;; owner address
ds~load_ref() ;; uri_scheme
ds~load_ref() ;; data_scheme
);
}
() save_data(cell content, cell nft_item_code, cell pricing, int owner_key, slice owner_addr, cell uri_scheme) impure inline {
() save_data(cell content, cell nft_item_code, cell pricing, int owner_key, slice owner_addr, cell data_scheme) impure inline {
set_data(begin_cell()
.store_ref(content)
.store_ref(nft_item_code)
.store_ref(pricing)
.store_uint(owner_key, 256)
.store_slice(owner_addr)
.store_ref(uri_scheme)
.store_ref(data_scheme)
.end_cell());
}
@ -53,13 +57,26 @@ cell calculate_nft_item_state_init(int item_index, cell nft_item_code) {
return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell();
}
cell get_uri(slice domain, cell uri_scheme) {
;; parse the prefix and the postfix out of uri_scheme
slice cs = uri_scheme.begin_parse();
cell get_attachment(slice domain, cell data_scheme) {
;; parse the prefix and the postfix out of data_scheme
slice cs = data_scheme.begin_parse();
slice prefix = cs~load_ref().begin_parse();
slice postfix = cs~load_ref().begin_parse();
slice postfixes = cs~load_ref().begin_parse();
slice postfix = postfixes~load_ref().begin_parse();
slice image_postfix = postfixes~load_ref().begin_parse();
;; create the slice with the uri: prefix + domain + postfix
return begin_cell().store_slice(prefix).store_slice(domain).store_slice(postfix).end_cell();
cell uri_cell = begin_cell().store_uint(1, 8).store_slice(prefix).store_slice(domain).store_slice(postfix).end_cell();
cell image_uri_cell = begin_cell().store_slice(prefix).store_slice(domain).store_slice(image_postfix).end_cell();
slice zone = cs~load_ref().begin_parse();
cell name_cell = begin_cell().store_slice(domain).store_slice(zone).end_cell();
cell desc_cell = cs~load_ref();
cell full_cell = begin_cell()
.store_ref(uri_cell)
.store_ref(image_uri_cell)
.store_ref(name_cell)
.store_ref(desc_cell)
.end_cell();
return full_cell;
}
slice calculate_nft_item_address(int wc, cell state_init) {
@ -119,7 +136,7 @@ int verify_signature(slice signature, slice sender_address, slice domain, int ow
int op = in_msg_body~load_uint(32);
var (content, nft_item_code, pricing, key, addr, uri_scheme) = load_data();
var (content, nft_item_code, pricing, key, addr, data_scheme) = load_data();
if (op == 0) { ;; deploy new nft
int now_time = now();
@ -140,7 +157,7 @@ int verify_signature(slice signature, slice sender_address, slice domain, int ow
cell nft_content = begin_cell()
.store_slice(sender_address)
.store_ref(begin_cell().store_slice(domain).end_cell())
.store_ref(get_uri(domain, uri_scheme))
.store_ref(get_attachment(domain, data_scheme))
.end_cell();
deploy_nft_item(item_index, nft_item_code, nft_content);
return ();
@ -162,12 +179,12 @@ int verify_signature(slice signature, slice sender_address, slice domain, int ow
;; Get methods
(int, cell, slice) get_collection_data() method_id {
var (content, nft_item_code, pricing, key, addr, uri_scheme) = load_data();
var (content, nft_item_code, pricing, key, addr, data_scheme) = load_data();
return (-1, content, zero_address());
}
slice get_nft_address_by_index(int index) method_id {
var (content, nft_item_code, pricing, key, addr, uri_scheme) = load_data();
var (content, nft_item_code, pricing, key, addr, data_scheme) = load_data();
cell state_init = calculate_nft_item_state_init(index, nft_item_code);
return calculate_nft_item_address(workchain(), state_init);
}
@ -177,7 +194,7 @@ cell get_nft_content(int index, cell individual_nft_content) method_id {
}
int get_price(slice domain) method_id {
var (content, nft_item_code, pricing, key, addr, uri_scheme) = load_data();
var (content, nft_item_code, pricing, key, addr, data_scheme) = load_data();
return calcprice(domain, pricing);
}

60
contracts/nft-item.fc

@ -3,10 +3,13 @@
#include "imports/op-codes.fc";
#include "imports/params.fc";
int min_tons_for_storage() asm "1000000000 PUSHINT"; ;; 1 TON
int min_tons_for_storage() asm "500000000 PUSHINT"; ;; 0.5 TON
;; sha256('uri')
const uri_key = 51065135818459385347574250312853146822620586594996463797054414300406918686668;
const name_key = 59089242681608890680090686026688704441792375738894456860693970539822503415433;
const image_key = 43884663033947008978309661017057008345326326811558777475113826163084742639165;
const description_key = 90922719342317012409671596374183159143637506542604000676488204638996496437508;
;;
;; === Storage ===
@ -15,26 +18,28 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
;; MsgAddressInt collection_address
;; MsgAddressInt owner_address
;; cell content
;; cell uri
;; cell domain - e.g contains "alice" (without ending \0) for "alice.ton" domain
;; int last_fill_up_time
(int, int, slice, slice, cell, cell, int) load_data() {
(int, int, slice, slice, cell, cell, cell, int) load_data() {
slice ds = get_data().begin_parse();
var (index, collection_address) = (ds~load_uint(256), ds~load_msg_addr());
if (ds.slice_bits() > 0) {
return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref(), ds~load_ref(), ds~load_uint(64));
return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref(), ds~load_ref(), ds~load_ref(), ds~load_uint(64));
} else {
return (0, index, collection_address, null(), null(), null(), 0); ;; nft not initialized yet
return (0, index, collection_address, null(), null(), null(), null(), 0); ;; nft not initialized yet
}
}
() store_data(int index, slice collection_address, slice owner_address, cell content, cell domain, int last_fill_up_time) impure {
() store_data(int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) impure {
set_data(
begin_cell()
.store_uint(index, 256)
.store_slice(collection_address)
.store_slice(owner_address)
.store_ref(content)
.store_ref(uri)
.store_ref(domain)
.store_uint(last_fill_up_time, 64)
.end_cell()
@ -57,7 +62,7 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
send_raw_message(msg.end_cell(), send_mode);
}
() transfer_ownership(int my_balance, int index, slice collection_address, slice owner_address, cell content, slice sender_address, int query_id, slice in_msg_body, int fwd_fees, cell domain) impure inline {
() transfer_ownership(int my_balance, int index, slice collection_address, slice owner_address, cell content, cell uri, slice sender_address, int query_id, slice in_msg_body, int fwd_fees, cell domain) impure inline {
slice new_owner_address = in_msg_body~load_msg_addr();
force_chain(new_owner_address);
slice response_destination = in_msg_body~load_msg_addr();
@ -83,7 +88,7 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
send_msg(response_destination, rest_amount, op::excesses(), query_id, null(), 1); ;; paying fees, revert on errors
}
store_data(index, collection_address, new_owner_address, content, domain, now());
store_data(index, collection_address, new_owner_address, content, uri, domain, now());
}
() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
@ -102,16 +107,27 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
cs~load_coins(); ;; skip ihr_fee
int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of forward_payload costs
(int init?, int index, slice collection_address, slice owner_address, cell content, cell domain, int last_fill_up_time) = load_data();
(int init?, int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) = load_data();
if (~ init?) {
throw_unless(405, equal_slices(collection_address, sender_address));
slice from_address = in_msg_body~load_msg_addr();
cell domain = in_msg_body~load_ref();
cell uri = in_msg_body~load_ref();
slice attachment = in_msg_body~load_ref().begin_parse();
cell uri = attachment~load_ref();
cell img_uri = attachment~load_ref();
cell name = attachment~load_ref();
cell description = attachment~load_ref();
cell content_dict = new_dict();
content_dict~udict_set_ref(256, uri_key, uri);
;; content_dict~udict_set_ref(256, uri_key, uri);
content_dict~udict_set_ref(256, image_key, img_uri);
content_dict~udict_set_ref(256, name_key, name);
content_dict~udict_set_ref(256, description_key, description);
cell content = begin_cell().store_uint(0, 8).store_dict(content_dict).end_cell();
store_data(index, collection_address, from_address, content, domain, now());
store_data(index, collection_address, from_address, content, uri, domain, now());
int rest_amount = msg_value - fwd_fee - min_tons_for_storage();
if (rest_amount > 0) {
send_msg(collection_address, rest_amount, op::excesses(), 0, null(), 1); ;; paying fees, revert on errors
}
return ();
}
@ -125,7 +141,7 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
if (op == 0) {
store_data(index, collection_address, owner_address, content, domain, now());
store_data(index, collection_address, owner_address, content, uri, domain, now());
return ();
}
@ -134,12 +150,12 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
if (op == op::transfer()) {
throw_unless(401, equal_slices(sender_address, owner_address));
transfer_ownership(my_balance, index, collection_address, owner_address, content, sender_address, query_id, in_msg_body, fwd_fee, domain);
transfer_ownership(my_balance, index, collection_address, owner_address, content, uri, sender_address, query_id, in_msg_body, fwd_fee, domain);
return ();
}
if (op == op::edit_content()) { ;; owner can change content and dns records
throw_unless(410, equal_slices(sender_address, owner_address));
store_data(index, collection_address, owner_address, in_msg_body~load_ref(), domain, now());
store_data(index, collection_address, owner_address, in_msg_body~load_ref(), in_msg_body~load_ref(), domain, now());
return ();
}
if (op == op::change_dns_record) { ;; change dns record
@ -161,7 +177,7 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
content = begin_cell().store_uint(0, 8).store_dict(keyvalue_map).end_cell();
store_data(index, collection_address, owner_address, content, domain, now());
store_data(index, collection_address, owner_address, content, uri, domain, now());
return ();
}
if (op == op::process_governance_decision) { ;; governance
@ -172,7 +188,7 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
int config_op = config_value~load_uint(8);
throw_unless(416, (config_op == 0) | (config_op == 1));
if (config_op == 0) { ;; transfer
transfer_ownership(my_balance, index, collection_address, owner_address, content, sender_address, query_id, config_value, fwd_fee, domain);
transfer_ownership(my_balance, index, collection_address, owner_address, content, uri, sender_address, query_id, config_value, fwd_fee, domain);
}
if (config_op == 1) { ;; destroy
send_msg(collection_address, 0, op::fill_up, query_id, null(), 128 + 32); ;; carry all the remaining balance + destroy
@ -191,22 +207,22 @@ const uri_key = 5106513581845938534757425031285314682262058659499646379705441430
;;
(int, int, slice, slice, cell) get_nft_data() method_id {
(int init?, int index, slice collection_address, slice owner_address, cell content, cell domain, int last_fill_up_time) = load_data();
return (init?, index, collection_address, owner_address, content);
(int init?, int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) = load_data();
return (init?, index, collection_address, owner_address, uri);
}
slice get_editor() method_id {
(int init?, int index, slice collection_address, slice owner_address, cell content, cell domain, int last_fill_up_time) = load_data();
(int init?, int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) = load_data();
return owner_address;
}
slice get_domain() method_id {
(int init?, int index, slice collection_address, slice owner_address, cell content, cell domain, int last_fill_up_time) = load_data();
(int init?, int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) = load_data();
return domain.begin_parse();
}
int get_last_fill_up_time() method_id {
(int init?, int index, slice collection_address, slice owner_address, cell content, cell domain, int last_fill_up_time) = load_data();
(int init?, int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) = load_data();
return last_fill_up_time;
}
@ -215,7 +231,7 @@ int get_last_fill_up_time() method_id {
throw_unless(70, mod(subdomain_bits, 8) == 0);
(int init?, int index, slice collection_address, slice owner_address, cell content, cell my_domain_cell, int last_fill_up_time) = load_data();
(int init?, int index, slice collection_address, slice owner_address, cell content, cell uri, cell domain, int last_fill_up_time) = load_data();
slice cs = content.begin_parse();
throw_unless(412, cs~load_uint(8) == 0); ;; data onchain tag

9
test/item.spec.ts

@ -4,7 +4,7 @@ import BN from "bn.js";
chai.use(chaiBN(BN));
import {Address, beginCell, Builder, Cell, contractAddress, parseDict, parseDictBitString, Slice} from "ton";
import {Address, beginCell, Builder, Cell, contractAddress, parseDict, parseDictBitString, Slice, toNano} from "ton";
import {runContract, SmartContract} from "ton-contract-executor";
import * as main from "../contracts/main";
import {internalMessage, randomAddress} from "./helpers";
@ -17,7 +17,7 @@ import {Base64} from "@tonconnect/protocol";
let data = main.itemDataUninit({domain: "test", collectionAddress: randomAddress("collection")});
describe("Creating items tests", () => {
describe("Creating item tests", () => {
let contract: SmartContract;
let debug: boolean = true;
@ -28,7 +28,8 @@ describe("Creating items tests", () => {
{debug: debug}
);
contract.setC7Config({
myself: randomAddress("item")
myself: randomAddress("item"),
balance: toNano(1).toNumber(),
})
});
@ -48,7 +49,7 @@ describe("Creating items tests", () => {
const setDataMsg = internalMessage({
from: ownerAddr,
body: await main.setContent({domain: "levcccc", zone: "example.ton"}),
value: new BN(0),
value: new BN(toNano(2)),
})
const res2 = await contract.sendInternalMessage(setDataMsg);
expect(res2.type).to.equal("success");

2
test/signing.spec.ts

@ -23,7 +23,7 @@ let data = main.collectionData({
ownerKey: ownerPubNum,
});
describe("Creating items tests", () => {
describe("Signing tests", () => {
let contract: SmartContract;
let debug: boolean = true;

Loading…
Cancel
Save