Embedded Yield
Build customer-signed deposits, balances, and withdrawals into your app, from first request to go-live.
Embedded Yield lets your customers earn from their own wallets inside your app. Your backend calls SDP with your API key, your app asks the customer's wallet to sign, and SDP verifies, broadcasts, and tracks every movement. SDP never holds the customer's key.
New to Earn? Run the Quickstart first, then come back to build the full integration.
How the pieces fit
| Part | Does | Never |
|---|---|---|
| Your backend | Holds the API key. Previews, builds, submits, and reads. Decides which customer may use which wallet. | Sends the API key to a browser or mobile app. |
| Your app | Shows quotes and status. Asks the customer's wallet to sign. | Broadcasts the transaction. |
| The customer's wallet | Signs the exact transaction SDP built. Owns the position and receives payouts. | Shares its private key. |
| SDP | Builds each transaction, verifies the signatures, records the movement, broadcasts it, and tracks it to finality. | Holds the customer's key or pools their funds. |
A deposit then runs: your backend builds, your app collects the signature, your backend submits, and your app shows the result as your backend tracks it.
Authenticate on your backend
Create an API key in each project with the grants your integration uses:
| Grant | Allows |
|---|---|
earn.read | Strategies, previews, positions, movements, earnings, and withdrawal requests. |
earn.execute | Builds and submits for deposits, withdrawals, and withdrawal requests. Includes earn.read. |
earn.manage | Building and submitting a withdrawal request cancellation. Includes earn.read. |
earn.execute and earn.manage do not include each other. The backend
module reads one SDP_API_KEY, so give that key every grant you call through
it. See Manage API keys.
Withdrawals and cancellations stay available after production access is removed or new deposits are disabled. A suspended organization still cannot authenticate, and Earn runs only on deployments whose release channel includes it.
Your API key can read every customer wallet your project has recorded. Before
any request, check that the signed-in customer owns the ownerAddress you
send, for example by verifying a message the wallet signed when the customer
linked it. Custody-wallet scopes on the API key do not apply to customer wallets:
the customer's signature authorizes each transaction.
Get the backend module
The dashboard generates a TypeScript module for a strategy with every call this guide uses:
- In the dashboard, open Markets > Embedded Yield > Integrate.
- Choose a strategy. Only fundable strategies can be selected.
- Click Copy all code, and save it as a server-only file such as
earn.ts. SetSDP_API_KEYin your backend's environment.
The module holds the strategy ID and the API URL for the project you
generated it in, so generate it again for production. It needs Node.js 20.18
or later, and @solana/kit only for its fee sponsor signer.
| Task | Module function |
|---|---|
| List the catalog | listEarnStrategies |
| Quote a deposit | previewEarnDeposit |
| Build a deposit | buildEarnDepositTransaction |
| Collect signatures | signEarnTransaction, createSponsorSigner |
| Submit a deposit | submitEarnDeposit |
| Track a movement | waitForEarnMovement, getEarnMovement |
| Read balances | listEarnPositions (every page), getEarnEarnings, listEarnMovements (one page per call) |
| Withdraw instantly | previewEarnWithdrawal, buildEarnWithdrawalTransaction, submitEarnWithdrawal |
| Choose a route | getEarnWithdrawalOptions |
| Request a withdrawal | previewEarnQueuedWithdrawal, previewEarnParRedemption, buildEarnQueuedWithdrawalRequest, buildEarnParRedemptionRequest, submitEarnWithdrawalRequest |
| Track requests | getEarnWithdrawalRequest, waitForEarnQueuedWithdrawal, listPendingEarnQueuedWithdrawals |
| Cancel a request | buildEarnWithdrawalRequestCancellation, submitEarnWithdrawalRequestCancellation |
When SDP refuses a request, a module function throws an EarnApiError with
status, code, details, and retryAfter. Branch on those, never on the
message.
Prefer raw requests? Every function maps to one endpoint in Deposit funds and Withdraw funds.
Make a deposit
| Step | Module function | Endpoint |
|---|---|---|
| 1. Quote | previewEarnDeposit | POST /v1/earn/vault-deposit-previews |
| 2. Build | buildEarnDepositTransaction | POST /v1/earn/external-wallet/deposit-transactions |
| 3. Sign | signEarnTransaction | In the customer's wallet |
| 4. Submit | submitEarnDeposit | POST /v1/earn/external-wallet/deposits |
| 5. Track | waitForEarnMovement | GET /v1/earn/external-wallet/movements/{movementId} |
buildEarnDepositTransaction previews the deposit itself when the strategy
needs a floor; call previewEarnDeposit too when you want to show your
customer a quote first.
signEarnTransaction(built, customerSigner, sponsorSigner?) passes the
base64 transaction to your signer, checks that the signed message is
unchanged, and adds your sponsor's signature when the build named a
feePayer. createSponsorSigner(secretKey, feePayerAddress) makes that
sponsor signer from your fee payer's 64-byte secret key, the array a Solana
CLI keypair file holds. The Quickstart shows a
customer signer for a browser wallet.
Three rules keep deposits safe:
- Sign exactly what SDP built. The wallet must not change or broadcast
the transaction. SDP refuses a changed one with
400. - Save before you submit. Store the idempotency key with the signed transaction, then submit. After an uncertain response, retry with both unchanged; see Retry safely.
- Track to the end. Save
movementIdand track it untilfinalizedorfailed.
Show balances
Read positions for withdrawals, earnings for the totals your customer sees, and movements for their history. Balances and earnings covers every field. Two rules matter most:
- Show a missing live value as unavailable, never as zero.
- Read every page of a list. A short list hides money.
Withdraw
- Read the position's live
withdrawableShareswithlistEarnPositions, then its routes withgetEarnWithdrawalOptions. - For an instant withdrawal, preview, build, sign, submit, and track, as for a deposit.
- For a solver queue or operator redemption, create a withdrawal request and
track it to
fulfilled,cancelled, orfailed. - On startup, restore every open request with
listPendingEarnQueuedWithdrawals.
| Step | Module function | Endpoint |
|---|---|---|
| Choose a route | getEarnWithdrawalOptions | POST /v1/earn/external-wallet/withdrawal-options |
| Withdraw instantly | buildEarnWithdrawalTransaction, submitEarnWithdrawal | POST /v1/earn/external-wallet/withdrawal-transactions, POST /v1/earn/external-wallet/withdrawals |
| Preview a request | previewEarnQueuedWithdrawal | POST /v1/earn/external-wallet/queued-withdrawal-previews |
| Build a request | buildEarnQueuedWithdrawalRequest | POST /v1/earn/external-wallet/withdrawal-request-transactions |
| Submit a request | submitEarnWithdrawalRequest | POST /v1/earn/external-wallet/withdrawal-requests |
| Track a request | getEarnWithdrawalRequest | GET /v1/earn/external-wallet/withdrawal-requests/{withdrawalRequestId} |
| Cancel a request | buildEarnWithdrawalRequestCancellation, submitEarnWithdrawalRequestCancellation | POST /v1/earn/external-wallet/withdrawal-request-cancel-transactions, POST /v1/earn/external-wallet/withdrawal-request-cancellations |
A withdrawal spends shares, not a dollar amount, and a request transaction is not a payout. Withdraw funds covers each route.
Go live
- Get production access: request mainnet access and switch to your production project, as steps 1 and 2 of Go to mainnet show. Embedded Yield needs no custody provider or wallet funding.
- Create a production API key with your Earn grants, and regenerate the backend module in your production project. Strategy IDs differ per environment.
- Sign with your customer's wallet on
solana:mainnet. - Support deposit floors. Nearly every production deposit requires
minSharesOut. The module sets it from a live preview when the strategy'sdepositSlippageasked for one at generation, so regenerate it if a build starts requiring a floor. - Persist idempotency keys and signed transactions before you submit.
- Restore open work after every restart: keep tracking each saved
movementIdthat is notfinalizedorfailed, and restore open requests withlistPendingEarnQueuedWithdrawals. - Confirm investor and wallet eligibility for each strategy you offer; see Strategies.
- Decide who pays fees, and if you sponsor them, bound and monitor your fee payer; see Fees and sponsorship.