Uploading to IPFS with NFT.storage
In this tutorial, we choose IPFS, a decentralized storage provider, to host the selected photo and the metadata object.
We'll also be using NFT.storage to help upload directly to IPFS, through their HTTP API. You can sign up for a free API key on their website.
Selecting the photo
You need to select an existing photo from our gallery. To present a picker UI and retrieve the file path, use launchImageLibrary
from the react-native-image-picker
library.
In the example app, this is done within the NftMinter
component where handleSelectImage
is called on a button press.
We save the image path as state, to be used in the next step.
import {launchImageLibrary} from 'react-native-image-picker';
const photo = await launchImageLibrary({
selectionLimit: 1,
mediaType: 'photo',
});
const selectedPhoto = photo?.assets?.[0];
if (!selectedPhoto?.uri) {
console.warn('Selected photo not found');
return;
}
const imagePath = selectedPhoto.uri;
Upload the photo
Now that we have the image path, we need to upload the raw bytes of the file to IPFS, using the NFT.storage /upload
endpoint.
The steps:
- Use the
rn-fetch-blob
library to read the image file into a Base 64 string. - Convert to raw bytes by decoding the Base64 string with
Buffer
. - Use
fetch
to send a request containing the image bytes to the upload endpoint.
In the example app, this is handled in a separate helper function uploadToIPFS
, which is called later within the larger the mintNft
function.
During this step, you'll need to provide your own API key from NFT.storage. In the example app, the NFT_STORAGE_API_KEY
value is set through an environment variable config, using the react-native-config
library.
// Read the image file and get the base64 string.
const imageBytesInBase64: string = await RNFetchBlob.fs.readFile(
imagePath,
'base64',
);
// Convert base64 into raw bytes.
const bytes = Buffer.from(imageBytesInBase64, 'base64');
// Upload the image to IPFS by sending a POST request to the NFT.storage upload endpoint.
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${Config.NFT_STORAGE_API_KEY}`,
};
const imageUpload = await fetch('https://api.nft.storage/upload', {
method: 'POST',
headers: {
...headers,
'Content-Type': 'image/jpg',
},
body: bytes,
});
const imageData = await imageUpload.json();
console.log(imageData.value.cid);
If successful, the imageData.value.cid
will contain a valid CID (Content Identifier). This is a string that uniquely identifies your uploaded asset.
You can view your uploaded asset on an IPFS gateway by passing in the CID in the URL (e.g: https://ipfs.io/ipfs/<cid>
). View an example of an uploaded photo on ipfs.io.
Uploading the metadata
Next, we need to construct a metadata object that conforms to the Metaplex NFT Standard, then upload it to the same /upload
endpoint.
Metadata fields:
- Name: The name of the NFT.
- Description: A description of the NFT.
- Image: A URL that hosts the photo. In this case, we use an
ipfs.io
URL with the CID of the uploaded photo.
In the example app, the metadata upload step is also handled within the uploadToIPFS function.
There is a slight difference that should be noted. The image field uses a precomputed CID for the photo, rather than waiting for the photo upload to finish. This is an optimization that is explained in the next section.
// Construct the metadata fields.
const metadata = JSON.stringify({
name,
description,
image: `https://ipfs.io/ipfs/${imageData.value.cid}`,
});
// Upload to IPFS
const metadataUpload = await fetch('https://api.nft.storage/upload', {
method: 'POST',
headers: {
...headers,
'Content-Type': 'application/json',
},
body: metadata,
});
const metadataData = await metadataUpload.json();
console.log(metadataData.value.cid);
If successful, metadataData.value.cid
will now contain a CID that points to a JSON object representing the NFT metadata. View an example of an uploaded metadata object.
To recap, we now have two CIDs that are viewable on IPFS. First, the CID of our uploaded photo, and second, the CID of JSON Metadata (which has a reference to the photo CID in the image
field).
Precomputing the CID
You may notice in the example app, that during the upload step in uploadToIPFS
we're able to precompute the CID of the photo asset before actually uploading it to IPFS. This is an optimization that allows us to construct and upload the metadata object, without waiting for the photo upload to complete and return the CID.
We take advantage of this by uploading both the photo and metadata asynchronously.
// Fire off both uploads aysnc
return Promise.all([
imageUpload.then(response => response.json()),
metadataUpload.then(response => response.json()),
]);
This is made possible because CIDs are generated deterministically from the binary data of any given asset. This mean we can compute the CID of an asset before uploading it to IPFS.
To compute the CID from the bytes of a given asset, see the getCid
function.
import {CID, hasher} from 'multiformats';
const crypto = require('crypto-browserify');
const SHA_256_CODE = 0x12;
const IPLD_RAW_BINARY_CODE = 0x55;
const getCid = async (bytes: Buffer) => {
const sha256 = hasher.from({
// As per multiformats table
// https://github.com/multiformats/multicodec/blob/master/table.csv#L9
name: 'sha2-256',
code: SHA_256_CODE,
encode: input =>
new Uint8Array(crypto.createHash('sha256').update(input).digest()),
});
const hash = await sha256.digest(bytes);
const cid = await CID.create(1, IPLD_RAW_BINARY_CODE, hash);
return cid;
};
ДимкаРеактнативный 🖋 | katsuhira02 📝 |
Go!