add nft code & script
This commit is contained in:
1 parent
7b8b45295a
commit
21bed2c1f3
8 files changed
+484
-1
No files matched your search
@@ -0,0 +1,24 @@
|
||||
int op::transfer() asm "0x5fcc3d14 PUSHINT";
|
||||
int op::ownership_assigned() asm "0x05138d91 PUSHINT";
|
||||
int op::excesses() asm "0xd53276db PUSHINT";
|
||||
int op::get_static_data() asm "0x2fcb26a2 PUSHINT";
|
||||
int op::report_static_data() asm "0x8b771735 PUSHINT";
|
||||
int op::get_royalty_params() asm "0x693d3950 PUSHINT";
|
||||
int op::report_royalty_params() asm "0xa8cb00ad PUSHINT";
|
||||
|
||||
;; NFTEditable
|
||||
int op::edit_content() asm "0x1a0b9d51 PUSHINT";
|
||||
int op::transfer_editorship() asm "0x1c04412a PUSHINT";
|
||||
int op::editorship_assigned() asm "0x511a4463 PUSHINT";
|
||||
|
||||
;; SBT
|
||||
int op::request_owner() asm "0xd0c3bfea PUSHINT";
|
||||
int op::owner_info() asm "0x0dd607e3 PUSHINT";
|
||||
|
||||
int op::prove_ownership() asm "0x04ded148 PUSHINT";
|
||||
int op::ownership_proof() asm "0x0524c7ae PUSHINT";
|
||||
int op::ownership_proof_bounced() asm "0xc18e86d2 PUSHINT";
|
||||
|
||||
int op::destroy() asm "0x1f04537a PUSHINT";
|
||||
int op::revoke() asm "0x6f89f5e3 PUSHINT";
|
||||
int op::take_excess() asm "0xd136d3b3 PUSHINT";
|
||||
@@ -0,0 +1,10 @@
|
||||
int workchain() asm "0 PUSHINT";
|
||||
|
||||
() force_chain(slice addr) impure {
|
||||
(int wc, _) = parse_std_addr(addr);
|
||||
throw_unless(333, wc == workchain());
|
||||
}
|
||||
|
||||
slice null_addr() asm "b{00} PUSHSLICE";
|
||||
int flag::regular() asm "0x10 PUSHINT";
|
||||
int flag::bounce() asm "0x8 PUSHINT";
|
||||
@@ -621,5 +621,7 @@ int get_seed() impure asm "RANDSEED";
|
||||
int equal_slice_bits(slice a, slice b) asm "SDEQ";
|
||||
int equal_slices(slice a, slice b) asm "SDEQ";
|
||||
|
||||
int builder_null?(builder b) asm "ISNULL";
|
||||
|
||||
;;; Concatenates two builders
|
||||
builder store_builder(builder to, builder from) asm "STBR";
|
||||
builder store_builder(builder to, builder from) asm "STBR";
|
||||
@@ -0,0 +1,215 @@
|
||||
;; NFT collection smart contract
|
||||
|
||||
#include "imports/stdlib.fc";
|
||||
#include "imports/op-codes.fc";
|
||||
#include "imports/params.fc";
|
||||
|
||||
;; storage scheme
|
||||
;; default#_ royalty_factor:uint16 royalty_base:uint16 royalty_address:MsgAddress = RoyaltyParams;
|
||||
;; storage#_ owner_address:MsgAddress next_item_index:uint64
|
||||
;; ^[collection_content:^Cell common_content:^Cell]
|
||||
;; nft_item_code:^Cell
|
||||
;; royalty_params:^RoyaltyParams
|
||||
;; = Storage;
|
||||
|
||||
(slice, int, cell, cell, cell) load_data() inline {
|
||||
var ds = get_data().begin_parse();
|
||||
return
|
||||
(ds~load_msg_addr(), ;; owner_address
|
||||
ds~load_uint(64), ;; next_item_index
|
||||
ds~load_ref(), ;; content
|
||||
ds~load_ref(), ;; nft_item_code
|
||||
ds~load_ref() ;; royalty_params
|
||||
);
|
||||
}
|
||||
|
||||
() save_data(slice owner_address, int next_item_index, cell content, cell nft_item_code, cell royalty_params) impure inline {
|
||||
set_data(begin_cell()
|
||||
.store_slice(owner_address)
|
||||
.store_uint(next_item_index, 64)
|
||||
.store_ref(content)
|
||||
.store_ref(nft_item_code)
|
||||
.store_ref(royalty_params)
|
||||
.end_cell());
|
||||
}
|
||||
|
||||
cell calculate_nft_item_state_init(int item_index, cell nft_item_code) {
|
||||
cell data = begin_cell().store_uint(item_index, 64).store_slice(my_address()).end_cell();
|
||||
return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell();
|
||||
}
|
||||
|
||||
slice calculate_nft_item_address(int wc, cell state_init) {
|
||||
return begin_cell().store_uint(4, 3)
|
||||
.store_int(wc, 8)
|
||||
.store_uint(cell_hash(state_init), 256)
|
||||
.end_cell()
|
||||
.begin_parse();
|
||||
}
|
||||
|
||||
() deploy_nft_item(int item_index, cell nft_item_code, int amount, cell nft_content) impure {
|
||||
cell state_init = calculate_nft_item_state_init(item_index, nft_item_code);
|
||||
slice nft_address = calculate_nft_item_address(workchain(), state_init);
|
||||
var msg = begin_cell()
|
||||
.store_uint(0x18, 6)
|
||||
.store_slice(nft_address)
|
||||
.store_coins(amount)
|
||||
.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
|
||||
.store_ref(state_init)
|
||||
.store_ref(nft_content);
|
||||
send_raw_message(msg.end_cell(), 1); ;; pay transfer fees separately, revert on errors
|
||||
}
|
||||
|
||||
(int, int) encode_number_to_text(int number) inline {
|
||||
int len = 0;
|
||||
int value = 0;
|
||||
int mult = 1;
|
||||
do {
|
||||
(number, int res) = number.divmod(10);
|
||||
value = value + (res + 48) * mult;
|
||||
mult = mult * 256;
|
||||
len = len + 1;
|
||||
} until (number == 0);
|
||||
return (len, value);
|
||||
}
|
||||
|
||||
() send_royalty_params(slice to_address, int query_id, slice data) impure inline {
|
||||
var msg = begin_cell()
|
||||
.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool packages:MsgAddress -> 011000
|
||||
.store_slice(to_address)
|
||||
.store_coins(0)
|
||||
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||||
.store_uint(op::report_royalty_params(), 32)
|
||||
.store_uint(query_id, 64)
|
||||
.store_slice(data);
|
||||
send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message
|
||||
}
|
||||
|
||||
() recv_internal(cell in_msg_full, slice in_msg_body) impure {
|
||||
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
|
||||
return ();
|
||||
}
|
||||
slice cs = in_msg_full.begin_parse();
|
||||
int flags = cs~load_uint(4);
|
||||
|
||||
if (flags & 1) { ;; ignore all bounced messages
|
||||
return ();
|
||||
}
|
||||
slice sender_address = cs~load_msg_addr();
|
||||
|
||||
int op = in_msg_body~load_uint(32);
|
||||
int query_id = in_msg_body~load_uint(64);
|
||||
|
||||
var (owner_address, next_item_index, content, nft_item_code, royalty_params) = load_data();
|
||||
|
||||
if (op == op::get_royalty_params()) {
|
||||
send_royalty_params(sender_address, query_id, royalty_params.begin_parse());
|
||||
return ();
|
||||
}
|
||||
|
||||
throw_unless(401, equal_slices(sender_address, owner_address));
|
||||
|
||||
|
||||
if (op == 1) { ;; deploy new nft
|
||||
int item_index = in_msg_body~load_uint(64);
|
||||
throw_unless(402, item_index <= next_item_index);
|
||||
var is_last = item_index == next_item_index;
|
||||
deploy_nft_item(item_index, nft_item_code, in_msg_body~load_coins(), in_msg_body~load_ref());
|
||||
if (is_last) {
|
||||
next_item_index += 1;
|
||||
save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
|
||||
}
|
||||
return ();
|
||||
}
|
||||
if (op == 2) { ;; batch deploy of new nfts
|
||||
int counter = 0;
|
||||
cell deploy_list = in_msg_body~load_ref();
|
||||
do {
|
||||
var (item_index, item, f?) = deploy_list~udict::delete_get_min(64);
|
||||
if (f?) {
|
||||
counter += 1;
|
||||
if (counter >= 250) { ;; Limit due to limits of action list size
|
||||
throw(399);
|
||||
}
|
||||
|
||||
throw_unless(403 + counter, item_index <= next_item_index);
|
||||
deploy_nft_item(item_index, nft_item_code, item~load_coins(), item~load_ref());
|
||||
if (item_index == next_item_index) {
|
||||
next_item_index += 1;
|
||||
}
|
||||
}
|
||||
} until ( ~ f?);
|
||||
save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
|
||||
return ();
|
||||
}
|
||||
if (op == 3) { ;; change owner
|
||||
slice new_owner = in_msg_body~load_msg_addr();
|
||||
save_data(new_owner, next_item_index, content, nft_item_code, royalty_params);
|
||||
return ();
|
||||
}
|
||||
|
||||
if (op == 0xCB33) { ;; mint nft throw next index
|
||||
in_msg_body~skip_bits(64);
|
||||
slice forward_content = in_msg_body~load_ref().begin_parse();
|
||||
(int t_len, int t_xt) = encode_number_to_text(next_item_index);
|
||||
builder mint_content = begin_cell()
|
||||
.store_ref(
|
||||
begin_cell()
|
||||
.store_uint(0x6e6674732f6d657461646174612f, 112) ;; nfts/metadata/
|
||||
.store_uint(t_xt, t_len * 8)
|
||||
.store_uint(0x2e6a736f6e, 5 * 8) ;; .json
|
||||
.end_cell()
|
||||
)
|
||||
.store_slice(forward_content);
|
||||
|
||||
deploy_nft_item(next_item_index, nft_item_code, in_msg_body~load_coins(), mint_content.end_cell());
|
||||
next_item_index += 1;
|
||||
save_data(owner_address, next_item_index, content, nft_item_code, royalty_params);
|
||||
return ();
|
||||
}
|
||||
|
||||
if (op == 0xCB38) { ;; upgrade contract
|
||||
cell new_code = in_msg_body~load_maybe_ref();
|
||||
cell new_data = in_msg_body~load_maybe_ref();
|
||||
ifnot (new_code.null?()) {
|
||||
set_code(new_code);
|
||||
}
|
||||
ifnot (new_data.null?()) {
|
||||
set_data(new_data);
|
||||
}
|
||||
return ();
|
||||
}
|
||||
|
||||
throw(0xffff);
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
(int, cell, slice) get_collection_data() method_id {
|
||||
var (owner_address, next_item_index, content, _, _) = load_data();
|
||||
slice cs = content.begin_parse();
|
||||
return (next_item_index, cs~load_ref(), owner_address);
|
||||
}
|
||||
|
||||
slice get_nft_address_by_index(int index) method_id {
|
||||
var (_, _, _, nft_item_code, _) = load_data();
|
||||
cell state_init = calculate_nft_item_state_init(index, nft_item_code);
|
||||
return calculate_nft_item_address(0, state_init);
|
||||
}
|
||||
|
||||
(int, int, slice) royalty_params() method_id {
|
||||
var (_, _, _, _, royalty) = load_data();
|
||||
slice rs = royalty.begin_parse();
|
||||
return (rs~load_uint(16), rs~load_uint(16), rs~load_msg_addr());
|
||||
}
|
||||
|
||||
cell get_nft_content(int index, cell individual_nft_content) method_id {
|
||||
var (_, _, content, _, _) = load_data();
|
||||
slice cs = content.begin_parse();
|
||||
cs~load_ref();
|
||||
slice common_content = cs~load_ref().begin_parse();
|
||||
return (begin_cell()
|
||||
.store_uint(1, 8) ;; offchain tag
|
||||
.store_slice(common_content)
|
||||
.store_ref(individual_nft_content)
|
||||
.end_cell());
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
;;
|
||||
;; TON NFT Item Smart Contract v2
|
||||
;; support ownership_assigned on minting nft
|
||||
;;
|
||||
|
||||
{-
|
||||
|
||||
NOTE that this tokens can be transferred within the same workchain.
|
||||
|
||||
This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions:
|
||||
|
||||
1) use more expensive but universal function below to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`)
|
||||
|
||||
2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain)
|
||||
|
||||
-}
|
||||
|
||||
#include "imports/stdlib.fc";
|
||||
#include "imports/op-codes.fc";
|
||||
#include "imports/params.fc";
|
||||
|
||||
int min_tons_for_storage() asm "50000000 PUSHINT"; ;; 0.05 TON
|
||||
|
||||
;;
|
||||
;; Storage
|
||||
;;
|
||||
;; uint64 index
|
||||
;; MsgAddressInt collection_address
|
||||
;; MsgAddressInt owner_address
|
||||
;; cell content
|
||||
;;
|
||||
|
||||
(int, int, slice, slice, cell) load_data() {
|
||||
slice ds = get_data().begin_parse();
|
||||
var (index, collection_address) = (ds~load_uint(64), ds~load_msg_addr());
|
||||
if (ds.slice_bits() > 0) {
|
||||
return (-1, index, collection_address, ds~load_msg_addr(), ds~load_ref());
|
||||
} else {
|
||||
return (0, index, collection_address, null(), null()); ;; nft not initialized yet
|
||||
}
|
||||
}
|
||||
|
||||
() store_data(int index, slice collection_address, slice owner_address, cell content) impure {
|
||||
set_data(
|
||||
begin_cell()
|
||||
.store_uint(index, 64)
|
||||
.store_slice(collection_address)
|
||||
.store_slice(owner_address)
|
||||
.store_ref(content)
|
||||
.end_cell()
|
||||
);
|
||||
}
|
||||
|
||||
() send_msg(slice to_address, int amount, int op, int query_id, builder payload, int send_mode) impure inline {
|
||||
var msg = begin_cell()
|
||||
.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 011000
|
||||
.store_slice(to_address)
|
||||
.store_coins(amount)
|
||||
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||||
.store_uint(op, 32)
|
||||
.store_uint(query_id, 64);
|
||||
|
||||
if (~ builder_null?(payload)) {
|
||||
msg = msg.store_builder(payload);
|
||||
}
|
||||
|
||||
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) impure inline {
|
||||
throw_unless(401, equal_slices(sender_address, owner_address));
|
||||
|
||||
slice new_owner_address = in_msg_body~load_msg_addr();
|
||||
force_chain(new_owner_address);
|
||||
slice response_destination = in_msg_body~load_msg_addr();
|
||||
in_msg_body~load_int(1); ;; this nft don't use custom_payload
|
||||
int forward_amount = in_msg_body~load_coins();
|
||||
|
||||
int rest_amount = my_balance - min_tons_for_storage();
|
||||
if (forward_amount) {
|
||||
rest_amount -= (forward_amount + fwd_fees);
|
||||
}
|
||||
int need_response = response_destination.preload_uint(2) != 0; ;; if NOT addr_none: 00
|
||||
if (need_response) {
|
||||
rest_amount -= fwd_fees;
|
||||
}
|
||||
|
||||
throw_unless(402, rest_amount >= 0); ;; base nft spends fixed amount of gas, will not check for response
|
||||
|
||||
if (forward_amount) {
|
||||
send_msg(new_owner_address, forward_amount, op::ownership_assigned(), query_id, begin_cell().store_slice(owner_address).store_slice(in_msg_body), 1); ;; paying fees, revert on errors
|
||||
}
|
||||
if (need_response) {
|
||||
force_chain(response_destination);
|
||||
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);
|
||||
}
|
||||
|
||||
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
|
||||
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
|
||||
return ();
|
||||
}
|
||||
|
||||
slice cs = in_msg_full.begin_parse();
|
||||
int flags = cs~load_uint(4);
|
||||
|
||||
if (flags & 1) { ;; ignore all bounced messages
|
||||
return ();
|
||||
}
|
||||
slice sender_address = cs~load_msg_addr();
|
||||
|
||||
cs~load_msg_addr(); ;; skip dst
|
||||
cs~load_coins(); ;; skip value
|
||||
cs~skip_bits(1); ;; skip extracurrency collection
|
||||
cs~load_coins(); ;; skip ihr_fee
|
||||
int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs
|
||||
|
||||
|
||||
(int init?, int index, slice collection_address, slice owner_address, cell content) = load_data();
|
||||
if (~ init?) {
|
||||
throw_unless(405, equal_slices(collection_address, sender_address));
|
||||
var new_owner_address = in_msg_body~load_msg_addr();
|
||||
store_data(index, collection_address, new_owner_address, in_msg_body~load_ref());
|
||||
if (in_msg_body.slice_data_empty?() == false) {
|
||||
var forward_amount = in_msg_body~load_coins();
|
||||
if (forward_amount) {
|
||||
send_msg(new_owner_address, forward_amount, op::ownership_assigned(), 0, begin_cell().store_slice(collection_address).store_slice(in_msg_body), 3); ;; paying fees, ignore errors
|
||||
}
|
||||
}
|
||||
return ();
|
||||
}
|
||||
|
||||
int op = in_msg_body~load_uint(32);
|
||||
int query_id = in_msg_body~load_uint(64);
|
||||
|
||||
if (op == op::transfer()) {
|
||||
transfer_ownership(my_balance, index, collection_address, owner_address, content, sender_address, query_id, in_msg_body, fwd_fee);
|
||||
return ();
|
||||
}
|
||||
if (op == op::get_static_data()) {
|
||||
send_msg(sender_address, 0, op::report_static_data(), query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message
|
||||
return ();
|
||||
}
|
||||
throw(0xffff);
|
||||
}
|
||||
|
||||
;;
|
||||
;; GET Methods
|
||||
;;
|
||||
|
||||
(int, int, slice, slice, cell) get_nft_data() method_id {
|
||||
(int init?, int index, slice collection_address, slice owner_address, cell content) = load_data();
|
||||
return (init?, index, collection_address, owner_address, content);
|
||||
}
|
||||
Reference in new issue
Block a user