# Setup

Wrap your wallet provider with `withYodl7702` and pass the result wherever you'd pass the wallet provider.

```tsx
import { signAuthorizationViaProvider, withYodl7702 } from '@yodlpay/react-native-eip-7702-provider';
import { YodlProvider, YodlPayment } from '@yodlpay/react-native';

const provider = withYodl7702(walletProvider, {
  // The only thing you must supply — no wallet signs 7702 authorizations the same way.
  signAuthorization: signAuthorizationViaProvider(walletProvider),
});

<YodlProvider sdk={sdk} provider={provider} address={address} chainId={chainId}>
  <YodlPayment qrData={qrData} />
</YodlProvider>;
```

That is the whole configuration for an integration against Yodl's hosted UI. `chains` defaults to Yodl's own bundler on the chains Yodl sponsors gas for, gas pricing defaults to asking that bundler, and chain reads go over the wallet you already passed in.

The wrapper reads the account and chain id from each `wallet_sendCalls` bundle, both supplied by the Yodl UI. The account implementation is the canonical SimpleAccount on EntryPoint 0.8 — used as both the delegated account logic and the authorization's delegate target, so the two can't drift. Sponsorship uses the standard [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) paymaster carried in the bundle's capabilities.

## Using Yodl's bundler

The default is `yodlHostedChains()`, which names the host that already sponsors the payment — the same URL the Yodl UI attaches to every bundle as `capabilities.paymasterService.url`. There is no bundler account, no API key and no per-op cost, because Yodl's pricing sponsors L2 gas.

Pass it explicitly when you'd rather see the choice at the call site, or narrow it:

```tsx
import { withYodl7702, yodlHostedChains } from '@yodlpay/react-native-eip-7702-provider';

const provider = withYodl7702(walletProvider, {
  chains: yodlHostedChains([8453]),   // Base only; omit the argument for every sponsored chain
  signAuthorization: signAuthorizationViaProvider(walletProvider),
});
```

If you already have a `createYodlClient(...)` instance, pass it as `yodl` and the wrapper follows it — its `baseUrl` (so a staging build submits to staging's bundler, where `yodlHostedChains()` is production-only), its chains (the ones Yodl sponsors gas on — a default instance also reads on mainnet, which stays out so a mainnet bundle fails with `5710` rather than building an operation the user would have to fund), its RPC proxy as `rpcUrl` where it names one, and its `getAuthHeaders`:

```tsx
const provider = withYodl7702(walletProvider, {
  yodl,
  signAuthorization: signAuthorizationViaProvider(walletProvider),
});
```

Explicit `chains` or `getAuthHeaders` beside `yodl` win over what the instance supplies. The `getAuthHeaders` inherited from the instance keeps sdk-core's promise: it is only sent to requests on `baseUrl`'s origin, so pairing `yodl` with your own bundler, RPC or paymaster never hands the host session token to a third party. Pass `getAuthHeaders` explicitly when you want credentials on your own endpoints.

A chain Yodl does not sponsor throws rather than being handed an invented endpoint. Mainnet is deliberately excluded — on L1 gas is bundled into the payment rather than sponsored, so a mainnet bundle fails loudly with EIP-5792 `5710` instead of quietly building a UserOperation the user has to fund.

:::note
**What the default delegates.** The bundler receives the signed UserOperation and the signed EIP-7702 authorization, decides whether and when to submit, and is the only source of the settlement status reported back through `wallet_getCallsStatus`. On the default, that host is Yodl's. The sponsored-chain list comes from Yodl's chain table in `@yodlpay/tokenlists`, so Yodl adding a corridor reaches you as a package upgrade rather than a config edit.
:::

## Bringing your own bundler

Pass `chains` entries directly and none of your traffic touches Yodl:

```tsx
const provider = withYodl7702(walletProvider, {
  // List only the chains you can actually serve the fallback on.
  chains: [
    {
      id: 8453,
      bundlerUrl: 'https://api.pimlico.io/v2/8453/rpc?apikey=YOUR_KEY',
      // rpcUrl is optional — see "Chain reads" below.
    },
  ],
  signAuthorization: signAuthorizationViaProvider(walletProvider),
});
```

:::note
`chains` here is independent of the SDK's `supportedChainIds`. This list is where the *fallback* can run — it should cover only chains you have a bundler for. A bundle on a chain outside this list fails with EIP-5792 code `5710`. An explicitly empty array is rejected rather than treated as an omission.
:::

## Gas pricing

[ERC-7769](https://eips.ethereum.org/EIPS/eip-7769) standardises the ERC-4337 JSON-RPC surface and deliberately leaves gas price out of it, so the right value is **bundler policy, not chain state** — and there is no vendor-neutral way to compute it. Pimlico serves a tiered oracle; Alchemy's Rundler expects a hand-assembled base fee plus a per-chain-buffered priority fee.

**You usually need to do nothing.** By default the wrapper asks the bundler you configured:

1. `pimlico_getUserOperationGasPrice` against that chain's `bundlerUrl`, taking the `fast` tier.
2. If the bundler doesn't implement that method, an EIP-1559 estimate from the chain, **doubled** — the same buffer viem applies for a bundler it knows nothing about.

Only an *unsupported-method* reply falls through to step 2. A real failure — auth, rate limit, network — propagates instead, because pricing against a bundler that is refusing to talk produces a submission rejected for an unactionable reason.

**Override it when your bundler prescribes something else**, notably Rundler, whose prechecks the generic estimate does not satisfy. The hook receives the bundle's chain id:

```tsx
estimateUserOperationFees: async (chainId) =>
  // e.g. Pimlico: (await pimlicoClient.getUserOperationGasPrice()).fast
  getYourBundlerGasPrice(chainId),

// @error: Never return 0n. Sponsorship covers the cost, not the gas-price fields.
```

`maxFeePerGas` is a **refundable ceiling** — a paymaster or refund covers the difference — so returning the fast tier never overcharges.

:::warning
Even paymaster-sponsored operations need **real, non-zero** fees. Sponsorship covers the cost, not the gas-price fields, so never return `0n`. A stale cached price below the bundler's floor fails the same way.
:::

## Chain reads

The wrapper makes three chain reads to send a payment: the EOA's **pending nonce** for the authorization, **`eth_getCode`** for delegation status, and the EntryPoint's **`getNonce`** per UserOperation. All three are ordinary node methods. A delegation read as ours is kept, so one read serves the capability probe, the warm-up and the send alike, until a send fails and puts it in doubt. An account that is not delegated yet is read again at each send, since the delegation may land in between, and with [`foreignDelegations`](#a-wallet-delegated-elsewhere) set every send reads, as does the probe. Either way the clients behind it are built once and kept, and viem is told not to check the account's deployment on its own. `wallet_getCapabilities` answers for each chain it is asked about, and `@yodlpay/sdk-core` asks about the payment's chain only. Two more come after a send, and only when something has gone wrong: the EntryPoint's `UserOperationEvent` logs for an operation's hash in the last 1,000 blocks, with the receipt of the transaction that carried it, when the bundler cannot say what became of the operation; and `getNonce` for the account's current nonce key when the bundler refuses a send with `AA25`.

Omit `rpcUrl` and they go over the wrapped wallet's own EIP-1193 transport. The wallet is already connected to a node, so there is no second endpoint to configure or to drift out of step with the wallet mid-payment. Supply `rpcUrl` when you'd rather not depend on the wallet vendor's node — it always takes precedence.

Two things to know if you omit it:

* **EIP-1193 carries no per-request chain.** Reads land on whichever chain the wallet is currently on, so the wrapper checks the wallet's `eth_chainId` against the bundle's chain once per bundle and refuses on a mismatch with `-32602`. Switch the wallet before sending — which any EIP-5792 caller already does. The two reads after a send check `eth_chainId` again and skip a wallet that has moved to another network rather than read that network. A skipped read counts as no answer, so an operation the bundler cannot report on stays pending until the wallet is back.
* **`blockTag: 'pending'` is the one read a wallet may refuse.** A wallet that method-whitelists its node proxy can reject it. That fails loudly and names `rpcUrl`, rather than retrying at `latest` — which could sign an authorization for an already-used nonce.

## Authorization signing

There is **no universal** way to sign an EIP-7702 authorization across wallets. Some expose `wallet_signAuthorization` over EIP-1193, some only a raw signer, and embedded wallets sign through their own SDK. The wrapper doesn't guess — you supply `signAuthorization`, receive the unsigned request, and return the signed authorization however your wallet does it.

**Try the built-in helper first.** For wallets that sign over EIP-1193:

```tsx
import { signAuthorizationViaProvider, withYodl7702 } from '@yodlpay/react-native-eip-7702-provider';

const provider = withYodl7702(walletProvider, {
  signAuthorization: signAuthorizationViaProvider(walletProvider),
});
```

**If the wallet doesn't sign over EIP-1193**, sign through its SDK instead:

```tsx
signAuthorization: ({ account, chainId, contractAddress, nonce }) =>
  yourWallet.signAuthorization({ address: account, chainId, contractAddress, nonce }),
```

The wrapper always fetches the `nonce` itself — over the chain's `rpcUrl` if one is configured, otherwise over the wallet's own transport — so the authorization can't go stale. You only sign.

### Picking a signing helper

`signAuthorizationViaProvider` is a **discovery convenience**. It tries `wallet_signAuthorization`, then falls back to raw `secp256k1_sign` — but only when the first is genuinely unsupported. Real errors such as user rejection surface as-is and are never masked.

Once you know which method your wallet uses, prefer the single-strategy primitive so failures stay precise:

| Helper | Method |
| --- | --- |
| `signAuthorizationViaWalletRpc(provider)` | `wallet_signAuthorization` only |
| `signAuthorizationViaSecp256k1(provider)` | raw `secp256k1_sign` only |
| `signAuthorizationViaProvider(provider)` | the former, falling back to the latter on unsupported-method |

### A wallet delegated elsewhere

An EOA carries one EIP-7702 delegation at a time. When another app has already delegated the payer's account to its own implementation (Privy's native gas sponsorship, for example, delegates to ZeroDev Kernel), the wrapper reads that delegate on each send and does what `foreignDelegations` says for it:

| `foreignDelegations[delegate]` | What happens | Sponsored by | Atomic |
| --- | --- | --- | --- |
| a provider of yours | **Theirs.** The bundle goes to that EIP-5792 provider as it stands: same `from`, calls and capabilities, no builder-code suffix. The id it answers with is what `wallet_getCallsStatus` and `wallet_showCallsStatus` are forwarded under, and `wallet_getCapabilities` for the account is its answer too | whoever sponsors your stack | as it says |
| `'repoint'` | **Re-pointed.** The send asks `signAuthorization` for an authorization to the SimpleAccount implementation, the same request an undelegated account gets, and the payment goes through the sponsored UserOperation path. If that callback rejects, the send fails with its error | Yodl | yes |
| unnamed | **Refused** with EIP-5792 `5760` (atomicity not supported). The Yodl UI then sends the calls one transaction at a time through the wallet. Sending a UserOperation without an authorization would run validation in a contract that has never seen it, and signing one would replace a smart account the payer set up somewhere else | nobody | no |

```tsx
/** The implementations other apps delegate your payers' wallets to. */
const Delegate = {
  // ZeroDev Kernel v3.3, which Privy's native gas sponsorship delegates to.
  KernelV3_3: '0xd6CEDDe84be40893d153Be9d467CD6aD37875b28',
  // Your other app's smart account.
  MyOtherApp: '0x1111111111111111111111111111111111111111',
} as const;

const provider = withYodl7702(walletProvider, {
  signAuthorization: signAuthorizationViaSecp256k1(walletProvider),
  foreignDelegations: {
    // A stack you run: its bundles go to your provider over ZeroDev's SDK.
    [Delegate.KernelV3_3]: kernelProvider,
    // An app of yours: take the delegation and pay through Yodl.
    [Delegate.MyOtherApp]: 'repoint',
  },
});
```

With anything named, the wrapper reads the delegation on every send (one `eth_getCode`), because the other app can take it back between payments. With the option left out, a delegation read as ours is kept and trusted until a send fails, and an account that is not delegated yet is read again at each send. Keys match in any case and checksums aren't required, but one delegate gets one fate: the same address under two spellings is refused at construction, and so is an empty map. Leave the option out for "none".

**A provider of yours** is an EIP-1193 provider that speaks EIP-5792 for those accounts: `wallet_getCapabilities`, `wallet_sendCalls`, `wallet_getCallsStatus` and `wallet_showCallsStatus` (ZeroDev's SDK builds Kernel's UserOperations). The batching, sponsorship and status are yours to provide. This wrapper is the router: a wallet that Privy re-delegates between payments reaches your provider when the delegate is Kernel's and Yodl's sponsored path when it is ours, decided per send. Bundles reach your provider after this wrapper's own checks, so the chain has to be one in `chains`, and a capability the bundle requires has to be `paymasterService` or marked optional. Nothing here warms up, signs or suffixes for them.

**`'repoint'`** is the choice for an app that is yours too, or one you have agreed to alternate with, when you want Yodl's sponsorship. The address is the consent that names the other app.

* An authorization is attached only when the delegate isn't ours: [ERC-7769](https://eips.ethereum.org/EIPS/eip-7769) provides the tuple to change a delegation, not to repeat it on every operation.
* While this delegation is in place, the other implementation's modules, session keys and ERC-1271 signatures don't apply. That app needs an authorization of its own to take the delegation back.
* A wallet policy that restricts which contracts it signs authorizations for (Privy has one) must allow `0xe6Cae83BdE06E4c305530e199D7217f42808555B`.

The wrapper refuses a contract deployed at the payer's address either way (`5760`), because no authorization can replace contract code.
