# Balances, history & receipts

A wallet UI needs three things the EIP-1193 provider doesn't hand you directly: a **token balance**, a **transfer history** for the activity feed, and a way to **wait for a transaction to confirm** so the balance can refresh. The SDK ships `OFERC20` and `OFEVM` — dependency-free helpers built on plain `URLSession` JSON-RPC that work against any public RPC for the chain.

## Read a token balance

```swift
let rpc = URL(string: "https://sepolia.base.org")!
let usdc = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"

let decimals = try await OFERC20.decimals(token: usdc, rpcURL: rpc)
let balance = try await OFERC20.formattedBalance(
    token: usdc, owner: walletAddress, decimals: decimals, rpcURL: rpc
)
// balance is a Decimal, e.g. 19.00
```

`OFERC20.balance(...)` returns the raw base-unit value as a lossless `String` if you'd rather format it yourself, and `OFERC20.transfer(to:amount:)` / `OFERC20.baseUnits(_:decimals:)` build `transfer` calldata.

## Transfer history (activity feed)

```swift
let transfers = try await OFERC20.transferHistory(
    token: usdc, owner: walletAddress, rpcURL: rpc, blocks: 24_000
)
for t in transfers {
    print(t.isOutgoing ? "Sent" : "Received", t.value, t.isOutgoing ? t.to : t.from)
}
```

`transferHistory` returns `[OFTokenTransfer]` newest-first — each with `hash`, `from`, `to`, `value` (raw base units), `blockNumber`, and `isOutgoing` (relative to `owner`).

:::note
Public RPCs cap `eth_getLogs` at **2000 blocks per query**, so `transferHistory` chunks the range automatically (`chunk: 2_000` by default) and scans `blocks` back from the latest block. Widen `blocks` to look further back, at the cost of more requests.
:::

## Wait for a transaction to confirm

After a send the balance won't change until the transaction is mined, so await a receipt before refreshing the UI:

```swift
// Plain transaction
let mined = try await OFEVM.waitForReceipt(txHash: txHash, rpcURL: rpc, timeout: 60)

// ERC-4337 user operation (a sponsored or EIP-7702 send) — via Openfort's bundler
let opMined = try await OFEVM.waitForUserOperationReceipt(
    userOpHash: userOpHash, chainId: 84532, publishableKey: "pk_test_...", timeout: 60
)
```

Both return `false` on timeout (and throw only on RPC errors).

## Related

* [Sponsored (gasless) transactions](/docs/products/embedded-wallet/swift/wallet/eip-7702)
* [Send a transaction](/docs/products/embedded-wallet/swift/wallet/send)
