@maroo-chain/viem
v0.4.0
Published
viem extension for the Maroo chain
Readme
@maroo-chain/viem
Viem integration for the Maroo chain. It provides the chain definition, typed client actions, PCL policy tools, and protocol metadata.
The package targets Maroo v0.9.0 or newer and requires Node.js 20 or newer.
Install
npm install @maroo-chain/viem viemCreate clients
Extend standard viem clients with Maroo actions:
import { createPublicClient, createWalletClient, http } from 'viem'
import {
marooPublicActions,
marooTestnet,
marooWalletActions,
} from '@maroo-chain/viem'
const publicClient = createPublicClient({
chain: marooTestnet,
transport: http(),
}).extend(marooPublicActions())
const walletClient = createWalletClient({
account,
chain: marooTestnet,
transport: http(),
}).extend(marooWalletActions())Maroo services are exposed as namespaces:
const pclParams = await publicClient.pcl.getParams()
const okrwParams = await publicClient.okrw.getParams()
const easParams = await publicClient.eas.getParams()
const [agentIds] = await publicClient.agent.getAgentIds({
args: [owner, { key: '0x', offset: 0n, limit: 10n, countTotal: false, reverse: false }],
})
await publicClient.identity.getVersion()
await publicClient.reputation.getVersion()Address overrides follow the target being replaced: fixed agent, eas, and
okrw precompile actions use address; PCL actions use pclAddress; and
identity/reputation actions use address for their resolved registry
contract.
Use PCL
Contract-scoped policies are enforced when users call a contract through a PCL proxy.
Deploy a proxy
import { pclProxyKinds } from '@maroo-chain/viem'
const hash = await walletClient.pcl.deployPclProxy({
kind: pclProxyKinds.Transparent,
logic,
initialOwner: account.address,
initializer,
})
const receipt = await publicClient.waitForTransactionReceipt({ hash })
const event = walletClient.pcl.deployPclProxy.extractEvent(receipt.logs)
const proxy = event.args.proxySet policies
policy.* builds wire-ready policy sets for ordinary scripts and contract
writes:
import { parseEther } from 'viem'
import { policy } from '@maroo-chain/viem'
const { eas, indexer } = await publicClient.eas.getParams()
const policies = [
policy.or(
policy.periodicVolume({
limits: [{
token: 'atokrw',
maxAmount: parseEther('2000000'),
resetPeriodSeconds: 86_400n,
}],
}),
policy.eas({
easContract: eas,
indexContract: indexer,
schemaUid,
}),
),
]
await walletClient.pcl.changeContractPolicies({
contract: proxy,
admin: account.address,
policies,
})Use one root per selector, composing selector-free child rules when needed:
const policies = [policy.and([ruleA, ruleB], { selector })]The wildcard root (selector: '0x') and matching selector root both apply
at contract and global scope.
Only the current policy admin can update policies or assign the next admin. Clearing policies preserves that authority.
Use PolicySet when an editor needs a decoded, editable policy model:
import { PolicySet } from '@maroo-chain/viem'
const { policies } = await publicClient.pcl.globalPolicies()
const editable = policies.map(PolicySet.decode)
const encoded = editable.map((value) => PolicySet.encode(value, { strict: true }))PolicySet.create(input) creates a validated semantic policy directly.
PolicySet.encode(value) preserves decoded wire state for round trips; pass
{ strict: true } at an authoring boundary to reject opaque or chain-invalid
policy trees.
policy.* is the concise create + encode path.
Simulate a call
Simulate a direct proxy call, including its pre-call and post-call policies:
const outcome = await publicClient.pcl.simulatePclProxy({
account: user,
address: proxy,
abi,
functionName: 'transfer',
args,
})
if (outcome.status === 'succeeded') {
console.log(outcome.result, outcome.data)
await walletClient.writeContract(outcome.request)
} else {
console.log(outcome.revert, outcome.violation)
console.error(outcome.error)
}ABI calls return typed result, write-ready request, and the same call’s raw
data. Raw calls return data only. EVM reverts return reverted; transport,
encoding, output-decoding and uncertain RPC failures throw. Pass a viem Account
as account for local signing, or an address for JSON-RPC signing.
Decode precompile errors
import { Abis, PclViolation, Selectors } from '@maroo-chain/viem'
import { decodeErrorResult } from 'viem'
const decoded = decodeErrorResult({ abi: Abis.errors, data: revertData })
const violation = PclViolation.from(decoded)
Selectors.errors.identify('0x37ff087b')
// { selector, errorName: 'ExceededPeriodicVolume', abiItem }
Selectors.errors.selector('ExceededPeriodicVolume') // '0x37ff087b'Abis.errors combines current precompile errors and Solidity Error / Panic.
PclViolation.from also accepts ContractFunctionRevertedError.data; invalid or
non-policy errors return null. A null projection does not imply policies passed.
Standalone actions and multicall
Use the /actions entry when extending a client is not desirable:
import { pcl } from '@maroo-chain/viem/actions'
const policyState = await pcl.contractPolicies(publicClient, {
contract: contractAddress,
})Precompile reads expose .call() descriptors on decorated clients:
const results = await publicClient.multicall({
contracts: addresses.map((address) =>
publicClient.pcl.contractPolicies.call({ contract: address })),
allowFailure: false,
})Decorated identity and reputation reads resolve their registry address
lazily and therefore do not expose .call(). Their standalone /actions
counterparts do, with an explicit address.
The chain definition is also available from a dedicated entry:
import { marooTestnet } from '@maroo-chain/viem/chains'Protocol metadata
import { Abis, Addresses, Selectors, marooTestnet } from '@maroo-chain/viem'
Abis.pcl
Addresses.pcl
Selectors.pcl.deployPclProxy
marooTestnet.contracts.multicall3.addressSelectors exposes function selectors and errors lookup helpers. Derive event
topics from Abis; network deployments belong to chain.contracts.
Development
See the repository contributing guide for setup and test instructions.
License
Licensed under the Apache License 2.0.
Copyright 2026 Hashed Open Finance.
