@d3or/slotseek

中文文档
1.3.1发布于 2026/08/11MITTypeScriptCommonJS近 30 天下载 3573

用于在 ERC20 令牌上找到余额和批准值的存储槽的图书馆,可用于在估计如果地址没有所需bal将失败的交易的天然气成本时,嘲笑地址的余额和批准值

中文使用文档
文档目录10 节
  1. 特征
  2. 如何运作
  3. 安装
  4. 可选校验出缓存
  5. 负缓冲
  6. 观察性
  7. 开始
  8. 用户通过 eth call 实现平衡的示例
  9. 在合同中找到存储槽的例子
  10. 嘲弄许可证的图示2

时档搜索

NPM version Follow on Twitter Build Status

槽seek是一个javascript 库,用于帮助找到存储槽 balanceOf 财务报告和审定财务报表 allowance 一份ERC20令牌合同中的绘图,以及许可证2允许绘图。 它还提供了一种生成模拟数据的方法,可以用来推翻合同状态 eth_call 或者说 eth_estimateGas 电话.

这个库的主要用途是估算如果地址没有所需的余额或批准,将失败的交易的天然气成本.

例如,在用户批准使用其代币的合同之前,估计交易在互换时将消耗的气体.

特征

  • 查找存储槽 balanceOf 财务报告和审定财务报表 allowance eRC20令牌合同中的绘图和许可证2
  • 生成可用于推翻合同状态的模拟数据 eth_call编号 :eth_estimateGas 调用
  • 支助 vyper 存储布局

如何运作

库使用蛮横的强力方法寻找存储槽 balanceOf 财务报告和审定财务报表 allowance 一份ERC20令牌合约中的绘图. 它通过使用一个用户提供的地址来做到这一点,我们知道该地址有平衡或批准,然后通过合同的存储槽通过 eth_getStorageAt JSON-RPC方法直到找到存储值与用户的余额或批准相匹配的槽.

这不是一个完美的方法,除了直接与RPC的合同互动外,还有更有效的方法可以找到存储槽. 但若不需要设置更多的工具/基本工具,特别是多链支持和运行时的气体估计,则很难做到这一点. 而且,在Javascript中帮助解决这个问题的工具也不多.

安装

npm install @d3or/slotseek
# or
yarn add @d3or/slotseek

可选校验出缓存

应用程序可以提供任何Aync缓存执行. 槽seek 不创建 网络连接或依赖于特定的缓存客户端.

import { generateMockBalanceData, StorageLayoutCacheAdapter } from "@d3or/slotseek";

const cache: StorageLayoutCacheAdapter = {
  get: async (key) => JSON.parse((await redis.get(key)) ?? "null"),
  set: async (key, layout, ttlSeconds) => {
    await redis.set(key, JSON.stringify(layout), "EX", ttlSeconds);
  },
};

await generateMockBalanceData(provider, {
  tokenAddress,
  holderAddress,
  mockAddress,
  cache,
  chainId: 8453, // optional; otherwise resolved from provider.getNetwork()
  cacheTtlSeconds: 7 * 24 * 60 * 60,
  cacheTimeoutMs: 200,
});

密钥通过布局类型,链式ID,和小写代号来进行版本和范围化. 根据正链上余额或备抵核实的布局被缓存 长 cacheTtlSeconds TTL( 默认7天) 。 格式错误、 过期、 失败或 缓存读取的缓存被视作缺失, 缓存写入失败不会失败槽 发现.

负缓冲

失败发现也被缓存, TTL 较短 (单位:千美元)negativeCacheTtlSeconds,默认为15分钟),所以重复引用 不支持的符号不会在每次呼叫时重运行完整的存储探测器 :

  • 发现零备抵(理由) zero-allowance)或(或) 用尽所有探测器(理由) not-found)写作负标记. 标记 键入 (kind, chainId, token),但津贴为所有者/发放者 具体,那么新鲜 zero-allowance 击出以 a 为单位的当前配对 单人 allowance() 电话:如果仍然是零,呼叫者收到 倒置批准槽(10),没有储存探测器;如果是正的 标记被忽略了 完全发现这对.
  • 平衡的发现,所有探测器都耗尽了 尽管在链上呈正值 余额写入 a not-found 标记,没有RPC,新鲜的点击会很快失败. 零平衡的结果是持有者特有的,从不负的.
  • not-found 标记记录探测预算(maxSlots已使用;一个呼叫者 搜索比所覆盖的标记更多的插槽会忽略它并重试.
  • 拒绝的存储探测器( 速率限制、 提供者错误) 的搜索 视之为瞬息万变 绝不是阴性.
  • 负标记带有绝对值 expiresAt 作者设置; 读者 尊重最低程度和他们自己的 negativeCacheTtlSeconds,所以一个 标记永远不会超过它的作家TTL。 期满后,发现再次运行.
  • 同一发现的同名呼叫者(同同名信使和同名信使) 持有者/所有人/喷雾器和探测器预算)分享飞行中的结果,包括: 失败和拒绝,所以爆发 相同的引用最多一个 探测器序列.

观察性

过 onCacheEvent 以接收结构化缓存遥测以连接到 指标或日志。 活动 type, (中文(简体) ), kind (单位:千美元)balance 编号 : approval, (中文(简体) ), chainId, (中文(简体) ), tokenAddress,和(对于负面结果) reason。事件类型 :

  • local_hit - 从程序内缓存得到服务
  • external_hit - 从应用程序提供的缓存适配器服务
  • negative_hit - 一个新的负标记被消耗
  • verified - 发现成功,布局被缓存
  • discovery_failed - 发现失败,结果为负面
  • cache_error - 外部适配器抛出或超时(无法打开)

回调可以是同步或同步;例外和拒绝被吞噬.

await generateMockApprovalData(provider, {
  ...args,
  cache,
  onCacheEvent: (event) => metrics.increment(`slotseek.cache.${event.type}`),
});

开始

  • 添加缓存选项以减少 RPC 调用的次数并减少再次找到相同槽所需的时间

用户通过 eth call 实现平衡的示例

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);

这也可以用来伪造批准,使用 generateMockApprovalData 函数代替 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);

您也可以同时通过提供 balance 财务报告和审定财务报表 approval 状态 diff 对象中的字段.

在合同中找到存储槽的例子

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);

嘲弄许可证的图示2

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);

版本与发布记录

@d3or/slotseek 共发布 7 个版本,首次发布于 2024/07/07;过去 12 个月发布了 3 个版本。

版本发布日期距今
1.3.1latest2026/08/113 个月前
1.3.02026/08/043 个月前
1.2.02026/07/293 个月前
1.1.22024/11/162 年前
1.0.22024/07/132 年前
1.0.12024/07/122 年前

关于 @d3or/slotseek 的常见问题

@d3or/slotseek 是什么?

用于在 ERC20 令牌上找到余额和批准值的存储槽的图书馆,可用于在估计如果地址没有所需bal将失败的交易的天然气成本时,嘲笑地址的余额和批准值

如何安装 @d3or/slotseek?

在项目目录运行 npm install @d3or/slotseek,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:ethers(^5.7.2)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@d3or/slotseek 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@d3or/slotseek 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@d3or/slotseek 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=14.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@d3or/slotseek 可以商用吗?

@d3or/slotseek 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@d3or/slotseek 的下载量有多少?

根据 npm 官方下载统计,@d3or/slotseek 在 2026/10/11 – 2026/11/09 这 30 天内被下载 3,573 次,日均约 119 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@d3or/slotseek 还在维护吗?

最新版本 1.3.1 发布于 2026/08/11(3 个月前);至今共发布 7 个版本,其中过去 12 个月发布了 3 个;首次发布于 2024/07/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。