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:
| Mode | How you create it | What you store | How you sign |
|---|
| Portal-managed | Set managed: true | The returned id | Pass id as presignatureId |
| Client-stored | Omit managed or set managed: false | The opaque data blob returned by the API | Pass 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:
- Created a Portal client
- 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:
| Header | Required | Description |
|---|
Idempotency-Key | No | A 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:
| Field | Type | Required | Description |
|---|
share | string | Yes | The JSON-serialized MPC share for the wallet. |
managed | boolean | No | Set to true to create a Portal-managed presignature. Defaults to false. |
expirationTs | number | No | Unix 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:
| Situation | Response |
|---|
| Original request already completed successfully | 200 OK with the same {id, expiresAt} — no new work is done. |
| Original request is still in progress | 409 Conflict with a Retry-After header. Retry after the suggested delay. |
| Original request failed before completing | 409 Conflict without Retry-After. Retry with a new idempotency key. |
| Key reused after the wallet’s signing share changed | 422 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:
| Field | Type | Required | Description |
|---|
share | string | Yes | The JSON-serialized MPC share for the wallet. |
expirationTs | number | No | Unix 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>"
}
| Field | Type | Description |
|---|
id | string | Unique identifier for the presignature. |
expiresAt | string | RFC 3339 expiration timestamp. |
data | string | Opaque 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>"
}'
| Field | Type | Required | Description |
|---|
share | string | Yes | The JSON-serialized MPC share. |
params | string | Yes | Hex digest string to sign, without a 0x prefix. |
presignature | string | No | The data value from a client-stored presign response. |
presignatureId | string | No | The id value from a Portal-managed presign response. Mutually exclusive with presignature. |
signingScheme | string | No | "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"
}'
| Field | Type | Required | Description |
|---|
share | string | Yes | The JSON-serialized MPC share. |
presignature | string | No | The data value from a client-stored presign response. Omit for standard (non-presigned) signing. |
presignatureId | string | No | The id value from a Portal-managed presign response. Mutually exclusive with presignature. |
method | string | Yes | RPC method (e.g., eth_sendTransaction, personal_sign). |
params | string/object/array | Yes | RPC parameters for the transaction. |
rpcUrl | string | Yes | RPC endpoint URL. |
chainId | string | Yes | CAIP-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"
}'
| Field | Type | Required | Description |
|---|
share | string | Yes | The JSON-serialized MPC share. |
presignature | string | No | The data value from a client-stored presign response. |
presignatureId | string | No | The id value from a Portal-managed presign response. Mutually exclusive with presignature. |
chain | string | Yes | Chain name or CAIP-2 ID (e.g., monad-testnet). |
to | string | Yes | Recipient address. |
token | string | Yes | Token identifier or "NATIVE" for native transfers. |
amount | string | Yes | Amount as a numeric string. |
rpcUrl | string | No | RPC 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