NFT marketplace
Overview
The nftmarketplace module provides a place to asynchronously sell non-fungible tokens with other users. It means users can trade or change their non-fungible assets now, but settle them in a agreed upon future time. This module uses forwards contracts as it’s infrastructure to ensure the trade settlement. Obviously, other third parties can implement some other NFT marketplaces using Forwards contracts.
Concepts
Trading Fungible vs. Non-Fungible
There are some slight differences between trading fungible and non-fungible tokens:
- Fungible tokens can have multiple providers and multiple requesters for a specific pair of tokens simultaneously. Therefore, the algorithm used would aim to select the best pairing orders from all available orders. On the other hand, non-fungible tokens typically have only one provider and multiple requesters.
- In the case of fungible tokens, there is no distinction between the requester and the provider. This means that anyone can place an order to either buy or sell a specific token pair. For example, in a pair of ADA and USDC, there is no difference between placing a sell ADA order or a buy ADA order at first. However, with non-fungible tokens, the seller must first list their NFT (put a sell order), and then potential buyers can place their bids (buy orders).
- Fungible tokens allow for the possibility of buying only a portion of the provided asset, if the seller allows it. However, with non-fungible tokens, it is not possible to buy only a portion of the asset right now. Orders for non-fungible tokens must involve the entire asset.
Reserve Auctions
Just like other NFT marketplaces, we can have reserve auctions for NFTs. Each NFT would be listed for a period of time and can be found on a specific page. during the auction period, anyone can place their bids and at the end of auction period, the best bid would be selected.
When the auction ends and the winner bid was found, a forwards contract would be created between owner and bidder.
In this architecture there are 4 main phases:
- On reserve auction creation, the seller would put some escrow inside the auction’s escrow account
- When the reserve auction receives its first bid, the bidder is required to place the predetermined fixed escrow amount as per the terms defined during the auction creation.
- Whenever a new bid is placed in the auction, the previous bidder's escrow is released and the new bidder is required to put in some escrow.
- After the auction is completed and a winner is determined, the escrowed amount of both sides will be released, contract would be signed and capture the escrows again from both sides.
Trade-ups
Another approach is to allow the NFT owner to choose the most favorable bid at the conclusion of the auction. This method can be particularly helpful when determining the best bid becomes challenging. For instance, if participants are allowed to place bids using a combination of their NFT and FT assets, the seller would have the option to select the most advantageous bid. For example, they may consider swapping one NFT for another NFT along with an additional 1000 USDC.
Trade-ups provide users with the opportunity to suggest trades to NFT owners using their existing assets and NFTs, without requiring an escrow until the suggestion is accepted and a forward contract is created. Once the suggestion is approved, both parties are required to place their escrow based on the terms determined at the time of trade-up creation.
Messages
MsgCreateReserveAuction
This message creates a reserve auction. The response includes the ID of created order.
message MsgCreateReserveAuction {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
string nft_class_id = 2;
string nft_id = 3;
cosmos.base.v1beta1.Coin reserve_price = 4;
google.protobuf.Duration auction_duration = 5 [
(gogoproto.stdduration) = true,
(gogoproto.nullable) = false
];
string seller_recipient_address = 6;
Terms terms = 7;
repeated string suggesters_must_have_badges = 8;
string preferred_fee_denom = 9;
}
message Terms {
forwards.contracts.v1.Period settlement_period = 1;
cosmos.base.v1beta1.Coin buyer_escrow = 2;
cosmos.base.v1beta1.Coin seller_escrow = 3;
}
message Period {
option (gogoproto.equal) = true;
google.protobuf.Timestamp start = 1 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
google.protobuf.Timestamp end = 2 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
}
message MsgCreateReserveAuctionResponse {
uint64 id = 1;
}
MsgCancelReserveAuction
This message is used to cancel a reserve auction. This message can be sent only by the reserve auction owner.
message MsgCancelReserveAuction {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
uint64 id = 2;
string preferred_fee_denom = 3;
}
message MsgCancelReserveAuctionResponse {}
MsgUpdateReserveAuction
This message is used to update a reserve auction. This message can be sent only by the reserve auction owner.
message MsgUpdateReserveAuction {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
uint64 id = 2;
cosmos.base.v1beta1.Coin new_reserve_price = 3;
string new_seller_recipient_address = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"];
google.protobuf.Duration new_auction_duration = 5 [
(gogoproto.stdduration) = true,
(gogoproto.nullable) = false
];
Terms new_terms = 6;
repeated string suggesters_must_have_badges = 7;
string preferred_fee_denom = 8;
}
message MsgUpdateReserveAuctionResponse {}
MsgPlaceBid
This message is used to place a bid on a reserve auction.
message MsgPlaceBid {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
string bidder_recipient_address = 2;
uint64 auction_id = 3;
cosmos.base.v1beta1.Coin bid_price = 4;
contracts.v1.Period settlement_period = 5;
string preferred_fee_denom = 6;
}
message MsgPlaceBidResponse {}
MsgCreateTradeUp
This message is used to create a trade-up. The response includes the ID of created trade-up.
message MsgCreateTradeUp {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
string nft_class_id = 2;
string nft_id = 3;
string seller_recipient_address = 4;
Terms terms = 5;
string preferred_fee_denom = 6;
}
message MsgCreateTradeUpResponse {
uint64 id = 1;
}
MsgCancelTradeUp
This message is used to cancel a trade-up. This message can be sent only by the trade-up owner.
message MsgCancelTradeUp {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
uint64 id = 2;
string preferred_fee_denom = 3;
}
message MsgCancelTradeUpResponse {}
MsgPlaceTradeUpSuggestion
This message is used to place a suggestion for a trade-up. The response includes the ID of the placed suggestion.
message MsgPlaceTradeUpSuggestion {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
string suggester_recipient_address = 2;
uint64 trade_up_id = 3;
repeated contracts.v1.Token tokens = 4 [(gogoproto.nullable) = false];
contracts.v1.Period settlement_period = 5;
string preferred_fee_denom = 6;
}
message MsgPlaceTradeUpSuggestionResponse {
uint64 suggestion_id = 1;
}
MsgCancelTradeUpSuggestion
This message is used to cancel a suggestion on a trade-up.
message MsgCancelTradeUpSuggestion {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
uint64 trade_up_id = 2;
uint64 suggestion_id = 3;
string preferred_fee_denom = 4;
}
message MsgCancelTradeUpSuggestionResponse {}
MsgAcceptTradeUpSuggestion
This message is used to accept a suggestion on a trade-up. This message can be sent only by the trade-up owner. The response includes the ID of created contract.
message MsgAcceptTradeUpSuggestion {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
uint64 trade_up_id = 2;
uint64 suggestion_id = 3;
string preferred_fee_denom = 4;
}
message MsgAcceptTradeUpSuggestionResponse {
uint64 contract_id = 1;
}
MsgRejectTradeUpSuggestion
This message is used to reject a suggestion on a trade-up. This message can be sent only by the trade-up owner.
message MsgRejectTradeUpSuggestion {
option (cosmos.msg.v1.signer) = "creator";
string creator = 1;
uint64 trade_up_id = 2;
uint64 suggestion_id = 3;
string preferred_fee_denom = 4;
}
message MsgRejectTradeUpSuggestionResponse {}
MsgUpdateModuleParams
This message updates the module parameters. Only the governance can execute this message.
message MsgUpdateModuleParams {
option (cosmos.msg.v1.signer) = "authority";
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
Params params = 2 [(gogoproto.nullable) = false];
}
message Params {
google.protobuf.Duration auction_extension_duration = 1 [
(gogoproto.stdduration) = true,
(gogoproto.nullable) = false
];
uint64 max_open_reserve_auctions_per_block = 2;
uint64 max_open_trade_ups_per_block = 3;
}
message MsgUpdateModuleParamsResponse {}
MsgUpdateFeeConfig
This message updates the module’s fee configurations. Only the governance can execute this message.
message MsgUpdateFeeConfig {
option (cosmos.msg.v1.signer) = "authority";
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
FeeConfigs fee_config = 2 [(gogoproto.nullable) = false];
}
message FeeConfigs {
FeeConfig reserve_auction_creation = 1 [(gogoproto.nullable) = false];
FeeConfig reserve_auction_cancellation = 2 [(gogoproto.nullable) = false];
FeeConfig reserve_auction_update = 3 [(gogoproto.nullable) = false];
FeeConfig reserve_auction_bidding = 4 [(gogoproto.nullable) = false];
FeeConfig reserve_auction_winner_bid = 5 [(gogoproto.nullable) = false];
FeeConfig trade_up_creation = 6 [(gogoproto.nullable) = false];
FeeConfig trade_up_cancellation = 7 [(gogoproto.nullable) = false];
FeeConfig trade_up_place_suggestion = 8 [(gogoproto.nullable) = false];
FeeConfig trade_up_cancel_suggestion = 9 [(gogoproto.nullable) = false];
FeeConfig trade_up_accept_suggestion = 10 [(gogoproto.nullable) = false];
FeeConfig trade_up_reject_suggestion = 11 [(gogoproto.nullable) = false];
FeeConfig trade_up_accepted_suggestion = 12 [(gogoproto.nullable) = false];
}
message FeeConfig {
bool enabled = 1;
repeated cosmos.base.v1beta1.Coin fixed = 2;
string percentage_of_underlying_asset = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = true
];
}
message MsgUpdateFeeConfigResponse {}
Queries
Params
This query fetches the parameters of the module.
message QueryParamsRequest {}
message QueryParamsResponse {
// params holds all the parameters of this module.
Params params = 1 [(gogoproto.nullable) = false];
}
FeeConfigs
This query fetches the fee configurations of the module.
message QueryFeeConfigsRequest {}
message QueryFeeConfigsResponse {
// fee config holds all the fee configurations of this module.
FeeConfigs fee_configs = 1 [(gogoproto.nullable) = false];
}
ReserveAuction
This query retrieves a reserve auction using its id.
message QueryGetReserveAuctionRequest {
uint64 id = 1;
}
message QueryGetReserveAuctionResponse {
ReserveAuction reserve_auction = 1 [(gogoproto.nullable) = false];
}
ReserveAuctionAll
This query fetches all reserve auctions.
message QueryAllReserveAuctionRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}
message QueryAllReserveAuctionResponse {
repeated ReserveAuction reserve_auctions = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
TradeUp
This query retrieves a trade-up using its id.
message QueryGetTradeUpRequest {
uint64 id = 1;
}
message QueryGetTradeUpResponse {
TradeUp trade_up = 1 [(gogoproto.nullable) = false];
}
TradeUpAll
This query fetches all trdae-ups.
message QueryAllTradeUpRequest {
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}
message QueryAllTradeUpResponse {
repeated TradeUp trade_ups = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
Events
ReserveAuctionCreatedEvent
Emitted when a reserve auction is created. It includes the reserve auction information.
message ReserveAuctionCreatedEvent {
uint64 id = 1;
string escrow_account = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string creator = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string nft_class_id = 4;
string nft_id = 5;
cosmos.base.v1beta1.Coin reserve_price = 6;
string seller_recipient_address = 7 [(cosmos_proto.scalar) = "cosmos.AddressString"];
contracts.v1.Period auction_period = 8;
Terms terms = 9;
}
message Terms {
forwards.contracts.v1.Period settlement_period = 1;
cosmos.base.v1beta1.Coin buyer_escrow = 2;
cosmos.base.v1beta1.Coin seller_escrow = 3;
}
ReserveAuctionCancelledEvent
Emitted when a reserve auction is cancelled. It includes the reserve auction ID.
message ReserveAuctionCancelledEvent {
uint64 id = 1;
}
ReserveAuctionUpdatedEvent
Emitted when a reserve auction is updated. It includes the updated reserve auction information.
message ReserveAuctionUpdatedEvent {
uint64 id = 1;
cosmos.base.v1beta1.Coin new_reserve_price = 2;
string new_seller_recipient_address = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
google.protobuf.Duration new_auction_duration = 4 [
(gogoproto.stdduration) = true,
(gogoproto.nullable) = false
];
Terms new_terms = 5;
}
message Terms {
forwards.contracts.v1.Period settlement_period = 1;
cosmos.base.v1beta1.Coin buyer_escrow = 2;
cosmos.base.v1beta1.Coin seller_escrow = 3;
}
ReserveAuctionBidReceivedEvent
Emitted when a bid is placed on reserve auction. It includes the bid’s information.
message ReserveAuctionBidReceivedEvent {
uint64 id = 1;
string bidder_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string bidder_recipient_address = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
cosmos.base.v1beta1.Coin bid_price = 4;
google.protobuf.Timestamp bid_time = 5 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
contracts.v1.Period settlement_period = 6;
}
ReserveAuctionFinishedEvent
Emitted when a reserve auction’s period is finished. It includes the winner bid information
message ReserveAuctionFinishedEvent {
uint64 id = 1;
string winner_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
cosmos.base.v1beta1.Coin winner_price = 3;
contracts.v1.Period settlement_period = 4;
}
TradeUpCreatedEvent
Emitted when a trade-up is created. It includes the trade-up information.
message TradeUpCreatedEvent {
uint64 id = 1;
string creator = 2;
string nft_class_id = 3;
string nft_id = 4;
string seller_recipient_address = 5;
Terms terms = 6;
}
message Terms {
forwards.contracts.v1.Period settlement_period = 1;
cosmos.base.v1beta1.Coin buyer_escrow = 2;
cosmos.base.v1beta1.Coin seller_escrow = 3;
}
TradeUpCancelledEvent
Emitted when a trade-up is cancelled. It includes the trade-up ID.
message TradeUpCancelledEvent {
uint64 id = 1;
}
TradeUpSuggestionReceivedEvent
Emitted when a suggestion is placed on a trade-up. It includes the suggestion information.
message TradeUpSuggestionReceivedEvent {
uint64 trade_up_id = 1;
uint64 suggestion_id = 2;
string suggester = 3;
repeated contracts.v1.Token suggested_tokens = 4 [(gogoproto.nullable) = false];
contracts.v1.Period settlement_period = 5;
}
TradeUpSuggestionCancelledEvent
Emitted when a suggestion is cancelled from a trade-up. It includes the cancelled suggestion’s ID.
message TradeUpSuggestionCancelledEvent {
uint64 trade_up_id = 1;
uint64 suggestion_id = 2;
}
TradeUpSuggestionAcceptedEvent
Emitted when a suggestion is accepted by the trade-up owner. It includes the accepted suggestion ID.
message TradeUpSuggestionAcceptedEvent {
uint64 trade_up_id = 1;
uint64 suggestion_id = 2;
}
TradeUpSuggestionRejectedEvent
Emitted when a suggestion is rejected by the trade-up owner. It includes the accepted suggestion ID.
message TradeUpSuggestionRejectedEvent {
uint64 trade_up_id = 1;
uint64 suggestion_id = 2;
}
State
Reserve Auction
Reserve auctions are stored using their IDs as store keys:
(
[]byte("/ReserveAuctions/value/") |
[]byte(id)
) -> ProtoBuf(ReserveAucion)
Reserve auctions are also indexed with their owner:
type ReserveAuctionIndexes struct {
// Owner is a multi index that indexes reserve-auctions by their owner address.
Owner *indexes.Multi[string, uint64, types.ReserveAuction]
}
Trade-up
Trade-ups are stored using their IDs as store keys:
(
[]byte("/TradeUps/value/") |
[]byte(id)
) -> ProtoBuf(TradeUp)
Trade-ups are also indexed with their owner:
type TradeUpIndexes struct {
// Owner is a multi index that indexes trade-up by their owner address.
Owner *indexes.Multi[string, uint64, types.TradeUp]
}
Params
Module parameters are stored under the following store key:
([]byte("Params/")) -> ProtoBuf(Params)
FeeConfigs
Module fee configs are stored under the following store key:
([]byte("FeeConfigs/")) -> ProtoBuf(FeeConfigs)
Parameters
Reserve Auction Params
auction_extension_duration
: This parameter determines the duration that an auction is extended if a new bid is placed.- Type:
google.protobuf.Duration
- Range:
(0, inf]
- Default Value:
600000000000 (10 minutes)
- Type:
max_open_reserve_auctions_per_block
: This parameter determines the maximum number of open reserve auctions that can be processed in a block.- Type:
uint64
- Range:
(0, inf]
- Default Value:
100000
- Type:
Trade-up Params
max_open_trade_ups_per_block
: This parameter determines the maximum number of open trade-ups that can be processed in a block.- Type:
uint64
- Range:
(0, inf]
- Default Value:
100000
- Type:
Genesis
The genesis state of this module includes:
Params
: Parameters for the trading module.Fee_configs
: Fee configurations of the trading module.reserve_auctions_list
: List of all reserve auctions.reserve_auctions_starting_id
: Next reserve auction id that can be generated.reserve_auctions_count
: Number of reserve auctions.trade_ups_list
: List of all trade-ups.trade_ups_starting_id
: Next trade-up id that can be generated.trade_ups_count
: Number of trade-ups.