@base-org/account
Version:
Base Account SDK
160 lines • 5.44 kB
JavaScript
import { multicall as viemMulticall } from 'viem/actions';
/**
* Executes multiple contract read calls in a single RPC request using multicall3.
*
* This utility batches multiple contract read operations into a single network request,
* significantly improving performance and reducing RPC costs when fetching data from
* multiple contracts or making multiple calls to the same contract.
*
* @example
* ```typescript
* import { multicall } from '@base-org/account';
* import { createPublicClient, http } from 'viem';
* import { base } from 'viem/chains';
*
* const client = createPublicClient({
* chain: base,
* transport: http(),
* });
*
* const results = await multicall(client, {
* contracts: [
* {
* address: '0x...',
* abi: myAbi,
* functionName: 'balanceOf',
* args: ['0x...'],
* },
* {
* address: '0x...',
* abi: myAbi,
* functionName: 'totalSupply',
* },
* ],
* });
*
* if (results[0].status === 'success') {
* console.log('Balance:', results[0].result);
* }
* ```
*
* @param client - Viem public client configured for the target chain
* @param config - Configuration object containing contracts array and options
* @param config.contracts - Array of contract calls to execute
* @param config.allowPartialFailure - If true, returns failures in results; if false, throws on any failure
* @param config.errorMessages - Custom error messages for each call
* @returns Promise resolving to array of results, one for each contract call
* @throws Error if allowPartialFailure is false and any call fails
*
* @public
*/
export async function multicall(client, config) {
const { contracts, allowPartialFailure = false, errorMessages = [] } = config;
if (!client) {
throw new Error('Public client is required for multicall');
}
if (!contracts || contracts.length === 0) {
throw new Error('At least one contract call is required');
}
try {
// biome-ignore lint/suspicious/noExplicitAny: viem's multicall requires specific contract type structure
const results = await viemMulticall(client, {
// biome-ignore lint/suspicious/noExplicitAny: viem's multicall requires specific ABI type
contracts: contracts,
});
return results.map((result, index) => {
if (result.status === 'success') {
return {
status: 'success',
result: result.result,
};
}
const failureError = result.status === 'failure' && 'error' in result
? result.error
: new Error('Unknown error');
const error = new Error(errorMessages[index] ||
`Contract call ${index} failed: ${failureError.message || 'Unknown error'}`);
if (!allowPartialFailure) {
throw error;
}
return {
status: 'failure',
error,
};
});
}
catch (error) {
// Re-throw if it's already our error or if allowPartialFailure is false
if (error instanceof Error &&
(error.message.includes('Contract call') || error.message.includes('required'))) {
throw error;
}
// Wrap unexpected errors
throw new Error(`Multicall execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Unwraps multicall results, throwing an error if any call failed.
* Use this helper when you want to ensure all calls succeeded and extract
* the result values in a type-safe way.
*
* @example
* ```typescript
* const results = await multicall(client, { contracts: [...] });
* const [balance, supply] = unwrapMulticallResults(results, [
* 'Failed to fetch balance',
* 'Failed to fetch supply',
* ]);
* ```
*
* @param results - Array of multicall results
* @param errorMessages - Custom error messages for each result
* @returns Array of unwrapped result values
* @throws Error if any result has status 'failure'
*
* @public
*/
export function unwrapMulticallResults(results, errorMessages = []) {
return results.map((result, index) => {
if (result.status === 'success') {
return result.result;
}
throw new Error(errorMessages[index] || result.error.message || `Call ${index} failed: Unknown error`);
});
}
/**
* Type guard to check if a multicall result is successful
*
* @example
* ```typescript
* const results = await multicall(client, { contracts: [...], allowPartialFailure: true });
* const successfulResults = results.filter(isMulticallSuccess);
* ```
*
* @param result - Multicall result to check
* @returns True if result is successful
*
* @public
*/
export function isMulticallSuccess(result) {
return result.status === 'success';
}
/**
* Type guard to check if a multicall result is a failure
*
* @example
* ```typescript
* const results = await multicall(client, { contracts: [...], allowPartialFailure: true });
* const failures = results.filter(isMulticallFailure);
* failures.forEach(f => console.error(f.error));
* ```
*
* @param result - Multicall result to check
* @returns True if result is a failure
*
* @public
*/
export function isMulticallFailure(result) {
return result.status === 'failure';
}
//# sourceMappingURL=index.js.map