Skip to main content
Persistent storage gives you a durable place to keep contract data on the network for a long time. Anything you can’t cheaply recreate (user balances, configuration, and the like) belongs there. A contract’s instance entry and the Wasm code entry it points to are separate ledger entries, but they live under the same archival rules, so they need the same care. Every persistent entry on the ledger is a key-value pair made up of:
  • a key (the identifier for the data),
  • a value (the data itself), and
  • a Time To Live (TTL), which is how many ledgers remain until the entry is no longer live. (The absolute cutoff is stored on the entry itself as its liveUntilLedger.)
Once an entry’s TTL runs out, the network archives it. Archived persistent data isn’t lost (it can be restored), but nothing can read it until that happens. Keeping the TTL topped up before it expires is the cheaper, less exciting path, and that’s what we’ll walk through here. By the end of this guide you’ll have four helper functions you can drop into a project: one that extends a single persistent entry, one for a contract’s instance, one for the Wasm code that instance points to, and one that extends all three together in a single operation.

The extension process

  1. Build the ledger key for the entry you want to extend.
  2. Build a transaction containing an extendFootprintTtl operation with your target TTL, and attach the Soroban transaction data.
  3. Prepare the transaction (this runs simulation to fill in the resources and fee for you), sign it with your secret key, and submit it to the network.
extendFootprintTtl sets a floor, not an exact value. Per the ExtendFootprintTTLOp semantics, the operation guarantees each entry in the read-only footprint lives for at least extendTo ledgers from now. Entries that already live longer than your target are left alone, so a repeated extension is harmless.There’s also a ceiling: an entry can only be extended up to the network’s maximum TTL, which is a network parameter (check the network limits in the Stellar Lab for the current value).

Extending a persistent entry

The example below assumes your contract keys its data with a symbol, which is the common case. If your contract uses a vec, a map, or a struct as its storage key, build the ScVal that matches instead of reaching for scvSymbol.

Extending the contract instance

Contract.getFootprint() hands you the ledger key for the deployed contract instance, so you don’t have to assemble that one by hand.
This extends the contract instance only. The Wasm code the instance points to is a separate ledger entry with its own TTL, and an extendFootprintTtl operation only touches the keys you actually put in the read-only footprint. A live instance backed by archived code still leaves your contract unusable, so the next section covers the code entry too.If you’re coming from the Rust side, this is a real difference worth internalizing: inside a contract, env.storage().instance().extend_ttl() extends the instance and the code together. The operation used here does not.

Extending the contract code

The code entry is keyed by the Wasm hash rather than by the contract address, and there’s no local way to derive one from the other. That means one extra round trip: read the contract’s instance entry from the network, pull the Wasm hash out of its executable, and build a contractCode ledger key from it. It’s worth knowing every contract deployed from the same Wasm shares that single code entry, so extending it benefits all of them at once.
getWasmHash assumes the contract runs an executable that was deployed. A Stellar Asset Contract instance has a built-in executable instead of a Wasm hash, so executable().wasmHash() will throw for one of those. Check instance.executable().switch() first if your code has to handle both.

Extending all three together

A Soroban operation has to be the only operation in its transaction, so you might expect to need one transaction per entry. Happily, you don’t. A single ExtendFootprintTTLOp extends every entry listed in the transaction’s read-only footprint, applying the same extendTo target to all of them. When a batch of entries should live until the same ledger, list them all in one footprint and extend them together. Here’s all three at once, reusing the getWasmHash helper from the previous section:
That shared extendTo is the one real constraint. Entries that need different target TTLs can’t share an operation, so extend each one in its own transaction. That’s exactly what the extendPersistentEntryTTL, extendContractInstanceTTL, and extendContractCodeTTL helpers above do: each builds its own operation, so each can carry its own extendTo target.
Batching keys into one footprint grows the transaction’s resource usage, since diskReadBytes has to cover the serialized size of every entry in the read-only set. server.prepareTransaction works that out for you, but a large enough batch can bump into the network’s transaction limits. If that happens, split the batch across a few transactions.
Putting one of these to work looks like this: