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

# Execution and observation

> Submit an agent action, keep retries safe, and read the result.

An action is the only thing in GOL that moves value. Your agent signs it, the API relays it, and the contract checks it against the owner's signed policy before the transfer happens. [Integrate hosted gas](/guides/hosted-gas) is the end-to-end walkthrough; this page is the SDK detail for the action itself.

## Prepare, sign, submit

The API compiles the exact EIP-712 `GasActionAuthorization` payload, including `maxChargeWei`, the most network gas the action may be charged. `signPreparedGasExecution` recomputes the transfer from the prepared fields, refuses a charge above the ceiling you pass, has your agent sign, and returns the submission body. Your agent never needs ETH.

```ts theme={null}
import { GolApiClient } from "@gol/sdk/server";
import { signPreparedGasExecution } from "@gol/sdk";
import type { AgentSigner } from "@gol/sdk";
import { randomBytes } from "node:crypto";
import type { Hex } from "viem";

declare const gol: GolApiClient;
declare const projectId: string;
declare const agentWallet: AgentSigner;
declare const mandateId: string;
declare const gasPolicyId: string;
declare const merchant: `0x${string}`;

// 32 random bytes, generated once per logical action and then reused.
const actionId = `0x${randomBytes(32).toString("hex")}` as Hex;

const input = {
  mandateId,
  gasPolicyId,
  recipient: merchant,
  amountBaseUnits: "250000",
  deadline: Math.floor(Date.now() / 1000) + 600,
};

const prepared = await gol.prepareGasExecution(projectId, actionId, input);
const body = await signPreparedGasExecution(agentWallet, prepared, {
  recipient: merchant,
  amountBaseUnits: 250_000n,
  maxChargeWei: 92_000_000_000_000n, // your ceiling for this action's gas
});
const execution = await gol.submitGasExecution(projectId, actionId, body);
```

The action ID is your idempotency key. It is also sent as the `Idempotency-Key` header, and the SDK validates that it is a 32-byte hex value before making the request, throwing `GolTransportError` otherwise.

## Retries never move value twice

Store `actionId` and the agent signature before you submit, and reuse exactly those values on a retry. This is the rule that makes a timeout safe.

```ts theme={null}
import { GolApiClient } from "@gol/sdk/server";
import type { Hex } from "viem";

declare const gol: GolApiClient;
declare const projectId: string;
declare const actionId: Hex;
declare const input: {
  mandateId: string;
  gasPolicyId: string;
  recipient: `0x${string}`;
  amountBaseUnits: string;
  deadline: number;
};
declare const agentSignature: Hex;
declare const maxChargeWei: string;

// Same ID, same signature, same fields: the original execution comes back.
const retried = await gol.submitGasExecution(projectId, actionId, {
  ...input,
  maxChargeWei,
  agentSignature,
});
```

| Retry                                          | Result                                          |
| ---------------------------------------------- | ----------------------------------------------- |
| Same `actionId`, same signature, same fields   | The original execution. No second transfer      |
| Same `actionId`, different signature or fields | Refused with `conflict`                         |
| New `actionId`, same intended transfer         | A second transfer, if the owner's caps allow it |

Never mint a new `actionId` to work around a timeout. That is how a retry becomes a duplicate payment.

## States

| State                                                    | Meaning                                                                                                     | Terminal |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -------- |
| `accepted`, `signed`, `broadcast`                        | Accepted and being sent by the GOL relayer                                                                  | No       |
| `external_pending`                                       | Waiting for your developer-operated relayer to sign and submit `transaction`                                | No       |
| `external_submitted`                                     | Your relayer's bytes were accepted and are being observed                                                   | No       |
| `indeterminate`                                          | Sent, awaiting chain evidence. Do not resubmit; GOL resolves it                                             | No       |
| `collected`                                              | The owner's account paid the charge, inline in the action transaction or by a later revert claim            | Yes      |
| `not_charged`                                            | Reached the selected head with no chargeable outcome, such as an unconsented outcome or a lifecycle refusal | Yes      |
| `uncollectable`                                          | A charge applied but was not paid, for example because the account was short of ETH                         | Yes      |
| `rejected_preflight`                                     | Refused before sending; no value moved                                                                      | Yes      |
| `finalized`                                              | A reverted transaction under revert consent, awaiting GOL's claim                                           | No       |
| `claim_signed`, `claim_submitted`, `claim_indeterminate` | GOL is claiming a reverted transaction's gas                                                                | No       |
| `disputed`                                               | Under review after a dispute                                                                                | No       |

Treat any state you do not recognize as non-terminal.

`waitForGasExecution` stops at a terminal state by default.

```ts theme={null}
import { waitForGasExecution } from "@gol/sdk/server";
import type { GasExecutionResponse } from "@gol/sdk";

declare const gol: import("@gol/sdk/server").GolApiClient;
declare const projectId: string;
declare const executionId: string;

// Stop as soon as a developer relayer needs to act, or at a terminal state.
const next = await waitForGasExecution(gol, projectId, executionId, {
  until: (execution: GasExecutionResponse) =>
    execution.state === "external_pending" ||
    ["collected", "not_charged", "uncollectable", "rejected_preflight"].includes(
      execution.state,
    ),
  intervalMs: 20_000,
});
```

`GasExecutionResponse` is exported from the browser-safe root entry point, so a server module can import it as a type without touching anything credentialed.

## What the result contains

`getGasExecution` returns separate views rather than one blended number, which is the point: network cost, what the owner owes, what the owner actually paid, and GOL's own cost never collapse into a single figure.

```ts theme={null}
import { GolApiClient } from "@gol/sdk/server";

declare const gol: GolApiClient;
declare const projectId: string;
declare const executionId: string;

const execution = await gol.getGasExecution(projectId, executionId);

execution.payer; // "owner_reimbursement" | "developer" | "developer_relayer"
execution.relayerMode; // "gol" | "developer" | null
execution.state;
execution.receipt?.outcome; // "success" | "refusal" | "revert"
execution.receipt?.totalNetworkWei; // what the network charged
execution.settlement?.chargedWei; // what the owner's account paid
execution.settlement?.paid; // false when the account could not cover the charge
execution.settlement?.recipient; // who was reimbursed
execution.receipt?.golLossWei; // what GOL absorbed
```

| Field                               | What it holds                                                                                                                                                                                                                                             |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `payer`, `relayerMode`, `submitter` | Who fronted the action's gas and who sent it                                                                                                                                                                                                              |
| `quote`                             | The caps the action was accepted under, including `maxChargeWei`, the agent-signed maximum                                                                                                                                                                |
| `settlement`                        | How the gas was paid: `kind` (`inline` or `claim`), `outcome`, metered `gasUnits` and `gasPriceWei`, the declared L1 and operator fees, `chargedWei`, `paid`, `recipient`, and `submitter`                                                                |
| `transaction`                       | For a developer-operated relayer only: the exact `to`, `data`, chain, gas limit, maximum fee, and submitter your relayer signs                                                                                                                            |
| `receipt`                           | The action's transaction and block, its `finalizedBlockNumber`, its `outcome`, and the fee breakdown: `gasUsed`, `effectiveGasPrice`, `executionWei`, `l1DataWei`, `operatorWei`, `totalNetworkWei`, `eligibleOwnerWei`, `golLossWei`, `developerCostWei` |
| `claimReceipt`                      | A later revert claim's transaction, `collectedWei`, `golClaimNetworkCostWei`, and a `status` of `collected` or `reverted`                                                                                                                                 |
| `ledger`                            | Entries of `category`, `amountWei`, and `transactionHash`, one per cost component                                                                                                                                                                         |
| `disputes`                          | Each dispute with its `status`, `reason`, `openedAt`, `resolvedAt`, and `resolution`                                                                                                                                                                      |
| `transactionHashes`, `broadcasts`   | Every hash involved, and each broadcast attempt with its `providerId` and outcome                                                                                                                                                                         |

`receipt`, `settlement`, and `claimReceipt` are `null` until the corresponding transaction is observed, so read them with optional chaining rather than assuming presence. `finalizedBlockNumber` shows how far the background observation has progressed; the hosted Base Sepolia setup settles at the lower `safe` head and continues observing through `finalized`, so a populated receipt is not by itself a finality claim.

A refusal or a revert is a contract outcome, not an API error: the request succeeded and the contract declined the action because it fell outside the owner's rules, usually a cap. No USDC moved. With refusal consent the owner still pays that transaction's gas inline. An API-level refusal with `capability_unavailable` is different: the action was never sent, for example because the policy is paused, revoked, expired, or disputed.

## List actions

`listGasExecutions` pages through a project's history and accepts a `state` filter. Each summary carries the three cost figures already reduced, so a table needs one call.

```ts theme={null}
import { GolApiClient } from "@gol/sdk/server";

declare const gol: GolApiClient;
declare const projectId: string;

const open = await gol.listGasExecutions(projectId, {
  limit: 50,
  state: "external_pending",
});
for (const summary of open.data) {
  console.log(
    summary.id,
    summary.state,
    summary.totalNetworkWei,
    summary.collectedWei,
    summary.golCostWei,
    summary.disputed,
  );
}
```

## Open a dispute

If a charge looks wrong, open a dispute. New submissions under that execution's gas policy pause until GOL resolves it, and a confirmed overcharge is refunded from GOL's funds.

```ts theme={null}
import { GolApiClient } from "@gol/sdk/server";

declare const gol: GolApiClient;
declare const projectId: string;
declare const executionId: string;

const dispute = await gol.openGasExecutionDispute(
  projectId,
  executionId,
  "The collected amount exceeds the gas actually used.",
);
```

## Poll or subscribe

Polling is authoritative. [Webhooks](/sdk/webhooks) tell you when to look, not what is true. Treat a webhook as a hint to fetch, deduplicate by event ID because delivery is at least once and may be out of order, and reconcile against `getGasExecution`.

## Relay it yourself

For a policy the owner approved with a developer-operated relayer, submission returns the execution in `external_pending` with `transaction`. `developerRelayerTransaction` checks that the calldata is the agent's signed action under this execution's mandate, action, and gas policy, and returns the EIP-1559 envelope. Your relayer chooses its nonce and a priority fee no higher than `maxFeePerGas`, signs from the approved submitter, and submits the raw bytes.

```ts theme={null}
import { developerRelayerTransaction } from "@gol/sdk";
import type { GasExecutionResponse } from "@gol/sdk";
import { GolApiClient } from "@gol/sdk/server";
import type { LocalAccount } from "viem";

declare const gol: GolApiClient;
declare const projectId: string;
declare const execution: GasExecutionResponse;
declare const relayer: LocalAccount;
declare const nonce: number;

const tx = developerRelayerTransaction(execution, { submitter: relayer.address });
const raw = await relayer.signTransaction({ ...tx, nonce, maxPriorityFeePerGas: 1_000_000n });
await gol.submitDeveloperRelayerTransaction(projectId, execution.id, raw);
```

GOL broadcasts the bytes and observes the receipt like its own, and you may broadcast them too. The owner reimburses your recipient inline in the action transaction; GOL records no cost for it.

## Reconcile from your own records

The figures are separately observable, so your application can check its own accounting without trusting a summary. The receipt's network cost and the inline charge are different fields, which is exactly the pair to compare.

```ts theme={null}
import { GolApiClient } from "@gol/sdk/server";

declare const gol: GolApiClient;
declare const projectId: string;
declare const executionId: string;

const { receipt, settlement } = await gol.getGasExecution(projectId, executionId);

if (receipt && settlement?.kind === "inline") {
  const networkCostWei = BigInt(receipt.totalNetworkWei);
  const chargedWei = BigInt(settlement.chargedWei);

  if (chargedWei > networkCostWei) {
    // GOL records the difference as a refund obligation and returns it through a dispute.
  }
}
```

Because the caps, the agent's maximum charge, and the outcomes the owner pays for are in the `quote` the action was accepted under, you can also check that a charge stayed inside what the owner and the agent agreed to, rather than only inside the network's own total.
