Submit a deposit

Submits a deposit transaction signed by the customer's wallet.

POST/v1/earn/external-wallet/deposits

SDP checks that the signed bytes match the built transaction and that every required signature is valid (the customer's, plus your fee payer's when the build named one), records the movement, then broadcasts. Idempotency-Key is required: a retry with the same key returns the original movement with replayed: true, and each built transaction can be submitted once. Reusing a key with a different body, or submitting one build under a second key, returns 409 CONFLICT. Submit the original signed bytes even after the blockhash expires, since the wallet may already have broadcast them. After an uncertain response, retry with the same key and body, then read the movement; never replace a signed transaction whose outcome is unknown.

Requires an API key with the earn.execute action.

Headers

  • x-project-id · string · min length 1 · example prj_example

    Required for dashboard sessions. API keys use their assigned project.

  • Idempotency-Key · string · required · pattern ^[\x20-\x7e]{1,255}$ · example idempotency_example_12345

    Idempotency key for safely retrying mutating requests. 1-255 printable ASCII characters; echoed back on the response.

Request body

application/json · required

Submit the signed bytes back. Requires the Idempotency-Key header; a retry with the same key resolves the original movement or withdrawal request instead of acting twice.

  • transactionId · string · required · min length 1 · max length 128 · example earn_external_wallet_transaction_e7a88258-1d19-48f3-8d0e-81fefa4bf3b3

    The built transaction being submitted; single-use.

  • signedTransaction · string · required · min length 1 · max length 1700 · pattern ^[A-Za-z0-9+/]+={0,2}$ · example AUyx/sEc49/1l6jxo3nbViPAIUqdrXCo8Qx5SfDvIbtUI0zex3Bi6Cyhhn1QTVU8zWDzXTRyIX2aCmq9v7yk5AOAAQABAvSV602SJ67iQCUGLwHzVtPnlMbZqL79wnV/nb+Mi9SJBUpTWpkpIQZNJOhxYNo4fHw1td28kruB5B+oQEEFRI0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAtzZHAtZXhhbXBsZQA=

    Base64 wire bytes of the signed transaction. They must be byte-for-byte the built message with only signatures added.

Responses

  • 200 The recorded deposit

    • data · object · required

      • deposit · object · required

        One recorded external-wallet vault movement.

        • settlementOutcome · string · one of delivered, cancelled

        • movementId · string · required · example earn_movement_a6aed3f5-4f89-4dc6-9d4b-0864021bd80d

        • positionId · string · required · example earn_position_2f8b6b04-7147-487e-b760-b1caa4fbcb8b

        • provider · string · required · example kamino

        • providerReference · string · required · example 7uib8xGAwkaPz4ZGCA6t8sSEid5Yp9ty13PHUweTypx

        • direction · string · required · one of deposit, withdrawal

        • status · string · required · one of requested, submitted, confirmed, finalized, failed

          The movement's status. requested means SDP recorded the signed transaction but has not yet seen it on the network. confirmed is optimistic and can still be rolled back; only finalized and failed are final.

        • settlement · string · required · one of atomic, provider_order

          How the movement settles. atomic completes in one transaction, so you may show it as done at confirmed while SDP tracks finality. provider_order pays out later through the provider, so chain confirmation alone is not payout: the movement stays confirmed until the provider completes it, and only finalized means it is done.

        • signature · string · required

          The transaction signature, for explorers.

        • ownerAddress · string · required · example 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM

          The customer's wallet address.

        • amount · string · required · max length 128 · pattern ^(?=.*[1-9])\d+(\.\d+)?$ · example 25

          Requested quantity, denominated in denomination.

        • denomination · string · required · example 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU

          Token mint for a deposit; share mint for a withdrawal.

        • tokenMint · string · required · example 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU

          The position's deposit-token mint. Show tokenAmount in these units; amount and denomination carry the onchain quantity, which is shares on a withdrawal.

        • tokenAmount · string | null · required · max length 128 · pattern ^(?=.*[1-9])\d+(\.\d+)?$ · example 25

          Quantity in tokenMint units. Kamino deposits and all withdrawals require a finalized observation; null beforehand or when the actual debit/payout cannot be observed. Other deposits report their fixed input amount.

        • failureReason · string | null · required

        • createdAt · string · required · format date-time · example 2025-01-01T00:00:00.000Z

          ISO 8601 timestamp.

        • confirmedAt · string | null · required · format date-time · example 2025-01-01T00:00:00.000Z

          ISO 8601 timestamp.

        • settledAt · string | null · required · format date-time · example 2025-01-01T00:00:00.000Z

          ISO 8601 timestamp.

        • replayed · boolean

          True when the idempotency key already produced this movement and nothing was re-sent.

    • meta · object · required

      Response metadata.

      • requestId · string · required · min length 1 · example req_example

        Request identifier for tracing.

      • timestamp · string · required · format date-time · example 2025-01-01T00:00:00.000Z

        ISO 8601 timestamp.

  • 400 Bad Request

    • error · object · required

      Standard error payload.

      • code · string · required

        Machine-readable error code.

      • message · string · required

        Human-readable error message.

      • details · object

        Optional error details for debugging.

        • * · any
    • meta · object

      • requestId · string · min length 1 · example req_example

        Request identifier for tracing.

  • 401 Unauthorized

    Same fields as 400.

  • 403 Forbidden. Requires the earn.execute action: an API key must hold it (a module's manage or execute also covers its read), and a signed-in member's organization role must include it. Without it the error is INSUFFICIENT_PERMISSIONS and error.details.action names the action. Any other 403 names its own reason.

    Same fields as 400.

  • 404 Not Found

    Same fields as 400.

  • 409 Conflict

    Same fields as 400.

  • 429 Too Many Requests

    Same fields as 400.

  • 500 Internal Server Error

    Same fields as 400.

  • 503 Service Unavailable

    Same fields as 400.

Example request

IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST 'https://api-beta.solana.com/v1/earn/external-wallet/deposits' \
  -H "Authorization: Bearer $SDP_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transactionId": "earn_external_wallet_transaction_e7a88258-1d19-48f3-8d0e-81fefa4bf3b3",
    "signedTransaction": "AUyx/sEc49/1l6jxo3nbViPAIUqdrXCo8Qx5SfDvIbtUI0zex3Bi6Cyhhn1QTVU8zWDzXTRyIX2aCmq9v7yk5AOA"
  }'
// Reuse this key when you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api-beta.solana.com/v1/earn/external-wallet/deposits", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SDP_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "transactionId": "earn_external_wallet_transaction_e7a88258-1d19-48f3-8d0e-81fefa4bf3b3",
    "signedTransaction": "AUyx/sEc49/1l6jxo3nbViPAIUqdrXCo8Qx5SfDvIbtUI0zex3Bi6Cyhhn1QTVU8zWDzXTRyIX2aCmq9v7yk5AOA"
  })
});
import os, requests, uuid

# Reuse this key when you retry this request.
idempotency_key = str(uuid.uuid4())
r = requests.post("https://api-beta.solana.com/v1/earn/external-wallet/deposits",
  headers={"Authorization": f"Bearer {os.environ['SDP_API_KEY']}", "Idempotency-Key": idempotency_key},
  json={
    "transactionId": "earn_external_wallet_transaction_e7a88258-1d19-48f3-8d0e-81fefa4bf3b3",
    "signedTransaction": "AUyx/sEc49/1l6jxo3nbViPAIUqdrXCo8Qx5SfDvIbtUI0zex3Bi6Cyhhn1QTVU8zWDzXTRyIX2aCmq9v7yk5AOA"
  })

Example responses

JSON
{
  "data": {
    "deposit": {
      "movementId": "earn_movement_a6aed3f5-4f89-4dc6-9d4b-0864021bd80d",
      "positionId": "earn_position_2f8b6b04-7147-487e-b760-b1caa4fbcb8b",
      "provider": "kamino",
      "providerReference": "C7N2fqV2GvhFghDf5PJSwstyjedjLNCY1cm2Ak7TeU83",
      "direction": "deposit",
      "settlement": "atomic",
      "signature": "3dhkSPLiQZQPXL96fWzMdWJtdbtAffkBKXw2xhsYbV3VNSnYrVAcdx1czsnLsE1ou4SvLecGdSYe4QpAuZdfjJAf",
      "ownerAddress": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      "amount": "25",
      "denomination": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      "tokenMint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      "failureReason": null,
      "createdAt": "2026-11-06T17:57:30.817Z",
      "status": "submitted",
      "tokenAmount": null,
      "confirmedAt": null,
      "settledAt": null,
      "replayed": false
    }
  },
  "meta": {
    "requestId": "req_example",
    "timestamp": "2025-01-01T00:00:00.000Z"
  }
}
JSON
{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "Missing action: earn.execute",
    "details": {
      "action": "earn.execute"
    }
  },
  "meta": {
    "requestId": "req_example"
  }
}

Error responses use the standard error shape.