Skip to main content
Get your API key at hackathon-apikey.vercel.app.
Execute arbitrary smart contract calls from the private balance of the account bound to your unlink client (set via createUnlink(), see Quickstart). This enables DeFi interactions (swaps, lending, etc.) while keeping the sender private. If you are deciding between execute() and BurnerWallet, start with DeFi.
import { buildCall, toExecuteCall } from "@unlink-xyz/sdk";

// The npk field identifies which Unlink account receives the output.
// Pass your Unlink address (bech32m); the backend derives the cryptographic
// note public key from it automatically.
const myAddress = await unlink.getAddress();

const swap = buildCall({
  to: "0xDeFiRouter",
  abi: "function swap(address tokenIn, address tokenOut, uint256 amount) returns (uint256)",
  functionName: "swap",
  args: [tokenIn, tokenOut, amount],
});

const result = await unlink.execute({
  withdrawals: [{ token: "0xTokenIn", amount: "1000000000000000000" }],
  calls: [toExecuteCall(swap)],
  outputs: [
    { npk: myAddress, token: "0xTokenOut", min_amount: "900000000000000000" },
  ],
  deadline: Math.floor(Date.now() / 1000) + 3600,
});

const confirmed = await unlink.pollTransactionStatus(result.txId);
Execute exposes amount, recipient, and token type on-chain (since it calls an external contract), but keeps the sender private.

Parameters

ParameterTypeRequiredDescription
withdrawalsArray<{ token: string; amount: string }>YesTokens to withdraw from your private balance for the calls
callsArray<{ to: string; data: string; value: string }>YesExternal contract calls the adapter will execute
outputsArray<{ npk: string; token: string; min_amount: string }>YesExpected outputs to deposit back into private balance
deadlinenumberYesUnix timestamp (seconds) after which the transaction reverts
Returns: { txId: string; status: string; adapterDataHash: string } The adapterDataHash is a binding commitment between the ZK proof and the adapter calls.

Calldata helpers

The SDK exports helpers for building the calls array. These encode ABI calls into the { to, data, value } format that execute() expects.

buildCall

Encode a call from an ABI fragment string:
import { buildCall, toExecuteCall } from "@unlink-xyz/sdk";

const call = buildCall({
  to: routerAddress,
  abi: "function exactInputSingle((address,address,uint24,address,uint256,uint256,uint160)) returns (uint256)",
  functionName: "exactInputSingle",
  args: [params],
});

// Convert to wire format for execute()
const wireCall = toExecuteCall(call);

approve

Build an ERC-20 approval call (commonly needed before a swap):
import { approve, toExecuteCall } from "@unlink-xyz/sdk";

const call = approve(usdcAddress, routerAddress, 1000000n);
const wireCall = toExecuteCall(call);

contract

Create a contract helper that encodes method calls:
import { contract, toExecuteCall } from "@unlink-xyz/sdk";

const router = contract(routerAddress, [
  "function exactInputSingle((address,address,uint24,address,uint256,uint256,uint160)) returns (uint256)",
  "function exactOutputSingle((address,address,uint24,address,uint256,uint256,uint160)) returns (uint256)",
]);

const call = router.exactInputSingle(params);
const wireCall = toExecuteCall(call);

toCall

Convert a transaction from ethers populateTransaction or viem simulateContract into an adapter call:
import { toCall, toExecuteCall } from "@unlink-xyz/sdk";

// ethers
const tx = await router.exactInputSingle.populateTransaction(params);
const wireCall = toExecuteCall(toCall(tx));