Build your own NFT application with JavaScript
f0.js is a cross-platform (browser and node.js) JavaScript library that lets you easily build decentralized applications around your NFT collection, such as an NFT drop vending machine, NFT inspector, NFT bots, and more.
It abstracts away all the tedious things required to interact with an NFT collection such as:
- IPFS
- Web3
- Merkle Proof (Invite lists)
- Gas rate & coin exchange rate converstion
f0.js
is made up of the following APIs:
- basic api: the core convenience methods to interact with the F0 contract. These apis are usually enough for building a vending maching interface.
- attributes: all the collection related attributes available once initialized.
- web3 api: cross-platform (both browser and node.js) interface to web3.js methods. If you want to build a full fledged web app around your NFT collection, you can use this.
- utility functions: various useful utility functions (such as parsing URL, calculating exchange rate, calculating gas cost, etc.)
WARNING
All demos in this documentation work on the Ethereum Rinkeby testnet.
Make sure you're connected to Rinkeby on your Metamask.
If you want to try the demos with YOUR OWN COLLECTION, first go create your own NFT collection using Factoria: https://factoria.app
Once created, you can interact with the contract using f0.js. This documentation explains how to use f0.js.
- Follow on Twitter to stay updated: https://twitter.com/skogard
- Join Discord to ask questions: https://discord.gg/BZtp5F6QQM
Include at the top of your HTML:
<script src="https://unpkg.com/[email protected]/dist/f0.js"></script>
and you can start using the global variable F0
like this:
const f0 = new F0();
...
Install the f0js
package:
npm install f0js
and you can start using the package like this:
const F0 = require('f0js')
const f0 = new F0()
...
Skinnerbox is a dead simple forkable NFT vending machine app: https://github.com/factoria-org/skinnerbox
WARNING
All demos in this documentation work on the Ethereum Rinkeby testnet.
Make sure you're connected to Rinkeby on your Metamask.
The F0 basic api is an abstraction around:
optimized for interacting with Factoria F0 contract, which will make your life easier compared to if you implemented all of these yourself.
For a low level protocol documentation that walks you through all the steps without using the f0.js library, see https://dev.factoria.app/f0
The F0 basic API will let you build an entire NFT minting website with just a couple of lines of JavaScript code.
If you want to build a full fledged website incorporating ALL F0 contract methods, read the web3 api section.
must construct an instance first
const f0 = new F0()
must initialize the instance
const f0 = await f0.init({
web3: web3, // (required) an instantiated web3 instance (both browser/node.js supported)
contract: contract_address, // (required) contract address
currency: currency, // your fiat currency: usd, jpy, eur, etc. default is "usd"
key: private_key // only in node.js environment
})
The init()
method takes a single JSON argument, which can have the following attributes:
web3
: (required) an instantiated web3 instance. both browser/node.js supportedcontract
: (required) the smart contract addresscurrency
: your fiat currency: "usd", "jpy", "eur", etc. The default is "usd".key
: your private key string (only in node.js environment).
returns the initialized F0 object.
let token = await f0.get(tokenId)
Fetch a token at tokenId. Includes metadata.
tokenId
: The tokenId
Example:
token := {
"tokenURI": "ipfs://bafkreic6dkpzpz637k3jt7g2vv3sey5dtqb2tlmwnfqn4txqhfi3mpw3di",
"raw": {
"image": "ipfs://bafybeihhl6qexxlf3x6jhhqfm2qdyrcctjrozx7nxfm5a7yo6ntgbajhea"
},
"converted": {
"image": "https://ipfs.io/ipfs/bafybeihhl6qexxlf3x6jhhqfm2qdyrcctjrozx7nxfm5a7yo6ntgbajhea"
}
}
token
: the token object, made up of the following attributes:tokenURI
: The tokenURI for the tokenraw
: the raw metadata fetched directly from the tokenURIconverted
: converted metadata that replaces all IPFS links into HTTP links
Get one token
<iframe width="100%" height="600" src="//jsfiddle.net/skogard/g2n9srve/5/embedded/html,result/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>Get multiple tokens
<iframe width="100%" height="700" src="//jsfiddle.net/skogard/epw35jnc/7/embedded/html,result/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>get all invites for this collection for the currently signed-in user.
let invites = await f0.myInvites()
none
returns an invite object. An invite object is made up of the following attributes:
{
<inviteKey1>: {
key: <inviteKey1>,
cid: <the IPFS CID at which the inivted address list for this key is stored>,
condition: {
raw: {
price: <mint condition (how much per token) in wei>,
start: <mint start time>,
limit: <mint limit per address>
},
converted: {
eth: <mint condition converted in ETH>,
<currency>: <mint condition converted in the default currency>,
start: <JavaScript Date object that represents the mint start time>,
limit: <mint limit per address>
}
},
list: <an array of addresses on this list, directly fetched from IPFS>,
proof: <merkle proof for the current user for this invite key>,
invited: <true if the current user is invited, false if not>
},
<inviteKey2>: ...,
<inviteKey3>: ...
...
}
Here's an actual example:
{
"0x74eff7f79000da537011a1b24a2531e9bce29ac07061e4ff141658ae4129231a": {
"key": "0x74eff7f79000da537011a1b24a2531e9bce29ac07061e4ff141658ae4129231a",
"cid": "bafkreihi3zlnto3l47l3tg5dcsrjgbqdjp5hplhjisiqzuq5gbvkbudm3a",
"condition": {
"raw": {
"price": 100000000000000000,
"start": 1639495740,
"limit": 42
},
"converted": {
"eth": 0.1,
"start": "2021-12-14T15:29:00.000Z",
"limit": 42,
"usd": 382.836
}
},
"list": [
"0x73316d4224263496201c3420b36Cdda9c0249574",
"0xB7e390864a90b7b923C9f9310C6F98aafE43F707",
"0xa84e7cc73ae095bed288a799aa6f870f52fce6b4",
"0xFb7b2717F7a2a30B42e21CEf03Dd0fC76Ef761E9"
],
"proof": [
"0xc1eb369bacd52a3044c24f7995207b48131d098e8a38a57b1ebfcf488cd25041",
"0x9466364966e1d172ba293b9b5c173ba9a9638b0c3f3e559c9ec1e4d519f9742e"
],
"invited": true
},
"0x0000000000000000000000000000000000000000000000000000000000000000": {
"key": "0x0000000000000000000000000000000000000000000000000000000000000000",
"cid": "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"condition": {
"raw": {
"price": 420000000000000000,
"start": 1639495860,
"limit": 69
},
"converted": {
"eth": 0.42,
"start": "2021-12-14T15:31:00.000Z",
"limit": 69,
"usd": 1607.9112
}
},
"list": [],
"proof": [],
"invited": true
}
}
You can use the invite object to display relevant information to the users, such as mint price in the default currency, invited addresses, and so on.
<iframe width="100%" height="900" src="//jsfiddle.net/skogard/qmzfxsoc/16/embedded/html,result/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>similar to myInvites, but returns ALL invites for the collection, not just the ones the current user is invited to.
let invites = await f0.invites()
none
See myInvites section.
<iframe width="100%" height="600" src="//jsfiddle.net/skogard/cqmsazyj/10/embedded/html,result/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>get an invite by key
let invite = f0.invite(key)
key
: The invite key. Returns the public invite if left empty ornull
is passed. (example:f0.invite()
orf0.invite(null)
returns the public invite object)
The invite object for the invite key
mint tokens using an invite key
let tokens = await f0.mint(inviteKey, mintCount)
inviteKey
: the invite key to use for minting. ifnull
, it will mint from the public launch invite.mintCount
: how many tokens to mint.
tokens
: An array of tokens successfully minted. Each token has the following attributes:tokenId
: The tokenIdlinks
: some relevant links for the token, such as marketplace URLsrarible
: rarible link for the tokenopensea
: opensea link for the token
Get logs based on an event name.
let logs = await f0.logs(eventName, options)
eventName
: The name of the events to read from. The F0 contract supports the following events:"Invited"
: An invite created on chain"NSUpdated"
: Name or symbol has changed"Configured"
: The config object (placeholder, base, supply, permanent) has been updated"WithdrawerUpdated"
: The withdrawer has been updated
options
: (optional) The "options" object passed to web3.js getPastEvents.
the evm log
<iframe width="100%" height="800" src="//jsfiddle.net/skogard/76gyahzw/11/embedded/html,result/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>Get the next available tokenId for minting
let nextId = await f0.nextId()
none
The next available tokenId
Get the "name" attribute of the collection
let name = await f0.name()
none
The collection name
Get the "symbol" attribute of the collection
let symbol = await f0.symbol()
none
The collection symbol
Get the collection configuration ("placeholder", "base", "supply", "permanent")
let config = await f0.config()
none
Returns an object that includes two attributes:
raw
: the raw config objectconverted
: a converted version of the raw config object that replaces "ipfs://" with "https://ipfs.io/ipfs/"
Get the placeholder metadata JSON
let placeholder = await f0.placeholder()
none
Returns the metadata JSON for the NFT collection placeholder
WARNING
All demos in this documentation work on the Ethereum Rinkeby testnet.
Make sure you're connected to Rinkeby on your Metamask.
once you call f0.init()
, the f0 instance will be initialized with various contract information:
The contract address
const contract_address = f0.address
The default currency set by the init()
function. (default: "usd")
const default_currency = f0.currency
The currently signed in account
const current_user = f0.account
const api = f0.api
let name = await api.name().call()
let symbol = await api.symbol().call()
WARNING
All demos in this documentation work on the Ethereum Rinkeby testnet.
Make sure you're connected to Rinkeby on your Metamask.
You may want to use web3.js to interact with various contract methods directly.
The initialized f0
object exposes an interface named api
that lets you call web3 methods.
NOTE
To learn what contract methods are available, take a look at https://dev.factoria.app/f0/#/?id=methods
f0.js
lets you inject any web3 instance into it to initialize.
This means you can use f0.js both in the browser and node.js.
- In many cases you will use f0.js in the browser through browser wallets like Metamask.
- However you can also build some cool automated features in node.js
In the browser, you can simply inject the default web3 instance initialized from window.ethereum
object:
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/web3/1.7.0-rc.0/web3.min.js"></script>
<script src="https://unpkg.com/f0js/dist/f0.js"></script>
<script>
const f0 = new F0();
document.addEventListener("DOMContentLoaded", async () => {
await f0.init({
web3: new Web3(window.ethereum),
contract: contract_address
})
let tokens = await f0.mint(null, 3) // mint 3 tokens!
})
</script>
</head>
</html>
In node.js, you will need to 2 additional things:
- connect to an RPC provider
- instantiate wallet with a private key
Here's an example where you connect an instance with Alchemy web3:
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const F0 = require("f0js")
const f0 = new F0();
await f0.init({
web3: createAlchemyWeb3(process.env.RINKEBY_URL),
contract: contract_address,
key: PRIVATE_KEY
})
Once initialized, the f0
object lets you call web3 methods using its api
interface:
f0.api.<method_name>.call()
f0.api.<method_name>.send()
which are equivalent to the following web3.js native contract methods:
contract.methods.<method_name>.call()
contract.methods.<method_name>.send()
The benefit of using f0.api
interface is that you can use it cross-platform (both in browser and node.js).
Example:
let tokens = await f0.api.mint({
key: "0x0000000000000000000000000000000000000000000000000000000000000000", proof: []
}, 1).send({
value: "10000000000000000"
})
Example:
let nextId = await f0.api.nextId().call()
let owner = await f0.api.ownerOf(1).call()
WARNING
All demos in this documentation work on the Ethereum Rinkeby testnet.
Make sure you're connected to Rinkeby on your Metamask.
Take any JavaScript object and turn every occurrence of ipfs://...
into https://ipfs.io/ipfs/...
let converted = f0.convert(original)
original
: the original javascript object to convert
the same javascript object, but with all occurrences of ipfs://<cid>
replaced with https://ipfs.io/ipfs/<cid>
Queries coingecko and ethGasStation APIs to return the current exchange rate and the current network fee rate:
let cost = await f0.cost()
none
cost
: the cost object constructed by merging coingecko and ethGasStation API results:gas
: The gwei/gas ratioprice
: the ethereum exchange rate for the default currency (you can set the default currency in init()
{
"gas": {
"fast": 830,
"fastest": 890,
"safeLow": 590,
"average": 700,
"block_time": 15.226415094339623,
"blockNum": 13804250,
"speed": 0.5804163185738596,
"safeLowWait": 21.6,
"avgWait": 3.6,
"fastWait": 0.5,
"fastestWait": 0.5
},
"price": 3787.53
}
pass gas amount and get the estimate cost calculated from cost().
let estimate = await f0.estimate(gas)
gas
: The gas amount (example: 242946)
estimate
: the estimate cost objecteth
: the estimate cost in ETHfiat
: the estimate cost in the default currency (set in init()
example:
{
"eth": {
"fastest": 0.029396466,
"fast": 0.025023438,
"average": 0.017978004,
"safeLow": 0.016034436
},
"usd": {
"fastest": 110.6527074939,
"fast": 94.1919741477,
"average": 67.6719037566,
"safeLow": 60.3560222694
}
}
let parsed = f0.parseURL(url)
url parser helper function for parsing URLs with hashes into key/value pairs.
url
: any web url with a hash value
This function parses any URL that takes the following form:
<protocol>://<host>/<path>#<key1>=<val1>&<key2>=<val2>...
The function parses the key value paris from the URL hash and returns a JavaScript object.
Example:
{
<key1>: <val1>,
<key2>: <val1>,
...
}
Take an invite object (fetched through await f0.invite(inviteKey)
) or an invites object (fetched through await f0.invites()
or await f0.myInvites()
), and returns a new version with an additional currency
attribute attached (calculated based on coingecko api)
let invite = await f0.invite("0x0000000000000000000000000000000000000000000000000000000000000000")
let calculated = await f0.calc(invite)
or
let invites = await f0.invites()
let calculated = await f0.calc(invites)
invite
: A singleinvite
object that contains a "price" attribute, or aninvites
object that contains ALL invite objects in a contract
For example:
let calculated = await f0.calc({
"0x0000000000000000000000000000000000000000000000000000000000000000": {
"key": "0x0000000000000000000000000000000000000000000000000000000000000000",
"cid": "bafkreiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"condition": {
"raw": {
"price": 10000000000000000,
"start": 1639451760,
"limit": 1000
},
"converted": {
"eth": 0.01,
"start": "2021-12-14T03:16:00.000Z",
"limit": 1000
}
},
"list": [],
"proof": [],
"invited": true
}
})
attaches a <currency>
attribute (default is 'usd') under the "condition.converted"
path and returns the result.