Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.inprocess.world/llms.txt

Use this file to discover all available pages before exploring further.

For Feria Nounish Artists: This documentation is inspired by Gabriel Lago from Cali, Colombia, who is building a Feria Nounish MiniApp for the artists participating in this year’s fair. The uploadToArweave function enables artists to upload their artwork and metadata to decentralized storage, creating permanent, onchain records of their contributions to the collective timeline.
Upload files to Arweave decentralized storage for use in your NFT collections and moments. This function works on client-side environments using the @ardrive/turbo-sdk web bundle.
This guide targets Next.js 16. The examples use the App Router and rely on Next.js 16’s native Buffer support in browser bundles.
Note: This function uploads files to Arweave and returns a URI that can be used in collection creation or moment creation.

Installation

Install the Turbo SDK and the Node.js polyfills required for browser environments:
pnpm add @ardrive/turbo-sdk buffer crypto-browserify stream-browserify process

Next.js Configuration

The Turbo SDK uses Node.js built-ins (buffer, crypto, stream) that are not available in the browser. Add the following to your next.config.js to polyfill them and to prevent the SDK from being bundled on the server:
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Prevent the Turbo SDK from being bundled — load it as a server external only
  serverExternalPackages: ["@ardrive/turbo-sdk"],

  // Turbopack alias (Next.js 16 dev server)
  turbopack: {
    resolveAlias: {
      buffer: "buffer/",
      crypto: "crypto-browserify",
      stream: "stream-browserify",
      process: "process/browser",
    },
  },

  // Webpack alias (production builds)
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        buffer: require.resolve("buffer/"),
        crypto: require.resolve("crypto-browserify"),
        stream: require.resolve("stream-browserify"),
        process: require.resolve("process/browser"),
      };
    }
    return config;
  },
};

module.exports = nextConfig;

Function Implementation

client.ts

Create a singleton Turbo client authenticated with your Arweave wallet key:
import { TurboFactory } from "@ardrive/turbo-sdk/web";

const ARWEAVE_KEY = JSON.parse(
  Buffer.from(process.env.NEXT_PUBLIC_ARWEAVE_KEY as string, "base64").toString()
);

const turboClient = TurboFactory.authenticated({ privateKey: ARWEAVE_KEY });

export default turboClient;

patchFetch.ts

The Turbo web SDK passes a ReadableStream as the fetch body, which browsers mark as “disturbed” after the first read. This helper intercepts those calls and converts the stream to a Blob so the body can be reused across SDK retries:
let baseOriginalFetch: typeof globalThis.fetch | null = null;
let patchCount = 0;

const patchFetch = (): (() => void) => {
  if (patchCount === 0) {
    baseOriginalFetch = globalThis.fetch;
    globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
      if (init?.body instanceof ReadableStream) {
        const blob = await new Response(init.body).blob();
        const { duplex: _duplex, ...rest } = init as RequestInit & { duplex?: string };
        return baseOriginalFetch!(input, { ...rest, body: blob });
      }
      return baseOriginalFetch!(input, init);
    };
  }
  patchCount++;

  let restored = false;
  return () => {
    if (restored) return;
    restored = true;
    patchCount--;
    if (patchCount === 0) {
      globalThis.fetch = baseOriginalFetch!;
      baseOriginalFetch = null;
    }
  };
};

export default patchFetch;

uploadFile.ts

Core upload logic using the Turbo client:
import turboClient from "./client";

export const uploadFile = async (
  file: File,
  getProgress: (progress: number) => void = () => {}
): Promise<string> => {
  const uint8Array = new Uint8Array(await file.arrayBuffer());

  const { id } = await turboClient.uploadFile({
    fileStreamFactory: () => Buffer.from(uint8Array),
    fileSizeFactory: () => file.size,
    dataItemOpts: {
      tags: [
        { name: "Content-Type", value: file.type },
        { name: "File-Name", value: file.name },
      ],
    },
    events: {
      onProgress: ({ totalBytes, processedBytes }) => {
        getProgress(Math.round((processedBytes / totalBytes) * 100));
      },
    },
  });

  return `ar://${id}`;
};

uploadToArweave.ts

The main entry point that applies the fetch patch around the upload:
import { uploadFile } from "./uploadFile";
import patchFetch from "./patchFetch";

const uploadToArweave = async (
  file: File,
  getProgress: (progress: number) => void = () => {}
): Promise<string> => {
  const restoreFetch = patchFetch();
  try {
    return await uploadFile(file, getProgress);
  } finally {
    restoreFetch();
  }
};

export default uploadToArweave;

Parameters

ParameterTypeRequiredDescription
fileFileYesThe file to upload to Arweave
getProgress(progress: number) => voidNoOptional callback function to track upload progress (0-100)

Return Value

Returns a Promise that resolves to a string containing the Arweave URI in the format: ar://{transaction_id}

Environment Setup

You’ll need to set up your Arweave wallet key as an environment variable:
# Your Arweave wallet key (base64 encoded)
NEXT_PUBLIC_ARWEAVE_KEY=your_base64_encoded_arweave_key_here

Usage Examples

Basic Upload

import uploadToArweave from "@/lib/arweave/uploadToArweave";

const file = document.getElementById("fileInput").files[0];
const arweaveUri = await uploadToArweave(file);
console.log("File uploaded to:", arweaveUri);
// Output: ar://abc123...

Upload with Progress Tracking

import uploadToArweave from "@/lib/arweave/uploadToArweave";

const file = document.getElementById("fileInput").files[0];

const handleProgress = (progress: number) => {
  console.log(`Upload progress: ${progress}%`);
  document.getElementById("progressBar").style.width = `${progress}%`;
};

try {
  const arweaveUri = await uploadToArweave(file, handleProgress);
  console.log("Upload complete:", arweaveUri);
} catch (error) {
  console.error("Upload failed:", error);
}

Upload JSON Metadata

Use the uploadJson helper to upload plain objects directly:
import { uploadJson } from "@/lib/arweave/uploadJson";

const metadata = {
  name: "Feria Nounish Collection",
  description: "Artwork from the Feria Nounish fair",
  image: "https://example.com/collection-image.jpg",
  external_link: "https://ferianounish.com",
};

const contractUri = await uploadJson(metadata);
console.log("Contract URI:", contractUri);
The uploadJson helper is implemented as:
import uploadToArweave from "./uploadToArweave";

export async function uploadJson(json: object): Promise<string> {
  const jsonString = JSON.stringify(json);
  const file = new File([jsonString], "upload.json", {
    type: "application/json",
  });
  return uploadToArweave(file);
}

Upload for Moment Content

import uploadToArweave from "@/lib/arweave/uploadToArweave";

const momentContent = {
  title: "My Feria Moment",
  content: "This is my contribution to the collective timeline",
  media: "https://example.com/my-artwork.jpg",
  timestamp: new Date().toISOString(),
};

const contentBlob = new Blob([JSON.stringify(momentContent, null, 2)], {
  type: "application/json",
});
const contentFile = new File([contentBlob], "moment.json", {
  type: "application/json",
});

const momentUri = await uploadToArweave(contentFile);
console.log("Moment URI:", momentUri);

React Hook Example

import { useState } from "react";
import uploadToArweave from "@/lib/arweave/uploadToArweave";

export const useArweaveUpload = () => {
  const [isUploading, setIsUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState<string | null>(null);

  const uploadFile = async (file: File): Promise<string> => {
    setIsUploading(true);
    setProgress(0);
    setError(null);

    try {
      const uri = await uploadToArweave(file, setProgress);
      setIsUploading(false);
      return uri;
    } catch (err) {
      setError(err instanceof Error ? err.message : "Upload failed");
      setIsUploading(false);
      throw err;
    }
  };

  return { uploadFile, isUploading, progress, error };
};

// Usage in component
const MyComponent = () => {
  const { uploadFile, isUploading, progress, error } = useArweaveUpload();

  const handleFileUpload = async (
    event: React.ChangeEvent<HTMLInputElement>
  ) => {
    const file = event.target.files?.[0];
    if (file) {
      try {
        const uri = await uploadFile(file);
        console.log("Uploaded to:", uri);
      } catch (err) {
        console.error("Upload failed:", err);
      }
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileUpload} />
      {isUploading && (
        <div>
          <p>Uploading... {progress}%</p>
          <progress value={progress} max={100} />
        </div>
      )}
      {error && <p style={{ color: "red" }}>Error: {error}</p>}
    </div>
  );
};

File Types Supported

The function supports any file type that can be stored on Arweave:
  • Images: PNG, JPEG, GIF, SVG, WebP
  • Documents: PDF, TXT, MD
  • Data: JSON, CSV, XML
  • Media: MP4, MP3, WebM
  • Archives: ZIP, TAR

Best Practices

  1. File Size: Turbo uploads are paid with Credits (not AR tokens directly). Larger files consume more credits. Top up your Turbo Credits balance at https://turbo.ar.io/topup.
  2. Content-Type Tagging: The function automatically tags files with their MIME type and file name for proper handling.
  3. Progress Tracking: Always implement progress tracking for better user experience, especially for larger files.
  4. Error Handling: Wrap upload calls in try-catch blocks to handle network issues or insufficient Turbo Credits.
  5. Metadata Structure: When uploading JSON metadata, follow standard NFT metadata schemas for compatibility with marketplaces.

Integration with Collection Creation

After uploading your metadata to Arweave, use the returned URI in your collection creation:
const contractUri = await uploadToArweave(metadataFile);

const createContractData = encodeFunctionData({
  abi: tokenAbi,
  functionName: "createContract",
  args: [
    contractUri, // Use the Arweave URI here
    collectionName,
    royaltyConfig,
    smartAccount.address,
    [],
  ],
});

Error Handling

Common errors and solutions:
  • Insufficient Turbo Credits: Ensure your Arweave wallet has enough Turbo Credits for the upload. Top up at https://turbo.ar.io/topup
  • Invalid wallet key: Verify your NEXT_PUBLIC_ARWEAVE_KEY is correctly base64 encoded
  • ReadableStream error: The patchFetch helper handles browser stream compatibility automatically
  • File too large: Consider compressing or optimizing your files before upload

Next Steps

After uploading files to Arweave: