> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumera.io/llms.txt
> Use this file to discover all available pages before exploring further.

# The Action precompile

> Request and query Cascade and Sense actions from Solidity contracts.

The Action precompile exposes the `x/action` module to the EVM at a single fixed address. Solidity contracts can request, finalize, approve, and query Cascade storage and Sense analysis actions without leaving the EVM execution context.

The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet runs `v1.12.0` and gains the EVM with its upgrade.

## Address

```text theme={null}
0x0000000000000000000000000000000000000901
```

Lumera custom precompiles start at `0x0900`. Everything below that range belongs to the Ethereum and Cosmos EVM standard sets. See the [precompiles overview](/smart-contracts/precompiles/overview) for the full address map.

## Design

The precompile mixes typed and generic methods. Typed methods carry action-specific metadata, which gives Solidity compile-time safety for fields that differ between Cascade and Sense. Generic methods share one signature regardless of action type.

| Category        | Methods                                                                                                                        |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Typed (Cascade) | `requestCascade`, `finalizeCascade`                                                                                            |
| Typed (Sense)   | `requestSense`, `finalizeSense`                                                                                                |
| Generic         | `approveAction`, `getAction`, `getActionFee`, `getParams`, `getActionsByState`, `getActionsByCreator`, `getActionsBySuperNode` |

## Action lifecycle

```text theme={null}
Request (Pending) -> Processing -> Finalize (Done) -> Approve (Approved)
                                                   -> Rejected / Failed / Expired
```

The `state` field in query results uses these values.

| State      | Value | Description                                         |
| ---------- | ----- | --------------------------------------------------- |
| Pending    | 1     | Newly created, awaiting SuperNode processing        |
| Processing | 2     | SuperNodes are working on the action                |
| Done       | 3     | Finalized by a SuperNode, awaiting creator approval |
| Approved   | 4     | Creator approved the result                         |
| Rejected   | 5     | Creator rejected the result                         |
| Failed     | 6     | Processing failed                                   |
| Expired    | 7     | Exceeded the expiration time                        |

The `actionType` field uses `1` for Sense and `2` for Cascade.

## Solidity interface

The interface below comes from [`IAction.sol`](https://github.com/LumeraProtocol/lumera/blob/master/precompiles/solidity/contracts/interfaces/IAction.sol) in the lumera repo.

```solidity IAction.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @notice Precompile at 0x0000000000000000000000000000000000000901
interface IAction {
    /// @notice LEP 5 availability commitment for Cascade storage verification.
    struct AvailabilityCommitment {
        string commitmentType;     // e.g. "merkle_blake3"
        uint8 hashAlgo;            // 0 = unspecified, 1 = BLAKE3, 2 = SHA256
        uint32 chunkSize;          // bytes per chunk
        uint64 totalSize;          // total file size in bytes
        uint32 numChunks;          // number of chunks
        bytes root;                // Merkle root hash
        uint32[] challengeIndices; // chunk indices the SuperNode must prove
    }

    /// @notice LEP 5 Merkle inclusion proof for a single challenged chunk.
    struct ChunkProof {
        uint32 chunkIndex;         // which chunk this proves
        bytes leafHash;            // hash of the chunk data
        bytes[] pathHashes;        // sibling hashes along the Merkle path
        bool[] pathDirections;     // true = right sibling, false = left
    }

    /// @notice Represents a single action on the Lumera chain.
    struct ActionInfo {
        string actionId;
        address creator;
        uint8 actionType;          // 1 = Sense, 2 = Cascade
        uint8 state;               // see the state table above
        string metadata;           // JSON metadata (type-specific)
        uint256 price;             // fee paid in ulume
        int64 expirationTime;
        int64 blockHeight;
        address[] superNodes;
    }

    event ActionRequested(
        string indexed actionId,
        address indexed creator,
        uint8 actionType,
        uint256 price
    );

    event ActionApproved(
        string indexed actionId,
        address indexed creator
    );

    /// @notice Request a Cascade storage action.
    function requestCascade(
        string calldata dataHash,
        string calldata fileName,
        uint64 rqIdsIc,
        string calldata signatures,
        uint256 price,
        int64 expirationTime,
        uint64 fileSizeKbs,
        AvailabilityCommitment calldata commitment  // pass an empty root to skip
    ) external returns (string memory actionId);

    /// @notice Request a Sense analysis action.
    function requestSense(
        string calldata dataHash,
        uint64 ddAndFingerprintsIc,
        uint256 price,
        int64 expirationTime,
        uint64 fileSizeKbs
    ) external returns (string memory actionId);

    /// @notice Finalize a Cascade action with storage proofs.
    function finalizeCascade(
        string calldata actionId,
        string[] calldata rqIdsIds,
        ChunkProof[] calldata chunkProofs  // pass empty for pre-LEP5
    ) external returns (bool success);

    /// @notice Finalize a Sense analysis action with results.
    function finalizeSense(
        string calldata actionId,
        string[] calldata ddAndFingerprintsIds,
        string calldata signatures
    ) external returns (bool success);

    /// @notice Approve a finalized action (creator only).
    function approveAction(
        string calldata actionId
    ) external returns (bool success);

    /// @notice Get details of a specific action by ID.
    function getAction(
        string calldata actionId
    ) external view returns (ActionInfo memory action);

    /// @notice Calculate the fee for an action of the given data size.
    function getActionFee(
        uint64 dataSizeKbs
    ) external view returns (uint256 baseFee, uint256 perKbFee, uint256 totalFee);

    /// @notice List actions created by a specific address.
    function getActionsByCreator(
        address creator,
        uint64 offset,
        uint64 limit
    ) external view returns (ActionInfo[] memory actions, uint64 total);

    /// @notice List actions filtered by state.
    function getActionsByState(
        uint8 state,
        uint64 offset,
        uint64 limit
    ) external view returns (ActionInfo[] memory actions, uint64 total);

    /// @notice List actions assigned to a specific SuperNode.
    function getActionsBySuperNode(
        address superNode,
        uint64 offset,
        uint64 limit
    ) external view returns (ActionInfo[] memory actions, uint64 total);

    /// @notice Get the action module parameters.
    function getParams() external view returns (
        uint256 baseActionFee,
        uint256 feePerKbyte,
        uint64 maxActionsPerBlock,
        uint64 minSuperNodes,
        int64 expirationDuration,
        string memory superNodeFeeShare,
        string memory foundationFeeShare,
        uint32 svcChallengeCount,
        uint32 svcMinChunksForChallenge
    );
}
```

## Request a Cascade action from a contract

A minimal storage client queries the fee, requests the action, and tracks the returned `actionId`.

```solidity CascadeStorageClient.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./IAction.sol";

contract CascadeStorageClient {
    IAction constant ACTION = IAction(0x0000000000000000000000000000000000000901);

    mapping(string => string) public uploads;

    event UploadRequested(string indexed dataHash, string actionId, uint256 totalFee);

    function uploadFile(
        string calldata dataHash,
        string calldata fileName,
        uint64 rqIdsIc,
        string calldata signatures,
        uint64 fileSizeKbs
    ) external {
        // 1. Query the fee to determine the price
        (, , uint256 totalFee) = ACTION.getActionFee(fileSizeKbs);

        // 2. Set expiration to 1 hour from now
        int64 expiration = int64(int256(block.timestamp)) + 3600;

        // 3. Request the Cascade action. An empty commitment skips LEP 5.
        IAction.AvailabilityCommitment memory commitment;
        string memory actionId = ACTION.requestCascade(
            dataHash,
            fileName,
            rqIdsIc,
            signatures,
            totalFee,
            expiration,
            fileSizeKbs,
            commitment
        );

        uploads[dataHash] = actionId;
        emit UploadRequested(dataHash, actionId, totalFee);
    }

    function checkUploadState(string calldata dataHash) external view returns (uint8 state) {
        IAction.ActionInfo memory info = ACTION.getAction(uploads[dataHash]);
        return info.state;
    }

    function approveUpload(string calldata dataHash) external {
        ACTION.approveAction(uploads[dataHash]);
    }
}
```

Requesting the action registers it on chain. The file itself still travels to SuperNodes off chain, the same way as in the normal [upload lifecycle](/cascade/concepts/upload-lifecycle). `finalizeCascade` and `finalizeSense` are called by SuperNodes when processing completes, not by your app.

## Fees

`getActionFee` returns the base fee, the per-kilobyte rate, and the total, all in `ulume`. The current parameters are 10000 ulume base plus 10 ulume per KB. Governance can change them, so query the fee on chain instead of hardcoding values.

## Call it from JavaScript

Read methods work through `eth_call` with no deployed contract.

```typescript query-action.ts theme={null}
import { ethers } from "ethers";

const ACTION_ADDRESS = "0x0000000000000000000000000000000000000901";

const ACTION_ABI = [
  "function getActionFee(uint64 dataSizeKbs) view returns (uint256 baseFee, uint256 perKbFee, uint256 totalFee)",
  "function getAction(string actionId) view returns (tuple(string actionId, address creator, uint8 actionType, uint8 state, string metadata, uint256 price, int64 expirationTime, int64 blockHeight, address[] superNodes))",
  "event ActionRequested(string indexed actionId, address indexed creator, uint8 actionType, uint256 price)",
];

// Point at your node's EVM JSON-RPC endpoint (port 8545)
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const action = new ethers.Contract(ACTION_ADDRESS, ACTION_ABI, provider);

// Query the fee for 100 KB
const [baseFee, perKbFee, totalFee] = await action.getActionFee(100n);
console.log(`Total fee for 100 KB: ${totalFee} ulume`);

// Watch for new actions
action.on("ActionRequested", (actionId, creator, actionType, price) => {
  console.log(`New action ${actionId} by ${creator}, type=${actionType}, price=${price}`);
});
```

Transaction methods work the same way with a signer attached.

## Behavior notes

* **Address translation.** The precompile converts your EVM `0x...` caller address to `lumera1...` before it reaches the message server. Addresses in returned `ActionInfo` structs come back converted to `0x...` form.
* **Metadata bridging.** Typed Solidity arguments become a JSON metadata string inside the precompile, then flow through `MsgRequestAction` to the keeper exactly like a Cosmos transaction.
* **Pagination.** List queries cap at 100 results per call. A larger `limit` is silently capped, so page with `offset`.
* **Events.** The precompile emits `ActionRequested`, `ActionFinalized`, and `ActionApproved` as EVM logs, so standard event subscriptions work.
* **Gas.** Calls meter gas from the underlying Cosmos gas usage converted to EVM gas units.

The Go implementation lives in [precompiles/action](https://github.com/LumeraProtocol/lumera/tree/master/precompiles/action), with a full reference in the [action precompile doc](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/precompiles/action-precompile.md).

## Next steps

<CardGroup cols={2}>
  <Card title="How Cascade works" icon="database" href="/cascade/how-cascade-works">
    The full storage flow behind every Cascade action.
  </Card>

  <Card title="SuperNode precompile" icon="server" href="/smart-contracts/precompiles/supernode">
    Read the state of the nodes that process your actions.
  </Card>

  <Card title="Standard precompiles" icon="cubes" href="/smart-contracts/precompiles/standard">
    Staking, governance, and IBC from Solidity.
  </Card>
</CardGroup>
