fio.erc721 - FIONFT Contract Specification
Notes: _beforeTokenTransfer action of erc721 OZ implementation has been overridden to prevent tranasfers of NFTs to the contract address. Error: “Cannot transfer to contract”
Public/External Actions
Actions | Description |
---|---|
mint to account with oracle approvals | |
burn (called by user) | |
oracles can burn nft by consensus | |
register oracle | |
unregister oracle | |
register custodian | |
unregister custodian | |
listDomainsOfOwner | Returns list of tokenIds owned by the provided ethereum account |
getOracle | Get oracle status by ethereum account |
getCustodian | Get custodian status by ethereum account |
getApproval | getApproval status by indexhash. This is emitted in consensus_activity event after a successful approval has been executed. |
retrieve JSON information about NFT | |
setBaseURI | Change the baseURI path (custodian only) |
_baseURI | Retrieve baseURI path (used as first portion of tokenURI) |
State Variables
State variables | Description |
---|---|
custodian_count | Keeps track of the number of custodians active in the oracle. Required for consensus math |
oracle_count | Keeps track of the number of custodians active in the oracle. Required for consensus math |
oraclelist | list of active oracles |
custodianlist | list of active custodians(private) |
_baseURIextended | Stored baseURI |
attribute[] | Holds the domain names for each tokenID 1:1 |
Mappings
Mapping | Description |
---|---|
attribute | Keeps track of the domain names by tokenId |
approvals | Keeps track of the approval statuses |
_owners | Keeps track of token owners |
Events
Events | Description |
---|---|
wrapped | Event emitted when wrap has completed. |
unwrapped | Event emitted when user has unwrapped NFT. |
custodian_registered | Event emitted when a custodian has been registered |
custodian_unregistered | Event emitted when a custodian has been unregistered |
oracle_registered | Event emitted when an oracle has been registered |
oracle_unregistered | Event emitted when an oracle has been unregistered |
consensus_activity | Event emitted when any oracle has approved a transaction Important: indexhash is emitted after every approval and is required to check status with getApproval action |
Mappings
Mappings | Members | Description |
---|---|---|
Custodians | custodian | Keeps track of the custodians that registered another custodian and their activation |
Oracles | oracle | Keeps track of the custodians that registered an oracle and the oracles activation count |
Approvals | pending | Keeps track of the approvals by obtid, the number of approvals, and the details of the approval
|
Roles
Role Name | Actions | Description |
---|---|---|
OWNER_ROLE | Deploy contract | default |
ORACLE_ROLE | wrapnft/butnnft | Only FIONFT oracle may use this function |
CUSTODIAN_ROLE | regoracle | Only FIONFT custodian may use this function |
Misc. Requirements
Issue | Summary | Decision |
---|---|---|
Max transaction size | Should we put a max transaction size limit on mint into the ERC721 contract. | Not needed |
ERC721 contract key | What is the status of the key used to set the contract? | Key should only be used for spinning up the contract and should be burned. |
Oracle “Admin” functions | Do we need to enable the ability for Oracles to call various contract actions such as approve, transfer, mint, etc. This can be performed in a couple of ways: 2 - The Oracles can sign the next transfer action as a multisig | Decision: We do not want any Oracle admin functions. |
Custodian “Admin” functions | Are there contract actions that should be exposed to Custodian “admin” approval?
| TBD: Are there any other contract actions that might be used for “admin” functionality? |
Unwrap fee | Is there a fee for | Yes. Unwrapping user pays the gas fee |
Wrap/Unwrap
wrapnft
Create FIONFT on Ethereum chain
Registering oracles
NFT is created using the
wrapnft
action
Implementation
New action:
wrapnft
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
account | Yes | address | Public address on ETH blockchain where wrapped NFT should be delivered. |
domain | Yes | string | FIO Domain being wrapped. This will be included in the NFT TokenURI |
obtid | Yes | uint256 | FIO chain transaction ID |
Example
{
"account": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
"domain": "dapixdev",
"obtid": "dfe50aad8e2271f84f87b8e603776d7e7970c636bb899c8993c08e9e2d21c106"
}
Processing
Request is validated per Exception handling.
Consensus required. Transaction is executed when all registered oracles have called
wrap
(submitted their “observation”).3 oracles call wrap to get it approved
Do the mint when the 3rd oracle calls
wrap
DONE
NFT is minted and transferred to
account
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid tokenURI | tokenURI is not valid. | 400 | "Invalid tokenURI" |
Invalid public address | Recipient public address is not valid | 400 | "Invalid public address" |
Invalid account | Uninitialized address is provided | 400 | “Invalid account” |
Invalid obtid | Obtid is is empty or uninitialized | 400 | “Invalid obtid” |
Domain string is invalid | Length is less than 1 or greater than 64 | 400 | “Invalid domain” |
Account parameter is contract address | Cannot wrap to contract account | 400 | “Cannot wrap to contract account” |
Already approved | Oracle address tries to approve a wrap multiple times | 400 | “sender has already approved this hash” |
Already complete | Oracle tries to approve a wrap that has already been completed | 400 | “Approval already complete” |
Approver must execute | Oracle tries to execute wrap it didn’t approve | 400 | “An approver must execute” |
Not enough oracles | Oracle tries to execute wrap when there are not enough registered oracles | 400 | “Oracles must be 3 or greater” |
No authority | The signer is not a registered Oracle and does not have authority to call this function | 403 | "AccessControl: account [user account] is missing role [role hash]" |
Response body
Parameter | Format | Definition |
---|---|---|
Return | uint256 | TokenID of minted NFT returned on success |
Events | String | consensus_activity emitted on successful approval Transfer, Wrapped, and Consensus_Activity (oracle) events emmitted on successful wrap |
Example
{
logIndex: 0,
transactionIndex: 0,
transactionHash: '0xbc61e4a5b6b1b0aca24bc7efc41394c953722de8f179223d13531cfc7002cdbc',
blockHash: '0x473345db7cecdf94afa1f33dcfb467d04f2bd184a659b01ed380880d8ef04d57',
blockNumber: 29,
address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
type: 'mined',
id: 'log_d4b4d126',
event: 'Transfer',
args: [Result]
},
{
logIndex: 1,
transactionIndex: 0,
transactionHash: '0xbc61e4a5b6b1b0aca24bc7efc41394c953722de8f179223d13531cfc7002cdbc',
blockHash: '0x473345db7cecdf94afa1f33dcfb467d04f2bd184a659b01ed380880d8ef04d57',
blockNumber: 29,
address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
type: 'mined',
id: 'log_664694e0',
event: 'wrapped',
args: [Result]
},
{
logIndex: 2,
transactionIndex: 0,
transactionHash: '0xbc61e4a5b6b1b0aca24bc7efc41394c953722de8f179223d13531cfc7002cdbc',
blockHash: '0x473345db7cecdf94afa1f33dcfb467d04f2bd184a659b01ed380880d8ef04d57',
blockNumber: 29,
address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
type: 'mined',
id: 'log_52b744c3',
event: 'consensus_activity',
args: [Result]
}
unwrapnft
Convert NFT (domain/address) on Ethereum chain to FIO domain/address on FIO chain.
Implementation
New action:
unwrapnft
Action is signed by domain holder and does not require oracle consensus
Fees
Alice covers the fee to unwrap
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
tokenID | Yes | uint256 | TokenID to be unwrapped (will be burned on Ethereum chain, sent to 0x000000000000…) |
fio_address | Yes | String | The FIO Handle where NFT should be delivered on FIO chain |
Example
{
"fio_address": "alice@wallet",
"tokenID": 123
}
Processing
Request is validated per Exception handling.
No Oracle Consensus required.
Contract burns NFT and event is emitted
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid FIO handle | Recipient FIO handle is not valid with less than 3 characters or greater than 64 characters | 400 | "Invalid FIO handle" |
Invalid TokenID | TokenID is not valid | 400 | "Invalid tokenID" |
No authority | The signer does not own the NFT to burn | 403 | “Invalid token owner“ |
Response body
Parameter | Format | Definition |
---|---|---|
Events | String | Approval, transfer and unwrapped event emitted on successful unwrap |
Example
burnnft
Burn target NFT. This is an action carried out by oracles for maintenance and burning domains that may be stuck across the bridge
Implementation
New action:
burnnft
Which FIO parameter should get passed in?
TokenId
TokenId of the domain held by the user
obtid
Unique transaction id
Fees
Oracles cover the fee to burn
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
tokenID | Yes | uint256 | TokenID to be unwrapped (will be burned on Ethereum chain, sent to 0x000000000000…) |
obtid | Yes | String | FIO transaction ID or other unique identifier |
Example
Processing
Request is validated per Exception handling.
Oracle Consensus required.
Contract burns NFT and event is emitted
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid FIO handle | Recipient FIO handle is not valid with less than 3 characters or greater than 64 characters | 400 | "Invalid FIO handle" |
Invalid TokenID | TokenID is not valid | 400 | "Invalid tokenID" |
No authority | The signer does not own the NFT to burn | 403 | “Invalid token owner“ |
Invalid obtid | Obtid is is empty or uninitialized | 400 | “Invalid obtid” |
Already approved | Oracle address tries to approve a wrap multiple times | 400 | “sender has already approved this hash” |
Already complete | Oracle tries to approve a wrap that has already been completed | 400 | “Approval already complete” |
Approver must execute | Oracle tries to execute wrap it didn’t approve | 400 | “An approver must execute” |
Response body
Parameter | Format | Definition |
---|---|---|
Events | String | Consensus_activity event emitted on successful approval by oracle Approval, Transfer, domainburned and consensus_activity logs emitted at successful execution |
Example
Oracles - register/unregister
regoracle
Register Oracle.
Registering oracles
Each Oracle is registered by custodians using
regoracle
action.
Implementation
New action:
regoracle
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
account | Yes | Ethereum public address | Ethereum address owned by the Oracle. |
Example
Processing
Request is validated per Exception handling.
Consensus required.
Oracles are registered by custodians. 2/3+1 of active custodians are required to complete registration of a new oracle.
A minimum of 3 oracles is required to approve a
wrap
transaction.Ethereum address is registered as a valid Oracle.
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid account | Ethereum account is not valid | 400 | "Invalid account" |
Self registration | Custodian tries to register itself as oracle | 400 | “Cannot register self” |
Already registered | The Ethereum account is already registered as an Oracle | 400 | “Oracle already registered” |
Already approved | Oracle address tries to approve a wrap multiple times | 400 | “sender has already approved this hash” |
Already complete | Oracle tries to approve a wrap that has already been completed | 400 | “Approval already complete” |
Approver must execute | Oracle tries to execute wrap it didn’t approve | 400 | “An approver must execute” |
No authority | The signer is not a registered Custodian and does not have authority to call this function | 403 | "AccessControl: account [user account] is missing role [role hash]" |
Response body
Parameter | Format | Definition |
---|---|---|
Events | String | consensus_activity event emitted successful approval RoleGranted, oracle_registered and consensus_activity events emitted on completed registration after all approvals |
Example
unregoracle
Unregister Oracle.
Implementation
New action:
unregoracle
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
account | Yes | Ethereum account | Ethereum account owned by the Oracle |
Example
Processing
Consensus required. Oracle is removed after 2/3+1 Custodians have called
unregoracle
with sameethaddress
.Ethereum address is removed as a valid Oracle.
A minimum of 3 oracles must be maintained. If only 3 Oracles are registered,
unregoracle
can not be called.
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid oracle | Oracle is not registered | 400 | "Oracle not registered" |
No authority | The signer is not a registered Custodian and does not have authority to call this function | 403 | "AccessControl: account [user account] is missing role [role hash]" |
Already approved | Oracle address tries to approve a wrap multiple times | 400 | “sender has already approved this hash” |
Already complete | Oracle tries to approve a wrap that has already been completed | 400 | “Approval already complete” |
Approver must execute | Oracle tries to execute wrap it didn’t approve | 400 | “An approver must execute” |
Invalid account | Ethereum account is not valid | 400 | “Invalid account” |
Minimum 3 oracles required | Trying to unregister an oracle when there are only 3 registered | 400 | “Minimum 3 oracles required” |
Response body
Parameter | Format | Definition |
---|---|---|
Event | String | consensus_activity event emitted on successful approval RoleRevoked, oracle_unregistered and consensus_activity events emitted on successful unregister of an oracle |
Example
Custodians - register/unregister
regcust
Register Custodian
Registering Custodians
Each Custodian is registered using
regcust
action.
Implementation
New action:
regcust
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
account | Yes | Ethereum account | Ethereum account owned by the Custodian |
Example
Processing
Request is validated per Exception handling.
Consensus required. Custodians are registered as follows:
The initial 10 custodians are set upon contract deployment by the contract owner.
Subsequently, 2/3+1 of active custodians must call
regcust
to register a new custodian.
Ethereum account is added as a valid Custodian.
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid account | Ethereum account is not valid | 400 | "Invalid account" |
Self registration | Custodian tries to register itself as custodian | 400 | “Cannot register self” |
Already registered | The Ethereum account is already registered as a custodian | 400 | “Already registered” |
Already approved | Custodian account tries to approve a wrap multiple times | 400 | “sender has already approved this hash” |
Already complete | Custodian tries to approve a wrap that has already been completed | 400 | “Approval already complete” |
Approver must execute | Custodian tries to execute wrap it didn’t approve | 400 | “An approver must execute” |
No authority | The signer is not a registered Custodian and does not have authority to call this function | 403 | "AccessControl: account [user account] is missing role [role hash]" |
Response body
Parameter | Format | Definition |
---|---|---|
Event | String | consensus_activity event emitted on successful approval RoleGranted, custodian_registered and consensus_activity emitted on successful registration of a custodian |
Example
unregcust
Unregister Custodian.
Implementation
New action: unregcust
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
account | Yes | Ethereum public address | Ethereum address owned by the Custodian |
Example
Processing
Request is validated per Exception handling.
Consensus required. Custodian is removed after 2/3+1 of active custodians have called
unregcust
the same Ethereum address.Ethereum address is removed as a valid Custodian.
A minimum of 7 Custodians must be maintained. If only 7 Custodians are registered,
unregcust
can not be called.A custodian can not singlehandedly unregister themselves. All changes require 2/3+1. Bug, the custodian can call
unregcust
for themselves and be counted in the 2/3+1.
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
Invalid custodian | Custodian is not registered | 400 | "Custodian not registered" |
No authority | The signer is not a registered Custodian and does not have authority to call this function | 403 | "AccessControl: account [user account] is missing role [role hash]" |
Already approved | Oracle address tries to approve a wrap multiple times | 400 | “sender has already approved this hash” |
Already complete | Oracle tries to approve a wrap that has already been completed | 400 | “Approval already complete” |
Approver must execute | Oracle tries to execute wrap it didn’t approve | 400 | “An approver must execute” |
Invalid account | Ethereum account is not valid | 400 | “Invalid account” |
Not enough custodians | Trying to unregister a custodian when there are not enough remaining | 400 | “Must contain 7 custodians” |
Response body
Parameter | Format | Definition |
---|---|---|
status | String | consensus_activity event emitted on successful approval RoleRevoked, custodian_unregistered and consensus_activity events are emitted after successfully unregistering a custodian |
Example
tokenURI
Get tokenURI JSON
Implementation
New action: tokenURI
Request body
Parameter | Required | Format | Definition |
---|---|---|---|
_tokenId | Yes | Integer | TokenId of NFT |
Example
Processing
JSON is formed inline and returned in response
Function is overriden
Exception handling
Error condition | Trigger | Type | Error message |
---|---|---|---|
NFT tokenId does not exist | Token ID not present | 400 | No token |
Response