viem
Version:
188 lines • 7.03 kB
JavaScript
import { erc20Abi } from '../../constants/abis.js';
import { isAddress } from '../../utils/address/isAddress.js';
import { isAddressEqual } from '../../utils/address/isAddressEqual.js';
import { encodeFunctionData } from '../../utils/index.js';
import { formatUnits } from '../../utils/unit/formatUnits.js';
import { parseUnits } from '../../utils/unit/parseUnits.js';
import { readContract } from '../public/readContract.js';
/**
* Shapes a base-unit value into an {@link Amount} (base units, `decimals`, and
* human-readable `formatted` string).
*
* @param amount - Amount in base units.
* @param decimals - Token decimals used to format the amount.
* @returns The {@link Amount}.
*/
export function toAmount(amount, decimals) {
return { amount, decimals, formatted: formatUnits(amount, decimals) };
}
/**
* Resolves a write amount to base units.
*
* @param amount - Base-unit amount, or formatted helper.
* @param decimals - Token decimals used to parse formatted amounts.
* @returns Amount in base units.
*/
export function toBaseUnits(amount, decimals) {
if (typeof amount === 'bigint')
return amount;
const resolved = amount.decimals ?? decimals;
return parseUnits(amount.formatted, requireTokenDecimals(resolved));
}
/**
* Requires resolved token decimals for parsing or formatting amounts.
*
* @param decimals - Token decimals.
* @returns Token decimals.
* @internal
*/
export function requireTokenDecimals(decimals) {
if (decimals === undefined)
throw new Error('Token decimals are required. Pass `amount.decimals` or select a declared token.');
return decimals;
}
/**
* Resolves the decimals used for formatting a write amount.
*
* @param amount - Base-unit amount, or formatted helper.
* @param decimals - Token decimals.
* @returns Token decimals, overridden by the formatted helper when present.
*/
export function resolveAmountDecimals(amount, decimals) {
if (typeof amount === 'bigint')
return decimals;
return amount.decimals ?? decimals;
}
/**
* Resolves the token contract `address` and `decimals` from a `token`, which is
* either the symbol of a token declared on the client's `tokens` array or a
* contract `address`.
*
* When `token` is a declared symbol, the `address` and `decimals` are resolved
* from the Client's `tokens` array for the Client's `chain.id` (`decimals` can
* be overridden via the explicit `decimals`). When `token` is an address, its
* `decimals` is inferred from the Client's `tokens` array when the address
* matches a declared token, otherwise taken from the explicit `decimals`.
*
* @param client - Client.
* @param parameters - Parameters.
* @returns The resolved `address` and `decimals`.
*/
export function resolveToken(client, parameters) {
const { decimals, token } = parameters;
const declared = findDeclaredToken(client, token);
if (declared)
return {
address: declared.address,
decimals: decimals ?? declared.decimals,
};
if (isAddress(token, { strict: false }))
return {
address: token,
decimals: decimals ?? inferDecimals(client, token),
};
throw new Error(`Token "${token}" is not a declared ERC-20 token on the client's \`tokens\` array (with an address for the client's chain), and is not a valid address.`);
}
/**
* Finds the declared token on the Client's `tokens` array matching `token`
* (either a token symbol or contract `address`), resolved to a {@link ResolvedToken}
* for the Client's `chain.id`. Returns `undefined` when no declared token
* matches, or the matching token has no address for the Client's chain.
*
* @param client - Client.
* @param token - Token symbol (declared on the client's `tokens` array) or contract address.
* @returns The matching resolved token config, or `undefined`.
*/
export function findDeclaredToken(client, token) {
const tokens = client.tokens;
const chainId = client.chain?.id;
if (!tokens || chainId === undefined)
return undefined;
const bySymbol = findTokenBySymbol(tokens, token);
if (bySymbol)
return resolveTokenForChain(bySymbol, chainId);
if (isAddress(token, { strict: false }))
for (const token_ of tokens) {
const resolved = resolveTokenForChain(token_, chainId);
if (resolved && isAddressEqual(resolved.address, token))
return resolved;
}
return undefined;
}
/**
* Resolves a {@link Token} to a {@link ResolvedToken} for `chainId`, or
* `undefined` when the token has no address for `chainId`. @internal
*/
function resolveTokenForChain(token, chainId) {
const address = token.addresses[chainId];
if (!address)
return undefined;
return {
address,
currency: token.currency,
decimals: token.decimals,
name: token.name,
popular: token.popular,
symbol: token.symbol,
};
}
/** Finds a {@link Token} by symbol (case-insensitively) on a `tokens` array. @internal */
function findTokenBySymbol(tokens, symbol) {
const lowerSymbol = symbol.toLowerCase();
for (const token of tokens) {
if (token.symbol?.toLowerCase() === lowerSymbol)
return token;
}
return undefined;
}
/**
* Infers a token's `decimals` from the Client's `tokens` array by matching
* `address` against each token's address for the Client's `chain.id`.
* @internal
*/
function inferDecimals(client, address) {
const tokens = client.tokens;
const chainId = client.chain?.id;
if (tokens && chainId !== undefined)
for (const token of tokens) {
const resolved = resolveTokenForChain(token, chainId);
if (resolved && isAddressEqual(resolved.address, address))
return resolved.decimals;
}
return undefined;
}
/**
* Resolves token decimals, fetching from the token contract when they are not
* provided explicitly or declared on the client.
* @internal
*/
export async function resolveTokenWithDecimals(client, parameters) {
const { address, decimals } = resolveToken(client, parameters);
if (decimals !== undefined)
return { address, decimals };
return {
address,
decimals: await readContract(client, {
abi: erc20Abi,
address,
functionName: 'decimals',
}),
};
}
/**
* Picks the transaction-override fields shared by write actions, so the
* action-specific args (`token`, `amount`, `to`, etc.) don't leak into
* `estimateContractGas` / `simulateContract` requests. @internal
*/
export function pickWriteParameters(parameters) {
const { account, chain, gas, maxFeePerGas, maxPriorityFeePerGas, nonce } = parameters;
return { account, chain, gas, maxFeePerGas, maxPriorityFeePerGas, nonce };
}
export function defineCall(call) {
return {
...call,
data: encodeFunctionData(call),
to: call.address,
};
}
//# sourceMappingURL=internal.js.map