Workflows
Introduction
A workflow is a directed acyclic graph (DAG) of execution steps that combine Web2 services, blockchain interactions, and AI logic into a single verifiable process. Each step is isolated, cryptographically attested, and connected through explicit input/output bindings.
Structure of a Workflow
A workflow is divided logical checkpoints. This segmentation makes it easier for developers to design, audit, and verify workflows while ensuring all mandatory protocol components are in place.
{
//Basic info for the execution content
"chain_id": <chain id>, // Chain ID of the target chain
"sender": "<address>", // Wallet address initiating execution
"delegate": "<delegate-address>", // Optional delegated account
"attestor": "<attestor-url>", // IPFS or signed attestor config
// Details of the Target contract
"target": {
"contract": "{{ENV.TARGET_CONTRACT}}", // Address of the target contract (passed as env)
"function": "<function signature>", // Function signature to call on the target contract
"authData_result": "${construct-property-analysis-evm.result}", // Referencing the result of the EVM CONSTRUCT STEP
"parameters": [] // If there are any extra param other than the AuthData tuple can be handled here
},
"sponsor_execution_fee": true, // If you want to sponsor the execution fee for the user
"value": "0", // If the target function signature is payable
"intent": {
"id": "{{TRANSACTION_INTENT_ID}}", // Unique identifier for intent
"signature": "{{USER_SIGNATURE}}", // User signature over intent
"deadline": "{{TRANSACTION_INTENT_DEADLINE}}" // Expiry timestamp
},
// RPC and Bundler config
"rpc_url": "${_SECRETS.rpcSepoliaURL}",
"bundler_url": "https://api.pimlico.io/v2/sepolia/rpc?apikey=${_SECRETS.pimlico-apikey}",
"paymaster_url": "https://api.pimlico.io/v2/sepolia/rpc?apikey=${_SECRETS.pimlico-apikey}",
"gas_limit": "100000",
"max_fee_per_gas": "20000000000",
"max_priority_fee_per_gas": "2000000000",
// Workflow steps
"workflow": {
"name": "<workflow-name>",
"version": "1.0.0",
"steps": [
{
"name": "<step-name>",
"type": "<step-type>", // HTTP, EVM_READ, EVM_ENCODER, BUNDLE
"image": "<executor-image>", // Containerized executor image
"attestor": "<attestor-url>", // Attestor configuration
"next": "<next-step>", // Optional link to next step
"config": { }, // Step-specific configuration
"inputs": { }, // Data from user or previous step
"outputs": [ ] // Data exported for next steps
}
]
}
}
A workflow definition contains 4 logical checkpoints:
1️⃣ Basic Info
2️⃣ Target Contract Details
3️⃣ RPC and Bundler Config
4️⃣ Workflow Steps
1. Basic Info
Defines the the context for execution.
{
"chain_id": <chain id>, // Chain ID of the target chain
"sender": "<address>", // Wallet address initiating execution
"delegate": "<delegate-address>", // Optional delegated account
"attestor": "<attestor-url>", // IPFS or signed attestor config
}
Field-by-field explanation:
chain_id
Identifies the blockchain network where the transaction/workflow will execute.
Examples:
1
→ Ethereum mainnet11155111
→ Ethereum Sepolia testnet
Prevents replay attacks across chains (so the intent is valid only on the specified network).
sender
The EOA (Externally Owned Account) or contract account that originates the workflow.
This is the primary identity responsible for initiating execution.
Typically signs the intent to prove authorization.
delegate
(optional)An alternate account that executes on behalf of the
sender
.Useful for:
Delegated accounts (EIP-7702 / Account Abstraction patterns).
Letting a smart contract wallet or sponsor pay fees and submit the transaction.
If unset, the
sender
executes directly.
attestor
Points to the attestation policy that validates this execution.
Could be:
An IPFS hash storing attestor configuration.
A container image reference (
image://…
) that encapsulates attestation logic.
Ensures that every workflow step is verifiable and comes from a trusted source.
2. Target Contract Details
Defines the the context for execution.
{
//basic info above (chain_id, sender, etc.)
"target": {
"contract": "{{ENV.TARGET_CONTRACT}}", // Address of the target contract (passed as env)
"function": "<function signature>", // Function signature to call on the target contract
"authData_result": "${construct-property-analysis-evm.result}",
// Reference to the encoded data produced
// by a previous EVM_CONSTRUCT step
"parameters": [] // Any additional parameters beyond AuthData tuple
},
"sponsor_execution_fee": true, // If true, execution gas is covered by sponsor
"value": "0", // ETH or native token value sent along (if payable)
"intent": {
"id": "{{TRANSACTION_INTENT_ID}}", // Unique identifier for this execution intent
"signature": "{{USER_SIGNATURE}}", // User’s cryptographic signature authorizing execution
"deadline": "{{TRANSACTION_INTENT_DEADLINE}}"
// Expiry timestamp after which intent is invalid
}
}
Field-by-field breakdown
target
contract
The on-chain smart contract address you want to call.
Typically passed as an environment variable so it can change across deployments (testnet vs. mainnet).
function
The ABI function signature (e.g.,
submitPropertyAnalysis((uint256,string))
).Defines exactly what function of the contract will be executed.
authData_result
Refers to output from a previous workflow step — specifically an EVM ENCODER/CONSTRUCT step that builds the calldata (like ABI encoding).
Keeps the pipeline composable: workflow steps generate data, and this field consumes it.
parameters
Holds any extra arguments that the contract function might need, besides the primary
AuthData
.Empty array
[]
if not needed.
sponsor_execution_fee
Boolean flag:
true
→ A sponsor (e.g., paymaster) covers the gas fees for this execution.false
→ The user pays fees directly.
Implements gasless transactions or meta-transactions.
value
The amount of native token (e.g., ETH, MATIC) sent along with the contract call.
0
if the function is not payable.Non-zero when the function requires value transfer.
intent
Encapsulates user authorization for the execution.
id
→ A globally unique identifier for this execution request. Prevents replay and duplication.signature
→ The cryptographic signature from thesender
proving they approved this intent.deadline
→ Expiration timestamp. After this point, the intent can no longer be executed, even if signed
3. RPC and Bundler Config
Defines the the context for execution.
{
//basic info above (chain_id, sender, etc.)
//target contract details
"rpc_url": "${_SECRETS.rpcSepoliaURL}", // RPC endpoint for chain interaction
"bundler_url": "https://api.pimlico.io/v2/sepolia/rpc?apikey=${_SECRETS.pimlico-apikey}",
// Bundler service for Account Abstraction txs
"paymaster_url": "https://api.pimlico.io/v2/sepolia/rpc?apikey=${_SECRETS.pimlico-apikey}",
// Paymaster service (for sponsored gas)
"gas_limit": "100000", // Upper bound on gas consumption for tx
"max_fee_per_gas": "20000000000", // Absolute max gas fee user is willing to pay
"max_priority_fee_per_gas": "2000000000" // Miner/validator tip to prioritize tx
}
Field-by-field explanation
rpc_url
The Ethereum JSON-RPC endpoint used to interact with the target chain (Sepolia in this case).
Provided via a secret (
_SECRETS.rpcSepoliaURL
) for security.Required for submitting transactions and reading chain state.
bundler_url
The ERC-4337 bundler service endpoint.
A bundler collects multiple user operations (UserOps) and submits them as transactions.
This enables Account Abstraction workflows where EOAs don’t directly broadcast transactions.
paymaster_url
Endpoint for a paymaster service, which can sponsor or subsidize gas fees.
Lets dApps or protocols offer gasless user experiences.
Works in conjunction with the bundler.
gas_limit
The maximum amount of gas this transaction is allowed to consume.
Prevents runaway execution and ensures cost predictability.
max_fee_per_gas
Defines the absolute maximum fee per unit of gas the user is willing to pay.
Denominated in wei (here:
20 gwei
).Protects the user from sudden fee spikes.
max_priority_fee_per_gas
The tip paid to validators/miners on top of the base fee.
Incentivizes faster inclusion of the transaction.
Here:
2 gwei
.
2. Workflow Steps (Business Logic)
The core business logic, expressed as a DAG of steps. Each step is executed by an executor and verified by an attestor. We categorize workflow steps into five major types:
2.1. HTTP Request Steps
What it is: Perform Web2 API requests (GET, POST). When to use: Fetch off-chain data (prices, telemetry, compliance feeds) or write to external APIs/databases.
Example – GET:
{
"name": "fetch-telemetry",
"image": "ghcr.io/krnl-labs/executor-http@sha256:7ea7eae002f173aab994444e9e0863f8a8fd8255c7ed3a234218e66c3e1f3c60",
"attestor": "https://public.mypinata.cloud/ipfs/bafybeid3msoebov6o54rtvjtrdwv7fg6tkeye2skrxh6jis25zok6pavzi",
"next": "write-db",
"config": {},
"inputs": {
"url": "https://doc.platform.lat/metacube/telemetry",
"method": "GET"
},
"outputs": [
{
"name": "cpu_usage_percent",
"value": "response.body.cpu_usage_percent",
"export": true
}
]
}
Field-by-field Explanation
name
: Human-readable label (fetch-telemetry
).image
: Executor container image – this one is a specific digest of the HTTP executor.attestor
: Cryptographic attestation source (IPFS file/image link with verification metadata).next
: Defines the step to run after this (write-db
).config
: Reserved for step-specific tuning (empty here).inputs
:url
: Endpoint to fetch telemetry data from.method
:"GET"
since it’s just retrieving.
outputs
:Maps a value from the HTTP response → workflow variable.
Here:
"response.body.cpu_usage_percent"
is extracted and exported ascpu_usage_percent
for downstream steps.
Example – POST:
{
"name": "write-db",
"image": "ghcr.io/krnl-labs/executor-http@sha256:...",
"attestor": "ipfs://.../attestor.json",
"next": "construct-evm",
"config": {},
"inputs": {
"url": "https://doc.platform.lat/zetaabc/telemetry",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"body": {
"cpu": "80",
"mem": "85"
}
},
"outputs": [
{
"name": "status",
"value": "response.body.status",
"export": true
}
]
}
Field-by-field Explanation
name
: Step identifier (write-db
).image
: Again the HTTP executor container (different digest).attestor
: IPFS link to its attestation metadata.next
: Next step isconstruct-evm
(likely assembling a transaction).inputs
:url
: Target API for database write.method
:"POST"
.headers
: Standard JSON content type.body
: JSON payload with telemetry (cpu
,mem
).
outputs
:Extracts
"response.body.status"
and exports asstatus
.
The executor and attestor image should remain constant, rest the config, inputs and outputs can change according to your use case
2.2. EVM Read Steps
What it is: Query smart contract state via read-only calls. When to use: Fetch balances, interest rates, prices, or other on-chain data without modifying state.
Example:
{
"name": "EVM-data-fetcher",
"image": "ghcr.io/krnl-labs/executor-evm-read@sha256:9c15f3e004352f1062a22b8bf7d7fa80498449b9407bc6efc107dbdd7acce5a4",
"attestor": "https://public.mypinata.cloud/ipfs/bafybeid3msoebov6o54rtvjtrdwv7fg6tkeye2skrxh6jis25zok6pavzi",
"next": "construct-evm",
"config": {
"function_signature": "balanceOf(address)",
"input_parameters": [
{
"name": "account",
"type": "address"
}
],
"output_parameters": [
{
"name": "balance",
"type": "uint256"
}
]
},
"inputs": {
"value": {
"account": "0x907089fC3966f52dB446345451Ad9aE3B164D94c"
},
"url": "https://lb.drpc.org/optimism-sepolia/",
"chainid": 11155420,
"contractAddress": "0xB9467B24117FD79D56F396ADC3cCDB695D905ae4"
},
"outputs": [
{
"name": "balance",
"value": "response.0",
"export": true
}
]
}
Field-by-field Explanation
name
Step identifier =
"EVM-data-fetcher"
.Labels this as the on-chain query node.
image
Executor container: EVM Read executor.
Specialized runtime that can encode calls, query RPC endpoints, and decode results.
attestor
IPFS link to the attestation config.
Ensures this step’s execution is cryptographically verifiable.
next
Workflow link → next step is
"construct-evm"
(likely assembling a transaction).
config
(call definition)
function_signature
:"balanceOf(address)"
The ABI function to call on the smart contract.
Standard ERC-20 balance check.
input_parameters
:Defines the inputs schema — here:
account
(type:address
).
output_parameters
:Defines the expected return values — here:
balance
(type:uint256
).
inputs
(runtime values)
value
:Passes the actual arguments for the call.
"account"
=0x9070...D94c
.
url
:RPC endpoint to query (Optimism Sepolia via DRPC load balancer).
chainid
:Numeric chain ID =
11155420
(Optimism Sepolia).
contractAddress
:The contract instance being queried (
0xB9467...5ae4
).
outputs
(exported result)
Defines what values are extracted from the RPC response.
value
:"response.0"
Means: take the first returned value from the call (the
balance
).
name
:"balance"
Store it under this variable name.
export: true
Makes it available to subsequent workflow steps (e.g.,
construct-evm
).
2.3. EVM Construct
Encodes raw workflow data into ABI-compliant calldata. This represents the response expected by your target smart contract.
Takes workflow results as inputs.
Outputs a
bytes
payload ready for target contract consumption.
Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./TargetBase.sol"; // Import base contract that provides AuthData struct + requireAuth modifier
contract TelemetryContract is TargetBase {
// Expected response struct from the KRNL Node
// Fields must be in alphabetic order (to match encoding/decoding consistency)
struct ExpectedResponse {
string cpu_usage_percent; // Telemetry: CPU usage
string mem_usage_percent; // Telemetry: Memory usage
}
// Target Function Signature (called by workflows after KRNL attestation)
function submitTelemetry(AuthData calldata authData)
external
requireAuth(authData) // Ensures KRNL attestation & signature are valid
{
// Decode the KRNL response (AuthData.result is a raw ABI-encoded payload)
ExpectedResponse memory krnlResponse = abi.decode(
authData.result, // Workflow-encoded calldata
(ExpectedResponse) // Decode into ExpectedResponse struct
);
// TODO: Application-specific logic goes here
// e.g. store telemetry, trigger alerts, reward nodes, etc.
....
....
}
}
{
"name": "construct-evm", // Step name: encode telemetry into ABI-compliant calldata
"image": "ghcr.io/krnl-labs/executor-encoder-evm@sha256:...", // Executor image for EVM ABI encoding
"attestor": "https://public.mypinata.cloud/ipfs/.../attestor.json", // Attestor verifying correct encoding
"next": "prepare-authdata", // Next system step in pipeline (adds cryptographic proofs)
"config": {
"parameters": [
{
"name": "telemetry", // Top-level tuple name
"type": "tuple", // Encoded as Solidity struct
"components": [ // Must align with ExpectedResponse struct in Solidity
{
"name": "cpuUsagePercent", // Matches struct field
"type": "string"
},
{
"name": "memUsagePercent", // Matches struct field
"type": "string"
}
]
}
]
},
"inputs": {
"value": {
"telemetry": {
// Internal references pull results from earlier workflow steps
"cpuUsagePercent": "${workflow_step_1.result}",
"memUsagePercent": "${workflow_step_2.result}"
}
}
},
"outputs": [
{
"name": "result", // Encoded ABI payload
"value": "result", // Standard output from encoder
"export": true // Make available to next step (prepare-authdata)
}
]
}
${workflow_step_1.result} and ${workflow_step_2.result} represent internal referencing within the DSL steps, where every step can reference a value from any of its previous steps in the format ${
<name_of_the_step>
.
<name_of_the_output_field>
}
Best Practices
Keep
authData_result
separate from extra parametersProtocol-level info (
nonce
,expiry
,signature
) →authData_result
.dApp-specific parameters →
parameters
array.
Explicit function signature
Include full ABI signature in
target.function
for clarity.Example:
"function": "purchaseTokens((uint256,uint256,bytes32,(bytes32,bytes,bytes)[],bytes,bool,bytes),uint256,address)"
Use environment variables
Sensitive info like contract addresses, RPC URLs →
${ENV.*}
or${_SECRETS.*}
.Example:
"contract": "${ENV.TARGET_CONTRACT}"
Include replay & intent protection
Always provide
intent.id
,intent.signature
,intent.deadline
.Prevents replay attacks across chains or repeated execution.
Explicit data binding
Outputs from previous steps → referenced using
${step-name.output}
.Avoid implicit assumptions; makes workflows auditable.
Validation in DSL
Simple type checks (
string
,uint256
,address
) in the workflow before contract call.Helps catch issues off-chain before execution.
What's next?
KRNL NodeLast updated