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

# Smart Wallets

> Submit prepared Amplify transactions from smart-contract wallets (ERC-4337, Privy, Dynamic) and server-side signers.

`@paxoslabs/amplify-sdk@1.0.0` is wallet-agnostic. Every `prepare` / `cancel` method returns

```ts theme={null}
import type { Address, Hex } from 'viem'

{
  transaction: {
    to: Address
    data: Hex
    value: string // decimal, usually "0"
    // optional, only with responseFormat: 'full' | 'structured'
    abi?: unknown
    functionName?: string
    args?: unknown[]
  }
}
```

The SDK does not bundle, sign, or submit anything. It just produces calldata. That means you can submit Amplify transactions from any wallet that can issue a contract call — EOAs via wagmi/viem, smart-contract accounts via Privy or Dynamic, ERC-4337 bundlers via Alchemy or Pimlico, and server-side `walletClient` signers.

<Info>
  If you arrived from the v0.5.x docs, the SDK no longer exposes
  smart-wallet-specific helpers, auth-method enums, or type guards. The flow
  is now: call `authorization.detect`, branch on `auth.method`, then submit
  `prepared.transaction`. See
  [Migrating from 0.5](/v1.0.0/intro/products/earn/developers/migrating-from-0-5)
  for the full mapping.
</Info>

## What the SDK gives you

`prepared.transaction` is the same shape regardless of vault, chain, or wallet:

```ts theme={null}
import { AmplifyClient } from '@paxoslabs/amplify-sdk'

const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})

const prepared = await client.amplify.deposit.prepare({
  vaultAddress: '0xbbbb000000000000000000000000000000000001',
  depositAsset: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
  depositAmount: '1000000',
  userAddress: '0xUserOrSmartAccount',
  chainId: 1,
})

// prepared.transaction.{to,data,value} — submit from any wallet
```

Your job is to route `{ to, data, value }` to whichever sender the connected wallet exposes. The rest of this guide shows that routing for the wallet types Amplify integrators most often use.

## `userAddress` vs. `to`

Two address fields appear on most `prepare` requests:

| Field         | Meaning                                                                                                  |
| ------------- | -------------------------------------------------------------------------------------------------------- |
| `userAddress` | The account that submits the transaction. For EOAs, the signer. For AA, the smart-account address.       |
| `to`          | (Optional) The address that receives the resulting shares (deposits) or assets (withdrawals settlement). |

Set `to` explicitly when a session key submits the transaction but the smart account should own the shares:

```ts theme={null}
const prepared = await client.amplify.deposit.prepare({
  vaultAddress,
  depositAsset,
  depositAmount,
  userAddress: sessionKeyAddress, // submits the tx
  to: smartAccountAddress,        // owns the shares
  chainId,
})
```

Withdrawals expose the same idea via `intendedDepositor`, `receiver`, and `refundReceiver` — pass each explicitly when the submitter and the share/asset owner differ. See the [Withdrawals guide](/v1.0.0/intro/products/earn/developers/guides/withdrawals).

## Permit vs. approval on smart wallets

For deposits you first call `client.core.authorization.detect(...)`. It returns one of three shapes:

```ts theme={null}
import type { Hex } from 'viem'

type AuthorizationResponseDto =
  | { method: 'permit'; permitData: { domain; types; value; deadline } }
  | { method: 'approval'; approvalTransaction: { encoded: Hex; /* … */ } }
  | { method: 'already_approved' }
```

* **Smart wallets that expose `signTypedData` (EIP-712)** — most Privy embedded smart wallets, Dynamic embedded wallets, and Safe — can sign EIP-2612 permits. Treat `method: 'permit'` identically to an EOA: sign the typed data, pass `permitSignature` + `permitDeadline` to `prepare`, and submit one transaction.
* **Smart wallets without typed-data signing** (some session-key flows, restricted policy-engine accounts) cannot produce a permit. Fall back to the approval path: submit `approvalTransaction.encoded` to the deposit asset, wait for confirmation, then call `prepare` *without* `permitSignature` / `permitDeadline`.

A defensive client handles all three branches:

```ts theme={null}
const auth = await client.core.authorization.detect({
  vaultAddress,
  tokenAddress: depositAsset,
  amount: depositAmount,
  userAddress,
  chainId,
})

let permitSignature: Hex | undefined
let permitDeadline: number | undefined

if (auth.method === 'permit' && supportsTypedData(wallet)) {
  permitSignature = await wallet.signTypedData({
    account: userAddress,
    domain: auth.permitData.domain,
    types: auth.permitData.types,
    primaryType: 'Permit',
    message: auth.permitData.value,
  })
  permitDeadline = Number(auth.permitData.deadline)
} else if (auth.method === 'approval' || auth.method === 'permit') {
  // Submit approval — either because the token requires it, or because
  // this wallet can't sign EIP-712.
  const approvalTx =
    auth.method === 'approval'
      ? auth.approvalTransaction
      : await fallbackApprovalFor(depositAsset, vaultAddress, depositAmount)
  const hash = await wallet.sendTransaction({
    to: depositAsset,
    data: approvalTx.encoded,
    chainId,
  })
  await publicClient.waitForTransactionReceipt({ hash })
}
// method === 'already_approved' → nothing to do

const prepared = await client.amplify.deposit.prepare({
  vaultAddress,
  depositAsset,
  depositAmount,
  userAddress,
  chainId,
  ...(permitSignature ? { permitSignature, permitDeadline } : {}),
})
```

The same `auth.method === 'approval'` path applies to withdrawals — except `tokenAddress` is the **share token** (`vaultAddress`), and the only valid response shapes are `approval` or `already_approved`.

## Pattern 1 — Privy / Dynamic smart wallets (UserOps under the hood)

Both Privy's `useSmartWallets` and Dynamic's embedded smart-account adapter expose a `sendTransaction`-style API that internally builds a UserOperation, sends it to a bundler, and resolves with a hash. From the SDK's perspective they are identical — just pass `prepared.transaction.{to,data,value}` through.

```tsx theme={null}
// pseudo-code; consult Privy / Dynamic docs for the exact hook shape
import { useSmartWallets } from '@privy-io/react-auth/smart-wallets'
import { AmplifyClient } from '@paxoslabs/amplify-sdk'
import type { Address, Hex } from 'viem'

const client = new AmplifyClient({ apiKey })

function useAmplifyDeposit() {
  const { client: smartWallet } = useSmartWallets()

  return async function deposit(params: {
    vaultAddress: Address
    depositAsset: Address
    depositAmount: string
    smartAccountAddress: Address
    chainId: number
  }) {
    const auth = await client.core.authorization.detect({
      vaultAddress: params.vaultAddress,
      tokenAddress: params.depositAsset,
      amount: params.depositAmount,
      userAddress: params.smartAccountAddress,
      chainId: params.chainId,
    })

    const calls: { to: Address; data: Hex; value?: bigint }[] = []

    if (auth.method === 'approval') {
      calls.push({
        to: params.depositAsset,
        data: auth.approvalTransaction.encoded as Hex,
      })
    }

    const prepared = await client.amplify.deposit.prepare({
      vaultAddress: params.vaultAddress,
      depositAsset: params.depositAsset,
      depositAmount: params.depositAmount,
      userAddress: params.smartAccountAddress,
      chainId: params.chainId,
      // No permitSignature — smart wallet is using the approval path.
    })

    calls.push({
      to: prepared.transaction.to as Address,
      data: prepared.transaction.data as Hex,
      value: BigInt(prepared.transaction.value),
    })

    // One user confirmation, one UserOperation — both calls bundle together.
    return smartWallet.sendTransaction({ calls })
  }
}
```

<Note>
  The hook signatures, `sendTransaction` parameters, and how gas sponsorship is
  configured are dictated by Privy and Dynamic — not by the Amplify SDK. The
  code above is illustrative; consult the wallet provider's docs for the
  authoritative API.
</Note>

If the asset supports permit **and** the smart wallet supports `signTypedData`, you can collapse to a single call by signing the permit first and skipping the approval step entirely.

## Pattern 2 — ERC-4337 bundlers (Alchemy, Pimlico, Biconomy)

Native ERC-4337 SDKs expose batched submission as `sendTransactions({ requests })` or `sendUserOperation`. The response is a `userOpHash`, not a transaction hash:

```ts theme={null}
import type { Address, Hex } from 'viem'

const userOpHash = await smartAccountClient.sendTransactions({
  requests: [
    auth.method === 'approval' && {
      to: depositAsset,
      data: auth.approvalTransaction.encoded as Hex,
    },
    {
      to: prepared.transaction.to as Address,
      data: prepared.transaction.data as Hex,
      value: BigInt(prepared.transaction.value),
    },
  ].filter(Boolean) as { to: Address; data: Hex; value?: bigint }[],
})

const receipt = await smartAccountClient.waitForUserOperationReceipt({
  hash: userOpHash,
})
const txHash = receipt.receipt.transactionHash
```

Two things to remember:

1. **Do not** use wagmi's `useWaitForTransactionReceipt` with a `userOpHash` — poll the bundler instead (`waitForUserOperationReceipt`).
2. Gas sponsorship is configured on the smart-account client (paymaster), not on the prepared transaction. The SDK never sets `gasPrice` or `maxFeePerGas`.

## Pattern 3 — viem `walletClient` (EOA or AA exposed as JSON-RPC)

For non-AA wallets and for AA wallets exposed through a viem `walletClient` (Safe via `safe-apps-sdk`, MetaMask Smart Account, etc.), submit directly:

```ts theme={null}
import type { Address, Hex } from 'viem'
import { createWalletClient, custom, parseEther } from 'viem'
import { mainnet } from 'viem/chains'

const walletClient = createWalletClient({
  account: userAddress,
  chain: mainnet,
  transport: custom(window.ethereum),
})

const prepared = await client.amplify.deposit.prepare({ /* ... */ })

const hash = await walletClient.sendTransaction({
  to: prepared.transaction.to as Address,
  data: prepared.transaction.data as Hex,
  value: BigInt(prepared.transaction.value),
  chain: mainnet,
})
```

Permit signing uses the same client:

```ts theme={null}
const signature = await walletClient.signTypedData({
  account: userAddress,
  domain: auth.permitData.domain,
  types: auth.permitData.types,
  primaryType: 'Permit',
  message: auth.permitData.value,
})
```

## Pattern 4 — Server-side signers

When the signer lives on your backend (custodial flows, automation), the same `walletClient` works with an in-memory account:

```ts theme={null}
import type { Address, Hex } from 'viem'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { mainnet } from 'viem/chains'
import { AmplifyClient } from '@paxoslabs/amplify-sdk'

const account = privateKeyToAccount(process.env.SIGNER_PRIVATE_KEY as Hex)
const walletClient = createWalletClient({
  account,
  chain: mainnet,
  transport: http(process.env.RPC_URL),
})

const client = new AmplifyClient({
  apiKey: process.env.PAXOS_LABS_API_KEY!,
})

const prepared = await client.amplify.deposit.prepare({
  vaultAddress,
  depositAsset,
  depositAmount,
  userAddress: account.address,
  chainId: mainnet.id,
})

const hash = await walletClient.sendTransaction({
  to: prepared.transaction.to as Address,
  data: prepared.transaction.data as Hex,
  value: BigInt(prepared.transaction.value),
})
```

<Warning>
  Server-side keys must never be sent to a browser. Keep the signer behind a
  trusted endpoint and surface only `prepared.transaction` to clients that need
  to display previews.
</Warning>

## Error handling

Smart-wallet submission can fail in three distinct layers — handle each:

```ts theme={null}
import { AmplifyError, AmplifyTimeoutError } from '@paxoslabs/amplify-sdk'

try {
  const prepared = await client.amplify.deposit.prepare({ /* ... */ })
  await smartWallet.sendTransaction({ /* prepared.transaction */ })
} catch (err) {
  if (err instanceof AmplifyTimeoutError) {
    // SDK couldn't reach the backend in time. Retry or surface a UI hint.
  } else if (err instanceof AmplifyError) {
    // err.statusCode  — backend HTTP status
    // err.body        — parsed error body (when JSON)
    // err.message     — human-readable summary
    // err.rawResponse — raw fetch Response, for debugging
  } else {
    // Wallet / bundler error — UserOp rejected, paymaster denied, etc.
  }
}
```

## Checklist

<AccordionGroup>
  <Accordion title="The wallet supports EIP-712 signTypedData">
    Use the `permit` path. One UserOp / one transaction for the whole deposit.
  </Accordion>

  <Accordion title="The wallet does not support typed-data signing">
    Use the `approval` path. Batch `approve` + `deposit` calls together so the
    user signs once. If the wallet cannot batch (rare for modern AA), submit
    sequentially and wait for the approval receipt before preparing the
    deposit.
  </Accordion>

  <Accordion title="Session keys / delegated signers">
    Pass the session-key address as `userAddress`, and the smart account as
    `to` (deposits) or `intendedDepositor` / `receiver` / `refundReceiver`
    (withdrawals). The session key submits the tx; the smart account owns the
    shares.
  </Accordion>

  <Accordion title="Server-side custodial signers">
    Use a viem `walletClient` with a `privateKeyToAccount`. Keep the key out
    of the browser; expose only the prepared transaction shape to clients.
  </Accordion>
</AccordionGroup>

## Related

* [Deposits guide](/v1.0.0/intro/products/earn/developers/guides/deposits) — full deposit flow with viem + wagmi.
* [Withdrawals guide](/v1.0.0/intro/products/earn/developers/guides/withdrawals) — share-token approval + `submitOrder`.
* [AI Coding Reference](/v1.0.0/intro/products/earn/developers/sdk-ai-reference) — every subclient method.
* [Migrating from 0.5](/v1.0.0/intro/products/earn/developers/migrating-from-0-5) — old smart-wallet helpers and their 1.0.0 equivalents.
