@interop/wallet-core
v0.79.1
Published
Shared wallet-domain logic (WAS sync engine core and wallet Space layout contracts) for Interop wallet apps.
Readme
Wallet Core (@interop/wallet-core)
Shared wallet-domain logic (WAS sync engine core and wallet Space layout contracts) for Interop wallet apps.
Table of Contents
Background
@interop/wallet-core is the shared, correctness-critical code two WAS-enabled
wallet apps (a React Native mobile wallet and a browser wallet) hold in common:
the cross-replica byte-compatibility surface both must agree on to converge on
identical bytes. It is isomorphic (browser, Node.js, React Native) and has no UI
or storage dependencies -- side effects are injected, and the dep-heavier
protocol subpaths are import-directly-only.
The subpaths:
@interop/wallet-core/sync-- the Wallet Attached Storage (WAS) replication engine core: theSyncEngineorchestration (single-flight, migrate-once, memoized provisioning invalidated on demand viainvalidateProvisioning, backoff), therunPull/runPushalgorithms, the replica-sideSyncStoreseam, and the genericSyncedCollectionSpecshape. The wire contract and port (WasSyncPort,WireDoc,DocCipher, ...) are re-exported from@interop/was-clientso an engine consumer imports one package.@interop/wallet-core/space-- the wallet Space layout contract: the shared collection ids and descriptive specs (private-credentials,public-credentials,wallet-activity,app-connections), thewallet-activitywire shape with its pureaddHistory*payload builders, thepublicCredentialUrlderivation, and thewas-linkQR hand-off contract (buildWasLinkPayload/parseWasLinkPayload/encodeWasLinkSecret) with its structural recognizer,isWasLinkPayload. Contacts collection specs live in@interop/social-core.@interop/wallet-core/webvh-- the account's did:webvh identity: the hosted DID log, its per-client update-key rotation, the client enrollment entries, the one-entry client-revocation edit (verification methods, update key, and standing commitments out in a single entry, the staged commitment recovered by log attribution), the enrolled-client listing over a caller-verified log (for a "your wallets" surface), the fetch-and-verify-the-published-log step those ceremonies share, the WAS-backed store they write through, and ZCap signing under the did:webvh verification-method id.@interop/wallet-core/resourceLog-- the wallet-domain half of the Resource Log Profile, the hash-linked log format governing key resources co-managed between a wallet's clients and the storage server. Three pieces. The did:webvh controller adapter, which answers which keys could have signed a given entry at a given document version, and supplies the admission hook every verifier consults. The ceremony-tail license that hook carries, which bounds what a standing credential's ladder key may append -- above all refusing a silent rekey against an unchanged document. And a re-export of@interop/vh-resource-log'sisResourceLogRefusal, the shared reading of the refusal taxonomy: which refusals a reader must not paper over with a cached copy, and which one it may (a chain-head rollback, reconcilable divergence). The adapter and the license read the account document through one import-free leaf beside them: relation resolution, ladder-VM recognition, and the credential class. They therefore answer exactly as the client listing does. That leaf's public home is thewebvhsubpath. The generic half lives in@interop/vh-resource-log-- chain verification against an adversarial host, the chain-head pin, the entry builders, the read/append/create path, and the sealing sweep. Transport is in@interop/was-client's/logsubpath, and the hashing and proof kernel in@interop/did-method-webvh.@interop/wallet-core/keys-- the user key and its wrap-set roster, governed by thekey-map/user-key.jsonlresource log: minting, the roster's init/read/rotate primitives with their client-side guards (the verified log itself, the latest-seen epoch pin, the document-backed recipient resolver), the log-governed (and sealable) descriptor store those primitives drive, and the user key rotation cascade's per-collection op (re-epoch a collection onto the roster's current user key, staleness detected from durable state alone, history escrowed -- also the completion sweep's building block), plus the detector that converges a roster left wrapping the current key to a recipient the account document no longer keys. AlsoensureWalletSpaceEpochs, the provision-time install of each encrypted wallet collection's key epoch[0] (a fresh random epoch key wrapped to the user key) -- the EDV-bearing second step ofprovisionWalletSpace. AlsowalletSpaceProvisioner, which builds the sync engine'sensureProvisionedclosure over both steps, single-flight across concurrent callers. Also the enrolled-client display labels (key-map/client-labels.json) and their WAS-backed store. Also the client-key record codec: the contents and strict validation of the local record each wallet client keeps its own key material in (storage and wrapping stay app-side).@interop/wallet-core/clients-- the enrolled-client management surface: the listing over the locally verified did:webvh log with display labels merged, the disconnect-eligibility policy as pure functions, the revocation cascade orchestrator (document edit, roster rotation with its seal backstop, collection fan-out, optional recovery re-mints), and the login-time roster policy (which now also seals a converged roster's governing log).@interop/wallet-core/descriptors-- the log-governed descriptor source: every read re-verifies the collection's governing resource log, keyed per collection bycollectionDescriptorLogPinId. It implements theEncryptionDescriptorSourceseam of@interop/was-client/edv, where the acquisition, cache fallback, unknown-epoch refresh policy, and self-refreshing EDV document cipher live.@interop/wallet-core/keyring-- the unlock layer: the unlock derivation, the signed{ version, encryption, wrapped, proof }account-pointer record codec (the proof is verified before the record is decrypted), and the unlock Space lifecycle.@interop/wallet-core/enrollment-- the client enrollment ceremony (connect code, approval, completion) plus the onboarding-response envelope that carries a connect code back over an exchange.@interop/wallet-core/genesis-- the account-genesis ceremony: the local mint of a brand-new account's complete key set (mintAccountKeySet: Space id, client identity seed, user key, did:webvh update keys) and the staged provisioning both apps must encode identically (ensureAccountGenesis: Space provisioning, the optional KMS key-map acquisition, did:webvh genesis, user-key roster genesis after DID publication, epoch[0] on every encrypted roster collection, and the Space-controller promotion, also exported standing alone asensurePromotedSpaceController). Idempotent end to end: a torn run heals by re-running. The keyring bind is deliberately not a stage, so a wallet with no unlock method bound at creation drives the same ceremony.@interop/wallet-core/unlock-- standing unlock credentials: every unlock method (passphrase, passkey PRF, recovery code) as a standing credential in the recovery-code configuration, with self-enrolling login. The credential-derived client identity and binding MAC key, the update-key ladder (latent-and-consumed did:webvh update authority from a random seed carried in the unlock record, the current rung recovered from the log itself), the unlock record codec (shell / bridge delegation / ladder members under a credential-authenticated binding, with the bridge-only re-mint), the merged document-inventory edit (a verbatimkeyAgreemententry or apublicKeyCommitmententry for a low-entropy-derived key, either naming its ladder's rung-0 hash asladderCommitment), and the self-enrolling continuation with its composed completion.@interop/wallet-core/recovery-- recovery codes on the roster identity model, over theunlockmachinery: a code as a minimal always-enrolled wallet client (format and derivation, the document half of issuance / revocation / recovery with its spend-on-use continuation, the pre-minteddid.jsonldelegation builder and the revocation cascade's bridge re-mint core; the record codec is theunlocksubpath's, re-exported here).@interop/wallet-core/clientAnnex-- the client annex, the sibling did:webvh log holding per-visit transient client keys in garbage-collected generations, published in the account's auxiliary annex Space. This is the authoring and maintenance surface of everything anchored on an unlock credential's update-key ladder: the ladder itself (rung and VM derivation, the shared attribution walks), the annex log and its GC, ZCap signing under a ladder VM, the ladder-anchored account-log ceremonies (genesis, self-enrollment, forget, and the last-client transition to a client-less account), the credential-anchored account genesis with its mend and its per-visit readiness ensure, and the transient-recovery continuation. It sits on top of the other subpaths and none of them import from it.@interop/wallet-core/menders-- the mender registry keyed by invariant: theInvariantDeclarationandRegistrationtypes, the closed vocabularies (authorities, triggers, account shapes, evidence, mend outcomes, gap kinds), theINVARIANT_IDScensus, themenderRegistryreaders withheldAuthorities, therunMenderBlockrunner with itsmendReportAccumulator, and the derived sets a wallet's audit tests pin (transientReachableInvariants,deriveGaps,undeclaredGaps,undeclaredInvariants). A wallet declares its own invariant table and its own registrations; nothing here executes a ceremony, and the subpath imports nothing from the signing or KMS graph.
The request pipeline (input classification, VPR parsing, cryptosuite
negotiation, VP composition, the App Connect app-key credential) now lives in
@interop/wallet-request.
The byte-for-byte WAS identity derivation (agentsFromSecret / agentsFromSeed
/ agentsFromKeyAgent, singleKeyResolver, zcapClientForSigner) now lives at
the @interop/was-client/identity
subpath.
Install
- Node.js 24+ is recommended.
pnpm install @interop/wallet-coreDevelopment
git clone https://github.com/interop-alliance/wallet-core.git
cd wallet-core
pnpm installUsage
import { SyncEngine, runPull, runPush } from '@interop/wallet-core/sync'
import {
PRIVATE_CREDENTIALS_COLLECTION,
publicCredentialUrl,
buildWasLinkPayload,
parseWasLinkPayload,
addHistoryCredentialCreated
} from '@interop/wallet-core/space'The sync and space subpaths are re-exported from the package root as well.
Every other subpath (webvh, resourceLog, keys, clients, descriptors,
keyring, enrollment, genesis, unlock, recovery, clientAnnex,
menders) is import-directly-only, so consumers of the root never pull the
signing / KMS / document-loader dependency graph.
Nine further exports are leaves of that same isolation, carved out to stay
dependency-light, for an offline consumer such as @interop/wallet-backup that
derives clients and opens records without ever touching the account log:
keys/clientKeyRecord-- the client-key record codec alone.keyring/kdf-- the unlock KDF.keyring/recordEnvelope-- the record envelope's descriptor mint, ciphers, addressed id, and frame parsers.keys/userKey-- the user key's key-agreement half.keys/userKeyGenerations-- the user-key-generation unwrap helpers.unlock/standingClient-- the standing client derivation.unlock/ladderDerivation-- the update-key rung and ladder-VM derivation.recovery/recoveryCode-- the recovery code codec and its client derivation.space/collections-- the wallet Space collection ids, specs and resource names, without thespacebarrel's transport graph.
None of these leaves reach wallet-core's own webvh, resourceLog, or
clientAnnex modules. The module barrels still re-export the same names.
Contribute
PRs accepted. See CONTRIBUTING.md for editor setup (Prettier, ESLint, and EditorConfig) and how it maps to CI.
If editing the Readme, please conform to the standard-readme specification.
License
MIT License © 2026 Interop Alliance.
