Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Execution Layer

Every on-chain write goes through the execution layer, a single interface, ExecutionLayer, with interchangeable implementations. The same application code works whether the user pays gas (wallet) or a relayer fronts it (API).

const r = await client.execution.writeData({ cloudId, dataId, payload });
await r.wait();   // identical on both layers

The two built-in layers

LayerWho signs / pays gasContract function called
Wallet (WalletExecution)the wallet (viem), user pays ETHdirect DataPrismCloud call
API (ApiExecution)EIP-712 request → dataprism-api; relayer pays (reimbursed in DPC)*For meta-tx via the Forwarder

The client picks one from options.mode ("wallet" default | "api"), or you inject a custom layer via options.execution.

import { walletExecution, apiExecution } from "@dataprism/sdk";
 
const wallet = walletExecution(network, walletClient);
const api    = apiExecution(network, walletClient, apiUrl);

The ExecutionLayer interface

interface ExecutionLayer {
  readonly mode: ExecutionMode;       // "wallet" | "api" | (custom string)
  readonly account: Address;          // signer address
 
  createPrism(input): Promise<CreatePrismExecutionResult>;
  createPrismWithData(input): Promise<CreatePrismExecutionResult>;
  transferPrism(input): Promise<ExecutionResult>;
  acceptTransfer(input): Promise<ExecutionResult>;
  deletePrism(input): Promise<ExecutionResult>;
 
  writeData(input): Promise<ExecutionResult>;
  modifyData(input): Promise<ExecutionResult>;
  deleteData(input): Promise<ExecutionResult>;
 
  batchWriteData(input): Promise<ExecutionResult>;
  batchModifyData(input): Promise<ExecutionResult>;
  batchDeleteData(input): Promise<ExecutionResult>;
}

Inputs

Every input extends MetaOptions = { relayerFee?: bigint; timestamp?: bigint }. These are ignored in wallet mode; in API mode they default to timestamp = now, relayerFee = 0n.

InputOwn fields
CreatePrismInputfundAmount: bigint, salt: Bytes32, relayerFundAmount?: bigint
CreatePrismWithDataInputfundAmount: bigint, salt: Bytes32, dataIds: Bytes32[], payloads: Payload[], relayerFundAmount?: bigint
TransferPrismInputcloudId, newOwner
AcceptTransferInput / DeletePrismInputcloudId
WriteDataInput / ModifyDataInputcloudId, dataId, payload
DeleteDataInputcloudId, dataId
BatchWriteDataInput / BatchModifyDataInputcloudId, dataIds[], payloads[]
BatchDeleteDataInputcloudId, dataIds[]

ExecutionResult & .wait()

interface ExecutionResult {
  mode: ExecutionMode;
  txHash?: Hex;        // wallet: available immediately
  requestId?: string;  // api: available immediately
  wait(options?: { intervalMs?; timeoutMs? }): Promise<ExecutionReceipt>;
}
  • Wallet: txHash is available right away; wait() awaits the transaction receipt.
  • API: requestId is available right away; wait() polls dataprism-api until the relayer succeeds, then returns the txHash. Only the API layer honors intervalMs.
  • createPrism returns a CreatePrismExecutionResult with a cloudId, immediate in wallet mode, available after wait() in API mode (computed locally via computeCloudId). Its wait() resolves to CreatePrismReceipt { txHash, cloudId }.
const created = await client.execution.createPrism({ fundAmount: 10n, salt });
const { cloudId } = await created.wait();
  • createPrismWithData creates a prism and writes an initial batch of entries in a single atomic transaction (one signature, one nonce increment), equivalent to createPrism then batchWriteData. It also returns a CreatePrismExecutionResult; its wait() resolves { cloudId, txHash }. Here fundAmount must cover the creation fee plus the per-entry data fees. This is what backs PrismManager.create(), which provisions the prism and writes the standard slots (key, encryption, metadata, providers) in one transaction, plus any extra initialData entries.
const created = await client.execution.createPrismWithData({
  fundAmount: 20n,
  salt,
  dataIds,
  payloads,
});
const { cloudId, txHash } = await created.wait();

When fundAmount > 0 the SDK signs an EIP-2612 permit on the DPC token and calls createPrismWithPermit (wallet), funding and creation in a single transaction, with no separate approve. In API mode the permit is sent as a PermitParams for the Forwarder's createPrismFor. See single-transaction funding.

Fees

FeeUnitPaid byWhen
GasETHthe userwallet mode
relayerFeeDPCprism balance → relayerapi mode
Protocol feeDPCprism balanceevery write / modify / delete

⚠️ In API mode the relayer fronts the gas, so you must set a relayerFee (in DPC) high enough to compensate it. In wallet mode the user pays gas in ETH and relayerFee is ignored.

Custom layers

ExecutionLayer is the call standard. For a custom path (a different relayer, a multisig, specific constraints…), extend the abstract BaseExecutionLayer: it already implements all nine write methods by normalizing each into an ExecutionOperation (a discriminated union) and delegating to a single method you provide.

class MyExecution extends BaseExecutionLayer {
  readonly mode = "my-custom-mode";        // ExecutionMode accepts any string
  get account() { return this.signerAddress; }
  async createPrism(input) { /* … */ }
  protected async execute(op /* ExecutionOperation */) {
    switch (op.kind) {
      case "writeData":      /* … */ break;
      case "batchWriteData": /* … */ break;
      // transferPrism, acceptTransfer, deletePrism, modifyData, deleteData, batch*
    }
  }
}
 
const client = new DataPrismClient(network, { execution: new MyExecution() });

The ExecutionOperation kinds are: transferPrism, acceptTransfer, deletePrism, writeData, modifyData, deleteData, batchWriteData, batchModifyData, batchDeleteData (createPrism is implemented directly, not via execute).

Signing (EIP-712)

The API path signs meta-transactions with DataPrismSigner (exposed as client.signer). Domain: { name: "DataPrismCloud", version: "1", chainId, verifyingContract: <DataPrismCloud> }. The nonce is read on-chain (nonces(account)) on every signature.

MethodType signed
signCreatePrismCreatePrism
signWriteData / signModifyData / signDeleteDataWriteData / ModifyData / DeleteData
signTransferPrism / signAcceptTransfer / signDeletePrismTransferPrism / AcceptTransfer / DeletePrism
signBatchWriteData / signBatchModifyData / signBatchDeleteDatabatch variants (pre-hashed)

All return EIP712Signature = { v, r, s }. Batch types sign over pre-computed dataIdsHash/payloadsHash; the SDK computes these for you via the exported helpers:

hashBytes32Array(values: Bytes32[]): Bytes32   // keccak256(concat(values))
hashPayloadsArray(payloads: Payload[]): Bytes32 // keccak256(concat(payloads.map(keccak256)))

Low-level writers (advanced)

The building blocks under the execution layer, exposed for advanced use and reachable via .raw on each layer:

  • NetworkWriter (wallet), each write is simulateContract (dry-run) then writeContract. Beyond the interface it also offers wallet-only extras: fundPrism, addWriter, updateWriterExpiration, removeWriter, claimVouchers. Helper: createNetworkWriterFromPrivateKey(network, privateKey).
  • ApiWriter (api), signs EIP-712 and POSTs to /requests, returning an ApiRequest (requestId, status). Also: getRequest, listRequests, waitForRequest, batchMultiUserWriteData(items), and extractCloudId(req). Statuses: pending → processing → success | failed.
Copyright © 2026 DataPrism.