Test without a live network
Outcome: a clean project that tests code built on the exact packed SDK, through its public exports only, with no RPC, subgraph, Pyth, or other live service involved.
You need: Node 24 and the SDK package you were granted (Get access),
installed in a clean project with its public peers: viem, react, react-dom, and
@tanstack/react-query. Use the exact package you intend to accept.
Pass a generated profile and inject viem clients built on custom transports. The transport has no
URL and rejects every JSON-RPC method the test didn’t define, and replacing fetch with a failing
function catches any stray HTTP path. The example reads an ERC-20 allowance with a fixed result,
builds a revoke transaction without sending it, and signs a Collateral Vault deposit approval that
it checks against typed data written out by hand.
import assert from 'node:assert/strict';
import { AnvilSDK, buildERC20RevokeTransaction, buildVaultDepositAuthorization, getERC20Allowance, signAuthorization, testnetProfile,} from '@anvil/sdk/core';import { createPublicClient, createWalletClient, custom, encodeFunctionData, parseAbi, recoverTypedDataAddress,} from 'viem';import { privateKeyToAccount } from 'viem/accounts';import { sepolia } from 'viem/chains';
const account = privateKeyToAccount( '0x0000000000000000000000000000000000000000000000000000000000000001');const token = '0x1111111111111111111111111111111111111111';const spender = '0x2222222222222222222222222222222222222222';const expectedAllowance = 123456789n;const requests: string[] = [];
globalThis.fetch = async input => { const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url; throw new Error(`live network access denied: ${url}`);};
const transport = custom({ async request({ method }) { requests.push(method); if (method === 'eth_chainId') return '0xaa36a7'; if (method === 'eth_call') { return `0x${expectedAllowance.toString(16).padStart(64, '0')}`; } throw new Error(`unexpected JSON-RPC method: ${method}`); },});const publicClient = createPublicClient({ chain: sepolia, transport });const walletClient = createWalletClient({ account, chain: sepolia, transport,});const sdk = new AnvilSDK({ accountAddress: account.address, profile: testnetProfile, publicClient, walletClient,});
const allowance = await getERC20Allowance(sdk, { erc20Address: token, owner: account.address, spender,});assert.equal(allowance, expectedAllowance, 'allowance contract mismatch');
const transaction = await buildERC20RevokeTransaction(sdk, { erc20Address: token, spender,});const expectedData = encodeFunctionData({ abi: parseAbi([ 'function approve(address spender, uint256 amount) returns (bool)', ]), args: [spender, 0n], functionName: 'approve',});const calldataMismatch = 'transaction calldata mismatch';assert.equal(transaction.to?.toLowerCase(), token.toLowerCase());assert.equal( transaction.from?.toLowerCase(), account.address.toLowerCase(), 'transaction signer mismatch');assert.equal(transaction.data, expectedData, calldataMismatch);assert.equal(transaction.chainId, BigInt(sepolia.id));assert.ok(requests.includes('eth_call'));assert.ok(requests.includes('eth_chainId'));
// Sign a vault deposit approval for a collateralizable contract, then// check it against typed data written out by hand: the domain and struct// CollateralVault verifies in `depositFromAccount`.const collateralizable = '0x3333333333333333333333333333333333333333';const depositAmount = 5_000_000n;const authorization = await buildVaultDepositAuthorization(sdk, { collateralizableAddress: collateralizable, tokenAddress: token, depositAmount, nonce: 0n, // explicit, so no `nonces` read is needed offline});const signature = await signAuthorization(sdk, authorization);const depositApprovalSigner = await recoverTypedDataAddress({ domain: { name: 'CollateralVault', version: '1', chainId: sepolia.id, verifyingContract: testnetProfile.contracts.collateralVault.address, }, types: { CollateralizableDepositApproval: [ { name: 'collateralizableAddress', type: 'address' }, { name: 'tokenAddress', type: 'address' }, { name: 'depositAmount', type: 'uint256' }, { name: 'approverNonce', type: 'uint256' }, ], }, primaryType: 'CollateralizableDepositApproval', message: { collateralizableAddress: collateralizable, tokenAddress: token, depositAmount, approverNonce: 0n, }, signature,});assert.equal( depositApprovalSigner, account.address, 'deposit approval signer mismatch');
console.log( JSON.stringify({ account: account.address, allowance: allowance.toString(), chainId: transaction.chainId.toString(), data: transaction.data, depositApprovalSigner, from: transaction.from, requests, to: transaction.to, }));Save the example as offline-sdk-test.mts (the extension makes it unambiguously ESM) in the clean
project. To block fetch-based network access before Node evaluates the SDK’s static imports, also
create deny-live-network.mjs:
globalThis.fetch = async input => { throw new Error(`live network access denied: ${String(input)}`);};Preload that guard, then run the typed example with Node’s built-in type stripping:
node --import ./deny-live-network.mjs offline-sdk-test.mtsSuccess exits with status 0 and prints one JSON object containing "allowance":"123456789",
"chainId":"11155111", and both expected JSON-RPC method names in requests. Any unexpected
network path or changed public result throws and exits non-zero.
The account comes from a fixed test-only key, testnetProfile supplies the public generated
environment, and the custom transport decides the chain and contract-read results. The assertions
check the read result, signer, target, chain id, and calldata. Keep real keys and live URLs out of
this fixture.
What this test covers, and what it leaves to you
- This test proves the package’s public contract. It shows that the installed package’s reads and transaction building behave as documented, with every network path under your control.
- Your acceptance runs cover the live pieces. A deployment, the subgraph, the oracle, a wallet connector, and transaction submission each need their own acceptance run against the same package artifact.