Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Notes: _beforeTokenTransfer action of erc721 OZ implementation has been overridden to prevent tranasfers of NFTs to the contract address. Error: “Cannot transfer to contract”

...

Actions

Description

wrapnft

mint to account with oracle approvals

unwrapnft

burn (called by user)

burnnft

oracles can burn nft by consensus

regoracle

register oracle

unregoracle

unregister oracle

regcust

register custodian

unregcust

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.

tokenURI

retrieve JSON information about NFT

setBaseURI

Change the baseURI path (custodian only)

_baseURI

Retrieve baseURI path (used as first portion of tokenURI)

...

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

Mappings

Members

Description

Custodians
mapping (

Events

Description

wrapped

Event emitted when wrap has completed.
emit wrapped(account, tokenID, obtid);

unwrapped

Event emitted when user has unwrapped NFT.
emit unwrapped(fioaddress, tokenID);

custodian_registered

Event emitted when a custodian has been registered
event custodian_registered(address ethaddress, uint256 eid);

custodian_unregistered

Event emitted when a custodian has been unregistered
event custodian_unregistered(address ethaddress, uint256 eid);

oracle_registered

Event emitted when an oracle has been registered
event oracle_registered(address ethaddress, uint256 eid);

oracle_unregistered

Event emitted when an oracle has been unregistered
event oracle_unregistered(address ethaddress, uint256 eid);

Mappings

consensus_activity

Event emitted when any oracle has approved a transaction
event consensus_activity(string signer, bytes32 hash);

Important: indexhash is emitted after every approval and is required to check status with getApproval action

Mappings

Mappings

Members

Description

Custodians
mapping (address=>custodian) custodians

custodian
- active

Keeps track of the custodians that registered another custodian and their activation

Oracles
mapping (address=>oracles) oracles

oracle
- active

Keeps track of the custodians that registered an oracle and the oracles activation count

Approvals
mapping (bytes32 => bool) approver;

pending
- approvals
- account
- obtid

Keeps track of the approvals by obtid, the number of approvals, and the details of the approval
This table is also used for oracle and custodian registration

...

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:
1 - The Oracles vote in who can execute the next essential action as follows:
Oracle 1 executes wrap action to mint using FIO obtid xxxxxxxx
Oracle 2 wrap action to transfer for the same FIO obtid to Alice
Oracle 3 executes wrap action for mint, but wrong recipient is provided. Trx goes to Alice defined by oracle 1 and 2

2 - The Oracles can sign the next transfer action as a multisig
This could be quite costly on the ethereum chain, but is possible to implement.
Example of multisig on ethereum https://github.com/christianlundkvist/simple-multisig/blob/master/contracts/SimpleMultiSig.sol

Decision: We do not want any Oracle admin functions.

Custodian “Admin” functions

Are there contract actions that should be exposed to Custodian “admin” approval?

  • Pause switch is an admin function.

  • Use case: refunds. If someone calls unwrap to the wrong address and it gets lost. We may want to enable the ability for Custodians to call 2/3+1 approval for a “mint” refund.

    • This can already be done by the Oracles calling wrap to a specific Ethereum address.

TBD: Are there any other contract actions that might be used for “admin” functionality?

Unwrap fee

Is there a fee for unwrap?

Yes. Unwrapping user pays the gas fee

...

  • 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

...

Implementation

  • New action: unwrapnft

Which FIO parameter should get passed in?

  • FIO Address

    • Less likely to be input incorrectly

    • Requires someone who buys NFT and then wants to unwrap it to own a FIO Handle.

    • Devs agree that FIO Handle is probably the cleanest path.

  • FIO Handle hashed

    • Hash of address somewhat obfuscates the recipient

  • FIO Pub Key

    • Would allow for a checksum key check in the contract (which consumes gas). Not sure if the public key checksum is worth it

  • Current Decision: Use FIO Handle

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

Code Block
{
  "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

...

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

Code Block
{
  "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
Code Block
  {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0x788d48faa5632eaad296ac61a818f1d4d7abbee940fd6c9e486b179486dfde7a',
      blockHash: '0xe18f9250af3b0a8f2a4f253cadf6eb6595281675b08dd477e8bf4761c79d189d',
      blockNumber: 32,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_aff8da75',
      event: 'Approval',
      args: [Result]
    },
    {
      logIndex: 1,
      transactionIndex: 0,
      transactionHash: '0x788d48faa5632eaad296ac61a818f1d4d7abbee940fd6c9e486b179486dfde7a',
      blockHash: '0xe18f9250af3b0a8f2a4f253cadf6eb6595281675b08dd477e8bf4761c79d189d',
      blockNumber: 32,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_c254637f',
      event: 'Transfer',
      args: [Result]
    },
    {
      logIndex: 2,
      transactionIndex: 0,
      transactionHash: '0x788d48faa5632eaad296ac61a818f1d4d7abbee940fd6c9e486b179486dfde7a',
      blockHash: '0xe18f9250af3b0a8f2a4f253cadf6eb6595281675b08dd477e8bf4761c79d189d',
      blockNumber: 32,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_f1c5d062',
      event: 'unwrapped',
      args: [Result]
    }

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

Code Block
{
  "tokenId": 1,
  "obtid": "dfe50aad8e2271f84f87b8e603776d7e7970c636bb899c8993c08e9e2d21c106"
}

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
Code Block
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0xbddf04ea3eed4ae7c47362e006323e674d6f4f9595f9af3f6d72247bd53bd3b3',
      blockHash: '0x2c0f520befbee0a4b3f898132b4702e64376382f5ed92c3fbdd64f01c2aff35f',
      blockNumber: 30,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_8c03207d',
      event: 'Approval',
      args: [Result]
    },
    {
      logIndex: 01,
      transactionIndex: 0,
      transactionHash: '0x788d48faa5632eaad296ac61a818f1d4d7abbee940fd6c9e486b179486dfde7a0xbddf04ea3eed4ae7c47362e006323e674d6f4f9595f9af3f6d72247bd53bd3b3',
      blockHash: '0xe18f9250af3b0a8f2a4f253cadf6eb6595281675b08dd477e8bf4761c79d189d0x2c0f520befbee0a4b3f898132b4702e64376382f5ed92c3fbdd64f01c2aff35f',
      blockNumber: 3230,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_aff8da75c037a4cf',
      event: 'ApprovalTransfer',
      args: [Result]
    },
    {
      logIndex: 12,
      transactionIndex: 0,
      transactionHash: '0x788d48faa5632eaad296ac61a818f1d4d7abbee940fd6c9e486b179486dfde7a0xbddf04ea3eed4ae7c47362e006323e674d6f4f9595f9af3f6d72247bd53bd3b3',
      blockHash: '0xe18f9250af3b0a8f2a4f253cadf6eb6595281675b08dd477e8bf4761c79d189d0x2c0f520befbee0a4b3f898132b4702e64376382f5ed92c3fbdd64f01c2aff35f',
      blockNumber: 3230,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_c254637f0cbddd9a',
      event: 'Transferdomainburned',
      args: [Result]
    },
    {
      logIndex: 23,
      transactionIndex: 0,
      transactionHash: '0x788d48faa5632eaad296ac61a818f1d4d7abbee940fd6c9e486b179486dfde7a0xbddf04ea3eed4ae7c47362e006323e674d6f4f9595f9af3f6d72247bd53bd3b3',
      blockHash: '0xe18f9250af3b0a8f2a4f253cadf6eb6595281675b08dd477e8bf4761c79d189d0x2c0f520befbee0a4b3f898132b4702e64376382f5ed92c3fbdd64f01c2aff35f',
      blockNumber: 3230,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_f1c5d062f8650889',
      event: 'unwrappedconsensus_activity',
      args: [Result]
    }

Oracles - register/unregister

...

  • 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

Code Block
{
  "account": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

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
Code Block
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0x966530233a34543db3bffe10c57ecd27091c113eade2d42e35c9005bb000b2e6',
      blockHash: '0x73e13c8a1f2da8acb67341488a36bacdfa5e3d9d97414cbd4c23fac22a64bc3f',
      blockNumber: 25,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_ee623d8e',
      event: 'RoleGranted',
      args: [Result]
    },
    {
      logIndex: 1,
      transactionIndex: 0,
      transactionHash: '0x966530233a34543db3bffe10c57ecd27091c113eade2d42e35c9005bb000b2e6',
      blockHash: '0x73e13c8a1f2da8acb67341488a36bacdfa5e3d9d97414cbd4c23fac22a64bc3f',
      blockNumber: 25,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_65a9a9ce',
      event: 'oracle_registered',
      args: [Result]
    },
    {
      logIndex: 2,
      transactionIndex: 0,
      transactionHash: '0x966530233a34543db3bffe10c57ecd27091c113eade2d42e35c9005bb000b2e6',
      blockHash: '0x73e13c8a1f2da8acb67341488a36bacdfa5e3d9d97414cbd4c23fac22a64bc3f',
      blockNumber: 25,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_91bff696',
      event: 'consensus_activity',
      args: [Result]
    }

unregoracle

Unregister Oracle.

Implementation

  • New action: unregoracle

Request body

Parameter

Required

Format

Definition

account

Yes

Ethereum account

Ethereum account owned by the Oracle

Example

Code Block
{
  "account": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

Processing

  • Consensus required. Oracle is removed after 2/3+1 Custodians have called unregoracle with same ethaddress.

  • 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”

No Minimum 3 oracles are registeredrequired

Trying to unregister an oracle when none there are only 3 registered anymore

400

“No “Minimum 3 oracles remaining”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
Code Block
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0x934e8335cbd0e48c2a323ca9bfbeb6a8b1f4145f37b68c157349ae47caf8eba3',
      blockHash: '0x374407d2d6f1200845a7b3c009985bb43c10f27c48dbf8ebacb814b1353f357a',
      blockNumber: 42,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_830fd387',
      event: 'RoleRevoked',
      args: [Result]
    },
    {
      logIndex: 1,
      transactionIndex: 0,
      transactionHash: '0x934e8335cbd0e48c2a323ca9bfbeb6a8b1f4145f37b68c157349ae47caf8eba3',
      blockHash: '0x374407d2d6f1200845a7b3c009985bb43c10f27c48dbf8ebacb814b1353f357a',
      blockNumber: 42,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_27824a7b',
      event: 'oracle_unregistered',
      args: [Result]
    },
    {
      logIndex: 2,
      transactionIndex: 0,
      transactionHash: '0x934e8335cbd0e48c2a323ca9bfbeb6a8b1f4145f37b68c157349ae47caf8eba3',
      blockHash: '0x374407d2d6f1200845a7b3c009985bb43c10f27c48dbf8ebacb814b1353f357a',
      blockNumber: 42,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_7e6cdb8a',
      event: 'consensus_activity',
      args: [Result]
    }

...

  • 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

Code Block
{
  "account": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

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

...

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

Code Block
{
  "account": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
}

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
Code Block
    {
      logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0x9b72e1dcad97f39a127e064dd055d44958a1af97cc83a271e78f4243dee68711',
      blockHash: '0x883323d797bd7d25179b021835a2f4d2200aa9aeb2f2431357574cffd8797133',
      blockNumber: 8,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_fbbd6f39',
      event: 'RoleRevoked',
      args: [Result]
    },
    {
      logIndex: 1,
      transactionIndex: 0,
      transactionHash: '0x9b72e1dcad97f39a127e064dd055d44958a1af97cc83a271e78f4243dee68711',
      blockHash: '0x883323d797bd7d25179b021835a2f4d2200aa9aeb2f2431357574cffd8797133',
      blockNumber: 8,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_81d06a32',
      event: 'custodian_unregistered',
      args: [Result]
    },
    {
      logIndex: 2,
      transactionIndex: 0,
      transactionHash: '0x9b72e1dcad97f39a127e064dd055d44958a1af97cc83a271e78f4243dee68711',
      blockHash: '0x883323d797bd7d25179b021835a2f4d2200aa9aeb2f2431357574cffd8797133',
      blockNumber: 8,
      address: '0x9488Ca72f463f3faB9357Fc40B2786eaeD646F8D',
      type: 'mined',
      id: 'log_e1677ec2',
      event: 'consensus_activity',
      args: [Result]
    }

...

tokenURI

Get tokenURI JSON

Implementation

  • New action: tokenURI

Request body

Parameter

Required

Format

Definition

_tokenId

Yes

Integer

TokenId of NFT

Example

Code Block
{
  "_tokenId": "1"
}

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

...