@moesi/model
v0.3.1
Published
Typed manifest authoring, schema, references, canonicalization, and structured errors for Moesi.
Downloads
1,062
Maintainers
Readme
@moesi/model
Typed manifest authoring, schema, ref grammar, canonicalization, and structured errors for Moesi.
No chain RPC, no viem, no settlement. Safe to import from any other package.
Install
npm install @moesi/modelWhat's here
defineManifest(input)— validates a typed TypeScript manifest through the same parser as YAMLbuildManifestInput(contracts, options)— builds a self-contained inline-init-code manifest and selects dependency closuresdeployerForFactory(factory)— mapscreate2/ CreateX factory labels to manifest deployersmanifestToYaml(manifest)— writes stable dependency-ordered YAML from typed or parsed manifestsmanifestInputOf(manifest)— recovers the original authoring object for deterministic CLI generationloadManifestFromString(yaml)— parses a Moesi YAML manifest to aParsedManifestcanonicalize(manifest)/canonicalizeValue(value)— RFC 8785-style JSON canonicalization for deterministic manifest hashingparseRefString(input)— parses environment, contract, chain, Kernel, item, seed-column, and${read:CHAIN|ADDRESS|SIGNATURE}refsMoesiError(isMoesiError) — typed error class with structuredE001–E012failuresCHAIN_KEYS,CREATE_X_GUARDS— enum constants and types
Manifest shape
Applications can author the same shape with TypeScript:
import { defineManifest } from "@moesi/model";
export default defineManifest({
apiVersion: "moesi.dev/v1.0",
chains: ["sepolia", "base-sepolia"],
contracts: {},
registry: { sepolia: {}, "base-sepolia": {} },
});Or write it as YAML:
apiVersion: moesi.dev/v1.0
chains: [sepolia, base-sepolia]
contracts:
Counter:
deployer: createX-CREATE2
guard: sender-protected
salt: counter-v1
artifact: ./abi/Counter.json
args: ["${kernel.address}"]
post_deploy:
- method: setValue
args: [42]
verify: { view: value }
registry:
sepolia: {}
base-sepolia: {}Tools importing contract definitions from another repository can build only the required dependency closure and serialize it without hand-writing manifest objects or YAML:
import {
buildManifestInput,
deployerForFactory,
manifestToYaml,
} from "@moesi/model";
const manifest = buildManifestInput(
[
{
name: "Registry",
deployer: deployerForFactory("create2"),
salt: "registry-v1",
initCode: "0x60006000f3",
},
{
name: "Router",
deployer: deployerForFactory("createX-CREATE2"),
guard: "sender-protected",
salt: "router-v1",
initCode: "0x60006000f3",
requires: ["Registry"],
},
],
{ roots: ["Router"] },
);
const yaml = manifestToYaml(manifest);Registry is included automatically and emitted before Router; the named
edge is written as ${contracts.Registry.address}. Direct CREATE is not
mapped because its address depends on the sender nonce.
Deployment labels name the implementation unambiguously:
| deployer | Implementation |
| --- | --- |
| create2 | Canonical deterministic deployment proxy using CREATE2 |
| createX-CREATE2 | CreateX deployCreate2 |
| createX-CREATE3 | CreateX deployCreate3 |
| nicks | Nick's keyless nonce-zero CREATE transaction |
The ambiguous legacy values createX and create3 are not accepted.
CreateX CREATE2/CREATE3 deployments may use an exact 11-byte entropy instead
of salt. The resolver inserts the resolve-time deployer and guard flag, so the
manifest remains portable across fleet identities:
contracts:
LegacyAdapter:
deployer: createX-CREATE3
guard: sender-protected
entropy: "0x04a9469db98e61f23775c1"
artifact: ./abi/LegacyAdapter.jsonSet exactly one of salt or entropy. The existing salt derivation keeps its
tail-11 behavior for backward compatibility; entropy is used verbatim.
External infrastructure can declare view and storage invariants without any deployment behavior:
contracts:
EntryPoint:
external: true
address: "${chain.registry.entryPoint}"
artifact: ./abi/EntryPoint.json
checks:
- view: supportsInterface
args: ["0x01ffc9a7"]
expect: true
slots:
- slot: eip1967.implementation
expected: "${chain.registry.entryPointImplementation}"slots: also accepts eip1967.admin, eip1967.beacon, or a raw 32-byte
slot. Address expectations are left-padded to an EVM storage word. Supplying
an artifact ABI enables the view checks; supplying deployedBytecode in the
resolver artifact bundle additionally enables runtime-bytecode comparison.
Live reads, constructor seeding, and peer readiness
Use a zero-argument live read when desired state comes from another deployed contract. The explicit chain and return type make the value independently decodable:
args:
- "${read:arbitrum|usdc|decimals() returns (uint8)}"seed_and_reconcile keeps one item list for constructor arrays and later
drift repair. ${seed.COLUMN} projects a column from every item into a
constructor argument; ${item.KEY} addresses one item in the synthesized
post-deploy operation:
contracts:
FeedRegistry:
deployer: createX-CREATE2
guard: sender-protected
salt: feeds-v1
artifact: ./abi/FeedRegistry.json
args: ["${seed.asset}", "${seed.feed}"]
seed_and_reconcile:
items:
- { asset: "${chain.registry.usdc}", feed: "${chain.registry.usdcFeed}" }
method: setFeed(address,address)
args: ["${item.asset}", "${item.feed}"]
verify:
view: feed(address)
args: ["${item.asset}"]
expect: "${item.feed}"A pending_peer block may guard a post-deploy operation, but it only becomes
active when the resolver caller supplies ObserveOptions.peerReady. The named
contract must exist in the manifest and the resolved peer chain must be a root
manifest chain.
License
Apache-2.0
