While this is still going to be a technical discussion, we're finally starting to put all of our features to work. Discussion on and off chain has been invaluable to development and these posts serve as thought-ware to help flush out features that make the best product. This post will discuss the mechanics of auctions, a key all-party process to well establish real time values of Non-Fungible Assets in the same way D/C Exchanges are all party process to determine the real time value of fungible assets.
Continuing from:
The above posts contain prototype level code that will continue to be used in this post, so if you haven't strapped in yet, you might fall off the ride. If you're not here for the code and just looking at features and discussion - let's go!
Starting with user interface, there might be either a list of your accounts NFTs, or an NFT you are looking at may have an auction button. Which will then have a starting price, and a time. Optionally, a 'Buy Token Now' price could be specified.
Once decided these options will be signed to Hive via a custom json and our layer 2 will pick up the transaction with the following code:
Payload: json.{set:"punk", uid:"A6", p:1000, t:7} to list punk:A6 at 1.000 for 7 days
1|exports.nft_auction = function(json, from, active, pc) {
2| let fnftp = getPathObj(['nfts', from]),
3| ahp = getPathObj(['ah']);
4| setp = getPathObj(['sets', json.set])
5| Promise.all([fnftp, ahp, setp])
6| .then(mem => {
7| if (mem[0][`${json.set}:${json.uid}`] && !mem[0][`${json.set}:${json.uid}`].l && active){
8| var ah = mem[1],
9| nft = mem[0][`${json.set}:${json.uid}`],
10| set = mem[2], fnfts = mem[0]
11| delete fnfts[`${json.set}:${json.uid}`] //remove nft from from
12| var p = json.p || 1000,
13| t = json.t || 7
14| if(typeof t != number || t > 30 || t < 1 )t = 7
15| if(typeof p != number || p < 1)p = 1000
16| const e = json.block_num + (json.t * 1200 * 24),
17| ep = chronAssign(e, {op:"ahe", item:`${json.set}:${json.uid}`, block: e})
18| ep.then(exp => {
19| var listing = {
20| p, //starting price
21| t, //time in days
22| e, //expires
23| x: exp, //expire path / vop
24| o: from
25| }
26| set.u.replace(`${json.uid}_${json.from}`, `${json.uid}_ah`)
27| var last_modified = nft.s.split(',')[0]
28| nft.s.replace(last_modified, Base64.fromNumber(json.block_num))
29| listing.nft = nft
30| ah[`${json.set}:${json.uid}`] = listing //place the listing in the AH
31| ops.push([{type:'put', path:['nfts', from], data: fnfts}])
32| ops.push([{type:'put', path:['ah'], data: ah}])
33| ops.push([{type:'put', path:['sets', json.set], data: set}])
34| let msg = `@${from} Listed ${json.set}:${json.uid} for auction`
35| if (config.hookurl) postToDiscord(msg)
36| ops.push({ type: 'put', path: ['feed', `${json.block_num}:${json.transaction_id}`], data: msg });
37| store.batch(ops, pc)
38| })
39| } else if (!active){
40| let msg = `@${from} tried to auction with out signing ACTIVE`
41| if (config.hookurl) postToDiscord(msg)
42| pc[0](pc[2])
43| } else {
44| let msg = `@${from} doesn't own ${json.set}:${json.uid}`
45| if (config.hookurl) postToDiscord(msg)
46| pc[0](pc[2])
47| }
48| })
49|}
nameOfLayer2_nft_auctionahe: Auction House EndaheUniqueID_Owner to UniqueID_ahah directoryEach Node on the layer 2 should be providing identical API to view the listings, so there will be some user interface(s) that will show whats available. This UI should also utilize local storage to keep up on which items you are watching and what you've bid on... so you can tell when you've been outbid.
Below is the code for a bid. Much like in an actual auction, the only available actions are bid.
Payload: json.{b:1002, set:"punk", uid:"A6"} bidding 1.002 for punk:A6
1|exports.nft_bid = function(json, from, active, pc) {
2| let balp = getPathNum(['balances', from]),
3| ahp = getPathObj(['ah', `${json.set}:${json.uid}`])
4| Promise.all([balp, ahp])
5| .then(mem => {
6| if(active && mem[1].p && mem[0] >= json.bid){
7| var listing = mem[1],
8| bal = mem[0]
9| if(listing.b){
10| if (json.bid > listing.b){
11| add(listing.f, listing.b)
12| .then(empty => {
13| listing.f = from
14| listing.b = json.bid; listing.c++
15| bal = bal - json.bid
16| var ops = []
17| ops.push([{type:'put', path:['ah', `${json.set}:${json.uid}`], data: listing}])
18| ops.push([{type:'put', path:['balances', from], data: bal}])
19| let msg = `@${from} bid ${parseFloat(json.bid/1000).toFixed(3)} ${config.TOKEN} on ${json.set}:${json.uid}'s auction`
20| if (config.hookurl) postToDiscord(msg)
21| ops.push({ type: 'put', path: ['feed', `${json.block_num}:${json.transaction_id}`], data: msg });
22| store.batch(ops, pc)
23| })
24| } else {
27| // low bid message
26| }
27| } else {
28| //roughly the same as above with out return... initial bid
29| }
30| } else {
31| //error msg
34| }
35| })
36|}
nameOfLayer2_nft_bidfrom's balance and the auction house from memoryfrom has enough tokens to make the bid, and that the item is up for auction with a lower bidfrom's bid is higherfrom's balanceWhen an auction is over the layer 2 will automatically process the listing and make several balance adjustments. This is done because a Virtual Op was set up at the listing, and every block the processor checks for any operations in memory under the current block number.
switch (b.op) {
case 'ahe':
let ahp = getPathObj(['ah', b.item]);
setp = getPathObj(['sets', b.item.split(':')[0]])
promises.push(AHEOp([ahp, setp], delKey, num, b)) //ensure Vops end before the next transaction is processed
...
function AHEOp(promies, delkey, num, b) {
return new Promise((resolve, reject) => {
Promise.all(promies)
.then(mem => {
let listing = mem[0],
set = mem[1],
ops = []
// const fee = parseInt(listing.b /100); add('rn', fee); listingb = listing.b - fee;
if (set.r > 0){
let royalty = parseInt(listing.b * set.r / 100)
add(set.a, royalty) //distribute royalty
add(listing.o, listing.b - royalty) //distribute rest
} else {
add(listing.o, listing.b)
}
nft = listing.nft
const last_modified = nft.s.split(',')[0]
nft.s.replace(last_modified, Base64.fromNumber(num)) //update last modified
set.u.replace(`${b.item.split(':')[1]}_ah`, `${b.item.split(':')[1]}_${listing.f}`) //update set
ops.push({ type: 'del', path: ['ah', b.item] }) //delete the listing
ops.push({ type: 'put', path: ['sets', b.item.split(':')[0]], data: set }) //update set
ops.push({ type: 'put', path: ['nfts', listing.f, b.item], data: nft }) //update nft
ops.push({ type: 'put', path: ['feed', `${num}:vop_${delkey.split(':')[1]}`], data: `Auction of ${b.item} has ended for ${parseFloat(listing.b / 1000).toFixed(3)} ${config.TOKEN}` })
ops.push({ type: 'del', path: ['chrono', delkey] })
store.batch(ops, [resolve, reject])
})
...
You may (or may not, sorry) be able to see we can securely auction these NFTs with minimal code. All the layer 2 nodes run this code and all of the NFTs they can support will be able to change the ownership, pay royalties, and enforce liens.
Building our features first let's us ensure that our final product will have the ability to expand into these use cases. For example, even if we don't have any code to put a lien holder on an NFT, we can think ahead and not allow a sale, auction, or transfer if a lien holder exists.
And thus far we haven't run in to any snags that won't let us implement our feature set.
Secure Transfers will work in much the same way, except only one party will be able to fulfill the listing.
My last NFT research post is on Minting... now that we've established all our features. That post will of course contain an announcement for a founders/testing set of DLUX tokens.