-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* eng * web3 ru * fix sidebar label * fix test * fix
- Loading branch information
Showing
5 changed files
with
637 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
--- | ||
id: web3-00 | ||
title: Uploading to IPFS with NFT.storage | ||
sidebar_label: Uploading to IPFS with NFT.storage | ||
--- | ||
|
||
In this tutorial, we choose [IPFS](https://ipfs.tech), a decentralized storage provider, to host the selected photo and the metadata object. | ||
|
||
We'll also be using [NFT.storage](https://nft.storage/docs/) 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. | ||
|
||
:::info | ||
In the example app, this is done within the `NftMinter` component where `handleSelectImage` is called on a [button press](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/components/NftMinter.tsx#L44). | ||
|
||
We save the image path as state, to be used in the next step. | ||
::: | ||
|
||
```jsx | ||
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: | ||
1. Use the `rn-fetch-blob` library to read the image file into a Base 64 string. | ||
2. Convert to raw bytes by decoding the Base64 string with `Buffer`. | ||
3. Use `fetch` to send a request containing the image bytes to the upload endpoint. | ||
:::info | ||
In the example app, this is handled in a separate helper function [`uploadToIPFS`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/ipfs/uploadToIPFS.ts#L7), which is called later within the larger the [`mintNft`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/components/NftMinter.tsx#L61) function. | ||
::: | ||
:::caution | ||
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. | ||
::: | ||
```jsx | ||
// 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)](https://docs.solanamobile.com/react-native/mobile_nft_minter_tutorial#uploading-to-ipfs-with-nftstorage). This is a string that uniquely identifies your uploaded asset. | ||
You can view your uploaded asset on an [IPFS gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/) by passing in the CID in the URL (e.g: `https://ipfs.io/ipfs/<cid>`). View an [example](https://ipfs.io/ipfs/bafkreicdv4jt7oaah73kvjfnm4f2yd5klbnyehlkpi33kxjakdo6encepe) 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](https://docs.metaplex.com/programs/token-metadata/token-standard#the-non-fungible-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. | ||
|
||
:::info | ||
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. | ||
::: | ||
|
||
```jsx | ||
// 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](https://ipfs.io/ipfs/bafkreidbymwcjxntxak7wkxvblzgtaivg2ktef47i3nfcqtbw4but5ufhe) 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`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/ipfs/uploadToIPFS.ts#L50) 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. | ||
```jsx | ||
// Fire off both uploads aysnc | ||
return Promise.all([ | ||
imageUpload.then(response => response.json()), | ||
metadataUpload.then(response => response.json()), | ||
]); | ||
``` | ||
:::tip | ||
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`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/ipfs/getCid.ts) function. | ||
```jsx | ||
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; | ||
}; | ||
``` | ||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> | ||
<!-- prettier-ignore-start --> | ||
<!-- markdownlint-disable --> | ||
<table> | ||
<tr> | ||
<td align="center"><a href="https://medium.com/react-native-init"><img src="https://avatars0.githubusercontent.com/u/6774813?v=4" width="100px;" alt=""/><br /><sub><b>ДимкаРеактнативный</b></sub></a><br /><a href="#content-gHashTag" title="Content">🖋</a></td> | ||
<td align="center"><a href="https://medium.com/react-native-init"><img src="https://avatars.githubusercontent.com/u/97621153?v=4" width="100px;" alt=""/><br /><sub><b>katsuhira02</b></sub></a><br /><a href="#content-katsuhira02" title="Content">📝</a></td> | ||
</tr> | ||
</table> | ||
<!-- markdownlint-enable --> | ||
<!-- prettier-ignore-end --> | ||
<!-- ALL-CONTRIBUTORS-LIST:END --> | ||
Go! | ||
[](https://link-to.app/xvh7Ush9kl) |
176 changes: 176 additions & 0 deletions
176
i18n/ru/docusaurus-plugin-content-docs/current/web3-00.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
--- | ||
id: web3-00 | ||
title: Uploading to IPFS with NFT.storage | ||
sidebar_label: Загрузка в IPFS с помощью NFT.storage | ||
--- | ||
|
||
В этом руководстве мы выбираем [IPFS](https://ipfs.tech), децентрализованный поставщик хранилища, чтобы разместить выбранное фото и объект метаданных. | ||
|
||
Мы также будем использовать [NFT.storage](https://nft.storage/docs/) для загрузки напрямую в IPFS через их HTTP API. Вы можете зарегистрироваться и получить бесплатный API-ключ на их сайте. | ||
|
||
## Выбор фотографии | ||
|
||
Вам нужно выбрать существующее фото из нашей галереи. Чтобы представить интерфейс выбора и получить путь к файлу, используйте `launchImageLibrary` из библиотеки `react-native-image-picker`. | ||
|
||
:::info | ||
В примере приложения это делается в компоненте `NftMinter`, где `handleSelectImage` вызывается при [нажатии кнопки](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/components/NftMinter.tsx#L44). | ||
Мы сохраняем путь к изображению как состояние, чтобы использовать его на следующем шаге. | ||
::: | ||
|
||
```jsx | ||
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; | ||
``` | ||
## Загрузка фото | ||
Теперь, когда у нас есть путь к изображению, нам нужно загрузить сырые байты файла в IPFS, используя конечную точку NFT.storage `/upload`. | ||
Шаги: | ||
1. Используйте библиотеку `rn-fetch-blob`, чтобы прочитать изображение в строку Base 64. | ||
2. Преобразуйте в сырые байты, декодируя строку Base64 с помощью `Buffer`. | ||
3. Используйте `fetch`, чтобы отправить запрос, содержащий байты изображения, на конечную точку загрузки. | ||
:::caution | ||
На этом шаге вам потребуется предоставить свой собственный API-ключ от NFT.storage. В примере приложения значение `NFT_STORAGE_API_KEY` устанавливается через конфигурацию переменных окружения с использованием библиотеки `react-native-config`. | ||
::: | ||
```jsx | ||
// 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); | ||
``` | ||
Если успешно, `imageData.value.cid` будет содержать действующий [CID (Content Identifier)](https://docs.solanamobile.com/react-native/mobile_nft_minter_tutorial#uploading-to-ipfs-with-nftstorage). Это строка, которая уникально идентифицирует ваш загруженный ресурс. | ||
Вы можете просмотреть свой загруженный ресурс на [IPFS gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/), передав CID в URL (например, `https://ipfs.io/ipfs/<cid>`). | ||
|
||
## Загрузка метаданных | ||
|
||
Далее нам нужно составить объект метаданных, соответствующий [стандарту Metaplex NFT](https://docs.metaplex.com/programs/token-metadata/token-standard#the-non-fungible-standard), затем загрузить его на ту же конечную точку `/upload`. | ||
|
||
:::info | ||
В примере приложения шаг загрузки метаданных также обрабатывается в функции [`uploadToIPFS`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/ipfs/uploadToIPFS.ts#L7), которая вызывается позже в рамках более крупной [`mintNft`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/components/NftMinter.tsx#L61) функции. | ||
Следует отметить небольшое отличие. Поле изображения использует предварительно вычисленный CID для фотографии, а не ожидает завершения загрузки фотографии. Это оптимизация, которая объясняется в следующем разделе. | ||
::: | ||
|
||
```jsx | ||
// 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); | ||
``` | ||
Если успешно, `metadataData.value.cid` теперь будет содержать CID, указывающий на JSON-объект, представляющий метаданные NFT. | ||
Подытожив, у нас теперь есть два CID, которые можно просмотреть в IPFS. Первый — CID нашей загруженной фотографии, и второй — CID JSON Metadata (который имеет ссылку на CID фотографии в поле `image`). | ||
## Предварительное вычисление CID | ||
Вы можете заметить в примере приложения, что во время шага загрузки в [`uploadToIPFS`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/ipfs/uploadToIPFS.ts#L50) мы можем предварительно вычислить CID актива фотографии до его фактической загрузки в IPFS. Это оптимизация, позволяющая нам создавать и загружать объект метаданных без ожидания завершения загрузки фотографии и возврата CID. | ||
```jsx | ||
// Fire off both uploads aysnc | ||
return Promise.all([ | ||
imageUpload.then(response => response.json()), | ||
metadataUpload.then(response => response.json()), | ||
]); | ||
``` | ||
Мы используем это преимущество, загружая как фотографию, так и метаданные асинхронно. | ||
:::tip | ||
Это стало возможным, потому что CID генерируются детерминированно из двоичных данных любого данного актива. Это означает, что мы можем вычислить CID актива до его загрузки в IPFS. | ||
::: | ||
Чтобы вычислить CID из байтов данного актива, см. функцию [`getCid`](https://github.com/solana-mobile/tutorial-apps/blob/main/MobileNFTMinter/ipfs/getCid.ts). | ||
```jsx | ||
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; | ||
}; | ||
``` | ||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> | ||
<!-- prettier-ignore-start --> | ||
<!-- markdownlint-disable --> | ||
<table> | ||
<tr> | ||
<td align="center"><a href="https://medium.com/react-native-init"><img src="https://avatars0.githubusercontent.com/u/6774813?v=4" width="100px;" alt=""/><br /><sub><b>ДимкаРеактнативный</b></sub></a><br /><a href="#content-gHashTag" title="Content">🖋</a></td> | ||
<td align="center"><a href="https://medium.com/react-native-init"><img src="https://avatars.githubusercontent.com/u/97621153?v=4" width="100px;" alt=""/><br /><sub><b>katsuhira02</b></sub></a><br /><a href="#content-katsuhira02" title="Content">📝</a></td> | ||
</tr> | ||
</table> | ||
<!-- markdownlint-enable --> | ||
<!-- prettier-ignore-end --> | ||
<!-- ALL-CONTRIBUTORS-LIST:END --> | ||
Go! | ||
[](https://link-to.app/xvh7Ush9kl) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.