NFT Research - Auctions and Secure Transfer

Building Economies

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!

Auctioning an NFT

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|}
  • 1: Function name of custom JSON ID nameOfLayer2_nft_auction
  • 2-4: Uses the custom JSON and payload to call the NFT, Set, and Auction House from the database
  • 7: Checks that the active key was used, and that the account owns the NFT, and no lien holder's are present
    • 38-46 Reports any error to a stream, and tells the Process Chain to compute the next transaction
  • 11: Removes the NFT from the owners directory
  • 12-15: Sets and verifies initial price and days of auction are valid.
  • 16: Calculates the last block of the auction
  • 17: Build's the Virtual Op that will settle the auction: ahe: Auction House End
  • 18: Get's the memory location of the ahe
  • 19-25: Builds the Listing
  • 26: Modifies the Set locator from UniqueID_Owner to UniqueID_ah
  • 27-28: Updates the Last Modified of the NFT to the current block number
  • 29: Places the NFT is the listing
  • 30: Places the Listing in the ah directory
  • 31-33: Builds a batch memory update to store the above changes
  • 34-36: Builds an information string for on chain, and possible off chain streaming
  • 37: Sends the batch memory operation to be stored, along with the process chain to start the next transaction in sequence in a memory safe way.

Bidding in an Auction

Each 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|}
  • 1: Function name of custom JSON ID nameOfLayer2_nft_bid
  • 2-3: Pulls from's balance and the auction house from memory
  • 6: Checks that from has enough tokens to make the bid, and that the item is up for auction with a lower bid
  • 9: Checks for previous bid
  • 10: Check from's bid is higher
  • 11: Returns the previous high bidder's tokens
  • 12->: Updates and saves the Listing and from's balance
  • 14: Keeps track of the total number of valid bids

Ending an Auction

When 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])
  })
...

Summary

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.

😉

H2
H3
H4
3 columns
2 columns
1 column
6 Comments