ÍNDICE
O que é o Irys?
Conhecimentos Prévios
Criando a Aplicação NextJS e Instalando Dependências
Inicializando o Irys
Financiando um Node Irys
Função de Envio de Arquivo
Codificando a Página de Envio de Imagens
Codificando a Rota da API de Envio
Recuperando Arquivos Enviados da Rede Arweave
Exibindo Imagens Enviadas
Considerações Finais
O Arweave permite o armazenamento descentralizado de dados. Os usuários pagam uma taxa única para enviar seus dados e têm garantia de armazenamento permanente. Os mineradores garantem a permanência dos dados armazenando e replicando dados em troca do token AR nativo do Arweave. Você pode pensar no Arweave como um disco rígido global ou um Amazon S3 descentralizado.
Este artigo explica como enviar arquivos para o Arweave usando o IRYS. Você aprenderá o que é o Irys e como ele torna o envio de dados para o Arweave simples. Você criará uma aplicação NextJS simples para enviar e visualizar as imagens enviadas para o Arweave, então relaxe e vamos começar essa aventura.
O que é o Irys?
O Irys, anteriormente conhecido como Bundlr, é uma camada de proveniência no Arweave. Ele escala os envios para o Arweave por meio de agrupamento (bundling), permite que você pague com tokens diferentes do AR e oferece fortes capacidades de proveniência para seus envios.
Proveniência significa a origem dos dados. No caso do Arweave, quem enviou os dados e quando. A proveniência do Irys é forte porque vai até o milissegundo e é imutável e permanente, então, depois que seus dados estão no Arweave, você pode usar as tags de proveniência do Irys para provar que você é o criador.
Também é mais barato e conveniente usar o Irys para envio para o Arweave, pois você não é cobrado por fragmento (chunk) como quando envia diretamente para o Arweave. Se você enviar apenas 1 KB, pagará apenas por isso, não pelo fragmento inteiro de 256 KB usado para armazená-lo.
Ainda assim, a taxa é dinâmica e depende de vários fatores, como o número exato de bytes que você está enviando e o custo do Arweave, que é o custo de armazenar X quantidade de bytes no Arweave usando preços lineares, etc. Você pode aprender como o custo é calculado na documentação do Irys.
As transações enviadas via Irys são consideradas finalizadas assim que são enviadas, pois o Irys garante que os dados sejam enviados para o Arweave. Ao contrário do Arweave, que tem um tempo de bloco de 2 minutos e requer 50 confirmações de bloco para que os dados sejam finalizados e armazenados permanentemente no Arweave, o Irys não tem essas restrições, pois usa finalidade otimista e tenta enviar os dados para o Arweave até que sejam incluídos em um bloco e confirmados. Você pode assumir com segurança que o Irys finaliza qualquer dado enviado pela rede.
Conhecimentos Prévios
Você deve estar familiarizado com JavaScript e saber como criar aplicativos React. Você deve saber como usar o prompt de comando e ser capaz de instalar pacotes NPM via terminal. Você deve instalar o Node.js v16 e o NPM em sua máquina de desenvolvimento e estar familiarizado com uma carteira baseada em EVM, como a MetaMask.
Criando a Aplicação NextJS e Instalando Dependências
Navegue até seu diretório de trabalho, crie um novo diretório e inicie um novo projeto NPM no diretório recém-criado, onde iremos instalar a aplicação NextJS. Execute o seguinte comando no terminal:
npx create-next-app@latest
Este comando cria uma nova aplicação NextJS no diretório. Por favor, mantenha-o simples e evite usar o novo roteador de aplicativos NextJS.
Instale as dependências executando:
npm install @irys/sdk @irys/query axios formidable
- @irys/sdk é usado para enviar dados via Irys para o Arweave.
- @irys/query é o pacote de consulta do Irys usado para buscar dados e transações enviadas.
- axios faz chamadas de rede a partir do navegador.
- formidable é usado para analisar arquivos enviados no servidor.
Após instalar essas dependências, execute a aplicação digitando no terminal:
npm run dev
A aplicação deve iniciar e rodar em localhost:3000.
Em seguida, navegue até a raiz da sua aplicação NextJS e crie um arquivo de variáveis de ambiente chamado .env-local
. Esse arquivo conterá as chaves privadas da sua carteira.
Adicione o arquivo .env-local
ao seu arquivo gitignore e nunca o envie para um repositório remoto. É aconselhável usar uma chave de carteira descartável para este exercício. Não use a chave de carteira que contém seus ativos de criptomoeda!
Abra o arquivo .env-local
e insira a chave privada da sua carteira.
Private_Key = insira-sua-chave-privada-aqui
Você concluiu a criação de uma aplicação NextJS básica e pode explorar o mundo do Irys para ver como é fácil usá-lo para enviar dados para o Arweave.
Inicializando o Irys
Crie uma nova pasta chamada utils
no diretório raiz da sua aplicação. Dentro da pasta utils
, crie um arquivo chamado utils.js
. Você implementará algumas funções relevantes neste arquivo.
No topo do arquivo utils.js
recém-criado, importe o pacote Irys:
import Irys from "@irys/sdk";
Em seguida, copie e cole o seguinte código dentro do arquivo utils.js
:
const getIrysClient = () => {
const irys = new Irys({
url: "https://devnet.irys.xyz",
token: "matic",
key: Private_Key,
config: {
providerUrl: "https://rpc-mumbai.maticvigil.com",
}
});
// Imprima o endereço da sua carteira
console.log(`endereço da carteira = ${irys.address}`);
return irys;
};
Esse código inicializa o Irys criando um construtor Irys que aceita um objeto com as chaves url, token, key e config. O url é o nó Irys ao qual queremos nos conectar, token é a moeda a ser usada para pagamento, key é a chave privada da carteira e config é necessário apenas se estivermos nos conectando a uma rede de testes, o que estamos fazendo neste tutorial.
Podemos nos conectar a três redes de nós Irys, que são:
As primeiras duas redes listadas são redes principais, que exigem tokens reais para pagamentos antes que você possa usá-las para enviar dados. A última é uma rede de testes, onde você pode usar tokens de teste.
A rede de testes exclui arquivos após 90 dias, enquanto a rede principal garante que seus dados sejam armazenados para sempre no Arweave.
A figura a seguir mostra as moedas suportadas:
Como você está testando coisas, você usará a rede de desenvolvimento com o token Matic Mumbai Polygon para pagamento. Você pode obter um token Matic de teste gratuito aqui.
Financiando um Node Irys
Você pode financiar um Node Irys conectado usando um modelo de pagamento conforme o uso, o que significa que você financia o nó com a quantia necessária para o próximo envio.
Você também pode financiar o nó antecipadamente, mas só pode usar o nó que financiou e também é permitido retirar quaisquer fundos não utilizados do nó.
Abra o arquivo utils/utils.js
e crie uma nova função chamada lazyFundNode
.
export const lazyFundNode = async (size) => {
const irys = getIrysClient();
const price = await irys.getPrice(size);
await irys.fund(price);
};
A função é async e recebe o tamanho dos dados que você está enviando como parâmetro. Ela chama o método getIrysClient
, que definimos anteriormente, para obter um novo cliente Irys que usa Matic Mumbai para pagar pelos envios. Em seguida, você await (aguarda) uma chamada para o método getPrice
para obter o preço para enviar uma imagem/dados do tamanho passado como parâmetro. Finalmente, você aguarda irys.fund(price)
, o que faz com que o token seja retirado da sua carteira para financiar o nó.
Função de Envio de Arquivo
Crie e exporte uma nova função dentro do arquivo utils/utils.js
chamada uploadFileToArweave
. Essa é uma função simples que faz o envio do arquivo.
No topo do arquivo utils.js
, adicione uma instrução de importação para o módulo fs
.
import * as fs from "fs";
export const uploadFileToArweave = async (filepath, tags) => {
const irys = getIrysClient();
const file = fs.readFileSync(filepath);
const { id } = await irys.upload(file, { tags });
console.log("arquivo enviado para ", `https://arweave.net/${id}`);
return id;
};
A função uploadFileToArweave
recebe dois parâmetros: o caminho do arquivo (filepath
) e tags. A função lê o arquivo do sistema de arquivos usando fs.readFileSync(filepath)
. Após ler o arquivo em um buffer
, ela chama irys.upload
, passando o buffer do arquivo e as tags do Arweave.
As tags do Arweave, muitas vezes chamadas apenas de tags, são definidas pelo usuário e são um array de objetos com o seguinte formato:
const tags = [ { name: "...", value: "..."} ];
Você aprenderá a usar essas tags para consultar dados enviados posteriormente.
Codificando a Página de Envio de Imagens
Em seguida, você irá codificar a página para permitir que os usuários selecionem um arquivo de imagem para envio posterior para o servidor para processamento adicional.
Abra o arquivo pages/index.js, remova o conteúdo anterior e substitua-o pelo seguinte conteúdo:
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
import React, { useState } from "react";
import ImageViewer from '@/components/ImageViewer';
const allowedFiles = (file) => {
const fileTypes = ["image/png", "image/jpeg", "image/jpg", "image/gif"];
return fileTypes.includes(file)
}
export default function Home() {
const [imageSource, setImageSource] = useState(null);
const [selectedFile, setSelectedFile] = useState(null)
const [caption, setCaption] = useState("");
const [description, setDescription] = useState("")
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fileInputRef = React.useRef();
const handleImageUpload = (event) => {
if (event.target.files && event.target.files[0] && allowedFiles(event.target.files[0].type)) {
setSelectedFile(event.target.files[0])
const reader = new FileReader();
reader.onload = function (e) {
setImageSource(e.target.result);
};
reader.readAsDataURL(event.target.files[0]);
} else {
setImageSource(null);
}
};
const uploadFileToArweave = async (event) => {
event.preventDefault();
try {
if (selectedFile && caption && description) {
setLoading(true);
const formData = new FormData();
//build the tags
const applicationName = {
name: "application-name",
value: "image-album",
};
const applicationType = { name: "Content-Type", value: selectedFile.type }
const imageCaption = { name: "caption", value: caption };
const imageDescription = { name: "description", value: description }
const metadata = [
applicationName,
imageCaption,
imageDescription,
applicationType
]
formData.append("file", selectedFile);
formData.append("metadata", JSON.stringify(metadata));
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
console.log ("response from the method: ",response.data)
}
} catch (error) {
setError(error.message);
console.log("error ", error);
} finally {
setLoading(false);
setSelectedFile(null);
setImageSource(null);
setCaption("");
setDescription("")
fileInputRef.current.value = null;
}
};
return (
<React.Fragment>
<div className="flex justify-center items-center">
{error && <p>There was an error: {error}</p>}
<div className="bg-white m-2 p-8 rounded shadow-md w-1/3">
<h2 className="text-2xl mb-4">Upload Image</h2>
<div className='flex-col'>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Upload an Image</label>
<input
type="file"
className="hidden"
id="imageInput"
onChange={handleImageUpload}
ref={fileInputRef}
accept=".png, .jpg, .jpeg"
/>
</div>
{/*Div para exibir a imagem selecionada */}
<div className="mt-2">
{imageSource ? (
<img className="mt-2 rounded-lg" src={imageSource} alt="Selected" />
) : (
<p className="text-gray-400">No image selected</p>
)}
</div>
<button
className="mt-2 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
onClick={() => document.getElementById('imageInput').click()}
>
Select Image
</button>
</div>
</div>
{selectedFile &&
<div className='bg-white m-2 p-8 w-2/3'>
<div className="bg-white p-8 m-4 rounded shadow-md">
<h2 className="text-2xl mb-4">Image Details</h2>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Image Caption</label>
<input
value={caption}
onChange={(e) => setCaption(e.target.value)}
type="text"
className="w-full border border-gray-300 px-3 py-2 rounded-md focus:ring focus:ring-blue-300"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Image Description</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full border border-gray-300 px-3 py-2 rounded-md resize-none focus:ring focus:ring-blue-300"
rows= "4"
></textarea>
</div>
<button
disabled={loading}
onClick={uploadFileToArweave}
className="bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600 focus:outline-none focus:ring focus:ring-green-300"
>
Upload Image
</button>
</div>
</div>
}
</div>
</React.Fragment>
)
}
Esta página contém um componente de envio de arquivo para selecionar imagens do computador do usuário. Vamos analisar as funções importantes deste componente.
Os estados do React que armazenam dados, como a imagem selecionada, a legenda da imagem e a descrição.
A função handleImageUpload está vinculada ao evento onChanged do carregador de arquivo.
A função handleImageUpload verifica se o usuário selecionou um arquivo e se o tipo de arquivo está entre os tipos permitidos.
A função allowedFiles verifica se o tipo MIME é "image/png", "image/jpeg", "image/jpg" ou "image/gif". Essa verificação garante simplicidade, pois a melhor maneira de verificar o conteúdo e o tipo de um arquivo é no lado do servidor.
A classe FileReader do navegador lê o arquivo selecionado e salva o resultado no estado React chamado imageSource. O arquivo de imagem é salvo em outro estado chamado selectedFile. A imagem selecionada é exibida com uma tag de imagem. Após a seleção da imagem, um formulário é indicado para que o usuário insira uma legenda e uma descrição para a imagem. A legenda e a descrição são salvas em um estado React chamado caption e description.
A função uploadFileToArweave está vinculada ao manipulador de eventos click do botão UploadImage do formulário de detalhes da imagem. A função verifica a existência de uma imagem selecionada, legenda e descrição. Você cria um novo FormData() para ser passado para o servidor. A imagem selecionada e os metadados são anexados ao FormData().
Você está criando três tags descrevendo a imagem e uma tag descrevendo o nome da aplicação. Você pode definir um número arbitrário de tags; a única restrição é que o tamanho total das tags não deve ultrapassar 2KB. Ao definir as tags, podemos incluir o criador dos dados específicos, que então fica associado a esse pedaço de dados.
const applicationName = { name: "application-name",value: "image-album"};
const applicationType = { name: "Content-Type", value: selectedFile.type }
const imageCaption = { name: "caption", value: caption };
const imageDescription = { name: "description", value: description }
As tags são inseridas em um array que é anexado aos dados do formulário com uma chave de metadata.
formData.append("file", selectedFile);
formData.append("metadata", JSON.stringify(metadata));
Finalmente, você chama o ponto de extremidade da API no servidor usando o axios, passando o formData no corpo da solicitação.
const resposta = await fetch("/api/upload", {
method: "POST",
body: formData,
});
Em seguida, você criará o ponto de extremidade para receber e processar o arquivo para posterior upload para o Arweave.
Codificando a Rota da API de Envio
Crie um novo arquivo pages/api/upload.js. Lembre-se de que o nome do arquivo deve corresponder ao ponto de extremidade. No topo do arquivo upload.js, importe o seguinte:
import formidable from "formidable";
import path from "path";
import * as fs from "fs";
import { lazyFundNode, uploadFileToArweave } from "../../utils/utils";
O pacote formidable é utilizado para processar o arquivo enviado pelo cliente. Você também importou as funções utilitárias que criamos anteriormente: lazyFundNode e uploadFileToArweave.
Em seguida, você exporta um objeto config para configurar o NextJS para não analisar automaticamente as solicitações provenientes da rota da API.
export const config = {
api: {
bodyParser: false,
},
};
Você analisa manualmente a solicitação do cliente usando formidable. Lembre-se, como a solicitação conterá o arquivo enviado e os campos de metadados, você definirá uma handler function para processar a solicitação do cliente.
const handler = async (req, res) => {
try {
fs.mkdirSync(path.join(process.cwd() + "/uploads", "/images"), {
recursive: true,
});
const { fields, files } = await readFile(req);
const filepath = files.file[0].filepath;
//obter o tamanho do arquivo que queremos carregar
const { size } = fs.statSync(filepath);
//financiar o Nó
await lazyFundNode(size);
//enviar o arquivo para o Arweave
const transId = await uploadFileToArweave(filepath, JSON.parse(fields.metadata));
fs.unlinkSync(filepath);
res.status(200).json(transId);
} catch (error) {
console.log("error ", error)
res.status(400).json({ error: error });
}
};
export default handler;
O handler é asynchronous e recebe um request e um objeto response como parâmetros. No topo do arquivo, criamos um diretório para armazenar a imagem enviada. O diretório é criado se não existir dentro da pasta da sua aplicação.
fs.mkdirSync(path.join(process.cwd() + "/uploads", "/images"), {
recursive: true,
});
path.join
adiciona o diretório de trabalho atual ao diretório recém-criado "uploads/images" para obter um caminho absoluto. A função readFile
processa o arquivo e retorna o objeto de arquivo e os campos enviados pelo cliente. Copie o código abaixo e cole-o no arquivo upload.js
.
const readFile = (req) => {
const options = {};
options.uploadDir = path.join(process.cwd(), "/uploads/images");
options.filename = (name, ext, path, form) => {
return Date.now().toString() + "_" + path.originalFilename;
};
options.maxFileSize = 4000 * 1024 * 1024;
const form = formidable(options);
return new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) reject(err);
resolve({ fields, files });
});
});
};
A função readFile
retorna uma Promise, que é resolvida para os campos (fields
) e arquivos (files
). Você cria um objeto de opções vazio para configurar formidable
. Primeiro, você define o uploadDir
, que corresponde ao diretório upload/images
que você criou anteriormente. Você também define filename
e o tamanho máximo do arquivo que deseja enviar para o servidor, neste caso, 4 MB.
Você cria uma instância formidable
passando essas opções:
const form = formidable(options);
A Promise é resolvida com os arquivos e campos se for bem-sucedida ou rejeitada com um erro. Após ler e processar o arquivo, obtemos o tamanho do arquivo usando o método fs.statsSync
, passando o caminho do arquivo como argumento.
const { size } = fs.statSync(filepath);
Você deve financiar o nó Irys com a quantidade de token necessária para enviar o arquivo. Neste exemplo, você está usando um método de pagamento conforme o uso. Você aguarda o resultado da função lazyFundNode(size)
. Lembre-se, esta é uma das funções utilitárias criadas anteriormente que aceita um parâmetro de tamanho para financiar um nó.
Em seguida, você chama a função uploadFileToArweave
, passando o caminho do arquivo e os metadados. Essa função foi criada anteriormente e processa e envia o arquivo para o Arweave.
const transId = await uploadFileToArweave(filepath, JSON.parse(fields.metadata));
Se tudo correr bem, você obtém o ID da transação do Arweave. O arquivo enviado é então excluído do sistema de arquivos do servidor.
fs.unlinkSync(filepath);
A função handler da API retorna o ID da transação para o cliente como response.
Recuperando Arquivos Enviados da Rede Arweave
Até agora, você aprendeu como configurar o Irys e utilizá-lo para enviar dados para o Arweave. Agora, você vai avançar e ver como recuperar dados enviados para o Arweave.
Vamos usar o pacote de consulta Irys para recuperar dados em vez de usar o GraphQL. Crie um novo arquivo na raiz da pasta do seu projeto chamado queries.js
. Dentro do arquivo queries.js
recém-criado, vamos criar e exportar uma instância do pacote de consulta; essa instância exportada será usada em toda a nossa aplicação.
Copie e cole o código dentro do arquivo queries.js
.
importação de Consulta de "@irys/query";
export const minhaConsulta = () => {
const minhaConsulta = new Query({ url: "https://devnet.irys.xyz/graphql" });
return minhaConsulta;
}
O objeto Query
recebe um objeto com a chave url
; esta chave é o nó que queremos consultar. myQuery
é retornada a partir da função acima. É isso que vamos usar para consultar nossos dados enviados.
Exibindo Imagens Enviadas
Crie uma nova pasta chamada components
no diretório raiz do projeto. Crie um novo arquivo dentro da pasta components
chamado ImageViewer.js
. Copie e cole o código abaixo no arquivo.
import React, { useEffect, useState } from "react";
import { myQuery } from "@/queries";
const ImageViewer = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false)
const loadUploadedData = async () => {
setLoading(true)
const query = myQuery();
const results = await query.search("irys:transactions").tags([{ name: "application-name", values: ["image-album"] }]).sort("ASC");
console.log("the result of the transactions: ", results)
setData(results);
setLoading(false);
}
useEffect(() => {
loadUploadedData()
}, [])
if (loading) {
return <div>Loading...........</div>
}
return <div className="flex flex-wrap">
{data &&
data.map(({ tags, id, }) => (
<div className="w-1/5 p-4" key={id}>
<img src={`https://arweave.net/${id}`} className="w-full h-auto rounded"
width={300}
height={300}
alt=""
/>
{tags.map(({ name, value }) => {
if (name == "caption") {
return <h3 className="mt-2 text-lg font-semibold" key={value}>{value}</h3>
} else if (name == "description") {
return <p className="text-gray-500" key={value}>{value}</p>
}
})}
</div>
))}
</div>
}
export default ImageViewer
No topo do arquivo, você importou coisas padrão do React como useState e useEffect. Em seguida, importamos a instância do pacote de consulta do Irys que foi exportada do arquivo queries.js.
import { myQuery } from "@/queries";
const loadUploadedData = async () => {
setLoading(true);
const query = myQuery();
const results = await query.search("irys:transactions").tags([{ name: "application-name", values: ["image-album"] }]).sort("ASC");
console.log("o resultado das transações: ", results);
setData(results);
setLoading(false);
}
Definimos uma função assíncrona loadUploadedData
. Esta função usa o pacote de consulta para recuperar dados. No topo do arquivo, estamos alterando o estado de carregamento para verdadeiro e estamos recuperando a instância do pacote de consulta que definimos.
Em seguida, fazemos uma pesquisa nas transações de dados enviados, limitando-a a transações com uma tag de "application-name"
com um valor de "image-album"
. Isso nos dá a imagem enviada através da nossa aplicação de exemplo, ordenada em ordem ascendente.
const results = await query.search("irys:transactions").tags([{ name: "application-name", values: ["image-album"] }]).sort("ASC");
O resultado retornado é salvo no estado do React e, definimos o estado de carregamento do aplicativo como falso.
A função loadUploadedData
é chamada dentro de um gancho useEffect
com um array de dependências vazia, o que significa que queremos chamar a função apenas uma vez.
Os dados retornados do nó são desestruturados para obter a imagem enviada com a legenda e a descrição exibidas na página. Abra a página index.js
e adicione o componente ImageViewer
à página. Adicione o ImageViewer
antes do fechamento de </React.Fragment>
.
Execute a aplicaçãoe envie uma imagem; quando a imagem for enviada, atualize a página para ver a imagem enviada.
Pensamentos Finais
Este post mostrou como enviar dados para a rede Arweave via Irys. Você construiu uma aplicação de trabalho de exemplo que pode ser personalizada para atender às suas necessidades.
Agradeço por permanecer na causa e chegar ao final deste post. Você pode encontrar o código do post no GitHub.
Artigo escrito por Osikhena Oshomah. Traduzido por Marcelo Panegali.
Top comments (0)