# Build a custom multicall

> Markdown export of the Gearbox Protocol documentation page for agents and retrieval systems.

Canonical page: https://docs.gearbox.finance/developers/build-custom-multicall
Source file: content/developers/build-custom-multicall.mdx
Section router: https://docs.gearbox.finance/developers/llms.txt
Section full export: https://docs.gearbox.finance/developers/llms-full.txt

A multicall executes an ordered list of Credit Account actions in one
transaction. Use it when you want to choose the individual steps instead of
using a prepared operation from [Manage](https://docs.gearbox.finance/developers/manage-position) or
following a [delayed operation's intent](https://docs.gearbox.finance/developers/delayed-withdrawals#intent).

## Calls and their targets

Each `MultiCall` entry contains:

| Field | Meaning |
| --- | --- |
| `target` | Credit Facade for account actions, or an adapter allowed by this Credit Manager for protocol interactions |
| `callData` | Encoded function and arguments for that target |

Use SDK helpers to encode account actions and the [router](#generate-adapter-calls-with-the-router)
to generate supported conversion paths. Encode adapter calls directly only
for operations the router does not cover; do not substitute a protocol
contract or token address as the target. The batch executes in order and
reverts if a call or required account check fails.

## Available actions

### Credit Facade actions

These are the supported Credit Facade multicall entries for V3.1.
Their `target` is the account's Credit Facade. Token amounts use integer base
units, not human-readable decimals: `1_000_000n` represents 1 token with 6
decimals. In TypeScript, use `bigint` for amounts, quotas, and bitmasks.
`MAX_UINT256` below means `2^256 - 1`.

Debt changes must respect market limits and cannot be repeated in the same
block for an account. Full repayment requires all quotas to be disabled.
Opening, closing, liquidation, and bot-driven batches have additional
permissions; this guide uses an owner-driven update of an existing account.

These are entries inside the batch. Functions such as `multicall`,
`openCreditAccount`, and `closeCreditAccount` are transaction entry points,
not calls to nest inside it.

| Action | Arguments and behavior |
| :--- | :--- |
| `addCollateral` | Transfer approved tokens from the caller into the Credit Account. The caller must approve the Credit Manager.<ul><li><code>token</code> (address): ERC-20 token to add</li><li><code>amount</code> (uint256): Positive amount in the token's base units</li></ul> |
| `addCollateralWithPermit` | Add collateral using an EIP-2612 permit. The caller signs the permit with the Credit Manager as spender and the funding amount as value.<ul><li><code>token</code> (address): ERC-20 token to add</li><li><code>amount</code> (uint256): Positive amount in the token's base units</li><li><code>deadline</code> (uint256): Permit expiry: Unix timestamp in seconds</li><li><code>v</code> (uint8): Signature recovery component</li><li><code>r</code> (bytes32): Signature r component</li><li><code>s</code> (bytes32): Signature s component</li></ul> |
| `increaseDebt` | Borrow underlying into the Credit Account.<ul><li><code>amount</code> (uint256): Amount to borrow in underlying base units</li></ul> |
| `decreaseDebt` | Repay using underlying already held by the Credit Account.<ul><li><code>amount</code> (uint256): Amount to repay in underlying base units</li></ul>An amount covering the total debt requests full repayment. Use <code>MAX_UINT256</code> for full repayment; the Credit Account still needs sufficient underlying to fund it. |
| `withdrawCollateral` | Transfer collateral from the Credit Account to a recipient.<ul><li><code>token</code> (address): Collateral token to withdraw</li><li><code>amount</code> (uint256): Amount in token base units; <code>MAX_UINT256</code> for the full balance</li><li><code>to</code> (address): Recipient</li></ul>For a supported phantom token, withdrawal redeems it and sends its deposited token to the recipient. |
| `updateQuota` | Increase, reduce, or disable a collateral token's quota.<ul><li><code>token</code> (address): Collateral token; cannot be the underlying</li><li><code>quotaChange</code> (int96): Signed change in underlying base units</li><li><code>minQuota</code> (uint96): Minimum acceptable resulting quota in underlying base units</li></ul>A positive change increases quota; a negative change reduces it. Use <code>-2^95</code> to disable the quota and <code>0</code> as the minimum. The call reverts if the resulting quota is below <code>minQuota</code>. |
| `setBotPermissions` | Set or revoke a bot's access to the Credit Account.<ul><li><code>bot</code> (address): Bot contract</li><li><code>permissions</code> (uint192): Permission bitmask; <code>0</code> revokes access</li></ul>Combine permission bits with bitwise OR:<ul><li>Add collateral: <code>1n &lt;&lt; 0n</code></li><li>Increase debt: <code>1n &lt;&lt; 1n</code></li><li>Decrease debt: <code>1n &lt;&lt; 2n</code></li><li>Withdraw collateral: <code>1n &lt;&lt; 5n</code></li><li>Update quota: <code>1n &lt;&lt; 6n</code></li><li>Call adapters: <code>1n &lt;&lt; 16n</code></li></ul>A nonzero grant must exactly match the bot's <code>requiredPermissions()</code>. Other bits are rejected, including the set-bot-permissions bit (<code>1n &lt;&lt; 8n</code>). |
| `onDemandPriceUpdates` | Submit price updates. This call must be first in the batch. <code>executeCaUpdate</code> supplies the required updates.<ul><li><code>updates</code> (PriceUpdate[]): List of feed updates</li></ul>Each update contains:<ul><li><code>priceFeed</code> (address): Price feed to update</li><li><code>data</code> (bytes): Feed-specific encoded update payload</li></ul> |
| `storeExpectedBalances` | Record minimum balances to check after subsequent operations.<ul><li><code>balanceDeltas</code> (BalanceDelta[]): List of minimum balance changes</li></ul>Each delta contains:<ul><li><code>token</code> (address): Token whose balance will be checked</li><li><code>amount</code> (int256): Signed minimum change in token base units</li></ul>The stored floor is the balance at this call plus the delta. A positive delta requires a gain, a negative delta permits a bounded spend, and zero requires the starting balance to be preserved. For example, 100 starting units with a delta of −30 sets a floor of 70. The floor cannot be negative.Call <code>compareBalances</code> before storing another set of expectations. The ABI field is named <code>amount</code>, not <code>minBalanceDelta</code>. |
| `compareBalances` | No arguments. Check that current balances are at least the stored floors, then clear the expectations. Reverts if no expectations were stored.Outstanding expectations are checked automatically at the end of the batch. |
| `setFullCheckParams` | Configure the final collateral check. Unavailable during closure or liquidation.<ul><li><code>collateralHints</code> (uint256[]): Ordered token masks to check first; <code>[]</code> for no hints</li><li><code>minHealthFactor</code> (uint16): Minimum health factor in basis points</li></ul>Each hint is an individual, single-bit collateral token mask from the Credit Manager. Token addresses, combined masks, and the underlying mask are not valid.For the health factor, <code>10000</code> means 1.0 and <code>12000</code> means 1.2. Values below <code>10000</code> revert. |

### Adapter actions

Adapter actions depend on the integrations enabled for the selected Credit
Manager. Common categories include:

| Action | Examples |
| --- | --- |
| Swap | Exchange tokens through an allowed DEX adapter |
| Deposit or mint | Convert tokens into vault shares or a position token |
| Withdraw or redeem | Convert shares or position tokens back into assets |
| Add or remove liquidity | Enter or exit a liquidity pool |
| Stake or unstake | Enter or exit a supported staking position |
| Claim rewards | Collect rewards from a supported position |
| Request redemption | Start a [delayed operation](https://docs.gearbox.finance/developers/delayed-withdrawals) |
| Claim redemption | Collect proceeds through the integration's claim adapter |
| Integration-specific operations | For example, receive a Midas greenlisting or transfer a redeemer where permitted |

There is no single fixed list of adapter methods across all markets. For
supported conversions, the router assembles the adapter chain for you. Use the
selected suite's `creditManager.adapters` and each adapter's ABI to identify
its supported calls. A method's presence does not remove its permissions or
integration-specific requirements.

## Select the account

Start with the connected `sdk`, `owner`, and `position` from
[Manage](https://docs.gearbox.finance/developers/manage-position#find-your-leveraged-positions).

```typescript
import type { MultiCall } from "@gearbox-protocol/sdk/onchain";

const chainId = position.chainId;
const chain = sdk.onchain.chain(chainId);
const account = await chain.accounts.getCreditAccountData(position.creditAccount);
if (!account) throw new Error("Credit Account not found");
const suite = chain.marketRegister.findCreditManager(account.creditManager);
const facade = suite.creditFacade;
```

## Assemble the calls

For example, repay part of a loan with wallet funds: **add underlying → repay**.
Here, `repayAmount` is a positive `bigint` in the pool underlying's base units.
This example uses an ordinary ERC-20 underlying; RWA markets may require
additional wrapping or factory-specific steps.

```typescript
if (repayAmount <= 0n) throw new Error("Repayment must be positive");
const calls: MultiCall[] = [
  ...facade.prepareAddCollateral([
    { token: account.underlying, balance: repayAmount },
  ], {}),
  facade.prepareChangeDebt(repayAmount, true), // true means decrease debt
];
```

Approve the funding amount to the SDK-resolved spender before execution,
as described in [Repay](https://docs.gearbox.finance/developers/manage-position#repay). Partial repayment
must leave debt above the market minimum. These helpers encode calls; they
do not calculate an affordable amount or validate the operation.

For other batches, order the steps by their dependencies:

- **Claim → convert → repay → withdraw:** claim before spending its proceeds.
- **Quotas:** include required quota updates when changing collateral; the
  transaction builder does not derive them for you.
- **Swap limits:** preserve the route's minimum outputs and balance checks.
- **Final state:** leave sufficient eligible collateral for the remaining
  debt. A claimed token may need conversion before its value counts toward HF.

## Generate adapter calls with the router

The router is an onchain contract that builds a conversion path using the
adapters available in the relevant Credit Manager. It returns the ordered
adapter calls needed to execute that path.

Use `chain.routerFor(account)` to find a conversion path through the market's
supported integrations. This is a read-only quote: it returns ordered
`route.calls` to insert into your batch, not a router transaction to send.

As an alternative to the wallet-funded example above, **convert collateral →
repay**. Here, `tokenIn` differs from the underlying, `inputBalance` is its
available balance when the route executes, and `swapAmount` is the portion to
convert. Amounts are `bigint` base units; `slippage` is in basis points
(`100` means 1%).

```typescript
if (swapAmount <= 0n || swapAmount > inputBalance) {
  throw new Error("Invalid conversion amount");
}
const route = await chain.routerFor(account).findManyToOnePath({
  creditAccount: account,
  creditManager: suite.creditManager,
  expectedBalances: [{ token: tokenIn, balance: inputBalance }],
  leftoverBalances: [{ token: tokenIn, balance: inputBalance - swapAmount }],
  target: account.underlying,
  slippage,
});
if (repayAmount <= 0n || repayAmount > route.minAmount) {
  throw new Error("Repayment exceeds the route's minimum output");
}
const calls: MultiCall[] = [
  ...route.calls,
  facade.prepareChangeDebt(repayAmount, true),
];
```

Preserve the returned sequence and its slippage and balance checks. `route.amount`
is the quoted output; `route.minAmount` is the slippage-adjusted minimum. The
example caps repayment at that minimum and must still satisfy debt, quota,
and health checks. Build and preview the complete batch below.

| Routing need | SDK method |
| --- | --- |
| Convert a single token's full balance | `findOneTokenPath` |
| Convert selected balances while retaining explicit amounts | `findManyToOnePath` with `expectedBalances` and `leftoverBalances` |
| Convert collateral and borrowed funds when opening | `findOpenStrategyPath` |
| Convert assets to underlying for closing | `findBestClosePath`; use the [Close guide](https://docs.gearbox.finance/developers/close-leveraged-position) for the complete operation |

For a **claim → convert** batch, quote with the balances expected after the
claim, including any pre-existing input-token balance, and place the claim
before `route.calls`. Do not reuse a quote made before settlement. A route
only supplies the conversion leg; add the required claim, debt, quota, and
withdrawal actions yourself. If no route is available, the batch is not ready
to execute.

## Build the transaction

```typescript
const tx = await chain.accounts.executeCaUpdate(account, calls);
```

Despite its name, `executeCaUpdate` only builds a transaction. It prepends
required price updates and chooses the Credit Facade or applicable RWA factory
as the transaction destination. It does not sign, send, or invent missing calls.

Continue with `chainId`, `tx`, and `owner` at
[Preview and check](https://docs.gearbox.finance/developers/build-preview-check#preview-and-check).
Use that page's transaction payload instructions for signing and execution.

## Include a delayed claim

For a fresh `claimable` entry from
[Monitor settlement](https://docs.gearbox.finance/developers/delayed-withdrawals#monitor-settlement), the
claim entry is:

```typescript
const claimCall: MultiCall = {
  target: claimable.claimCall.to,
  callData: claimable.claimCall.callData,
};
```

Place this entry before the calls that use its proceeds, replacing the
repayment example's funding step as appropriate. The claim does not execute
the recorded intent. Add your chosen conversion, repayment, quota, or withdrawal
calls so the complete batch passes the final health check.
For expected-balance checks around a claim, use
`chain.accounts.assembleClaimDelayedCalls(...)` with the lower-level withdrawal
data; the raw entry above does not add those checks.

## Source

- [Account transaction builder](https://github.com/Gearbox-protocol/sdk/blob/d9699ecce9e74d39651d9b3b455f93e9c67b3b85/src/onchain/accounts/CreditAccountsServiceV310.ts)
- [Credit Facade call helpers](https://github.com/Gearbox-protocol/sdk/blob/d9699ecce9e74d39651d9b3b455f93e9c67b3b85/src/onchain/market/credit/CreditFacadeV310Contract.ts)
- [Router path construction](https://github.com/Gearbox-protocol/sdk/blob/11ff4136681561df17e42f56015c636071f89957/src/onchain/router/RouterV310Contract.ts)
- [Complete Credit Facade multicall interface](https://github.com/Gearbox-protocol/core-v3/blob/510fc6541c3767ce825929b4c311826fe81d6fa5/contracts/interfaces/ICreditFacadeV3Multicall.sol)
- [Credit Facade argument validation](https://github.com/Gearbox-protocol/core-v3/blob/510fc6541c3767ce825929b4c311826fe81d6fa5/contracts/credit/CreditFacadeV3.sol)
- [Balance delta structure and checks](https://github.com/Gearbox-protocol/core-v3/blob/510fc6541c3767ce825929b4c311826fe81d6fa5/contracts/libraries/BalancesLogic.sol)
- [Price update structure](https://github.com/Gearbox-protocol/core-v3/blob/510fc6541c3767ce825929b4c311826fe81d6fa5/contracts/interfaces/base/IPriceFeedStore.sol)
