UPLOAD FLOW

How Uploads Work

A step-by-step walkthrough of the upload process, from file selection to permanent storage on Arweave.

~3 min read

Uploading to Helix takes seconds, but behind that simple interface is a carefully orchestrated process ensuring your data is encrypted, paid for, and permanently stored. Let's trace a file's journey from your device to the permaweb.

The Upload Journey

01

File Selection

You select a file through drag-and-drop or the file picker. The file is read into memory using the browser's FileReader API. At this point, your file has not left your device.

// Read file into memory
const reader = new FileReader();
reader.onload = (e) => {
  const arrayBuffer = e.target.result;
  // File now in memory, ready for processing
};
reader.readAsArrayBuffer(file);
02

Encryption (Optional)

If encryption is enabled (default), a random 256-bit AES key is generated. The file is encrypted using AES-GCM with a random IV. The key is stored locally—it never leaves your browser.

// Generate random key
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

// Generate random IV
const iv = crypto.getRandomValues(new Uint8Array(12));

// Encrypt file data
const encrypted = await crypto.subtle.encrypt(
  { name: 'AES-GCM', iv },
  key,
  fileData
);

// Combine IV + encrypted data for storage
const combined = new Uint8Array(iv.length + encrypted.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encrypted), iv.length);
03

Cost Calculation

We query Irys for the exact cost to store your file permanently. The cost is based on file size and current network conditions. You see the price in SOL before approving anything.

// Get exact cost from Irys
const price = await irys.getPrice(fileBytes);
const priceInSol = irys.utils.fromAtomic(price);

// Display to user
console.log(`Storage cost: ${priceInSol} SOL`);
// Typical: 0.000005 SOL for 1KB (~$0.001)
04

Wallet Approval

Your wallet (Phantom, Solflare, etc.) prompts you to approve the transaction. You see exactly how much SOL will be spent. This is a real Solana transaction transferring funds to Irys.

WALLET PROMPT

Approve transaction

0.000218 SOL to Irys

05

Irys Upload

Once payment confirms on Solana (~400ms), the encrypted data is uploaded to Irys. You receive a transaction ID immediately—this is your file's permanent address.

// Upload to Irys
const receipt = await irys.upload(encryptedData, {
  tags: [
    { name: "Content-Type", value: "application/octet-stream" },
    { name: "App-Name", value: "Helix" },
    { name: "Wallet", value: walletAddress },
  ]
});

// Permanent URL available immediately
const url = `https://arweave.net/${receipt.id}`;
// e.g., https://arweave.net/abc123...xyz
06

Arweave Anchoring

In the background, Irys bundles your data with others and anchors it to the Arweave blockchain. This happens automatically—your file is already accessible via the transaction ID.

Irys Bundle
Your File + Others
Anchor
Bundle verification
Arweave Block
Permanent storage
07

Metadata Storage

We save the transaction ID and metadata to our database, linked to your wallet address. This enables your dashboard and sharing features. The actual file content stays on Arweave.

// Save to database
await prisma.file.create({
  data: {
    walletAddress: wallet.publicKey.toString(),
    transactionId: receipt.id,
    encryptedName: encryptedFileName,  // Client-encrypted
    mimeType: file.type,
    size: file.size,
    isEncrypted: true,
  }
});

Timeline

0.0s

File Selected

User selects file through UI

0.1s

Encryption Complete

Client-side AES-256 encryption

0.2s

Cost Calculated

Irys returns exact storage price

0.5s

Wallet Approved

User signs transaction

0.9s

Solana Confirmed

Payment confirmed on-chain

1.5s

Upload Complete

File accessible at arweave.net/{id}

~10min

Arweave Anchored

Permanent block confirmation

Forever Stored

Data persists permanently

Instant Availability

Your file is accessible within seconds of upload, even though Arweave block confirmation takes longer. Irys provides this instant availability while guaranteeing eventual permanent storage.

What Gets Stored Where

Data
Location
Access
Encrypted file content
Arweave
Public (but encrypted)
Encryption key
Your browser (localStorage)
Only you
Transaction ID
Helix database
Your wallet
File metadata
Helix database (encrypted)
Your wallet

Error Handling

Uploads can fail at various stages. Here's how we handle each:

Wallet Rejection

If you reject the wallet transaction, nothing happens. No data has been uploaded, no payment made. Simply try again when ready.

Insufficient Balance

If your wallet doesn't have enough SOL, we'll show the required amount before you attempt the transaction. No failed partial uploads.

Network Error During Upload

If the upload fails after payment, the funds are held in your Irys balance. You can retry the upload without paying again—the balance persists.

Browser Closed Mid-Upload

If payment succeeded but upload didn't complete, your Irys balance still has the funds. Reconnect your wallet and upload again.

Atomic Operations

We designed the flow so that you never lose money without getting storage. Either the entire process completes, or your funds remain available for retry.

Download Flow

Downloading reverses the upload process:

  1. Fetch encrypted data from arweave.net/{transactionId}
  2. Retrieve encryption key from localStorage
  3. Extract IV from first 12 bytes of data
  4. Decrypt remaining data using AES-GCM
  5. Create blob and trigger browser download
download.ts
async function downloadFile(transactionId: string, key: CryptoKey) {
  // Fetch encrypted data from Arweave
  const response = await fetch(`https://arweave.net/${transactionId}`);
  const encrypted = await response.arrayBuffer();

  // Extract IV (first 12 bytes)
  const iv = new Uint8Array(encrypted.slice(0, 12));
  const ciphertext = encrypted.slice(12);

  // Decrypt
  const decrypted = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv },
    key,
    ciphertext
  );

  // Create download
  const blob = new Blob([decrypted]);
  const url = URL.createObjectURL(blob);
  // Trigger download...
}

Last updated: February 2025