openapi: 3.1.0
info:
  title: Portal Enclave MPC API
  version: '1.0'
  description: |
    The Enclave MPC API provides endpoints for MPC wallet generation, signing,
    backup, and recovery. All endpoints require a Client API Key or Client
    Session Token as a Bearer token.

    ## Base URL
    `https://mpc-client.portalhq.io`
servers:
  - url: https://mpc-client.portalhq.io
security:
  - bearerAuth: []
tags:
  - name: MPC Operations
    description: MPC wallet generation, signing, asset transfers, backup, and recovery
paths:
  /v1/generate:
    post:
      operationId: generate
      summary: Create the client's wallet
      description: >
        Generates MPC shares for both SECP256K1 (EVM, Bitcoin, etc.) and ED25519

        (Solana, XRP, etc.) curves (a Portal wallet).


        After storing the MPC shares from this endpoint, confirm storage with
        the

        Client API's update signing share pairs statuses endpoint before
        attempting

        to sign.
      tags:
        - MPC Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Empty object. No parameters required.
            example: {}
      responses:
        '200':
          description: Wallet created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcShareResponse'
              example:
                SECP256K1:
                  share: eyJjbG...
                  id: cm0r6nkek00ej10dwwx8wdjmf
                ED25519:
                  share: eyJjbG...
                  id: cm0r6nk7c00hbzefaq9s93s0x
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: DKG_FAILED
                message: Some message
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
  /v1/assets/send:
    post:
      operationId: sendAssets
      summary: Send the client's assets
      description: >
        Sends a token the client holds to another address. This endpoint builds,

        signs, and submits a transaction to an RPC gateway.


        **Important:** A 200 response only confirms that the transaction was

        received by the RPC gateway — it does not mean the transaction is
        confirmed

        on-chain.
      tags:
        - MPC Operations
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendAssetsRequest'
            example:
              share: eyJjbG...
              chain: ethereum
              to: '0xdFd8302f44727A6348F702fF7B594f127dE3A902'
              token: NATIVE
              amount: '0.0001'
              rpcUrl: https://mainnet.infura.io/v3/YOUR_KEY
      responses:
        '200':
          description: Transaction submitted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendAssetsResponse'
              example:
                transactionHash: >-
                  0x02073df9ba08895d681f80ae45440ba57e766602c97ee6321df5ac81bca900cc
                metadata:
                  amount: '0.01'
                  rawAmount: '10000000000000000'
                  tokenAddress: '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14'
                  tokenDecimals: 18
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: RPC_OP_FAILED
                message: 'Failed to send transaction: details'
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
        '404':
          description: >-
            Portal-managed presignature not found or not decryptable with the
            supplied share
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
        '409':
          description: Idempotent request already completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdempotencyConflict'
        '422':
          description: Idempotency key reused with different payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdempotencyReuse'
  /v1/sign:
    post:
      operationId: sign
      summary: Sign a transaction or message by chain
      description: |
        Signs a transaction or message using an RPC method and chain ID.

        The `Idempotency-Key` header is accepted only for broadcast methods
        (for example `eth_sendTransaction`, `sol_signAndSendTransaction`).
        Supplying the header with a non-broadcast method returns 400.
      tags:
        - MPC Operations
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignRequest'
            example:
              share: eyJjbG...
              method: eth_sendTransaction
              params:
                value: '0x01'
                from: ''
                to: '0xdFd8302f44727A6348F702fF7B594f127dE3A902'
                data: ''
              rpcUrl: https://api.portalhq.io/rpc/v1/eip155/10143
              chainId: eip155:10143
      responses:
        '200':
          description: Signed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignResponse'
              example:
                data: >-
                  0xf7a6a07fa599db56fca50fa1202670b59054e7ed452ea57b3f5b43148b8bdb165beb3a4b0fd532162a1f0fd475de1a9ecd07c95186f7a0856ce1bffa45e3acc91b
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: RPC_OP_FAILED
                message: 'Failed to send transaction: details'
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
        '404':
          description: >-
            Portal-managed presignature not found or not decryptable with the
            supplied share
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
        '409':
          description: Idempotent request already completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdempotencyConflict'
        '422':
          description: Idempotency key reused with different payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdempotencyReuse'
  /v1/raw/sign/{curve}:
    post:
      operationId: rawSign
      summary: Sign a transaction or message by curve
      description: >
        Signs a raw hex digest using the specified elliptic curve.

        The `data` field in the response is a hex string **without** the leading
        `0x`.
      tags:
        - MPC Operations
      parameters:
        - $ref: '#/components/parameters/Curve'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RawSignRequest'
            example:
              params: 7369676e2074686973
              share: eyJjbG...
      responses:
        '200':
          description: Signed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SignResponse'
              example:
                data: >-
                  f7a6a07fa599db56fca50fa1202670b59054e7ed452ea57b3f5b43148b8bdb165beb3a4b0fd532162a1f0fd475de1a9ecd07c95186f7a0856ce1bffa45e3acc91b
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: BAD_REQUEST
                message: Can't parse the request body
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
        '404':
          description: >-
            Portal-managed presignature not found or not decryptable with the
            supplied share
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
  /v1/presign/{curve}:
    post:
      operationId: presign
      summary: Create a presignature
      description: >
        Pre-computes MPC signing data for faster subsequent transaction signing.

        Each presignature can only be used once. You can have up to 100 active

        presignatures per client.


        Presignatures currently only support the `SECP256K1` curve (EVM,
        Bitcoin).

        ED25519 (Solana) support is coming soon.
      tags:
        - MPC Operations
      parameters:
        - $ref: '#/components/parameters/Curve'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PresignRequest'
            examples:
              clientStored:
                summary: Client-stored presignature
                value:
                  share: eyJjbG...
              portalManaged:
                summary: Portal-managed presignature
                value:
                  share: eyJjbG...
                  managed: true
      responses:
        '200':
          description: Presignature created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresignResponse'
              examples:
                clientStored:
                  summary: Client-stored presignature
                  value:
                    id: <UUID>
                    expiresAt: '2025-03-18T10:00:00Z'
                    data: <PRESIGNATURE_DATA>
                portalManaged:
                  summary: Portal-managed presignature
                  value:
                    id: <UUID>
                    expiresAt: '2025-03-18T10:00:00Z'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: BAD_REQUEST
                message: Can't parse the request body
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
        '409':
          description: |
            Portal-managed presign request with this idempotency key is either
            still in progress or previously failed before activation. When the
            original attempt is still in progress, the response includes a
            `Retry-After` header; a 409 without `Retry-After` means the previous
            attempt has failed and the caller should retry with a new
            idempotency key.
          headers:
            Retry-After:
              description: >-
                Seconds to wait before retrying when the original attempt is
                still in progress.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
        '422':
          description: Idempotency key reused with a different signing share pair
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
  /v1/presignatures:
    get:
      operationId: listPresignatures
      summary: List active presignatures
      description: |
        Lists active presignatures for the authenticated client. The response
        includes both client-stored and Portal-managed presignatures. Use the
        `managed` field on each entry to determine whether signing requires the
        opaque `presignature` data blob or the `presignatureId`.
      tags:
        - MPC Operations
      responses:
        '200':
          description: Active presignatures returned successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListPresignaturesResponse'
              example:
                presignatures:
                  - id: <UUID>
                    expiresAt: '2025-03-18T10:00:00Z'
                    curve: SECP256K1
                    managed: true
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
  /v1/presignatures/{id}:
    delete:
      operationId: deletePresignature
      summary: Delete a presignature
      description: |
        Deletes a presignature by ID. After deletion, the presignature can no
        longer be used for signing. For client-stored presignatures, the locally
        stored opaque `data` blob for that ID also becomes unusable.
      tags:
        - MPC Operations
      parameters:
        - $ref: '#/components/parameters/PresignatureId'
      responses:
        '204':
          description: Presignature deleted successfully
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Incorrect API key format
        '404':
          description: Presignature not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: NOT_FOUND
                message: Presignature not found
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
  /v1/backup:
    post:
      operationId: backup
      summary: Back up the client's wallet
      description: |
        Creates backup shares for the wallet. After storing the backup shares,
        confirm storage with the Client API's update backup share pairs statuses
        endpoint before attempting to recover.
      tags:
        - MPC Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BackupRequest'
            example:
              generateResponse: >-
                {"SECP256K1":{"share":"share","id":"shareId"},"ED25519":{"share":"share","id":"shareId"}}
      responses:
        '200':
          description: Backup created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcShareResponse'
              example:
                SECP256K1:
                  share: eyJjbGl...
                  id: clu32l6c8008wuvz52jokyo3r
                ED25519:
                  share: eyJjbGl...
                  id: clu32l9310087evz5u5v9k1ao
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: BAD_REQUEST
                message: Can't parse the request body
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Authorization header is missing
  /v1/recover:
    post:
      operationId: recover
      summary: Recover the client's wallet
      description: >
        Recovers signing shares for the wallet using backup shares. After
        storing

        the recovered MPC shares, confirm storage with the Client API's update

        signing share pairs statuses endpoint before attempting to sign.
      tags:
        - MPC Operations
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecoverRequest'
            example:
              backupResponse: >-
                {"SECP256K1":{"share":"share","id":"shareId"},"ED25519":{"share":"share","id":"shareId"}}
      responses:
        '200':
          description: Wallet recovered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcShareResponse'
              example:
                SECP256K1:
                  share: eyJjbGl...
                  id: clu32l6c8008wuvz52jokyo3r
                ED25519:
                  share: eyJjbGl...
                  id: clu32l9310087evz5u5v9k1ao
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MpcErrorResponse'
              example:
                id: BAD_REQUEST
                message: Can't parse the request body
        '401':
          description: Unauthorized
          content:
            text/plain:
              schema:
                type: string
              example: Authorization header is missing
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Client API Key or Client Session Token
  parameters:
    Curve:
      name: curve
      in: path
      required: true
      description: The elliptic curve to use.
      schema:
        type: string
        enum:
          - SECP256K1
          - ED25519
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        A caller-provided key used for safe retries. Behavior depends on the
        endpoint:

        - **`POST /v1/presign/{curve}` with `managed: true`** — retrying a
          completed request with the same key returns the same
          `{id, expiresAt}` (200). If the original attempt is still in
          progress, the retry returns 409 with a `Retry-After` header; if
          the original attempt failed before completing, it returns 409
          without `Retry-After` (caller should retry with a new key).
          Reusing a key after the wallet's signing share has changed (for
          example, after a reshare or recovery) returns 422.
        - **`POST /v1/sign`** — only honored for broadcast methods (for
          example `eth_sendTransaction`, `sol_signAndSendTransaction`).
          Supplying the header with a non-broadcast method returns 400.
          For broadcast methods, retrying a completed request returns 409
          (`IDEMPOTENT_REQUEST_ALREADY_COMPLETED`), and reuse with a
          different payload returns 422. On 409, the response body does
          not include the original signing result — the header prevents
          double-execution, it does not replay the prior response.
        - **`POST /v1/assets/send`** — retrying a completed request returns
          409 (`IDEMPOTENT_REQUEST_ALREADY_COMPLETED`), and reuse with a
          different payload returns 422. On 409, the response body does
          not include the original `transactionHash` — the header prevents
          double-submission, it does not replay the prior response.

        Client-stored presignature creation does not currently honor this
        header.
      schema:
        type: string
    PresignatureId:
      name: id
      in: path
      required: true
      description: The presignature ID.
      schema:
        type: string
  schemas:
    MpcErrorResponse:
      type: object
      description: Standard error response from the Enclave MPC API.
      properties:
        id:
          type: string
          description: >-
            Short error identifier (e.g. `DKG_FAILED`, `BAD_REQUEST`,
            `RPC_OP_FAILED`).
        message:
          type: string
          description: Human-readable error details.
      required:
        - id
    MpcShare:
      type: object
      description: An MPC share for a single curve.
      properties:
        share:
          type: string
          description: The MPC share data (base64-encoded).
        id:
          type: string
          description: Unique identifier for this share.
      required:
        - share
        - id
    MpcShareResponse:
      type: object
      description: MPC shares for both supported curves.
      properties:
        SECP256K1:
          $ref: '#/components/schemas/MpcShare'
        ED25519:
          $ref: '#/components/schemas/MpcShare'
      required:
        - SECP256K1
        - ED25519
    SendAssetsRequest:
      type: object
      description: Request body for sending assets.
      properties:
        share:
          type: string
          description: The MPC share for the relevant curve.
        chain:
          type: string
          description: |
            The blockchain network. Accepts a CAIP-2 chain ID or a friendly name
            such as `ethereum`, `solana`, `polygon`, `base`, `sepolia`, etc.
        to:
          type: string
          description: The recipient's address.
        token:
          type: string
          description: |
            Token contract address (ERC-20), mint address (SPL), or a shorthand
            like `NATIVE`, `USDC`, or `USDT`.
        amount:
          type: string
          description: The amount of tokens to send (e.g. `"0.1"` is 0.1 of the token).
        rpcUrl:
          type: string
          description: |
            The RPC node provider URL that the transaction will be submitted
            through. Not required for Solana or Solana Devnet.
        nonce:
          type: string
          description: |
            Hex string of the nonce to set on the transaction (e.g. `"0x01"`).
            Must include the `0x` prefix and a leading zero for single digits.
        metadataStr:
          type: string
          description: Optional metadata string for the transaction.
        sponsorGas:
          type: boolean
          description: |
            Set to `false` to opt out of gas sponsorship for Account Abstraction
            clients. Defaults to `true` when omitted.
        presignature:
          type: string
          description: >
            The `data` value from a client-stored presign response. Omit for
            standard

            (non-presigned) signing. Mutually exclusive with `presignatureId`.
        presignatureId:
          type: string
          description: >
            The `id` value from a Portal-managed presign response. Omit for
            standard

            (non-presigned) signing. Mutually exclusive with `presignature`.
      required:
        - share
        - chain
        - to
        - token
        - amount
        - rpcUrl
    SendAssetsResponse:
      type: object
      description: Response after submitting a send-assets transaction.
      properties:
        transactionHash:
          type: string
          description: |
            The transaction hash (or User Operation hash for Account Abstraction
            clients).
        metadata:
          type: object
          properties:
            amount:
              type: string
              description: Human-readable amount sent.
            rawAmount:
              type: string
              description: Raw amount in the token's smallest unit.
            tokenAddress:
              type: string
              description: The token's contract address.
            tokenDecimals:
              type: integer
              description: Number of decimals for the token.
      required:
        - transactionHash
    SignRequest:
      type: object
      description: Request body for signing a transaction or message by chain.
      properties:
        method:
          type: string
          description: |
            The signer RPC method (e.g. `personal_sign`, `eth_sendTransaction`,
            `sol_signAndSendTransaction`).
        params:
          oneOf:
            - type: object
            - type: array
          description: |
            The parameters for the RPC method as a JSON object or array
            (e.g. `{"value": "0x01", "from": "", "to": "0x...", "data": ""}`).
        share:
          type: string
          description: The MPC share for the relevant curve.
        chainId:
          type: string
          description: The blockchain network as a CAIP-2 chain ID (e.g. `eip155:10143`).
        to:
          type: string
          description: The recipient's address.
        rpcUrl:
          type: string
          description: The node provider RPC URL.
        metadataStr:
          type: string
          description: Optional metadata string for the transaction.
        sponsorGas:
          type: boolean
          description: |
            Set to `false` to opt out of gas sponsorship for Account Abstraction
            clients. Defaults to `true` when omitted.
        presignature:
          type: string
          description: >
            The `data` value from a client-stored presign response. Omit for
            standard

            (non-presigned) signing. Mutually exclusive with `presignatureId`.
        presignatureId:
          type: string
          description: >
            The `id` value from a Portal-managed presign response. Omit for
            standard

            (non-presigned) signing. Mutually exclusive with `presignature`.
      required:
        - method
        - params
        - share
        - chainId
        - to
        - rpcUrl
    SignResponse:
      type: object
      description: Response containing the signed data.
      properties:
        data:
          type: string
          description: The signed data as a hex string.
      required:
        - data
    RawSignRequest:
      type: object
      description: Request body for signing a raw hex digest by curve.
      properties:
        params:
          type: string
          description: |
            A hex string of the digest to sign **without** the leading `0x`
            (e.g. `"7369676e2074686973"`).
        share:
          type: string
          description: The MPC share for the relevant curve.
        presignature:
          type: string
          description: >
            The `data` value from a client-stored presign response. Omit for
            standard

            (non-presigned) signing. Mutually exclusive with `presignatureId`.
        presignatureId:
          type: string
          description: >
            The `id` value from a Portal-managed presign response. Omit for
            standard

            (non-presigned) signing. Mutually exclusive with `presignature`.
        signingScheme:
          type: string
          enum:
            - cggmp
            - frost
          description: |
            The signing scheme to use. Defaults to `cggmp`.
      required:
        - params
        - share
    PresignRequest:
      type: object
      description: Request body for creating a presignature.
      properties:
        share:
          type: string
          description: The JSON-serialized MPC share for the wallet.
        expirationTs:
          type: number
          description: |
            Unix timestamp for when the presignature expires. Defaults to 7 days
            from creation. Maximum 365 days.
        managed:
          type: boolean
          default: false
          description: |
            Set to `true` to create a Portal-managed presignature, where Portal
            stores the encrypted payload and returns only an `id`. Defaults to
            `false`, which creates a client-stored presignature and returns the
            opaque payload as `data`.
      required:
        - share
    PresignResponse:
      type: object
      description: Response containing the created presignature.
      properties:
        id:
          type: string
          description: Unique identifier for the presignature.
        expiresAt:
          type: string
          format: date-time
          description: RFC 3339 expiration timestamp.
        data:
          type: string
          description: |
            Base64-encoded opaque presignature payload for client-stored
            presignatures. Pass this as the `presignature` field in signing
            requests. Omitted for Portal-managed presignatures.
      required:
        - id
        - expiresAt
    PresignatureEntry:
      type: object
      description: A listed active presignature.
      properties:
        id:
          type: string
          description: Unique identifier for the presignature.
        expiresAt:
          type: string
          format: date-time
          description: RFC 3339 expiration timestamp. May be omitted if unavailable.
        curve:
          type: string
          description: |
            The elliptic curve used by the presignature. Currently only
            `SECP256K1` is supported; `ED25519` will be added once support
            ships.
          enum:
            - SECP256K1
        managed:
          type: boolean
          description: |
            `true` if the presignature is Portal-managed (sign with
            `presignatureId`); `false` if it is client-stored (sign with the
            opaque `presignature` data blob).
      required:
        - id
        - curve
        - managed
    ListPresignaturesResponse:
      type: object
      description: Active presignatures for the authenticated client.
      properties:
        presignatures:
          type: array
          items:
            $ref: '#/components/schemas/PresignatureEntry'
      required:
        - presignatures
    BackupRequest:
      type: object
      description: Request body for backing up a wallet.
      properties:
        generateResponse:
          type: string
          description: |
            JSON-stringified response body from the generate endpoint.
      required:
        - generateResponse
    RecoverRequest:
      type: object
      description: Request body for recovering a wallet.
      properties:
        backupResponse:
          type: string
          description: |
            JSON-stringified response body from the backup endpoint.
      required:
        - backupResponse
    IdempotencyConflict:
      type: object
      description: >-
        Returned when the Idempotency-Key has already been used with an
        identical request body.
      properties:
        id:
          type: string
          example: IDEMPOTENT_REQUEST_ALREADY_COMPLETED
        message:
          type: string
          example: Request already completed
      required:
        - id
        - message
    IdempotencyReuse:
      type: object
      description: >-
        Returned when the Idempotency-Key has already been used with a different
        request body.
      properties:
        id:
          type: string
          example: IDEMPOTENCY_KEY_REUSED
        message:
          type: string
          example: Idempotency key reused for different request payload
      required:
        - id
        - message
