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
Parameter | Type | Required | Description |
---|---|---|---|
portfolioId | string | Yes | UUID of the portfolio |
Request Body
Field | Type | Required | Description |
---|---|---|---|
chain | string | Yes | Chain identifier (e.g. ethereum , solana ) |
asset | string | Yes | Asset contract/mint address to withdraw |
amount | string | Yes | Amount to withdraw (in asset's raw decimals) |
walletAddress | string | Yes | User wallet requesting withdrawal (used for signature/sponsor checks) |
sponsorAddress | string | No | Optional sponsor paying transaction fees |
destinationAddress | string | No* | Required for EVM withdrawals; optional for Solana |
sponsor_evm | boolean | No | Must be true for EVM withdrawals (default: false ) |
⚠️ EVM withdrawals require
destinationAddress
andsponsor_evm: true
. Solana withdrawals will use the samewalletAddress
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:
- Sign the UserOperation using your wallet
- 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
Field | Type | Required | Description |
---|---|---|---|
chain | string | Yes | Chain identifier (e.g. ethereum ) |
signedUserOp | object | Yes | Complete signed UserOperation object |
userOpHash | string | Yes | Hash 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:
- Get Payload: Call
/withdraw
to get the unsigned payload - Sign Payload: Sign the payload using the user's wallet
- Submit Signed Payload:
- EVM: Call
/submit_sponsored_transaction
with the signed UserOperation - Solana: Submit the signed transaction directly to a Solana node
- EVM: Call
- Monitor Status:
- EVM: Use the returned
transactionId
to poll transaction status - Solana: Monitor the transaction on the Solana blockchain
- EVM: Use the returned
- 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
andevmUserOpHash
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) andfeeAmount
(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 Code | Message Description |
---|---|
INSUFFICIENT_BALANCE | Vault lacks available funds for withdrawal |
INVALID_AMOUNT | Amount format is invalid or too small |
UNSUPPORTED_ASSET | Asset is not supported for withdrawal |
VAULT_MAINTENANCE | Vault is temporarily unavailable |
UNSUPPORTED_WITHDRAWAL_TYPE | Non-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
andsponsor_evm: true
: For EVM chains, both fields are mandatory. Ifsponsor_evm
isfalse
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. IfdestinationAddress
is omitted, funds will be sent towalletAddress
. - ⚠️ 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.