Documentation Index
Fetch the complete documentation index at: https://docs.kynesys.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Storage Programs API Reference
This page is the deep-link reference for the Storage Programs surface of @kynesyslabs/demosdk@v3.1.0. Every signature, return type, and parameter shape on this page is lifted directly from the SDK source. For the narrative walkthrough, start at Getting Started.
The Storage Programs surface lives in two modules:
@kynesyslabs/demosdk/websdk — the Demos class with the storagePrograms accessor (signing + reading).
@kynesyslabs/demosdk/storage — the StorageProgram class (payload builders, validators, ACL helpers), all type aliases, the STORAGE_PROGRAM_CONSTANTS object, and the type guards.
Imports
import { Demos } from "@kynesyslabs/demosdk/websdk"
import {
StorageProgram,
STORAGE_PROGRAM_CONSTANTS,
isStorageProgramPayload,
isValidEncoding,
isValidStorageLocation,
} from "@kynesyslabs/demosdk/storage"
import type {
StorageProgramData,
StorageProgramListItem,
StorageProgramOperation,
StorageEncoding,
StorageLocation,
StorageACLMode,
StorageGroupPermissions,
StorageProgramACL,
StorageProgramPayload,
StorageProgramTransactionContent,
StorageProgramTransaction,
} from "@kynesyslabs/demosdk/storage"
There is no DemosClient and no demos.storageProgram (singular). The class is Demos and the accessor is demos.storagePrograms (plural). The static helpers on StorageProgram (e.g. deriveStorageAddress, getDataSize) are not exported as standalone functions — call them on the class.
Demos storage accessor
The storagePrograms property on a connected Demos instance exposes two methods:
demos.storagePrograms.sign
Signs a Storage Program payload as a transaction. The returned Transaction is ready for demos.confirm followed by demos.broadcast (or demos.broadcastAndWait).
Signature
storagePrograms.sign(
payload: StorageProgramPayload,
): Promise<Transaction>
Parameters
| Parameter | Type | Description |
|---|
payload | StorageProgramPayload | A payload produced by one of the StorageProgram.* builders. Must include storageAddress. |
Returns
Promise<Transaction> — a fully signed transaction with content.type = "storageProgram" and content.data = ["storageProgram", payload].
Behavior
- Throws
"Wallet not connected" if no keypair is attached to the Demos instance.
- Throws
"Storage address Not found in payload" if payload.storageAddress is missing.
- Sets
tx.content.to to payload.storageAddress before signing.
Example
const payload = StorageProgram.createStorageProgram(
demos.getAddress(),
"appConfig",
{ version: "1.0" },
"json",
{ mode: "public" },
{ nonce: await demos.getAddressNonce(demos.getAddress()) },
)
const tx = await demos.storagePrograms.sign(payload)
const validity = await demos.confirm(tx)
await demos.broadcast(validity)
demos.storagePrograms.read
Fetches a Storage Program by address over HTTP. Issues GET <rpc_url>/storage-program/<address>. If a wallet is connected, the request is annotated with identity and signature headers (ed25519 signature over the connected address) so ACL-protected programs can resolve permissions.
Signature
storagePrograms.read(
address: string,
): Promise<StorageProgramResponse>
Parameters
| Parameter | Type | Description |
|---|
address | string | The storage program address (stor-{40 hex chars}). |
Returns
Promise<StorageProgramResponse> — see StorageProgramResponse. The success field indicates whether the lookup resolved; on failure, error and errorCode are populated.
Example
const result = await demos.storagePrograms.read("stor-7a8b9c...")
if (result.success) {
console.log(result.data) // top-level data field
console.log(result.metadata) // top-level metadata field
console.log(result.sizeBytes) // top-level sizeBytes field
} else {
console.error(result.errorCode, result.error)
}
Reads do not produce a transaction and incur no fee. The response shape is flat — fields like data, metadata, owner, sizeBytes, createdAt, updatedAt live at the top level of the response object, not nested under data.variables / data.metadata.
StorageProgram static helpers
All payload builders and validators are static methods on the StorageProgram class. They are pure (with the exception of the async query helpers documented under RPC Queries) — they build typed payloads, they do not broadcast.
Address derivation
StorageProgram.deriveStorageAddress
Computes the deterministic storage address for a (deployer, programName, nonce, salt) tuple.
Signature
static deriveStorageAddress(
deployerAddress: string,
programName: string,
nonce: number,
salt?: string,
): string
Parameters
| Parameter | Type | Required | Description |
|---|
deployerAddress | string | yes | The address that will own the program. |
programName | string | yes | Human-readable name of the program. |
nonce | number | yes | Sender’s account nonce — guarantees a unique address per transaction even when programName collides. |
salt | string | no | Optional extra entropy. Defaults to "". |
Returns
string — stor- followed by the first 40 characters of sha256("<deployer>:<programName>:<nonce>:<salt>").
Example
const address = StorageProgram.deriveStorageAddress(
"demos1abc...",
"myConfig",
42,
"salt123",
)
// "stor-7a8b9c..." (40 hex chars after the "stor-" prefix)
Program operations
StorageProgram.createStorageProgram
Builds a CREATE_STORAGE_PROGRAM payload. The storage address is derived inside the helper from (deployerAddress, programName, options.nonce, options.salt).
Signature
static createStorageProgram(
deployerAddress: string,
programName: string,
data: Record<string, any> | string,
encoding?: StorageEncoding,
acl?: Partial<StorageProgramACL>,
options?: {
nonce: number
salt?: string
metadata?: Record<string, unknown>
storageLocation?: StorageLocation
},
): StorageProgramPayload
Parameters
| Parameter | Type | Required | Description |
|---|
deployerAddress | string | yes | The creator’s address; becomes the program owner. |
programName | string | yes | Unique-per-deployer-per-nonce program name. |
data | Record<string, any> | string | yes | Initial payload — JSON object for "json" encoding, base64 string for "binary". |
encoding | StorageEncoding | no | "json" (default) or "binary". |
acl | Partial<StorageProgramACL> | no | Access control. Missing fields default to { mode: "owner" }. |
options | { nonce; salt?; metadata?; storageLocation? } | yes | nonce is required. |
Returns
StorageProgramPayload with operation: "CREATE_STORAGE_PROGRAM" and storageLocation defaulting to "onchain" when omitted.
Throws
"nonce is required for storage program creation" if options.nonce is undefined.
Example
// JSON storage with public read access
const payload = StorageProgram.createStorageProgram(
"demos1abc...",
"appConfig",
{ version: "1.0", features: ["auth", "storage"] },
"json",
{ mode: "public" },
{ nonce: 42 },
)
// Binary storage with group ACL and metadata
const binary = StorageProgram.createStorageProgram(
"demos1abc...",
"teamDocument",
base64EncodedPdf,
"binary",
{
mode: "restricted",
groups: {
editors: {
members: ["demos1a...", "demos1b..."],
permissions: ["read", "write"],
},
viewers: {
members: ["demos1c..."],
permissions: ["read"],
},
},
},
{
nonce: 43,
metadata: { filename: "report.pdf", mimeType: "application/pdf" },
},
)
StorageProgram.writeStorage
Builds a WRITE_STORAGE payload that replaces the entire data field of an existing program. For surgical updates, use the granular helpers below.
Signature
static writeStorage(
storageAddress: string,
data: Record<string, any> | string,
encoding?: StorageEncoding,
): StorageProgramPayload
Parameters
| Parameter | Type | Required | Description |
|---|
storageAddress | string | yes | The stor-{hash} address. |
data | Record<string, any> | string | yes | New payload (must match the program’s encoding). |
encoding | StorageEncoding | no | "json" (default) or "binary". |
Returns
StorageProgramPayload with operation: "WRITE_STORAGE".
Example
const payload = StorageProgram.writeStorage(
"stor-7a8b9c...",
{ newKey: "value", existingKey: "updatedValue" },
"json",
)
StorageProgram.readStorage
Builds a READ_STORAGE payload for transaction validation only. This does not perform an actual read — use demos.storagePrograms.read (or one of the RPC query helpers) to fetch program data.
Signature
static readStorage(storageAddress: string): StorageProgramPayload
Returns
StorageProgramPayload with operation: "READ_STORAGE" — useful when you need to construct a payload object for validation pipelines or type-guards.
Example
// Validation only — does NOT fetch data
const payload = StorageProgram.readStorage("stor-7a8b9c...")
// Real read:
const data = await demos.storagePrograms.read("stor-7a8b9c...")
StorageProgram.updateAccessControl
Builds an UPDATE_ACCESS_CONTROL payload. Owner-only at execution time.
Signature
static updateAccessControl(
storageAddress: string,
acl: Partial<StorageProgramACL>,
): StorageProgramPayload
Parameters
| Parameter | Type | Required | Description |
|---|
storageAddress | string | yes | The program address. |
acl | Partial<StorageProgramACL> | yes | New ACL configuration. The full ACL is replaced — to keep existing fields, include them. |
Returns
StorageProgramPayload with operation: "UPDATE_ACCESS_CONTROL".
Example
// Make the program public
StorageProgram.updateAccessControl("stor-7a8b9c...", { mode: "public" })
// Block specific addresses while staying public
StorageProgram.updateAccessControl("stor-7a8b9c...", {
mode: "public",
blacklisted: ["demos1bad...", "demos1spam..."],
})
// Group-based access
StorageProgram.updateAccessControl("stor-7a8b9c...", {
mode: "restricted",
groups: {
admins: { members: ["demos1admin..."], permissions: ["read", "write", "delete"] },
users: { members: ["demos1u1...", "demos1u2..."], permissions: ["read"] },
},
})
StorageProgram.deleteStorageProgram
Builds a DELETE_STORAGE_PROGRAM payload. Irreversible at execution time; the program and all stored data are removed.
Signature
static deleteStorageProgram(storageAddress: string): StorageProgramPayload
Returns
StorageProgramPayload with operation: "DELETE_STORAGE_PROGRAM".
Example
const payload = StorageProgram.deleteStorageProgram("stor-7a8b9c...")
Granular field operations
These helpers build payloads for in-place edits to JSON-encoded programs. Each is rejected by the chain when applied to a binary-encoded program.
StorageProgram.setField
Sets a single top-level field to a value.
Signature
static setField(
storageAddress: string,
field: string,
value: unknown,
): StorageProgramPayload
Returns
StorageProgramPayload with operation: "SET_FIELD", plus field and value.
Example
StorageProgram.setField("stor-7a8b9c...", "theme", "dark")
StorageProgram.setItem
Replaces an item at index inside an array field. Negative indexing is supported (-1 = last element).
Signature
static setItem(
storageAddress: string,
field: string,
index: number,
value: unknown,
): StorageProgramPayload
Returns
StorageProgramPayload with operation: "SET_ITEM", plus field, index, and value.
Example
// Replace the first user
StorageProgram.setItem("stor-7a8b9c...", "users", 0, { name: "Updated", role: "admin" })
// Replace the last user
StorageProgram.setItem("stor-7a8b9c...", "users", -1, { name: "Last", role: "user" })
StorageProgram.appendItem
Appends an item to an array field.
Signature
static appendItem(
storageAddress: string,
field: string,
value: unknown,
): StorageProgramPayload
Returns
StorageProgramPayload with operation: "APPEND_ITEM", plus field and value.
Example
StorageProgram.appendItem("stor-7a8b9c...", "users", { name: "New", role: "viewer" })
StorageProgram.deleteField
Removes a top-level field from the stored object entirely.
Signature
static deleteField(
storageAddress: string,
field: string,
): StorageProgramPayload
Returns
StorageProgramPayload with operation: "DELETE_FIELD", plus field.
Example
StorageProgram.deleteField("stor-7a8b9c...", "legacyConfig")
StorageProgram.deleteItem
Removes the item at index from an array field. Negative indexing is supported. The array is compacted after removal.
Signature
static deleteItem(
storageAddress: string,
field: string,
index: number,
): StorageProgramPayload
Returns
StorageProgramPayload with operation: "DELETE_ITEM", plus field and index.
Example
// Remove the first item
StorageProgram.deleteItem("stor-7a8b9c...", "users", 0)
// Remove the last item
StorageProgram.deleteItem("stor-7a8b9c...", "users", -1)
Validators
StorageProgram.validateSize
Returns true when the data fits within STORAGE_PROGRAM_CONSTANTS.MAX_SIZE_BYTES (1 MB).
Signature
static validateSize(
data: Record<string, any> | string,
encoding?: StorageEncoding,
): boolean
Example
if (!StorageProgram.validateSize(data, "json")) {
throw new Error("Payload exceeds 1 MB")
}
StorageProgram.getDataSize
Returns the size of the data in bytes. For "binary" encoding, the input is treated as base64 and the decoded byte length is returned. For "json" encoding, JSON.stringify is run and the UTF-8 byte length of the result is returned.
Signature
static getDataSize(
data: Record<string, any> | string,
encoding?: StorageEncoding,
): number
Example
const size = StorageProgram.getDataSize({ key: "value" }, "json")
console.log(`Data size: ${size} bytes`)
StorageProgram.validateNestingDepth
Returns true when the JSON object’s nesting depth is <= maxDepth. JSON-only.
Signature
static validateNestingDepth(
data: any,
maxDepth?: number,
): boolean
| Parameter | Type | Default | Description |
|---|
data | any | — | Object to inspect. |
maxDepth | number | STORAGE_PROGRAM_CONSTANTS.MAX_JSON_NESTING_DEPTH (64) | Maximum allowed nesting depth. |
Example
if (!StorageProgram.validateNestingDepth(data)) {
throw new Error("JSON nesting too deep (>64 levels)")
}
StorageProgram.calculateStorageFee
Computes the storage fee for a payload. Pricing is 1 DEM per 10 KB chunk, minimum 1 DEM, returned as a bigint in OS (1 DEM = 10⁹ OS).
Signature
static calculateStorageFee(
data: Record<string, any> | string,
encoding?: StorageEncoding,
): bigint
Returns
bigint — fee in OS. Use denomination.osToDem (from @kynesyslabs/demosdk) to convert to whole DEM for display.
Example
import { denomination } from "@kynesyslabs/demosdk"
const feeOs = StorageProgram.calculateStorageFee({ key: "value" }, "json")
console.log(`Storage fee: ${denomination.osToDem(feeOs)} DEM`)
// Reference points:
// 5 KB -> 1 DEM
// 15 KB -> 2 DEM
// 100 KB -> 10 DEM
// 1 MB -> 103 DEM
Type reference
StorageProgramPayload
The single payload shape produced by every StorageProgram.* builder. Different operations populate different subsets of fields.
interface StorageProgramPayload {
operation: StorageProgramOperation
storageAddress: string
/** Required for CREATE_STORAGE_PROGRAM */
programName?: string
/** "json" (default) or "binary" */
encoding?: StorageEncoding
/**
* For json encoding: Record<string, any> (max 64 nesting levels)
* For binary encoding: base64 encoded string
* Max size: 1 MB.
*/
data?: Record<string, any> | string
/** Robust ACL: owner / public / restricted, plus allowed/blacklisted/groups */
acl?: StorageProgramACL
/** @deprecated Use `acl` instead. */
accessControl?: StorageProgramAccessControl
/** @deprecated Use `acl.allowed` instead. */
allowedAddresses?: string[]
/** Optional salt mixed into address derivation */
salt?: string
/** Optional free-form metadata (filename, mimeType, description, ...) */
metadata?: Record<string, unknown>
/** "onchain" (default). "ipfs" is reserved and currently falls back to onchain. */
storageLocation?: StorageLocation
// Granular operation fields (JSON encoding only)
/** Required for SET_FIELD / SET_ITEM / APPEND_ITEM / DELETE_FIELD / DELETE_ITEM */
field?: string
/** Required for SET_ITEM and DELETE_ITEM */
index?: number
/** Required for SET_FIELD / SET_ITEM / APPEND_ITEM */
value?: unknown
}
StorageProgramACL
interface StorageProgramACL {
/** Default behavior when no other rule matches */
mode: StorageACLMode
/** Addresses explicitly allowed to read/write */
allowed?: string[]
/** Addresses explicitly blocked (wins over allowed/groups) */
blacklisted?: string[]
/** Named groups with members and per-group permissions */
groups?: Record<string, StorageGroupPermissions>
}
ACL resolution order (highest priority first):
- Owner — full access, always.
- Blacklisted — denied, regardless of allowed/groups.
allowed — full permissions granted.
groups — per-group permission check.
mode fallback — "owner" and "restricted" deny; "public" allows read.
StorageGroupPermissions
interface StorageGroupPermissions {
members: string[]
permissions: ("read" | "write" | "delete")[]
}
StorageACLMode
type StorageACLMode = "owner" | "public" | "restricted"
| Mode | Default behavior (no other rule matched) |
|---|
"owner" | Owner only. Most restrictive. |
"public" | Anyone reads, only the owner writes. |
"restricted" | Only addresses in allowed / groups may access. |
StorageEncoding
type StorageEncoding = "json" | "binary"
"json" — data is Record<string, any>.
"binary" — data is a base64 string.
StorageLocation
type StorageLocation = "onchain" | "ipfs"
"ipfs" is reserved for future hybrid storage and is not yet implemented — payloads with storageLocation: "ipfs" currently fall back to "onchain".
StorageProgramOperation
type StorageProgramOperation =
| "CREATE_STORAGE_PROGRAM"
| "WRITE_STORAGE"
| "READ_STORAGE"
| "UPDATE_ACCESS_CONTROL"
| "DELETE_STORAGE_PROGRAM"
| "SET_FIELD"
| "SET_ITEM"
| "APPEND_ITEM"
| "DELETE_FIELD"
| "DELETE_ITEM"
READ_STORAGE is a query operation — it is not submitted as a transaction in normal usage.
StorageProgramTransactionContent
type StorageProgramTransactionContent = Omit<TransactionContent, "type" | "data"> & {
type: "storageProgram"
data: ["storageProgram", StorageProgramPayload]
}
StorageProgramTransaction
interface StorageProgramTransaction extends Omit<Transaction, "content"> {
content: StorageProgramTransactionContent
}
StorageProgramData
Returned by node-level RPC queries (e.g. StorageProgram.getByAddress).
interface StorageProgramData {
storageAddress: string
owner: string
programName: string
encoding: "json" | "binary"
data?: Record<string, unknown> | string | null
metadata?: Record<string, unknown> | null
storageLocation: string
sizeBytes: number
createdAt: string
updatedAt: string
/** Transaction hash that created this storage program */
createdByTx?: string
/** Transaction hash of the last modification */
lastModifiedByTx?: string
/** All transaction hashes that touched this program */
interactionTxs?: string[]
}
StorageProgramListItem
interface StorageProgramListItem {
storageAddress: string
programName: string
encoding: "json" | "binary"
sizeBytes: number
storageLocation: string
createdAt: string
updatedAt: string
}
StorageProgramResponse
The response shape returned by demos.storagePrograms.read. All payload fields are top-level — there is no nested data.variables / data.metadata envelope.
interface StorageProgramResponse {
success: boolean
storageAddress?: string
owner?: string
programName?: string
encoding?: "json" | "binary"
data?: Record<string, unknown> | string | null
metadata?: Record<string, unknown> | null
storageLocation?: string
sizeBytes?: number
createdAt?: string
updatedAt?: string
error?: string
errorCode?: string
}
StorageFieldType
Returned by the getFieldType RPC helper.
type StorageFieldType =
| "string"
| "number"
| "boolean"
| "array"
| "object"
| "null"
| "undefined"
StorageProgramFieldsResponse
interface StorageProgramFieldsResponse {
fields: string[]
count: number
}
Constants
STORAGE_PROGRAM_CONSTANTS
import { STORAGE_PROGRAM_CONSTANTS } from "@kynesyslabs/demosdk/storage"
| Constant | Value | Meaning |
|---|
MAX_SIZE_BYTES | 1_048_576 (1 MB) | Hard cap on payload size for both JSON and binary encodings. |
PRICING_CHUNK_BYTES | 10_240 (10 KB) | Granularity for fee calculation. |
FEE_PER_CHUNK | OS_PER_DEM (10⁹ OS = 1 DEM) | Fee per chunk, expressed as a bigint in OS. |
MAX_JSON_NESTING_DEPTH | 64 | Maximum nesting depth for JSON encoding. |
The object is declared as const, so its fields are read-only.
Type guards
Runtime narrowing helpers exported from @kynesyslabs/demosdk/storage.
isStorageProgramPayload
function isStorageProgramPayload(payload: unknown): payload is StorageProgramPayload
Returns true when payload is a non-null object with a string storageAddress and a string operation matching one of the values in StorageProgramOperation.
isValidEncoding
function isValidEncoding(encoding: unknown): encoding is StorageEncoding
Returns true only for "json" or "binary".
isValidStorageLocation
function isValidStorageLocation(location: unknown): location is StorageLocation
Returns true only for "onchain" or "ipfs".
Deprecated members
These exist for backward compatibility with pre-v3 storage programs and should not be used in new code.
| Deprecated | Replacement |
|---|
StorageProgram.createStorageProgramLegacy(...) | StorageProgram.createStorageProgram(..., acl, options) |
StorageProgram.updateAccessControlLegacy(...) | StorageProgram.updateAccessControl(address, acl) |
StorageProgramAccessControl ("private" | "public" | "restricted" | "deployer-only") | StorageACLMode ("owner" | "public" | "restricted") inside StorageProgramACL |
StorageProgramPayload.accessControl | StorageProgramPayload.acl.mode |
StorageProgramPayload.allowedAddresses | StorageProgramPayload.acl.allowed |
The legacy "private" and "deployer-only" modes both map to the modern mode: "owner".
Cross-references
- Getting Started — end-to-end walkthrough (connect, build, sign, broadcast, read).
- Operations — when to use create vs. write vs. granular ops, and the full lifecycle.
- Access Control —
owner / public / restricted, allowlists, blacklists, groups, and resolution order.
- RPC Queries — node-level queries such as
StorageProgram.getByAddress, getByOwner, searchByName, getFields, getValue, getItem, hasField, getFieldType, getAll.
- Cookbook: Storage Programs — recipes for public profiles, team workspaces, and binary attachments.