# 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="krnl-node" %}
[krnl-node](https://docs.krnl.xyz/core-concepts/krnl-node)
{% endcontent-ref %}

[^1]:
