# <span className="brand brand-openfort" /> Openfort

[Openfort](https://www.openfort.io/docs/products/server/viem-integration) backend wallets are EOAs whose keys stay on Openfort's side. The SDK exposes the four signing methods viem wants, so `toAccount` bridges it to a wallet client.

## Install

```bash
pnpm add @openfort/openfort-node viem
pnpm add -D deployoor tsx
```

## The account

The SDK has no viem helper of its own, so wrap the account with viem's `toAccount`:

```ts
// accounts.ts
import Openfort from "@openfort/openfort-node";
import { toAccount } from "viem/accounts";

const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY as string, {
  walletSecret: process.env.OPENFORT_WALLET_SECRET as string,
});

const account = await openfort.accounts.evm.backend.get({
  id: process.env.OPENFORT_ACCOUNT_ID as string,
});

export const deployer = toAccount({
  address: account.address,
  sign: ({ hash }) => account.sign({ hash }),
  signMessage: ({ message }) => account.signMessage({ message }),
  signTransaction: (transaction) => account.signTransaction(transaction),
  signTypedData: (typedData) => account.signTypedData(typedData),
});
```

Create the wallet once, out of band, and keep its id in the environment:

```ts
const created = await openfort.accounts.evm.backend.create();
console.log(created.id, created.address); // acc_..., 0x...
```

`create()` mints a new keypair every call. It takes no name, so it is not a get-or-create: calling it on each run would leave you a new, unfunded deployer every time. Create once and `get({ id })` after that, or pass an `idempotencyKey` if you would rather keep it in the deploy path. `get({ address })` works too if you track the address instead.

## The deploy

```ts
// scripts/deploy.ts, run with `tsx scripts/deploy.ts`
import { createPublicClient, createWalletClient, http } from "viem";
import { baseSepolia } from "viem/chains";
import { deployer } from "../accounts";
import { getOrDeployCounter } from "../deployers";

const transport = http(process.env.RPC_URL);
const clients = {
  walletClient: createWalletClient({ account: deployer, chain: baseSepolia, transport }),
  publicClient: createPublicClient({ chain: baseSepolia, transport }),
};

const { contract, freshDeploy } = await getOrDeployCounter({ ...clients, args: [0n] });
if (freshDeploy) await contract.write.inc();
```

`toAccount` gives you a local account, so viem asks Openfort for a signature and then broadcasts the raw transaction itself. The wallet's own private key never enters your process, though the credentials below do.

## Multi-chain with one account

The account is not bound to a chain, so build a client per chain and loop:

```ts
import { arbitrum, base, optimism } from "viem/chains";

const rpcUrls: Record<number, string | undefined> = {
  [base.id]: process.env.BASE_RPC_URL,
  [optimism.id]: process.env.OP_RPC_URL,
  [arbitrum.id]: process.env.ARBITRUM_RPC_URL,
};

for (const chain of [base, optimism, arbitrum]) {
  const rpcUrl = rpcUrls[chain.id];
  // http() with no url silently falls back to the chain's public RPC.
  if (rpcUrl === undefined) throw new Error(`No RPC URL for chain ${chain.id}`);
  const transport = http(rpcUrl);
  await getOrDeployCounter({
    walletClient: createWalletClient({ account: deployer, chain, transport }),
    publicClient: createPublicClient({ chain, transport }),
    args: [0n],
  });
}
```

Each deploy records under its own `deployments/<chainId>-<network>/`.

## Notes

* The backend wallet pays its own gas, so fund it before the first deploy.
* Openfort's gasless path is ERC-4337: it routes a user operation through a paymaster and a smart account, which is not a viem wallet client and so will not drive a deployer. Keep deploys on the funded-EOA path above.
* `OPENFORT_SECRET_KEY` authenticates the API and `OPENFORT_WALLET_SECRET` authorises signing. Both are server-only: keep them out of browser bundles and out of source control. The wallet secret is the one that can move funds, so a leak of it is the worst case, but the secret key authenticates every call and deserves the same handling.
* viem calls `signTransaction(tx, { serializer })`, and the SDK's method takes only the transaction. A custom serializer is therefore ignored, which does not affect an ordinary deploy.
