> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tape.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Examples

Four examples, ordered by how much they pull in. The first needs nothing but HTTP, the second uses the SDK the way most applications do, the third builds the write instruction by hand for integrators who want the wire format and nothing else, and the fourth puts a git repository on a tape.

## 1. Read with plain HTTP

Every track is fetchable by address from any gateway, with no SDK and no signing. Track-addressed URLs never change payloads, so they cache indefinitely ([direct URLs](/sdks/reading#direct-urls)).

<CodeGroup>
  ```bash curl theme={null}
  curl "$TAPE_GATEWAY/object/<track-address>" -o photo.jpg

  # partial content: standard HTTP Range, answered with 206
  curl -H "Range: bytes=0-1048575" "$TAPE_GATEWAY/object/<track-address>" -o first-mb.bin
  ```

  ```typescript fetch theme={null}
  const res = await fetch(`${GATEWAY_URL}/object/${trackId}`);
  if (!res.ok) throw new Error(`gateway ${res.status}`);
  const bytes = new Uint8Array(await res.arrayBuffer());
  ```
</CodeGroup>

## 2. Write and read back with the SDK

The one-call path: a named write dispatched by size, then retrieval by name. Client setup (keypair and gateway endpoint) is in the [quickstart](/sdks/quickstart).

<CodeGroup>
  ```rust Rust theme={null}
  use tape_sdk::Tapedrive;

  let client = Tapedrive::new(rpc, payer);

  // one-time setup: reserve the bucket tape (quickstart covers it)
  let track = client
      .put_object(&bucket, "notes/hello.md", b"# hello tape", Some("text/markdown"))
      .await?;

  let bytes = client.get_object(&bucket.address(), "notes/hello.md").await?;
  assert_eq!(bytes, b"# hello tape");
  ```
</CodeGroup>

The `waitFor` is for the first read only, covering the moment between the write landing and the gateway serving the fresh track. Once it has been served, consecutive reads return immediately.

The snippet writes 12 bytes, which lands inline and is certified on the spot. Anything bigger dispatches to the coded path, where certification is what finishes the write: a quorum of the track's spool group signs that it holds your slices, and gateways serve the track only once that lands (until then, reads answer 400). The SDK runs that finish inside every one-call write ([certify](/sdks/writing#certify-finishing-the-write)).

## 3. Write from a bare Solana transaction

For integrators who want to append data without pulling in the Tapedrive library. The inline write is one instruction whose payload rides the transaction itself, and inline tracks are certified the moment they land, so a single transaction is a complete, finished write. Coded writes are different: they land registered but unserved until a quorum of the spool group certifies them, and nodes eventually reclaim tracks that never certify. Slice distribution and that certification finish are what the SDK write engine does, so above the inline budget, use the [SDK](/sdks/writing).

The instruction below is byte-checked against both the on-chain parser and the SDK's own
builders; the file compiles as-is against `solana-sdk`, and simulating it against the
program deployed on devnet routes it to the `TrackWrite` handler.

### The wire format

Instruction data, all integers little-endian:

| Offset | Size | Field          | Value               |
| ------ | ---- | -------------- | ------------------- |
| 0      | 1    | discriminant   | `0xB0` (TrackWrite) |
| 1      | 1    | kind           | `0x00` (inline)     |
| 2      | 2    | payload length | u16                 |
| 4      | n    | payload        | the bytes, verbatim |

Naming the track makes it an [object](/protocol/architecture/objects) and appends a 12-byte trailer plus the name (skip all of it for a nameless track):

| Offset | Size | Field        | Value                           |
| ------ | ---- | ------------ | ------------------------------- |
| 4+n    | 2    | name length  | u16, 1 to 1024                  |
| 6+n    | 2    | content type | u16 discriminant, `0` = unknown |
| 8+n    | 8    | logical size | u64, the payload length         |
| 16+n   | m    | name         | raw name bytes                  |

Five accounts, in this order:

| # | Account      | Signer | Writable | What it is                                    |
| - | ------------ | ------ | -------- | --------------------------------------------- |
| 0 | fee payer    | yes    | yes      | pays the transaction fee                      |
| 1 | signer       | yes    | no       | the tape's authority, or its delegate         |
| 2 | system state | no     | no       | PDA of `["system"]` under the program         |
| 3 | tape         | no     | yes      | PDA of `["cassette", authority]`              |
| 4 | slot hashes  | no     | no       | `SysvarS1otHashes111111111111111111111111111` |

No track account appears: the track is appended into the tape account's Merkle tree, and its number is assigned on-chain (the tape's next track number), not passed in.

The 825-byte figure is the packet budget for a wallet transaction, not the format's ceiling: CPI instruction data can carry the full 10 KiB, so another program can stage a payload in an account across transactions and land it with a single CPI write.

### The builder, one file

Depends only on `solana-sdk`. The program ID matches the SDK's generated constants; the [networks page](/protocol/architecture/networks) lists per-environment addresses.

```rust tape_write_ix.rs theme={null}
//! Build a Tapedrive inline write instruction with no SDK dependency.
//! Byte-accurate against the program's parser and the SDK builders.

use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::sysvar;

pub const TAPEDRIVE_PROGRAM_ID: Pubkey =
    Pubkey::from_str_const("8R87WfS1qQ5nQTzmu18KtzQY5L4EvoH9GpisuWAVDH4j");

const IX_TRACK_WRITE: u8 = 0xB0; // TapeInstruction::TrackWrite
const KIND_INLINE: u8 = 0x00; // TrackKind::Inline

/// Program-side payload cap. In practice a transaction packet fits
/// roughly 825 payload bytes; beyond that, use the SDK's coded path.
pub const TRACK_WRITE_MAX_BYTES: usize = 10 * 1024;
pub const MAX_NAME_LEN: usize = 1024;

/// Content-type discriminants (full table in the SDK reference).
pub const CONTENT_UNKNOWN: u16 = 0;
pub const CONTENT_TEXT_PLAIN: u16 = 11;
pub const CONTENT_TEXT_MARKDOWN: u16 = 16;
pub const CONTENT_APPLICATION_JSON: u16 = 25;

/// The tape (bucket) account: PDA of ["cassette", authority].
pub fn tape_pda(authority: &Pubkey) -> Pubkey {
    Pubkey::find_program_address(&[b"cassette", authority.as_ref()], &TAPEDRIVE_PROGRAM_ID).0
}

/// The program's system-state account: PDA of ["system"].
pub fn system_pda() -> Pubkey {
    Pubkey::find_program_address(&[b"system"], &TAPEDRIVE_PROGRAM_ID).0
}

/// The track address a write will get: PDA of ["track", tape, track_number LE].
/// Only needed for read-back; the write itself never takes it.
pub fn track_pda(tape: &Pubkey, track_number: u64) -> Pubkey {
    Pubkey::find_program_address(
        &[b"track", tape.as_ref(), &track_number.to_le_bytes()],
        &TAPEDRIVE_PROGRAM_ID,
    )
    .0
}

/// Build the inline write. The tape must already exist (reserve it once with
/// the SDK or CLI), and `signer` must be its authority or delegate.
/// `object` names the track: (name, content type discriminant).
pub fn inline_write_ix(
    fee_payer: &Pubkey,
    signer: &Pubkey,
    tape: &Pubkey,
    payload: &[u8],
    object: Option<(&str, u16)>,
) -> Instruction {
    assert!(!payload.is_empty() && payload.len() <= TRACK_WRITE_MAX_BYTES);

    let mut data = Vec::with_capacity(4 + payload.len());
    data.push(IX_TRACK_WRITE);
    data.push(KIND_INLINE);
    data.extend_from_slice(&(payload.len() as u16).to_le_bytes());
    data.extend_from_slice(payload);
    if let Some((name, content_type)) = object {
        let name = name.as_bytes();
        assert!(!name.is_empty() && name.len() <= MAX_NAME_LEN);
        data.extend_from_slice(&(name.len() as u16).to_le_bytes());
        data.extend_from_slice(&content_type.to_le_bytes());
        data.extend_from_slice(&(payload.len() as u64).to_le_bytes());
        data.extend_from_slice(name);
    }

    Instruction {
        program_id: TAPEDRIVE_PROGRAM_ID,
        accounts: vec![
            AccountMeta::new(*fee_payer, true),
            AccountMeta::new_readonly(*signer, true),
            AccountMeta::new_readonly(system_pda(), false),
            AccountMeta::new(*tape, false),
            AccountMeta::new_readonly(sysvar::slot_hashes::ID, false),
        ],
        data,
    }
}
```

### Sending it

```rust theme={null}
use solana_sdk::compute_budget::ComputeBudgetInstruction;
use solana_sdk::signature::Signer;
use solana_sdk::transaction::Transaction;

let tape = tape_pda(&authority.pubkey());
let ix = inline_write_ix(
    &payer.pubkey(),
    &authority.pubkey(),
    &tape,
    b"# hello tape",
    Some(("notes/hello.md", CONTENT_TEXT_MARKDOWN)),
);

// 100k CU covers a track write
let tx = Transaction::new_signed_with_payer(
    &[ComputeBudgetInstruction::set_compute_unit_limit(100_000), ix],
    Some(&payer.pubkey()),
    &[&payer, &authority],
    rpc.get_latest_blockhash()?,
);
rpc.send_and_confirm_transaction(&tx)?;
```

What lands: an inline track, certified immediately, with its track number auto-assigned. The transaction logs a `TrackWritten` event carrying the number and the derived track address; `track_pda(tape, number)` reproduces the address for read-back through [example 1](#1-read-with-plain-http), and a named write also resolves by name through any listing.

The program enforces the rest: the tape must exist, be inside its activation and expiry epochs, and have capacity for the payload; the signer must be the tape's authority or its configured delegate; a named write's name must be 1 to 1024 bytes. Reservation itself is one SDK or CLI call ([quickstart](/sdks/quickstart)).

## 4. Git with a tape as the remote

`git push` and `git clone` work against a tape through `git-remote-tape`, a standard Git
remote helper. Once it is on your `PATH`, Git learns the `tape://` transport. Each push
writes one packfile as a content-addressed track, and the refs live in one named object
that each push rewrites. Every byte a clone receives is checked against the on-chain
commitment before Git sees it.

### Install it

```bash theme={null}
curl -fsSL https://tape.network/install.sh | sh
```

Git support is included through the installed `git-remote-tape` helper. See
the [installation page](/install) for supported platforms.

### Use it

For Tapenet, complete the [wallet and funding setup](/tapenet/access) first.

Reserve a tape to push to. The helper finds its keypair under `~/.tape/cassettes/`, which is where `tape create` puts it.

```bash theme={null}
tape create --capacity 100m --epochs 720

cd my-repo
git remote add tape tape://<tape-address>
git push tape main
git clone tape://<tape-address> check && git -C check log --oneline -1
```

Anyone with the tape address can clone, with no wallet and no account. Only the tape's keypair can push. Set `TAPE_GATEWAY_URL` to read through a gateway, which is several times faster than reading from storage nodes directly and is the only path from a network that lets only port 443 out. The helper verifies what the gateway returns before handing it to git.

| Step            | Time                                                          |
| --------------- | ------------------------------------------------------------- |
| push, 3 objects | 11 to 14 s through a private RPC, 21 s through the public RPC |
| clone, 12 packs | 5.5 s                                                         |
| pull            | 5 s                                                           |

A push is two chain writes, so it cannot take less than about 7 s. The public RPC rate-limits a push and asks for a ten second wait, which is where the extra time goes.

Two things to know before trusting it with anything that matters. Nothing can be unpushed, because storage is append only, so a secret you push cannot be taken back. And every repository is public: reads are open to anyone with the address, so a private repository here has to mean an encrypted one, which the helper does not do yet.
