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

# Yield via Osero Earn

> Convert USDC into USDS or the yield-bearing sUSDS savings token, and redeem either asset back to USDC from a Para-powered wallet.

<Info>
  **Looking for the full documentation?** Visit the complete [Osero Earn docs](https://docs.osero.org/osero-sdk) for the full SDK reference.
</Info>

Combine Para's wallet infrastructure with the **Osero Earn Integration SDK** (`@osero/client`) to let users convert USDC into USDS or the yield-bearing sUSDS savings token, and redeem either asset back to USDC, from a Para-powered wallet. Users can interact through your application's interface without installing a browser extension or managing a seed phrase.

## What You'll Learn

This walkthrough covers the Osero Earn lifecycle: installing the SDK alongside Para, initializing a client, previewing a quote, minting and redeeming on supported chains, reading balances, inspecting execution plans, and using alternate signer adapters.

<Note>
  **Version scope:** The API and supported-chain details below target `@osero/client@0.8.0`, the npm `latest` release at the time of writing. Check `SUPPORTED_CHAIN_IDS` at runtime when upgrading.
</Note>

## Why Combine Para and Osero Earn

| Para Wallets                                                 | Osero Earn Integration SDK                                                                                         |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| Embedded wallet onboarding backed by Para's MPC architecture | Unified actions for USDS and sUSDS on the chains in `SUPPORTED_CHAIN_IDS`                                          |
| Email and social login without seed-phrase onboarding        | Handles chain-specific contracts, calldata, approvals, and transaction ordering                                    |
| Para-backed EVM accounts usable through viem                 | Wallet-library-independent `ExecutionPlan` output                                                                  |
| Application-controlled wallet experience                     | Typed operational errors through `ResultAsync`, with documented exceptions for configuration and programmer misuse |

## Example Use Cases

1. **Consumer Earn Product:** Users deposit USDC into a Para wallet and mint sUSDS for yield, entirely inside your app's UI, with no protocol interaction visible to them.
2. **Fintech Stablecoin Rails:** Embed USDS mint/redeem flows in a fintech app using Para's white-label wallet onboarding, giving users a stable, non-yield-bearing balance alongside an optional yield toggle.
3. **Treasury Automation:** Automate treasury USDS/sUSDS positions from Para wallets with programmable mint and redeem rules, previewing expected output before every rebalance.

## What You Need

* An authenticated Para session with an EVM wallet
* A supported EVM chain
* `@osero/client` and its required `viem` peer dependency
* A production RPC endpoint for every chain you use
* Optional: `ethers` for the ethers v6 adapter

## How It Works

Each mint or redeem flow follows the same three-layer pattern:

1. **Preview:** A read-only helper such as `previewMintSUsds` quotes the expected output as a `bigint`. It needs no wallet or sender.
2. **Action:** `mintUsds`, `mintSUsds`, `redeemUsds`, or `redeemSUsds` builds a typed, wallet-independent `ExecutionPlan`. Building a plan may perform live RPC reads to obtain fees, vault previews, or PSM quotes.
3. **Adapter:** `sendWith(...)` executes the plan through a supported signer: a Para-backed viem wallet client, or an ethers v6 signer.

The plan is created before the signer is invoked, so your application can inspect its transactions before prompting the user. A plan is independent of the wallet library, but it is still bound to the request's `sender`: the selected signer must control the plan's `from` address and be connected to the target chain where applicable.

### Supported Chains

The direct Osero Earn actions in `@osero/client@0.8.0` support:

| Chain            | Chain ID | Route                                                                                      |
| ---------------- | -------- | ------------------------------------------------------------------------------------------ |
| Ethereum Mainnet | 1        | Spark `UsdsPsmWrapper`, backed by the Lite PSM; ERC-4626 sUSDS deposit/redeem where needed |
| OP Mainnet       | 10       | Spark PSM3                                                                                 |
| Unichain         | 130      | Spark PSM3                                                                                 |
| Base             | 8453     | Spark PSM3                                                                                 |
| Arbitrum One     | 42161    | Spark PSM3                                                                                 |

Use the SDK's registry as the source of truth:

```typescript theme={null}
import { SUPPORTED_CHAIN_IDS } from "@osero/client";

console.log(SUPPORTED_CHAIN_IDS);
// @osero/client@0.8.0: [1, 10, 130, 8453, 42161]
```

The hosted Osero API may recognize additional chains. That does not imply support in the direct Earn actions described in this guide.

## Step-by-Step Integration

### Step 1: Install Dependencies

This walkthrough uses Para's minimal web SDK and the low-level viem integration directly:

```shell theme={null}
npm install @getpara/web-sdk @getpara/viem-v2-integration @osero/client viem
```

`viem` is a required peer dependency of `@osero/client`. Osero uses it to encode calldata and create public clients even when another adapter broadcasts transactions.

Optional adapters:

```shell theme={null}
# ethers v6 adapter
npm install ethers
```

`@osero/client` is ESM, is authored in strict TypeScript, and includes type declarations. No additional `@types` package is required.

<Info>
  If your React application already uses `@getpara/react-sdk` and `ParaProvider`, reuse the authenticated Para instance from that integration instead of constructing a second independent client. Para's current React guidance also provides `useParaViemClient` for hook-based applications.
</Info>

### Step 2: Initialize Para and Osero

The code below creates the SDK clients, but it does **not** implement Para login. Complete your application's [Para authentication flow](/v3/react/guides/custom-ui-web-sdk) and ensure the user has an EVM wallet before creating the viem account.

Use authenticated RPC endpoints in production. Public RPC endpoints are commonly rate-limited.

```typescript theme={null}
import { ParaWeb } from "@getpara/web-sdk";
import {
  createParaViemAccount,
  createParaViemClient,
} from "@getpara/viem-v2-integration";
import { OseroClient } from "@osero/client";
import { http } from "viem";
import { base } from "viem/chains";

const baseRpcUrl = "https://mainnet.base.org"; // Replace in production

const para = new ParaWeb("YOUR_PARA_API_KEY");

const client = OseroClient.create({
  transports: {
    8453: http(baseRpcUrl),
  },
  defaultSlippageBps: 10, // 0.10%; the SDK default is 5 bps
});
```

### Step 3: Create the Para-Backed viem Wallet

Run this only after Para authentication has completed and an EVM wallet exists:

```typescript theme={null}
const account = createParaViemAccount({ para });

const walletClient = createParaViemClient({
  para,
  walletClientConfig: {
    account,
    chain: base,
    transport: http(baseRpcUrl),
  },
});
```

The older `createParaAccount(para)` alias and positional `createParaViemClient(para, config)` overload still exist in `@getpara/viem-v2-integration@3.11.0`, but they are deprecated.

You do not need to create a separate viem `publicClient` for Osero reads. `OseroClient` creates and caches its own public clients using the transports supplied to `OseroClient.create`.

### Step 4: Preview an sUSDS Mint

Preview helpers return the expected output as a raw `bigint`. They require only `chainId` and `amount`.

```typescript theme={null}
import { previewMintSUsds } from "@osero/client/actions";
import { formatUnits, parseUnits } from "viem";

const amount = parseUnits("100", 6); // 100 USDC

const quote = await previewMintSUsds(client, {
  chainId: 8453,
  amount,
});

if (quote.isErr()) {
  console.error("Could not quote mint:", quote.error.name, quote.error.message);
} else {
  console.log(
    `Expected output: approximately ${formatUnits(quote.value, 18)} sUSDS`,
  );
}
```

A preview is not a guaranteed execution result. The relevant on-chain state can change between previewing, building the plan, and mining the transaction.

### Step 5: Mint sUSDS

On user confirmation, build the plan and send it through the Para-backed wallet client:

```typescript theme={null}
import { mintSUsds } from "@osero/client/actions";
import { sendWith } from "@osero/client/viem";

try {
  const result = await mintSUsds(client, {
    chainId: 8453,
    amount,
    sender: account.address,
  }).andThen(sendWith(walletClient));

  if (result.isErr()) {
    console.error(result.error.name, result.error.message);
  } else {
    console.log("sUSDS received in final tx", result.value.txHash);
  }
} catch (error) {
  // Covers documented synchronous/configuration failures and unexpected
  // rejected promises that do not become a ResultAsync Err value.
  console.error("Mint execution failed unexpectedly", error);
}
```

On Base and the other supported L2s, `mintSUsds` produces an `Erc20ApprovalRequired` plan with two transactions:

1. Approve USDC to PSM3.
2. Swap USDC for sUSDS through PSM3.

On Ethereum Mainnet, it produces a four-transaction `MultiStepExecution` route:

1. Approve USDC to `UsdsPsmWrapper`.
2. Convert USDC to USDS.
3. Approve USDS to the sUSDS vault.
4. Deposit USDS and deliver sUSDS to the receiver.

`sendWith(walletClient)` executes the transactions in order and waits for each required confirmation before continuing.

To receive non-yield-bearing USDS instead, use `mintUsds`. `redeemUsds` follows the same action → plan → adapter pattern in the opposite direction.

### Step 6: Send Output to Another Address

Each action accepts an optional `receiver`. The signer still pays with assets owned by `sender`, while the resulting asset is delivered to `receiver`.

```typescript theme={null}
import type { Address } from "viem";

const custodyAddress: Address = "0x0000000000000000000000000000000000000001";

const result = await mintSUsds(client, {
  chainId: 8453,
  amount,
  sender: account.address,
  receiver: custodyAddress,
}).andThen(sendWith(walletClient));
```

<Note>
  On Ethereum Mainnet, `sender` temporarily receives the intermediate USDS even when `receiver` differs. Any USDS dust left after the vault deposit remains with `sender`.
</Note>

Setting `receiver` does not authorize that address to sign for `sender`, and it does not move custody of the signing wallet.

### Step 7: Redeem sUSDS to USDC

```typescript theme={null}
import { redeemSUsds } from "@osero/client/actions";
import { getSUsdsBalance } from "@osero/client";

const balanceResult = await getSUsdsBalance(client, {
  chainId: 8453,
  account: account.address,
});

if (balanceResult.isErr()) {
  console.error(balanceResult.error.name, balanceResult.error.message);
} else {
  try {
    const redeemResult = await redeemSUsds(client, {
      chainId: 8453,
      amount: balanceResult.value, // sUSDS uses 18 decimals
      sender: account.address,
    }).andThen(sendWith(walletClient));

    if (redeemResult.isErr()) {
      console.error(redeemResult.error.name, redeemResult.error.message);
    } else {
      console.log("USDC received in final tx", redeemResult.value.txHash);
    }
  } catch (error) {
    console.error("Redeem execution failed unexpectedly", error);
  }
}
```

### Step 8: Read Balances

Osero exposes helpers for USDC, USDS, and sUSDS on every supported direct-action chain.

```typescript theme={null}
import { getSUsdsBalance, getTokenBalances } from "@osero/client";
import { formatUnits } from "viem";

const balances = await getTokenBalances(client, {
  chainId: 8453,
  account: account.address,
});

if (balances.isErr()) {
  console.error(balances.error.name, balances.error.message);
} else {
  console.log("USDC:", formatUnits(balances.value.USDC, 6));
  console.log("USDS:", formatUnits(balances.value.USDS, 18));
  console.log("sUSDS:", formatUnits(balances.value.sUSDS, 18));
}

const susds = await getSUsdsBalance(client, {
  chainId: 8453,
  account: account.address,
});
```

Reading balances before and after execution lets you measure the exact token delta received. The final transaction hash alone does not communicate the exact amount received.

## Broadcasting with Other Signers

Osero's action API is adapter-independent, but each request and plan is bound to its `sender`. Build the request with the address controlled by the selected signer.

```typescript theme={null}
import type { Address } from "viem";
import { mintUsds } from "@osero/client/actions";
import { sendWith as sendWithViem } from "@osero/client/viem";
import { sendWith as sendWithEthers } from "@osero/client/ethers";

const requestFor = (sender: Address) => ({
  chainId: 8453,
  amount,
  sender,
});

// Para-backed viem wallet. The client must be configured for Base.
await mintUsds(client, requestFor(account.address)).andThen(
  sendWithViem(walletClient),
);

// ethers v6 signer. The signer must control ethersAddress and be connected
// to the target chain.
await mintUsds(client, requestFor(ethersAddress)).andThen(
  sendWithEthers(ethersSigner),
);
```

## Inspecting an Execution Plan Before Signing

Actions build their plans before invoking a signer, but they are not pure functions: they can perform live RPC reads and return transport errors.

```typescript theme={null}
import { flattenExecutionPlan } from "@osero/client";

const planResult = await mintSUsds(client, {
  chainId: 8453,
  amount: parseUnits("100", 6),
  sender: account.address,
});

if (planResult.isErr()) {
  console.error(planResult.error.name, planResult.error.message);
} else {
  const transactions = flattenExecutionPlan(planResult.value);

  for (const transaction of transactions) {
    console.log("Chain:", transaction.chainId);
    console.log("Operation:", transaction.operation);
    console.log("From:", transaction.from);
    console.log("To:", transaction.to);
    console.log("Value:", transaction.value);
    console.log("Calldata:", transaction.data);
  }

  // No signer has been invoked yet.
}
```

The transaction `data` is encoded calldata. Decode it against the relevant ABI before presenting it as a human-readable signing explanation.

An execution plan is a discriminated union. Pattern-match on `plan.__typename` when handling the shapes directly:

| Shape                   | Description                                                                 |
| ----------------------- | --------------------------------------------------------------------------- |
| `TransactionRequest`    | One pre-encoded transaction.                                                |
| `Erc20ApprovalRequired` | One or more approval transactions followed by one main transaction.         |
| `MultiStepExecution`    | Ordered execution steps where each step must settle before the next starts. |

For `@osero/client@0.8.0`:

| Action        | Ethereum Mainnet                | Supported L2s                   |
| ------------- | ------------------------------- | ------------------------------- |
| `mintUsds`    | `Erc20ApprovalRequired` (2 txs) | `Erc20ApprovalRequired` (2 txs) |
| `mintSUsds`   | `MultiStepExecution` (4 txs)    | `Erc20ApprovalRequired` (2 txs) |
| `redeemUsds`  | `Erc20ApprovalRequired` (2 txs) | `Erc20ApprovalRequired` (2 txs) |
| `redeemSUsds` | `MultiStepExecution` (3 txs)    | `Erc20ApprovalRequired` (2 txs) |

## Error Handling

Osero uses `neverthrow` for its normal action, preview, balance, and adapter workflows. Check the final `ResultAsync` result after chaining:

```typescript theme={null}
const request = {
  chainId: 8453,
  amount,
  sender: account.address,
};

try {
  const result = await mintSUsds(client, request).andThen(
    sendWith(walletClient),
  );

  if (result.isErr()) {
    console.error(result.error.name, result.error.message);
    return;
  }

  console.log(result.value.txHash);
} catch (error) {
  console.error("Unexpected thrown or rejected failure", error);
}
```

Do not describe the entire package as having "no thrown exceptions." Public synchronous or rejected-promise paths include:

* `OseroClient.getPublicClient(unsupportedChain)` throwing `UnsupportedChainError` when called directly.
* viem `sendWith(walletClient)` throwing if the client has no attached account or chain.
* Invalid non-integer or out-of-range `slippageBps` reaching `applySlippage` and throwing `RangeError` in affected L2 paths.

Validate application input before calling the SDK. In particular, keep `slippageBps` an integer in the supported range and wrap the full action/adapter expression in `try`/`catch`.

### Errors relevant to the direct Earn flow

All Osero error classes extend `OseroError`, but the exact error union depends on the helper and adapter being used.

| Error                      | Typical cause                                                                                     | Suggested handling                                       |
| -------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `CancelError`              | User rejected a wallet request, or an API request was explicitly aborted.                         | Show a cancellation message; do not automatically retry. |
| `ValidationError`          | Invalid request data such as an amount less than or equal to zero.                                | Correct the input before retrying.                       |
| `UnsupportedChainError`    | The requested chain is not in `SUPPORTED_CHAIN_IDS`.                                              | Ask the user to select a supported chain.                |
| `InsufficientBalanceError` | A preview or preflight check reports too little of the required token.                            | Show required and available balances.                    |
| `SigningError`             | Pre-broadcast wallet, gas-estimation, signing, or permission failure.                             | Inspect `error.cause`; retry only when appropriate.      |
| `TransactionError`         | The viem adapter observes a mined, reverted receipt. It includes `txHash` and may include `link`. | Show the transaction hash and explorer link.             |
| `UnexpectedError`          | RPC failure, timeout, unsupported adapter state, or another unclassified failure.                 | Inspect `error.cause` and retry only if transient.       |

<Warning>
  **ethers caveat:** ethers v6 throws `CALL_EXCEPTION` from `TransactionResponse.wait()` for a mined status-0 transaction. In `@osero/client@0.8.0`, the ethers adapter can map that rejection to `UnexpectedError` instead of `TransactionError`. Inspect `UnexpectedError.cause` for an ethers receipt or transaction hash.
</Warning>

`ApiRequestError` is another exported `OseroError` subclass, but it belongs to the separate hosted Osero API client rather than the direct Earn flow shown here.

<Info>
  **Want more detail?** Check out the full [Osero Earn docs](https://docs.osero.org/osero-sdk) for complete SDK reference and guides.
</Info>
