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

# Owner signing

> The two owner paths, the five account dialects, and verifying before an owner signs.

An owner approves a mandate in their own wallet. GOL never receives, stores, or transmits the owner's key, and the SDK has no function that accepts one: every owner helper takes a `signer` you supply, and returns a transaction that anyone may broadcast. Authority is entirely in the owner's signature.

## Two different owner paths

Do not conflate these. They are different operations with different functions and different failure modes.

|              | Approval                                                                      | Account operation                                                                             |
| ------------ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| What it does | Grants or changes a mandate                                                   | Makes the account itself act, such as installing the core                                     |
| Shape        | One `eth_signTypedData_v4`                                                    | A Safe transaction, an EntryPoint v0.7 user operation, or the delegated EOA's own transaction |
| Functions    | `signPreparedGasPolicy`, `signPreparedSafetyAction`, `signPreparedRevocation` | `prepareOwnerOperation` with `signOwnerOperation`                                             |
| Who signs    | The owner, or a Safe's threshold of owners                                    | The account's root owner validator, a Safe's owners, or the EOA's key                         |
| Who pays gas | The sender of the returned call                                               | The account, from its own ETH                                                                 |
| Effect       | The mandate core records the owner's authority                                | The account installs, upgrades, or removes the core                                           |

An approval never fires when the account owner acts, and an account operation never grants a mandate. A mandate only ever moves value when an agent submits an action that the owner's signed policy permits.

## The five dialects

Each supported account checks a different typed-data shape for the same core digest. The SDK builds the right one from the account family the API reports, so you never assemble it yourself.

| Family      | Approval format                                                                                                                      | Signature packed as                                 | Account operations                                            |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------- |
| `safe`      | `SafeMessage(bytes message)` under the Safe's own domain                                                                             | Threshold signatures, preserved byte for byte       | Safe transaction signed as `SafeTx` typed data                |
| `nexus`     | ERC-7739 `PersonalSign(bytes prefixed)` with the K1 validator prefix                                                                 | K1 prefix plus a plain ECDSA signature              | EntryPoint v0.7 user operation, `personal_sign`               |
| `kernel`    | `Kernel(bytes32 hash)` with the root locator                                                                                         | Root locator prefix plus a plain ECDSA signature    | EntryPoint v0.7 user operation, `personal_sign`               |
| `alchemy`   | `ReplaySafeHash(bytes32 hash)` at owner entity zero                                                                                  | Entity-zero prefix plus a plain ECDSA signature     | EntryPoint v0.7 user operation, `personal_sign`               |
| `nexus7702` | The GOL payload itself (`CombinedGasApproval`, `GasRevocation`, or `SafetyAction`) under the core's `GOL Mandate` version `3` domain | A plain ECDSA signature by the EOA's key, unchanged | The EOA sends its own transaction (`kind: "eoa_transaction"`) |

[Supported accounts](/guides/accounts) lists the exact implementation addresses and the GOL installation method for each. The API checks the proxy runtime or EIP-7702 delegation, the implementation, and the installed configuration at one block before every approval and action, so a family name alone is never enough. For a delegated EOA only the EOA's key is owner authority; a validator installed on its delegate never is.

<Note>
  The SDK never signs an approval with `personal_sign` over a raw digest. The only raw hashes it asks a wallet to sign are the EIP-7702 authorization and the Nexus 1.3.3 initialization hash of a delegated EOA's setup, and it checks that each signature recovers to the account. Every owner approval is `eth_signTypedData_v4`, which shows the owner the domain, the type, and every field they are approving instead of an opaque hash.
</Note>

## Sign an approval

The server compiles, the browser verifies, the owner signs, and anyone sends.

```ts theme={null}
import { signPreparedGasPolicy } from "@gol/sdk";
import type { OwnerSigner, PreparedGasPolicy } from "@gol/sdk";
import { createWalletClient, custom } from "viem";
import { baseSepolia } from "viem/chains";
import type { Address, EIP1193Provider } from "viem";

declare const ownerProvider: EIP1193Provider;
declare const ownerAddress: Address;
declare const prepared: PreparedGasPolicy;
declare const account: Address;
declare const agent: Address;
declare const recipients: Address[];

const walletClient = createWalletClient({
  account: ownerAddress,
  chain: baseSepolia,
  transport: custom(ownerProvider),
});
const ownerWallet: OwnerSigner = walletClient;

const call = await signPreparedGasPolicy(ownerWallet, prepared, {
  account,
  agent,
  recipients,
  relayer: { mode: "gol" },
});
const transactionHash = await walletClient.sendTransaction(call);
```

The third argument is what makes this safe. `signPreparedGasPolicy` recomputes the policy bytes, the mandate and gas policy IDs, the combined digest, and the wallet payload, then compares them with the values you pass, including the relayer mode, submitter, reimbursement recipient, and any tree limits. It throws `prepared_policy_mismatch: <what differed>` rather than opening a wallet for something other than what you intended.

You may also verify first, show the owner a review screen, and only then sign. `verifyPreparedGasPolicy` does the same checks and returns nothing.

## Pause, resume, and revoke a mandate

A pause, resume, or revoke is a safety action on a mandate, not a new approval. It targets the root or any child mandate of the policy's tree and uses the same digest-and-payload verification.

```ts theme={null}
import { signPreparedSafetyAction } from "@gol/sdk";
import type { OwnerSigner, PreparedMandateSafetyAction } from "@gol/sdk";

declare const ownerWallet: OwnerSigner;
declare const preparedPause: PreparedMandateSafetyAction;

const pauseCall = await signPreparedSafetyAction(ownerWallet, preparedPause, { action: "pause" });
```

A paused policy refuses new submissions before they are signed or sent, so no value moves. An action already accepted and waiting to be send is held while paused, proceeds if the owner resumes before its quote expires, and closes with no owner liability at quote expiry. The policy identity, caps, and expiry never change across a pause or resume.

## Revoke the gas policy

`signPreparedRevocation` verifies the relayed gas-policy revocation and returns the call. The same revocation is also available as a direct call the account makes itself, so the owner can always revoke without GOL.

```ts theme={null}
import { accountExecuteCall, signPreparedRevocation } from "@gol/sdk";
import type { OwnerSigner, PreparedPolicyRevocation } from "@gol/sdk";
import type { Address, Hex } from "viem";

declare const ownerWallet: OwnerSigner;
declare const revocation: PreparedPolicyRevocation;
declare const family: "safe" | "nexus" | "kernel" | "alchemy" | "nexus7702";
declare const account: Address;

const relayed = await signPreparedRevocation(ownerWallet, revocation);

// The same revocation as a call the account makes itself.
const directCall = accountExecuteCall(
  family,
  account,
  revocation.directOwnerCall.to as Address,
  revocation.directOwnerCall.data as Hex,
);
```

Response types are generated from the OpenAPI contract, where every field is a JSON string. Narrow an address or a hash to viem's `Address` or `Hex` when you pass it to an encoder, as above.

Revocation takes effect on-chain immediately. The next action, and any unpaid revert claim under the policy, fails. For a delegated EOA, `accountExecuteCall` returns the direct call itself, which the EOA sends from its own key. See [integrate hosted gas](/guides/hosted-gas#11-revoke-the-gas-policy) for the full sequence.

## What the helpers throw

These are plain `Error` objects with a stable message, thrown before a wallet is asked to sign anything.

| Message                                                                   | Meaning                                                                                                                                     |
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `prepared_policy_mismatch: <field>`                                       | The API response did not match what your application asked for, or was not internally consistent                                            |
| `wallet_payload_mismatch`                                                 | The wallet payload was not the reviewed shape for that family, account, core, and digest                                                    |
| `safety_action_mismatch`                                                  | The prepared pause, resume, or revoke did not carry the expected action, mandate, action code, function name, direct call, chain, or digest |
| `revocation_mismatch`                                                     | The prepared gas-policy revocation did not carry the expected function name, direct call, chain, or digest                                  |
| `prepared_setup_mismatch: <field>`                                        | A prepared EIP-7702 setup was not the reviewed delegate, chain, authorization, or canonical initialization                                  |
| `eip7702_authorization_signer_mismatch`, `initialization_signer_mismatch` | A setup signature did not recover to the account                                                                                            |
| `account_delegation_mismatch`, `account_delegate_runtime_mismatch`        | A delegated EOA does not point at the reviewed Nexus 1.3.3 runtime                                                                          |
| `wrong_chain`                                                             | A chain client was pointed somewhere other than Base Sepolia                                                                                |
| `account_proxy_mismatch`                                                  | The account's proxy runtime is not the reviewed one for that family                                                                         |
| `eoa_transaction_is_sent_by_owner_wallet`                                 | A delegated EOA's operation was passed to `signOwnerOperation`; the EOA sends it itself                                                     |
| `account_implementation_mismatch`                                         | The account's implementation slot is not the reviewed one for that family                                                                   |
| `account_implementation_unavailable`                                      | The implementation slot could not be read                                                                                                   |
| `owner_signer_requires_sign_message`                                      | A user operation was signed with a signer that has no `signMessage`                                                                         |
| `invalid_owner_signature`                                                 | The wallet returned bytes that are not a 65-byte ECDSA signature with `v` of 27 or 28                                                       |
| `invalid_safe_owner_signatures`                                           | A Safe payload was not valid hex, or exceeded 4096 bytes                                                                                    |

A throw means GOL or the SDK would not proceed. None of them means the owner's account is unsafe, and none of them should be resolved by retrying with modified inputs.

## Safe threshold signatures

`packOwnerSignature` deliberately does not reshape a Safe signature. Safe's own `checkSignatures` enforces the threshold and accepts assembled multisignature payloads, including contract-signature offsets, so the SDK preserves the wallet's exact bytes instead of imposing a one-owner ECDSA shape.

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

declare const assembledSafeSignatures: `0x${string}`;

const packed = packOwnerSignature("safe", assembledSafeSignatures);
```

For a one-owner Safe, the wallet's own signature passes through unchanged. For a multi-owner Safe, collect the threshold of signatures through your own coordination and pass the assembled payload to `operation.encode`.

## Confirm the owner path independently

`ownerSigningHash` and `ownerSafetySigningHash` read the account's own domain separator and validator configuration and compute the hash the account will check. They are the on-chain cross-check for a payload the SDK built.

```ts theme={null}
import { ownerSigningHash, ownerSafetySigningHash } from "@gol/sdk";
import type { AccountFamily } from "@gol/sdk";
import { createPublicClient, http } from "viem";
import { baseSepolia } from "viem/chains";
import type { Address, Hex } from "viem";

declare const family: AccountFamily;
declare const account: Address;
declare const digest: Hex;

const publicClient = createPublicClient({
  chain: baseSepolia,
  transport: http(),
});

// Confirms the account implementation, then reads the owner's own domain.
const approvalHash = await ownerSigningHash(publicClient, family, account, digest);

// Remains available after an implementation drift. It grants nothing.
const safetyHash = await ownerSafetySigningHash(
  publicClient,
  family,
  account,
  digest,
);
```

`ownerSigningHash` runs the full implementation preflight first and throws if the account is not the reviewed configuration, because a new authority should not be prepared for an unrecognized account. `ownerSafetySigningHash` skips that check on purpose: an owner must be able to pause and revoke even after their account's implementation changes. The difference is the whole reason a mandate can always be stopped.
