Skip to main content

Initialize

import { GauntletClient } from '@gauntlet-xyz/sdk'
import { createPublicClient, createWalletClient, http, privateKeyToAccount } from 'viem'
import { base } from 'viem/chains'

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)

const publicClient = createPublicClient({
  chain: base,
  transport: http(process.env.RPC_URL_BASE!),
})

const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(process.env.RPC_URL_BASE!),
})

const client = new GauntletClient({
  evmClients: { [base.id]: publicClient },
  wallet: walletClient,
  builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team
})

Embedded Wallet (Privy)

The SDK reads only wallet.account.address — it never signs. Any viem-compatible wallet works, including embedded wallets from Privy, Dynamic, or similar providers.
import { GauntletClient } from '@gauntlet-xyz/sdk'
import { createPublicClient, createWalletClient, custom, http } from 'viem'
import { base } from 'viem/chains'
import { useWallets } from '@privy-io/react-auth'

const { wallets } = useWallets()
const embeddedWallet = wallets.find(w => w.walletClientType === 'privy')
const provider = await embeddedWallet.getEthereumProvider()

const publicClient = createPublicClient({ chain: base, transport: http(process.env.RPC_URL_BASE!) })

const walletClient = createWalletClient({
  account: embeddedWallet.address as `0x${string}`,
  chain: base,
  transport: custom(provider),
})

const client = new GauntletClient({
  evmClients: { [base.id]: publicClient },
  wallet: walletClient,
  builderCode: 'your-builder-code', // issued by Gauntlet — request from the partnerships team
})

Deposit

import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'

const steps = await getDepositTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n, // 1 USDC (6 decimals)
  receiver: '0xReceiver', // optional, defaults to wallet account
})
// returns:
// [
//   { payload: { type: 'approve', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } },
//   { payload: { type: 'requestDeposit', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } }
// ]

// steps must be executed in order — approve before requestDeposit
for (const step of steps) {
  // Estimate gas — simulates exact calldata before broadcast, catches reverts before spending gas
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })

  const hash = await walletClient.sendTransaction({ ...step.payload, gas })

  // Wait for confirmation before the next step — deposit reverts if preceding approve is not mined
  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })

  if (receipt.status !== 'success') {
    throw new Error(`Transaction reverted: ${step.payload.type}`)
  }
}

Withdraw

import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm'

// Withdraw entire position
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  entireAmount: true,
  receiver: '0xReceiver', // optional
})
// returns:
// [
//   { payload: { type: 'requestRedeem', to: '0x...', data: '0x...', account: '0x...' }, tx: { ... } }
// ]

for (const step of steps) {
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })

  const hash = await walletClient.sendTransaction({ ...step.payload, gas })

  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })

  if (receipt.status !== 'success') {
    throw new Error(`Transaction reverted: ${step.payload.type}`)
  }
}
import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm'

// By shares
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  shares: 500_000000000000000000n,
})

// By asset amount
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 500_000n,
})

Check User Current Balance

getUserCurrentBalance returns a unified view of all three balance states for a vault position. Call it on load and after any deposit or withdrawal transaction to keep your UI in sync.
import { getUserCurrentBalance } from '@gauntlet-xyz/sdk'
import { VaultId } from '@gauntlet-xyz/sdk/evm'

const balances = await getUserCurrentBalance(client, {
  vaultId: VaultId.AeraUsdAlpha,
  address: '0xUser',
})

console.log(balances)
// [
//   {
//     chain: 'base',
//     token: '0xUSDCADDRESS123',
//     decimals: 6
//     pendingDeposit: 0n,
//     balance: 1_000_000n,
//     pendingWithdraw: 0n,
//   },
// ]

After an async deposit

When a user deposits with depositMode: 'async', the amount appears in pendingDeposit while the vault solver queues it. During this time the funds are locked in the provisioner contract and are not yet earning yield. They move to balance once the solver settles the request — usually within 2 hours.
import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'
import { getUserCurrentBalance } from '@gauntlet-xyz/sdk'

// Submit the async deposit
const steps = await getDepositTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 500_000n,
  depositMode: 'async',
})

for (const step of steps) {
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })
  const hash = await walletClient.sendTransaction({ ...step.payload, gas })
  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })
  if (receipt.status !== 'success') {
    throw new Error(`Transaction reverted: ${step.payload.type}`)
  }
}

// funds are locked in pendingDeposit — not yet earning — until solver settles (~2 hours)
const balances = await getUserCurrentBalance(client, {
  vaultId: VaultId.AeraUsdAlpha,
  address: '0xUser',
})

After an async withdrawal

When a user withdraws with depositMode: 'async', the amount moves from balance to pendingWithdraw. During this time the vault shares have been redeemed and the assets are no longer earning yield, but they have not yet been transferred. Once the solver settles the request (usually within 2 hours), the assets become claimable as ERC-20 tokens in the receiver wallet.
import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm'
import { getUserCurrentBalance } from '@gauntlet-xyz/sdk'

// Submit the async withdraw
const steps = await getWithdrawTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  entireAmount: true,
  depositMode: 'async',
})

for (const step of steps) {
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })
  const hash = await walletClient.sendTransaction({ ...step.payload, gas })
  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })
  if (receipt.status !== 'success') {
    throw new Error(`Transaction reverted: ${step.payload.type}`)
  }
}

// assets are in pendingWithdraw — no longer earning — until solver settles (~2 hours)
const balances = await getUserCurrentBalance(client, {
  vaultId: VaultId.AeraUsdAlpha,
  address: '0xUser',
})

Sync deposit and withdrawal

Sync transactions skip the queue. The balance moves immediately — no pendingDeposit or pendingWithdraw. Morpho vaults operate in sync mode; Aera vaults operate in async mode.
import { getDepositTx, getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm'

// Sync deposit with a Morpho vault: balance goes straight to `balance`
const depositSteps = await getDepositTx(client, {
  vaultId: VaultId.BaseUsdcPrime, // Morpho vault — sync mode
  amount: 500_000n,
})

for (const step of depositSteps) {
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })
  const hash = await walletClient.sendTransaction({ ...step.payload, gas })
  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })
  if (receipt.status !== 'success') {
    throw new Error(`Transaction reverted: ${step.payload.type}`)
  }
}

// Sync withdraw: balance drops from `balance` immediately;
// tokens appear in the receiver wallet right away
const withdrawSteps = await getWithdrawTx(client, {
  vaultId: VaultId.BaseUsdcPrime, // Morpho vault — sync mode
  entireAmount: true,
})

for (const step of withdrawSteps) {
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })
  const hash = await walletClient.sendTransaction({ ...step.payload, gas })
  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })
  if (receipt.status !== 'success') {
    throw new Error(`Transaction reverted: ${step.payload.type}`)
  }
}

Wagmi / writeContract

When integrating with wagmi, use step.tx fields with writeContractAsync. Pass step.tx.attribution as dataSuffix — wagmi appends it to the calldata before sending. Omitting dataSuffix silently drops attribution: the transaction succeeds but volume is not tracked.
import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'
import { useWriteContract } from 'wagmi'

const { writeContractAsync } = useWriteContract()

const steps = await getDepositTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
})

for (const step of steps) {
  await writeContractAsync({
    address: step.tx.address,
    abi: step.tx.abi,
    functionName: step.tx.functionName,
    args: step.tx.args,
    account: step.tx.account,
    dataSuffix: step.tx.attribution, // required — omitting silently drops attribution
  })
}

Slippage

Both getDepositTx and getWithdrawTx accept a slippageBps parameter (integer basis points, e.g. 50 = 0.5%). Defaults to 100 (1%).
import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'

const steps = await getDepositTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
  slippageBps: 50, // 0.5% slippage tolerance
})

Error Handling

import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'
import {
  VaultNotFoundError,
  AccountRequiredError,
  RpcNotConfiguredError,
  UnsupportedDepositModeError,
  InvalidSlippageBPSError,
  UnimplementedFeatureError,
  UnitConversionError,
} from '@gauntlet-xyz/sdk'

try {
  await getDepositTx(client, {
    vaultId: VaultId.AeraUsdAlpha,
    amount: 1_000_000n,
  })
} catch (e) {
  if (e instanceof VaultNotFoundError)          { /* e.vaultId, e.chainId */ }
  if (e instanceof AccountRequiredError)        { /* add wallet to GauntletClient config */ }
  if (e instanceof RpcNotConfiguredError)       { /* e.chainId — add RPC URL for this chain */ }
  if (e instanceof UnsupportedDepositModeError) { /* e.vaultId, e.requested, e.available */ }
  if (e instanceof InvalidSlippageBPSError)     { /* e.slippage — must be integer 0–10000 */ }
  if (e instanceof UnimplementedFeatureError)   { /* e.feature */ }
  if (e instanceof UnitConversionError)         { /* e.vaultAddress */ }
}

Go Deeper

SDK Reference

Full constructor, methods, result shapes, and errors.

Deposit Your First Dollar

The full integration guide with confirmation and fallback guidance.