# 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.

```json
{
  //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&#x20;

2️⃣ Target Contract Details&#x20;

3️⃣ RPC and Bundler Config

4️⃣ Workflow Steps

#### 1. Basic Info&#x20;

Defines the the context for execution.&#x20;

```json
{
  "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 mainnet
    * `11155111` → 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&#x20;

Defines the the context for execution.&#x20;

```json
{
  //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 the `sender` 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.&#x20;

```json
{
  //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:**

```json
{
  "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 as `cpu_usage_percent` for downstream steps.

**Example – POST:**

```json
{
  "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 is `construct-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 as `status`.

{% hint style="warning" %}
The executor and attestor image should remain constant, rest the config, inputs and outputs can change according to your use case
{% endhint %}

***

#### 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:**

```json
{
  "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:**

<pre class="language-javascript"><code class="lang-javascript"><a data-footnote-ref href="#user-content-fn-1">// PART 1: SMART CONTRACT</a>

// 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 &#x26; 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.
        ....
        ....
    }
}

</code></pre>

<pre class="language-json"><code class="lang-json"><a data-footnote-ref href="#user-content-fn-1">// PART 2: KRNL DSL</a>

{
  "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)
    }
  ]
}

</code></pre>

{% hint style="warning" %} <mark style="color:blue;">${workflow\_step\_1.result}</mark> and <mark style="color:blue;">${workflow\_step\_2.result}</mark> represent internal referencing within the DSL steps, where every step can reference a value from any of its previous steps in the format <mark style="color:blue;">`${`</mark><mark style="color:red;">`<name_of_the_step>`</mark>`.`<mark style="color:red;">`<name_of_the_output_field>`</mark><mark style="color:blue;">`}`</mark>
{% endhint %}

### Best Practices

* **Keep `authData_result` separate from extra parameters**
  * Protocol-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:

    ```json
    "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:

    ```json
    "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?

{% content-ref url="/pages/Rb93ZSknjGVjZr4bPDKl" %}
[KRNL Node](/core-concepts/krnl-node.md)
{% endcontent-ref %}

[^1]:


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.krnl.xyz/core-concepts/workflows.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
