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.

TL;DR: we pin your files to IPFS first, and the returned CID becomes the authoritative identifier. That CID goes into the on-chain deposit — binding your payment to the exact content that was pinned.

What is a CID?

A Content Identifier (CID) is a cryptographic hash that uniquely identifies content in IPFS. The same content always produces the same CID, enabling verifiable, immutable storage. Example: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi

Why CID Matters

In traditional cloud storage, you pay for space regardless of what you store. With the sdk, you pay for specific content identified by its CID. This creates a binding between your payment and your data.

The Problem We’re Solving

Without binding payments to a specific CID, we’d run into issues like:
  • Upload without paying: Pin files to IPFS first, then never actually pay
  • Pay once, upload many: Reuse a single payment for multiple different files
  • Content substitution: Pay for one file, store something completely different

The Pin-First Approach

We pin files to IPFS before creating the on-chain deposit, making the CID a cryptographic commitment:
User selects files

Server pins files to IPFS

Server returns authoritative CID

CID is included in the on-chain deposit

User pays (locked to this specific CID)

Server marks upload active
The CID in the deposit is exactly what was pinned — no separate verification step needed.

How It Works

1. CID from IPFS

When you upload files, we pin them to IPFS and receive the authoritative CID back:
bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
IPFS uses:
  • SHA-256 hashing
  • dagPB codec (IPFS directory structure)
  • UnixFS for file and directory representation

2. CID in Blockchain Deposit

The CID becomes part of your on-chain deposit:
// Create deposit instruction with CID
const instruction = createDepositInstruction({
  publicKey: userWallet,
  contentCID: cid,  // ← Locked to this CID
  fileSize: totalBytes,
  durationDays: 30,
  depositAmount: costInSOL,
});

// Creates a unique deposit account derived from:
// - User's wallet address
// - SHA-256 hash of the CID
// - Deposit seed constant
This means:
  • Same user + same CID = same deposit account
  • Different CID = different deposit account
  • Can’t reuse deposits for different content

3. Payment Confirmed

Once the on-chain transaction is confirmed, we mark the upload active in our database. The CID is now permanently linked to your payment — accessible for the full duration you paid for.

Benefits

Payment Integrity

Deposits are locked to specific content, preventing fraud

Content Verification

Server verifies uploaded content matches committed CID

Deterministic Accounts

Same content produces same on-chain deposit account

Renewal Capability

CID serves as stable identifier for storage renewals

Multi-File Uploads

For directories with multiple files, the sdk generates a root CID that represents all files:
Root CID (bafybei...)
├── file1.jpg (bafybei...)
├── file2.pdf (bafybei...)
└── file3.txt (bafybei...)
Each file has its own CID, but the directory has a root CID that cryptographically links to all files. This is what gets stored in the blockchain deposit. You can access:
  • Whole directory: ipfs://{rootCID}/
  • Individual file: ipfs://{rootCID}/file1.jpg
  • File by its own CID: ipfs://{fileCID}

Security Properties

Prevents Double-Spending

// First deposit for CID "bafyabc..."
const deposit1 = createDeposit({ cid: "bafyabc..." }); // ✅ Success

// Try to create another deposit for same CID
const deposit2 = createDeposit({ cid: "bafyabc..." }); // ❌ Fails
// Same user + same CID = same PDA = already exists

Prevents Content Substitution

Because we pin files to IPFS before building the deposit, the CID in the on-chain deposit is always exactly what was pinned. There’s no window to swap content after paying — the pin and the payment reference the same CID.

Enables Renewals

// Original upload: CID "bafyabc...", expires in 30 days

// Later, renew storage for same CID
await renewStorageDuration({
  cid: "bafyabc...",  // Find deposit by CID
  additionalDays: 30,
});

// Database lookup: WHERE contentCid = "bafyabc..."
// Extends expiration date ✅

Advanced Details

We use CIDv1 with base32 encoding:
bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
│   │                                                      │
│   └─ base32 encoded hash                                │
└─ multibase prefix (b = base32)
Breaking it down:
  • b: base32 encoding
  • afy: CID version 1
  • bei…: SHA-256 hash of content
Deposit accounts are deterministically derived using:
const cidHash = sha256(cid);  // Hash the CID string
const seeds = [
  "deposit",           // Constant seed
  userPublicKey,       // User's wallet
  cidHash              // Hash of CID
];

const [depositPDA] = PublicKey.findProgramAddressSync(seeds, programId);
This creates an address that:
  • Is unique per user per content
  • Can be found without storing addresses
  • Prevents duplicate deposits
Pinning before creating the deposit means the CID in the on-chain record is always the real IPFS CID — not a locally computed estimate that might differ from what the storage backend produces.Different IPFS implementations can produce different CIDs for the same content depending on chunk size, codec settings, and directory encoding. By pinning first and using the returned CID, we eliminate that mismatch entirely.

Learn More

IPFS CID Spec

Deep dive into content addressing

CAR Format

Content Addressable aRchive format

dagPB Codec

IPFS directory structure

Solana PDAs

Program Derived Addresses