Writing transactions
Build swaps, deposits, loans, leverage and orders from typed arguments, and let the SDK derive every account.
Updated Nov 9, 2026
Every user action is one or a few instructions, and dusk.write has a typed builder for most of them.
A typed builder takes one object whose fields TypeScript checks, accepts amounts as bigint, number or string, and derives the vaults, positions and token programs from the market, the owner and the mints. It also adds the event-CPI accounts and, where the program needs it, the hLP reconciliation prefix. Anything else is built by name on the generic path.
Swaps
buildSwapTransaction takes the market, the trader, both mints, exactAssetIn and minAssetOut, and derives the reserve vaults, the trader's associated token accounts and the token programs. It fetches the market first: when either hLP vault has supply or residual exposure it prepends, exactly once, the five accounts the program needs (the srLP mint, both hLP srLP vaults and both interest vaults), then any transfer-hook extras you pass as remainingAccounts. The quick start has the full call.
Liquidity
const liquidityAccounts = {
market, owner, baseMint, quoteMint, srlpMint,
ownerBaseAccount, ownerQuoteAccount, ownerSrlpAccount,
}
await dusk.write.addLiquidityTransaction({
...liquidityAccounts,
baseDepositAmount, quoteDepositAmount, minSrlpAmount,
})
await dusk.write.removeLiquidityTransaction({
...liquidityAccounts,
srlpAmount, minBaseAmountOut, minQuoteAmountOut,
})A withdrawal must pass cash, market-health and slippage checks; simulate to learn which one binds. Harvesting yield is a separate instruction (below).
hLP deposits and withdrawals
Both yield-account PDAs for the vault must exist before the liquidity instruction runs, so these are composite builders that return setup instructions when needed:
const {
transaction,
setupInstructions,
baseYieldAccount,
quoteYieldAccount,
} = await dusk.write.depositSingleSided(
{ depositAmount, minHlpAmount },
{
payer: owner, owner, market, targetHlpMint, baseMint, quoteMint,
accounts: depositAccounts,
},
)
// setupInstructions is empty once both yield accounts exist.
await provider.sendAndConfirm(transaction)withdrawSingleSided has the same shape. The initializer is idempotent and safe to compose unconditionally, including when someone has prefunded the PDA address.
Both builders type-check arguments and accounts against the IDL. Pass raw amounts as bigint, safe integer number or decimal string; missing or unknown fields fail before encoding.
A deposit also settles the funding both vaults have accrued, in the same instruction. depositSingleSided always prepends the settlement accounts (the srLP mint, both hLP srLP vaults, and each side's interest vault, mint and reserve vault) ahead of your transfer-hook extras, and takes an optional marketSnapshot so it can skip fetching the market again. A deposit built through the generic path must carry the same prefix.
Collateral, borrowing and repayment
const position = { market, owner, positionId }
await dusk.write.depositCollateralTransaction({
...position, assetMint: collateralAssetMint, ownerAssetAccount,
depositAmount,
})
await dusk.write.buildBorrowTransaction({
...position, debtAssetMint, collateralAssetMint,
borrowAmount, minDebtAmountOut, minLiquidationCfBps,
})
await dusk.write.repayTransaction({
...position, debtAssetMint, collateralAssetMint,
ownerDebtAccount, repayAmount,
})
await dusk.write.withdrawCollateralTransaction({
...position, assetMint: collateralAssetMint, ownerAssetAccount,
withdrawAmount, minAssetAmountOut, minLiquidationCfBps,
})minLiquidationCfBps is the lowest collateral factor the borrower will accept; the program stores the factor it computes and refuses the transaction below that floor. positionId is any public key the owner chooses; one position can hold both collateral sides.
Repay with u64::MAX to clear a debt side exactly. Anyone may repay or donate collateral to a position they do not own, passing its owner as positionOwner; that grants no rights over it.
Leverage and orders
Opening a position is one instruction on the Dusk program:
await dusk.write.buildOpenLeverageTransaction({
market,
owner,
positionId,
debtAsset: "quote", // the side the position borrows
debtMint,
collateralMint,
marginAmount,
multiplierBps: 20_000, // 2x, at most 200_000
minCollateralOut,
})The builder opens the position for the signer; leave limitPriceNad out for a market entry. To open on behalf of another owner, pass positionOwner on the generic openLeverage path.
closeLeverageTransaction, addLeverageMarginTransaction, increaseLeverageTransaction and decreaseLeverageTransaction manage an open position, named by market, positionOwner, positionId, debtAsset and both mints. Dusk does not expose a margin-only withdrawal: use decreaseLeverageTransaction or closeLeverageTransaction when taking value out of a position.
Orders live in the leverage-delegate program and require a delegation on the position first: buildCreateLeverageDelegationTransaction, with the delegate program and the approved actions. Then dusk.orders.createOrderTransaction places a take-profit or stop-loss with the trigger closeout price in NAD and the fraction to close, and dusk.orders.cancelOrderTransaction removes one. Limit entries (createLeverageEntryOrder, which escrows margin with an expiry) and hLP stop-loss or stop-rate orders (createHlpOrder) use the checked generic builders on dusk.orders.instruction or dusk.orders.transaction.
Execution instructions (executeLeverageEntryOrder, executeHlpOrder, the before…/after… hooks with delegatedCloseLeverage) are for keepers; every executed order pays the protocol order fee, and orderProtocolFeeAccounts returns the treasury accounts to pass as protocolFee.
Referral
To bind a listed partner when debt is opened, use the referred builders; they derive the partner and accrual accounts, initialize the accrual idempotently and compose the setup with the debt instruction:
const { transaction, referralPartner, referralAccrual } =
await dusk.write.referredBorrow(
{ borrowAmount, minDebtAmountOut, minLiquidationCfBps },
{
payer: borrower, referrer, market, debtMint,
accounts: borrowAccounts,
},
)referredOpenLeverage is the leverage equivalent. Both validate their arguments and accounts before encoding, like the hLP builders.
Partners claim with claimReferralInterestTransaction and rotate their payout wallet with setReferralRecipientTransaction. Binding never changes the borrower's principal, debt, interest, health or liquidation terms.
Yield
harvest pays a yield account's claimable balance to its recipient's canonical associated token account. dusk.yield.buildHarvest builds it, creating the recipient's token account if needed and funding accrued interest first.
The caller may be the LP owner, the designated recipient, or a harvest authority the owner set with setHarvestAuthority; anyone else is rejected with InvalidSigner. setYieldRecipient changes the destination; only the owner can change either.
Market operators
Use the seven typed update constructors to validate parameter changes against the shared protocol policy. The operator or its manager announces one atomic bundle; there is no support, voting or queue step in Dusk core.
const update = concentrationParameterUpdate({
peakAmplificationNad: 20n * 1_000_000_000n,
coreHalfWidthBps: 100,
fadeWidthBps: 400,
rampDurationSlots: 432_000n,
})
const metadata = await uploadProposalMetadata({ title, markdown, upload })
const { proposal, transaction } = await dusk.operator.announce({
payer, operator, market, updates: [update], metadata,
})The metadata helper uploads and verifies the exact Markdown bytes. Render a rationale only after its length and SHA-256 match the committed metadata. Every bundle waits the maximum junior exit notice plus one nominal day; concentration ramps begin after eligible execution and retain their own minimum duration and value guards.
dusk.operator.execute builds execution and any required revenue-archive setup. A nonzero ownership successor must sign acceptance.
cancel invalidates the pending bundle; resolve closes a terminal proposal and refunds its rent. The operator's removeManager acts immediately and cancels a distinct manager's pending bundle.
Managers cannot transfer ownership, change revenue terms or supersede operator-authored changes. An ownership transfer always clears the manager; core renunciation is permanent.
dusk.operator.claim pays collected cash to its recorded recipient, including an archived recipient when revenueEpoch is supplied. Unpaid interest follows the recipient active when it is collected. See Market operators.
The generic path
Any instruction in the IDL can also be built by name:
const [yieldAccount] = deriveYieldAccountAddress(
market, owner, lpMint, assetMint, "hlp",
)
const transaction = await dusk.write.transaction(
"setYieldRecipient",
{ tokenKind: { hlp: {} }, recipient },
{ accounts: { market, owner, assetMint, lpMint, yieldAccount } },
)write.builder, write.instruction and write.rpc take the same arguments and return an Anchor method builder, a bare instruction or the signature of the sent transaction. TypeScript checks arguments and account names against the IDL.
Supply accounts Anchor cannot derive, including self-seeded market and existing order accounts. Wide integers accept BN, bigint, safe integer number or decimal string; missing, unknown or out-of-range values fail before encoding.
The path adds the event-CPI accounts but never the hLP prefix. Use it only for instructions without a typed builder, such as setYieldRecipient and setHarvestAuthority.
Sending safely
- Simulate, then send. Previews price a trade; only a simulation validates the accounts and surfaces program errors before you pay a fee.
- Compute and size. Composite flows (hLP with setup, referred debt, delegated closes) can need a requested heap frame or an address lookup table.
- Transfer hooks. For Token-2022 mints with hooks, resolve the hook accounts with
resolveTransferHookAccountMetasand pass them as remaining accounts after any prefix the builder adds. - Freshness. Check the deployment identity before enabling a write, as the app does; a stale client against a redeployed program fails with account or discriminator errors.
- Idempotency.
positionId, order ids and proposal nonces are yours to choose; reuse them deliberately, never accidentally.
See Errors and failure handling for the failures you will meet and what they mean.