> For the complete documentation index, see [llms.txt](https://docs.osero.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.osero.com/osero-sdk/core-concepts/execution-plan.md).

# Execution Plan

Every action in `@osero/client` returns an `ExecutionPlan` — a wallet-agnostic description of the transactions that need to happen to complete the requested operation. The `ExecutionPlan` is the central abstraction of the SDK's design, and understanding it unlocks three important capabilities: dry-runs, portability, and testability.

## What is an ExecutionPlan?

An `ExecutionPlan` is a discriminated union of three shapes, representing the different kinds of transaction sequences the SDK needs to execute:

All three variants are discriminated by a `__typename` literal. Pattern-match on `plan.__typename` in a `switch` to handle each shape.

### TransactionRequest

A single pre-encoded transaction, ready to send.

```typescript
type TransactionRequest = {
  readonly __typename: 'TransactionRequest';
  readonly chainId: number;
  readonly from: `0x${string}`;
  readonly to: `0x${string}`;
  readonly data: `0x${string}`;
  readonly value: bigint;            // defaults to 0n; always present
  readonly operation: OperationType; // e.g. 'APPROVE_ERC20', 'MINT_USDS'
};
```

`OperationType` is a string-literal union used as a lightweight provenance tag so callers and adapters can classify a step without decoding calldata:

```typescript
type OperationType =
  | 'APPROVE_ERC20'
  | 'MINT_USDS'
  | 'MINT_SUSDS'
  | 'DEPOSIT_USDS_FOR_SUSDS'
  | 'REDEEM_USDS_FOR_USDC'
  | 'REDEEM_SUSDS_FOR_USDC'
  | 'REDEEM_SUSDS_FOR_USDS';
```

### Erc20ApprovalRequired

One or more ERC-20 approval transactions gating a single main transaction. This is the shape used by all L2 mint and redeem operations — the caller first approves the PSM3 to spend their token, then the swap is executed.

```typescript
type Erc20Approval = {
  readonly token: `0x${string}`;
  readonly spender: `0x${string}`;
  readonly amount: bigint;
  readonly byTransaction: TransactionRequest; // the actual `approve` tx
};

type Erc20ApprovalRequired = {
  readonly __typename: 'Erc20ApprovalRequired';
  readonly approvals: readonly Erc20Approval[];
  readonly originalTransaction: TransactionRequest; // the gated main tx
};
```

Each entry in `approvals` is an `Erc20Approval` record (not a raw `TransactionRequest`) — use `approval.byTransaction` when you need the underlying transaction. The main transaction lives on `originalTransaction`.

### MultiStepExecution

An ordered list of steps, where each step depends on the previous one settling on-chain. This is used on Ethereum Mainnet for `mintSUsds` (USDC → USDS → sUSDS, two phases) and `redeemSUsds` (sUSDS → USDS → USDC, two phases).

```typescript
type ExecutionStep = TransactionRequest | Erc20ApprovalRequired;

type MultiStepExecution = {
  readonly __typename: 'MultiStepExecution';
  readonly steps: readonly ExecutionStep[];
};
```

## The sendWith adapter

The `sendWith` function (from `@osero/client/viem`, `@osero/client/ethers`, or `@osero/client/privy`) is the only component that touches a real wallet. It walks the plan, broadcasts each transaction in the correct order, and waits for on-chain confirmation between steps where required.

```typescript
// The adapter is the same regardless of plan shape
const result = await mintSUsds(client, request).andThen(sendWith(wallet));
```

## Dry-runs: inspecting a plan without signing

Because actions are pure functions that produce plans before touching any wallet, you can inspect the full transaction sequence before broadcasting anything. Use `flattenExecutionPlan` to get a flat array of individual transactions:

```typescript
import { flattenExecutionPlan } from '@osero/client';
import { mintSUsds } from '@osero/client/actions';
import { parseUnits } from 'viem';

const planResult = await mintSUsds(client, {
  chainId: 8453,
  amount: parseUnits('100', 6),
  sender: '0xYourAddress',
});

if (planResult.isOk()) {
  const txs = flattenExecutionPlan(planResult.value);
  for (const tx of txs) {
    console.log('Operation:', tx.operation);
    console.log('To:', tx.to);
    console.log('Data:', tx.data);
  }
  // No wallet interaction has happened
}
```

This is useful for debugging, pre-flight checks, or displaying transaction details to users before they sign.

## Portability: one plan, any wallet

Because the plan is wallet-agnostic, you can route it to any signing infrastructure. Built-in adapters ship for **viem**, **ethers v6**, and **Privy server wallets**:

```typescript
// viem WalletClient
import { sendWith } from '@osero/client/viem';
await plan.andThen(sendWith(viemWallet));

// ethers v6 Signer
import { sendWith } from '@osero/client/ethers';
await plan.andThen(sendWith(ethersSigner));

// Privy server wallet
import { sendWith } from '@osero/client/privy';
await plan.andThen(sendWith(privy, privyWallet));
```

If you need to broadcast through something else — a relayer, a 4337 bundler, a batching contract — you can flatten the plan to its underlying transactions and feed them into your own pipeline. The SDK does not ship a built-in adapter for these; you write the broadcast loop yourself.

```typescript
if (planResult.isOk()) {
  const txs = flattenExecutionPlan(planResult.value);
  for (const tx of txs) {
    // Your own sending logic: relayer, bundler, batcher, etc.
    await myBroadcaster.send(tx);
  }
}
```

## Testability: mocking without a live chain

Actions are pure functions over an `OseroClient`. To unit-test them without a live chain or real wallet, inject a mock `PublicClient` when constructing the client:

```typescript
import { OseroClient } from '@osero/client';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

// In tests: inject a mock or forked client
const mockClient = OseroClient.create({
  transports: {
    8453: http('http://localhost:8545'), // local anvil fork
  },
});

const result = await mintSUsds(mockClient, {
  chainId: 8453,
  amount: parseUnits('100', 6),
  sender: testAddress,
});

// Assert on the plan without broadcasting
expect(result.isOk()).toBe(true);
```

## Plan shapes by action

| Action        | Mainnet shape                | L2 shape                        |
| ------------- | ---------------------------- | ------------------------------- |
| `mintUsds`    | `Erc20ApprovalRequired`      | `Erc20ApprovalRequired`         |
| `mintSUsds`   | `MultiStepExecution` (4 txs) | `Erc20ApprovalRequired` (2 txs) |
| `redeemUsds`  | `Erc20ApprovalRequired`      | `Erc20ApprovalRequired`         |
| `redeemSUsds` | `MultiStepExecution` (3 txs) | `Erc20ApprovalRequired` (2 txs) |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.osero.com/osero-sdk/core-concepts/execution-plan.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
