Mint

deposit

TypeScript Example

Note: This example assumes you have a walletClient configured in your code base. Please refer to https://viem.sh/docs/clients/wallet for detailed documentation.

import {
  createPublicClient,
  createWalletClient,
  http,
  parseUnits,
  formatUnits,
  isAddress,
  type Address,
  type Hex,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
import { publicClient, walletClient } from "./client"

// --- Configuration ---

// Using 'as const' helps TypeScript infer the types more strictly.
const tellerAbi = [
  /* ... (start of ABI) ... */
  {
    inputs: [
      { internalType: "contract ERC20", name: "depositAsset", type: "address" },
      { internalType: "uint256", name: "depositAmount", type: "uint256" },
      { internalType: "uint256", name: "minimumMint", type: "uint256" },
      { internalType: "address", name: "to", type: "address" },
    ],
    name: "deposit",
    outputs: [{ internalType: "uint256", name: "shares", type: "uint256" }],
    stateMutability: "nonpayable",
    type: "function",
  },
  /* ... (rest of the ABI) ... */
] as const;

const tellerAddress = "0x...";

export const DEFAULT_DEPOSIT_SLIPPAGE = 50; // 50 bps (0.5%)

// --- End Configuration ---

// Example ERC20 token to deposit (USDC on Ethereum Mainnet).
const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const usdcDecimals = 6; // USDC has 6 decimal places.

// 1. Define an async function to call the deposit function
async function makeDeposit(
  {
    accountantAddress,
    depositTokenAddress,
    depositTokenDecimals = 6, // For most stable coin values the decimals be 6 though it is not a guarantee
    amount,
    slippage = 50, // 50 bps (0.5%),
    recipient,
  }: {
    accountantAddress: Address, 
    depositTokenAddress: Address,
    depositTokenDecimals?: number,
    amount: string,
    slippage?: number, // The slippage can be set by the user. It is a good practice to set a default amount.
    recipient: Address 
  }) {
  
  if (!isAddress(contractAddress)) {
    console.error(
      "Please replace '0x...' with your smart contract's address in the script.",
    );
    return;
  }

  try {
    // 2. Convert the human-readable amount to the token's base unit (uint256)
    // e.g., "100.5" USDC with 6 decimals becomes 100500000.
    const depositAmount = parseUnits(depositAmount, depositTokenDecimals);

    // 3. Fetch the rate from the blockchain
    // Optional: if you need to get the decimals from the depositToken you can
    // do that with a multicall. See Helper functions below
    const rate = await publicClient.readContract({
      abi: AccountantAbi,
      address: accountantAddress,
      functionName: 'getRateInQuote',
      args: [depositTokenAddress],
    })
    
    // 4. Calculate the minimumMint value.
    // See helper function section below to see logic for this calculation.
    const minimumMint = calculateMinimumMint(
      depositAmount,
      rate.result,
      slippage
    );

    // 5. Use `writeContract` to send the transaction
    // This returns a transaction hash immediately.
    const hash = await walletClient.writeContract({
      abi: tellerAbi,
      address: tellerAddress,
      functionName: "deposit",
      args: [depositTokenAddress, depositAmount, minimumMint, recipient],
      // The account is inferred from the walletClient
    });

    console.log(`Transaction sent! Hash: ${hash}`);
    console.log("Waiting for transaction to be mined...");

    // 6. Wait for the transaction to be included in a block
    const receipt = await publicClient.waitForTransactionReceipt({ hash });

    console.log("Transaction was successfully mined!");
    console.log(`- Block Number: ${receipt.blockNumber}`);
    console.log(`- Gas Used: ${receipt.gasUsed}`);
    console.log(
      `- View on Etherscan: https://etherscan.io/tx/${receipt.transactionHash}`,
    );

    // Note: The return value ('shares') is not directly available in the receipt.
    // To get it, you would need to parse the transaction logs for the 'Deposit' event.
  } catch (err) {
    console.error("Error sending deposit transaction:", err);
  }
}

// 7. Execute the function with example parameters
// This will deposit 50.0 USDC.
makeDeposit(accountantAddress, usdcAddress, usdcDecimals, "50.0", recipientAddress);

depositWithPermit

import {
  parseUnits,
  isAddress,
  type Address,
  type Hex,
  splitSignature,
} from "viem";
import { publicClient, walletClient } from "./client"; // Assumes client.ts is in the same folder

// --- ABIs ---

const tellerAbi = [
  /* ... (start of ABI) ... */
  {
    inputs: [
      { internalType: "contract ERC20", name: "depositAsset", type: "address" },
      { internalType: "uint256", name: "depositAmount", type: "uint256" },
      { internalType: "uint256", name: "minimumMint", type: "uint256" },
      { internalType: "uint256", name: "deadline", type: "uint256" },
      { internalType: "address", name: "to", type: "address" },
      { internalType: "uint8", name: "v", type: "uint8" },
      { internalType: "bytes32", name: "r", type: "bytes32" },
      { internalType: "bytes32", name: "s", type: "bytes32" },
    ],
    name: "depositWithPermit",
    outputs: [{ internalType: "uint256", name: "shares", type: "uint256" }],
    stateMutability: "nonpayable",
    type: "function",
  },
  /* ... (rest of the ABI) ... */
] as const;

const accountantAbi = [
  {
    inputs: [{ internalType: "address", name: "asset", type: "address" }],
    name: "getRateInQuote",
    outputs: [{ internalType: "uint256", name: "rate", type: "uint256" }],
    stateMutability: "view",
    type: "function",
  },
] as const;

const erc2612Abi = [
  {
    inputs: [{ internalType: "address", name: "owner", type: "address" }],
    name: "nonces",
    outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
    stateMutability: "view",
    type: "function",
  },
  {
    inputs: [],
    name: "name",
    outputs: [{ internalType: "string", name: "", type: "string" }],
    stateMutability: "view",
    type: "function",
  },
] as const;

// --- Helper Functions ---

// --- Main Deposit Function ---

async function makeDepositWithPermit({
  tellerAddress,
  accountantAddress,
  depositTokenAddress,
  depositTokenDecimals = 6,
  amount,
  slippageBps = 50, // 50 bps = 0.5%
  recipient,
}: {
  tellerAddress: Address;
  accountantAddress: Address;
  depositTokenAddress: Address;
  depositTokenDecimals?: number;
  amount: string;
  slippageBps?: number;
  recipient: Address;
}) {
  if (!isAddress(tellerAddress) || tellerAddress === "0x...") {
    console.error("A valid teller contract address is required.");
    return;
  }

  try {
    // 1. Calculate deposit amount and minimum shares to mint
    const depositAmount = parseUnits(amount, depositTokenDecimals);
    const rate = await publicClient.readContract({
      abi: accountantAbi,
      address: accountantAddress,
      functionName: "getRateInQuote",
      args: [depositTokenAddress],
    });
    const minimumMint = calculateMinimumMint(depositAmount, rate, slippageBps);

    // 2. Fetch necessary data for the EIP-2612 signature
    console.log("Fetching permit data (nonce and token name)...");
    const [nonce, name] = await Promise.all([
      publicClient.readContract({
        abi: erc2612Abi,
        address: depositTokenAddress,
        functionName: "nonces",
        args: [walletClient.account.address],
      }),
      publicClient.readContract({
        abi: erc2612Abi,
        address: depositTokenAddress,
        functionName: "name",
      }),
    ]);

    // 3. Define the EIP-712 typed data structure for the permit
    const domain = {
      name,
      version: "1", // Check the ERC20 contract for the correct version
      chainId: walletClient.chain.id,
      verifyingContract: depositTokenAddress,
    };

    const types = {
      Permit: [
        { name: "owner", type: "address" },
        { name: "spender", type: "address" },
        { name: "value", type: "uint256" },
        { name: "nonce", type: "uint256" },
        { name: "deadline", type: "uint256" },
      ],
    } as const;

    // A deadline of 30 minutes from now is generally safe.
    const deadline = BigInt(Math.floor(Date.now() / 1000) + 1800);

    const message = {
      owner: walletClient.account.address,
      spender: tellerAddress, // The contract we are giving permission to
      value: depositAmount,
      nonce,
      deadline,
    };

    // 4. Request the user to sign the permit message
    console.log("Requesting signature from wallet...");
    const signature = await walletClient.signTypedData({
      domain,
      types,
      primaryType: "Permit",
      message,
    });

    // 5. Split the signature into v, r, and s components
    const { v, r, s } = splitSignature(signature);

    // 6. Call `depositWithPermit` with all arguments
    console.log("Signature received. Sending transaction...");
    const hash = await walletClient.writeContract({
      abi: tellerAbi,
      address: tellerAddress,
      functionName: "depositWithPermit",
      args: [
        depositTokenAddress,
        depositAmount,
        minimumMint,
        deadline,
        recipient,
        v,
        r,
        s,
      ],
    });

    console.log(`Transaction sent! Hash: ${hash}`);
    console.log("Waiting for transaction to be mined...");

    const receipt = await publicClient.waitForTransactionReceipt({ hash });

    console.log("Transaction was successfully mined!");
    console.log(`- Block Number: ${receipt.blockNumber}`);
    console.log(
      `- View on Etherscan: https://etherscan.io/tx/${receipt.transactionHash}`,
    );
  } catch (err) {
    console.error("Error sending deposit-with-permit transaction:", err);
  }
}

// --- Execution Example ---

  // TODO: Replace with your actual contract addresses
  const myTellerAddress = "0x...";
  const myAccountantAddress = "0x...";

  const recipientAddress = walletClient.account.address;

  // Example ERC20 token (USDC on Ethereum Mainnet). Must support EIP-2612.
  const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
  const usdcDecimals = 6;

/* ... (start of Deposit function) ... */

await makeDepositWithPermit({
  tellerAddress: myTellerAddress,
  accountantAddress: myAccountantAddress,
  depositTokenAddress: usdcAddress,
  depositTokenDecimals: usdcDecimals, // 6default for most stable coins
  amount: "50.0", // Deposit 50.0 USDC
  recipient: recipientAddress,
});
/* ... (rest of Deposit function) ... */

TypeScript Helper Function Examples

In order to keep the code readable we've abstracted some of the logic into these helper function.

getRateInQuoteWithAssetDecimals

Note: This function is leveraging the multicall Function from Viem. Read more about it here https://viem.sh/docs/contract/multicall

import { isAddress, type Address } from "viem";
import { publicClient } from "./client"

const getRateInQuoteWithAssetDecimals = async ({
  assetAddress,
  accountantAddress,
}: { assetAddress: Address, accountantAddress: Address }) => {

  const results = await publicClient.multicall({
    contracts: [
      {
        abi: erc20Abi,
        address: assetAddress,
        functionName: 'decimals',
      },
      {
        abi: AccountantAbi,
        address: accountantAddress,
        functionName: 'getRateInQuote',
        args: [assetAddress],
      },
    ],
  });
  return results;
};

calculateMinimumMint

const calculateMinimumMint = (
  depositAmount: bigint,
  rate: bigint,
  slippage: number // In bps (e.g., 50 for 0.5%)
): bigint => {

  // Convert bps to WAD format (multiply by WAD/10000)
  const slippageAsBigInt = (BigInt(slippage) * WAD.bigint) / BigInt(10000);

  // Calculate ideal mint amount without slippage
  const minimumMint = (depositAmount * WAD.bigint) / rate;

  // Calculate the amount to subtract for slippage tolerance
  const slippageAmount = (minimumMint * slippageAsBigInt) / WAD.bigint;

  // Return mint amount minus slippage buffer
  return minimumMint - slippageAmount;
};

Last updated