description |
---|
Use the NFTX protocol to access liquidity for NFT buys and sells within marketplaces and aggregators |
NFTX is an NFT liquidity protocol that can be integrated into other NFT marketplace or NFT aggregator (both Gem and Uniswap have already integrated with NFTX).
The steps for displaying the items on your NFT marketplace include
- Fetch NFTX vaults and their NFT holdings & fee settings
- Check the price of buy/sell of those tokens on 0xProtocol for an ETH price
- Call the 0xMarketplaceZap to perform the buy/sell (or the mint (sell) or redeem (buy) functions)
You can make a single request to retrieve all vault data from NFTX, or you can make individual requests depending on your requirements.
{% hint style="info" %} Vault tokens have a 1:1 relationship with the NFTs within the vault.
- Adding an NFT to the vault, a vault token (vToken) is minted.
- Removing an NFT from the vault, a vToken is burned
Retrieving items from the vault, or buying NFTs, requires the 1 token to be burned plus the fees associated with the buy. Likewise, adding an item will return a vToken minus the fees associated with the sell. {% endhint %}
These are the pseudo steps to integrate NFTX NFTs and liquidity into your application
- Retrieve all NFTX vaults from the subgraph, including global fees, and holdings
- Use the
asset > id
in the subgraph response to link the NFTX vault with the appropriate NFT collection in your application - Verify that the vault has
features > enableTargetRedeems
set totrue
so specific NFTs can be bought from the vault. - Check if the vault uses default fees using
usesFactoryFees
, iftrue
use the global fees to calculate the tokens required to buy an NFT, iffalse
use thefees > targetRedeemFee
associated with the vault. To buy an NFT you need 1 token + target redeem fee. - Using the 0xAPI, check the ETH cost to buy the required tokens. As the number of tokens required increases (i.e. the user adds more items to their basket) the average price of the NFT will increase due to price impact on the token buy.
- When the user buys, call the
0xMarketplaceZap
to complete the purchase, or alternatively call your own contracts to buy the token, redeem the NFTs using theredeemTo
function on the vault contract. - For realtime updates on holdings, you can request individual vault holdings.
{% hint style="info" %}
We recommend using the NFTXMarketplace0xZap
contract for interactions to ensure the best price is offered. This address can be found on our contract-addresses.md page.
{% endhint %}
There are 3 main logic approaches that we can undertake in this contract.
- BuyAndRedeem
- BuyAndSwap
- MintAndSell
Both BuyAndSwap
and MintAndSell
functions have 721 and 1155 function variants that can be called. BuyAndRedeem
handles both of these standards in a single function. The 1155 variations will take an additional uint256[] amounts
function parameter to allow multiples of each token to be interacted with.
Purchases vault tokens from 0x with ETH (converted to WETH in the call) and then redeems the tokens for either random or specific token IDs from the vault. The specified recipient will receive the ERC721 tokens, as well as any WETH dust that is left over from the tx.
vaultId
(uint256) : The ID of the NFTX vaultamount
(uint256) : The number of tokens to buyspecificIds
(uint256[]) : An array of any specific token IDs to be mintedswapCallData
(bytes) : The `data` field from the 0x API responseto
(address payable) : The recipient of the token IDs from the tx
- Emits NFTXMarketplace0xZap : Buy(amount, quoteAmount, to);
- Emits NFTXVaultUpgradable : Redeemed(redeemedIds, specificIds, to);
- Emits NFTXMarketplace0xZap : DustReturned(remaining, dustBalance, dustRecipient);
Purchases vault tokens from 0x with ETH (converted to WETH in the call) and then swaps the tokens for either random or specific token IDs from the vault. The specified recipient will receive the ERC721 / ERC1155 tokens, as well as any WETH dust that is left over from the tx.
vaultId
(uint256) : The ID of the NFTX vaultidsIn
(uint256[]) : An array of random token IDs to be mintedspecificIds
(uint256[]) : An array of any specific token IDs to be mintedswapCallData
(bytes) : The `data` field from the 0x API responseto
(address payable) : The recipient of the token IDs from the tx
- Emits NFTXMarketplace0xZap : Swap(idsIn.length, amount, to);
- Emits NFTXVaultUpgradable : Redeemed(redeemedIds, specificIds, to);
- Emits NFTXMarketplace0xZap : DustReturned(remaining, dustBalance, dustRecipient);
Mints tokens on our NFTX vault and sells the generated tokens on 0x.
vaultId
(uint256) : The ID of the NFTX vaultids
(uint256[]) : An array of token IDs to be mintedswapCallData
(bytes) : The `data` field from the 0x API responseto
(address payable) : The recipient of the token IDs from the tx
- Emits NFTXMarketplace0xZap : emit Sell(ids.length, amount, to);
- Emits NFTXVaultUpgradable : Redeemed(redeemedIds, specificIds, to);
- Emits NFTXMarketplace0xZap : DustReturned(remaining, dustBalance, dustRecipient);
The NFTX V2 subgraph is a GraphQL endpoint that is hosted on The Graph. It can be accessed for free through the existing hosted service, and also through The Graph Studio which is a decentralised endpoint supported by a number of indexers.
When making a request is will be a POST
request and the endpoint URL will always remain the same, while the the body of the request will change depending on the data you require.
Hosted service graph endpoint: https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2
Decentralised Studio endpoint: https://gateway.thegraph.com/api/[api-key]/subgraphs/id/4gZf3atMXjYDh4g48Zr83NFX3rkvZED86VqMNhgEXgLc
Studio Playground: https://thegraph.com/explorer/subgraphs/4gZf3atMXjYDh4g48Zr83NFX3rkvZED86VqMNhgEXgLc?view=Playground
{% hint style="info" %} An API key is required when making requests through the decentralised studio. Each request costs around 0.0005 GRT and you recieve free credits when you first sign up for a developer key. {% endhint %}
{% hint style="warning" %} The Graph Hosted service is being decomissioned in Q1 of 2023 and the only way to access the subgraphs will be through the new decentralised studio. {% endhint %}
A few notes about making requests to the Subgraph
- Requests are always POST and the body of the request will define the response
- Reponses from GraphQL are always
200
success responses, even when the request fails. See below for the response from an incorrect query sent
{% code title="200 response from a misformed request body" %}
{
"errors": [
{
"locations": [
{
"column": 1,
"line": 1
}
],
"message": "Unexpected `not[Name]`\nExpected `{`, `query`, `mutation`, `subscription` or `fragment`"
}
]
}
{% endcode %}
The 0x endpoint is used to check the price for buying the tokens required to redeem NFTs from the NFTX vaults.
The price endpoint allows you to get the current price for displaying on the front end. Once someone adds an item to their basket to purchase you want to make another call to the quote
endpoint to get the call data.
{% code title="GET request for pricing" overflow="wrap" %}
https://api.0x.org/swap/v1/price?buyToken=0x269616D549D7e8Eaa82DFb17028d0B212D11232A&sellToken=WETH&buyAmount=1030000000000000000
{% endcode %}
{% code title="GET quote request for buying/selling" overflow="wrap" %}
https://api.0x.org/swap/v1/quote?buyToken=0x269616D549D7e8Eaa82DFb17028d0B212D11232A&sellToken=WETH&buyAmount=1030000000000000000
{% endcode %}
The below request body returns all the data you need to include every collection within NFTX into your marketplace or aggregator. To increase response times and lower the response size you can remove any data points that are not required for your particular implementation.
Data requests for a specific vault can be found below.
{% code title="Request URL" %}
https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2
{% endcode %}
{% code title="GraphQL Query" %}
{
globals {
fees {
mintFee
randomRedeemFee
targetRedeemFee
randomSwapFee
targetSwapFee
}
}
vaults(
first: 1000
where: { vaultId_gte: 0, isFinalized: true, totalHoldings_gt: 0, shutdownDate: "0" }
) {
vaultId
id
is1155
isFinalized
totalHoldings
totalMints
totalRedeems
totalFees
totalSwaps
createdAt
shutdownDate
holdings(first: 1000, orderBy: tokenId, orderDirection: asc) {
id
tokenId
amount
dateAdded
}
token {
id
name
symbol
}
fees {
mintFee
randomRedeemFee
targetRedeemFee
randomSwapFee
targetSwapFee
}
usesFactoryFees
asset {
id
name
symbol
}
eligibilityModule {
id
name
eligibleIds
eligibleRange
}
features {
enableMint
enableRandomRedeem
enableTargetRedeem
enableRandomSwap
enableTargetSwap
}
}
}
{% endcode %}
{% hint style="info" %} The above request body is what the NFTX V2 frontend uses to retrieve all the required data to build out the homepage of the app. You should use a combination of requests to the subgraph to retrieve the data as needed for your own marketplace or aggregator setup. \
Additional request examples are found below. {% endhint %}
Below is a breakdown of the response from the request response.
These are the default fee's that are set for the NFTX protocol. If a vault returns usesFactoryFees: true
then the globals fees are used for items within that vault. This is true even if the vault returns alternative fees.
{% code title="Example response for global fees" lineNumbers="true" %}
"globals": [
{
"fees": {
"mintFee": "100000000000000000",
"randomRedeemFee": "40000000000000000",
"targetRedeemFee": "60000000000000000",
"randomSwapFee": "40000000000000000",
"targetSwapFee": "100000000000000000"
}
}
],
{% endcode %}
The response is defined in wei and shows
fees
: Contains the differnt global fee settings.mintFee
: Selling an item on NFTX. The current default is 10%, or 0.1 tokenrandomRedeemFee
: Buying a random NFT from the vault. Default is 4% or 0.04 tokentargetRedeemFee
: Buying a specific NFT from the vault. Default is 6% of 0.06 tokenrandomSwapFee
: Swapping an existing item in your wallet with a random item in the vault. Default is 4% or 0.04 tokentargetSwapFee
: Swapping an exisitng item in your wallet with a specific NFT in the vault. Default is 10% or 0.1 token
{% hint style="info" %}
Some vaults will not allow all vault features like targetRedeem, targetSwap even though there are fees set globally. See below the details defined in features
{% endhint %}
vaultId
: The squential number of the vault created on NFTX.id
: the token contract addressis1155
: Defines if the collection is an ERC1155 collection. Iffalse
the collection will be ER721, iftrue
is an 1155 collection.isFinalized
: If set totrue
the vault details can not be updated unless going through a governance process and a vote through Aragon. There is awhere
clause on the request to setisFinalized: true
so only finalised vaults are returned in this request.totalHoldings
: The total number of NFTs inside the vault. For 1155's this counts the total number of NFTs, not the total individual NFTtokenId
numbers.totalMints
: total number of NFTs that have been added to the vault since the vault was created. This can be through selling or staking.totalRedeems
: total number of NFTs taken from the vault since the vault was created. This can be done through target buys, random buys, unstaking.totalFees
: total number of fees generated on the vault through buys/sells/swaps.createdAt
: the unix timestamp when the vault was createdshutdownDate
: If this is set to0
the vault is still functional. If there is a value other than0
(a unix timestamp) then the vault has been shut down and should be ignored. This is whyshutdownDate: "0"
is included in thewhere
filter during the initial request.holdings
: provides the first1000
tokens held in the vaultid
: the ID of the NFTtokenId
: the NFT token ID.amount
: always 1 when dealing withERC721
, but if the vault hasis1155: true
then there can be multiple amounts for a single token id.dateAdded
: the unix timestamp the NFT was added to the vault
token
: details about the vault contract/tokenid
: the contract address of the vault on NFTXname
: the vault name defined by the vault creatorsymbol
: the cash tag given to the vault
fees
: these fees are only valid if the following is returnedusesFactoryFees: false
- see definitions in the global fee response above
usesFactoryFees
: if this is set totrue
then the fees from the global fees section should be used and these vault specific fees should be ignored. If this is set tofalse
then the fees defined against the vault fees response should be used.asset
: these are the details about the NFT collection that the vault is based onid
: the contract address for the collection. You would use this to match holdings in NFTX with NFT collections displayed in your marketplace or aggregator.name
: the name of the NFT Collection - this can sometimes be the same as the vaultsymbol
: the symbol for the NFT collections - this can somethimes be the same as the vault
eligibilityModule
: this is important where vaults are set to only allow certain items. Examples include the ArtBlocks collections which are all tied to the same contract, however are differentiated by the NFT id ranges. This is only important if you are looking to allow instant sells on your platform to ensure the NFT is able to go into the vault.features
: when vaults are created they can have certain features disabled. By default, all items are enabled, however for some vaults users choose to remove the ability to choose a specific item to redeem, making the vault more like a lucky dip. For each, the relevant fee is added (or removed for mints) to the transaction.enableMint
: when set totrue
this will allow users to sell NFTs into the vault.enableRandomRedeem
: when set totrue
allows users to retrieve random items from the vault.enableTargetRedeem
: when set totrue
allows users to choose item to buy from teh vault. If this is disabled, you probably don't want to list them on your marketplace or aggregator.enableRandomSwap
: when set totrue
allows users to swap an existing NFT from their wallet that is suitable for the vault with a random one in the vault.enableTargetSwap
: when set totrue
allows users to choose an item to swap out of the vault.
The below request body returns all the data for a specific vault. To increase response times and lower the response size you can remove any data points that are not required for your particular implementation.
The { globals { fees { ... } } }
has been left in, but this could be captured once and cached as the default global fees don't change.
{
globals {
fees {
mintFee
randomRedeemFee
targetRedeemFee
randomSwapFee
targetSwapFee
}
}
vaults(
where: { vaultId: "297" }
) {
vaultId
id
is1155
totalHoldings
holdings(first: 1000, orderBy: tokenId, orderDirection: asc) {
id
tokenId
amount
dateAdded
}
token {
id
name
symbol
}
fees {
mintFee
randomRedeemFee
targetRedeemFee
randomSwapFee
targetSwapFee
}
usesFactoryFees
asset {
id
name
symbol
}
eligibilityModule {
id
name
eligibleIds
eligibleRange
}
features {
enableMint
enableRandomRedeem
enableTargetRedeem
enableRandomSwap
enableTargetSwap
}
}
}
If the response shows that totalHoldings
is more than 1000 then a follow up request containing can be made to get the next set of holdings.
{
vaults(
where: { vaultId: "297" }
) {
holdings(first: 1000, skip: 1000, orderBy: tokenId, orderDirection: asc) {
id
tokenId
amount
dateAdded
}
}
}
The use of skip: 1000
will get the next set of holdings up to 2000
. You can either keep looping based on the original totalHoldings
value, or check to see if the holdings
array has 1000 items, and if you skip to retrieve the next set.
Currently there are only a couple collections with more than 1000 items.
The final step is to price the buy/sell price for the NFTs. All NFTs within the vault are the same floor price. To calculate the price for each item you check the fee settings for the collection, and add that to the number of NFTs being bought. For example, on a vault with the default targetRedeem (the fee to choose an item to buy) of 6% the following tokens would be required...
Number of NFTs | Fee | Total required |
---|---|---|
1 | 6% | 1.06 |
2 | 6% | 2.12 |
5 | 6% | 5.30 |
10 | 6% | 10.60 |
Next, you calculate the amount of ETH required to buy that number of tokens. NFTX recently switched using the 0x protocol when buying tokens through our 0xMarketplaceZap to take advantage of liquidty on any number of providers, including Uniswap V3 concentrated liquidity pools.
Request
{% code title="0x API Request" overflow="wrap" %}
https://api.0x.org/swap/v1/price?buyToken=0x269616D549D7e8Eaa82DFb17028d0B212D11232A&sellToken=WETH&buyAmount=1030000000000000000
{% endcode %}
Response
{% code title="0x API Response" overflow="wrap" %}
{
"chainId": 1,
"price": "66.139686266145680957",
"estimatedPriceImpact": "2.4621",
"value": "0",
"gasPrice": "37000000000",
"gas": "259700",
"estimatedGas": "259700",
"protocolFee": "0",
"minimumProtocolFee": "0",
"buyTokenAddress": "0x269616d549d7e8eaa82dfb17028d0b212d11232a",
"buyAmount": "1030000000000000000",
"sellTokenAddress": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"sellAmount": "68123876854130051385",
"sources": [
{
"name": "0x",
"proportion": "0"
},
{
"name": "Uniswap",
"proportion": "0"
},
{
"name": "Uniswap_V2",
"proportion": "0"
},
{
"name": "Curve",
"proportion": "0"
},
{
"name": "Balancer",
"proportion": "0"
},
{
"name": "Balancer_V2",
"proportion": "0"
},
{
"name": "Bancor",
"proportion": "0"
},
{
"name": "BancorV3",
"proportion": "0"
},
{
"name": "mStable",
"proportion": "0"
},
{
"name": "SushiSwap",
"proportion": "0.9231"
},
{
"name": "Shell",
"proportion": "0"
},
{
"name": "DODO",
"proportion": "0"
},
{
"name": "DODO_V2",
"proportion": "0"
},
{
"name": "CryptoCom",
"proportion": "0"
},
{
"name": "Lido",
"proportion": "0"
},
{
"name": "MakerPsm",
"proportion": "0"
},
{
"name": "KyberDMM",
"proportion": "0"
},
{
"name": "Component",
"proportion": "0"
},
{
"name": "Saddle",
"proportion": "0"
},
{
"name": "Uniswap_V3",
"proportion": "0.07692"
},
{
"name": "Curve_V2",
"proportion": "0"
},
{
"name": "ShibaSwap",
"proportion": "0"
},
{
"name": "Synapse",
"proportion": "0"
},
{
"name": "Synthetix",
"proportion": "0"
},
{
"name": "Aave_V2",
"proportion": "0"
},
{
"name": "Compound",
"proportion": "0"
}
],
"allowanceTarget": "0xdef1c0ded9bec7f1a1670819833240f027b25eff",
"sellTokenToEthRate": "1",
"buyTokenToEthRate": "0.01550116240807967",
"expectedSlippage": null
}
{% endcode %}
Fetch global fees
{% code title="GraphQL request" %}
{
globals {
fees {
mintFee
randomRedeemFee
targetRedeemFee
randomSwapFee
targetSwapFee
}
}
}
{% endcode %}
{% code title="cURL request" overflow="wrap" %}
curl --location --request POST 'https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"{\n globals {\n fees {\n mintFee\n randomRedeemFee\n targetRedeemFee\n randomSwapFee\n targetSwapFee\n }\n }\n}\n","variables":{}}'
{% endcode %}
Fetch all active vault ids with more than one item
{% code title="GraphQL request" %}
{
vaults(
first: 1000
where: { isFinalized: true, totalHoldings_gt: 0, shutdownDate: "0" }
) {
vaultId
}
}
{% endcode %}
{% code title="cURL request" overflow="wrap" %}
curl --location --request POST 'https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"{\n vaults(\n first: 1000\n where: { isFinalized: true, totalHoldings_gt: 0, shutdownDate: \"0\" }\n ) {\n vaultId\n }\n}\n","variables":{}}'
{% endcode %}
Fetch single vault details
The {vaultId}
can be pulled from response on the Fetch all active vault ids request above, i.e. "0" is the CryptoPunks vault, and "1" is the Avastar vault.
{% code title="GraphQL request" %}
{
vaults(
first: 1000
where: { vaultId: "{vaultId}" }
) {
vaultId
id
is1155
totalHoldings
holdings(first: 1000, orderBy: tokenId, orderDirection: asc) {
id
tokenId
amount
dateAdded
}
token {
id
name
symbol
}
fees {
mintFee
randomRedeemFee
targetRedeemFee
randomSwapFee
targetSwapFee
}
usesFactoryFees
asset {
id
name
symbol
}
eligibilityModule {
id
name
eligibleIds
eligibleRange
}
features {
enableMint
enableRandomRedeem
enableTargetRedeem
enableRandomSwap
enableTargetSwap
}
}
}
{% endcode %}
{% code title="cURL request" overflow="wrap" %}
curl --location --request POST 'https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"{\n globals {\n fees {\n mintFee\n randomRedeemFee\n targetRedeemFee\n randomSwapFee\n targetSwapFee\n }\n }\n vaults(\n first: 1000\n where: { vaultId: \"0\" }\n ) {\n vaultId\n id\n is1155\n totalHoldings\n holdings(first: 1000, orderBy: tokenId, orderDirection: asc) {\n id\n tokenId\n amount\n dateAdded\n }\n token {\n id\n name\n symbol\n }\n fees {\n mintFee\n randomRedeemFee\n targetRedeemFee\n randomSwapFee\n targetSwapFee\n }\n usesFactoryFees\n asset {\n id\n name\n symbol\n }\n eligibilityModule {\n id\n name\n eligibleIds\n eligibleRange\n }\n features {\n enableMint\n enableRandomRedeem\n enableTargetRedeem\n enableRandomSwap\n enableTargetSwap\n }\n }\n}\n","variables":{}}'
{% endcode %}
Fetch singe vault holdings
The {vaultId}
can be pulled from response on the Fetch all active vault ids request above, i.e. "0" is the CryptoPunks vault, and "1" is the Avastar vault.
{% code title="GraphQL request" %}
{
vaults(
where: { vaultId: "{vaultId}" }
) {
totalHoldings
holdings(first: 1000, orderBy: tokenId, orderDirection: asc) {
id
tokenId
amount
dateAdded
}
}
}
{% endcode %}
{% code title="cURL request" overflow="wrap" %}
curl --location --request POST 'https://api.thegraph.com/subgraphs/name/nftx-project/nftx-v2' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"{\n vaults(\n where: { vaultId: \"0\" }\n ) {\n totalHoldings\n holdings(first: 1000, orderBy: tokenId, orderDirection: asc) {\n id\n tokenId\n amount\n dateAdded\n }\n }\n}","variables":{}}'
{% endcode %}