> For the complete documentation index, see [llms.txt](https://docs.swapper.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.swapper.finance/widget-integration/custom-contract-calls.md).

# Custom Contract Calls

Custom contract calls let you run **additional on-chain actions after the deposit swap completes** — all in the same user flow. Stake the received tokens, wrap them, add liquidity, deposit into a vault: any contract interaction, chained.

The widget executes the flow as: **swap → deliver → your calls, in sequence**. If any call reverts, the whole thing reverts.

## The `ContractCall` object

Each call is:

```typescript
interface ContractCall {
  callType: ContractCallType; // how to handle amounts
  target: string;             // contract to call
  value: string;              // native value in wei (usually "0")
  callData: string;           // ABI-encoded function call
  payload: string;            // extra data for balance encoding
}
```

Pass an array as `customContractCalls`:

```typescript
import { SwapperIframe, ContractCallType } from "@swapper-finance/deposit-sdk";

const swapper = new SwapperIframe({
  integratorId: "your-id",
  dstChainId: "1",
  dstTokenAddr: "0x…",        // token the calls operate on
  depositWalletAddress: "0x…",
  customContractCalls: [ /* calls */ ],
});
```

## Call types

`ContractCallType` controls how the executor fills in amounts — because the exact post-swap balance isn't known when you build the calls.

| Type                    | Value | Behavior                                                                                                                                                                                                       |
| ----------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEFAULT`               | `0`   | Use the exact amounts encoded in `callData`; nothing is rewritten. `payload` is `"0x"`.                                                                                                                        |
| `FULL_TOKEN_BALANCE`    | `1`   | Overwrite one argument of `callData` with the **full token balance** at execution time. `payload` names the token and which argument to overwrite — see [Balance payload encoding](#balance-payload-encoding). |
| `FULL_NATIVE_BALANCE`   | `2`   | Send the **entire native balance** as the call's `value`. `payload` is `"0x"` — nothing in `callData` is rewritten.                                                                                            |
| `COLLECT_TOKEN_BALANCE` | `3`   | Book-keeping step that snapshots a token's balance for a later call. `payload` is **only** the token address; `target`/`callData` are empty.                                                                   |

## Balance payload encoding

`FULL_TOKEN_BALANCE` is the type you'll reach for most, and its `payload` is the part that trips people up. The rule:

```ts
payload = abiCoder.encode(["address", "uint256"], [tokenAddress, amountArgIndex]);
```

**`amountArgIndex` is the zero-indexed position of the `amount` argument in&#x20;*****your*****&#x20;target function's signature** — i.e. which 32-byte word of the calldata the executor overwrites with the token balance. It is **not** a fixed constant, and it is unrelated to the placeholder value you put in `callData`. Compute it from your own function:

| Function                                                                           | `amount` is argument… | `amountArgIndex` |
| ---------------------------------------------------------------------------------- | --------------------- | ---------------- |
| `approve(address spender, uint256 amount)`                                         | 2nd                   | `1`              |
| `transfer(address recipient, uint256 amount)`                                      | 2nd                   | `1`              |
| `deposit(uint256 _amount, bool _shouldQueue, bytes[] _data)`                       | 1st                   | `0`              |
| `depositFor(address currency, address forAddress, uint256 amount, uint256 broker)` | 3rd                   | `2`              |

In `callData`, put a placeholder (`0`) at that same argument — the executor overwrites it with the real balance at runtime. **A wrong `amountArgIndex` patches the wrong word of your calldata and the call will almost certainly revert** (e.g. using `0` for `depositFor` would overwrite the `currency` address with a number). The index equals the argument position whenever every preceding argument is a value type — the usual case; functions with dynamic-type args before the amount are the exception.

The other two balance types are shaped **differently** — don't copy the `(address, uint256)` payload onto them:

```ts
// FULL_NATIVE_BALANCE — no payload; the full native balance is sent as `value`
{ callType: ContractCallType.FULL_NATIVE_BALANCE, target, value: "0", callData, payload: "0x" }

// COLLECT_TOKEN_BALANCE — payload is just the token address (no index)
{
  callType: ContractCallType.COLLECT_TOKEN_BALANCE,
  target: "0x0000000000000000000000000000000000000000",
  value: "0",
  callData: "0x",
  payload: abiCoder.encode(["address"], [tokenAddress]),
}
```

## ERC-20 helpers

The SDK ships helpers so you don't hand-encode common ERC-20 ops.

**Exact amount:**

```typescript
import { approve, transfer, transferFrom } from "@swapper-finance/deposit-sdk";

approve(tokenAddress, spender, amount);
transfer(tokenAddress, recipient, amount);
transferFrom(tokenAddress, from, to, amount);
```

**Full balance** (most common after a swap, when the amount isn't known ahead of time):

```typescript
import { approveBalance, transferBalance } from "@swapper-finance/deposit-sdk";

approveBalance(tokenAddress, spender);   // approve the whole balance
transferBalance(tokenAddress, recipient); // transfer the whole balance
```

Both helpers target standard ERC-20 `approve`/`transfer`, where `amount` is argument index `1` — a handy reference for the rule above. For a non-standard target function, build the `payload` with the exported helper instead of hand-rolling the tuple (harder to misread as "always 0"):

```typescript
import { encodeBalancePayload } from "@swapper-finance/deposit-sdk";

// depositFor(address currency, address forAddress, uint256 amount, uint256 broker)
payload: encodeBalancePayload(tokenAddress, 2); // amount is argument index 2
```

## Worked example: swap → stake → return receipt

Deposit any token, swap to POL, approve the staking pool, stake the full balance, and send the staked receipt token to the user:

```typescript
import {
  openSwapperModal,
  ContractCallType,
  approveBalance,
  transferBalance,
} from "@swapper-finance/deposit-sdk";
import { ethers } from "ethers";

const stakeInterface = new ethers.utils.Interface([
  "function deposit(uint256 _amount, bool _shouldQueue, bytes[] _data)",
]);
const abiCoder = new ethers.utils.AbiCoder();

openSwapperModal({
  integratorId: "your-integrator-id",
  dstChainId: "1",
  dstTokenAddr: "0x455…3F6", // POL
  depositWalletAddress: "0x928…8da",
  customContractCalls: [
    // 1. Approve the staking pool to pull POL
    approveBalance(
      "0x455…3F6", // POL
      "0xCfa…d67", // pool
    ),

    // 2. Stake the full POL balance
    {
      callType: ContractCallType.FULL_TOKEN_BALANCE,
      target: "0xCfa…d67",
      value: "0",
      // amountArgIndex is 0 ONLY because `_amount` is deposit()'s 1st argument.
      // For e.g. transfer(address, uint256) it would be 1 — see
      // "Balance payload encoding" above. Don't copy the 0 blindly.
      callData: stakeInterface.encodeFunctionData("deposit", ["0", true, ["0x"]]),
      payload: abiCoder.encode(
        ["address", "uint256"],
        ["0x455…3F6", "0"], // [token, amountArgIndex]
      ),
    },

    // 3. Send the staked receipt token to the user
    transferBalance(
      "0x2ff…753", // stPOL
      "0x928…8da", // recipient
    ),
  ],
});
```

## Updating calls at runtime

```typescript
swapper.updateCustomContractCalls([ approveBalance("0x…", "0x…") /* … */ ]);
// or
swapper.updateConfig({ customContractCalls: [ /* … */ ] });
```

## Guidelines

* **Order matters.** Calls run sequentially — approvals before the operations that need them.
* **Get `amountArgIndex` right** for `FULL_TOKEN_BALANCE` — it's the position of the `amount` argument in *your* function, not always `0`. See [Balance payload encoding](#balance-payload-encoding).
* **All-or-nothing.** A revert anywhere rolls back the entire sequence.
* **Test with small amounts first.** Chained calls are easy to get subtly wrong; verify the whole sequence before relying on it.
* **Gas** is estimated automatically for the chain.
