Skip to main content
When broadcasting transactions through Portal’s Enclave MPC API, network failures, timeouts, or client-side retries can result in duplicate operations. Idempotency keys provide a mechanism to safely retry requests without the risk of processing the same operation twice.

Supported endpoints

Idempotency keys are supported on the following Enclave MPC API endpoints:
Idempotency keys are optional. If the Idempotency-Key header is omitted, the request is processed normally without idempotency enforcement.

Using the iOS, Android and Flutter SDKs

The iOS SDK (8.1.0 and later), the Android SDK (10.1.0 and later) and the Flutter SDK (2.1.0 and later) send the key for you. On iOS and Android, pass it in the request options for eth_sendTransaction, sol_signAndSendTransaction and sol_signAndConfirmTransaction, or in the sendAsset parameters on EVM and Solana chains. On Flutter, pass idempotencyKey to request for the same methods, to sendTransaction on EVM chains, or to sendAsset on EVM and Solana chains. Portal enforces it whether the SDK signs on the device or through the Enclave MPC API. See the iOS, Android and Flutter guides.

Broadcast RPC methods

Idempotency keys are only compatible with RPC methods that broadcast transactions.

How it works

  1. Include the Idempotency-Key header with a unique value in your request.
  2. Portal creates a record with status PENDING and processes the request normally.
  3. On a successful broadcast via an RPC provider, the record is updated to BROADCASTED. On failure, it is updated to FAILED.
  4. Any subsequent request with the same idempotency key from the same client is rejected with a 409 — it will not be re-processed, regardless of the original request’s outcome.
If a request stays in PENDING status for more than 10 minutes (for example, due to an unexpected process interruption), it is automatically marked as UNKNOWN.

Expected response handling flow

Follow the expected flow in order to safely retry, handle response codes, and to protect yourself against double spending.
  1. Submit a transaction with a unique idempotency key.
  2. Add retry logic to handle failures and uncertain outcomes: a. if the status code is 409 and the id is IDEMPOTENT_REQUEST_IN_PROGRESS then retry with backoff. b. if the status code is 409 and the id is IDEMPOTENT_REQUEST_ALREADY_COMPLETED then stop retrying. The transaction has been broadcast. c. if the status code is 409 and the id is IDEMPOTENT_REQUEST_PREVIOUSLY_FAILED then stop retrying. The original request failed, or its outcome is unknown (for example, a request marked UNKNOWN after staying PENDING), so it may have been broadcast. Check the chain or your transaction history before re-submitting with a different idempotency key. Re-submitting without checking risks double spending. d. if the status code is 409 and the id is IDEMPOTENT_REQUEST_UNEXPECTED_STATE then stop retrying. The transaction has failed in an unknown state. Follow error handling logic. You may not want to automatically resend without verifying balances first. It is not safe to spend again. You are at risk of double spending. e. if the status code is 422 then stop retrying. You are submitting different transactions with the same idempotency key. Follow error handling logic.

Key format

V4 UUIDs are a good default choice for idempotency keys (e.g. 550e8400-e29b-41d4-a716-446655440000).

Key expiration

Portal keeps an idempotency key for at least 24 hours after the request was last updated, then removes it. Once it is removed, the same key value is treated as a new request, so a retry is protected only while the key is kept. Check the chain before retrying an operation that is older than 24 hours, whatever key you use.

Response behavior

Once a request is made with an idempotency key, that key cannot be reused for any new request while Portal keeps it (at least 24 hours) — even if the original request failed. Generate a new key for each new operation.

Error format

Idempotency errors follow the same structure as other MPC errors documented on the Error Codes page:

Example

Best practices

  • Generate a unique key per request: Use a V4 UUID or another random identifier with enough entropy to avoid collisions.
  • Store the key before sending: Persist the key to your database or logs before making the request, so you can correlate retries.
  • Reuse the same key only for identical retries: If you need to retry the exact same operation with the same request body, reuse the key. If any parameters change, generate a new key.
  • Handle rejection responses gracefully: When you receive a 409 or 422 response, check the id field to determine whether the original request is still processing, already succeeded, or failed.
  • Use distinct keys per logical operation: Each independent transaction or signing operation should have its own unique idempotency key.