StorageInstance provides access to a namespace storage contract — an on-chain key-value store scoped to your smart account. It is the mechanism that lets one call's output flow into a later call as a runtime value, within the same batch.
import { createComposableBatch } from '@biconomy/smart-batching';
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';
const publicClient = createPublicClient({ chain: base, transport: http() });
const batch = createComposableBatch(publicClient, '0xYourSmartAccountAddress');Created via batch.storage():
const storage = batch.storage();All methods that produce composable calls (write, check, runtimeValue) are async — always await them when passing into batch.add.
| Property | Type | Description |
|---|---|---|
accountAddress |
Address |
The smart account address this storage instance is scoped to |
Generates a unique bigint storage key scoped to this account. Each call returns a different key, so multiple slots within the same batch never collide.
getStorageKey(params?: {
accountAddress?: Address; // override the scoped account
callerAddress?: Address; // defaults to accountAddress
}): Promise<bigint>const storage = batch.storage();
const key1 = await storage.getStorageKey(); // unique key
const key2 = await storage.getStorageKey(); // different unique key — no collisionEncodes a call to write a value into a storage slot. The value is written at execution time when the composability module processes the batch.
write(params: {
value: bigint | boolean | Address | Hex | number; // value to store
storageKey?: bigint; // key returned by getStorageKey(); auto-generated if omitted
slotIndex?: number; // slot index within the key; defaults to 0
accountAddress?: Address;
callerAddress?: Address;
}): Promise<ComposableCall>const storageKey = await storage.getStorageKey();
const amount = parseUnits('10', 6);
batch.add([
// Write a known value into storage at signing time
await storage.write({ storageKey, value: amount }),
// Then use it as a runtime arg in a later call
usdc.write({
functionName: 'transfer',
args: ['0xRecipientAddress', await storage.runtimeValue({ storageKey })],
}),
]);All supported value types:
await storage.write({ storageKey, value: 42n }); // bigint
await storage.write({ storageKey, value: true }); // boolean
await storage.write({ storageKey, value: '0xSomeAddress' }); // Address
await storage.write({ storageKey, value: '0xdeadbeef' }); // Hex
await storage.write({ storageKey, value: 100 }); // numberReturns a RuntimeValue that resolves to the value in a storage slot at execution time. Pass it as an argument to any write call that follows the slot being populated.
runtimeValue(params?: {
storageKey?: bigint;
slotIndex?: number; // defaults to 0
constraint?: RuntimeConstraint;
accountAddress?: Address;
callerAddress?: Address;
}): Promise<RuntimeValue>const storageKey = await storage.getStorageKey();
batch.add([
// Populate the slot via capture
myContract.write({
functionName: 'computeAmount',
args: [parseUnits('5', 6)],
capture: { type: 'execResult', storageKey },
}),
// Use the captured value as a runtime arg in the next call
usdc.write({
functionName: 'transfer',
args: ['0xRecipientAddress', await storage.runtimeValue({ storageKey })],
}),
]);With a constraint — reverts if the slot value does not satisfy the constraint at execution time:
await storage.runtimeValue({
storageKey,
constraint: { gte: parseUnits('1', 6) }, // slot must hold at least 1 USDC
})// Signed constraint — slot value is compared as int256
await storage.runtimeValue({
storageKey,
constraint: { gteSigned: -100n },
})// OR constraint — passes if any one child passes
await storage.runtimeValue({
storageKey,
constraint: { or: [{ eq: 0n }, { gte: parseUnits('1', 6) }] },
})See RuntimeConstraint reference for all available constraint shapes.
Multi-output — reading indexed slots:
// myContract.multiReturn() returns (uint256 a, uint256 b)
// slotIndex 0 → a, slotIndex 1 → b
myContract.write({
functionName: 'multiReturn',
args: [],
capture: { type: 'execResult', storageKey },
}),
usdc.write({
functionName: 'transfer',
args: ['0xRecipientA', await storage.runtimeValue({ storageKey, slotIndex: 0 })],
}),
usdc.write({
functionName: 'transfer',
args: ['0xRecipientB', await storage.runtimeValue({ storageKey, slotIndex: 1 })],
}),Reads a storage slot on-chain during execution and asserts its value against constraints. If any constraint fails the entire batch reverts.
check(params: {
constraint: RuntimeConstraint; // required
storageKey?: bigint;
slotIndex?: number; // defaults to 0
accountAddress?: Address;
callerAddress?: Address;
}): Promise<ComposableCall>const storageKey = await storage.getStorageKey();
batch.add([
myContract.write({
functionName: 'computeAmount',
args: [parseUnits('5', 6)],
capture: { type: 'execResult', storageKey },
}),
// Assert the captured value equals the expected result on-chain
await storage.check({
storageKey,
constraint: { eq: parseUnits('10', 6) },
}),
// Only proceed with the transfer if the captured value passes
usdc.write({
functionName: 'transfer',
args: ['0xRecipientAddress', await storage.runtimeValue({ storageKey })],
}),
]);Checking a specific slot index:
await storage.check({ storageKey, slotIndex: 0, constraint: { eq: 10n } })
await storage.check({ storageKey, slotIndex: 1, constraint: { eq: 21n } })Signed and OR constraints work the same way:
// Signed — slot value compared as int256
await storage.check({ storageKey, constraint: { gteSigned: -100n } })
// OR — passes if any one child passes
await storage.check({ storageKey, constraint: { or: [{ eq: 0n }, { gte: 100n }] } })See RuntimeConstraint reference for all available constraint shapes.
When a captured call returns multiple values, the composability module stores each at an indexed slot derived from the base slot:
slotIndex: 0→ first return valueslotIndex: 1→ second return valueslotIndex: 2→ third return value
When a captured call returns a single value, slotIndex defaults to 0 and can be omitted.
// multiReturn(7, 3) → (sum=10, product=21, greater=true)
myContract.write({
functionName: 'multiReturn',
args: [7n, 3n],
capture: { type: 'execResult', storageKey },
}),
await storage.check({ storageKey, slotIndex: 0, constraint: { eq: 10n } }),
await storage.check({ storageKey, slotIndex: 1, constraint: { eq: 21n } }),
await storage.check({ storageKey, slotIndex: 2, constraint: { eq: 1n } }),