Auction Contract – Part 1

Auction Contract – Part 1

Overview

In the next few posts, we will build a sample auction contract.

The auction contract is simple, but sufficient for many use cases. We’ll start out with the template for the contract. The template contains the reducers, which are the named bits of contract code that get executed when actions are sent to the contract. For the auction contract, there are 3 reducers:

  • start – Start the auction by adding an item and the number of days the auction will run. The item can be any coin.
  • bid – Bid on the item. Coins are sent to bid on the item. The highest bid is retained in the contract, all other bids are sent back.
  • close – Close the contract. If the end date is passed, the item coin is transferred to the highest bidder, and the highest bid is sent to the owner of the item. If there are no bids, the item is returned to the auction owner.

start reducer

(
  $i := $inputs[0];
  $o := $i.output;
  $item := $o.coins[0];
  $itemOwner := $x.ledger($o.ledger);
  $start := $x.dt.now();
  $end := $x.dt.datefns.addDays($start,$i.other.days);
  $newstate := {
    'highestBid': null,
    'highestBidder': null,
    'start': $start,
    'end': $end,
    'item': $item,
    'itemOwner': $itemOwner
  };
  $x.result($newstate) 
)
  1. Get the input in $i, and reference the output in $o. The output contains the coin we want to auction.
  2. Get the $item from the output. Remember, coins in Rekoner can represent any asset.
  3. Get the $itemOwner. Note the use of the ledger extension function. This will extract a readable name, if one is available in the environment the contract is being run. Since we are in the development environment, it will get the name attached to the ledger. In a production environment, there is no name – a ledger is a public key (or equivalent), but it’s handy to have the name when developing.
  4. The $startDate is set to now, and the end data is set to a variable number of days from now. The variable is extracted from the other field of the input, which can be any JSON data passed in on the input.
  5. The last statement in a JSONata expression is the return value. In this case, it’s the return value expected from all reducers, which can be constructed with the result extension function. For the start reducer, the only thing we need to pass back is the $newstate of the contract. The $newstate for the auction contract contains the highestBid, the highestBidder, the start date, the end date, the item, and the itemOwner. This state will get updated by future actions sent to the contract.

bid reducer

(
  $i := $inputs[0];
  $o := $i.output;
  $bidder := $x.ledger($o.ledger);
  $now := $x.dt.now();
  $expired := $x.dt.date($state.end);
  $expired := $x.dt.datefns.isAfter($now, $expires);
  $expired ? (
    $output = $x.output($bidder, 'bid', {
      'msg': 'auction expired'
    }, $o.coins);
    $x.result($state,[$output]);
  ) : (
    $bid := $o.coins[0];
    $hbid := $state.highestBid;
    $highest := $hbid = null ? true : $x.coin.greaterThan($bid, $hbid);
    $obidder := $highest ? 
      $x.output($bidder, 'bid', {
        'msg': 'bid accepted'
      }) : 
      $x.output($bidder, 'bid', {
        'msg': 'bid too low or wrong coin'
      }, $o.coins);
    $newstate := $highest ? $merge([$state, {
      'highestBid': $bid,
      'highestBidder': $bidder
    }]) : $state;
    $returnBid := $highest and $hbid != null ?
      $x.output($state.highestBidder, 'bid', {
        'msg': 'you were out bid'
      }, [$hbid]) : null;

    $outputs := $returnBid = null ? [$obidder] : [$obidder, $returnBid];
    $x.result($newstate, $outputs);
  )
)
  1. Check whether the auction has $expired. A quick note – conditionals in JSONata use a ternary notation. If the auction has expired, we send an output to the bidder (via x.output), returning his bid with a message saying the auction has expired.
  2. If the auction is still active, we check to see if the bid is higher than the existing highest bid (or if it’s the first bid). See $highest. If it’s the highest bid an output is constructed for the bidder indicating the bid was accepted. Otherwise the output indicates the bid was too low and returns the coin that was bid.
  3. The $newstate of the contract is the old state merged with the new highestBid and highestBidder if the bid is accepted. Otherwise the state is not changed.
  4. A $returnBid output is constructed which returns the previous highest bid to the ledger that was out bid.
  5. The $outputs for the reducer result are the ouput to the bidder ($obidder), and the $returnBid, if one exists.
  6. The reducer result is constructed with the $newstate, and the new $outputs.

close reducer

(
  $owner := $state.itemOwner;
  $bidder := $state.highestBidder;
  $closer := $x.ledger($action.ledger);
  $owner = $closer or $bidder = $closer  ? (
    $now := $x.dt.now();
    $expired := $x.dt.date($state.end);
    $x.log('now', $now);
    $x.log('expired', $expired);
    $isExpired := $x.dt.datefns.isAfter($now, $expired);
    $isExpired ? (
      $state.highestBidder != null ? (
        $oowner := $x.output($owner, 'auction', {
          'msg': 'your item was sold!'
        }, [$state.highestBid]);
        $obidder := $x.output($owner, 'auction', {
          'msg': 'your bid won!'
        }, [$state.item]);
        $x.result($state, [$oowner, $obidder]);
      ) : (
        $output := $x.output($closer, 'auction', {
          'msg': 'no bids for item'
        }, [$state.item]);
        $x.result($state, [$output]);
      )
    ) : (
      $output := $x.output($closer, 'auction', {
        'msg': 'auction still active'
      });
      $x.result($state,[$output]);
    )
  ) : (
    $output = $x.output($closer, 'auction', {
      'msg': 'not owner or highest bidder of auction'
    });
    $x.result($state,[$output]);
  )
)

  1. If the person sending the action is either the highestBidder or the itemOwner, they can close the auction, otherwise the sender is sent an output with an error message.
  2. It the auction is still active, an error output is returned to the $closer.
  3. If there were no bids (highestBidder = null), the item ($state.item) is returned to the $owner.
  4. Otherwise, the item is sent to the state.highestBidder in an output, and the state.highestBid is sent to the $owner.
2018-11-21T08:08:51+00:00September 23rd, 2018|Categories: example, JSONata, programming|Tags: , , |