Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt

Use this file to discover all available pages before exploring further.

Presignatures let you front-load part of the MPC computation so that subsequent signing completes faster. For a general overview, see the Presignatures resource. This guide walks through creating a presignature and using it with the sign, raw sign, and send asset endpoints. The Enclave MPC API supports two presignature storage modes:
ModeHow you create itWhat you storeHow you sign
Portal-managedSet managed: trueThe returned idPass id as presignatureId
Client-storedOmit managed or set managed: falseThe opaque data blob returned by the APIPass data as presignature
Portal-managed presignatures are optional. They are useful when you want Portal to store the encrypted client-side presignature payload so your application only needs to track presignature IDs and expiration times.

Prerequisites

Before starting, make sure you have:
  1. Created a Portal client
  2. Created a wallet and have access to the share data

Step 1: Create a Presignature

Call the presign endpoint with your share and choose whether the presignature should be client-stored or Portal-managed.
Presignatures currently only support the SECP256K1 curve (EVM, Bitcoin). ED25519 (Solana) support is coming soon.

Option 1: Portal-Managed

To let Portal store the encrypted client presignature payload, set managed to true.
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/presign/SECP256K1 \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000' \
  --data '{
    "share": "<SECP256K1_SHARE>",
    "managed": true
  }'
Headers:
HeaderRequiredDescription
Idempotency-KeyNoA caller-provided key for safe retries. If creation succeeds but the response is lost, retrying with the same key returns the same {id, expiresAt} result without redoing the work. See idempotency behavior below.
Request body:
FieldTypeRequiredDescription
sharestringYesThe JSON-serialized MPC share for the wallet.
managedbooleanNoSet to true to create a Portal-managed presignature. Defaults to false.
expirationTsnumberNoUnix timestamp for when the presignature expires. Defaults to 7 days. Max 365 days.
Response:
{
  "id": "<UUID>",
  "expiresAt": "2025-03-18T10:00:00Z"
}
The response does not include data. Store the id and pass it as presignatureId when signing.
For presignature creation, idempotency currently applies only to Portal-managed presignatures. It is not currently available for client-stored presignatures.

Idempotency Behavior

Use a fresh Idempotency-Key (typically a random UUID) per presign request. The key should only be reused when you are retrying the same request after a network failure or lost response. In that case, the API distinguishes a few outcomes:
SituationResponse
Original request already completed successfully200 OK with the same {id, expiresAt} — no new work is done.
Original request is still in progress409 Conflict with a Retry-After header. Retry after the suggested delay.
Original request failed before completing409 Conflict without Retry-After. Retry with a new idempotency key.
Key reused after the wallet’s signing share changed422 Unprocessable Entity. This typically indicates the wallet was re-shared or recovered between the two calls, so the retry is no longer the same logical request. Use a new idempotency key.

Security Properties

Storing the presignature on Portal’s side does not let Portal sign on your behalf. The payload is encrypted inside the enclave with a key derived from your MPC share and the presignature ID. Your share never leaves the enclave, so Portal infrastructure only ever sees ciphertext. When you sign with presignatureId, the enclave fetches the encrypted payload and decrypts it using the share from the signing request. If that share differs from the one used to create the presignature, decryption fails and the API returns 404. Portal-managed presignatures are single-use and subject to the usual expiration. Once a presignature has been used to sign, it cannot be reused.

Option 2: Client-Stored

Omit managed (or set it to false) to create a client-stored presignature. The response includes an opaque data blob that you store and pass back when signing.
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/presign/SECP256K1 \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --data '{
    "share": "<SECP256K1_SHARE>"
  }'
Request body:
FieldTypeRequiredDescription
sharestringYesThe JSON-serialized MPC share for the wallet.
expirationTsnumberNoUnix timestamp for when the presignature expires. Defaults to 7 days. Max 365 days.
Response:
{
  "id": "<UUID>",
  "expiresAt": "2025-03-18T10:00:00Z",
  "data": "<PRESIGNATURE_DATA>"
}
FieldTypeDescription
idstringUnique identifier for the presignature.
expiresAtstringRFC 3339 expiration timestamp.
datastringOpaque presignature payload. Pass this as the presignature field in signing requests.
Store the data value from the response — you will pass it as the presignature field in the next step. It is also recommended to store the id and expiresAt values for bookkeeping purposes. The id may be needed when communicating with Portal support.

Step 2: Use the Presignature

Pass either:
  • presignature: the data value from a client-stored presignature response
  • presignatureId: the id value from a Portal-managed presignature response
These fields are mutually exclusive.

Raw Sign

Sign an arbitrary hex digest string with a presignature.
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/raw/sign/SECP256K1 \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --data '{
    "params": "<HEX_DIGEST_STRING>",
    "presignature": "<PRESIGNATURE_DATA>",
    "share": "<SECP256K1_SHARE>"
  }'
FieldTypeRequiredDescription
sharestringYesThe JSON-serialized MPC share.
paramsstringYesHex digest string to sign, without a 0x prefix.
presignaturestringNoThe data value from a client-stored presign response.
presignatureIdstringNoThe id value from a Portal-managed presign response. Mutually exclusive with presignature.
signingSchemestringNo"cggmp" (default) or "frost".
Presignatures are currently available for SECP256K1 only. The curve in the URL path (/v1/raw/sign/{curve}) must match the curve used when creating the presignature — passing presignature or presignatureId to /v1/raw/sign/ED25519 returns an error.
For a Portal-managed presignature, pass presignatureId instead:
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/raw/sign/SECP256K1 \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --data '{
    "params": "<HEX_DIGEST_STRING>",
    "presignatureId": "<UUID>",
    "share": "<SECP256K1_SHARE>"
  }'

Sign (RPC-based)

Use a presignature with the RPC-based sign endpoint for blockchain transactions.
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --data '{
    "share": "<SECP256K1_SHARE>",
    "presignature": "<PRESIGNATURE_DATA>",
    "method": "eth_sendTransaction",
    "params": {"value": "0x01", "from": "YOUR_ETH_ADDRESS", "to": "RECIPIENT_ADDRESS", "data": ""},
    "rpcUrl": "YOUR_RPC_URL",
    "chainId": "eip155:10143"
  }'
FieldTypeRequiredDescription
sharestringYesThe JSON-serialized MPC share.
presignaturestringNoThe data value from a client-stored presign response. Omit for standard (non-presigned) signing.
presignatureIdstringNoThe id value from a Portal-managed presign response. Mutually exclusive with presignature.
methodstringYesRPC method (e.g., eth_sendTransaction, personal_sign).
paramsstring/object/arrayYesRPC parameters for the transaction.
rpcUrlstringYesRPC endpoint URL.
chainIdstringYesCAIP-2 chain ID (e.g., eip155:10143).
For a Portal-managed presignature, replace presignature with presignatureId:
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/sign \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --data '{
    "share": "<SECP256K1_SHARE>",
    "presignatureId": "<UUID>",
    "method": "eth_sendTransaction",
    "params": {"value": "0x01", "from": "YOUR_ETH_ADDRESS", "to": "RECIPIENT_ADDRESS", "data": ""},
    "rpcUrl": "YOUR_RPC_URL",
    "chainId": "eip155:10143"
  }'

Send Assets

Use a presignature with the high-level send assets endpoint.
curl --request POST \
  --url https://mpc-client.portalhq.io/v1/assets/send \
  --header 'Authorization: Bearer <clientApiKey>' \
  --header 'Content-Type: application/json' \
  --data '{
    "share": "<SECP256K1_SHARE>",
    "presignature": "<PRESIGNATURE_DATA>",
    "rpcUrl": "https://api.portalhq.io/rpc/v1/eip155/10143",
    "chain": "monad-testnet",
    "to": "0xDestinationAddress",
    "token": "NATIVE",
    "amount": "0.0001"
  }'
FieldTypeRequiredDescription
sharestringYesThe JSON-serialized MPC share.
presignaturestringNoThe data value from a client-stored presign response.
presignatureIdstringNoThe id value from a Portal-managed presign response. Mutually exclusive with presignature.
chainstringYesChain name or CAIP-2 ID (e.g., monad-testnet).
tostringYesRecipient address.
tokenstringYesToken identifier or "NATIVE" for native transfers.
amountstringYesAmount as a numeric string.
rpcUrlstringNoRPC endpoint URL.
For a Portal-managed presignature, replace presignature with presignatureId.

List and Delete Presignatures

Use the list endpoint to see active presignatures for the client API key. The response includes both Portal-managed and client-stored presignatures.
curl --request GET \
  --url https://mpc-client.portalhq.io/v1/presignatures \
  --header 'Authorization: Bearer <clientApiKey>'
{
  "presignatures": [
    {
      "id": "<UUID>",
      "expiresAt": "2025-03-18T10:00:00Z",
      "curve": "SECP256K1",
      "managed": true
    }
  ]
}
Delete a presignature when you no longer need it:
curl --request DELETE \
  --url https://mpc-client.portalhq.io/v1/presignatures/<UUID> \
  --header 'Authorization: Bearer <clientApiKey>'
On success, the API returns 204 No Content with an empty body. After deletion, the presignature can no longer be used for signing — for a client-stored presignature, the locally stored data blob for that ID becomes unusable.

Important Notes

  • Single-use: Each presignature can only be used once. After signing, you must create a new one.
  • Active limit: You can have up to 100 active presignatures per client. Set up a regular process to discard expired presignatures.
  • Curve matching: For raw sign, the presignature’s curve must match the URL path curve.
  • Expiration: Presignatures expire after their expiresAt timestamp. The default is 7 days; the maximum is 365 days.
  • Portal-managed signing: presignatureId only works for Portal-managed presignatures and requires the same MPC share used to create the presignature.

Additional Resources