fip | title | status | type | author | created | updated |
---|---|---|---|---|---|---|
26 |
FIO Domain Marketplace |
Final |
Functionality |
Isaiah Williams - EOS BlockSmith, Thomas Le - EOS BlockSmith |
2021-02-05 |
2022-04-25 |
What we are planning to create is a marketplace where users can buy and sell FIO Domains. Users will interact with the frontend website to view and place domains up for sale. The frontend will interact with the FIO Escrow Smart Contract to facilitate transactions between users who buy and sell domains.
Contract | Action | Endpoint | Description |
---|---|---|---|
fio.escrow | listdomain | list_domain | User puts domain up for sale |
fio.escrow | cxlistdomain | cancel_list_domain | Seller of domain decides not to sell and cancels their listing |
fio.escrow | buydomain | buy_domain | User buys a domain that is up for sale |
fio.escrow | setmrkplcfg | set_marketplace_config | The mrkplconfig table holds configs for a given marketplace. This action writes to that table. |
fio.escrow | cxburned | none | This is an admin action that is only called from fio.address::burnexpired to cancel the listing of a domain if it is burned and still listed for sale (not already sold or cancelled) |
fio.address | xferescrow | none | Transfers domain to fio.escrow . Called from fio.escrow only |
Endpoint | Description |
---|---|
get_escrow_listings | Returns all listings based on status with optional actor parameter for further filtering |
Contract | Action | Endpoint | Description |
---|---|---|---|
fio.token | transfer | none | Added code to fio.token::transfer so that, from the fio.escrow contract, it can transfer tokens from two accounts. |
fio.address | burnexpired | burn_expired | If a domain is expired and still listed, when it is going to be burned it needs to also be removed as a listing for sale. |
fio.treasury | bprewdupdate | ? | added EscrowContract to authorization list |
fio.treasury | bppoolupdate | ? | added EscrowContract to authorization list |
fio.treasury | fdtnrwdupdat | ? | added EscrowContract to authorization list |
- Listing - a domain that is up for sale
- FIO Domain Marketplace - the frontend website
- Seller - the seller of a domain
- Buyer - the buyer of a domain
- Listing Fee - fee paid to the marketplace from the seller to the marketplace. Flat fee in FIO.
- Commission Fee - fee paid to the marketplace when a domain is sold. This is a percentage of the sale price.
- Escrow Account (fio.escrow) - the account that owns the smart contract.
- E-Break - Term used within the contract for a setting that will disable all actions in the event of a major exploit or bug found to mitigate damages.
There does not currently exist a platform for buying, selling, and trading FIO Domains. As the popularity of FIO grows, the likelihood of a desired domain being available decreases. It is also common for individuals to purchase names of popular name brands in order to later sell the domain to the company who owns the brand (name squatting). Without a marketplace for domains to be sold, these transactions become difficult. By providing ease of use solutions to name and address transactions, a marketplace also increases the revenue flow of block producers by increasing the amount of fees generated by each transaction.
Users will interact with the frontend website to view and place domains up for sale. The frontend will interact with the FIO Escrow Smart Contract to facilitate transactions between users who buy and sell domains. The diagram below displays how the user, the website, the smart contract and the account that owns the smart contract all work together.
This diagram reads left to right and top to bottom. There are three different scenarios outlined and they are distinguished by the color of the arrows. The first scenario, in red, starts will the user using the website to place a domain up for sale for a specified price. This user is denoted as "seller." To initiate this scenario, the seller will pay a listing fee to list their domain for sale on the marketplace. The website then communicates that the smart contract needs to use the "listdomain" action, which will transfer ownership of the domain to an “escrow_account” and place that domain for sale on the website. The escrow account is the account that owns the escrow smart contract and, acts as an intermediary that will hold the domain while listed.
The second scenario, in blue, starts will the seller canceling their listing on the website. The website then communicates with the smart contract in order to transfer ownership of the domain from the escrow account back to the seller.
The last scenario, in green, starts with a different user buying a listing on the website. This second user is denoted as "buyer." The website then communicates with the smart contract in order to transfer ownership of the address to the buyer and transfer the funds used to buy the domain to the seller. The marketplace will take a commission for facilitating this transaction.
Holds all the domains currently available for sale with related information like the owner and amount of FIO the user wants.
We are saving the commission fee per domain listing because the commission fee can be changed but for existing listings it should not change because they agreed to list with a certain commission fee.
Column | Type | Description |
---|---|---|
id | uint64 | Primary key |
owner | uint64 | actor of owners account |
ownerhash | uint128 | hashed value of owner |
domain | string | name of domain for sale |
domainhash | uint128 | hashed value of domain |
sale_price | int64 | sale price in SUF |
commission_fee | int64 | commission fee applied to sale price |
date_listed | uint64 | date at time of listing |
status | uint8 | status = 1: on sale, status = 2: Sold, status = 3; Cancelled |
date_updated | uint64 | date last updated status, this will be when the listing was sold or cancelled |
- Primary Key
- bydomain
- byowner
Holds information related to the marketplace.
Column | Type | Description |
---|---|---|
id | uint64 | Primary key |
owner | uint64 | actor of owners account |
ownerhash | uint128 | hashed value of owner |
commission_fee | uint64 | Commission fee is a percentage taken out of the sale price when a domain is sold |
listing_fee | uint64 | Listing fee is taken up front for creating a listing |
e_break | uint64 | This is a setting that will disable all actions in the event of a major exploit or bug found to mitigate damages |
- primary key
- bymarketplace
- byowner
Returns all listings based on status with optional actor parameter for further filtering
Parameter | Required | Format | Definition |
---|---|---|---|
Status | Yes | Integer Value 1-3 | status = 1: on sale, status = 2: Sold, status = 3; Cancelled |
actor | No | String of actor name | |
limit | No | Positive Int | Number of records to return. If omitted, all records will be returned. Due to table read timeout, a value of less than 1,000 is recommended. |
offset | No | Positive Int | First record from list to return. If omitted, 0 is assumed. |
{
"status": 1,
"offset": 0,
"limit": 1000,
"actor": "wjeo4abnk4c2"
}
- Request is validated per Exception handling
- All Escrow listings with the status of the provided parameter
- If
actor
is provided it takes the results and only returns records of that status and with that actor name
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
Invalid Status value | Value of status is not 1, 2 or 3 | 400 | "status" | Value sent in, i.e. "5" | "Invalid status value" |
Invalid limit | limit is not valid | 400 | "limit" | Value sent in, e.g. "-1" | "Invalid limit" |
invalid offset | offset not valid | 400 | "offset" | Value sent in, e.g. "-1" | "Invalid offset" |
No Escrow Listings | There are no escrow listings based on the parameters. | 404 | "No Escrow Listings" |
Group | Parameter | Format | Definition |
---|---|---|---|
listings | JSON Array | Array of Escrow Listings | |
listings | id | Int | ID of Escrow Listings |
listings | commission_fee | String | commission percentage |
listings | date_listed | string | timestamp when the listing was created |
listings | date_updated | string | timestamp when the listing was last updated |
listings | domain | String | the FIO domain being listed |
listings | owner | String | account name of the owner of the domain |
listings | sale_price | Int | price the owner wants for the domain, in SUFs |
listings | status | Int | Status of the listing. |
more | Int | Number of results remaining |
{
"listings": [
{
"id": 0,
"commission_fee": "6.00000000000000000",
"date_listed": "2021-11-30T20:54:08",
"date_updated": "2021-11-30T20:54:08",
"domain": "agreement",
"owner": "wjeo4abnk4c2",
"sale_price": 300000000000,
"status": 1
}
],
"more": 0
}
This action will post the domain up for sale on the marketplace for a specified price.
A listing fee is collected from the domain owner for this action that goes to the marketplace.
The domain is transferred to the fio.escrow
account for the duration of the listing. This will prevent the ability to change the domain_public setting but it WILL allow new addresses to be registered on that domain if it is already set to public.
Parameter | Required | Format | Definition |
---|---|---|---|
actor | Yes | 12 character string | Valid actor of signer (seller) |
fio_domain | Yes | FIO Domain | Valid and FIO Domain that does not expire within the next 90 days |
sale_price | Yes | Positive Int | The amount of SUFs for which the seller wants to sell their domain |
max_fee | Yes | Positive Int | Maximum amount of SUFs the user is willing to pay for fee. Should be preceded by /get_fee for correct value. |
tpid | Yes | FIO Address | FIO Address of the entity which generates this transaction. TPID rewards will be paid to this address. Set to empty if not known. |
{
"actor": "2odzomo2v4pe"
"fio_domain": "alice",
"sale_price": 20000000000,
"max_fee": 1000000000,
"tpid": "rewards@wallet"
}
- Request is validated per exception handling
- Hash both the
actor
,fio_domain
to store alongside the string, for indexing. - Retrieve current
commission_fee
from the marketplace config table to be saved with the listing - Add domain to the
domainsale
table with relevant information - Transfer domain from seller to
fio.escrow
account. - Change
status
flag ondomainsale
table to 1. (status = 1: on sale, status = 2: Sold, status = 3; Cancelled) - Update
date_updated
column indomainsale
table. - A listing fee is collected by the marketplace and is set in the marketplace config table
- Verify tx does not exceed max transaction size.
- Increase account RAM
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
Invalid sale price | Supplied sale_price (in SUF) is not greater than 1 FIO |
400 | "sale_price" | Value sent in, e.g. "1000000000" | "Sale price must be greater than 1 FIO" |
Invalid sale price | Supplied sale_price (in SUF) is greater than 999,999 FIO |
400 | "sale_price" | Value sent in, e.g. "1000000000000000" | "Sale price must be less than 999999 FIO" |
Invalid fee value | max_fee format is not valid | 400 | "max_fee" | Value sent in, e.g. "-100" | "Invalid fee value" |
Fee exceeds maximum | Actual fee is greater than supplied max_fee | 400 | "max_fee" | Value sent in, e.g. "1000000000" | "Fee exceeds supplied maximum" |
Invalid FIO Domain format | FIO Domain format is not valid | 400 | "fio_domain" | Value sent in, e.g. "alice+123" | "Invalid FIO domain" |
FIO Domain expired | FIO Domain is expired | 400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO Domain expired. Renew first." |
Not owner of FIO Domain | The signer does not own the domain | 403 | Type: invalid_signature | ||
Actor not signer | The signer and the actor do not match |
403 | Type: invalid_signature |
Parameter | Format | Definition |
---|---|---|
status | String | OK if successful |
listing_id | Int | id of the listing |
fee_collected | Int | Amount of SUFs collected as fee |
{
"status": "OK",
"listing_id": 1
"fee_collected": 2000000000
}
This action will take the domain off of the marketplace and return ownership of the domain to the seller.
Parameter | Required | Format | Definition |
---|---|---|---|
fio_domain | Yes | FIO Domain | Valid and unexpired FIO Domain |
actor | Yes | 12 character string | Valid actor of signer (seller) |
max_fee | Yes | Positive Int | Maximum amount of SUFs the user is willing to pay for fee. Should be preceded by /get_fee for correct value. |
tpid | Yes | FIO Address | FIO Address of the entity which generates this transaction. TPID rewards will be paid to this address. Set to empty if not known. |
{
"actor": "2odzomo2v4pe",
"fio_domain": "alice",
"max_fee": 1000000000,
"tpid": "rewards@wallet"
}
- Request is validated per exception handling
- Change
status
flag ondomainsale
table to 3. (status = 1: on sale, status = 2: Sold, status = 3; Cancelled) - Update
date_updated
column indomainsale
table. - Transfer domain from
fio.escrow
to actor which will be verified to be the name of the account that originally listed the domain - Verify that the fee for this does not exceed the max fee specified.
- Charge appropriate fee
- Verify tx does not exceed max transaction size.
- Increase account RAM
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
Invalid FIO Domain format | FIO Domain format is not valid | 400 | "fio_domain" | Value sent in, e.g. "alice" | "Invalid FIO domain" |
FIO Domain expired | FIO Domain is expired | 400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO Domain expired. Renew first." |
FIO Domain listing not found | Supplied fio_domain is not listed on domainsale table. |
400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO domain not listed in domainsale table" |
Invalid fee value | max_fee format is not valid | 400 | "max_fee" | Value sent in, e.g. "-100" | "Invalid fee value" |
Fee exceeds maximum | Actual fee is greater than supplied max_fee | 400 | "max_fee" | Value sent in, e.g. "1000000000" | "Fee exceeds supplied maximum" |
Insufficient funds to cover fee | Account does not have enough funds to cover fee | 400 | "max_fee" | Value sent in, e.g. "1000000000" | "Insufficient funds to cover fee" |
Not owner of FIO Domain | The signer does not own the domain | 403 | |||
Actor not signer | The signer and the actor do not match |
403 | Type: invalid_signature |
Parameter | Format | Definition |
---|---|---|
status | String | OK if successful |
fee_collected | Int | Amount of SUFs collected as fee |
{
"status": "OK",
"fee_collected": 2000000000
}
This action facilitates the purchase of a domain listed for sale. The buyer will pay the full listed price + tx fee. A commission calculated by multiplying the commission_fee
by the sale price is subtracted and sent to the marketplace owner account. The remaining FIO will be sent to the seller and the domain will be transferred to the buyer.
Example:
- Commission fee is set to 5%
- Domain listed for 500 FIO
- Buyer pays 500 FIO + tx fee
- Commission calculated by 0.05 * 500 = 25
- 25 FIO is sent to marketplace owner account in marketplace config table
- 475 FIO is sent to seller
- Domain is transferred from
fio.escrow
to buyer.
Parameter | Required | Format | Definition |
---|---|---|---|
actor | Yes | 12 character string | Valid actor of signer (buyer) |
fio_domain | Yes | FIO Domain | Domain that can be found on the domainsale table. |
max_buy_price | Yes | Positive Int | Amount of FIO, in SUFs, that the buyer expects to pay |
max_fee | Yes | Positive Int | Maximum amount of SUFs the user is willing to pay for fee. Should be preceded by /get_fee for correct value. |
tpid | Yes | FIO Address | FIO Address of the entity which generates this transaction. TPID rewards will be paid to this address. Set to empty if not known. |
{
"actor": "2odzomo2v4pe",
"fio_domain": "alice",
"max_buy_price": 100000000000,
"max_fee": 1000000000,
"tpid": "rewards@wallet"
}
- Request is validated per exception handling
- Calculate commission amount by multiplying
commission_fee
by thesale_price
- Send, from buyer, commission amount sent to marketplace owner.
- Send, from buyer, rest ( sale price minus commission amount ) of sale price to the domain seller
- Transfer domain from
fio.escrow
to buyer. - Change
status
flag ondomainsale
table to 2. (status = 1: on sale, status = 2: Sold, status = 3; Cancelled) - Update
date_updated
column indomainsale
table. - Verify that the fee for this does not exceed the max fee specified.
- Charge appropriate fee
- Verify tx does not exceed max transaction size.
- Increase account RAM
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
FIO Domain listing not found | Supplied fio_domain is not listed on domainsale table. |
400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO domain not listed in domainsale table" |
FIO Domain expired | FIO Domain is expired | 400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO Domain expired. Renew first." |
Insufficient Funds | buyer doesn't have enough funds to cover domain cost and fee |
400 | "buy_price" | Value sent in, e.g. "1000000000" | "Not enough FIO" |
Invalid fee value | max_fee format is not valid | 400 | "max_fee" | Value sent in, e.g. "-100" | "Invalid fee value" |
Fee exceeds maximum | Actual fee is greater than supplied max_fee | 400 | "max_fee" | Value sent in, e.g. "1000000000" | "Fee exceeds supplied maximum" |
Sale Price exceeds buy_price parameter | Actual sale price of domain exceeds the buy_price supplied | 400 | "buyer_max_buy_price" | Value sent in, e.g. "100000000" | "sale price exceeds supplied buy price" |
Actor not signer | The signer and the actor do not match |
403 | Type: invalid_signature |
Parameter | Format | Definition |
---|---|---|
status | String | OK if successful |
buy_price | Int | Amount of SUFs collected for buy_price |
fee_collected | Int | Amount of SUFs collected as fee |
{
"status": "OK",
"max_buy_price": 100000000000,
"fee_collected": 2000000000
}
This sets the marketplace configurations.
When calling this action for the very first time, to set the owner of the marketplace with the actor
parameter, it will need to be an msig by eosio.prods.
Once the marketplace actors name is in the table, this same action can be called by the marketplace to set the parameters.
When this action is called as an msig by eosio.prods it will not be charged a fee, but if the marketplace owner calls it, there will be a fee.
Parameter | Required | Format | Definition |
---|---|---|---|
actor | Yes | 12 character string | Valid actor of the owner of the marketplace. |
listing_fee | No | FIO amount in SUFs | Flat fee in FIO taken up front for listing a domain for sale |
commission_fee | No | Decimal number less than 25 | Used to calculate commission fee from sale price. This is a percentage. |
e_break | No | 0 for off, 1 for on | Enables or disables e-break |
max_fee | Yes | Positive Int | Maximum amount of SUFs the user is willing to pay for fee. Should be preceded by /get_fee for correct value. |
{
"owner": "awesomemrkpl",
"listing_fee": "3000000000",
"commission_fee": 6,
"e_break": 0,
"max_fee": 2000000000
}
- Request is validated per exception handling
- Find marketplace
- Determine if this action is being called as an msig by eosio.prods
- If by an msig by eosio.prods it will allow the creation of a record on the table
- Set owner
- Set commission fee
- Set listing fee
- Set e-break
- If not an msig by eosio.prods it will only allow updating 3 parameters
- Modify commission fee
- Modify listing fee
- Modify e-break
- If by an msig by eosio.prods it will allow the creation of a record on the table
- Charge appropriate fee if caller not eosio
- Verify tx does not exceed max transaction size
- Increase account RAM
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
listing_fee | No | FIO amount in SUFs | Flat fee in FIO taken up front for listing a domain for sale | ||
commission_fee | No | Decimal number less than 25 | Used to calculate commission fee from sale price. This is a percentage. | ||
e_break | No | 0 for off, any positive number for on | Enables or disables e-break | ||
max_fee | Yes | Positive Int | Maximum amount of SUFs the user is willing to pay for fee. Should be preceded by /get_fee for correct value. | ||
Actor not signer | The signer of this request must be eosio | 403 | Type: invalid_signature | ||
Actor not marketplace owner | The signer of this request must match marketplace owner (once a record is present) | 403 | Type: invalid_signature |
Parameter | Format | Definition |
---|---|---|
status | String | OK if successful |
{
"status": "OK"
}
This action is called when a listing is found for a domain that is being burned. It will only be called if the listing is still active and it sets the status to 3, cancelled.
Parameter | Required | Format | Definition |
---|---|---|---|
domainhash | Yes | uint128 | Hashed value of domainname being cancelled |
{
"domainhash": "0xca0c8869bd11e8add2becc729ce08368",
}
- Request requires to be from AddressContract.
- Use domainhash to find entry in
domainsales
table. - Check status is equal to 1 (still an active listing) and if so
- Set status to 3, cancelled
- Update
date_updated
column in table
N/A. Handled by has_auth()
only
none, inline action
none
This is an action added to the fio.address
contract specifically for the fio.escrow contract. Only the EscrowContract
can call this action.
This action facilitates transferring the domain for sale to fio.escrow
and back to the seller in the case of a cancel. In the case of a sale it transfers to the buyer.
Parameter | Required | Format | Definition |
---|---|---|---|
fio_domain | Yes | FIO domain name | valid, registered, unexpired domain |
public_key | Yes | FIO public key | Valid FIO Domain that is already listed |
isEscrow | Yes | bool | This is a flag that is used to determine if the domain is transferred to fio.escrow or a normal account |
actor | Yes | FIO Domain | Valid FIO Domain that is already listed |
- Request is validated per exception handling
- Find domain
- if isEscrow is true
- Set domain's owner to
fio.escrow
- Set domain's owner to
- if isEscrow is not true
- Validate public key
- get account actor name from public key
- set domains owner to name obtained from public key
Error condition | Trigger | Type | fields:name | fields:value | Error message |
---|---|---|---|---|---|
Insufficient Funds | buyer doesn't have enough funds |
400 | "Not enough FIO" | ||
Invalid FIO Domain format | FIO Domain format is not valid | 400 | "fio_domain" | Value sent in, e.g. "alice" | "Invalid FIO domain" |
FIO Domain expired | FIO Domain is expired | 400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO Domain expired. Renew first." |
FIO Domain not registered | FIO Domain is not registered | 400 | "fio_domain" | Value sent in, e.g. "alice" | "FIO Domain not registered" |
none, inline action
none
- Added the following code to
fio.token::transfer
so that, from the fio.escrow contract, it can transfer tokens from two accounts.
if (from != SYSTEMACCOUNT && from != TREASURYACCOUNT && from != EscrowContract) {
if(!has_auth(EscrowContract)){
check(to == TREASURYACCOUNT, "transfer not allowed");
}
}
eosio_assert((has_auth(SYSTEMACCOUNT) || has_auth(TREASURYACCOUNT) || has_auth(EscrowContract)),
"missing required authority of treasury or eosio");
Note: I added a && from != EscrowContract
and then wrapped the check()
to see if the caller !has_auth(EscrowContract)
.
This action needs to be updated because there is no expiration to a domain being listed. If a domain is expired and still listed, when it is going to be burned it needs to also be removed as a listing for sale.
- Look up domain that is being burned in the
domainsales
table - If found, remove the entry from
domainsales
if (nameiter == nameexpidx.end()) {
domains.erase(domainiter);
recordProcessed++;
// ADDED
// Find any domains listed for sale on the fio.escrow contract table
auto domainsalesbydomain = domainsales.get_index<"bydomain"_n>();
auto domainsaleiter = domainsalesbydomain.find(domainhash);
// if found, call cxburned on fio.escrow
if(domainsaleiter != domainsalesbydomain.end()){
if(domainsaleiter->status == 1) {
action(permission_level{get_self(), "active"_n},
EscrowContract, "cxburned"_n,
make_tuple(domainhash)
).send();
}
// END ADDED
}
}
Released in:
An approach was considered to keep the domain ownership with the seller until the buyer makes a purchase and transfer then. However, we feel that having the ownership of the domain transferred to fio.escrow
account is necessary because it makes sure that when a buyer comes along that it is 100% available for sale. If the ownership doesn't transfer then the seller can still use the domain until it sells, and then, if a buyer comes along it might suddenly, without notice, disappear from their account.
- Domains set to public are still able to have addresses registered on them
- We may not need api end-points for our actions so none are included in this document, as of right now
- Domains cannot be changed from public to private or private to public while listed for sale.
- Any section that is blank will be filled out in the future as we get further along in the process
TBD
- Functionality for addresses
- Being able to renew a domain upon purchase
- Messaging system
- Expiring domain page to see all domains that are about to expire
- For the frontend to facilitate the registering of usernames to domains
- Being able to send an offer to the seller and they can either accept or decline
- “Willing to sell” designation
- Being able to send and receive FIO
- Project Documentation - https://fioprotocol.atlassian.net/l/c/ddpA8xJb
- Fee/Commission Structure Discussion - https://fioprotocol.atlassian.net/l/c/XN5rbhK0
- Marketplace Fee/Commission Documentation - https://fioprotocol.atlassian.net/l/c/P65fQTqg