Skip to content

Withdraw

Initiates a withdrawal of assets from a portfolio's vault to an external wallet.

⚠️ Important: Both EVM and Solana withdrawals are 2-step processes:

Step 1: Call /withdraw to get the unsigned payload Step 2: Sign the payload with your wallet Step 3: Submit the signed payload:

  • EVM: Call /submit_sponsored_transaction endpoint
  • Solana: Submit directly to a Solana node

Request URL

POST https://ddp.definitive.fi/v2/organization/portfolios/{portfolioId}/withdraw

Path Parameters

ParameterTypeRequiredDescription
portfolioIdstringYesUUID of the portfolio

Request Body

FieldTypeRequiredDescription
chainstringYesChain identifier (e.g. ethereum, solana)
assetstringYesAsset contract/mint address to withdraw
amountstringYesAmount to withdraw (in asset's raw decimals)
walletAddressstringYesUser wallet requesting withdrawal (used for signature/sponsor checks)
sponsorAddressstringNoOptional sponsor paying transaction fees
destinationAddressstringNo*Required for EVM withdrawals; optional for Solana
sponsor_evmbooleanNoMust be true for EVM withdrawals (default: false)

⚠️ EVM withdrawals require destinationAddress and sponsor_evm: true. Solana withdrawals will use the same walletAddress unless overridden.

Example

const json = await AuthHelpers.signAndSend({
  path: "/v2/organization/portfolios/00000000-0000-0000-0000-000000000001/withdraw",
  method: "POST",
  body: {
    chain: "ethereum",
    asset: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
    amount: "500000000", // 500 USDC (6 decimals)
    walletAddress: "0x123456aaaaaaaaaaaaa45678",
    destinationAddress: "0x742d35aaaaaaaaaaaaaaaaaa",
    sponsor_evm: true, // required for EVM withdrawals
  },
  organizationId: "00000000-0000-0000-0000-000000000000",
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
});

Response

{
  // EVM withdrawals return evmUserOp and evmUserOpHash for signing
  evmUserOp: {
    sender: "0x742d35Cc6A7a0532c156C3F8E2A3CBc0c7a9C0e7",
    nonce: "0x1",
    initCode: "0x",
    callData: "0x123456789abcdef...",
    accountGasLimits: "0x000000000000000000000000000013dca00000000000000000000000000ee3b",
    preVerificationGas: "0x0",
    gasFees: "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    paymasterAndData: "0x",
    signature: "0x" // empty - to be signed by user
  },
  evmUserOpHash: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
  withdrawAmount: "500000000", // actual amount being withdrawn after fees
  feeAmount: "1000000" // fee amount in same asset units
}

Submit Sponsored Transaction (Step 2 for EVM)

This endpoint is ONLY for EVM withdrawals. After getting the withdrawal payload from the /withdraw endpoint, you must:

  1. Sign the UserOperation using your wallet
  2. Submit the signed UserOperation using this endpoint

This endpoint will return a transactionId that you can use to poll the transaction status.

Request URL

POST https://ddp.definitive.fi/v2/organization/portfolios/{portfolioId}/submit_sponsored_transaction

Request Body

FieldTypeRequiredDescription
chainstringYesChain identifier (e.g. ethereum)
signedUserOpobjectYesComplete signed UserOperation object
userOpHashstringYesHash of the UserOperation

Example: Complete EVM Withdrawal Flow

// STEP 1: Get withdrawal payload from /withdraw endpoint
const withdrawalResponse = await AuthHelpers.signAndSend({
  path: "/v2/organization/portfolios/00000000-0000-0000-0000-000000000001/withdraw",
  method: "POST",
  body: {
    chain: "ethereum",
    asset: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
    amount: "500000000",
    walletAddress: "0x123456aaaaaaaaaaaaa45678",
    destinationAddress: "0x742d35aaaaaaaaaaaaaaaaaa",
    sponsor_evm: true,
  },
  organizationId: "00000000-0000-0000-0000-000000000000",
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
});
 
// STEP 2: Sign the UserOperation with user's wallet (client-side)
const signedUserOp = await signUserOperation(withdrawalResponse.evmUserOp);
 
// STEP 3: Submit signed transaction via /submit_sponsored_transaction
const result = await AuthHelpers.signAndSend({
  path: "/v2/organization/portfolios/00000000-0000-0000-0000-000000000001/submit_sponsored_transaction",
  method: "POST",
  body: {
    chain: "ethereum",
    signedUserOp,
    userOpHash: withdrawalResponse.evmUserOpHash,
  },
  organizationId: "00000000-0000-0000-0000-000000000000",
  apiKey: process.env.API_KEY,
  apiSecret: process.env.API_SECRET,
});
 
// STEP 4: Use the transactionId to poll for transaction status
console.log("Transaction ID:", result.transactionId);

Response

{
  transactionId: "00000000-0000-0000-0000-000000000010",
  status: "pending",
  chain: "ethereum"
}

Withdrawal Process

Both EVM and Solana Withdrawals Follow These Steps:

  1. Get Payload: Call /withdraw to get the unsigned payload
  2. Sign Payload: Sign the payload using the user's wallet
  3. Submit Signed Payload:
    • EVM: Call /submit_sponsored_transaction with the signed UserOperation
    • Solana: Submit the signed transaction directly to a Solana node
  4. Monitor Status:
    • EVM: Use the returned transactionId to poll transaction status
    • Solana: Monitor the transaction on the Solana blockchain
  5. Settlement: The vault state will update shortly after on-chain confirmation.

Notes

  • Both EVM and Solana withdrawals follow a 2-step process: get payload → sign → submit
  • EVM withdrawals return evmUserOp and evmUserOpHash for signing with user's wallet
  • Solana withdrawals return a base64-encoded transaction string instead of EVM UserOperation
  • The response includes withdrawAmount (actual amount after fees) and feeAmount (fee charged)
  • EVM withdrawals use destinationAddress for final delivery
  • EVM signed transactions are submitted back to our /submit_sponsored_transaction endpoint
  • Solana signed transactions are submitted directly to a Solana node

Error Responses

Error CodeMessage Description
INSUFFICIENT_BALANCEVault lacks available funds for withdrawal
INVALID_AMOUNTAmount format is invalid or too small
UNSUPPORTED_ASSETAsset is not supported for withdrawal
VAULT_MAINTENANCEVault is temporarily unavailable
UNSUPPORTED_WITHDRAWAL_TYPENon-sponsored EVM withdrawals not supported

Common Pitfalls & Tips

  • ⚠️ All withdrawals are a 2-step process: You MUST call /withdraw first to get the payload, then sign it and submit it. For EVM, submit to /submit_sponsored_transaction. For Solana, submit directly to a Solana node.
  • ⚠️ Sign the UserOperation client-side: The signing must happen on the client side with the user's wallet. Never send private keys to the server.
  • ⚠️ portfolioId is required: Always use the correct portfolio UUID in the path. The backend uses this to route and authorize your request.
  • ⚠️ EVM withdrawals require destinationAddress and sponsor_evm: true: For EVM chains, both fields are mandatory. If sponsor_evm is false or missing, you'll get an UNSUPPORTED_WITHDRAWAL_TYPE error.
  • ⚠️ SVM withdrawals require the walletAddress field: For Solana, always provide the user's wallet address. If destinationAddress is omitted, funds will be sent to walletAddress.
  • ⚠️ Solana withdrawals submit to node: For Solana, after getting the payload from /withdraw and signing it, submit directly to a Solana node - do not use /submit_sponsored_transaction.
  • Vaults are auto-created: If a vault does not exist for the portfolio and chain, it will be created automatically during deposit. Withdrawals require the vault to be funded and deployed.
  • Authentication: Requests must be signed and include the correct headers. See Request Authorization for details on required headers and signature generation.
  • Test with small amounts first: To avoid failed transactions due to misconfiguration, always test with small amounts before production use.