Skip to main content

Extend a persistent entry and contract using the JavaScript SDK

Persistent storage provides a durable mechanism to retain data on the network over a long period. User data, contract instances and their corresponding code (wasm hash) are stored as instance storage with persistent durability to ensure that they remain available, and are managed efficiently by the network's archival system.

Each entry made to the persistent storage is stored as a key-value store. An entry consists of:

  • A key (identifier for the data)
  • A value (the data itself)
  • A Time-To-Live (TTL) that determines how long the data remains on the network

When the TTL expires, the data is then archived on the network and is no longer accessible unless it is unarchived. To prevent data and contracts from being inaccessible, the TTL can be extended before it expires.

Extension Process

  1. Create the Ledger key for the entry to be extended.
  2. Build a transaction that includes the extendFootprintTtl operation with your target TTL and attach the Soroban transaction data.
  3. Prepare the transaction, sign it with your secret key, and submit it to the network.

Extending a persistent entry

const StellarSdk = require("@stellar/stellar-sdk");
const { Server } = require("@stellar/stellar-sdk/rpc");

async function extendPersistentEntryTTL(contractId, storageKey, sourceKeypair) {
const server = new Server("https://soroban-testnet.stellar.org");
const account = await server.getAccount(sourceKeypair.publicKey());
const fee = "100"; // BASE_FEE of 100 stroops

// Create an identifier for the persistent entry
const persistentEntry = new StellarSdk.xdr.LedgerKey.contractData({
contract: StellarSdk.Address.fromString(contractId).toScAddress(),
key: StellarSdk.xdr.ScVal.scvSymbol(storageKey),
durability: StellarSdk.xdr.ContractDataDurability.persistent(),
});

// Build the transaction
let transaction = new StellarSdk.TransactionBuilder(account, {
fee,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.extendFootprintTtl({
extendTo: 100_000, // The number of ledgers past the LCL (last closed ledger) by which to extend. Roughly 5 days
}),
)
.setTimeout(30)
.build();

// Attach Soroban transaction data
const sorobanData = new StellarSdk.SorobanDataBuilder()
.setReadOnly(persistentEntry)
.build();
transaction.setSorobanData(sorobanData);

// Prepare the transaction for signing.
transaction = await server.prepareTransaction(transaction);

transaction.sign(sourceKeypair);
return await server.sendTransaction(transaction);
}

Extending the contract code

const StellarSdk = require("@stellar/stellar-sdk");
const { Server } = require("@stellar/stellar-sdk/rpc");

async function extendContractCodeTTL(contractId, sourceKeypair) {
const server = new Server("https://soroban-testnet.stellar.org");
const account = await server.getAccount(sourceKeypair.publicKey());
const fee = "100";

// Get the footprint
const contract = new StellarSdk.Contract(contractId);
const footprint = contract.getFootprint();

// Build the transaction
let transaction = new StellarSdk.TransactionBuilder(account, {
fee,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.extendFootprintTtl({
extendTo: 100_000, //The number of ledgers past the LCL by which to extend
footprint: footprint,
}),
)
.setTimeout(30)
.build();

// Attach Soroban transaction data
const sorobanData = new StellarSdk.SorobanDataBuilder()
.setFootprint(footprint)
.build();
transaction.setSorobanData(sorobanData);

// Prepare the transaction
transaction = await server.prepareTransaction(transaction);

transaction.sign(sourceKeypair);
return await server.sendTransaction(transaction);
}

Extending both the contract and a persistent entry

In Stellar, each Soroban operation must be the only Soroban operation in a single transaction — but a single ExtendFootprintTTLOp extends every entry listed in the transaction's read-only footprint, applying the same extendTo target to all of them. So when several entries should be extended to the same target, you can list them all in one footprint and extend them together in a single operation, as shown below. The one real constraint is that shared extendTo: entries that need different target TTLs cannot share an operation and must be split across separate operations (and therefore separate transactions).

async function extendContractAndPersistentEntry(
contractId,
storageKey,
sourceKeypair,
) {
const server = new Server("https://soroban-testnet.stellar.org");
const account = await server.getAccount(sourceKeypair.publicKey());
const fee = "100";

// Ledger key for the persistent entry...
const persistentEntry = new StellarSdk.xdr.LedgerKey.contractData({
contract: StellarSdk.Address.fromString(contractId).toScAddress(),
key: StellarSdk.xdr.ScVal.scvSymbol(storageKey),
durability: StellarSdk.xdr.ContractDataDurability.persistent(),
});

// ...and the contract's own footprint key.
const contract = new StellarSdk.Contract(contractId);

// A single ExtendFootprintTtl operation extends the whole read-only footprint.
let transaction = new StellarSdk.TransactionBuilder(account, {
fee,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.extendFootprintTtl({
extendTo: 100_000, // one shared target, applied to every key below
}),
)
.setTimeout(30)
.build();

// List every key to extend together in one read-only footprint.
const sorobanData = new StellarSdk.SorobanDataBuilder()
.setReadOnly([persistentEntry, contract.getFootprint()])
.build();
transaction.setSorobanData(sorobanData);

transaction = await server.prepareTransaction(transaction);
transaction.sign(sourceKeypair);
return await server.sendTransaction(transaction);
}

If the entries instead need different target TTLs, extend each in its own transaction — for example by calling the single-entry extendPersistentEntryTTL and extendContractCodeTTL helpers above with different extendTo values.

Usage example:

const contractId = "CBEE2DHGMYRKJKSJO5E55LBKFMPXE57ZWKTXTCRMC5ANIRJ7IW2Y2WVE";
const storageKey = "BALANCE";
const sourceKeypair = StellarSdk.Keypair.fromSecret(
"SXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
);

extendPersistentEntryTTL(contractId, storageKey, sourceKeypair)
.then((result) => console.log("Extension successful:", result))
.catch((error) => console.error("Extension failed:", error));