Browse Source

Pricing

master
Lev 2 years ago
parent
commit
b35dbb9283
  1. 31
      contracts/imports/dns-utils.fc
  2. 131
      contracts/main.ts
  3. 10
      contracts/nft-collection.fc
  4. 14
      test/creation.spec.ts

31
contracts/imports/dns-utils.fc

@ -172,6 +172,37 @@ int check_domain_string(slice domain) {
return (10, 1); return (10, 1);
} }
int price_function(int length, int multiplierx10, 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
;; 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;
if (length == 3) {
price = 200;
}
if (length == 4) {
price = 100;
}
if (length == 5) {
price = 50;
}
if (length == 6) {
price = 40;
}
if (length == 7) {
price = 30;
}
if (length == 8) {
price = 20;
}
if (length >= 9) {
price = 10;
}
price = (steepness * price + (10 - steepness) * 50) / 10;
return price * one_ton * multiplierx10 / 10;
}
int get_min_price(int domain_bits_length, int now_time) { int get_min_price(int domain_bits_length, int now_time) {
(int start_min_price, int end_min_price) = get_min_price_config(domain_bits_length / 8); (int start_min_price, int end_min_price) = get_min_price_config(domain_bits_length / 8);
start_min_price *= one_ton; start_min_price *= one_ton;

131
contracts/main.ts

@ -1,16 +1,16 @@
import BN from "bn.js"; import BN from "bn.js";
import { Cell, beginCell, Address } from "ton"; import {Cell, beginCell, Address} from "ton";
import { C7Config, SmartContract } from "ton-contract-executor"; import {C7Config, SmartContract} from "ton-contract-executor";
import {encodeOffChainContent, makeSnakeCell} from "./utils"; import {encodeOffChainContent, makeSnakeCell} from "./utils";
import { randomBytes } from "crypto"; import {randomBytes} from "crypto";
import { keyPairFromSeed, KeyPair, sign } from "ton-crypto"; import {keyPairFromSeed, KeyPair, sign} from "ton-crypto";
import { ExpansionPanelActions } from "@material-ui/core"; import {ExpansionPanelActions} from "@material-ui/core";
// encode contract storage according to save_data() contract method // encode contract storage according to save_data() contract method
export function genKeyPair(): KeyPair { export function genKeyPair(): KeyPair {
let seed = randomBytes(32); let seed = randomBytes(32);
return keyPairFromSeed(seed); return keyPairFromSeed(seed);
} }
// nft_item_code:^Cell // nft_item_code:^Cell
@ -22,89 +22,100 @@ export function genKeyPair(): KeyPair {
// cell auction - auction info // cell auction - auction info
// int64 last_fill_up_time // int64 last_fill_up_time
export function data(params: { ownerAddress: Address; collectionAddress: Address, code: Cell, domain: String, publicKey: Buffer }): Cell { export function data(params: { ownerAddress: Address; collectionAddress: Address, code: Cell, domain: String, publicKey: Buffer }): Cell {
let data_cell = beginCell() let data_cell = beginCell()
// For code: https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-auction/build.sh // For code: https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-auction/build.sh
.storeRef(params.code) .storeRef(params.code)
.storeUint(0, 256) .storeUint(0, 256)
.storeAddress(params.collectionAddress) .storeAddress(params.collectionAddress)
.storeAddress(params.ownerAddress) .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://agorata.io/collection.json")) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md
.storeRef(makeSnakeCell(Buffer.from(params.domain))) .storeRef(makeSnakeCell(Buffer.from(params.domain)))
.storeDict(null) .storeDict(null)
.storeUint(0, 64).endCell(); .storeUint(0, 64).endCell();
return beginCell().storeRef(data_cell).storeBuffer(params.publicKey).endCell(); return beginCell().storeRef(data_cell).storeBuffer(params.publicKey).endCell();
} }
export function collectionData(params: { code: Cell, ownerAddress: Address, ownerKey: number }): Cell { export function collectionData(params: {
code: Cell, ownerAddress: Address, ownerKey: number,
price_multiplier?: number, price_steepness?: number
}): Cell {
if (params.price_multiplier == undefined) {
params.price_multiplier = 10;
}
if (params.price_steepness == undefined) {
params.price_steepness = 5;
}
return beginCell() return beginCell()
.storeRef(encodeOffChainContent("https://agorata.io/collection.json")) .storeRef(encodeOffChainContent("https://agorata.io/collection.json"))
.storeRef(params.code) .storeRef(params.code)
.storeRef(beginCell().endCell()) .storeRef(beginCell().storeUint(params.price_multiplier, 8).storeUint(params.price_steepness, 4).endCell())
.storeUint(params.ownerKey, 256) .storeUint(params.ownerKey, 256)
.storeAddress(params.ownerAddress) .storeAddress(params.ownerAddress)
.endCell(); .endCell();
} }
export function auctionWithWinner(winnerAddress: Address) { export function auctionWithWinner(winnerAddress: Address) {
return beginCell().storeAddress(winnerAddress).storeCoins(0).storeUint(0, 64) return beginCell().storeAddress(winnerAddress).storeCoins(0).storeUint(0, 64)
} }
export function setContractBalance(contract: SmartContract, balance: number) { export function setContractBalance(contract: SmartContract, balance: number) {
contract.setC7Config({balance: balance}); contract.setC7Config({balance: balance});
} }
export function TON(): number { return 1000000000; } export function TON(): number {
return 1000000000;
}
// message encoders for all ops (see contracts/imports/constants.fc for consts) // message encoders for all ops (see contracts/imports/constants.fc for consts)
export function transferOwnership(params: { newOwnerAddress: Address }): Cell { export function transferOwnership(params: { newOwnerAddress: Address }): Cell {
return beginCell().storeUint(0x5fcc3d14, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).storeAddress(null).storeInt(0, 1).storeCoins(1 * TON()).endCell(); return beginCell().storeUint(0x5fcc3d14, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).storeAddress(null).storeInt(0, 1).storeCoins(1 * TON()).endCell();
} }
export function createItem(params: { domain: String }): Cell { export function createItem(params: { domain: String }): Cell {
let signature = '000'; let signature = '000';
return beginCell() return beginCell()
.storeUint(0, 32) .storeUint(0, 32)
.storeRef(makeSnakeCell(Buffer.from(params.domain + ';' + signature))) .storeRef(makeSnakeCell(Buffer.from(params.domain + ';' + signature)))
.endCell(); .endCell();
} }
export function instantBuySignature(receiverAddress: Address, issuedCollectionAddr: Address, amount: number, domain: Cell, privateKey: Buffer): Buffer { 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 messageToSign = beginCell().storeAddress(receiverAddress).storeAddress(issuedCollectionAddr).storeUint(amount, 256).storeRef(domain).endCell();
let hash = messageToSign.hash(); let hash = messageToSign.hash();
// console.log(hash.toString("hex")); // console.log(hash.toString("hex"));
// console.log((new BN(hash)).toString(10)) // console.log((new BN(hash)).toString(10))
// return sign(hash, privateKey); // return sign(hash, privateKey);
let althash = new BN("FF11841721E4DAD5AE679A1A338B2EBFC5AAF7529C200B4EF9D71831B1DCB969", "hex"); let althash = new BN("FF11841721E4DAD5AE679A1A338B2EBFC5AAF7529C200B4EF9D71831B1DCB969", "hex");
return sign(althash.toBuffer(), privateKey); return sign(althash.toBuffer(), privateKey);
} }
export function instantBuyMessage(params: { receiverAddress: Address, issuedCollectionAddr: Address, price: number, domain: String, privateKey: Buffer}): Cell { export function instantBuyMessage(params: { receiverAddress: Address, issuedCollectionAddr: Address, price: number, domain: String, privateKey: Buffer }): Cell {
let domainSnakeCell = makeSnakeCell(Buffer.from(params.domain)); let domainSnakeCell = makeSnakeCell(Buffer.from(params.domain));
let signature = instantBuySignature(params.receiverAddress, params.issuedCollectionAddr, params.price, domainSnakeCell, params.privateKey); let signature = instantBuySignature(params.receiverAddress, params.issuedCollectionAddr, params.price, domainSnakeCell, params.privateKey);
console.log(signature.toString("hex").toUpperCase()); console.log(signature.toString("hex").toUpperCase());
let cell = beginCell() let cell = beginCell()
.storeUint(0x16c7d435, 32) // opcode .storeUint(0x16c7d435, 32) // opcode
.storeUint(0, 64) // query id .storeUint(0, 64) // query id
.storeBuffer(signature) // body .storeBuffer(signature) // body
.storeUint(params.price, 256) .storeUint(params.price, 256)
.storeRef(domainSnakeCell).endCell(); .storeRef(domainSnakeCell).endCell();
return cell; return cell;
} }
export function currentState(contract: SmartContract) { export function currentState(contract: SmartContract) {
let c4 = contract.dataCell.beginParse(); let c4 = contract.dataCell.beginParse();
let reader = c4.readRef(); let reader = c4.readRef();
return { return {
nft_item_code: reader.readRef(), nft_item_code: reader.readRef(),
index: reader.readUint(256), index: reader.readUint(256),
collectionAddress: reader.readAddress(), collectionAddress: reader.readAddress(),
ownerAddress: reader.readAddress(), ownerAddress: reader.readAddress(),
collectionContent: reader.readRef(), collectionContent: reader.readRef(),
domain: reader.readRef(), domain: reader.readRef(),
// auction: reader.readCell(), – TODO: still havent's figured out to load auction here // auction: reader.readCell(), – TODO: still havent's figured out to load auction here
// lastFillUpTime: reader.readInt(64) // lastFillUpTime: reader.readInt(64)
} }
} }

10
contracts/nft-collection.fc

@ -41,7 +41,10 @@ int calcprice(slice domain, cell pricing) inline_ref {
throw_unless(201, len <= 126 * 8); ;; maxmimum 126 characters throw_unless(201, len <= 126 * 8); ;; maxmimum 126 characters
throw_unless(202, mod(len, 8) == 0); throw_unless(202, mod(len, 8) == 0);
throw_unless(203, check_domain_string(domain)); throw_unless(203, check_domain_string(domain));
return 100000; ;; todo slice pr = pricing.begin_parse();
int multiplier = pr~load_uint(8);
int steepness = pr~load_uint(4);
return price_function(len / 8, multiplier, steepness);
} }
cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { cell calculate_nft_item_state_init(int item_index, cell nft_item_code) {
@ -138,6 +141,11 @@ cell get_nft_content(int index, cell individual_nft_content) method_id {
return individual_nft_content; return individual_nft_content;
} }
int get_price(slice domain) method_id {
var (content, nft_item_code, pricing, key, addr) = load_data();
return calcprice(domain, pricing);
}
(int, cell) dnsresolve(slice subdomain, int category) method_id { (int, cell) dnsresolve(slice subdomain, int category) method_id {
throw_unless(70, mod(slice_bits(subdomain), 8) == 0); throw_unless(70, mod(slice_bits(subdomain), 8) == 0);

14
test/creation.spec.ts

@ -33,13 +33,23 @@ describe("Creating items tests", () => {
const sendToSelfMessage = internalMessage({ const sendToSelfMessage = internalMessage({
from: ownerAddr, from: ownerAddr,
body: main.createItem({ domain: "test" }), body: main.createItem({ domain: "test" }),
value: new BN(10 * main.TON()), value: new BN(100 * main.TON()),
}); });
const res = await contract.sendInternalMessage(sendToSelfMessage); const res = await contract.sendInternalMessage(sendToSelfMessage);
console.log(res); console.log(res);
expect(res.type).to.equal("success"); expect(res.type).to.equal("success");
expect(res.exit_code).to.equal(0); expect(res.exit_code).to.equal(0);
});
it("does not allow to buy an item if the price is too low", async () => {
main.setContractBalance(contract, 10 * main.TON());
let ownerAddr = randomAddress("owner");
const sendToSelfMessage = internalMessage({
from: ownerAddr,
body: main.createItem({ domain: "test" }),
value: new BN(10 * main.TON()),
});
const res = await contract.sendInternalMessage(sendToSelfMessage);
expect(res.type).to.equal("failed");
}); });
}); });

Loading…
Cancel
Save