Supported requests
Portal enforces the key whether the transaction is signed on the device or through the Enclave MPC API (
useEnclaveMPCApi).
See Requests that don’t take a key for everything else.
Import the helpers
The idempotency helpers are exported by@portal-hq/utils, which @portal-hq/core depends on. @portal-hq/core doesn’t re-export them.
package.json, add @portal-hq/utils at the same version as @portal-hq/core.
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 key must be a string. The SDK trims surrounding whitespace, and the key must then follow these rules:
A key that breaks these rules throws a
PortalIdempotencyError with reason set to 'invalidKey' before the approval step, so nothing is signed or sent. The error message names the rule that failed and never contains the key.
Check for this error with PortalIdempotencyError.is(error), not instanceof. The error is identified by a cross-copy brand, so instanceof returns false when your dependency tree resolves two copies of @portal-hq/utils.
Send a transaction with a key
Build the transaction once, then send it with the key. Keep the transaction object together with the key, because a retry must send the same transaction:PortalRequestMethod.SolSignAndSendTransaction or PortalRequestMethod.SolSignAndConfirmTransaction and a solana: chain ID. You can combine idempotencyKey with the other RequestOptions, such as signatureApprovalMemo.
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 method, chain ID, transaction object and 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 instead of broadcasting again.
chainId, method and params match the first request’s, and it compares params as the JSON string the SDK sends. Retry with the same transaction object: the same fields in a different order serialize differently, and the retry can be rejected with IDEMPOTENCY_KEY_REUSED.
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
Portal’s rejections are thrown as aPortalMpcError whose id is one of the IDs below. Use these helpers from @portal-hq/utils to recognize them:
Branch on
id to decide what to do next. Each ID is also available as a constant on PortalIdempotencyErrorId:
Match rejections with
isIdempotencyRejection() or 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 recognizes a retry only when the rebuilt transaction matches the first one:
- EVM: the build carries no nonce or gas, so with unchanged inputs the rebuilt transaction 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 usually rejected with
IDEMPOTENCY_KEY_REUSED, which doesn’t tell you whether the first transfer is in progress, completed or failed.
- Solana: wait until the first transfer is confirmed or its recent blockhash has expired, usually within about 60–90 seconds after the transaction was built.
- EVM: wait until the first transfer is confirmed or the account’s nonce has moved past it.
Requests that don’t take a key
The Enclave MPC API also accepts keys for Tron and Stellar broadcasts. The React Native SDK doesn’t take a key for them:
sendAsset doesn’t support Tron or Stellar chains, and a key passed to portal.request with any method not listed under Supported requests is ignored with a warning.
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), the presignature attempt carries the same key as the request. Presignatures apply only to SECP256K1 signing, so this affects EVM sends.
- If Portal rejects the presignature attempt because of its key, the SDK throws that rejection and doesn’t fall back to a normal sign.
- If the presignature attempt fails for another reason, the SDK falls back to one normal sign with the same key and request. If the failed attempt already reached Portal, Portal can reject the fallback, for example with
IDEMPOTENT_REQUEST_IN_PROGRESS, where a request without a key would have been signed. The SDK then logs a warning that the presignature attempt may already have used the key.
Next Steps
- Learn about signing transactions
- Read Send tokens for the full
sendAssetflow - Review
RequestOptionsandSendAssetParamsin the@portal-hq/corereference - See Idempotency keys for the Enclave MPC API