Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.toju.network/llms.txt

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

Overview

The createDeposit method uploads files to decentralized storage and creates a payment record on the Solana blockchain.

Estimate Cost First

Before uploading, get a cost estimate:
const estimate = await client.estimateStorageCost(files, durationDays);

console.log(`Cost: ${estimate.costInSOL} SOL (~$${estimate.costInUSD})`);
console.log(`Total size: ${estimate.totalSizeInMB} MB`);

Response

{
  costInSOL: number;      // Cost in SOL
  costInUSD: number;      // Approximate USD value
  totalSizeInMB: number;  // Total file size
  durationDays: number;   // Storage duration
}
Always estimate costs before uploading to avoid transaction failures due to insufficient balance.

Create Deposit

Upload files and create a deposit on-chain:
const result = await client.createDeposit({
  file,                    // File or File[]
  durationDays: 30,        // Storage duration
  payer: publicKey,        // Wallet public key
  userEmail: '[email protected]', // Optional: for expiration warnings
  signTransaction: async (tx) => {
    return await signTransaction(tx);
  },
});

Parameters

file
File | File[]
required
Single file or array of files to upload
durationDays
number
required
Storage duration in days (minimum: 7)
payer
PublicKey
required
Solana wallet public key for payment
signTransaction
Function
required
Async function to sign the transaction
async (tx: Transaction) => Promise<Transaction>
userEmail
string
Optional email for expiration reminders (sent 7 days before)

Response

success
boolean
Whether the upload succeeded
cid
string
Content identifier for the uploaded file(s)
signature
string
Solana transaction signature
url
string
IPFS gateway URL to access the file
message
string
Human-readable success message
error
string
Error message (only if success is false)

Complete Example

import { useDeposit } from '@toju.network/sol';
import { useWallet } from '@solana/wallet-adapter-react';
import { useState } from 'react';
import { toast } from 'sonner';

function UploadComponent() {
  const client = useDeposit('testnet');
  const { publicKey, signTransaction } = useWallet();
  const [files, setFiles] = useState<File[]>([]);
  const [duration, setDuration] = useState(30);
  const [email, setEmail] = useState('');

  const handleUpload = async () => {
    if (!publicKey || !signTransaction) {
      toast.error('Please connect your wallet');
      return;
    }

    // 1. Estimate cost
    const estimate = await client.estimateStorageCost(files, duration);
    console.log(`Estimated cost: ${estimate.costInSOL} SOL`);

    // 2. Check balance
    const balance = await connection.getBalance(publicKey);
    if (balance < estimate.costInSOL * LAMPORTS_PER_SOL) {
      toast.error('Insufficient balance');
      return;
    }

    // 3. Create deposit
    const toastId = toast.loading('Uploading to IPFS...');

    try {
      const result = await client.createDeposit({
        file: files.length === 1 ? files[0] : files,
        durationDays: duration,
        payer: publicKey,
        userEmail: email || undefined,
        signTransaction: async (tx) => {
          toast.loading('Please sign the transaction...', { id: toastId });
          return await signTransaction(tx);
        },
      });

      if (result.success) {
        toast.success(
          `Upload successful! CID: ${result.cid}`,
          { id: toastId, duration: 5000 }
        );
        
        console.log('View file:', result.url);
        console.log('Transaction:', result.signature);
      } else {
        toast.error(result.error, { id: toastId });
      }
    } catch (error) {
      toast.error('Upload failed', { id: toastId });
      console.error(error);
    }
  };

  return (
    <div>
      <input 
        type="file" 
        multiple 
        onChange={(e) => setFiles(Array.from(e.target.files || []))} 
      />
      <input
        type="number"
        value={duration}
        onChange={(e) => setDuration(Number(e.target.value))}
        min={7}
      />
      <input
        type="email"
        placeholder="Email (optional)"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <button onClick={handleUpload}>
        Upload
      </button>
    </div>
  );
}

Single vs Multiple Files

Single File

const file = new File(['content'], 'document.pdf');

const result = await client.createDeposit({
  file,  // Single File object
  durationDays: 30,
  payer: publicKey,
  signTransaction,
});

// Access at: result.url (direct file link)

Multiple Files

const files = [
  new File(['content1'], 'file1.txt'),
  new File(['content2'], 'file2.txt'),
];

const result = await client.createDeposit({
  file: files,  // File array
  durationDays: 30,
  payer: publicKey,
  signTransaction,
});

// Access at:
// result.url             → Directory listing
// result.url/file1.txt   → Individual file
Multiple files are packaged as a directory on IPFS. Pinata returns a single root CID for the directory. See Content Identifiers for details.

Error Handling

Common errors and solutions:
Error: Transaction fails due to insufficient SOLSolution: Check balance before uploading using estimateStorageCost
const estimate = await client.estimateStorageCost(files, duration);
const balance = await connection.getBalance(publicKey);

if (balance / LAMPORTS_PER_SOL < estimate.costInSOL) {
  alert('You need more SOL');
}
Error: User cancels the wallet signature requestSolution: Catch the error and show appropriate message
try {
  const result = await client.createDeposit({...});
} catch (error) {
  if (error.message.includes('User rejected')) {
    toast.info('Transaction cancelled');
  }
}
Error: Failed to upload to IPFS or confirm transactionSolution: Implement retry logic or ask user to try again
const maxRetries = 3;
let attempt = 0;

while (attempt < maxRetries) {
  try {
    const result = await client.createDeposit({...});
    if (result.success) break;
  } catch (error) {
    attempt++;
    if (attempt === maxRetries) throw error;
    await new Promise(r => setTimeout(r, 2000)); // Wait 2s
  }
}
Error: Browser runs out of memory processing large filesSolution: Limit file size or implement chunked uploads
const MAX_SIZE = 100 * 1024 * 1024; // 100 MB

if (file.size > MAX_SIZE) {
  toast.error('File too large. Max 100 MB');
  return;
}

Transaction Flow

What happens when you call createDeposit:
1

Pin to IPFS

Server receives your files and pins them to IPFS. The server returns the authoritative CID for your content.
2

Build Transaction

A Solana transaction is built using that CID along with the payment amount and duration
3

Sign Transaction

Your signTransaction callback is invoked
4

Submit to Network

Transaction is submitted to Solana blockchain
5

Wait for Confirmation

SDK waits for transaction confirmation
6

Confirm Upload

Server marks the upload active in the database, linking the transaction signature to the pinned CID
7

Return Result

CID, transaction signature, and gateway URL are returned to your app

Best Practices

Always Estimate First

Check costs before uploading to avoid failed transactions

Show Progress

Use loading states and toasts for better UX

Handle Errors Gracefully

Provide clear error messages to users

Verify Balance

Ensure sufficient SOL before initiating upload

Upload History

View past uploads

Storage Renewal

Extend storage duration

CID Computation

How CIDs are computed

Storage Payments

Payment mechanics