Hi Secret Agents!
Our friend Baedrik wrote this amazing walkthrough of his project from the Secret Games Hackathon, which began several months ago, prior to the Vulcan upgrade to enable Secret Contracts. After learning and collaborating with Enigma, Secret Foundation, Puzzle, Chain of Secrets, Secure Secrets, and others in the Network, we are publishing a guide to encourage open-source collaboration.
October Ecosystem Update
If you aren’t familiar with our Secret community of communities, check out the blog post above to learn what happened just in the last month. Also, we hope you get involved in our community governance system through various committee structures and processes. There will always be more secrets to share!
Secret Auctions
Okay, now back to the subject! The most recent version of this guide can be found in this README.md file:
Above is a privacy-preserving “Secret Contract” implementing a sealed bid auction, where tokens for bidding and selling are SNIP-20 compliant. Be sure to read the WALKTHROUGH.md if you are using this contract as a template to build your own secret contract.
Technically, they don’t have to be fully compliant. They just need to follow the SNIP-20 spec for the Send
function (as well as the RegisterReceive
function, which is needed to set up the Send/Receive functionality). This privacy-preserving equivalent of the ERC-20 token standard, also known as Secret-20, requires that Send
should callback a Receive
function with the following msg format.
pub struct Snip20ReceiveMsg {
pub sender: HumanAddr,
pub from: HumanAddr,
pub amount: Uint128,
pub msg: Option<Binary>,
}
- “sender” is the address that is sending the tokens
- “from” is the owner of the tokens
- “amount” is how many tokens were sent
- “msg” is optionally defined by a user, passed along from the original SendMsg
SNIP-20 Specification: Fungible Privacy Token Standard
Writing the auction contract to work with SNIP-20 compliant tokens also helped reveal areas where the SNIP-20 Spec (as well as SNIP-721 for non-fungible privacy tokens) could be improved or expanded. The necessary Receive message format was one example.
The bid/sell tokens should also follow the SNIP-20 format for TokenInfo queries. Until that format is finalized, the following format is assumed (based on current secretSCRT implementation):
pub struct TokenInfo {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub total_supply: Option<Uint128>,
}
Running On Holodeck-2 Testnet
If you want to run sealed-bid auctions on Holodeck-2 Testnet, I have stored the snip20-reference-impl. Its code ID is 1. You can create a new token with:
secretcli tx compute instantiate 1 '{"name": "*token_name*","admin": "*address_with_admin_privileges*", "symbol": "*token_symbol*", "decimals": *decimal_places*, "initial_balances": [{"address": "*address1*", "amount": "*amount_for_address1*"}], "prng_seed": "*base64_encoded_string*","config": {"public_total_supply": *true_or_false*}}' --from *your_key_alias_or_addr* --label *name_for_the_contract* -y
You may include as many address/amount pairs as you like in the initial_balances
field.
You will want to create a token for sale as well as a token to bid ~ currently, the auction mechanism does not allow these tokens to be the same (there is no meaningful reason to swap different amounts of the same fungible token). When the SNIP-721 spec is more fleshed out, this will probably be changed to allow for the exchanging of different NFT token IDs regardless of whether they are part of the same NFT contract or not. Once you have created your test sale and bid tokens, you are ready to create an auction. I have also stored this sealed-bid auction contract on Holodeck-2 testnet. Its code ID is 2.
I have created a bash script file (named auction.sh) to make it easier to use the auction contract. It requires that you have secretcli and jq installed.
sudo apt-get install jq
The script is expecting the auction contract to have code ID 2, but you can change that on the first line if you store a new version of the contract.
This script was primarily written as a quick-and-dirty helper for my own testing purposes. If you intend to create a production UI for the contract, when placing a bid, you should follow the example of the script and use the optional padding field when calling a token contract’s Send. You will want the number of digits of the send amount + the number of characters in the padding field to be a constant number (I use 40 characters, because the maximum number of digits of Uint128 is 39, and I always want at least one blank in padding). This way you do not leak information about the number of digits of the bid.
You do not have to do this for consign, because the consignment amount is public (it is the amount of tokens for sale in the auction), but it will not cause any problems if you also pad the consignment call to Send if the same function is used to Send the tokens for both placing bids, and consigning tokens.
Using the Sealed-Bid Auction Contract
Creating a New Auction
You can create a new auction with:
secretcli tx compute instantiate 2 '{"sell_contract": {"code_hash": "*sale_tokens_code_hash*", "address": "*sale_tokens_contract_address*"}, "bid_contract": {"code_hash": "*bid_tokens_code_hash*", "address": "*bid_tokens_contract_address*"}, "sell_amount": "*amount_being_sold_in_smallest_denomination_of_sale_token*", "minimum_bid": "*minimum_accepted_bid_in_smallest_denomination_of_bid_token*", "description": "*optional_text_description*"}' --from *your_key_alias_or_addr* --label *name_for_the_auction* --gas 300000 -y
You can find a contract’s code hash with:
secretcli q compute contract-hash *contract_address*
Copy it without the 0x prefix and surround it with quotes in the instantiate command.
The description field is optional. It will accept a free-form text string (best to avoid using double-quotes). One possible use would be to list the approximate date that you plan to finalize the auction. In a sealed bid auction, a pre-defined end date is not necessary. It is necessary in an open ascending bid auction because bidders need to know when the auction will close so that they can monitor if they are winning and bid higher if they are not.
Because in a sealed bid auction, no one knows if they are the highest bidder until after the auction ends, the bidder has no further actions after placing his bid. For this reason, the auction owner can finalize the auction at any time. If at any point a bidder no longer wants to wait for the owner to finalize the auction, he can retract his bid and have his bid tokens returned. For this reason, it might benefit the auction owner to give an approximate end date in the description so that his highest bid doesn’t get retracted before he decides to close the auction. If user consensus would like to have an end date implemented, in which no bids will be accepted after such time, the owner can not finalize the auction before the end date, and afterwards, anyone can close the auction, it can be included.
The auction will not allow a sale amount of 0.
The auction will not currently allow the sale contract address to be the same as the bid contract address, because there is no reason to swap different amounts of the same fungible token. When the SNIP-721 spec is more fleshed out, this will probably be changed to allow for the exchanging of different NFT token IDs regardless of whether they are part of the same NFT contract or not.
Viewing the Auction Information
You can view the sell and bid token information, the amount being sold, the minimum bid, the description if present, the auction contract address, and the status of the auction with:
secretcli q compute query *auction_contract_address* '{"auction_info":{}}'
There is a lot of info there, so it is best viewed piping it through jq:
secretcli q compute query *auction_contract_address* '{"auction_info":{}}'|jq
Status will either be “Closed” if the auction is over, or it will be “Accepting bids.”
If the auction is accepting bids, auction_info
will also tell you if the auction owner has consigned the tokens to be sold to the auction. You may want to wait until the owner consigns the tokens before you bid, but there is no risk in doing it earlier. At any time before the auction closes, you can retract your bid to have your tokens returned to you. But if you wait until the owner consigns his tokens, you can be more sure the owner is likely to finalize the auction, because once the tokens to be sold are consigned to the auction, he can not get his original tokens or the bid tokens until he finalizes the auction.
The original consigned tokens will only be returned if there are no active bids (either no bids were placed meeting the minimum asking price or all qualifying bids have been retracted). Otherwise, the highest bid placed at the time of closure will be accepted and the swap will take place.
If the auction is closed, it will display if there are any outstanding funds still residing in the auction account. This should never happen, but if it does for some unforeseen reason, it will remind the user to either use retract_bid to have their bid tokens returned (if they haven’t already been returned), or use return_all to return all the funds still held by the auction. Return_all can only be called after the auction has closed.
Consigning Tokens To Be Sold
To consign the tokens to be sold, the owner should Send the tokens to the contract address with:
secretcli tx compute execute *sale_tokens_contract_address* '{"send": {"recipient": "*auction_contract_address*", "amount": "*amount_being_sold_in_smallest_denomination_of_sell_token*"}}' --from *your_key_alias_or_addr* --gas 500000 -y
It will only accept consignment from the address that created the auction. Any other address trying to consign tokens will have them immediately returned. You can consign an amount smaller than the total amount to be sold, but the auction will not be displayed as fully consigned until you have sent the full amount. You may consign the total amount in multiple Send transactions if desired, and any tokens you send in excess of the sale amount will be returned to you. If the auction has been closed, any tokens you send for consignment will be immediately returned, and the auction will remain closed.
Place Bids
To place a bid, the bidder should Send the tokens to the contract address with:
secretcli tx compute execute *bid_tokens_contract_address* '{"send": {"recipient": "*auction_contract_address*", "amount": "*bid_amount_in_smallest_denomination_of_bidding_token*"}}' --from *your_key_alias_or_addr* --gas 500000 -y
The tokens bid will be placed in escrow until the auction has concluded or you call retract_bid to retract your bid and have all tokens returned. You may retract your bid at any time before the auction ends. You may only have one active bid at a time. If you place more than one bid, the smallest bid will be returned to you, because obviously that bid will lose to your new bid if they both stayed active. If you bid the same amount as your previous bid, it will retain your original bid’s timestamp, because, in the event of ties, the bid placed earlier is deemed the winner. If you place a bid that is less than the minimum bid, those tokens will be immediately returned to you. Also, if you place a bid after the auction has closed, those tokens will be immediately returned.
The auction will not allow a bid of 0.
It is recommended that the UI designed to send a bid use the optional “padding” field when calling the bid token contract’s Send function. You should make the number of digits of the bid amount + the number of characters in the “padding” field a constant. That way the size of the Send does not leak information about the size of the bid. The helper auction.sh ensures that the number of digits of the bid + the number of spaces sent in the “padding” field always adds up to 40. Any other UI (or a cmdline call) would do best to implement something similar.
View Your Active Bid
You may view your current active bid amount and the bid time with:
secretcli tx compute execute *auction_contract_address* '{"view_bid": {}}' --from *your_key_alias_or_addr* --gas 200000 -y
You must use the same address that you did the Send transaction with to view the bid. Based on feedback, viewing keys could be implemented if users wanted to be able to see the bid through a query in addition to viewing through an execute.
Retract Your Active Bid
You may retract your current active bid with:
secretcli tx compute execute *auction_contract_address* '{"retract_bid": {}}' --from *your_key_alias_or_addr* --gas 300000 -y
You may retract your bid at any time before the auction closes to both retract your bid and to return your tokens. In the unlikely event that your tokens were not returned automatically when the auction ended, you may call retract_bid after the auction closed to return them manually.
Finalize the Auction Sale
The auction creator may close an auction with:
secretcli tx compute execute *auction_contract_address* '{"finalize": {"only_if_bids": *true_or_false*}}' --from *your_key_alias_or_addr* --gas 2000000 -y
Only the auction creator can finalize an auction. The boolean only_if_bids parameter is used to prevent the auction from closing if there are no active bids. If there are no active bids, but only_if_bids was set to false, then the auction will be closed, and all consigned tokens will be returned to the auction creator. If the auction is closed before the auction creator has consigned all the tokens for sale, any tokens consigned will be returned to the auction creator, and any active bids will be returned to the bidders. If all the sale tokens have been consigned, and there is at least one active bid, the highest bid will be accepted (if tied, the tying bid placed earlier will be accepted). The auction will then swap the tokens between the auction creator and the highest bidder, and return all the non-winning bids to their respective bidders.
Return Funds In The Event Of Error
In the unlikely event of some unforeseen error that results in funds being held by an auction after it has closed, anyone may run:
secretcli tx compute execute *auction_contract_address* '{"return_all": {}}' --from *your_key_alias_or_addr* --gas 2000000 -y
Return_all may only be called after an auction is closed. Auction_info will indicate whether any funds are still held by a closed auction. Even if return_all is not called, bidders who have not received their bids back can still call retract_bid to have their bids returned.
Notes for UI Builders
It is recommended that the UI designed to send a bid use the optional “padding” field when calling the bid token contract’s Send function. You will want the number of digits of the send amount + the number of characters in the padding field to be a constant number (I use 40 characters, because the maximum number of digits of Uint128 is 39, and I always want at least one blank in padding). That way the size of the Send does not leak information about the size of the bid.
Also, you should be aware that responses from bidding and consigning (functions that are called indirectly when doing a Send tx with a token contract) are sent in the log attributes. This is because when one contract calls another contract, only logs (not the data field) are forwarded back to the user. On the other hand, any time you call the auction contract directly, the response will be sent in the data field, which is the preferred method of returning json responses.
Use Cases of Secret Auctions
Secret Auctions could also be used for other kinds of digital content monetization. First and foremost, cryptoart made it possible to “sell” provable ownership of data, which may represent verifiable goods and/or experiences. Now Secret Network enables many blockchains to empower a new future of artists, including musicians and video producers. Animated GIFs are popular in the rare digital art scene.
However, token creators have not been selling a lot of music or videos. On the contrary, there is no wonder why Audius is one the most used applications that involves blockchain technology. Spotify data is money. Harvesting user interactions with streaming platforms can be #DeFi, meaning controlled by users who actually generate and share information.
The next iteration of the auction will also include the ability to accept a ReceiveNft call, so that it can be used with SNIP-721 non-fungible tokens. The NFT version will likely include having optional TokenID fields when creating the auction, to specify that a specific NFT is up for sale. The creator could also specify that only a specific NFT Token ID will be accepted as a bid. This technically would no longer be an auction if only one Token ID will be accepted for a bid, but it would allow the ability to facilitate the trading of 1 specific NFT for another in a trustless manner. It could also allow someone to essentially post an offer to buy a specific NFT for his asking price (of either a specific NFT or an amount of SNIP-20 tokens).
SNIP-721 Specification: “Secret NFTs”
“I noticed that the document was using two different conventions in specifying the name and request parameters, depending on whether it was copied from the CW721 spec or from the SNIP-20 spec. I went with the tabular format used in the SNIP-20 spec, but didn’t change any sections other than Metadata in case the CW721 style was preferred. Also, I didn’t change the name of NftInfo/AllNftInfo to PublicNftInfo/AllPublicNftInfo because I thought it would be best to maintain the naming convention set by CW721 as that is likely what interNFT is planning on using.” – PR#2
Secret Monetization
Essentially, an entire side of auction theory has been inaccessible to “decentralized applications” without programmable privacy features. Cryptoartists may sell NFTs representing their artwork, but in terms of enforcing the license, not much is new. Given there is much to be said for this new capability ~ trading verifiably unique digital items ~ let’s focus on what’s next for a moment. Using regular NFTs on Ethereum as an example, consider that everyone has to bid “on-chain” and prices are public knowledge. Furthermore, participants are somewhat anonymous, but their public keys are known to the miners / validators.
Learn about Secret Tokens:
With Secret Auctions, new approaches to direct sponsorship could emerge, leading to a revolution of all types of content creators gaining power by sharing and helping others via contextualized value systems. How (and why) do you show support? What does it mean to actually own something? Who cares to prove authenticity? On Ethereum and other distributed state machines, NFTs are a public goods by default, and that’s great! Of course, we will not get far without every side of the proverbial token market. Everyone is valuable.
Privacy is creative freedom!!!