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

# Transfers

> Prepare, execute, and track native and ERC-20 transfers from a Server Wallet, and handle uncertain submissions correctly.

A transfer is a two-phase operation. You prepare it to get a quote, then execute that quote. Both phases are keyed by an idempotency ID that you own.

Transfers require an active wallet. Prepare after [`createOrRestore`](/wallets/sdk/server/identity-and-credentials) has returned.

## Describe the transfer

```typescript theme={null}
export interface Transfer {
  chainId: number
  to: string
  asset: 'native' | string
  amount: string
}
```

`asset` is either `'native'` or an ERC-20 contract address. `amount` is a decimal string in the asset's base units, and `parseAmount` converts from a human-readable value:

```typescript theme={null}
import { parseAmount } from '@polygonlabs/oms-server-wallet-sdk'

parseAmount('0.001', 18)  // '1000000000000000'
parseAmount('25.50', 6)   // '25500000'
```

`parseAmount` raises `INVALID_AMOUNT` for a negative value, a malformed number, or more fractional digits than the asset has decimals. An unsupported `chainId` raises `UNSUPPORTED_CHAIN`.

## Prepare and execute

```typescript theme={null}
const operationId = crypto.randomUUID()

const prepared = await wallet.prepareTransfer(operationId, {
  chainId: 137,
  to: '0x1111111111111111111111111111111111111111',
  asset: 'native',
  amount: parseAmount('0.001', 18),
})

if (prepared.status === 'quoted') {
  await wallet.executeTransfer(operationId)
}
```

Persist `operationId` before you call `prepareTransfer`, not after. If your process dies between generating the ID and recording it, you cannot reconcile the operation that OMS may already hold.

Confirm the prepared payload against your own authorization rules before executing. `prepareTransfer` produces a quote; it does not move funds.

`executeTransfer` submits the stored quote once. Calling it again reconciles against the existing submission rather than sending a second transfer.

## Sponsorship is required

Server Wallet transfers must be sponsored. When sponsorship is unavailable for the project, chain, or transfer, preparation raises `SPONSORSHIP_REQUIRED` with status `409`.

There is no unsponsored fallback. The SDK never selects a fee option or asks the wallet to pay a fee. See [Gas Sponsorship](/wallets/gas-sponsorship).

## Idempotency

An operation ID contains 8 to 100 characters, limited to letters, digits, underscores, and hyphens. `crypto.randomUUID()` satisfies this.

| Situation              | Result                                                          |
| ---------------------- | --------------------------------------------------------------- |
| Same ID, same input    | Returns the existing operation. No second transfer is prepared. |
| Same ID, changed input | Raises `IDEMPOTENCY_CONFLICT`.                                  |
| Expired quote          | Requires a new ID, and a fresh authorization decision.          |

Reusing an ID is how you retry safely. It never prepares another transfer, so a retry loop around `prepareTransfer` cannot double-spend.

## Track an operation

```typescript theme={null}
const operation = await wallet.getOperation(operationId)
```

`getOperation` returns the persisted operation, or `null` when the ID is unknown. It polls the upstream status for operations that are still pending or uncertain.

| Status       | Meaning                                  |
| ------------ | ---------------------------------------- |
| `preparing`  | Preparation is in progress               |
| `quoted`     | A quote exists and has not been executed |
| `submitting` | Execution is in progress                 |
| `pending`    | Submitted, not yet confirmed             |
| `executed`   | Confirmed                                |
| `failed`     | Terminal failure                         |
| `unknown`    | The result is not yet determined         |

`listOperations()` returns the operations the store holds.

## Never resend an uncertain transfer

`unknown` is a real state, not an error. It is what you get when an execution response was lost in transit, and it does not tell you whether the transfer reached the chain.

Keep polling `getOperation` with the same ID. Do not prepare a replacement transfer because the first response was lost: the original may already be on-chain, and a replacement would send the funds twice. The idempotency rules above exist so that the correct response to uncertainty is to retry the same ID, never to generate a new one.

## Errors

`WalletError` carries a stable `code`, a safe `message`, and an HTTP-oriented `status`. `UpstreamError` extends it with the numeric OMS error code and the RPC method that produced it.

Neither exposes raw ID tokens, private key material, or upstream response bodies, so both are safe to log.

Continue with [swaps](/wallets/sdk/server/swaps) to exchange assets across chains, or [sign and verify](/wallets/sdk/server/sign-and-verify) for signing that submits no transaction.
