Mint
deposit
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
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