Skip to content

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, ANVL and USDC), 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 to oracleConfig.hermesEndpoint; see Price reads need Hermes access.
  • subgraph.url (and subgraph.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’s execute() throws WalletNotConnectedError until 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 like sdk.loc.getOutstandingLetterOfCredit works without it.
  • Hermes access: for any call that needs a current market price, meaning sdk.pricing reads 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.