@polygonlabs/spol-contracts-sdk
v0.0.1
Published
Typed [viem](https://viem.sh) contract clients for the sPOL protocol, covering L1 (Ethereum mainnet / Sepolia) and L2 (Polygon / Amoy).
Downloads
52
Keywords
Readme
spol-contracts-sdk
Typed viem contract clients for the sPOL protocol, covering L1 (Ethereum mainnet / Sepolia) and L2 (Polygon / Amoy).
The sPOL protocol spans ~10 smart contracts across two chains. Without a central SDK, every service would copy the same ABIs, hardcode the same addresses, and redefine the same event signatures — diverging silently as the protocol evolves. This package provides a single source of truth: ABIs defined once, addresses defaulting to production values (overridable per-service via env vars), and event signatures co-located with their ABI entries.
Development
bun run build # compile to dist/
bun run typecheck # type-check without emittingEntry points
| Entry point | Contents |
| ------------------------------ | --------------------------------------------------------- |
| spol-contracts-sdk | createL1Contracts, createL2Contracts — primary API |
| spol-contracts-sdk/events | Per-contract event objects { signature, eventABI } |
| spol-contracts-sdk/factories | Per-contract factory functions (custom address use cases) |
| spol-contracts-sdk/abis | Raw ABI exports (non-viem use cases) |
Usage
Primary API — createL1Contracts / createL2Contracts
Pass any viem PublicClient or WalletClient. The chain ID is read from the client to select
the right contract addresses automatically. All contracts and event objects for that chain are
returned together in one bundle.
import { createL1Contracts, createL2Contracts } from 'spol-contracts-sdk';
import { createPublicClient, http } from 'viem';
import { mainnet, polygon } from 'viem/chains';
const l1Contracts = createL1Contracts(
createPublicClient({ chain: mainnet, transport: http(l1RpcUrl) })
);
const l2Contracts = createL2Contracts(
createPublicClient({ chain: polygon, transport: http(l2RpcUrl) })
);Why this over direct factory/ABI imports:
- All contracts for a chain in one object — no scattered imports or address lookups.
- Chain ID drives address selection — no network-conditional branching, no address env vars to manage.
- Type safety propagates from your client:
PublicClient→ read-only methods;WalletClient→ write methods too. - Throws at construction on an unsupported chain — fails fast rather than silently at call time.
Calling contract functions
const totalSupply = await l1Contracts.spol.read.totalSupply();
const nonce = await l1Contracts.spolController.read.globalWithdrawNonce();
const hash = await l1Contracts.spolMessenger.write.updateL2ExchangeRate(); // WalletClient onlyGetting a contract address
Addresses live on the contract object — access them directly rather than importing a separate address module.
const controllerAddress = l1Contracts.spolController.address;
const childAddress = l2Contracts.sPOLChild.address;The correct address for the connected chain is always present.
Events (from contracts)
Events are bundled alongside contract instances — prefer this when you already have a client.
// Topic hash for filtering raw logs
l2Contracts.sPOLChildEvents.ExchangeRateUpdated.signature;
l1Contracts.withdrawManagerEvents.ExitStarted.signature;
// Typed ABI entry for event consumer configs
l2Contracts.sPOLChildEvents.sPOLMinted.eventABI;
l1Contracts.sPOLMessengerEvents.MigrationProcessed.eventABI;Sub-paths
Use only when the primary API doesn't fit your context.
spol-contracts-sdk/events
Use when you need event objects but have no viem client — typically when building event consumer configs at service startup before a client exists:
import {
sPOLChildEvents,
sPOLMessengerEvents,
withdrawManagerEvents
} from 'spol-contracts-sdk/events';
const config: IEventConsumerConfig = {
contractAddress: '0x...',
events: [
sPOLChildEvents.sPOLMinted.eventABI,
sPOLChildEvents.sPOLBurned.eventABI
] as unknown as IEventConsumerConfig['events']
};Each event entry shape: { signature: '0x...', eventABI: { type, name, inputs, anonymous } } as const.
The as unknown as cast is required because viem's as const produces readonly types narrower
than the mutable array type some consumer frameworks expect.
spol-contracts-sdk/abis
Only when passing a raw ABI to non-viem code (e.g. ethers.Contract):
import { sPOLControllerAbi } from 'spol-contracts-sdk/abis';
const contract = new ethers.Contract(address, sPOLControllerAbi, provider);spol-contracts-sdk/factories
Only when you need a viem contract at a custom address not in the SDK config:
import { getSPolChildContract } from 'spol-contracts-sdk/factories';
const contract = getSPolChildContract({ address: customAddress, client });Supported chains
| Layer | Chain | Chain ID | | ----- | ---------------- | -------- | | L1 | Ethereum mainnet | 1 | | L1 | Sepolia | 11155111 | | L2 | Polygon | 137 | | L2 | Amoy | 80002 |
Passing a client on an unsupported chain throws immediately with a clear error listing the supported chain IDs.
Contract addresses
Default addresses are hardcoded in src/config.ts and can be overridden via environment
variables — useful for local development or fork testing. Invalid values are silently ignored
and the hardcoded default is used instead.
| Chain | Contract | Env var |
| ---------------- | ---------------- | ---------------------------- |
| Ethereum mainnet | sPOL | MAINNET_SPOL |
| Ethereum mainnet | sPOLController | MAINNET_SPOL_CONTROLLER |
| Ethereum mainnet | sPOLMessenger | MAINNET_SPOL_MESSENGER |
| Ethereum mainnet | polBridger | MAINNET_POL_BRIDGER |
| Ethereum mainnet | rootChainManager | MAINNET_ROOT_CHAIN_MANAGER |
| Ethereum mainnet | stakeManager | MAINNET_STAKE_MANAGER |
| Ethereum mainnet | validatorShare | MAINNET_VALIDATOR_SHARE |
| Ethereum mainnet | withdrawManager | MAINNET_WITHDRAW_MANAGER |
| Sepolia | sPOL | SEPOLIA_SPOL |
| Sepolia | sPOLController | SEPOLIA_SPOL_CONTROLLER |
| Sepolia | sPOLMessenger | SEPOLIA_SPOL_MESSENGER |
| Sepolia | polBridger | SEPOLIA_POL_BRIDGER |
| Sepolia | rootChainManager | SEPOLIA_ROOT_CHAIN_MANAGER |
| Sepolia | stakeManager | SEPOLIA_STAKE_MANAGER |
| Sepolia | validatorShare | SEPOLIA_VALIDATOR_SHARE |
| Sepolia | withdrawManager | SEPOLIA_WITHDRAW_MANAGER |
| Polygon | sPOLChild | POLYGON_SPOL_CHILD |
| Polygon | polBridger | POLYGON_POL_BRIDGER |
| Amoy | sPOLChild | AMOY_SPOL_CHILD |
| Amoy | polBridger | AMOY_POL_BRIDGER |
