A complete DeFi transaction integration spans three Binance Web3 APIs:
- DeFi API discovers the investment and builds the unsigned transaction calldata.
- Transaction API broadcasts the transaction after your wallet signs it.
- Wallet API reads the resulting on-chain transaction and its status.
The DeFi API and Transaction API never hold your private key. They only return unsigned transaction data or relay a transaction that you have already signed. Keep signing operations in your own wallet or signer.
Every request to these APIs is authenticated with your API Key and Secret Key. See Authentication for the required headers and HMAC signing details.
End-to-End Flow
Code
Steps 1, 2, 3 use the DeFi API; steps 5 and 6 use the Transaction API and Wallet API respectively.
The build endpoint you call in step 2 depends on the action: /deposit, /redeem, /lp-add,
/lp-remove, or /claim (plus the pure-computation /lp-add/calculate before an LP add).
Step 0 â Authenticate API Requests
All requests in this guide are authenticated. Use the exact URL path, including /build, when
calculating X-OC-SIGN; the API request signature is separate from the on-chain wallet signature.
See Authentication for the required headers, HMAC-SHA256 pre-hash, timestamp
window, and rate limits.
Step 1 â Discover the Investment
Every build request is keyed by investmentId, which is chain-agnostic (the service resolves the
chain from it â you do not pass binanceChainId, except for one claim case noted below).
- Use
POST /api/v1/defi/data/investment/listor/investment/detailto find theinvestmentIdfor the pool / vault / strategy the user wants. - For
claimwithclaimType=REWARD_PROTOCOL, usePOST /api/v1/defi/data/protocol/listto find thedefiProtocolId(e.g.pancakeswap3). - For
claimwithclaimType=REDEMPTION, theredemptionIdcomes from the user'sposition/listresponse (redemptionIdfield on pending-redemption positions).
Step 2 â Build the Transaction
Call the build endpoint with address, investmentId, the action-specific fields, and optionally
simulate=true. The response data is a DefiTxResponse: an ordered dataList of
DefiCallDataItem (APPROVE first when needed, then the main action) and, when simulate=true, a
preview object.
Sample build call â deposit
Code
Code
Sign and broadcast every dataList item, in order. If callDataType=APPROVE is present, it
must be signed, broadcast, and confirmed on-chain before the main action â otherwise the main
transaction reverts for lack of allowance.
Step 3 â Preview Before Signing (optional)
Set simulate=true on any build request to receive data.preview without changing on-chain state.
The preview tells you whether the action is expected to succeed, the projected balanceChange
(positive = received, negative = spent), the estimated network fee (feeAndContract), and â for
lending protocols â the healthFactor change. Use it to confirm slippage, gas, and health-factor
impact before proceeding with signing.
preview shape:
| Field | Meaning |
|---|---|
success | Whether the simulated tx is expected to land. false means the action would revert on-chain â do not proceed with signing. When success=false carries a non-blank errorMessage (a revert), the request returns the revert error code 40484 / 40485 instead of the preview body (see Error Codes). |
balanceChange | Signed token deltas (amount positive = received, negative = spent). Empty array when the simulation produces no deltas (e.g. nothing to claim). |
feeAndContract.estimatedNetworkFee | Estimated gas cost â amount / tokenSymbol (the fee-paying native coin) / valueUsd, plus optional rentFeeAmount / rentFeeValueUsd / priorityFeeAmount. The rent / priority-fee fields are null on EVM chains and only populated on Solana. Fields are null when the simulation failed before fee estimation. |
feeAndContract.interactWith.address | The contract the action interacts with. |
healthFactor | Lending health-factor before / after. null for non-lending protocols or when unavailable. |
warnings | Non-blocking risk warnings; empty when none. |
errorMessage | When success=false and non-blank, the simulated tx would revert â the service maps it to a revert error code (40485 for "exceeds your current position", 40484 otherwise) and the request returns that code instead of this preview body. |
Solana preview notes
A Solana-specific point to surface to the user before signing:
- Rent â receiving a token on Solana for the first time opens an associated token account (ATA),
which charges a rent deposit (the most common rent source â not just new stake accounts or LP
positions). The rent is paid in SOL from the payer's balance, roughly 0.002 SOL per account, and
is unrelated to the received token amount. The preview surfaces it as
estimatedNetworkFee.rentFeeAmount(in SOL) /rentFeeValueUsd, alongsidepriorityFeeAmount. Closing the account later refunds the rent â except for some accounts created when opening an LP position, whose rent is non-refundable. Show this fee to the user before signing.
Code
A successful simulation returns success: true with the projected deltas, the estimated fee, and â
for lending protocols â the health-factor change:
Code
balanceChange entries are signed (amount positive = received, negative = spent). healthFactor
and warnings are populated only for lending protocols; non-lending actions return
healthFactor: null and an empty warnings array.
When the simulated tx would revert, the request returns an error code (HTTP 200, non-zero code)
instead of a 200 with preview.success=false. 40485 means the redeem amount exceeds the current
position; 40484 covers any other revert â read msg for the original revert reason (e.g. the
amount is below the protocol minimum) and do not prompt the user to sign:
Code
Revert codes (
40484/40485) are returned only whensimulate=true. The mapping from a revert message to a code is substring containment and is server-side configurable; an unmapped revert falls back to40484. See Error Codes.
Redeem waiting period
A /transaction/redeem response also carries data.redeemDelayDays â the redeem waiting period as
a [min, max] day pair of decimal strings:
redeemDelayDays | Meaning |
|---|---|
[] (empty) | Instant, no waiting period |
["7","7"] | Fixed 7 days |
["7","10"] | 7â10 days |
The wait starts after the redeem transaction is confirmed on-chain. redeemDelayDays is null on
the other build endpoints. Delays are configured in seconds server-side and rounded up to whole
days when surfaced, so a 2d4h cooldown returns 3.
Protocols with a waiting period in this release:
- EVM: Lista (
helio), Aster (astherus), and others. - Solana: every protocol below redeems in two steps (
/redeemâ cooldown â/claimREDEMPTION). Typical waits â order of magnitude only; always use the returnedredeemDelayDays:
| Protocol | Typical redeemDelayDays |
|---|---|
sol_staking (native staking) | 2â3 days |
marinadefinance liquid / native | 2â4 days / 2â3 days |
renzo (Solana) | 3â5 days |
solayer | 2â3 days |
jupiter jupSOL / Stake JUP | 2 days / 7 days |
fragmetric | 2â30 days, by fund |
jito | 2â3 days |
The per-protocol redeem/claim handle differs â see Solana async redemption.
Step 4 â Sign Locally
Use the wallet that owns each dataList[i].from. The Binance Web3 API must never receive a private
key or seed phrase.
EVM chains
Map each DefiCallDataItem to an EIP-1559 or legacy transaction and sign it. The resulting raw
signed transaction is a hex string beginning with 0x. EVM is one of two supported chain families
in this release â see Solana chains below for the other.
BNB Smart Chain is an EIP-1559 chain, so build responses populate
maxFeePerGas+maxPriorityFeePerGasand leavegasPricenullon BSC. Other EVM chains may populate either set â use the 1559 fields when present; fall back togasPricewhen it carries a value.valueis a hex string; all gas fields are decimal strings.
DefiCallDataItem field | Signed transaction field |
|---|---|
from | Signer / sender |
to | Transaction recipient |
data | ABI-encoded calldata |
value | Native-token value in wei (hex) |
gasLimit | Gas limit |
gasPrice | Legacy gas price in wei (null on EIP-1559 chains) |
maxPriorityFeePerGas | EIP-1559 priority fee in wei |
maxFeePerGas | EIP-1559 max fee per gas |
Python example: sign every dataList item
Install the signing and RPC libraries:
Code
The private key stays local and is never sent to Binance Web3 API. build_response is the JSON
response from a build endpoint. Sign each dataList item in order; broadcast and confirm the
APPROVE item before broadcasting the main action.
Code
The helper builds an EIP-1559 (type-2) transaction when maxPriorityFeePerGas is present, using the
build response's maxFeePerGas as the fee cap; otherwise it falls back to a legacy transaction and
estimates gasPrice from the node when the API omitted it. Nonces are base_nonce + i so the
APPROVE and the main action get distinct nonces â but you still must wait for each broadcast to
confirm before signing the next item, since the main action requires the APPROVE to be mined first.
The API request HMAC signature and the on-chain wallet signature are independent.
Solana chains
For Solana (binanceChainId = "CT_501"), a build response's dataList is always exactly one
item â there is no APPROVE (Solana has no ERC-20 allowance concept). That item carries a
base64-serialized wire transaction in data; the compute-budget fee is injected server-side for
every action (do not add ComputeBudget / priority-fee yourself). to and value are null.
Deserialize data, sign your fee-payer slot, re-serialize to base64, and broadcast. The fee-payer
is account_keys[0] â the only slot you sign.
DefiCallDataItem field | Solana signing interpretation |
|---|---|
from | base58 fee-payer public key (the user's signer) |
to / value | null â Solana encodes the target program inside the wire tx |
data | base64-serialized wire transaction (fee injected for all) |
gasLimit | estimated compute-unit consumption (on-chain limit ~1.2Ã) |
gasPrice | per-compute-unit fee in micro-lamports (already set) |
maxPriorityFeePerGas / maxFeePerGas | null â Solana uses the Solana fee model, not EIP-1559 |
Solana transactions reference a recent blockhash that expires in roughly 30 seconds. Sign and broadcast the transaction as soon as it is built. If the blockhash expires before broadcast, rebuild the calldata and try again.
Python example: sign the Solana dataList item
Install a Solana signing library (the example uses solders, the Rust-backed types used by
solana-py):
Code
The private key stays local and is never sent to Binance Web3 API. build_response is the JSON
response from a build endpoint. The Solana dataList has one item; deserialize it, sign your
fee-payer slot (account_keys[0]), and re-serialize.
Code
The example signs only the fee-payer slot. The API request HMAC signature and the on-chain wallet signature are independent.
Step 5 â Broadcast the Signed Transaction
Call the Transaction API POST /api/v1/dex/pre-transaction/broadcast-transaction for each signed
dataList item:
| Field | Value |
|---|---|
binanceChainId | The chain matching the investmentId |
signedTransaction | EVM chains: raw signed transaction, 0x-prefixed hex. Solana: base64. |
address | The user's wallet address (the from of the item) |
Broadcast the APPROVE item first and wait for its confirmation before broadcasting the main action
(EVM only â Solana dataList has a single item and no APPROVE). A successful response returns
data.txHash â keep it for the next step.
Sample Broadcast Call (EVM)
Code
Sample Broadcast Call (Solana)
Code
The X-OC-SIGN value above is the Binance Web3 API request signature. It is unrelated to the
on-chain wallet signature encoded in signedTransaction.
Step 6 â Query Transaction Details and Poll Status
Use the txHash returned by the broadcast response with the Wallet API endpoint
GET /api/v1/dex/post-transaction/transaction-detail-by-txhash:
Code
The response's data array contains the transaction detail. Inspect data[].txStatus:
txStatus | Meaning | Client action |
|---|---|---|
pending | The transaction has been broadcast but is not finalized | Poll again with backoff |
success | The transaction succeeded on-chain | Mark as confirmed |
fail | The transaction was mined but failed | Mark as failed; inspect gas, method, and transfer details |
Immediately after broadcast, the endpoint may return an empty data array while indexing catches up
â retry with backoff. Always pass both binanceChainId and the exact txHash; the APPROVE and the
main action have different hashes. For Solana, pass binanceChainId=CT_501 and the base58
transaction signature as txHash.
Calldata Validity & Approvals
- Calldata has no server-side expiry on EVM chains â the returned
dataListitems are static on-chain transaction data, not server-issued quotes; the build endpoint does not assign a TTL and the samedataListcan be re-signed and re-broadcast until it lands on-chain. There are two exceptions: liquidity-management transactions (lp-add,lp-remove) embed adeadlineparameter in the calldata â build time + 20 minutes â enforced on-chain: past that timestamp the transaction is not rejected at the API layer but reverts after broadcast (e.g.ExpiredDeadline), surfacing astxStatus=failwhen polling; so if more than 20 minutes have elapsed before signing, rebuild the calldata.lp-add/calculatebuilds no calldata and has no on-chain timeout, but its pool/tick snapshot ages just as fast â recalculate before building; and on Solana, the wire transaction references a recent blockhash that expires in roughly 30 seconds, so sign and broadcast it as soon as it is built; if it expires before broadcast, rebuild the calldata. - Re-build rather than cache â gas price and the position state move between build and sign, so call the build endpoint again if significant time has elapsed since the build response (especially before LP actions, and always for Solana given the blockhash window).
- APPROVE is an EVM-only, unlimited allowance â when an APPROVE item is present, it approves the
maximum amount (
type(uint256).max) to the spender contract returned in that item'sto. This is a one-time, per-token, per-spender approval; subsequent actions on the same token/spender reuse it (the build returns[DEPOSIT]only, no APPROVE, when the allowance is already sufficient). If your compliance posture forbids unlimited approvals, ensure the signer is aware before signing the APPROVE item, or revoke the allowance after the main action lands. Solana has no ERC-20 allowance concept, so itsdataListis always a single item with no APPROVE.
LP Add / Remove Specifics
-
Tick range â pick exactly one source:
nftId: append to an existing position, reusing its rangepriceRange: percentage bandtickLower+tickUpper: explicit rawint24pair
Rules:
- If more than one is supplied, only the highest-priority source is used and the others are
silently ignored (priority:
nftId>priceRange> explicit tick pair) - If none is supplied, the request is rejected
tickLower/tickUppermust be aligned to the pool'stickSpacing, otherwise the request is rejected
-
Size the paired token first â when you only have one side, call
POST /transaction/lp-add/calculateto get the paired token amount before/lp-add. This endpoint is pure computation â it does not touch the chain and does not deduct a fee. -
LP remove â keyed by
nftId+ratio(range(0, 1]); notokenListis needed, the per-token amounts are derived from the on-chain position.
Claim Specifics
claimType determines which companion fields are required:
claimType | Required fields |
|---|---|
REWARD_PROTOCOL | defiProtocolId + binanceChainId |
REWARD_INVESTMENT | investmentId |
LP_FEE | investmentId + nftId |
REDEMPTION | investmentId + redemptionId â EVM (Lista/Aster/Solv) and Solana ticket protocols (renzo, marinadefinance liquid, jito, solayer, fragmetric, jupiter Stake JUP); investmentId + stakeAccounts â Solana native staking (sol_staking) and Jupiter jupSOL (claimed under sol_staking, see the table below) |
binanceChainId is normally resolved from investmentId and any client value is ignored â
except for REWARD_PROTOCOL with no investmentId, where the client must pass binanceChainId
because it is the only chain signal. tokenAddressList optionally narrows the claim scope.
Solana async redemption (two-step)
Several Solana protocols redeem asynchronously: the /redeem call deactivates / enqueues a
withdrawal, and the funds become claimable via /claim REDEMPTION after a cooldown. The cooldown
varies by protocol (2 to 30 days â see redeem waiting period); always
take it from the redeemDelayDays returned by the redeem response. The field you pass to identify
the position differs by protocol:
| Protocol (redeem â claim) | redeem identifies the position with | claim REDEMPTION identifies with | Where the claim handle comes from |
|---|---|---|---|
sol_staking (native staking) | stakeAccounts (deactivates the whole stake account; token/ratio ignored) | stakeAccounts (same address) | the stake account address you passed to redeem |
jupiter jupSOL (stake_sol) | token / ratio (SOL amount; minimum 1 SOL; redeem mints + deactivates a new stake account) | stakeAccounts (the stake account the redeem created) | position/list positionDetail.userStakeAccount under sol_staking (the stake account also equals redemptionId); claim works with either the sol_staking or the jupSOL investmentId + stakeAccounts |
jupiter Stake JUP (stake_jup) | token / ratio | redemptionId | position/list positionDetail.redemptionId (only after unlockTimeReached = true) |
renzo | token / ratio (burns VRT) | redemptionId | position/list positionDetail.redemptionId (only after unlockTimeReached = true) |
marinadefinance (liquid) | token / ratio | redemptionId | position/list positionDetail.redemptionId (the delayed-unstake ticket) |
jito | token / ratio | redemptionId | position/list positionDetail.redemptionId (the stake account the redeem created) |
solayer (sSOL) | token / ratio | redemptionId | position/list positionDetail.redemptionId (the stake account the redeem created; same mechanism as jito) |
fragmetric | token / ratio (locks the fragAsset in a withdrawal request) | redemptionId | position/list positionDetail.redemptionId (the withdrawal request_id; claimable once its batch is processed) |
Notes:
- For
sol_stakingandjupiterjupSOL,stakeAccountsmakesredemptionIdoptional (no redemption record). This release supports one stake account per call (stakeAccountslength at most 1). jupiterjupSOL is a cross-protocol flow: the redeem burns jupSOL and creates a native stake account; the subsequent claim is a nativeStake.Withdraw. The claim handle (userStakeAccount/redemptionId) appears undersol_stakingin the position list (not underjupiter); claim with either thesol_stakingor the jupSOLinvestmentId, together withstakeAccounts.- After redeem, poll
position/listwithforceRefresh=trueuntil the claim handle (userStakeAccount/redemptionId) appears andunlockTimeReached = true; then call/claim. marinadefinancenative redeem has no claim request field â the withdrawable exit account is discovered on-chain by the service at claim time.- Instant-redeem protocols (
kamino,jupiterlend_borrow_supply/jlp) have noREDEMPTIONclaim step â funds settle in the same transaction. - A
sol_stakingposition entry may also carry aredemptionIdequal to the stake account address, butclaimREDEMPTIONfor native staking takes it instakeAccountsâ passing it asredemptionIdis rejected with40453. - Do not modify the wire transaction after signing the fee-payer slot. The compute-budget fee is
already injected server-side for every action, so adding ComputeBudget / priority-fee instructions
yourself is unnecessary and will break the transaction. Re-signing a slot the service already
filled invalidates the pre-signed signature and the transaction fails on-chain. Some builds carry
two signature slots:
renzoredeem (base slot pre-signed server-side) and Raydium / Orca CLMM new-positionlp-add(the position-NFT mint slot pre-signed). For these you still sign only your fee-payer slot; the pre-signed slot is preserved bypopulate.
Field Mapping Across the Flow
| Source | Next step |
|---|---|
/data/investment/* â investmentId | Pass to the build endpoint |
/data/protocol/list â defiProtocolId | Pass to /claim with claimType=REWARD_PROTOCOL |
/data/position/list â redemptionId | Pass to /claim with claimType=REDEMPTION |
/transaction/* â dataList[i] | Map each item to a local wallet transaction and sign it (in order) |
/transaction/* (simulate=true) â preview | Inspect balanceChange / healthFactor before signing |
signed dataList[i] â signedTransaction | POST to /broadcast-transaction |
/broadcast-transaction â data.txHash | Pass to Wallet API transaction-detail-by-txhash |
Wallet API â data[].txStatus | Determine the final transaction outcome (pending / success / fail) |
Common Pitfalls
| Pitfall | Fix |
|---|---|
40102 Signature error | Include /build in the signed request path and sign the exact raw body; see Authentication. |
DeFi build rejected (40450â40460, 40480â40488) | The code identifies the failure phase â branch on it, not on msg. 40480 insufficient balance â top up; 40457 no liquidity to remove; 40460 simulation failed; 40482 RPC error â retry; 40485 redeem amount exceeds position; 40484 other preview revert (read msg); 40488 Solana SPL stake pool redeem failed (jito / jupSOL). 40459 is the catch-all â read msg. See Error Codes. |
| Main transaction reverts on broadcast | The APPROVE item must be broadcast and confirmed on-chain before the main action; do not skip or reorder it. |
| LP add rejected over ticks | tickLower / tickUpper must be raw int24 aligned to the pool's tickSpacing, or use priceRange instead. |
Claim 40454 on field mismatch | Provide the fields required by the claimType (see the table above); REWARD_PROTOCOL needs binanceChainId. |
| Solana tx expired before broadcast | Solana wire tx blockhash expires in ~30s. If broadcast fails after the blockhash expired, re-build the calldata â do not retry the old dataList. See Solana chains. |
Address / chain format mismatch (40001) | An EVM 0x-hex address passed for a Solana (CT_501) investment, or a base58 Solana address passed for an EVM investment, is rejected with 40001. Match the address format to the chain. |
Related Endpoints
| API | Endpoint | Purpose |
|---|---|---|
| DeFi API | POST /api/v1/defi/data/investment/list | Discover investmentId |
| DeFi API | POST /api/v1/defi/data/investment/detail | Inspect an investment product |
| DeFi API | POST /api/v1/defi/data/protocol/list | Discover defiProtocolId for REWARD_PROTOCOL |
| DeFi API | POST /api/v1/defi/data/position/list | Find redemptionId for REDEMPTION |
| DeFi API | POST /api/v1/defi/transaction/deposit | Build a deposit / stake transaction |
| DeFi API | POST /api/v1/defi/transaction/redeem | Build a redeem / unstake transaction |
| DeFi API | POST /api/v1/defi/transaction/lp-add | Build an add-liquidity transaction |
| DeFi API | POST /api/v1/defi/transaction/lp-add/calculate | Size the paired token before an LP add |
| DeFi API | POST /api/v1/defi/transaction/lp-remove | Build a remove-liquidity transaction |
| DeFi API | POST /api/v1/defi/transaction/claim | Build a claim / redemption transaction |
| Transaction API | POST /api/v1/dex/pre-transaction/broadcast-transaction | Broadcast a signed approval or main transaction |
| Wallet API | GET /api/v1/dex/post-transaction/transaction-detail-by-txhash | Query pending / success / fail status |