Environments and profiles
Outcome: one AnvilSDK instance pointed at mainnet or the public testnet, with every contract
call using that environment’s generated address-and-ABI binding.
You need: only what Read a LOC already set up. This page explains the
profile field that page used.
What a profile is
A profile is a generated record of one environment’s deployment: its chain id, the contract addresses the SDK calls, the subgraph endpoint, and a few smaller fields. It comes from the protocol’s own deployment record, so a redeployment updates every address and the subgraph URL together and they stay in step.
An AnvilSdkProfile carries:
chainId: the chain this environment’s contracts are deployed on.contracts: one binding per contract the SDK knows about (collateralVault,letterOfCredit,priceOracle, and, where the environment has them,ANVLandUSDC), each an address plus its ABI and that ABI’s content fingerprint.name: the environment’s own name, such as'mainnet'or'testnet'.pyth.hermesPath: the path where Anvil’s own apps serve their Hermes proxy. The SDK ignores it and sends price reads tooracleConfig.hermesEndpoint; see Price reads need Hermes access.subgraph.url(andsubgraph.name): the environment’s subgraph endpoint.tokenAliases: each environment-local token address mapped to its canonical mainnet address, for consumers that index tokens by their mainnet identity. It’s empty where the environment’s tokens are already canonical.
Get a profile: static import or runtime selection
Two shapes cover every case. Import a specific profile when your build already knows its target environment, so a bundler can drop the other profiles’ code:
import { createPublicClient, http } from 'viem';import { sepolia } from 'viem/chains';
import { AnvilSDK, testnetProfile } from '@anvil/sdk/core';
export async function readLOC(rpcUrl: string, id: bigint) { const sdk = new AnvilSDK({ publicClient: createPublicClient({ chain: sepolia, transport: http(rpcUrl), }), profile: testnetProfile, });
return sdk.loc.getLetterOfCredit({ id });}mainnetProfile is the other static import. Select from anvilProfiles by name instead when one
build serves more than one environment and learns which at runtime:
// Select by name instead of a static import: every profile ships in one// bundle, so this fits a consumer that only learns which environment// it's targeting at runtime (a static `testnetProfile` import lets a// per-environment build tree-shake the rest instead).const profile = anvilProfiles[environmentName];return new AnvilSDK({ publicClient, profile });testnetProfile, mainnetProfile, and anvilProfiles are what a partner build targets.
Profiles keep addresses and interfaces together
Every SDK instance takes a profile. Each named protocol contract resolves its address and ABI from
the same generated binding, so the SDK always calls a deployment with the interface generated for
it. The profile is the one supported way to configure contracts: the SDK has no address-only
override and no construction path without a profile. subgraphUrl is separate from contract
encoding, so it can still override the profile’s subgraph endpoint:
// The subgraph endpoint may vary independently of deployed contracts,// so an explicit URL overrides the one in the profile.return new AnvilSDK({ publicClient, profile: testnetProfile, subgraphUrl: 'https://your-subgraph-url',});Mainnet uses the same profile path
Mainnet takes mainnetProfile, just as the public testnet takes testnetProfile:
// Mainnet follows the same explicit generated-profile contract as every// other supported environment.return new AnvilSDK({ profile: mainnetProfile, publicClient, walletClient, subgraphUrl: 'https://your-subgraph-url',});Constructing without a profile throws a typed
ConfigError that says what to pass. An
address-only configuration from an earlier SDK release also throws, even when its addresses match a
profile. That holds JavaScript callers to the same address-and-ABI binding that TypeScript enforces
at compile time.
An explicit generated AnvilSdkProfile is required. Pass your generated environment profile as `profile`.Multicall3 is the one named contract outside the profile: its deterministic address and interface are built into the SDK. Token addresses you pass as method parameters use the standard ERC-20 interface unless a profile token binding claims the address.
The chain guard
Before resolving a contract address, the SDK confirms that the publicClient’s live chain id equals
profile.chainId. On a mismatch it throws ConfigError before making the call, because a valid
address on the wrong network gives a wrong answer. The message names both sides: a testnet profile
wired to a mainnet client reports connected chain 1 disagreeing with the configured 'testnet'
profile (chain 11155111).
The guard compares chain ids, so it catches a profile for one chain used with a client on another. Choosing the right profile for the right chain is up to you: more than one environment can share a chain id, and the guard accepts any of them.
Which calls need which config
What a call needs depends on what it does, whatever the environment:
- A
PublicClient: always, for every read. - A
WalletClient: for writes only. Reads, validation, and building a workflow all work without one; a built workflow’sexecute()throwsWalletNotConnectedErroruntil one is supplied. - A generated
profile: always. It’s the SDK’s supported environment boundary, and it supplies every named protocol contract’s address-and-ABI binding. subgraphUrl, or a profile that carries one: for subgraph-backed reads, meaning LOC and vault listings, history, and collateral-token discovery (sdk.loc.getCollateralTokens). A single-record read likesdk.loc.getOutstandingLetterOfCreditworks without it.- Hermes access: for any call that needs a current market price, meaning
sdk.pricingreads and the workflows that post a fresh oracle price, such as creating a Dynamic LOC or changing its collateral. See the next section.
Price reads need Hermes access
The SDK fetches current prices from Pyth’s Hermes service, which needs authenticated access. Use your own Pyth API key; select partners without one can request access to the Anvil Hermes proxy by reaching out to the team through the contact form. Prices and the oracle shows how to configure each.
AnvilDependencies in the reference
lists every field this page leaves out, including cache configuration, ENS’s mainnetRpcUrl, and
the oracle settings.
What the SDK handles, and what your app handles
- The SDK keeps the environment consistent. It resolves every contract’s address and ABI from
one generated profile, checks the connected chain before each contract call, and reports a missing
or mismatched configuration as a typed
ConfigError. - Your app picks the environment. It chooses the profile, supplies the RPC URL and any subgraph override, and makes sure the profile is the right one when environments share a chain.