hashgraph / hedera-nft-auction-demo

Demo NFT auction
Apache License 2.0
24 stars 11 forks source link
hacktoberfest

Build codecov License Discord

Hedera Non Fungible Token Auction Demo

Dependencies

Notes

The java projects use Lombok, ensure that the plug is installed in your IDE and configured properly Lombok Plugin

Note that enabling annotation processing differs between versions of IntelliJ Preferences > Compiler > Annotation Processors before IntelliJ2017, starting with IntelliJ 2017, the "Enable Annotation Processing" checkbox has moved to: Settings > Build, Execution, Deployment > Compiler > Annotation Processors

Description

This project consists of two main modules, a Java back-end and a front-end.

The front end displays auctions and enables users to place bids (optionally requires a browser plug in to sign transactions) and monitor the auction's progress.

The back end fulfils 3 separate roles

The latter can be run in readonly mode, meaning the processing will validate bids but will not be able to participate in refunds or token transfers on auction completion.

Note: The three roles can be run within a single instance of the Java application or run in individual Java instances, for example, one instance could process bids, while one or more others could serve the UI REST API for scalability purposes. This is determined by environment variable parameters. The Docker deployment runs two instances, one for bid processing, the other for the UI and Admin REST APIs for example.

Additional detailed documentation on how the system is architected may be found in the docs folder of this project.

Code Audit

While being a demonstration, the code within this repository has undergone a code audit which was performed by a third party.

You may find the result of the code audit here

And proof of the document's authenticity on ProvenDB

Setup, compilation, execution for developer testing

Pull the repository from github

git clone https://github.com/hashgraph/hedera-nft-auction-demo.git

Generating certificates

The solution supports https by default and therefore requires certificates. A certs.sh script found under docker-files enables you to create self-signed certificates for testing.

The certs.sh script will create a certificate (cert.pem) and key (key.pem) under docker-files. These files are referenced in the .env file.

For a production deployment, it is recommended that you supply appropriate certificates. you may use .jks, .pfx or .p12 certificates with an associated password, or .pem with an associated .pem key for the java servers. For the Javascript UI, the files should be compatible with nginx.

Only for standalone Java, leave blank or comment out for Docker

HTTPS_KEY_OR_PASS=../docker-files/key.pem
HTTPS_CERTIFICATE=../docker-files/cert.pem

If you provide own certificates for production, update the .env file and docker-files/conf.d/default.conf if the filenames are different.

default.conf example, note the ssl_certificate directives are repeated three times in the file.

    ...
    ssl_certificate     /demo/cert.pem;
    ssl_certificate_key /demo/key.pem;
    ...    

With docker

This section assumes you have docker and docker-compose installed, if not, please consult the following links to perform the installation.

Docker containers for the database, ui, rest api and bid processing are all included. In addition an NGINX proxy is started to manage incoming http requests and distribute them to the appropriate container.

By default, the NGINX proxy listens on port 80 and redirects http requests to https.

Setup environment

cd hedera-nft-auction-demo
cd docker-files
cp .env.sample .env
nano .env

setup the .env properties as follows

note: you may generate a new API key as follows, although any string will work

./gradlew generateApiKey

you may leave the other properties as they are for now

cd hedera-nft-auction-demo
cd hedera-nft-auction-demo-javascript-client
cp .env.sample .env

you may leave the properties as they are for now

Start docker containers

Using pre-built images

cd hedera-nft-auction-demo/scripts
./run.sh image

Building your own images from source code

cd hedera-nft-auction-demo/scripts
./run.sh compile

you may now navigate to http://localhost:8080 to verify the UI is up and running, it should indicate no auctions are currently setup.

Stop docker containers

Using pre-built images

cd hedera-nft-auction-demo/scripts
./stop.sh image

Built from code

cd hedera-nft-auction-demo/scripts
./stop.sh compile

Show docker container logs

Using pre-built images

cd hedera-nft-auction-demo/scripts
./log.sh image

Built from code

cd hedera-nft-auction-demo/scripts
./log.sh compile

Create a sample auction

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '{
  "symbol":"MTT",
  "name":"Test Token",
  "clean":false
}'  https://localhost:8082/v1/admin/easysetup

Restart the docker containers for the topic to be taken into account

Stop and restart the containers

cd hedera-nft-auction-demo/scripts
./stop.sh image
./run.sh image

or

cd hedera-nft-auction-demo/scripts
./stop.sh compile
./run.sh compile

You should see logs similar to

nft-auction-demo-node | 2021-04-23 12:26:27.063 INFO com.hedera.demo.auction.node.app.subscriber.TopicSubscriber - Auction for token 0.0.539174 added (150) nft-auction-demo-node | 2021-04-23 12:26:27.063 INFO com.hedera.demo.auction.node.app.subscriber.TopicSubscriber - Auction for token 0.0.539174 added (150) nft-auction-demo-node | 2021-04-23 12:26:29.022 INFO com.hedera.demo.auction.node.app.readinesswatcher.HederaAuctionReadinessWatcher - Watching auction account Id 0.0.539175, token Id 0.0.539174 (36) nft-auction-demo-node | 2021-04-23 12:26:29.024 DEBUG com.hedera.demo.auction.node.app.readinesswatcher.HederaAuctionReadinessWatcher - Checking ownership of token 0.0.539174 for account 0.0.539175 (52) nft-auction-demo-node | 2021-04-23 12:26:29.364 INFO com.hedera.demo.auction.node.app.readinesswatcher.AbstractAuctionReadinessWatcher - Account 0.0.539175 owns token 0.0.539174, starting auction (70) nft-auction-demo-node | 2021-04-23 12:26:39.380 DEBUG com.hedera.demo.auction.node.app.bidwatcher.HederaBidsWatcher - Checking for bids on account 0.0.539175 and token 0.0.539174 (38) ...

you may now navigate to https://localhost:8080 to verify the UI is up and running, it should show the auction created above (it may take a few seconds to appear).

Notes

Standalone

Java Appnet Node

cd hedera-nft-auction-demo
cd hedera-nft-auction-demo-java-node

# Build the code
./gradlew assemble

setup the environment

cp .env.sample .env
nano .env

set the following properties according to your Hedera account details

Note: The operator id/key is used to query the hedera network for the token's metadata if present (FileId in memo) and issue or sign scheduled transactions.

You may edit additional parameters such as MIRROR_PROVIDER, etc... if you wish (although only the hedera mirror API is supported at this time).

start the application

./gradlew runNode

Javascript UI

Note: The UI assumes it is served from the same server as the client REST api (The Java Appnet Node above)

Edit environment variables

cd hedera-nft-auction-demo
cd hedera-nft-auction-demo-javascript-client
cp .env.local.sample .env.local
nano .env.local
cd hedera-nft-auction-demo
cd hedera-nft-auction-demo-javascript-client
npm install
npm run-script build

Setting up an auction

A number of helper functions are available from the project in order to get you started quickly.

Super simple

This command takes a number of parameters runs all the necessary steps to create a demo auction:

This requires that the REST api and database are up and running

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '{
  "symbol":"MTT",
  "name":"Test Token",
  "clean":false
}' https://localhost:8082/v1/admin/easysetup

Step by step

The examples below show curl commands, however the hedera-nft-auction-demo-java-node project includes postman files for the admin and client APIS which you can import into Postman instead.

Create a topic

This will create a new topic id and set the TOPIC_ID in the .env file.

It is now necessary to restart the application for the changes to take effect.

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
https://localhost:8082/v1/admin/topic

returns a topic id

{
    "topicId": "0.0.57044"
}

Create a simple token

This command will create a token named Token Name, an initial supply of 1, 0 decimals and add a memo to the token.

If image, certificate or description are included, files will be created on IPFS and will be referenced in a metadata file also on IPFS. The metadata file URI will be used for the token's symbol. If neither of these attributes are set, the symbol attribute is used.

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "name": "Token Name",
  "symbol": "Symbol",
  "initialSupply": 1,
  "decimals": 0,
  "memo": "token memo",
  "description": {
    "type": "string",
    "description": "Describes the asset to which this token represents."
  },
  "image": {
    "type": "base64",
    "description": "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"
  },
  "certificate": {
    "type": "base64",
    "description": "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"
  }
}
' https://localhost:8082/v1/admin/token

or

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "name": "Token Name",
  "symbol": "Symbol",
  "initialSupply": 1,
  "decimals": 0,
  "memo": "token memo",
  "description": {
    "type": "string",
    "description": "Describes the asset to which this token represents."
  },
  "image": {
    "type": "file",
    "description": "GoldCoin.jpg"
  },
  "certificate": {
    "type": "file",
    "description": "silver.jpg"
  }
}
' https://localhost:8082/v1/admin/token

returns a token id

{
    "tokenId": "0.0.58792"
}

Create an auction account

This command will create an auction account with an initial balance of 100 hbar and use the operator key for the account.

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "keylist": {
    "keys": [
      {
        "key": "302a300506032b65700321005b73818c623c27ae05f96cbcb83703538034eafa1f381480da220612881435c5"
      }
    ],
    "threshold": 1
  },
  "initialBalance": 100
}' https://localhost:8082/v1/admin/auctionaccount

returns an account id

{
    "accountId": "0.0.58793"
}

Create an auction account with a key list

This command will create an auction account with an initial balance of 100 hbar and a key list for scheduled transactions.

_Note: if the environment file contains an entry for MASTER_KEY, it will automatically be added to the keylist below with a threshold of 1, resulting in an auction account having a threshold key of 1 of 2, one of the keys being the master key, the other being the threshold key supplied in the JSON._

Note: all keys are public keys

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "keylist": {
    "keys": [
      {
        "key": "302a300506032b657003210090ec5045925d37b358ee0c60f858dc79c3b4370cbf7e0c5dad882f1171265cb3"
      },
      {
        "key": "302a300506032b657003210076045799d169c6b6fc2bf45f779171a1cb10fd239b4f758bc556cb0de6799105"
      }
    ],
    "threshold": 2
  },
  "initialBalance": 100
}' https://localhost:8082/v1/admin/auctionaccount

returns an account id

{
    "accountId": "0.0.58793"
}

Create the auction

be sure the replace {{tokenId}}, {{accountId}} in the json below with the values you obtained earlier.

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "tokenid": "{{tokenId}}",
  "auctionaccountid": "{{accountId}}",
  "reserve": 0,
  "minimumbid": 1000000,
  "endtimestamp": "",
  "winnercanbid": true,
  "title": "Auction title",
  "description": "Auction description"
}' https://localhost:8082/v1/admin/auction

Note: the minimum bid and reserve are expressed in tinybars

Transfer the token to the auction account

This transfer the token from the account that created it to the auctionaccountid, supply the tokenId and accountId created above in the parameters.

be sure the replace {{tokenId}}, {{accountId}} in the json below with the values you obtained earlier.

Note: If the token has been created with the REST api call above, it will already by associated and owned by the auctionaccountid, there is no need to transfer it.

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "tokenid" : "{{tokenId}}",
  "auctionaccountid" : "{{accountId}}"
}' https://localhost:8082/v1/admin/transfer

Run the components

Note: Each of the steps below need to be run from a different command line window

cd hedera-nft-auction-demo

Appnet node and REST API

cd hedera-nft-auction-demo-java-node
java -jar build/libs/hedera-nft-auction-demo-1.0.jar

Web UI

cd hedera-nft-auction-demo-javascript-client
npm start

Developing new features requiring database changes

This is only required in order to create/modify database objects, it happens automatically when the application is launched too.

Setup the environment

for the commands below, the following environment variables need to be set

export DATABASE_URL="postgresql://localhost:5432/"
export POSTGRES_DB="schema name"
export POSTGRES_USER="youruser"
export POSTGRES_PASSWORD="yourpassword"

Setup the database objects

./gradlew flywayMigrate

Build the database classes

./gradlew jooqGenerate

Testing

The project contains a number of tests suites which are described below

Unit testing

Unit testing is run automatically when you ./gradlew build, you can run these independently with ./gradlew test.

There are no interactions with the database or the Hedera network in these tests.

Integration testing

Integration testing is run automatically when you ./gradlew build, you can run these independently with ./gradlew testIntegration.

These tests include testing the outcome of various operations in the database.

System testing

System testing is run with ./gradlew testSystem.

These tests include testing the outcome of various operations in the database and invoke Hedera APIs.

Load testing

In order to load test the REST API, a Jmeter sample file is available within hedera-nft-auction-demo-java-node\rest-load-test. This file will exercise the client REST API. This is a simple test file which queries 4 different endpoints with a given auction id and bidder account id.

To run it, first install Apache JMeter. (Note: we used version 5.3)

then in a terminal

cd hedera-nft-auction-demo-java-node\rest-load-test
rm -f AssertionResults.xml
rm -f GraphResults.csv
rm -f SummaryReport.csv

{PATH_TO_JMETER}/jmeter -n -f \
-Jbidaccount=0.0.2167090 \
-Jauctionid=1 \
-t client-api-test.jmx

Ensure you specify a valid auctionid, bidaccountid to ensure positive responses from the API, else the script will fail (it expects a 200 response for all calls).

By default, the script will run with 20 threads, ramp-up over 60 seconds and run for 150 seconds. The API calls will be targetted at https://localhost:8081.

You may override these defaults by specifying additional parameters on the command line:

Results of the test will be found in the following files:

Token Specification

Binaries from base64

If you'd like to attach an image and optionally a certificate document to the token, convert the two binary files to base64 and include in the JSON below.

Note: The type for the image and certificate is base64

{
  "name": "Token Name",
  "symbol": "Symbol",
  "initialSupply": 1,
  "decimals": 0,
  "memo": "token memo",
  "description": {
    "type": "string",
    "description": "Describes the asset to which this token represents."
  },
  "image": {
    "type": "base64",
    "description": "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"
  },
  "certificate": {
    "type": "base64",
    "description": "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"
  }
}

Binaries from files

If you'd like to attach an image and optionally a certificate document to the token using existing binary files, include the name of the files in the JSON below. The path where the files should be located is defaulted to sample-files/ and may be modified in the .env file. Note: If the path contains a .env file, an error will be generated.

Note: The type for the image and certificate is file

{
  "name": "Token Name",
  "symbol": "Symbol",
  "initialSupply": 1,
  "decimals": 0,
  "memo": "token memo",
  "description": {
    "type": "string",
    "description": "Describes the asset to which this token represents."
  },
  "image": {
    "type": "file",
    "description": "GoldCoin.jpg"
  },
  "certificate": {
    "type": "file",
    "description": "silver.jpg"
  }
}

Use of IPFS

If image, certificate or description are included above, files will be created on IPFS and will be referenced in a metadata file also on IPFS. The metadata file URI will be used for the token's symbol.

If neither of these attributes are set, the symbol attribute is used.

For example, the above token specifications would result in a file on IPFS containing this JSON data:

{
  "name": "Token Name",
  "symbol": "Symbol",
  "initialSupply": 1,
  "decimals": 0,
  "memo": "token memo",
  "description": {
    "type": "string",
    "description": "Describes the asset to which this token represents."
  },
  "image": {
    "type": "string",
    "description": "https://cloudflare-ipfs.com/ipfs/bafkreieqtmwpcj75uff42bmmk3wldjdjmf2fpmi6zjkbdv67qsbzgs22qa"
  },
  "certificate": {
    "type": "string",
    "description": "https://cloudflare-ipfs.com/ipfs/bafkreieqtmwpcj75uff42bmmk3wldjdjmf2fpmi6zjkbdv67qsbzgs22qa"
  }
}

Where the image.description and certificate.description attributes are links to the provided image and description binaries on IPFS.

The uri to this JSON file on IPFS will be stored in the created token's symbol.

Full Application network setup

The back-end can be considered a node within the application network, there may be several such nodes in operation to ensure full decentralisation of the auction process. Such nodes fall into three categories:

Note: This Master Node will eventually become a Validator Node in its own right once the whitelisting of the Token Associate and Account Update transactions has occurred. It is a temporary solution in the mean time.

Determining the node type

Depending on the type of node you're setting up, you may need different information or may need to submit some information to other node operators.

Readonly Node

You must acquire the Topic Id for the application network from the entity that setup the application network in the first place. This Topic Id is used by the application network to share details of new auctions being created.

Validator Node

You must acquire the Topic Id as described above for a Readonly Node.

In addition, you'll need to generate an ED25519 private/public key and share the public key with whoever is setting up an auction for you to validate.

Master Node

This node will be creating the Topic Id to share with the Readonly and Validator Nodes.

One additional ED25519 private/public key will be required, the MASTER_KEY. the key should be shared with whoever is setting up an auction account.

Generating keys

A helper function is available to generate keys as follows

./gradlew generateKey

or

curl -H "Content-Type: application/json" -X GET -k https://localhost:8081/v1/generatekey

_Note: this runs on the client REST API port (8081), not the admin API port (8082)

Environment setup

Creating accounts

If you need to create accounts for testing purposes, you may use the following helper.

./gradlew createAccount --args="100"

which will create an account with the specified initial balance in hbar and will return if successful (the actual keys have been truncated here but will be output fully).

2021-07-02 13:27:16.026 INFO  com.hedera.demo.auction.exerciser.CreateAccount - Account created 0.0.2060215 (43)
2021-07-02 13:27:16.029 INFO  com.hedera.demo.auction.exerciser.CreateAccount - Private key is 302e020.......83135bbb84cf17 (44)
2021-07-02 13:27:16.030 INFO  com.hedera.demo.auction.exerciser.CreateAccount - Public key is 302a3005.......f06f311c60accc (45)

All node types

Validator nodes

in addition to all node types above

Master node

in addition to all node types above

note: you may generate a new API key as follows, although any string will work

./gradlew generateApiKey

Creating the topic ID to share with the rest of the network

From the command line of your node (assuming the admin API is enabled)

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
https://localhost:8082/v1/admin/topic

This will create and output a topic id and will also update your .env file with its value. You may now share this topic Id with the rest of the application network participants.

Creating a token to auction

You may now create a token to auction, see documentation above for helpers if you're not sure how to do this.

Creating an account for a token auction

This action needs to be performed for every new token to be auctioned, the same account cannot be used for two different tokens, the application will reject the auction creation if this is the case.

This command will create an auction account with an initial balance of 100 hbar, and a key list for scheduled transactions.

_Note: if the environment file contains an entry for MASTER_KEY, it will automatically be added to the keylist below with a threshold of 1, resulting in an auction account having a threshold key of 1 of 2, one of the keys being the master key, the other being the threshold key supplied in the JSON._

Note: all keys are public keys

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "keylist": {
    "keys": [
      {
        "key": "validator 1 public key"
      },
      {
        "key": "validator 2 public key"
      },
      {
        "key": "validator 3 public key"
      }
    ],
    "threshold": 2
  },
  "initialBalance": 100
}' https://localhost:8082/v1/admin/auctionaccount

Creating an auction

be sure the replace {{tokenId}}, {{accountId}} in the json below with the values you obtained earlier, you may also set different values for:

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "tokenid": "{{tokenid}}",
  "auctionaccountid": "{{accountid}}",
  "reserve": 0,
  "minimumbid": 1000000,
  "endtimestamp": "",
  "winnercanbid": true,
  "title": "Auction title",
  "description": "Auction description"
}' https://localhost:8082/v1/admin/auction

Note: the minimum bid and reserve are expressed in tinybars

This will submit a HCS message on the application network's topic id so that all participants are aware of the auction.

Transfer the token to the auction account

This has to be done by the token creator so that the auction for the token can start. At the end of the auction, the token will be transferred to the winner and the hbar proceeds transferred to the token owner. In the event there are no bids, the token is transferred back to the owner.

Alternative with gradle

Copy the sample yaml file and edit

cd hedera-nft-auction-demo-java-node
cp AuctionSetup.yaml.sample AuctionSetup.yaml
nano AuctionSetup.yaml

specify whether a new topic should be created or not

createTopic: true

specify the account to use to create the token

setupOperator:
  accountId: 0.0.xxxxx
  privateKey: 302.....

specify the token details

token:
  name: Token Name
  symbol: Symbol

specify the public keys, threshold and initial balance to use for the auction account

auctionAccount:
  publicKeys:
    - 302a300506032b6570032100130044fa6c178739733d525210d2965cb89420255335349e50c8b329e4732c75
    - 302a300506032b65700321008ba273d242fb1ebd3c66c26d88c5c433876d5cffdfd6e5520a151034eb9eabff
  threshold: 2
  balance: 10

specify the auction's details

auction:
  reserve: 0
  minimumbid: 10
  endtimestamp: 2d
  winnercanbid: true
  title: auction title
  description: auction description

and the host for the admin api calls

adminApiHost: https://localhost:8082

then run the setup

./gradlew setupHelper

Sending bids to the auction

Note: This uses .env to read the operator keys

The exerciser creates a number of pre-defined accounts and funds them with 10 hbar from the operator's account as specified in the .env file, if these accounts already exist (they are saved in a file), the code checks their balance is sufficient and if not, tops them up to 10 hbar.

When run, the exerciser starts a number of threads and within each thread sends a number of bids.

Finally, the exerciser outputs the highest bid (or bids if several equal bids were sent).

Copy the sample yaml file and edit

cd hedera-nft-auction-demo-java-node
cp AuctionSetup.yaml.sample AuctionSetup.yaml
AuctionSetup.yaml

specify the auction account to send bids to, the number of accounts to send from, the number of threads and transfers to run.

exerciser:
  auctionAccount = 0.0.xxxx
  numAccounts = 10
  numThreads = 4
  numTransfers = 4
./gradlew exerciseAuction 

Validators information for rendering in the UI

You may list the validators who participate in the network in the UI by adding them to the database via the REST API.

After sending the request to the admin API, a message will be sent to the TOPIC ID so that any other participants' list of validators will be updated automatically.

Note: in the event of a create or update, if a parameter such as url isn't specified, it will be set to an empty string.

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
    "validators": [
    {
      "name": "name of the validator",
      "url": "url of the validator",
      "publicKey": "optional public key",
      "operation" : "add"
    }
  ]
}' https://localhost:8082/v1/admin/validators

You may modify the details of a validator as follows:

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
    "validators": [
    {
      "nameToUpdate": "name of the validator to update",
      "name": "new name of the validator",
      "url": "new url",
      "publicKey": "new public key",
      "operation" : "update"
    }
  ]
}' https://localhost:8082/v1/admin/validators

And finally, you may delete details of a validator as follows:

curl -H "Content-Type: application/json" -X POST -k \
--header 'X-API-key: your api key' \
-d '
{
  "validators": [
    {
      "name": "name of the validator to delete",
      "operation" : "delete"
    }
  ]
}' https://localhost:8082/v1/admin/validators