Skip to main content
Deposit into gtUSDa on Base — a multi-depositor USDC vault on Aera V3. The same flow works for any Gauntlet vault.

Initialize the SDK

Set builderCode to your partner identifier — this enables attribution and fee sharing on every transaction the SDK builds. Builder codes must be requested from Gauntlet — using an unregistered string will append bytes to calldata but volume will not be counted.
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
})

Build the Deposit

VaultId.AeraUsdAlpha is a typed constant from the SDK — its value is 'gtusda', the vault’s identifier in the manifest. You can also discover vault IDs at runtime using getVaults.
import { getDepositTx, VaultId } from '@gauntlet-xyz/sdk/evm'

const steps = await getDepositTx(client, {
  vaultId: VaultId.AeraUsdAlpha,
  amount: 1_000_000n,
  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: { ... } }
// ]
The SDK resolves gtUSDa on Base to vault 0x000000000001CdB57E58Fa75Fe420a0f4D6640D5 and provisioner 0x18CF8d963E1a727F9bbF3AEffa0Bd04FB4dBdA07, checks USDC allowance on-chain, and builds the deposit with attribution baked into calldata.

Submit the Transactions

Each step exposes two surfaces. Pick the one that fits your stack — both carry attribution.

step.payload — scripts and embedded wallets

payload.data is the fully formed calldata with attribution already concatenated. Use estimateGas before sending — it simulates the exact bytes that will hit the chain (catching reverts before you spend gas) and returns the gas units needed. Wait for each receipt before the next step, since the deposit reverts if the approval hasn’t landed yet.
// steps = [approve, requestDeposit] — must execute in order
for (const step of steps) {
  // Estimate gas — simulates the exact calldata that will be broadcast.
  // Throws if the call would revert (e.g. insufficient balance, wrong allowance)
  // before spending gas. Returns the gas units needed for the tx.
  const gas = await publicClient.estimateGas({
    to: step.payload.to,
    data: step.payload.data,
    account: step.payload.account,
  })

  // Send with the estimated gas limit — prevents out-of-gas failures on-chain.
  const hash = await walletClient.sendTransaction({ ...step.payload, gas })

  // Wait for confirmation before the next step — the deposit will revert if the
  // approval isn't mined first.
  const receipt = await publicClient.waitForTransactionReceipt({ hash, confirmations: 2 })

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

step.tx — browser wallets (wagmi)

Use the structured ABI fields with writeContract. Pass step.tx.attribution as dataSuffix — wagmi appends it to the calldata before sending. Without it the transaction goes through but volume is not attributed.
// wagmi — MetaMask, Coinbase Wallet, WalletConnect, etc.
for (const step of steps) {
  await walletClient.writeContract({
    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
  })
}

Confirm the Deposit

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

const balance = await getUserCurrentBalance(client, {
  vaultId: VaultId.AeraUsdAlpha,
  address: '0xUser',
})
// returns:
// [{
//   chain: 'base',
//   token: '0xtoken',
//   decimals: 6,
//   pendingDeposit: 1_000_000n, // locked in provisioner, not yet earning yield — moves to `balance` once solver settles (~2 hours)
//   balance: 0n,
//   pendingWithdraw: 0n,
// }]

Withdraw from gtUSDa

Same pattern — build steps, then submit using whichever path matches your stack.
import { getWithdrawTx, VaultId } from '@gauntlet-xyz/sdk/evm'

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

// step.payload path
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}`)
}
Other withdraw variants:
// 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,
})

What’s Next

Show User's Return

Show the user’s gtUSDa position, ROI, and activity history.

Track Your Attribution

Inspect deposit volume and attribution data for your integration.

Attribution

How ERC-8021 builder codes work and how to verify attribution is tracked.

SDK Reference

Full constructor, data methods, transaction methods, and error reference.