Minimizing Contract ABI Exposure
Learn how to minimize contract ABI exposure in dApps to prevent unauthorized users from discovering contract interfaces.
Overview
When building dApps, contract ABIs are typically bundled into JavaScript files. Since these static assets are served without authentication, anyone can extract ABIs and discover contract interfaces - even functions they cannot call. This can reveal sensitive business logic and attack surfaces.
Prividium provides two approaches to reduce ABI exposure: bundling only the functions your application uses (recommended) and fetching filtered ABIs at runtime for maximum security.
The Problem
Consider a typical dApp that imports a contract ABI:
// This entire ABI is bundled into your JavaScript
import { contractAbi } from './abis/myContract';
const contract = getContract({
address: CONTRACT_ADDRESS,
abi: contractAbi,
client: publicClient
});Anyone who inspects your JavaScript bundle can see the full ABI, including:
- Admin functions they cannot call
- Internal functions meant for specific roles
- Function signatures that reveal business logic
Pattern 1: Minimal Bundled ABI (Recommended)
Bundle only the ABI entries your application actually calls. This preserves viem's compile-time type safety while limiting exposure to functions the app needs:
// Only include functions you use — full viem type inference works
const minimalAbi = [
{
type: 'function',
name: 'balanceOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view'
}
] as const;This is the recommended approach because:
- Full viem type safety —
readContract,writeContract, etc. infer parameter and return types at compile time - Simple — no runtime dependency, no loading states, no extra API calls
- Minimal exposure — only reveals functions the app's UI already exposes
The remaining exposure is limited to functions the dApp calls, which are inherently visible to users through the UI.
Pattern 2: Runtime ABI Fetching (Maximum Security)
For applications where even minimal ABI exposure is unacceptable, fetch ABIs at runtime using the authenticated API. This returns only the functions the current user can access, but requires giving up viem's compile-time type inference since the ABI is not known statically.
// Fetch ABI at runtime instead of bundling
const { abi, functions } = await prividium.fetchContractAbi(CONTRACT_ADDRESS);
const contract = getContract({
address: CONTRACT_ADDRESS,
abi,
client: publicClient
});The response includes:
contractAddress- The contract addressname- The contract name (if configured)abi- The filtered ABI (see "What's Included" below)functions- Metadata about accessible functions (selector, signature, name, accessType)
What's Included in the Filtered ABI
| ABI Item Type | Included? |
|---|---|
| Functions | Filtered by permissions |
| Errors | Always included |
| Constructor | Always included |
| Fallback/Receive | Always included |
| Events | Excluded |
Vue 3 Composable Example
import { type Address, type GetContractReturnType, getContract } from 'viem';
import { ref } from 'vue';
import { prividium, publicClient } from './lib/prividium';
type ContractAbi = Awaited<ReturnType<typeof prividium.fetchContractAbi>>['abi'];
type Contract = GetContractReturnType<ContractAbi, typeof publicClient>;
export function useContract(contractAddress: Address) {
const abi = ref<ContractAbi | null>(null);
const contract = ref<Contract | null>(null);
const loading = ref(true);
const error = ref<unknown>(null);
async function loadContract() {
loading.value = true;
error.value = null;
try {
const response = await prividium.fetchContractAbi(contractAddress);
abi.value = response.abi;
contract.value = getContract({
address: contractAddress,
abi: response.abi,
client: publicClient
});
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
return { abi, contract, loading, error, loadContract };
}Note: Runtime-fetched ABIs are dynamically typed. Viem's compile-time type inference requires statically known ABIs. If type safety is important for your project, prefer Pattern 1.
API Reference
prividium.fetchContractAbi(contractAddress)
Fetches the contract ABI filtered to only functions the authenticated user can access.
Parameters:
contractAddress(string) - The contract address (hex string with 0x prefix)
Returns: Promise resolving to:
{
contractAddress: string; // The contract address
name: string | null; // Contract name if configured
abi: AbiItem[]; // Filtered ABI array
functions: Array<{
selector: string; // 4-byte function selector
signature: string; // Full function signature
name: string; // Function name
accessType: 'read' | 'write'; // Whether function is read-only or state-changing
}>;
}Errors:
- 401 - Not authenticated
- 404 - Contract not found
- 422 - Contract has malformed ABI (invalid JSON or structure)
Access Rules
The ABI is filtered based on the user's permissions:
| Rule Type | Included in ABI? |
|---|---|
public | Always included |
checkRole | Included if user has role |
restrictArgument | Always included |
checkRoleOrRestrictArgument | Included if user has role |
checkRoleAndRestrictArgument | Included if user has role |