Somnia Accounts / Reference
Addresses and chains
Where every address comes from, which chains are covered, and what happens on one that is not.
The rule
No address is ever a constant in this package, and none should be one in yours.
src/generated/deployments.ts is written on every build from the committed deploy manifests under
smart-contracts/deployments, so an address in your code is a second source of truth the moment it
exists.
Coverage
| Chain | Id | Environment | Salt version |
|---|---|---|---|
| Somnia mainnet | 5031 |
somnia-mainnet |
v1 |
| Somnia testnet | 50312 |
somnia-testnet |
v1 |
| Hideki testnet | 50383 |
hideki-testnet |
v1 |
The factory is at the same address on all three, and so is every other contract in the set. That is CREATE3: the address depends on the deployer and the salt, not on the chain or the bytecode.
import {SUPPORTED_CHAIN_IDS} from "@somnia-chain/accounts-sdk";
SUPPORTED_CHAIN_IDS; // readonly number[], ascending
SUPPORTED_CHAIN_IDS is derived from the manifests, not typed out — so a new deployment appears here
without anyone editing a list.
A chain absent from that table has no accounts. If your application supports more chains than
this — Somnia Elwood (50313) and a local fork (31337) are the common cases — decide what that
means before a user reaches it. See What an unsupported chain does below.
Contract names
DeployedName is a union of exactly what the manifests spell, so a typo is a compile error:
| Name | What it is |
|---|---|
SomniaAccountFactory |
the factory proxy — the address to call |
SomniaAccountFactoryImpl, …ImplV2, …ImplV3 |
implementations behind it |
Safe |
the singleton accounts are proxies of |
CompatibilityFallbackHandler |
what answers isValidSignature for an account |
MultiSend |
batcher that permits a nested delegatecall — not what this SDK uses |
MultiSendCallOnly |
batcher that does not. This is the one |
safeSingleton, fallbackHandler |
the factory's configuration, echoing Safe and CompatibilityFallbackHandler |
factoryAdmin |
an EOA: who can upgrade the factory |
protocolDeployer |
the pre-existing CREATE3 registry the rest derive from |
The last three are not contracts this repo deploys. They are published because looking one of them up is exactly what an operator needs to do, and the alternative is a hardcoded constant elsewhere.
Looking one up
import {
contractAddress,
deploymentFor,
deploymentsFor,
factoryAddress,
} from "@somnia-chain/accounts-sdk";
contractAddress(chainId, "MultiSendCallOnly"); // one address
factoryAddress(chainId); // the factory, named because it is the one you want most
deploymentFor(chainId); // the whole set for a chain
deploymentsFor(chainId); // every committed set, when a chain has more than one salt version
What an unsupported chain does
It throws, and that is deliberate:
UnsupportedChainError— nothing is committed for that chain id.MissingContractError— the chain is covered but that manifest has no entry for that name.AmbiguousDeploymentError— more than one salt version is committed and you did not say which. PasssaltVersion.
None of them returns undefined, because undefined at the point an address goes into a
transaction becomes the zero address — a call to nowhere that succeeds, costs gas, and does nothing.
import {
SUPPORTED_CHAIN_IDS,
UnsupportedChainError,
factoryAddress,
} from "@somnia-chain/accounts-sdk";
// Ask first, in a UI that spans more chains than the factory does.
if (!SUPPORTED_CHAIN_IDS.includes(chainId)) {
// render "no accounts on this network" rather than a disabled button with no explanation
}
// Or catch, in a code path that should not have got here.
try {
factoryAddress(chainId);
} catch (e) {
if (e instanceof UnsupportedChainError) console.warn(`no factory on ${e.chainId}`);
else throw e;
}
Salt versions
A chain can have more than one committed deployment — a v2 salt produces a different factory
address and therefore a different set of accounts. Every address function takes an optional
saltVersion for that reason, and omitting it on a chain with several is
AmbiguousDeploymentError rather than a silent pick.
All three chains are on v1 today, so the argument can be left off everywhere.
The client's chain is checked
readAccountAddress compares the client's chain against the one you asked about and throws
WrongNetworkError (with expected and actual) rather than reading the wrong factory. A client
pinned to mainnet answering questions about testnet is otherwise indistinguishable from a correct
answer.
Related
- Account derivation — how an address is computed from a key
- Errors
docs/contracts.mdin the repository — every address per chain, and the standing admin risk