4.2.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. The SDK never generates a key or retries a request on its own.
Supported requests
The SDK sends the key to Portal in the
Idempotency-Key header, which is exported as PORTAL_IDEMPOTENCY_KEY_HEADER.
EIP-1193 libraries that wrap
portal.provider, such as ethers.js, call request({ method, params }) and can’t pass a key. Send a transaction that needs a key with portal.request(...) or portal.provider.request(...) directly.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 page reloads, 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 a
PortalIdempotencyError with reason: 'invalidKey' before the user is asked to approve the request, so nothing is signed or sent. The SDK checks the key even on a method that would otherwise ignore it, so a malformed key still throws there. The error message names the rule that failed and never contains the key. eth_sendRawTransaction and sol_sendTransaction don’t check the key: they reject any key with reason: 'unsupportedTarget'.
Send a transaction with a key
Build the transaction once, then send it with the key:portal.buildTransaction for your Solana chain ID and send it with sol_signAndSendTransaction or sol_signAndConfirmTransaction the same way.
If the user doesn’t approve the request, portal.request(...) resolves with undefined and nothing reaches Portal, so the key is not recorded and you can use it again for the same transfer.
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 object 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.
chainId, method and params match the first request. The SDK sends params as a JSON string, so the same fields in a different order count as a different request, and Portal rejects the retry with IDEMPOTENCY_KEY_REUSED. Keep the transaction object you built and send it again, rather than rebuilding it.
Reuse a key only for the identical request. If anything changes, such as the amount, the recipient, the chain or the method, generate a new key.
Handle a rejection
Two helpers identify idempotency rejections. Both take the caught value asunknown and match on its id, never on the message or code:
Branch on
id to decide what to do next. Each ID is also available as a constant on PortalIdempotencyErrorId:
The rejection’s HTTP status (
409 or 422) is not set on the PortalMpcError. Match rejections on id.Retry sendAsset
Pass the key in the sendAsset parameters:
sendAsset builds the transaction again on every call, and Portal recognizes a retry only when the rebuilt transaction matches the first one:
- EVM: the built transaction carries no nonce or gas, so with the same inputs it normally matches. 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.
- EVM: wait until the first transfer is confirmed or the account nonce has moved past it.
Requests that don’t take a key
Warnings are only printed when
logLevel is 'warn' or more verbose. See Logging.
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), the SDK also sends the key when it signs an EVM transaction with a presignature, with the same params as a sign without one. A retry therefore matches the first request whichever way the SDK signs it. When no presignature is available, the SDK signs normally and still sends the key. When a presignature is available but the sign with it fails, the Web SDK doesn’t fall back to a normal sign, so the failed attempt is never sent again automatically. Retry it yourself with the same key.
Next Steps
- Review the idempotency API reference
- Learn about signing transactions
- Read Send tokens for the full
sendAssetflow - See Idempotency keys for the Enclave MPC API