Skip to content

Commit

Permalink
Upload photo theme (#155)
Browse files Browse the repository at this point in the history
* eng

* web3 ru

* fix sidebar label

* fix test

* fix
  • Loading branch information
gHashTag authored Aug 14, 2023
1 parent 82a0c63 commit f5531b1
Show file tree
Hide file tree
Showing 5 changed files with 637 additions and 259 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12.x'
node-version: '16.x'
- name: Test Build
run: |
if [ -e yarn.lock ]; then
Expand All @@ -32,7 +32,7 @@ jobs:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12.x'
node-version: '16.x'
- name: Add key to allow access to repository
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
Expand Down
185 changes: 185 additions & 0 deletions docs/web3-00.md
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!
[![EnglishMoji!](/img/logo/englishmoji.png)](https://link-to.app/xvh7Ush9kl)
176 changes: 176 additions & 0 deletions i18n/ru/docusaurus-plugin-content-docs/current/web3-00.md
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!
[![EnglishMoji!](/img/logo/englishmoji.png)](https://link-to.app/xvh7Ush9kl)
1 change: 1 addition & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ module.exports = {
],
['UI Kit']: ['unicorn00', 'unicorn01', 'unicorn02'],
['Authentication']: ['auth1-00', 'auth1-01', 'auth1-02'],
['Web3']: ['web3-00'],
['AWS Amplify']: ['amplify-00', 'amplify-01', 'amplify-02', 'amplify-03', 'amplify-04', 'notif-00', 'amplify-05', 'amplify-06'],
['Telegraf']: [
'telegraf/telegraf00'
Expand Down
Loading

0 comments on commit f5531b1

Please sign in to comment.