@somnia-chain/contracts-sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,8 +28,18 @@ tryAddress("hideki-testnet", "somnia-markets", "MarketsCore"); // Address | null
28
28
 
29
29
  contracts("somnia-mainnet", "somnia-markets"); // enumerate, for admin screens
30
30
  identify("somnia-mainnet", "0x1a47…"); // reverse lookup: project, name, kind
31
+
32
+ allContracts();
33
+ // every current deployment across every live network — the dashboard form.
34
+ // Retired networks and superseded deployments are excluded by default, so a
35
+ // wiped testnet's addresses cannot be rendered as current by forgetting to
36
+ // filter: { includeRetiredNetworks, includeSuperseded, project } to widen it.
31
37
  ```
32
38
 
39
+ Entries carry `address`, `kind`, `status`, `version`, and `contract` — the
40
+ Solidity contract name, where a source stated one. `kind` and `status` are
41
+ unions (`DeploymentKind`, `ContractStatus`), not `string`.
42
+
33
43
  Networks: `networks` (all, retired included), `network(slug)`,
34
44
  `liveNetworks()`, and `toChain(slug, { rpcUrl })` for a viem-`Chain`-shaped
35
45
  object — you supply the RPC URL, the registry deliberately records no public
@@ -60,8 +70,23 @@ abiByHash("sha256:…"); // historical ABIs too — decode an old tx corr
60
70
  verifyAbi("sha256:…", abi); // sha256 over key-sorted compact JSON — verify, don't trust filenames
61
71
  writeFunctions("somnia-mainnet", "somnia-markets", "MarketsCore");
62
72
  // all state-changing functions — allowlists are application policy, not deployment facts
73
+
74
+ allContractsWithAbi();
75
+ // allContracts() with each ABI resolved to `abiJson` — addresses, networks and
76
+ // ABIs in one call. Costs the whole corpus, which is why it lives here.
63
77
  ```
64
78
 
79
+ ### Not every deployment has an ABI
80
+
81
+ `abiFor` returns `null`, and `abiJson` is `null`, for a real share of what the
82
+ registry holds — around half, concentrated in the testnets. That is a fact
83
+ about the records, not a gap in the package: the registry records a contract
84
+ name only where a broadcast log stated it or the repo declared one, and an ABI
85
+ is looked up by that name. Deployments created from a shared script as
86
+ per-asset instances — `BTC_reader`, `ADA_oracle` — name a role rather than a
87
+ contract, so they carry an address and no ABI. `contract` is set on exactly
88
+ the entries that have one, so it is the field to branch on.
89
+
65
90
  ## Decoding calldata
66
91
 
67
92
  Requires `viem` (optional peer dependency; only this entry needs it).
@@ -82,6 +107,12 @@ decodeBySelector(data); // 4-byte fallback across every ABI the registry holds
82
107
 
83
108
  ## Versioning
84
109
 
85
- The package is a snapshot of the registry, published when its data moves.
110
+ The package is a snapshot of the registry, and it is published when that
111
+ snapshot changes rather than when someone remembers to publish it. The
112
+ reconciliation lane compares the surface it would generate against the one the
113
+ last release shipped, and cuts a patch version whenever addresses, ABIs or
114
+ chain information moved — so a contract deployed today reaches npm on the next
115
+ lane run. Minor and major versions are cut by hand, for API changes.
116
+
86
117
  Update the dependency to pick up newly deployed contracts — nothing is fetched
87
118
  at runtime.
package/dist/abis.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { abisByHash } from "./gen/abis-index.js";
2
- import { type NetworkSlug } from "./index.js";
2
+ import { type AllContractsOptions, type ListedContract, type NetworkSlug } from "./index.js";
3
3
  export { abisByHash };
4
4
  export { abiDigest, verifyAbi } from "./digest.js";
5
5
  /**
@@ -24,6 +24,26 @@ export type AbiFunction = AbiItem & {
24
24
  export declare function abiFor(net: NetworkSlug, project: string, name: string): readonly AbiItem[] | null;
25
25
  /** Resolve a digest — including a historical one no current deployment points at — to its ABI. */
26
26
  export declare function abiByHash(digest: string): readonly AbiItem[] | null;
27
+ export interface ResolvedContract extends ListedContract {
28
+ /**
29
+ * The recorded ABI, or null where the registry has none. Null is common and
30
+ * is a fact about the registry, not an error: an ABI is recorded only where
31
+ * a source named the contract, so per-asset instances deployed from a shared
32
+ * script — `BTC_reader`, `ADA_router` — usually carry an address and no ABI.
33
+ */
34
+ abiJson: readonly AbiItem[] | null;
35
+ }
36
+ /**
37
+ * Every current deployment with its ABI already resolved — addresses,
38
+ * networks and ABIs in one call.
39
+ *
40
+ * This is the whole-corpus convenience, and it costs the whole corpus: it
41
+ * lives here rather than in the root entry so that importing it is a decision.
42
+ * A server or admin surface rendering every contract wants exactly this; a
43
+ * client bundle wants `allContracts()` from the root and a per-contract
44
+ * `./abi/*` module for the one ABI it actually needs.
45
+ */
46
+ export declare function allContractsWithAbi(opts?: AllContractsOptions): ResolvedContract[];
27
47
  /**
28
48
  * The state-changing functions of one deployment's ABI. Deliberately the
29
49
  * whole set: trimming it to an allowlist is operational policy, and policy
package/dist/abis.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { abisByHash } from "./gen/abis-index.js";
2
- import { registry } from "./index.js";
2
+ import { allContracts, registry, } from "./index.js";
3
3
  export { abisByHash };
4
4
  export { abiDigest, verifyAbi } from "./digest.js";
5
5
  /** The recorded ABI for one deployment, or null where the registry has none — including an unknown name, because the decoder path treats absence as an answer. For literal types, import the contract's `./abi/*` module. */
@@ -12,6 +12,22 @@ export function abiFor(net, project, name) {
12
12
  export function abiByHash(digest) {
13
13
  return abisByHash[digest] ?? null;
14
14
  }
15
+ /**
16
+ * Every current deployment with its ABI already resolved — addresses,
17
+ * networks and ABIs in one call.
18
+ *
19
+ * This is the whole-corpus convenience, and it costs the whole corpus: it
20
+ * lives here rather than in the root entry so that importing it is a decision.
21
+ * A server or admin surface rendering every contract wants exactly this; a
22
+ * client bundle wants `allContracts()` from the root and a per-contract
23
+ * `./abi/*` module for the one ABI it actually needs.
24
+ */
25
+ export function allContractsWithAbi(opts = {}) {
26
+ return allContracts(opts).map((entry) => ({
27
+ ...entry,
28
+ abiJson: entry.abi ? abiByHash(entry.abi) : null,
29
+ }));
30
+ }
15
31
  /**
16
32
  * The state-changing functions of one deployment's ABI. Deliberately the
17
33
  * whole set: trimming it to an allowlist is operational policy, and policy
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Enumeration over the address books, with no generated data in sight.
3
+ *
4
+ * Split out for the same reason `decode-core.ts` is: the generated modules are
5
+ * a build product, so anything that imports them cannot be tested until they
6
+ * exist — and the filtering here is precisely what a dashboard relies on to
7
+ * not render a wiped testnet's addresses as current. Structural types rather
8
+ * than the package's public ones, so this file depends on nothing.
9
+ */
10
+ export interface BookEntry {
11
+ address: string;
12
+ kind: string;
13
+ status: string;
14
+ version: string;
15
+ contract?: string;
16
+ implementation?: string;
17
+ startBlock?: number;
18
+ abi?: string;
19
+ }
20
+ export interface NetworkBook {
21
+ status: string;
22
+ contracts: Record<string, Record<string, BookEntry>>;
23
+ }
24
+ export type Books = Record<string, NetworkBook>;
25
+ export interface Listed extends BookEntry {
26
+ network: string;
27
+ project: string;
28
+ name: string;
29
+ }
30
+ export interface EnumerateOptions {
31
+ includeRetiredNetworks?: boolean;
32
+ includeSuperseded?: boolean;
33
+ project?: string;
34
+ }
35
+ /** One network's deployments, optionally one project's. Throws on an unknown slug. */
36
+ export declare function listContracts(books: Books, net: string, project?: string): Listed[];
37
+ /**
38
+ * Every current deployment across every live network.
39
+ *
40
+ * Both exclusions are defaults rather than the caller's job on purpose: a
41
+ * retired network's addresses answer on a chain that was wiped, and a
42
+ * superseded deployment is one a later release replaced. Requiring each call
43
+ * site to remember that is how one of them forgets.
44
+ */
45
+ export declare function listAll(books: Books, opts?: EnumerateOptions): Listed[];
46
+ /**
47
+ * Address -> deployment, for one network.
48
+ *
49
+ * Proxy implementations resolve to their proxy's identity so that a signer
50
+ * looking at an implementation address learns which proxy it serves. The
51
+ * proxy's own entry always wins: `set` runs for every deployment first, and
52
+ * an implementation only claims an address nothing has claimed already.
53
+ */
54
+ export declare function buildIdentityIndex(books: Books, net: string): Map<string, Listed & {
55
+ matchedBy: "address" | "implementation";
56
+ }>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Enumeration over the address books, with no generated data in sight.
3
+ *
4
+ * Split out for the same reason `decode-core.ts` is: the generated modules are
5
+ * a build product, so anything that imports them cannot be tested until they
6
+ * exist — and the filtering here is precisely what a dashboard relies on to
7
+ * not render a wiped testnet's addresses as current. Structural types rather
8
+ * than the package's public ones, so this file depends on nothing.
9
+ */
10
+ /** One network's deployments, optionally one project's. Throws on an unknown slug. */
11
+ export function listContracts(books, net, project) {
12
+ const book = books[net];
13
+ if (!book)
14
+ throw new Error(`unknown network: ${net}`);
15
+ const out = [];
16
+ for (const [proj, entries] of Object.entries(book.contracts)) {
17
+ if (project !== undefined && proj !== project)
18
+ continue;
19
+ for (const [name, entry] of Object.entries(entries)) {
20
+ out.push({ network: net, project: proj, name, ...entry });
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+ /**
26
+ * Every current deployment across every live network.
27
+ *
28
+ * Both exclusions are defaults rather than the caller's job on purpose: a
29
+ * retired network's addresses answer on a chain that was wiped, and a
30
+ * superseded deployment is one a later release replaced. Requiring each call
31
+ * site to remember that is how one of them forgets.
32
+ */
33
+ export function listAll(books, opts = {}) {
34
+ const out = [];
35
+ for (const net of Object.keys(books).sort()) {
36
+ if (!opts.includeRetiredNetworks && books[net].status !== "live")
37
+ continue;
38
+ for (const entry of listContracts(books, net, opts.project)) {
39
+ if (!opts.includeSuperseded && entry.status !== "active")
40
+ continue;
41
+ out.push(entry);
42
+ }
43
+ }
44
+ return out.sort((a, b) => a.network.localeCompare(b.network) ||
45
+ a.project.localeCompare(b.project) ||
46
+ a.name.localeCompare(b.name));
47
+ }
48
+ /**
49
+ * Address -> deployment, for one network.
50
+ *
51
+ * Proxy implementations resolve to their proxy's identity so that a signer
52
+ * looking at an implementation address learns which proxy it serves. The
53
+ * proxy's own entry always wins: `set` runs for every deployment first, and
54
+ * an implementation only claims an address nothing has claimed already.
55
+ */
56
+ export function buildIdentityIndex(books, net) {
57
+ const index = new Map();
58
+ const entries = listContracts(books, net);
59
+ for (const entry of entries) {
60
+ index.set(entry.address.toLowerCase(), { ...entry, matchedBy: "address" });
61
+ }
62
+ for (const entry of entries) {
63
+ if (!entry.implementation)
64
+ continue;
65
+ const key = entry.implementation.toLowerCase();
66
+ if (!index.has(key))
67
+ index.set(key, { ...entry, matchedBy: "implementation" });
68
+ }
69
+ return index;
70
+ }
@@ -1,5 +1,5 @@
1
1
  export declare const registryVersion: {
2
- readonly commit: "1bb7205d19ebffbb91872517da633c90dac40991";
3
- readonly generatedAt: "2026-09-29T06:09:44.003Z";
2
+ readonly commit: "316af61c453e611840fea61e5ff3450c9fd58f3c";
3
+ readonly generatedAt: "2026-09-29T12:20:25.060Z";
4
4
  readonly schemaVersion: 1;
5
5
  };
package/dist/gen/meta.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Generated by `registry sdk` from dist-public/ — do not edit.
2
2
  export const registryVersion = {
3
- commit: "1bb7205d19ebffbb91872517da633c90dac40991",
4
- generatedAt: "2026-09-29T06:09:44.003Z",
3
+ commit: "316af61c453e611840fea61e5ff3450c9fd58f3c",
4
+ generatedAt: "2026-09-29T12:20:25.060Z",
5
5
  schemaVersion: 1,
6
6
  };
@@ -1057,6 +1057,15 @@ export declare const hideki2Testnet: {
1057
1057
  readonly status: "active";
1058
1058
  readonly version: "addr:0x1a483b48";
1059
1059
  };
1060
+ readonly "prophecy-oracle": {
1061
+ readonly abi: "sha256:ecc4ba1d4404f9487b4d5d3e2b34ef1b7a1d9d188dbc94925ee07cd57d02943e";
1062
+ readonly address: "0xc09b648aDf093808A0478eb12Bc4eBa709413463";
1063
+ readonly contract: "ERC1967Proxy";
1064
+ readonly kind: "singleton";
1065
+ readonly startBlock: 3567925;
1066
+ readonly status: "active";
1067
+ readonly version: "addr:0xc09b648a";
1068
+ };
1060
1069
  readonly "settlement-scheduler": {
1061
1070
  readonly abi: "sha256:ecc4ba1d4404f9487b4d5d3e2b34ef1b7a1d9d188dbc94925ee07cd57d02943e";
1062
1071
  readonly address: "0x9345df09F70EFe1F7d5608cD4e9f4a9871647E52";
@@ -1063,6 +1063,15 @@ export const hideki2Testnet = {
1063
1063
  "status": "active",
1064
1064
  "version": "addr:0x1a483b48"
1065
1065
  },
1066
+ "prophecy-oracle": {
1067
+ "abi": "sha256:ecc4ba1d4404f9487b4d5d3e2b34ef1b7a1d9d188dbc94925ee07cd57d02943e",
1068
+ "address": "0xc09b648aDf093808A0478eb12Bc4eBa709413463",
1069
+ "contract": "ERC1967Proxy",
1070
+ "kind": "singleton",
1071
+ "startBlock": 3567925,
1072
+ "status": "active",
1073
+ "version": "addr:0xc09b648a"
1074
+ },
1066
1075
  "settlement-scheduler": {
1067
1076
  "abi": "sha256:ecc4ba1d4404f9487b4d5d3e2b34ef1b7a1d9d188dbc94925ee07cd57d02943e",
1068
1077
  "address": "0x9345df09F70EFe1F7d5608cD4e9f4a9871647E52",
package/dist/index.d.ts CHANGED
@@ -7,6 +7,19 @@ export type ProjectSlug<N extends NetworkSlug> = keyof Registry[N]["contracts"]
7
7
  export type ContractName<N extends NetworkSlug, P extends ProjectSlug<N>> = keyof Registry[N]["contracts"][P] & string;
8
8
  export type Address = `0x${string}`;
9
9
  export type AbiDigest = `sha256:${string}`;
10
+ /**
11
+ * What a deployment is, structurally. `proxy` and `beacon-proxy` carry an
12
+ * `implementation`; `implementation` and `beacon` are the targets behind one
13
+ * and are recorded in their own right so an upgrade has something to point at;
14
+ * `external` is a contract Somnia depends on but did not deploy.
15
+ */
16
+ export type DeploymentKind = "singleton" | "proxy" | "beacon" | "beacon-proxy" | "implementation" | "external";
17
+ /**
18
+ * Whether a deployment is the one to use. `superseded` and `retired` records
19
+ * are kept rather than deleted — decoding an old transaction needs the address
20
+ * that was current when it was sent.
21
+ */
22
+ export type ContractStatus = "active" | "superseded" | "retired";
10
23
  /** One deployment's record; narrow it by passing slugs and a name. The `abi` field is a digest — resolve it via the `./abis` entry point or a per-contract `./abi/*` module. */
11
24
  export type ContractEntry<N extends NetworkSlug = NetworkSlug, P extends ProjectSlug<N> = ProjectSlug<N>, C extends ContractName<N, P> = ContractName<N, P>> = Registry[N]["contracts"][P][C];
12
25
  type DistributedOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
@@ -46,24 +59,61 @@ export declare function tryAddress(net: NetworkSlug, project: string, name: stri
46
59
  export interface ListedContract {
47
60
  network: NetworkSlug;
48
61
  project: string;
62
+ /** The registry's name for this deployment — a role, like `BTC_reader`. */
49
63
  name: string;
50
64
  address: Address;
51
- kind: string;
52
- status: string;
65
+ kind: DeploymentKind;
66
+ status: ContractStatus;
53
67
  version: string;
68
+ /**
69
+ * The Solidity contract name, where a source states it. Absent where no
70
+ * source did: the registry records a name only from a broadcast log or a
71
+ * repo declaration, never a guess. This is also the key the per-contract
72
+ * `./abi/<project>/<contract>` modules are published under, so an
73
+ * enumerated deployment can reach its typed ABI only when this is set —
74
+ * and it is set on exactly the deployments that have an `abi` digest.
75
+ */
76
+ contract?: string;
54
77
  implementation?: Address;
55
78
  startBlock?: number;
56
79
  abi?: AbiDigest;
57
80
  }
58
81
  /** Enumerate a network's deployments, optionally one project's. */
59
82
  export declare function contracts(net: NetworkSlug, project?: string): ListedContract[];
83
+ export interface AllContractsOptions {
84
+ /**
85
+ * Include networks the registry has retired. Off by default: a retired
86
+ * network's addresses still exist but answer on a chain that was wiped, so
87
+ * showing them beside live ones is how a dashboard displays a balance that
88
+ * is not there.
89
+ */
90
+ includeRetiredNetworks?: boolean;
91
+ /**
92
+ * Include deployments superseded by a later one. Off by default — the
93
+ * registry publishes only each deployment's current release, so this
94
+ * changes nothing today and keeps meaning the same thing if that changes.
95
+ */
96
+ includeSuperseded?: boolean;
97
+ project?: string;
98
+ }
99
+ /**
100
+ * Every current deployment, across every live network.
101
+ *
102
+ * The one call a dashboard wants: enumerating per network means knowing the
103
+ * slugs, remembering that retired ones exist, and filtering status by hand at
104
+ * every call site — three chances to show a wiped testnet's addresses as
105
+ * current. Ordered by network slug, then project, then name, so a rendered
106
+ * list is stable between releases.
107
+ */
108
+ export declare function allContracts(opts?: AllContractsOptions): ListedContract[];
60
109
  export interface ContractIdentity {
61
110
  network: NetworkSlug;
62
111
  project: string;
63
112
  name: string;
64
- kind: string;
65
- status: string;
113
+ kind: DeploymentKind;
114
+ status: ContractStatus;
66
115
  address: Address;
116
+ contract?: string;
67
117
  implementation?: Address;
68
118
  abi?: AbiDigest;
69
119
  /** "address" — the queried address is the deployment; "implementation" — it is the implementation behind one. */
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { registry } from "./gen/registry.js";
2
+ import { buildIdentityIndex, listAll, listContracts } from "./enumerate.js";
2
3
  export * from "./gen/registry.js";
3
4
  export { registryVersion } from "./gen/meta.js";
4
5
  function info(slug) {
@@ -56,20 +57,22 @@ export function tryAddress(net, project, name) {
56
57
  const book = registry[net]?.contracts;
57
58
  return book?.[project]?.[name]?.address ?? null;
58
59
  }
60
+ const books = registry;
59
61
  /** Enumerate a network's deployments, optionally one project's. */
60
62
  export function contracts(net, project) {
61
- const book = registry[net]?.contracts;
62
- if (!book)
63
- throw new Error(`unknown network: ${String(net)}`);
64
- const out = [];
65
- for (const [proj, entries] of Object.entries(book)) {
66
- if (project !== undefined && proj !== project)
67
- continue;
68
- for (const [name, entry] of Object.entries(entries)) {
69
- out.push({ network: net, project: proj, name, ...entry });
70
- }
71
- }
72
- return out;
63
+ return listContracts(books, net, project);
64
+ }
65
+ /**
66
+ * Every current deployment, across every live network.
67
+ *
68
+ * The one call a dashboard wants: enumerating per network means knowing the
69
+ * slugs, remembering that retired ones exist, and filtering status by hand at
70
+ * every call site — three chances to show a wiped testnet's addresses as
71
+ * current. Ordered by network slug, then project, then name, so a rendered
72
+ * list is stable between releases.
73
+ */
74
+ export function allContracts(opts = {}) {
75
+ return listAll(books, opts);
73
76
  }
74
77
  const identityIndex = new Map();
75
78
  /**
@@ -81,16 +84,7 @@ const identityIndex = new Map();
81
84
  export function identify(net, addr) {
82
85
  let index = identityIndex.get(net);
83
86
  if (!index) {
84
- index = new Map();
85
- for (const entry of contracts(net)) {
86
- const identity = { ...entry, matchedBy: "address" };
87
- index.set(entry.address.toLowerCase(), identity);
88
- if (entry.implementation) {
89
- const viaImpl = entry.implementation.toLowerCase();
90
- if (!index.has(viaImpl))
91
- index.set(viaImpl, { ...identity, matchedBy: "implementation" });
92
- }
93
- }
87
+ index = buildIdentityIndex(books, net);
94
88
  identityIndex.set(net, index);
95
89
  }
96
90
  return index.get(addr.toLowerCase()) ?? null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somnia-chain/contracts-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Somnia contract addresses, ABIs and chain information, typed. Generated from the Somnia contract registry.",
5
5
  "license": "MIT",
6
6
  "type": "module",