๐ Calldata Decoding
The Lattice's 5" touchscreen can display decoded transaction calldata instead of unreadable hex blobs. The SDK automatically fetches and caches contract ABIs to show human-readable function calls and parameters on the device screen.
Calldata decoding is automatic in v4.0 when using the sign() function for EVM transactions. The SDK handles ABI fetching and decoding behind the scenes.
How It Worksโ
When you sign a contract interaction transaction:
- SDK detects contract call - Checks if
datafield is present - Fetches ABI - Tries Etherscan first, falls back to 4byte.directory
- Caches decoder - Stores ABI for future use
- Sends to device - Lattice decodes and displays human-readable info
- User reviews - Clear function name and parameters shown on screen
Example: Automatic Decodingโ
New in v4.0: Calldata decoding happens automatically - no extra configuration needed!
import { setup, pair, sign } from 'gridplus-sdk';
import { encodeFunctionData, parseUnits, parseGwei } from 'viem';
// One-time setup
const isPaired = await setup({
name: 'My DeFi App',
deviceId: 'ABC123',
password: 'my-secure-password',
getStoredClient: () => localStorage.getItem('lattice-client'),
setStoredClient: (client) => localStorage.setItem('lattice-client', client),
});
if (!isPaired) {
const secret = prompt('Enter pairing code');
await pair(secret);
}
// Build a contract interaction transaction
const erc20Abi = [
{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'recipient', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
},
];
const tx = {
type: 'eip1559',
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC contract
data: encodeFunctionData({
abi: erc20Abi,
functionName: 'transfer',
args: [
'0x742d35Cc6634C0532925a3b844Bc9e7595f8b2dc',
parseUnits('100', 6), // 100 USDC
],
}),
value: 0n,
nonce: 0,
gas: 100000n,
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('2'),
chainId: 1,
};
// Sign transaction - ABI decoding happens automatically!
const result = await sign(tx);
// User sees on Lattice screen:
// Contract: USDC Token
// Function: transfer
// recipient: 0x742d...b2dc
// amount: 100 USDC
What the User Seesโ
Before Decoding (Raw Hex)โ
To: 0xA0b8...eB48
Data: 0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b844bc9e7595f8b2dc0000000000000000000000000000000000000000000000000000000005f5e100
After Decoding (Human-Readable)โ
Contract: USD Coin
Function: transfer
recipient: 0x742d...b2dc
amount: 100000000 (100 USDC)
ABI Source Priorityโ
The SDK fetches ABIs in this order:
-
Etherscan (Preferred)
- Full contract ABI with parameter names
- Only works for verified contracts
- Includes function descriptions
- Supports all EVM chains (Etherscan, Arbiscan, Polygonscan, etc.)
-
4byte.directory (Fallback)
- Canonical function signature only
- Works for unverified contracts
- Parameter names show as
#1,#2, etc. - Anyone can add missing signatures
If you're building a dApp, verify your contracts on Etherscan! This gives users the best experience with parameter names and function descriptions.
Security Considerationsโ
Information Attacksโ
You might wonder: "Can someone submit a malicious ABI to trick users?"
Short answer: No, this is not a practical attack.
Why it's safe:
- Etherscan ABIs - Only available for verified contracts (source code must match bytecode)
- 4byte collision - Extremely difficult to create a meaningful collision with just 4 bytes
- Self-referential spec - The first 4 bytes of calldata must match the ABI, and these bytes are immutable in the transaction
- Validation - If the ABI doesn't match the calldata, Lattice shows raw hex instead (alerts user)
Calldata Mismatch Detectionโ
If an ABI definition doesn't match the transaction calldata:
- Lattice firmware detects the mismatch
- Falls back to displaying raw hex
- User sees something is wrong
โ ๏ธ UNABLE TO DECODE
Data: 0xa9059cbb0000...
(Proceed with caution)
Advanced: DeFi Protocol Interactionsโ
Uniswap Swap Exampleโ
import { sign } from 'gridplus-sdk/api/signing';
import { encodeFunctionData, parseEther, parseGwei } from 'viem';
const uniswapRouterAbi = [
{
name: 'swapExactTokensForTokens',
type: 'function',
inputs: [
{ name: 'amountIn', type: 'uint256' },
{ name: 'amountOutMin', type: 'uint256' },
{ name: 'path', type: 'address[]' },
{ name: 'to', type: 'address' },
{ name: 'deadline', type: 'uint256' },
],
},
];
const tx = {
type: 'eip1559',
to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', // Uniswap Router
data: encodeFunctionData({
abi: uniswapRouterAbi,
functionName: 'swapExactTokensForTokens',
args: [
parseUnits('1000', 6), // 1000 USDC
parseUnits('0.95', 18), // Min 0.95 ETH
[usdcAddress, wethAddress], // USDC -> WETH
userAddress,
Math.floor(Date.now() / 1000) + 1800, // 30 min deadline
],
}),
value: 0n,
gas: 300000n,
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('2'),
chainId: 1,
};
const result = await sign(tx);
// User sees:
// Contract: Uniswap V2 Router
// Function: swapExactTokensForTokens
// amountIn: 1000 USDC
// amountOutMin: 0.95 ETH
// path: [USDC, WETH]
// to: 0x742d...b2dc
// deadline: 30 minutes
Supported Chainsโ
Automatic ABI fetching works on all EVM chains with Etherscan-compatible explorers:
- Ethereum (Etherscan)
- Arbitrum (Arbiscan)
- Polygon (Polygonscan)
- Optimism (Optimistic Etherscan)
- Base (Basescan)
- Avalanche (Snowtrace)
- BSC (BscScan)
- And many more...
Troubleshootingโ
ABI Not Decodingโ
If your transaction shows hex instead of decoded params:
- Check contract verification - Is the contract verified on Etherscan?
- Check 4byte - Does the function signature exist on 4byte.directory?
- Check calldata format - Is the
datafield properly formatted? - Check network - Is the SDK using the correct chain explorer?
Adding Missing Functions to 4byteโ
If your function isn't on 4byte.directory:
- Go to 4byte.directory
- Click "Submit Signature"
- Enter your function signature (e.g.,
transfer(address,uint256)) - The SDK will automatically use it for future transactions
Migration from v3.xโ
v3.x required manual decoder setup:
const { def } = await Utils.fetchCalldataDecoder(
calldata,
contractAddress,
chainId,
);
const req = { ...txData, decoder: def };
v4.0 handles it automatically:
// Just sign - decoding happens behind the scenes!
const result = await sign(tx);
See the Migration Guide for complete v3 โ v4 upgrade instructions.