> ## 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

> Pre-compute MPC signing data for faster transaction signing with the Enclave MPC API.

Presignatures let you front-load part of the MPC computation so that subsequent signing completes faster. For a general overview, see the [Presignatures resource](/resources/presignatures).

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:

1. [Created a Portal client](/apis/enclave-mpc/guide/create-a-client)
2. [Created a wallet](/apis/enclave-mpc/guide/create-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.

<Note>
  Presignatures currently only support the `SECP256K1` curve (EVM, Bitcoin). ED25519 (Solana) support is coming soon.
</Note>

### Option 1: Portal-Managed

To let Portal store the encrypted client presignature payload, set `managed` to `true`.

```bash theme={null}
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](#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:**

```json theme={null}
{
  "id": "<UUID>",
  "expiresAt": "2025-03-18T10:00:00Z"
}
```

The response does not include `data`. Store the `id` and pass it as `presignatureId` when signing.

<Note>
  For presignature creation, idempotency currently applies only to Portal-managed presignatures. It is not currently available for client-stored presignatures.
</Note>

#### 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.

```bash theme={null}
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:**

```json theme={null}
{
  "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. |

<Note>
  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.
</Note>

## 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.

```bash theme={null}
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"`.                                                              |

<Warning>
  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.
</Warning>

For a Portal-managed presignature, pass `presignatureId` instead:

```bash theme={null}
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.

```bash theme={null}
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`:

```bash theme={null}
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.

```bash theme={null}
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.

```bash theme={null}
curl --request GET \
  --url https://mpc-client.portalhq.io/v1/presignatures \
  --header 'Authorization: Bearer <clientApiKey>'
```

```json theme={null}
{
  "presignatures": [
    {
      "id": "<UUID>",
      "expiresAt": "2025-03-18T10:00:00Z",
      "curve": "SECP256K1",
      "managed": true
    }
  ]
}
```

Delete a presignature when you no longer need it:

```bash theme={null}
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

<Warning>
  * **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.
</Warning>

## Additional Resources

* [Presignatures overview](/resources/presignatures) — General concepts and use cases
* [Send tokens](/apis/enclave-mpc/guide/send-tokens) — Sending tokens without presignatures
* [Sign Ethereum transactions](/apis/enclave-mpc/guide/sign-ethereum-transactions) — Standard EVM signing
* [Concurrent transactions](/apis/enclave-mpc/guide/concurrent-transactions) — Handling multiple transactions
