8.1.0, you can attach an idempotency key to a send. Portal broadcasts a key at most once while it remembers the key (at least 24 hours), so a retry with the same key within that time cannot send the same transaction twice.
Idempotency keys are optional. Requests without one behave exactly as before.
Supported requests
Portal enforces the key whether the transaction is signed on the device or through the Enclave MPC API (
useEnclaveMPCApi).
Generate a key
generateIdempotencyKey() returns a lowercase UUID v4. Generate one key per operation, such as one transfer or one payment, and store it with the pending operation before you send. A retry, even one after the app restarts, can then reuse it.
You can also use your own key, for example an ID from your backend. The SDK trims surrounding whitespace, and the key must then follow these rules:
A key that breaks these rules throws
PortalIdempotencyError.invalidKey before the user is asked to approve the request, so nothing is signed or sent.
Send a transaction with a key
Build the transaction once, then send it with the key:portal.buildSolanaTransaction(chainId:params:) and send it with .sol_signAndSendTransaction or .sol_signAndConfirmTransaction the same way.
Retry with the same key
If a send fails and you don’t know whether the transaction went out, for example after a timeout, callportal.request(...) again with the same transaction and the same key. One of two things happens:
- The first attempt never reached Portal, so Portal has no record of the key, and the retry sends the transaction.
- Portal already recorded the key, whether that attempt is still in progress, was broadcast or failed, and it rejects the retry with a
PortalMpcErrorinstead of broadcasting again.
Handle a rejection
PortalMpcError has two helpers for idempotency rejections:
Branch on
id to decide what to do next. Each ID is also available as a constant on PortalIdempotencyErrorId:
Match rejections on
id, not on the error message or code.Retry sendAsset
Pass the key in SendAssetParams:
sendAsset builds the transaction again on every call, and Portal compares a retry with the request it first received under the key before it checks that request’s status:
- EVM: the rebuilt transaction normally matches the first one, because Portal fills in the nonce and gas when it signs. A retry gets the status of the first request, as described in Handle a rejection.
- Solana: each build carries a new recent blockhash, so a retry with the same key is normally rejected with
IDEMPOTENCY_KEY_REUSED, whatever happened to the first attempt.
- Solana: wait until the first transfer is confirmed or its recent blockhash has expired, about 60–90 seconds after the transaction was built.
- EVM: wait until the first transfer is confirmed or the account nonce has moved past it.
Requests that don’t take a key
How long Portal remembers a key
Portal remembers a key for at least 24 hours after the request was last updated, then removes it. Once the key is removed, the same key is treated as a new request. Because the exact removal time isn’t fixed, check the chain before you retry an operation that is older than 24 hours, whatever key you use.Presignatures
If you enable presignatures (usePresignatures in FeatureFlags) and a presignature attempt fails after reaching Portal, the SDK’s fallback sign reuses the key. Portal can reject it, for example with IDEMPOTENT_REQUEST_IN_PROGRESS or IDEMPOTENT_REQUEST_PREVIOUSLY_FAILED, where a request without a key would have been signed. Handle it like any other rejection.
Custom signers
If you pass your ownPortalSignerProtocol signer to PortalProvider, implement the overload that takes the key so your requests are protected:
idempotencyKey arrives trimmed and validated, and is non-nil only for eth_sendTransaction, sol_signAndSendTransaction and sol_signAndConfirmTransaction. Existing signers keep compiling without this overload, but the SDK drops the key for them and logs a warning.
Next Steps
- Review the generateIdempotencyKey reference
- Learn about signing transactions
- Read Send tokens for the full
sendAssetflow - See Idempotency keys for the Enclave MPC API