openapi: 3.0.3
info:
  title: Para REST API
  version: "1.0"
  description: Server-to-server wallet creation and signing over HTTP

servers:
  - url: https://api.beta.getpara.com
    description: Beta
  - url: https://api.getpara.com
    description: Production

security:
  - ApiKeyAuth: []

paths:
  /v1/wallets/{walletId}/yield/markets:
    get:
      operationId: listYieldMarkets
      summary: List markets
      tags:
      - Yield
      description: List supported USDC markets and their contract addresses. Select one explicitly with marketId.
        The catalog does not guarantee execution is enabled. Confirm the Morpho backend release and yield
        enablement separately for each API environment before submitting Morpho operations.
      parameters:
      - $ref: '#/components/parameters/WalletId'
      - $ref: '#/components/parameters/RequestId'
      responses:
        '200':
          description: Supported markets for the current API environment.
          content:
            application/json:
              schema:
                type: object
                properties:
                  markets:
                    type: array
                    items:
                      $ref: '#/components/schemas/YieldMarket'
                required:
                - markets
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Wallet is not ready for signing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
  /v1/wallets/{walletId}/yield/deposits:
    post:
      operationId: createYieldDeposit
      summary: Deposit
      tags:
      - Yield
      description: Deposit USDC into the selected market and return an operation to track its progress.
      parameters:
      - $ref: '#/components/parameters/WalletId'
      - $ref: '#/components/parameters/RequestId'
      - name: Idempotency-Key
        in: header
        required: true
        description: Required durable key scoped to your API partner across yield operations. Reuse the
          same key and normalized request to retrieve or explicitly retry its operation. A different wallet,
          chain, market, action or amount returns 409. Unlike generic POST idempotency, this key is retained with
          the operation rather than expiring after 24 hours.
        schema:
          type: string
          minLength: 1
          maxLength: 256
          pattern: ^[\x21-\x7e]+$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/YieldDepositRequest'
      responses:
        '202':
          description: Operation accepted or still processing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldOperation'
          headers:
            Location:
              description: Relative URL for polling this operation.
              schema:
                type: string
        '200':
          description: Existing terminal or paused operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldOperation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '409':
          description: Wallet not ready, execution lock busy, or conflicting idempotency key. YIELD_DISABLED means Para has temporarily disabled new execution or paused deposits;
            reads and cancellation of eligible unsigned work remain available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/wallets/{walletId}/yield/withdrawals:
    post:
      operationId: createYieldWithdrawal
      summary: Withdraw
      tags:
      - Yield
      description: Withdraw some or all supplied USDC from the selected market to the same wallet.
      parameters:
      - $ref: '#/components/parameters/WalletId'
      - $ref: '#/components/parameters/RequestId'
      - name: Idempotency-Key
        in: header
        required: true
        description: Required durable key scoped to your API partner across yield operations. Reuse the
          same key and normalized request to retrieve or explicitly retry its operation. A different wallet,
          chain, market, action or amount returns 409. Unlike generic POST idempotency, this key is retained with
          the operation rather than expiring after 24 hours.
        schema:
          type: string
          minLength: 1
          maxLength: 256
          pattern: ^[\x21-\x7e]+$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/YieldWithdrawalRequest'
      responses:
        '202':
          description: Operation accepted or still processing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldOperation'
          headers:
            Location:
              description: Relative URL for polling this operation.
              schema:
                type: string
        '200':
          description: Existing terminal or paused operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldOperation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '409':
          description: Wallet not ready, execution lock busy, or conflicting idempotency key. YIELD_DISABLED means Para has temporarily disabled new execution or paused deposits;
            reads and cancellation of eligible unsigned work remain available.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/wallets/{walletId}/yield/position:
    get:
      operationId: getYieldPosition
      summary: Get position
      tags:
      - Yield
      description: Get the wallet's supplied USDC balance and estimated withdrawal availability in the selected market.
        Provide marketId or chainId. Para derives the chain from marketId; chainId alone selects Aave V3.
        If both are supplied, they must identify the same chain.
      parameters:
      - $ref: '#/components/parameters/WalletId'
      - $ref: '#/components/parameters/RequestId'
      - name: chainId
        in: query
        required: false
        description: Optional when marketId is supplied; must match its chain. Without marketId, selects Aave V3
          on this chain. Native USDC on Ethereum mainnet (1) or Base mainnet (8453), or Aave test USDC on Sepolia (11155111).
          Base Sepolia (84532) uses test USDC and is available only in sandbox and local test environments.
        schema:
          type: integer
          enum:
          - 1
          - 8453
          - 11155111
          - 84532
      - name: marketId
        in: query
        required: false
        description: Market ID from the markets endpoint. Determines the chain and asset. Required if chainId is omitted.
          Without marketId, chainId selects Aave V3. Unsupported IDs or a mismatched chainId return 400.
          The sandbox-only Morpho demo market is morpho-v2-base-sepolia-demo-usdc on Base Sepolia (84532);
          it holds test USDC idle and earns no lending yield.
        schema:
          type: string
          example: morpho-v2-base-steakhouse-usdc
      responses:
        '200':
          description: Current position and snapshot block.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldPosition'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '409':
          description: Wallet is not ready for signing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/wallets/{walletId}/yield/operations/{operationId}:
    get:
      operationId: getYieldOperation
      summary: Get operation
      tags:
      - Yield
      description: Get the current status, transactions, and any error for a yield operation.
      parameters:
      - $ref: '#/components/parameters/WalletId'
      - $ref: '#/components/parameters/RequestId'
      - name: operationId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Current operation state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldOperation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '409':
          description: Wallet is not ready for signing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/wallets/{walletId}/yield/operations/{operationId}/cancel:
    post:
      operationId: cancelYieldOperation
      summary: Cancel operation
      tags:
      - Yield
      description: Cancel remaining unsigned work for a yield operation with no unresolved signed transaction.
      parameters:
      - $ref: '#/components/parameters/WalletId'
      - $ref: '#/components/parameters/RequestId'
      - name: operationId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Cancelled operation.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YieldOperation'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '409':
          description: Wallet not ready, execution lock busy, or the operation has an unresolved signed transaction or is already complete.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/address-screening/status:
    get:
      operationId: getAddressScreeningStatus
      summary: Get Address Screening Status
      description: Check whether an address is sanctioned.
      tags:
        - Screening
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - name: address
          in: query
          required: true
          description: Address to screen.
          schema:
            type: string
            example: "0x000000000000000000000000000000000000dEaD"
      responses:
        "200":
          description: Address screening status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AddressScreeningStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Address screening is unavailable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                screeningUnavailable:
                  summary: Address screening unavailable
                  value:
                    code: "ADDRESS_SCREENING_UNAVAILABLE"
                    message: "Address screening is unavailable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets:
    get:
      operationId: listWallets
      summary: List Wallets
      description: List wallets with optional filters and cursor-based pagination.
      tags:
        - Wallets
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: userIdentifier
          in: query
          required: false
          description: Filter by user identifier. Must be supplied with `userIdentifierType`.
          schema:
            type: string
            example: "alice@example.com"
        - name: userIdentifierType
          in: query
          required: false
          description: Type of user identifier. Must be supplied with `userIdentifier`.
          schema:
            type: string
            enum: [EMAIL, PHONE, CUSTOM_ID, GUEST_ID, DISCORD, TWITTER, TELEGRAM, FARCASTER]
        - name: type
          in: query
          required: false
          description: Filter by blockchain network type
          schema:
            type: string
            enum: [EVM, SOLANA, COSMOS, STELLAR, SUI]
        - name: status
          in: query
          required: false
          description: Filter by readiness fields. `ready` requires both key generation and share persistence to be complete; `creating` requires both to be incomplete.
          schema:
            type: string
            enum: [creating, ready]
        - name: address
          in: query
          required: false
          description: Filter by on-chain wallet address
          schema:
            type: string
      responses:
        "200":
          description: Paginated list of wallets
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WalletListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

    post:
      operationId: createWallet
      summary: Create Wallet
      description: Create an EVM, Cosmos, Solana, Stellar, or Sui wallet through the same route. The `type` selects the chain-specific address and signing behavior; omit `scheme` to use the default for that wallet type.
      tags:
        - Wallets
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWalletRequest"
      responses:
        "201":
          description: Wallet created. The endpoint waits for an address, but `status` can still be `creating`; use `status`, not the presence of `address` or `publicKey`, to determine whether key generation is complete.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Wallet"
          headers:
            Location:
              schema:
                type: string
              description: Path to the created wallet
              example: /v1/wallets/0a1b2c3d-4e5f-6789-abcd-ef0123456789
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: |
            Conflict. Either:
            - A pregen wallet already exists for the authenticated partner with the same `userIdentifier`, `userIdentifierType`, `type`, and resolved `scheme` (code: WALLET_ALREADY_EXISTS)
            - A request with this idempotency key is currently being processed (code: CONFLICT)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                walletExists:
                  summary: Duplicate wallet
                  value:
                    code: "WALLET_ALREADY_EXISTS"
                    message: "a wallet for this identifier and type already exists"
                    walletId: "0a1b2c3d-4e5f-6789-abcd-ef0123456789"
                idempotencyConflict:
                  summary: Idempotency key in-flight
                  value:
                    code: "CONFLICT"
                    message: "A request with this idempotency key is currently being processed"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}:
    get:
      operationId: getWallet
      summary: Get Wallet
      description: Retrieve wallet details by ID.
      tags:
        - Wallets
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Wallet details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Wallet"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"
    patch:
      operationId: updateWalletIdentifier
      summary: Update Wallet Identifier
      description: |
        Update the user identifier on an unclaimed (pregen) wallet.
        Only works on wallets that have not yet been claimed by a user.
      tags:
        - Wallets
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWalletIdentifierRequest"
      responses:
        "200":
          description: Updated wallet
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Wallet"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Wallet has already been claimed by a user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                code: "WALLET_ALREADY_CLAIMED"
                message: "cannot update identifier on a claimed wallet"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/migrate-share:
    post:
      operationId: migrateShare
      summary: Migrate SDK Pregen Wallet Share
      description: |
        Migrate an SDK-pregenerated wallet to the REST API by persisting its user share
        in Para's hardware-isolated enclave.

        The request body must contain an `encryptedPayload` — the user share encrypted with
        the enclave's P-256 public key using ECIES. The SDK's `migrateWalletShare()` method
        handles encryption automatically. Non-SDK callers must implement ECIES-P256 encryption
        using the key from `GET /v1/enclave/public-key`.

        After migration the wallet's `sharesPersisted` flag becomes `true` and the wallet
        can use the REST routes supported by its type: `sign-raw` and `sign-message` support
        all five wallet types; `sign-transaction` supports EVM, Solana, Stellar, and Sui;
        `sign-typed-data` and `sign-authorization` are EVM-only; `transfer` and
        `estimate-fee` support EVM and Solana.

        Migration is additive. The original SDK signing flow continues to work.
      tags:
        - Wallets
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MigrateShareRequest"
      responses:
        "200":
          description: Wallet migrated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Wallet"
        "400":
          description: Invalid encrypted payload, incomplete wallet key generation, or a payload that targets a different wallet.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                code: "INVALID_REQUEST"
                message: "encryptedPayload is required"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: REST API share migration is only supported for API-created wallets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                code: "FORBIDDEN"
                message: "REST API share migration is only supported for API-created wallets"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Wallet shares are already persisted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                code: "CONFLICT"
                message: "shares already persisted for this wallet"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/enclave/public-key:
    get:
      operationId: getEnclavePublicKey
      summary: Get Enclave Public Key
      description: |
        Retrieve the enclave's P-256 public key in PEM format. Non-SDK callers need this
        key to implement ECIES encryption for the `migrate-share` endpoint.

        SDK users do not need to call this endpoint directly — the `migrateWalletShare()`
        method fetches and caches the key automatically.
      tags:
        - Enclave
      parameters:
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          description: Enclave public key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnclavePublicKeyResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/balance:
    get:
      operationId: getWalletBalance
      summary: Get Wallet Balance
      description: |
        Retrieve the native or token balance for a wallet.

        **EVM wallets:** Provide `chainId`. Optionally provide `tokenAddress` to query an ERC-20 token balance
        instead of the native balance.

        **Solana wallets:** Optionally provide `network` (`SOLANA` or `SOLANA_DEVNET`; defaults based on environment).
        Provide `tokenAddress` (SPL mint address) to query an SPL token balance instead of native SOL.

        Not supported for COSMOS, STELLAR, or SUI wallets.
      tags:
        - Wallets
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - name: chainId
          in: query
          required: false
          description: "EVM chain ID. Required for EVM wallets."
          schema:
            type: string
            example: "1"
        - name: tokenAddress
          in: query
          required: false
          description: "Token contract address. EVM: ERC-20 contract. Solana: SPL mint address. Omit for native balance."
          schema:
            type: string
        - name: network
          in: query
          required: false
          description: "Solana network. In production, defaults to SOLANA. In beta, only SOLANA_DEVNET is allowed (and is the default)."
          schema:
            type: string
            enum: [SOLANA, SOLANA_DEVNET]
      responses:
        "200":
          description: Wallet balance
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WalletBalanceResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/sign-raw:
    post:
      operationId: signRaw
      summary: Sign Raw
      description: |
        Sign raw bytes with a wallet's private key share via MPC.

        The input `data` is hex-encoded bytes. The MPC protocol signs these bytes
        directly — no hashing or prefix is applied, and no chain-specific logic
        is involved. This makes `sign-raw` suitable for **any chain** that uses
        the same curve as the wallet's key scheme:

        - **DKLS/CGGMP wallets** (EVM, COSMOS) use secp256k1 — compatible with
          Bitcoin and any other secp256k1-based chain.
        - **ED25519 wallets** (SOLANA, STELLAR, SUI) use Ed25519 — compatible with any
          Ed25519-based chain.

        To use Para wallets with an unsupported chain, create a wallet with the
        matching scheme (e.g., an EVM wallet for secp256k1), pre-hash your
        transaction on the client side, then call `sign-raw` with the hash. Derive
        the chain-specific address from the wallet's `publicKey`.

        Use `sign-transaction` or `sign-message` instead if you want Para to
        handle chain-specific serialization for EVM, Solana, Stellar, or Sui.

        The response `signature` is a hex string (no `0x` prefix). For DKLS/CGGMP
        schemes this is 65 bytes: `r (32) + s (32) + v (1)`.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignRawRequest"
      responses:
        "200":
          description: Signature
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignRawResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden. Either the API key is invalid (`FORBIDDEN`) or a policy
            denied the operation (`POLICY_DENIED`).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/PolicyDeniedError"
              examples:
                forbidden:
                  summary: Invalid API key
                  value:
                    code: "FORBIDDEN"
                    message: "invalid secret api key"
                policyDenied:
                  summary: Denied by policy
                  value:
                    code: "POLICY_DENIED"
                    message: "Transaction denied by policy"
                    deniedBy:
                      scopeName: "signing"
                      permissionType: "SIGN_MESSAGE"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/sign-transaction:
    post:
      operationId: signTransaction
      summary: Sign Transaction
      description: |
        Sign a transaction with a wallet's private key share via MPC.

        **EVM:** Provide unsigned transaction fields as a JSON object and the endpoint
        will RLP-encode them, sign via MPC, and return a fully serialized signed
        transaction (hex-encoded with `0x` prefix) ready for broadcast.

        **Solana:** Provide a base64-encoded serialized Solana transaction (as built
        by `@solana/web3.js`). Both legacy `Transaction` and versioned
        `VersionedTransaction` (v0, with Address Lookup Tables) are accepted; the
        endpoint auto-detects the format, signs via MPC, and returns the signed
        transaction as a base64 string ready for broadcast.

        Set top-level `broadcast: true` to broadcast the signed transaction for EVM
        or Solana wallets. Broadcasted calls create a persisted transaction record,
        return `txHash` and `transactionId`, and set `x-transaction-id`. Omitted
        `broadcast` remains sign-only and writes no transaction-history row. Stellar
        wallets do not support `broadcast: true`.

        **Stellar:** Provide a base64-encoded Stellar transaction envelope (XDR) in
        the `transaction` field and the network passphrase in `networkPassphrase`.
        The endpoint computes the transaction hash, signs it via MPC, and returns
        the signed transaction as a base64-encoded XDR string.

        **Sui:** Provide a base64-encoded, BCS-serialized transaction (`TransactionData`)
        in the `transaction` field. The endpoint computes the Sui intent digest, signs it
        via MPC, and returns the serialized Sui signature
        (`base64(flag || signature || publicKey)`) in `signature` alongside the echoed
        `transaction` bytes. Sui does not support `broadcast: true`; `networkPassphrase`
        is used only for Stellar wallets.

        Cosmos wallets are not supported by this endpoint.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignTransactionRequest"
      responses:
        "200":
          description: Signed transaction
          headers:
            x-transaction-id:
              schema:
                type: string
                format: uuid
              description: "Present only when `broadcast: true` created a persisted transaction record."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignTransactionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden. Either the API key is invalid (`FORBIDDEN`) or a policy
            denied the operation (`POLICY_DENIED`).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/PolicyDeniedError"
              examples:
                forbidden:
                  summary: Invalid API key
                  value:
                    code: "FORBIDDEN"
                    message: "invalid secret api key"
                policyDenied:
                  summary: Denied by policy
                  value:
                    code: "POLICY_DENIED"
                    message: "Transaction denied by policy"
                    deniedBy:
                      scopeName: "transfers"
                      permissionType: "TRANSFER"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/sign-message:
    post:
      operationId: signMessage
      summary: Sign Message
      description: |
        Sign a human-readable message with a wallet's private key share via MPC.

        For EVM wallets, the EIP-191 prefix (`\x19Ethereum Signed Message:\n<length>`)
        is prepended and the result is keccak256-hashed before MPC signing. For Solana,
        Cosmos, and Stellar wallets, the raw UTF-8 bytes of the message are signed directly.
        For Sui wallets, the message is wrapped in the Sui `PersonalMessage` intent and
        blake2b-256 hashed before MPC signing.

        The response `signature` is hex-encoded without a `0x` prefix, except for Sui
        wallets, which return the serialized Sui signature
        (`base64(flag || signature || publicKey)`).
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignMessageRequest"
      responses:
        "200":
          description: Signature
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignMessageResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden. Either the API key is invalid (`FORBIDDEN`) or a policy
            denied the operation (`POLICY_DENIED`).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/PolicyDeniedError"
              examples:
                forbidden:
                  summary: Invalid API key
                  value:
                    code: "FORBIDDEN"
                    message: "invalid secret api key"
                policyDenied:
                  summary: Denied by policy
                  value:
                    code: "POLICY_DENIED"
                    message: "Transaction denied by policy"
                    deniedBy:
                      scopeName: "signing"
                      permissionType: "SIGN_MESSAGE"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/sign-typed-data:
    post:
      operationId: signTypedData
      summary: Sign Typed Data
      description: |
        Signs EIP-712 typed structured data. Only supported for EVM wallets.

        Computes the EIP-712 hash of the provided domain, types, and message, then
        signs via MPC. The `EIP712Domain` type is handled automatically and should
        be omitted from `types`.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignTypedDataRequest"
      responses:
        "200":
          description: Signature
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignTypedDataResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Forbidden. Either the API key is invalid (`FORBIDDEN`) or a policy
            denied the operation (`POLICY_DENIED`).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/PolicyDeniedError"
              examples:
                forbidden:
                  summary: Invalid API key
                  value:
                    code: "FORBIDDEN"
                    message: "invalid secret api key"
                policyDenied:
                  summary: Denied by policy
                  value:
                    code: "POLICY_DENIED"
                    message: "Transaction denied by policy"
                    deniedBy:
                      scopeName: "signing"
                      permissionType: "SIGN_MESSAGE"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/sign-authorization:
    post:
      operationId: signAuthorization
      summary: Sign Authorization (EIP-7702)
      description: |
        Signs an EIP-7702 authorization for account delegation. Only supported for
        EVM wallets.

        EIP-7702 delegates execution to a smart contract while preserving the EOA's
        address. Once an authorization is applied on-chain, the delegation persists
        until replaced or cleared by another authorization. A later execution revert
        does not undo an applied delegation. Signing alone does not change the account.

        The endpoint computes the authorization hash per EIP-7702
        (`keccak256(0x05 || rlp([chainId, address, nonce]))`), signs it via MPC,
        and returns the decomposed signature with `yParity` (0 or 1) — not
        the legacy `v` (27/28) used by other signing endpoints.

        Provide at least one of `address` or `contractAddress` as the delegation
        target. Both are accepted; when both are supplied, `contractAddress` takes
        precedence.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignAuthorizationRequest"
      responses:
        "200":
          description: Signed authorization
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignAuthorizationResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/IdempotencyConflict"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/transfer:
    post:
      operationId: transfer
      summary: Transfer
      description: |
        High-level transfer endpoint that handles nonce, gas, and fee estimation
        automatically. Supports native and token transfers on EVM and Solana.

        **Gas sponsorship pilot:** Para must enroll your partner before you use this
        mode. Enrollment covers all your eligible API-created, unclaimed EVM wallets,
        including wallets created afterward. Wallet ownership, signing readiness,
        permissions, and the configured per-operation gas cap still apply. Non-EVM wallets
        keep their existing transfer behavior.

        Eligible EVM token transfers return `202` with `executionMode: sponsored`
        and a tracking ID instead of signed transaction bytes. Require `Idempotency-Key`,
        a supported `chainId` and `tokenAddress`, and a positive integer `value` in token
        base units. Omit `kind` or use `ERC20`. Omit `broadcast` or set it to `true`;
        leave recipient screening enabled. Native transfers, unsupported chains or tokens,
        sign-only requests, transaction overrides, and disabled recipient screening
        retain the existing transfer path.

        `202` records the request; policy, recipient screening, and per-operation gas-cap checks
        run afterward. Rejections during preparation appear as tracked `failed`
        results; uncertain outcomes remain nonterminal. Transfer/replay responses
        use `txHash`, while transaction lookup/history uses `hash` after inclusion.

        Pausing sponsorship admission while the partner remains enabled rejects new
        eligible sponsored requests instead of falling back to wallet-paid gas.
        Retrying the original request and key retrieves the same operation, including
        after 24 hours or an enrollment change. It does not resume a policy review.
        Partners without sponsorship enabled use the existing transfer path and its
        `200` response described below.
        Existing policy reviews can be resumed; conditional approval policies are not
        supported for sponsored transfers in this pilot.
        See [Gas sponsorship](/v3/rest/gas-sponsorship) for tracking and review continuation.

        **EVM:** Provide `to`, `value`, and `chainId`. The endpoint auto-fills `nonce`
        (via `getTransactionCount`), `gasLimit` (via `estimateGas` with 20% buffer),
        and fee data (`maxFeePerGas`/`gasPrice` via `getFeeData`) if omitted. Transaction
        `type` is auto-detected from chain (EIP-1559 if supported, legacy otherwise).
        For ERC-20 transfers, provide `tokenAddress` — the endpoint encodes the
        `transfer(address,uint256)` calldata automatically.

        **Solana:** Provide `to` and `value` (in lamports for SOL, smallest unit for SPL).
        For SPL token transfers, also provide `tokenAddress` (the mint address).
        Blockhash is fetched automatically.

        Address screening is enabled by default when Para has screening configured.
        Set `addressScreening: false` to skip recipient screening for a request.

        Use `kind` to explicitly declare the transfer type (`NATIVE`, `ERC20`, or `SPL`),
        or omit it to infer from the presence of `tokenAddress` (backwards compatible).
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/TransferIdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TransferRequest"
      responses:
        "200":
          description: Signed transaction
          headers:
            x-transaction-id:
              schema:
                type: string
                format: uuid
              description: "Set when the transfer was broadcast (default behavior). Matches the persisted transaction record id."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LegacyTransferResponse"
        "202":
          description: Sponsored operation accepted or retrieved by its original idempotency key
          headers:
            x-transaction-id:
              description: Durable tracking ID for the sponsored operation.
              schema:
                type: string
                format: uuid
            Location:
              description: Path to the existing transaction lookup.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SponsoredTransferResponse"
              example:
                executionMode: sponsored
                transactionId: "550e8400-e29b-41d4-a716-446655440000"
                status: pending
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: |
            Invalid API key or insufficient wallet authority. Legacy transfers can
            also return policy or recipient-screening failures synchronously.
            For sponsored requests accepted with `202`, those checks run afterward;
            inspect the tracked operation's status.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/Error"
                  - $ref: "#/components/schemas/PolicyDeniedError"
              examples:
                forbidden:
                  summary: Invalid API key
                  value:
                    code: "FORBIDDEN"
                    message: "invalid secret api key"
                policyDenied:
                  summary: Denied by policy
                  value:
                    code: "POLICY_DENIED"
                    message: "Transaction denied by policy"
                    deniedBy:
                      scopeName: "transfers"
                      permissionType: "TRANSFER"
                      condition:
                        resource: "VALUE"
                        comparator: "GREATER_THAN"
                        reference: "1000000000000000000"
                sanctionedAddress:
                  summary: Sanctioned recipient
                  value:
                    code: "SANCTIONED_ADDRESS"
                    message: "Recipient address is sanctioned"
                screeningUnavailable:
                  summary: Address screening unavailable
                  value:
                    code: "ADDRESS_SCREENING_UNAVAILABLE"
                    message: "Address screening is unavailable"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: |
            Idempotency conflict, unavailable wallet execution, or another unresolved
            operation. Sponsored key conflicts use `IDEMPOTENCY_CONFLICT`; a pending-operation
            conflict includes its tracking ID. Sponsored per-operation gas-cap checks run after `202`
            and report failure through the tracked status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          $ref: "#/components/responses/Unprocessable"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"
        "503":
          $ref: "#/components/responses/SponsorshipUnavailable"

  /faucet:
    post:
      operationId: requestFaucet
      summary: Request Testnet Tokens
      description: |
        Send testnet tokens to a Para wallet. The wallet must belong to an
        authenticated user under the partner's API key. Rate limited to 10
        requests per API key per day, with a 24-hour cooldown per wallet.
      tags:
        - Faucet
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FaucetRequest"
      responses:
        "200":
          description: Testnet tokens sent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FaucetResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/transactions:
    get:
      operationId: listRestTransactions
      summary: List Transactions
      description: |
        List the REST transaction history for a wallet, scoped to the authenticated partner.
        Results are ordered by `createdAt` DESC and paginated via opaque cursor.

        `POST /transfer` broadcasts by default and appears in this list. `POST /sign-transaction`
        appears only when called with `broadcast: true`. Sign-only requests (`broadcast: false`
        or omitted for `sign-transaction`) do not write history rows. Sponsored transfers
        create a record when accepted, before signing or submission, including when policy review is required.
        Legacy EVM and Solana broadcasts remain `submitted` during durable reconciliation after active monitoring ends.
        Optional `monitoring` metadata describes unresolved checks and verified outcomes.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - name: status
          in: query
          required: false
          description: Filter by transaction status.
          schema:
            type: string
            enum: [pending, submitted, confirmed, reverted, failed]
        - name: intentKind
          in: query
          required: false
          description: Filter by the REST operation that created the transaction record.
          schema:
            type: string
            enum: [transfer, sign_transaction, yield]
      responses:
        "200":
          description: Paginated list of transactions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RestTransactionListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/transactions/{transactionId}:
    get:
      operationId: getRestTransaction
      summary: Get Transaction
      description: |
        Look up a single persisted REST transaction record by id. Returns `404` with an identical
        body for records that do not exist or that belong to a different partner.
        Current wallet access is required. Sponsored operations include `executionMode`,
        `stage`, and available review, UserOperation and gas-cost fields. Poll this record
        for the final outcome; `hash` always means the on-chain transaction hash.
        This lookup reflects the latest persisted outcome. An idempotent POST replay may return an older
        cached response, including a historical monitoring-timeout error; it does not refresh status.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - name: transactionId
          in: path
          required: true
          description: "Transaction record id returned by `POST /transfer` or `POST /sign-transaction` with `broadcast: true`."
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Transaction record
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RestTransaction"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/wallets/{walletId}/transactions/{transactionId}/resume:
    post:
      operationId: resumeRestTransaction
      summary: Resume Sponsored Transaction
      description: |
        Gas sponsorship pilot only. Continue an existing operation after its policy review
        is approved. Current partner and wallet access are required. Supply the applicable
        `reviewId`; the original transfer intent cannot be changed through this endpoint.
        Calling `transfer` again with its original idempotency key only retrieves the
        operation and does not trigger continuation. A later step can require another review.
        `202` accepts continuation; policy and per-operation gas-cap checks run asynchronously.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - name: transactionId
          in: path
          required: true
          description: Tracking ID of the sponsored transfer awaiting review.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ResumeRestTransactionRequest"
      responses:
        "202":
          description: Sponsored operation continued or awaiting further review
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SponsoredTransferResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Current wallet authority does not permit continuation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: The operation cannot continue in its current state, is busy, or the review ID does not match.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"
        "503":
          $ref: "#/components/responses/SponsorshipUnavailable"

  /v1/wallets/{walletId}/estimate-fee:
    post:
      operationId: estimateFee
      summary: Estimate Transfer Fee
      description: |
        Estimate the fee for a transfer without signing or broadcasting. Accepts
        the same core parameters as the transfer endpoint. Fees are advisory and
        subject to network conditions.

        **EVM:** Returns `estimatedFee`, `transferAmount`, and the native `currency`
        symbol, plus `gasLimit` and either `maxFeePerGas` with `maxPriorityFeePerGas`
        for EIP-1559 transactions or `gasPrice` for legacy transactions.

        **Solana:** Returns `estimatedFee`, `transferAmount`, and `currency` (`SOL`).

        A non-fatal estimation warning is returned in `warning` when available.
      tags:
        - Signing
      parameters:
        - $ref: "#/components/parameters/WalletId"
        - $ref: "#/components/parameters/RequestId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EstimateFeeRequest"
      responses:
        "200":
          description: Fee estimate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EstimateFeeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: REST API fee estimation is only supported for API-created wallets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                code: "FORBIDDEN"
                message: "REST API fee estimation is only supported for API-created wallets"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

  /v1/onramp/purchases/{purchaseId}:
    get:
      operationId: getOnRampPurchase
      summary: Get On-Ramp Purchase
      description: |
        Look up a fiat on-ramp purchase by id. Use this to reconcile a purchase you may have missed the
        `onramp.completed` webhook for, or to check one that is still in flight.

        The response is the `onramp.completed` webhook payload plus a `status` field. A purchase that has not
        settled yet returns partial data — `transactionHash` and `completedAt` are `null` until `status` is
        `FINISHED` — so read `status` rather than inferring completion from the hash.

        Scoped to the calling partner. A purchase that does not exist, is malformed, or belongs to a different
        partner all return an identical `404`.
      tags:
        - On-Ramp
      parameters:
        - $ref: "#/components/parameters/RequestId"
        - name: purchaseId
          in: path
          required: true
          description: "Para on-ramp purchase id — the `purchaseId` carried on the `ONRAMP_LAUNCHED` SDK event when the ramp launched, and on the `onramp.completed` webhook when it settled."
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: On-ramp purchase record
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OnRampPurchaseRecord"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "500":
          $ref: "#/components/responses/ServerError"

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Your partner secret key (server-side only)
      x-default: sk_your_secret_key_here

  parameters:
    WalletId:
      name: walletId
      in: path
      required: true
      description: Wallet UUID. A malformed or inaccessible wallet ID returns `404`.
      schema:
        type: string
        format: uuid
        example: "0a1b2c3d-4e5f-6789-abcd-ef0123456789"

    RequestId:
      name: X-Request-Id
      in: header
      required: false
      description: UUID for request tracing. Para returns one if omitted.
      schema:
        type: string
        format: uuid

    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        For sponsored transfers, use the durable behavior documented on `POST /transfer`.
        Other POST endpoints use this key for safe retries. If a request with the same key
        is received within 24 hours, the original response is returned without re-executing.
        Recommended format: UUID v4. Max length 256 characters.
        Returns 422 if the same key is reused with a different request body.
        Returns 409 if a request with the same key is still being processed.
        Replays return the original response, not the latest chain outcome. For a persisted broadcast,
        use the returned `transactionId` with GET transaction history to track its current status.
      schema:
        type: string
        maxLength: 256

    TransferIdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Required for sponsored transfers. Save one key per transfer intent and reuse it
        with the same request to recover the original operation, even after 24 hours or
        sponsorship is disabled. Reuse with a different wallet or intent returns
        `409 IDEMPOTENCY_CONFLICT`. Replays do not resume policy review.
        Legacy transfers retain the optional 24-hour behavior described by `IdempotencyKey`.
      schema:
        type: string
        maxLength: 256

    Limit:
      name: limit
      in: query
      required: false
      description: Maximum number of items to return (1-100)
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50

    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque cursor for pagination. Use the value from a previous response's pagination.cursor field.
      schema:
        type: string

  schemas:
    YieldMarket:
      type: object
      properties:
        id:
          type: string
          example: morpho-v2-base-steakhouse-usdc
        chainId:
          type: integer
          example: 8453
        protocol:
          type: string
          enum:
          - aave-v3
          - morpho-v2
        asset:
          type: string
          enum:
          - USDC
        tokenAddress:
          type: string
        vaultAddress:
          type: string
          description: Morpho vault address.
        poolAddress:
          type: string
          description: Aave pool address.
      required:
      - id
      - chainId
      - protocol
      - asset
      - tokenAddress
    YieldDepositRequest:
      type: object
      properties:
        chainId:
          example: 8453
          oneOf:
          - type: integer
            enum:
            - 1
            - 8453
            - 11155111
            - 84532
          - type: string
            enum:
            - '1'
            - '8453'
            - '11155111'
            - '84532'
          description: Optional when marketId is supplied; must match its chain. Without marketId, selects Aave V3
            on this chain. Native USDC on Ethereum mainnet (1) or Base mainnet (8453), or Aave test USDC on Sepolia (11155111).
            Base Sepolia (84532) uses test USDC and is available only in sandbox and local test environments.
            Base uses native USDC, not USDbC. Mainnet chain IDs use real funds regardless of API environment.
        marketId:
          type: string
          description: Market ID from the markets endpoint. Determines the chain and asset. Required if chainId is omitted.
            Without marketId, chainId selects Aave V3. Unsupported market IDs or a mismatched chainId return 400
            before operation creation or signing.
            Additional request fields are rejected. Morpho supports morpho-v2-ethereum-steakhouse-usdc on Ethereum (1)
            and morpho-v2-base-steakhouse-usdc on Base (8453). The sandbox-only Morpho demo market
            is morpho-v2-base-sepolia-demo-usdc on Base Sepolia (84532); it holds test USDC idle and earns no lending yield.
          example: morpho-v2-base-steakhouse-usdc
        amount:
          type: string
          pattern: ^(0|[1-9]\d*)(\.\d{1,6})?$
          maxLength: 85
          description: Positive USDC amount in decimal units, not base units. At most six decimal places;
            zero is rejected.
          example: '10'
      required:
      - amount
      anyOf:
      - required: [marketId]
      - required: [chainId]
      additionalProperties: false
    YieldWithdrawalRequest:
      type: object
      properties:
        chainId:
          example: 8453
          oneOf:
          - type: integer
            enum:
            - 1
            - 8453
            - 11155111
            - 84532
          - type: string
            enum:
            - '1'
            - '8453'
            - '11155111'
            - '84532'
          description: Optional when marketId is supplied; must match its chain. Without marketId, selects Aave V3
            on this chain. Native USDC on Ethereum mainnet (1) or Base mainnet (8453), or Aave test USDC on Sepolia (11155111).
            Base Sepolia (84532) uses test USDC and is available only in sandbox and local test environments.
            Base uses native USDC, not USDbC. Mainnet chain IDs use real funds regardless of API environment.
        marketId:
          type: string
          description: Market ID from the markets endpoint. Determines the chain and asset. Required if chainId is omitted.
            Without marketId, chainId selects Aave V3. Unsupported market IDs or a mismatched chainId return 400
            before operation creation or signing.
            Additional request fields are rejected. Morpho supports morpho-v2-ethereum-steakhouse-usdc on Ethereum (1)
            and morpho-v2-base-steakhouse-usdc on Base (8453). The sandbox-only Morpho demo market
            is morpho-v2-base-sepolia-demo-usdc on Base Sepolia (84532); it holds test USDC idle and earns no lending yield.
          example: morpho-v2-base-steakhouse-usdc
        amount:
          oneOf:
          - type: string
            pattern: ^(0|[1-9]\d*)(\.\d{1,6})?$
            maxLength: 85
            description: Positive USDC amount in decimal units, not base units. At most six decimal places;
              zero is rejected.
            example: '10'
          - type: string
            enum:
            - all
            description: Withdraw the full position. Morpho redeems the share balance captured when Para
              prepares the transaction; retries reuse that exact share amount. Insufficient liquidity does
              not result in a partial withdrawal. Aave uses its full-withdrawal sentinel.
      required:
      - amount
      anyOf:
      - required: [marketId]
      - required: [chainId]
      additionalProperties: false
    YieldTransaction:
      type: object
      properties:
        phase:
          type: string
          enum:
          - approval
          - action
        hash:
          type: string
        nonce:
          type: integer
          minimum: 0
        transactionId:
          type: string
          format: uuid
          description: Corresponding transaction history record; intentKind is yield.
        status:
          type: string
          enum:
          - signed
          - submitted
          - confirmed
          - reverted
          - failed
          description: failed means the saved nonce was consumed in a finalized block without a receipt
            for this hash. The original signed bytes cannot execute again; check the position and
            transaction history before creating a new request.
        blockNumber:
          type: integer
        blockHash:
          type: string
      required:
      - phase
      - hash
      - nonce
      - transactionId
      - status
    YieldOperation:
      type: object
      properties:
        id:
          type: string
          format: uuid
        walletId:
          type: string
          format: uuid
        chainId:
          example: 8453
          type: integer
          enum:
          - 1
          - 8453
          - 11155111
        marketId:
          type: string
          example: morpho-v2-base-steakhouse-usdc
        source:
          type: string
          enum:
          - aave-v3
          - morpho-v2
        action:
          type: string
          enum:
          - deposit
          - withdraw
        amount:
          type: string
          description: Normalized decimal USDC amount, or all for withdrawals.
          example: '10.0'
        status:
          type: string
          enum:
          - pending
          - submitted
          - confirmed
          - failed
          - requires_action
          - cancelled
          description: pending work automatically retries recognized temporary RPC failures before signing
            and waits for another yield operation on the same wallet. submitted can mean signed and durably stored even when
            RPC acceptance is uncertain. confirmed requires two confirmations of the canonical block;
            this is not finality. failed means an on-chain revert or NONCE_CONSUMED; it does not guarantee
            that no funds moved. requires_action needs an explicit retry after correcting the issue.
        transactions:
          type: array
          items:
            $ref: '#/components/schemas/YieldTransaction'
        error:
          type: object
          properties:
            code:
              type: string
              description: |
                AAVE_SUPPLY_CAP_EXCEEDED means the deposit would exceed the Aave market supply cap.
                The operation is requires_action and no deposit was signed. Retry the same request
                after capacity is available, or cancel its unsigned work. A confirmed token approval remains.
                SIGNING_REQUIRES_ACTION can indicate a Morpho withdrawal simulation failure due to liquidity
                or another execution restriction. Correct the issue and retry, or cancel unsigned work.
                NONCE_CONSUMED means the saved nonce was consumed in a finalized block without a receipt
                for the original hash. Check the position and transaction history before creating a new request.
            message:
              type: string
              nullable: true
            reviewId:
              type: string
              description: Existing policy review identifier when approval is required.
          required:
          - code
          - message
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
      - id
      - walletId
      - chainId
      - source
      - marketId
      - action
      - amount
      - status
      - transactions
      - createdAt
      - updatedAt
    YieldPosition:
      type: object
      properties:
        chainId:
          example: 8453
          type: integer
          enum:
          - 1
          - 8453
          - 11155111
        marketId:
          type: string
          example: morpho-v2-base-steakhouse-usdc
        protocol:
          type: string
          enum:
          - aave-v3
          - morpho-v2
        asset:
          type: string
          enum:
          - USDC
        tokenAddress:
          type: string
        suppliedBalance:
          type: string
          description: Current position value in USDC decimal units, including accrued yield. Aave uses the aToken
            balance; Morpho uses previewRedeem for the current shares, rounding down. Not historical earnings.
        walletBalance:
          type: string
          description: Wallet USDC balance in decimal units.
        availableLiquidity:
          type: string
          description: Aave only. USDC held by the reserve aToken contract; other protocol constraints may restrict withdrawals.
        withdrawal:
          type: object
          properties:
            availableAmount:
              type: string
              nullable: true
              description: Advisory amount in USDC units. Morpho returns the full-position previewRedeem value only when
                a full redemption simulation succeeds, null if it fails, and zero for an empty position. This is not an
                exact maximum; a partial withdrawal may still succeed. Morpho V2 maxWithdraw and maxRedeem return zero
                and are not used. Aave returns null for failed simulation or debt constraints, and zero for inactive or paused reserves.
            advisory:
              type: boolean
              enum:
              - true
            restrictions:
              type: array
              items:
                type: string
              description: 'Examples: reserve_inactive, reserve_paused, collateral_or_debt_constraints, limited_reserve_liquidity,
                withdrawal_simulation_unavailable, zero_redeemable_assets.'
          required:
          - availableAmount
          - advisory
          - restrictions
        vaultAddress:
          type: string
          description: Morpho only. Address of the selected Morpho V2 vault on Ethereum or Base.
          example: '0xbeef0e0834849aCC03f0089F01f4F1Eeb06873C9'
        shares:
          type: object
          description: Morpho only. Vault share balance in raw units, not a USDC amount.
          properties:
            balance:
              type: string
              pattern: ^[0-9]+$
            decimals:
              type: integer
              enum:
              - 18
          required:
          - balance
          - decimals
        reserve:
          type: object
          description: Aave only. Current reserve configuration.
          properties:
            active:
              type: boolean
            frozen:
              type: boolean
            paused:
              type: boolean
          required:
          - active
          - frozen
          - paused
        blockNumber:
          type: integer
        blockTimestamp:
          type: string
          format: date-time
      required:
      - chainId
      - protocol
      - marketId
      - asset
      - tokenAddress
      - suppliedBalance
      - walletBalance
      - withdrawal
      - blockNumber
      - blockTimestamp
    CreateWalletRequest:
      type: object
      required:
        - type
        - userIdentifier
        - userIdentifierType
      properties:
        type:
          type: string
          enum: [EVM, SOLANA, COSMOS, STELLAR, SUI]
          description: Wallet type that selects the chain-specific address and signing behavior
        userIdentifier:
          type: string
          minLength: 1
          description: User identifier. Validation depends on `userIdentifierType`.
          example: "alice@example.com"
        userIdentifierType:
          type: string
          enum: [EMAIL, PHONE, CUSTOM_ID, GUEST_ID, DISCORD, TWITTER, TELEGRAM]
          description: Type of user identifier
        scheme:
          type: string
          enum: [DKLS, CGGMP, ED25519]
          description: "Signature scheme. Defaults to DKLS for EVM and Cosmos, and ED25519 for Solana, Stellar, and Sui. Normally omit this field and let the wallet type select the scheme."
        cosmosPrefix:
          type: string
          description: Bech32 prefix for Cosmos wallets
          example: "cosmos"

    UpdateWalletIdentifierRequest:
      type: object
      required:
        - userIdentifier
        - userIdentifierType
      properties:
        userIdentifier:
          type: string
          minLength: 1
          description: New user identifier. Validation depends on `userIdentifierType`.
          example: "bob@example.com"
        userIdentifierType:
          type: string
          enum: [EMAIL, PHONE, CUSTOM_ID, GUEST_ID, DISCORD, TWITTER, TELEGRAM]
          description: Type of user identifier

    MigrateShareRequest:
      type: object
      required:
        - encryptedPayload
      properties:
        encryptedPayload:
          type: string
          minLength: 1
          maxLength: 64000
          pattern: '\S'
          description: |
            JSON string containing the ECIES-encrypted user share. The SDK's
            `migrateWalletShare()` method produces this automatically. Non-SDK
            callers must encrypt the share with the enclave's P-256 public key
            (from `GET /v1/enclave/public-key`) using ECIES-P256-AES256-SHA256.
            The value must contain a non-whitespace character and cannot exceed
            64,000 characters.
          example: "{\"encryptedData\":\"base64...\",\"ephemeral\":\"base64...\",\"algorithm\":\"ECIES-P256-AES256-SHA256\"}"

    EnclavePublicKeyResponse:
      type: object
      required:
        - publicKey
        - keyFingerprint
        - generatedAt
      properties:
        publicKey:
          type: string
          description: PEM-formatted P-256 public key for ECIES encryption
          example: "-----BEGIN PUBLIC KEY-----\nMFkwEwYH...\n-----END PUBLIC KEY-----"
        keyFingerprint:
          type: string
          description: SHA-256 fingerprint of the public key for verification
          example: "SHA256:abc123..."
        generatedAt:
          type: string
          format: date-time
          description: Timestamp when the key was generated
          example: "2025-01-15T00:00:00.000Z"

    Wallet:
      type: object
      required:
        - id
        - type
        - scheme
        - status
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: Unique wallet identifier
          example: "0a1b2c3d-4e5f-6789-abcd-ef0123456789"
        type:
          type: string
          enum: [EVM, SOLANA, COSMOS, STELLAR, SUI]
          description: Blockchain network type
          example: "EVM"
        scheme:
          type: string
          enum: [DKLS, CGGMP, ED25519]
          description: Signature scheme
          example: "DKLS"
        status:
          type: string
          enum: [creating, ready]
          description: "`creating` until key generation completes; `ready` after it completes. `address` and `publicKey` can be present while status is `creating`."
        address:
          type: string
          description: Wallet address. It can be present while status is `creating`.
          example: "0x742d35Cc6634C0532925a3b844Bc9e7595f..."
        publicKey:
          type: string
          description: Public key. It can be present while status is `creating`.
        userIdentifier:
          type: string
          description: The user identifier associated with this wallet
          example: "alice@example.com"
        userIdentifierType:
          type: string
          enum: [EMAIL, PHONE, CUSTOM_ID, GUEST_ID, DISCORD, TWITTER, TELEGRAM, FARCASTER]
          description: Type of user identifier
        createdAt:
          type: string
          format: date-time
          description: ISO 8601 timestamp
          example: "2024-01-15T09:30:00Z"

    WalletListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Wallet"
        pagination:
          $ref: "#/components/schemas/Pagination"

    SignRawRequest:
      type: object
      required:
        - data
      properties:
        data:
          type: string
          minLength: 2
          pattern: '^(?:0[xX])?(?:[0-9a-fA-F]{2})+$'
          description: Hex-encoded data to sign (optional `0x` prefix; non-empty and an even number of hex characters)
          example: "0x48656c6c6f20576f726c64"
        encoding:
          type: string
          minLength: 3
          maxLength: 3
          pattern: '^[hH][eE][xX]$'
          default: hex
          description: Encoding of `data`. Only `hex` is supported; the value is case-insensitive.
        walletType:
          type: string
          enum: [EVM, SOLANA, COSMOS, STELLAR, SUI]
          description: Expected wallet type. If provided and the wallet is a different type, the request is rejected.

    SignRawResponse:
      type: object
      required:
        - signature
      properties:
        signature:
          type: string
          description: "Hex-encoded signature without `0x` prefix. DKLS/CGGMP signatures are 65 bytes (r + s + v); ED25519 signatures are 64 bytes."
          example: "a1b2c3d4e5f6..."

    Pagination:
      type: object
      description: Cursor-based pagination metadata included in all list responses.
      required:
        - cursor
        - hasMore
        - limit
      properties:
        cursor:
          type: string
          nullable: true
          description: Cursor to fetch the next page. Null when no more results.
        hasMore:
          type: boolean
          description: Whether more results exist beyond this page
        limit:
          type: integer
          minimum: 1
          maximum: 100
          description: The limit that was applied to this request
          example: 50

    SignTransactionRequest:
      type: object
      required:
        - transaction
      properties:
        transaction:
          description: |
            EVM: JSON object with unsigned transaction fields.
            Solana: base64-encoded serialized transaction string.
            Stellar: base64-encoded XDR transaction envelope string.
            Sui: base64-encoded, BCS-serialized transaction (`TransactionData`) string.
            The API differentiates by the wallet's type (determined by `walletId`).
            Objects are treated as EVM transactions; strings are treated as Solana,
            Stellar, or Sui transactions depending on the wallet type. For Stellar
            wallets, `networkPassphrase` is required.
          oneOf:
            - $ref: "#/components/schemas/EvmTransaction"
            - type: string
              description: "Base64-encoded chain transaction. Solana accepts serialized legacy and v0 transactions. Stellar accepts an XDR envelope and also requires `networkPassphrase`. Sui accepts BCS-serialized `TransactionData`."
              example: "AQAAAA..."
        networkPassphrase:
          type: string
          description: "Stellar network passphrase. Required when the wallet referenced by `walletId` is a STELLAR wallet. Ignored for other wallet types."
          example: "Public Global Stellar Network ; September 2015"
        network:
          type: string
          enum: [SOLANA, SOLANA_DEVNET]
          description: "Solana network to broadcast to when `broadcast: true`. Uses the same environment rules as `POST /transfer`."
        broadcast:
          type: boolean
          default: false
          description: "When true, broadcast the signed EVM or Solana transaction, create a persisted transaction record, and return `txHash` plus `transactionId`. Omitted or false remains sign-only. Stellar and Sui wallets reject `broadcast: true`."

    EvmTransaction:
      type: object
      required:
        - to
        - chainId
        - type
      properties:
        to:
          type: string
          description: Destination address (contract deployment not supported)
          example: "0x742d35Cc6634C0532925a3b844Bc9e7595f..."
        value:
          type: string
          description: Value in wei (decimal or 0x-prefixed hex)
          example: "0x2386f26fc10000"
        chainId:
          oneOf:
            - type: string
            - type: integer
          description: Chain ID (decimal or 0x-prefixed hex, e.g. "1" for Ethereum mainnet)
          example: "1"
        data:
          type: string
          description: Hex-encoded calldata (0x-prefixed)
        nonce:
          type: integer
          minimum: 0
          description: Transaction nonce
        gasLimit:
          type: string
          description: Gas limit (decimal or 0x-prefixed hex)
          example: "0x5208"
        maxFeePerGas:
          type: string
          description: EIP-1559 max fee per gas (decimal or 0x-prefixed hex)
        maxPriorityFeePerGas:
          type: string
          description: EIP-1559 max priority fee per gas (decimal or 0x-prefixed hex)
        gasPrice:
          type: string
          description: Legacy gas price (decimal or 0x-prefixed hex)
        type:
          type: integer
          enum: [0, 2]
          description: "Transaction type: 0 (legacy) or 2 (EIP-1559)"

    SignTransactionResponse:
      description: "Response shape depends on wallet type. EVM, Solana, and Stellar return `signedTransaction`; Sui returns `signature` and the unchanged `transaction`."
      oneOf:
        - $ref: "#/components/schemas/SerializedTransactionResponse"
        - $ref: "#/components/schemas/SuiSignTransactionResponse"

    SerializedTransactionResponse:
      type: object
      required:
        - signedTransaction
      properties:
        signedTransaction:
          type: string
          description: "Signed transaction ready for broadcast. EVM: RLP-encoded hex string with `0x` prefix. Solana: base64-encoded string. Stellar: base64-encoded XDR string."
          example: "0x02f8..."
        txHash:
          type: string
          description: "Transaction hash. Present only when `broadcast: true` succeeds."
          example: "0x1234abcd..."
        transactionId:
          type: string
          format: uuid
          description: "Persisted transaction record id. Present only when `broadcast: true` creates a transaction-history row."
          example: "550e8400-e29b-41d4-a716-446655440000"

    SuiSignTransactionResponse:
      type: object
      required:
        - signature
        - transaction
      properties:
        signature:
          type: string
          description: "Base64-encoded serialized Sui signature (`flag || signature || publicKey`)."
          example: "AABase64SerializedSuiSignature..."
        transaction:
          type: string
          description: "The unchanged base64-encoded BCS transaction bytes supplied in the request."
          example: "AAACAAgA..."

    SignMessageRequest:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          minLength: 1
          description: Non-empty human-readable message to sign
          example: "Hello World"

    SignMessageResponse:
      type: object
      required:
        - signature
      properties:
        signature:
          type: string
          description: "Hex-encoded signature without `0x` prefix. Sui wallets return a base64-encoded serialized Sui signature instead."
          example: "a1b2c3d4e5f6..."

    SignTypedDataRequest:
      type: object
      required:
        - typedData
      properties:
        typedData:
          $ref: "#/components/schemas/EIP712TypedData"

    EIP712TypedData:
      type: object
      required:
        - domain
        - types
        - primaryType
        - message
      properties:
        domain:
          type: object
          description: EIP-712 domain separator fields
          example:
            name: "MyDApp"
            version: "1"
            chainId: 11155111
            verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
        types:
          type: object
          description: "Type definitions mapping type names to arrays of {name, type} field descriptors. Omit EIP712Domain — it is handled automatically."
          additionalProperties:
            type: array
            items:
              type: object
              properties:
                name:
                  type: string
                type:
                  type: string
          example:
            Mail:
              - name: "from"
                type: "address"
              - name: "to"
                type: "address"
              - name: "contents"
                type: "string"
        primaryType:
          type: string
          description: Primary type name that the message conforms to
          example: "Mail"
        message:
          type: object
          description: Structured data to sign
          example:
            from: "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
            to: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
            contents: "Hello, world!"

    SignTypedDataResponse:
      type: object
      required:
        - signature
      properties:
        signature:
          type: string
          description: "Hex-encoded signature without `0x` prefix."
          example: "a1b2c3d4e5f6..."

    SignAuthorizationRequest:
      type: object
      required:
        - authorization
      properties:
        authorization:
          type: object
          required:
            - chainId
            - nonce
          anyOf:
            - required: [address]
            - required: [contractAddress]
          description: Provide `address` or `contractAddress` as the delegation target. If both are supplied, `contractAddress` is used.
          properties:
            address:
              type: string
              minLength: 1
              description: "Contract address to delegate execution to. Used when `contractAddress` is not supplied."
              example: "0x1234567890abcdef1234567890abcdef12345678"
            contractAddress:
              type: string
              minLength: 1
              description: "Alias for `address`. Takes precedence when both fields are supplied."
              example: "0x1234567890abcdef1234567890abcdef12345678"
            chainId:
              oneOf:
                - type: integer
                  minimum: 0
                - type: string
              description: "Non-negative integer chain ID. String values are parsed with `Number()`; use a decimal or `0x`-prefixed numeric string. Use 0 for chain-agnostic authorizations."
              example: 1
            nonce:
              type: integer
              minimum: 0
              description: "Account nonce for the authorization."
              example: 0

    SignAuthorizationResponse:
      type: object
      required:
        - address
        - chainId
        - nonce
        - r
        - s
        - yParity
        - signature
      properties:
        address:
          type: string
          description: "Contract address the authorization delegates to."
          example: "0x1234567890abcdef1234567890abcdef12345678"
        chainId:
          type: integer
          description: "Chain ID from the authorization."
          example: 1
        nonce:
          type: integer
          description: "Nonce from the authorization."
          example: 0
        r:
          type: string
          description: "ECDSA signature r component (hex with 0x prefix)."
          example: "0xa1b2c3..."
        s:
          type: string
          description: "ECDSA signature s component (hex with 0x prefix)."
          example: "0xd4e5f6..."
        yParity:
          type: integer
          enum: [0, 1]
          description: "EIP-7702 parity bit (0 or 1). Unlike other signing endpoints that return v (27/28), this uses yParity per the EIP-7702 spec."
          example: 0
        signature:
          type: string
          description: "Raw hex-encoded 65-byte signature without `0x` prefix (r + s + v)."
          example: "a1b2c3d4e5f6..."

    AddressScreeningStatus:
      type: object
      required:
        - address
        - isSanctioned
      properties:
        address:
          type: string
          description: Screened address.
          example: "0x000000000000000000000000000000000000dEaD"
        isSanctioned:
          type: boolean
          description: Whether the address is sanctioned.
          example: false

    TransferRequest:
      type: object
      description: |
        Sponsored EVM transfers require an enrolled partner, an idempotency key, a supported
        chain and token, and a positive integer value in token base units. Omit nonce,
        type and gas/fee overrides; broadcast and addressScreening must be omitted or true.
        Requests with ordinary transfer overrides retain the legacy path.
      required:
        - to
        - value
      properties:
        to:
          type: string
          description: Recipient address
          example: "0x742d35Cc6634C0532925a3b844Bc9e7595f..."
        value:
          type: string
          description: "Amount in smallest unit (wei for ETH, lamports for SOL)"
          example: "1000000000000000"
        chainId:
          oneOf:
            - type: string
            - type: integer
          description: "Chain ID. Required for EVM wallets, ignored for Solana."
          example: "1"
        kind:
          type: string
          enum: [NATIVE, ERC20, SPL]
          description: |
            Transfer type discriminator. EVM wallets accept `NATIVE` or `ERC20`.
            Solana wallets accept `NATIVE` or `SPL`. If omitted, inferred from
            `tokenAddress` presence (backwards compatible).
        tokenAddress:
          type: string
          description: "Token contract address. Required when kind is `ERC20` or `SPL`. EVM: ERC-20 contract. Solana: SPL mint address."
        nonce:
          type: integer
          minimum: 0
          description: "Transaction nonce. EVM only — auto-fetched via `getTransactionCount` if omitted."
        gasLimit:
          type: string
          description: "Gas limit. EVM only — auto-estimated with 20% buffer if omitted."
        maxFeePerGas:
          type: string
          description: "EIP-1559 max fee per gas. EVM only — auto-fetched if omitted."
        maxPriorityFeePerGas:
          type: string
          description: "EIP-1559 max priority fee per gas. EVM only — auto-fetched if omitted."
        gasPrice:
          type: string
          description: "Legacy gas price. EVM only — auto-fetched if omitted."
        type:
          type: integer
          enum: [0, 2]
          description: "Transaction type: 0 (legacy) or 2 (EIP-1559). EVM only — auto-detected from chain if omitted."
        network:
          type: string
          enum: [SOLANA, SOLANA_DEVNET]
          description: "Solana network. In production, defaults to SOLANA. In beta, only SOLANA_DEVNET is allowed (and is the default)."
        broadcast:
          type: boolean
          default: true
          description: "Whether to broadcast the signed transaction. Defaults to true. Set to false to receive only the signed transaction without broadcasting."
        addressScreening:
          type: boolean
          default: true
          description: "Whether to screen the recipient address before signing. Defaults to true when address screening is enabled for the API."

    TransferResponse:
      oneOf:
        - $ref: "#/components/schemas/LegacyTransferResponse"
        - $ref: "#/components/schemas/SponsoredTransferResponse"

    LegacyTransferResponse:
      type: object
      required:
        - signedTransaction
      properties:
        executionMode:
          type: string
          enum: [legacy]
          description: Legacy mode; omitted by existing responses.
        signedTransaction:
          type: string
          description: "Signed transaction ready for broadcast. EVM: RLP-encoded hex string. Solana: base64-encoded string."
          example: "0x02f8..."
        txHash:
          type: string
          nullable: true
          description: "Transaction hash. Present when broadcast is true (default). Omitted when broadcast is false."
          example: "0x1234abcd..."
        transactionId:
          type: string
          format: uuid
          description: "Persisted transaction record id (present only when broadcasted). Use it to poll `GET /v1/wallets/{walletId}/transactions/{transactionId}` or match incoming `rest.transaction.confirmed` / `rest.transaction.failed` webhooks."
          example: "550e8400-e29b-41d4-a716-446655440000"

    RestSponsorshipDetails:
      type: object
      properties:
        userOperationHash:
          type: string
          description: ERC-4337 operation hash. This is not an on-chain transaction hash.
        stage:
          type: string
          enum: [requires_action, preparing, signed, submission_unknown, included, delivery_unverified]
          description: |
            `requires_action` needs policy review; `submission_unknown` means submission
            may have happened; `included` awaits receipt verification. `delivery_unverified` means
            an included operation has unresolved token-delivery evidence. These stages are
            nonterminal; do not resend the transfer with a new key.
        reviewId:
          type: string
          description: Applicable policy review ID; use it with the explicit resume endpoint after approval.
        actualGasCost:
          type: string
          pattern: '^[0-9]+$'
          description: |
            Settled gas cost for this individual operation in native-token base units,
            including included reverts. Omitted before settlement. Parse as an integer
            string or bigint, not a floating-point number.

    SponsoredTransferResponse:
      allOf:
        - $ref: "#/components/schemas/RestSponsorshipDetails"
      type: object
      required: [executionMode, transactionId, status]
      not:
        required: [signedTransaction]
      properties:
        executionMode:
          type: string
          enum: [sponsored]
        transactionId:
          type: string
          format: uuid
          description: Durable operation ID for transaction lookup and review continuation.
        status:
          type: string
          enum: [pending, submitted, confirmed, reverted, failed]
        txHash:
          type: string
          description: On-chain transaction hash, present only after inclusion. Not the UserOperation hash.

    ResumeRestTransactionRequest:
      type: object
      required: [reviewId]
      additionalProperties: false
      properties:
        reviewId:
          type: string
          minLength: 1
          description: Approved review for this operation; does not replace its original transfer intent.

    OnRampPurchaseRecord:
      type: object
      required:
        - status
        - userId
        - purchaseId
        - provider
        - transactionHash
        - asset
        - testMode
        - completedAt
      properties:
        status:
          type: string
          enum: [INITIATED, CANCELLED, FINISHED]
          description: |
            Lifecycle state. `INITIATED` = created, not yet settled. `FINISHED` = the provider confirmed delivery
            (this is when `onramp.completed` fires). `CANCELLED` = the purchase will not settle. **Treat unknown
            status values as non-terminal** — future versions may add states.
        userId:
          type: string
          format: uuid
        purchaseId:
          type: string
          format: uuid
          description: "Para's id for this purchase."
        provider:
          type: string
          enum: [COINBASE, MOONPAY, RAMP, STRIPE]
          description: "The provider that fulfilled the purchase. Coinbase Apple Pay reports as `COINBASE`; the surface is conveyed by `paymentMethod`."
        paymentMethod:
          type: string
          enum: [WIDGET, APPLE_PAY]
          description: "Checkout style used."
        transactionHash:
          type: string
          nullable: true
          description: "On-chain hash of the crypto delivered to the user. `null` until settled, and also `null` when the provider settled without reporting one."
        asset:
          type: object
          description: "The delivered token, resolved from the catalog. Fields are best-effort; an empty object means the token could not be resolved."
          properties:
            symbol:
              type: string
              example: "USDC"
            name:
              type: string
              example: "USD Coin"
            contractAddress:
              type: string
              nullable: true
              description: "Token contract address, or `null` for a network's native asset."
            type:
              type: string
              description: "Ecosystem — e.g. `EVM`, `SOLANA`, `COSMOS`."
            chainId:
              type: string
              example: "8453"
            networkName:
              type: string
              example: "Base"
            decimals:
              type: integer
              nullable: true
        assetQuantity:
          type: string
          description: "Crypto amount delivered, as a decimal token amount."
        fiatAmount:
          type: string
        fiatCurrency:
          type: string
          example: "USD"
        walletId:
          type: string
          format: uuid
          nullable: true
          description: "Set when the crypto landed in a Para wallet; absent for a raw external-address destination."
        destinationAddress:
          type: string
          nullable: true
          description: "The on-chain address the crypto was delivered to."
        testMode:
          type: boolean
          description: "Whether this was a test purchase. Test purchases run a real quote but are never charged — filter them out of production accounting."
        completedAt:
          type: string
          format: date-time
          nullable: true
          description: "When the purchase was marked settled. `null` unless `status` is `FINISHED`."

    RestTransaction:
      allOf:
        - $ref: "#/components/schemas/RestTransactionSummary"
        - $ref: "#/components/schemas/RestSponsorshipDetails"

    RestTransactionSummary:
      type: object
      required:
        - transactionId
        - walletId
        - walletType
        - intentKind
        - status
        - createdAt
      properties:
        executionMode:
          type: string
          enum: [legacy, sponsored]
          description: Sponsored records use `sponsored`; legacy responses may omit this field.
        transactionId:
          type: string
          format: uuid
          example: "550e8400-e29b-41d4-a716-446655440000"
        walletId:
          type: string
          format: uuid
        walletType:
          type: string
          enum: [EVM, SOLANA]
        intentKind:
          type: string
          enum: [transfer, sign_transaction, yield]
          description: "REST operation that created the record."
        chainId:
          type: string
          description: "EVM chain ID (EVM records only)."
        network:
          type: string
          enum: [SOLANA, SOLANA_DEVNET]
          description: "Solana network (Solana records only)."
        to:
          type: string
          description: "Destination address. Absent for Solana `sign_transaction` records because Para does not decode arbitrary Solana instructions."
        value:
          type: string
          description: "Transfer amount in smallest unit (wei / lamports / token smallest unit), or EVM `sign_transaction` value when supplied."
        tokenAddress:
          type: string
          description: "Token contract (ERC-20 / SPL mint) address if a token transfer, else absent. Always absent for `sign_transaction` records."
        status:
          type: string
          enum: [pending, submitted, confirmed, reverted, failed]
          description: |
            `pending` and `submitted` are nonterminal. `confirmed`, `reverted` and `failed`
            are terminal results; treat unknown future values as nonterminal.
            For sponsored operations, inclusion is `submitted` with stage `included` until
            receipt verification. Successful execution with verified token delivery is `confirmed`;
            an included execution revert is `reverted`. Unknown submission stays nonterminal,
            and unresolved token evidence remains `pending` with stage `delivery_unverified`.
            Legacy EVM `transfer` and `sign_transaction` broadcasts remain `submitted` during durable
            reconciliation after the 30-minute active window. Receipt resolution requires canonical
            block identity, at least two confirmations, and finalized-block evidence. Timeout, RPC errors,
            and nonce consumption without an identified winner are unresolved, not payment failure.
            Solana broadcasts require finalized signature status and also retain unresolved monitoring context.
            An identified same-nonce winner leaves the original `failed` with `REPLACED` or `CANCELLED`;
            inspect `monitoring` for the actual winner's outcome. Historical `failed` / `monitor_timeout`
            records remain uncertain until chain-backed reconciliation corrects them.
        monitoring:
          $ref: "#/components/schemas/RestTransactionMonitoring"
        hash:
          type: string
          description: "Original on-chain transaction hash. An identified same-nonce winner is exposed separately in `monitoring.winningHash`. Sponsored operations expose this after inclusion; never a UserOperation hash."
        blockNumber:
          type: string
          description: "Block number as a decimal string (present when confirmed or reverted)."
        blockHash:
          type: string
        failureStage:
          type: string
          enum: [mpc_sign, signature_apply, signer_verify, broadcast, monitor_timeout]
          description: "Stage at which the pipeline failed. `monitor_timeout` appears only on historical records and does not establish chain failure."
        failureCode:
          type: string
          description: "Structured broadcast error code, or `REPLACED` / `CANCELLED` when a verified same-nonce winner superseded the original transaction."
        failureMessage:
          type: string
          description: "Truncated error message (max 512 chars)."
        createdAt:
          type: string
          format: date-time
        submittedAt:
          type: string
          format: date-time
          description: "Set when status first moves to `submitted`."
        resolvedAt:
          type: string
          format: date-time
          description: "Set when status reaches a terminal value (`confirmed`, `reverted`, `failed`)."

    RestTransactionMonitoring:
      type: object
      required:
        - state
      description: |
        Optional durable monitoring metadata for legacy EVM and Solana broadcasts. Active monitoring hands off after
        30 minutes without changing unresolved chain outcome to failed. After 24 hours, escalation retains
        unresolved state and checks continue with backoff capped at one hour. Unsupported finality evidence
        or missing legacy context remains unresolved and requires operational follow-up.
      properties:
        state:
          type: string
          enum: [active, reconciling, escalated, resolved]
        timedOutAt:
          type: string
          format: date-time
          description: When active monitoring handed off to durable checks.
        lastCheckedAt:
          type: string
          format: date-time
          description: Most recent reconciliation check.
        reason:
          type: string
          enum: [pending, nonce_consumed, noncanonical, insufficient_confirmations, awaiting_finality, missing_context, rpc_error]
          description: Latest reason the chain outcome remains unresolved. Nonce consumption alone does not identify a winner.
        chainOutcome:
          type: string
          enum: [success, revert, replacement, cancellation]
          description: Verified outcome; absent while unresolved.
        winningHash:
          type: string
          description: Verified transaction using the original EVM sender and nonce. The original record hash remains unchanged.
        winningStatus:
          type: string
          enum: [confirmed, reverted]
          description: Verified execution result of the identified winning transaction.

    RestTransactionListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/RestTransactionSummary"
        pagination:
          $ref: "#/components/schemas/Pagination"

    EstimateFeeRequest:
      type: object
      required:
        - to
        - value
      properties:
        to:
          type: string
          description: Destination address
          example: "0x742d35Cc6634C0532925a3b844Bc9e7595f..."
        value:
          type: string
          description: "Transfer amount in smallest unit (wei for ETH, lamports for SOL)"
          example: "1000000000000000"
        chainId:
          oneOf:
            - type: string
            - type: integer
          description: "Chain ID. Required for EVM wallets, ignored for Solana."
          example: "1"
        tokenAddress:
          type: string
          description: "Token contract address. EVM: ERC-20 contract. Solana: SPL mint address."
        network:
          type: string
          enum: [SOLANA, SOLANA_DEVNET]
          description: "Solana network. In production, defaults to SOLANA. In beta, only SOLANA_DEVNET is allowed (and is the default)."

    EstimateFeeResponse:
      type: object
      required:
        - estimatedFee
        - transferAmount
        - currency
      properties:
        estimatedFee:
          type: string
          description: "Estimated fee in standard units of the native currency (e.g. ETH, SOL)"
          example: "0.000756"
        transferAmount:
          type: string
          description: "Requested transfer amount in standard units. Token transfers use the token's decimals."
          example: "0.001"
        currency:
          type: string
          description: "Native currency symbol (e.g. ETH, SOL). It remains the native symbol for token transfers."
          example: "ETH"
        gasLimit:
          type: string
          description: "Estimated gas limit. EVM only."
          example: "25200"
        maxFeePerGas:
          type: string
          description: "EIP-1559 max fee per gas (in wei). EVM only."
          example: "30000000000"
        maxPriorityFeePerGas:
          type: string
          description: "EIP-1559 max priority fee per gas (in wei). EVM only."
          example: "2000000000"
        gasPrice:
          type: string
          description: "Legacy gas price (in wei). EVM legacy transactions only."
          example: "30000000000"
        warning:
          type: string
          description: "Present when estimation returns a fallback warning."

    WalletBalanceResponse:
      type: object
      required:
        - balance
        - symbol
        - rawBalance
      properties:
        balance:
          type: string
          description: "Human-readable balance in standard units (e.g. ETH, SOL)"
          example: "1.5"
        symbol:
          type: string
          description: "Token symbol (e.g. ETH, SOL, USDC) or mint/contract address for tokens without on-chain symbol"
          example: "ETH"
        rawBalance:
          type: string
          description: "Balance in smallest unit (wei for ETH, lamports for SOL, raw token units for ERC-20/SPL)"
          example: "1500000000000000000"

    PolicyDeniedError:
      type: object
      properties:
        code:
          type: string
          example: POLICY_DENIED
        message:
          type: string
          example: Transaction denied by policy
        deniedBy:
          type: object
          properties:
            scopeName:
              type: string
            permissionType:
              type: string
            condition:
              type: object
              properties:
                resource:
                  type: string
                comparator:
                  type: string
                reference: {}

    FaucetRequest:
      type: object
      required:
        - walletId
      properties:
        walletId:
          type: string
          description: ID of the wallet to receive testnet tokens
          example: "0a1b2c3d-4e5f-6789-abcd-ef0123456789"
        chain:
          type: string
          description: Target testnet chain
          default: ETHEREUM_SEPOLIA
          example: "ETHEREUM_SEPOLIA"

    FaucetResponse:
      type: object
      required:
        - transactionHash
        - amount
        - chain
        - walletId
        - address
      properties:
        transactionHash:
          type: string
          description: On-chain transaction hash of the faucet transfer
          example: "0x1234abcd..."
        amount:
          type: string
          description: Amount of testnet tokens sent in standard units
          example: "0.01"
        chain:
          type: string
          description: Chain the tokens were sent on
          example: "ETHEREUM_SEPOLIA"
        walletId:
          type: string
          description: ID of the wallet that received the tokens
          example: "0a1b2c3d-4e5f-6789-abcd-ef0123456789"
        address:
          type: string
          description: On-chain address that received the tokens
          example: "0x742d35Cc6634C0532925a3b844Bc9e7595f..."

    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Machine-readable error code for programmatic handling
          example: "INVALID_REQUEST"
        message:
          type: string
          description: Human-readable error message
        transactionId:
          type: string
          format: uuid
          description: "Persisted transaction record id. Present only when a broadcast request failed after a history row was created."
        failureStage:
          type: string
          enum: [mpc_sign, signature_apply, signer_verify, broadcast, monitor_timeout]
          description: "Which stage of the broadcast lifecycle failed. Present on persisted broadcast failures."
        failureCode:
          type: string
          description: "Machine-readable failure reason from the underlying broadcast helper (e.g. `INSUFFICIENT_NATIVE_BALANCE`, `EXECUTION_FAILED`). Present on broadcast-stage failures."
        signedTransaction:
          type: string
          description: "Legacy signed transaction bytes on broadcast failures after signing. Sponsored responses never expose signed payloads."
      additionalProperties: true
      description: |
        All error responses include `code` and `message` fields. Some errors include extra fields (e.g. `walletId` on 409 Conflict).
        Broadcast failures that occur after a persisted transaction row is created include `transactionId`,
        `failureStage`, and (on broadcast-stage failures) `failureCode`, and also set the `x-transaction-id`
        response header. Legacy failures after signing can include `signedTransaction` for inspecting
        the already-signed bytes. Sponsored responses never expose signed payloads; recover the original
        operation with its tracking ID or original request and idempotency key.

        Common error codes: `INVALID_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `WALLET_ALREADY_EXISTS`, `WALLET_ALREADY_CLAIMED`, `RATE_LIMITED`, `INTERNAL_ERROR`.

  responses:
    BadRequest:
      description: Invalid request body
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "INVALID_REQUEST"
            message: "type must be one of EVM, SOLANA, COSMOS, STELLAR, SUI"

    Unauthorized:
      description: API key not provided
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "UNAUTHORIZED"
            message: "secret api key not provided"

    Forbidden:
      description: Invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "FORBIDDEN"
            message: "invalid secret api key"

    NotFound:
      description: Wallet not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "NOT_FOUND"
            message: "wallet not found"

    TooManyRequests:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the rate limit window resets
          example: 60
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "RATE_LIMITED"
            message: "Rate limit exceeded, try again shortly."

    IdempotencyConflict:
      description: A request with this idempotency key is currently being processed
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "CONFLICT"
            message: "A request with this idempotency key is currently being processed"

    Unprocessable:
      description: Idempotency key reused with a different request body
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "UNPROCESSABLE"
            message: "Idempotency key has already been used with different request parameters"

    SponsorshipUnavailable:
      description: Sponsorship cannot accept this eligible sponsored request; it does not fall back to wallet-paid gas.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "SPONSORSHIP_UNAVAILABLE"
            message: "Gas sponsorship is unavailable"

    ServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: "INTERNAL_ERROR"
            message: "Internal Server Error"
