
Security News
Critical Security Vulnerability in React Server Components
React disclosed a CVSS 10.0 RCE in React Server Components and is advising users to upgrade affected packages and frameworks to patched versions now.
@d3or/slotseek
Advanced tools
A library for finding the storage slots on an ERC20 token for balances and approvals, which can be used to mock the balances and approvals of an address when estimating gas costs of transactions that would fail if the address did not have the required bal
slotseek is a javascript library that assists with finding the storage slots for the balanceOf and allowance mappings in an ERC20 token contract, and the permit2 allowance mapping. It also provides a way to generate mock data that can be used to override the state of a contract in an eth_call or eth_estimateGas call.
The main use case for this library is to estimate gas costs of transactions that would fail if the address did not have the required balance or approval.
For example, estimating the gas a transaction will consume when swapping, before the user has approved the contract to spend their tokens.
balanceOf and allowance mappings in an ERC20 token contract, and permit2 allowance mappingeth_call/eth_estimateGas callThe library uses a brute force approach to find the storage slot of the balanceOf and allowance mappings in an ERC20 token contract. It does this by using a user-provided address that we know has a balance or approval, and then iterates through the storage slots of the contract via the eth_getStorageAt JSON-RPC method until it finds the slot where the storage value matches the user's balance or approval.
This is not a perfect method, and there are more efficient ways to find the storage slot outside of just interacting directly with the contract over RPC. But it's difficult to do so without needing to setup more tools/infra, especially for multi-chain support and gas estimation at runtime. Also, there are not many tools to help with this in javascript.
npm install @d3or/slotseek
# or
yarn add @d3or/slotseek
import { ethers } from "ethers";
import { generateMockBalanceData } from "@d3or/slotseek";
async function fakeUserBalance() {
// Setup - Base RPC
const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const holderAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder
const mockAddress = ethers.Wallet.createRandom().address; // Address to fake balance for
const mockBalanceAmount = "1000000000000"; // 1 million USDC (6 decimal places), optional. If not provided, defaults to the balance of the holder
// Generate mock balance data
const data = await generateMockBalanceData(provider, {
tokenAddress,
holderAddress,
mockAddress,
mockBalanceAmount,
});
// Prepare state diff object
const stateDiff = {
[tokenAddress]: {
stateDiff: {
[data.slot]: data.balance,
},
},
};
// Prepare balanceOf call
const balanceOfSelector = "0x70a08231";
const encodedAddress = ethers.utils.defaultAbiCoder
.encode(["address"], [mockAddress])
.slice(2);
const getBalanceCalldata = balanceOfSelector + encodedAddress;
// Make the eth_call with state overrides, or eth_estimateGas
const balanceOfResponse = await provider.send("eth_call", [
{
from: mockAddress,
to: tokenAddress,
data: getBalanceCalldata,
},
"latest",
stateDiff,
]);
// Decode and log the result
const balance = ethers.BigNumber.from(
ethers.utils.defaultAbiCoder.decode(["uint256"], balanceOfResponse)[0]
);
console.log(
`Mocked balance for ${mockAddress}: ${ethers.utils.formatUnits(
balance,
6
)} USDC`
);
}
fakeUserBalance().catch(console.error);
This can also be used to fake approvals, by using the generateMockApprovalData function instead of generateMockBalanceData.
import { ethers } from "ethers";
import { generateMockApprovalData } from "@d3or/slotseek";
async function fakeUserApproval() {
// Setup
const provider = new ethers.providers.JsonRpcProvider("YOUR_RPC_URL");
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const ownerAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder
const spenderAddress = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // Spender address
const mockAddress = ethers.Wallet.createRandom().address; // Address to fake balance for
const mockApprovalAmount = "1000000000000"; // 1 million USDC (6 decimal places)
// Generate mock approval data
const mockApprovalData = await generateMockApprovalData(provider, {
tokenAddress,
ownerAddress,
spenderAddress,
mockAddress,
mockApprovalAmount,
});
// Prepare state diff object
const stateDiff = {
[tokenAddress]: {
stateDiff: {
[mockApprovalData.slot]: mockApprovalData.approval,
},
},
};
// Function selector for allowance(address,address)
const allowanceSelector = "0xdd62ed3e";
// Encode the owner and spender addresses
const encodedAddresses = ethers.utils.defaultAbiCoder
.encode(["address", "address"], [mockAddress, spenderAddress])
.slice(2);
const getAllowanceCalldata = allowanceSelector + encodedAddresses;
// Make the eth_call with state overrides, or eth_estimateGas
const allowanceResponse = await provider.send("eth_call", [
{
from: mockAddress,
to: tokenAddress,
data: getAllowanceCalldata,
},
"latest",
stateDiff,
]);
// Decode and log the result
const allowance = ethers.BigNumber.from(
ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0]
);
console.log(
`Mocked allowance for ${mockAddress}: ${ethers.utils.formatUnits(
allowance,
6
)} USDC`
);
}
fakeUserApproval().catch(console.error);
You can also override both the balance and the allowance at the same time by providing both the balance and approval fields in the state diff object.
import { ethers } from "ethers";
import { getErc20BalanceStorageSlot } from "@d3or/slotseek";
async function findStorageSlot() {
// Setup - Base RPC
const provider = new ethers.providers.JsonRpcProvider(
"https://mainnet.base.org"
);
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const holderAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder
const maxSlots = 100; // Max slots to search
// Find the storage slot for the balance of the holde
// or for approvals, use getErc20AllowanceStorageSlot
const { slot, balance, isVyper } = await getErc20BalanceStorageSlot(
provider,
tokenAddress,
holderAddress,
maxSlots
);
console.log(
`User has balance of ${ethers.utils.formatUnits(
balance,
6
)} USDC stored at slot #${Number(slot)}`
);
}
findStorageSlot().catch(console.error);
import { ethers } from "ethers";
import { computePermit2AllowanceStorageSlot } from "@d3or/slotseek";
async function findStorageSlot() {
// Setup - Base RPC
const provider = new ethers.providers.JsonRpcProvider(
"https://mainnet.base.org"
);
// Constants
const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base
const mockAddress = "0x0000c3Caa36E2d9A8CD5269C976eDe05018f0000"; // USDC holder to mock approval for
const spenderAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
// Compute storage slot of where the allowance would be held
const { slot } = computePermit2AllowanceStorageSlot(mockAddress, tokenAddress, spenderAddress)
const permit2Contract = '0x000000000022d473030f116ddee9f6b43ac78ba3'
// Prepare state diff object
const stateDiff = {
[permit2Contract]: {
stateDiff: {
[slot]: ethers.utils.hexZeroPad(
ethers.utils.hexlify(ethers.BigNumber.from("1461501637330902918203684832716283019655932142975")),
32
)
,
},
},
};
// Function selector for allowance(address,address,address)
const allowanceSelector = "0x927da105";
// Encode the owner and spender addresses
const encodedAddresses = ethers.utils.defaultAbiCoder
.encode(["address", "address", "address"], [mockAddress, tokenAddress, spenderAddress])
.slice(2);
const getAllowanceCalldata = allowanceSelector + encodedAddresses;
const callParams = [
{
to: permit2Contract,
data: getAllowanceCalldata,
},
"latest",
];
const allowanceResponse = await baseProvider.send("eth_call", [
...callParams,
stateDiff,
]);
// convert the response to a BigNumber
const approvalAmount = ethers.BigNumber.from(
ethers.utils.defaultAbiCoder.decode(["uint256"], allowanceResponse)[0]
);
console.log(
`Mocked balance for ${mockAddress}: ${ethers.utils.formatUnits(
approvalAmount,
6
)} USDC`
);
}
findStorageSlot().catch(console.error);
FAQs
A library for finding the storage slots on an ERC20 token for balances and approvals, which can be used to mock the balances and approvals of an address when estimating gas costs of transactions that would fail if the address did not have the required bal
We found that @d3or/slotseek demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
React disclosed a CVSS 10.0 RCE in React Server Components and is advising users to upgrade affected packages and frameworks to patched versions now.

Research
/Security News
We spotted a wave of auto-generated “elf-*” npm packages published every two minutes from new accounts, with simple malware variants and early takedowns underway.

Security News
TypeScript 6.0 will be the last JavaScript-based major release, as the project shifts to the TypeScript 7 native toolchain with major build speedups.