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 layersThe two built-in layers
| Layer | Who signs / pays gas | Contract function called |
|---|---|---|
Wallet (WalletExecution) | the wallet (viem), user pays ETH | direct 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.
| Input | Own fields |
|---|---|
CreatePrismInput | fundAmount: bigint, salt: Bytes32, relayerFundAmount?: bigint |
CreatePrismWithDataInput | fundAmount: bigint, salt: Bytes32, dataIds: Bytes32[], payloads: Payload[], relayerFundAmount?: bigint |
TransferPrismInput | cloudId, newOwner |
AcceptTransferInput / DeletePrismInput | cloudId |
WriteDataInput / ModifyDataInput | cloudId, dataId, payload |
DeleteDataInput | cloudId, dataId |
BatchWriteDataInput / BatchModifyDataInput | cloudId, dataIds[], payloads[] |
BatchDeleteDataInput | cloudId, dataIds[] |
ExecutionResult & .wait()
interface ExecutionResult {
mode: ExecutionMode;
txHash?: Hex; // wallet: available immediately
requestId?: string; // api: available immediately
wait(options?: { intervalMs?; timeoutMs? }): Promise<ExecutionReceipt>;
}- Wallet:
txHashis available right away;wait()awaits the transaction receipt. - API:
requestIdis available right away;wait()pollsdataprism-apiuntil the relayer succeeds, then returns thetxHash. Only the API layer honorsintervalMs. createPrismreturns aCreatePrismExecutionResultwith acloudId, immediate in wallet mode, available afterwait()in API mode (computed locally viacomputeCloudId). Itswait()resolves toCreatePrismReceipt { txHash, cloudId }.
const created = await client.execution.createPrism({ fundAmount: 10n, salt });
const { cloudId } = await created.wait();createPrismWithDatacreates a prism and writes an initial batch of entries in a single atomic transaction (one signature, one nonce increment), equivalent tocreatePrismthenbatchWriteData. It also returns aCreatePrismExecutionResult; itswait()resolves{ cloudId, txHash }. HerefundAmountmust cover the creation fee plus the per-entry data fees. This is what backsPrismManager.create(), which provisions the prism and writes the standard slots (key, encryption, metadata, providers) in one transaction, plus any extrainitialDataentries.
const created = await client.execution.createPrismWithData({
fundAmount: 20n,
salt,
dataIds,
payloads,
});
const { cloudId, txHash } = await created.wait();When
fundAmount > 0the SDK signs an EIP-2612permiton the DPC token and callscreatePrismWithPermit(wallet), funding and creation in a single transaction, with no separateapprove. In API mode the permit is sent as aPermitParamsfor the Forwarder'screatePrismFor. See single-transaction funding.
Fees
| Fee | Unit | Paid by | When |
|---|---|---|---|
| Gas | ETH | the user | wallet mode |
relayerFee | DPC | prism balance → relayer | api mode |
| Protocol fee | DPC | prism balance | every 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 andrelayerFeeis 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.
| Method | Type signed |
|---|---|
signCreatePrism | CreatePrism |
signWriteData / signModifyData / signDeleteData | WriteData / ModifyData / DeleteData |
signTransferPrism / signAcceptTransfer / signDeletePrism | TransferPrism / AcceptTransfer / DeletePrism |
signBatchWriteData / signBatchModifyData / signBatchDeleteData | batch 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 issimulateContract(dry-run) thenwriteContract. 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 anApiRequest(requestId,status). Also:getRequest,listRequests,waitForRequest,batchMultiUserWriteData(items), andextractCloudId(req). Statuses:pending → processing → success | failed.

