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

# Browser entry point

> What @gol/sdk exports, and how to connect an owner wallet to it.

`@gol/sdk` is the browser-safe entry point. It has no Node.js imports, no API key, and no network calls of its own: it builds payloads, recomputes them, and hands a call back for you to send. Anything that needs your project credentials lives in [`@gol/sdk/server`](/sdk/server).

The exported surface is grouped below. Every signature and type is listed in the [SDK reference](/sdk/reference).

## Connect an owner wallet

Owner signing is a two-party flow. Your server asks the API to compile an approval, your browser verifies what came back, and the owner's wallet signs it. GOL never receives the owner's key, and the SDK has no function that accepts one.

`OwnerSigner` is the whole contract between the SDK and an owner's wallet. A viem wallet client already satisfies it.

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

// `ownerProvider` is the EIP-1193 provider your application already uses to
// reach the owner's wallet, such as an injected browser provider.
declare const ownerProvider: EIP1193Provider;
// `ownerAddress` is the connected owner account's address.
declare const ownerAddress: Address;

export const ownerWalletClient = createWalletClient({
  account: ownerAddress,
  chain: baseSepolia,
  transport: custom(ownerProvider),
});

export const ownerWallet: OwnerSigner = ownerWalletClient;
```

`OwnerSigner` needs `signTypedData`. It uses `signMessage` only for the Nexus, Kernel, and Alchemy account operations, so a wallet that cannot sign a raw message works for approvals but not for those installations.

<Warning>
  Never pass a private key, a mnemonic, or a keystore to this package. There is no API for it, and a key that reaches a server has already left the owner's control.
</Warning>

## Recompute before you sign

`verifyPreparedGasPolicy` is the reason this package can be trusted with an owner's authority. It re-derives the policy bytes, the mandate and gas policy IDs, the combined digest, and the wallet payload from the API's response, then compares them against what your application asked for. It throws on any difference, so a wrong or tampered response cannot reach the signing step.

```ts theme={null}
import { verifyPreparedGasPolicy } from "@gol/sdk";
import type { PreparedGasPolicy } from "@gol/sdk";
import type { Address } from "viem";

// `prepared` is the response from your server's prepareGasPolicy call.
declare const prepared: PreparedGasPolicy;
declare const account: Address;
declare const agent: Address;
declare const recipients: Address[];
declare const maxPerActionBaseUnits: bigint;

verifyPreparedGasPolicy(prepared, {
  account,
  agent,
  recipients,
  maxPerActionBaseUnits,
});
```

The expectations are optional but recommended. Without them the function still proves the response is internally consistent; with them it also proves the response is the one you asked for. Every mismatch throws an `Error` whose message begins `prepared_policy_mismatch:` and names what differed.

`signPreparedGasPolicy` performs the same verification itself before it asks the wallet to sign, so the explicit call above is only needed when you want to fail earlier, for example to render a review screen before opening the wallet.

## Sign and hand the call back

Every owner helper returns a `{ to, data }` transaction, never a signature for you to forward. Authority is entirely in the owner's signature inside that calldata.

```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;

// One eth_signTypedData_v4 request, in the format the account checks.
const call = await signPreparedGasPolicy(ownerWallet, prepared, {
  account,
  agent,
  recipients,
});
const transactionHash = await walletClient.sendTransaction(call);
```

Anyone may send `call`. The relayer, your server, or a third party can broadcast it; only the owner's signature makes it valid. That is why a mandate survives a relayer outage, and why a compromised server cannot forge one.

## Owner account operations

An approval and an installation are different things, and so are different functions.

An **approval** is a single `eth_signTypedData_v4` over a core digest, handled by `signPreparedGasPolicy`, `signPreparedSafetyAction`, and `signPreparedRevocation`. See [owner signing](/sdk/owner-signing).

An **account operation** makes the account itself do something, such as install the GOL core. `prepareOwnerOperation` builds it in the correct shape for the family: a Safe transaction for Safe, an EntryPoint v0.7 user operation for Nexus, Kernel, and Alchemy, and the EOA's own transaction for a delegated EOA. The account pays its own gas from its own ETH. A delegated EOA installs the core in its [EIP-7702 setup](/guides/accounts#eip-7702-setup) instead.

```ts theme={null}
import {
  prepareOwnerInstallation,
  prepareOwnerOperation,
  signOwnerOperation,
} from "@gol/sdk";
import type { AccountFamily, OwnerOperation, OwnerSigner } from "@gol/sdk";
import { createPublicClient, createWalletClient, custom, http } from "viem";
import { baseSepolia } from "viem/chains";
import type { Address, EIP1193Provider } from "viem";

declare const ownerProvider: EIP1193Provider;
declare const ownerAddress: Address;
declare const family: AccountFamily;
declare const account: Address;
declare const core: Address;

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

const installation = await prepareOwnerInstallation(
  publicClient,
  family,
  account,
  core,
);
const operation: OwnerOperation = await prepareOwnerOperation(
  publicClient,
  family,
  account,
  installation,
);
if (operation.kind === "eoa_transaction") {
  // A delegated EOA makes the call from its own key; there is nothing to sign separately.
  const { to, data } = operation.transaction;
  await walletClient.sendTransaction({ to, data });
} else {
  const signature = await signOwnerOperation(ownerWallet, operation);
  if (operation.kind === "safe_transaction") {
    const { to, data } = operation.encode(signature);
    await walletClient.sendTransaction({ to, data });
  } else {
    const { to, data, gas } = operation.encode(signature, account);
    await walletClient.sendTransaction({ to, data, gas });
  }
}
```

For Nexus, Kernel, and Alchemy you can instead hand `operation.userOperation` to your own bundler. A Safe with more than one owner needs its threshold of signatures: pass the assembled bytes to `operation.encode`.

`preparePermanentRemoval` builds the reverse operation, invalidating every mandate on the account before uninstalling the core, so a later reinstallation cannot revive them.

## Encode a policy yourself

Normally the API compiles the policy and you verify it. These helpers let you compute the same bytes locally, which is useful for a review screen or a local test.

```ts theme={null}
import {
  BASE_SEPOLIA_USDC,
  contractProjectId,
  encodeTransferPolicy,
  hashGasMandate,
} from "@gol/sdk";
import type { GasMandateTerms } from "@gol/sdk";
import { keccak256 } from "viem";
import { baseSepolia } from "viem/chains";
import type { Address } from "viem";

declare const recipients: Address[];
declare const terms: GasMandateTerms;
declare const core: Address;
declare const projectUuid: string;

const policyBytes = encodeTransferPolicy({
  asset: BASE_SEPOLIA_USDC,
  recipients,
  maxPerActionBaseUnits: 1_000_000n,
  maxTotalBaseUnits: 10_000_000n,
});

const projectId = contractProjectId(projectUuid);
const mandateId = hashGasMandate(
  { ...terms, projectId, policyHash: keccak256(policyBytes) },
  baseSepolia.id,
  core,
);
```

`contractProjectId` turns your project's UUID into the `bytes32` the contract stores. `encodeTransferPolicy` accepts 1 to 16 recipients and refuses a per-action cap above the total cap. `hashGasMandate` recomputes the mandate ID from the terms, so a mismatch with the API is visible before you ask an owner to sign.

## Inspect a wallet payload

`buildWalletSigningPayload` shows the exact typed data a family expects for a GOL owner payload, and `verifyWalletSigningPayload` checks a payload you received. [Supported accounts](/guides/accounts) lists the per-family formats and implementations.

```ts theme={null}
import {
  buildWalletSigningPayload,
  verifyWalletSigningPayload,
} from "@gol/sdk";
import type { AccountFamily } from "@gol/sdk";
import type { Address, Hex } from "viem";

declare const family: AccountFamily;
declare const account: Address;
declare const core: Address;
declare const mandateId: Hex;
declare const gasPolicyId: Hex;

const expected = {
  family,
  account,
  core,
  // Or GasRevocation { gasPolicyId, nonce, deadline }, or SafetyAction { mandateId, action, nonce, deadline }.
  payload: { primaryType: "CombinedGasApproval" as const, message: { mandateId, gasPolicyId } },
};
const payload = buildWalletSigningPayload(expected);

payload.method; // "eth_signTypedData_v4"
payload.signatureFormat; // "safe_threshold" | "nexus_k1" | "kernel_root" | "alchemy_entity_zero" | "eoa"
payload.digest; // the core digest the account will check
payload.expectedHash; // the hash of the typed data itself; equals the digest for a delegated EOA

verifyWalletSigningPayload(payload, expected);
```

`verifyWalletSigningPayload` recomputes the payload from the family, account, core, and GOL payload and throws `wallet_payload_mismatch` unless every field matches, including the hash of the typed data itself.

## Check the account first

`assertAccountImplementation` is a read-only preflight that verifies the proxy runtime and the ERC-1967 implementation slot at one block. The API runs the same check before every approval and action; calling it yourself lets you fail before opening a wallet.

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

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

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

await assertAccountImplementation(publicClient, family, account);
```

It throws `wrong_chain`, `account_proxy_mismatch`, `account_implementation_unavailable`, or `account_implementation_mismatch`. A throw is not a verdict on the account's safety, only that GOL cannot recognize it, so stop and ask the owner rather than retrying.

## Keep credentials out of this bundle

`@gol/sdk` needs no API key, which is what makes it safe here. Everything that carries a project credential is in [`@gol/sdk/server`](/sdk/server). If your bundler is about to inline a `gol_` prefixed string, an API key has crossed from the server into the browser.
