Skip to main content
Your program can store data on Tapedrive by calling the Tapedrive program. This page carries two complete examples from the examples directory of the tape repository, both run end to end. Quill is a delegated writer that stores small named objects. Bundle collects a payload across several transactions and writes it as one track, for payloads larger than one transaction carries.

How much fits

What you need

  • The Solana toolchain with cargo build-sbf, and a recent stable Rust for the client and the tests.
  • A wallet with SOL for transaction fees and TAPE for the reservation. If you want to use Tapenet, follow the wallet and funding setup.
  • The tape-api, tape-core and tape-crypto crates, version 0.4.0, which the manifests below pull from crates.io.

Quill: a delegated writer

A tape has an owner and an optional delegate, and either one can write to it. Quill does not own a tape. The tape’s owner delegates writes to a PDA the program controls, ["quill", tape], once. The PDA holds no data. It exists so the program can sign for it. Each write is then two calls. build_track_write_ix from tape-api builds the Tapedrive instruction, with a name and a content type so the write is a named object that a gateway can list and serve. invoke_signed sends it, signing as the PDA. The caller chooses the name and the bytes, and the program relays them. Before it sends anything the program checks four things: the fee payer signed, the writer account is its own PDA for that tape, and the Tapedrive program, its system account and the slot hashes sysvar are the real ones. Tapedrive itself checks the tape, its capacity and its expiry.

Full code

The program is three files.
program/Cargo.toml
program/src/lib.rs
program/src/error.rs
program/src/write.rs
The test module at the end of write.rs is left out here. cargo test runs the write under Mollusk with the real Tapedrive program loaded, so the cross-program call is exercised, not just compiled. The client reserves the tape, delegates it, sends one write and reads the tape back, using solana-client and the same builders.
client/Cargo.toml
client/src/main.rs

Build and deploy

solana program deploy prints the program id. It deploys to whichever cluster your Solana CLI is configured for.

Run it

The client reads three environment variables. all runs reserve, delegate, write and show in one go, and each step is also its own command.
Output
The tape is the one owned by your wallet key itself, at ["cassette", payer], so reserve runs once per wallet. After that, write appends as many objects as you like:

Read it back

The object is readable the moment the write confirms. Use the tape address the client printed:
Output
Tapenet’s gateway serves the same object by name at https://gw.tape.network/site/<tape-address>/greetings/0001. Replace <tape-address> with the tape address your client printed.

Using this in your own program

  • Depend on tape-api. Its builders are pure and need no RPC.
  • Get write access to a tape: own it, or have the owner delegate to your PDA. From the CLI that is tape delegate <pda>. From code it is build_set_tape_delegate_ix, as in the client above.
  • For each write, decide two things. data: inline for small payloads that must be readable at once, coded for large ones. object: Some with a name to make the write listable and servable, None for a bare track keyed by its hash.
  • Build with build_track_write_ix, send with invoke_signed. The instruction wants five accounts in order: fee payer, signer (your PDA), the Tapedrive system account, the tape, and the slot hashes sysvar. The builder lays them out. Check that the Tapedrive program and sysvar accounts the caller passed are the real ones, as write.rs does.
  • The PDA needs no state, so there is nothing to initialise and nothing to close.
  • Anchor and native programs use the code as shown. A Pinocchio program reuses the instruction bytes and the five-account order with its own types.

Bundle: a payload larger than one transaction

A transaction from a wallet fits about 825 bytes of inline data, because the whole payload has to fit one packet. A cross-program call is bound by a different limit, Solana’s 10 KiB cap on instruction data, which is also Tapedrive’s inline maximum. Bundle uses that gap. It keeps one buffer account per tape and payer, at ["bundle", tape, payer]. append adds the next piece of the payload and creates the buffer on the first call. flush writes the finished buffer to Tapedrive with one call. close returns the buffer’s rent. The buffer holds the exact bytes of the track write instruction, so flush points the call straight at the account with no copy. The tape owner sets the buffer PDA as the tape’s delegate once, so it can sign the write. The payer is part of the buffer’s seeds, so only the payer who fills a buffer can append to it, flush it or close it, and flush accepts only the real Tapedrive program as its target. The result is an ordinary inline track, certified on confirm, for payloads between 825 bytes and 10 KiB. Above 10 KiB there is no trick. Use a coded write.

Full code

The program is written with Quasar, a zero-copy no_std framework, and builds with the standard Solana toolchain.
program/Cargo.toml
program/src/lib.rs
program/src/wire.rs
program/src/buffer.rs
program/src/error.rs
program/src/instructions/mod.rs
program/src/instructions/append.rs
program/src/instructions/flush.rs
program/src/instructions/close.rs
The tests drive the real sequence under Mollusk: several appends, then a flush into the loaded Tapedrive program. They also show the instruction layouts a client sends. append is the byte 0 followed by the piece, with the payer, the tape, the buffer, the rent sysvar and the system program. flush is the byte 1, with the payer, the tape, the buffer, the Tapedrive system account, the slot hashes sysvar and the Tapedrive program. close is the byte 2, with the payer, the tape and the buffer.
tests/Cargo.toml
tests/src/lib.rs

Build and test

All four tests pass: a 4 KB payload collected over several appends lands as one inline track, the buffer grows with each append, a flush before any append fails, and an append transaction with a 928-byte piece fits one packet while 1024 does not. Two things in this example track the deployed Tapedrive program and have to be refreshed after a Tapedrive redeploy: TAPEDRIVE_ID in wire.rs, and the prebuilt tests/fixtures/tapedrive.so the tests load.

Troubleshooting