Monorepo: 1Shot API Open Wallet Standard (@1shotapi/open-wallet).
OWS uses three iframe layers: Host Layer, Branding Layer, and Signing Layer.
examples/host Host Layer — @1shotapi/ows-provider (EIP-1193)
examples/general-wallet Branding Layer — React + Tailwind general-purpose wallet demo
packages/ows-signer Signing Layer (plain JS, zero deps, on-chain)
packages/ows-signer-utils Branding Layer ↔ Signing Layer (evm.signMessage, etc.)
packages/ows-types Shared types, errors, and cross-layer utils (including SD-JWT VC)
packages/ows-oid4 Optional credentials add-on (CredentialsHelper, HTTP OID4 clients)
packages/ows-wallet-utils Branding Layer Postmate/RPC wrappers; credentials wire hooks only
packages/ows-provider Host Layer EIP-1193 + credentials proxy; SD-JWT verify
examples/shared Demo-only OID4 mocks, stores, and fixtures (not published)
packages/ows-onchain EIP-8244 deploy pipeline for ows-signer
spec/credentials/ OWS Credentials Extension normative docs
packages/ows-signeris plain JavaScript only. No TypeScript, bundler, dependencies, or build step.- Signing Layer signs curves/digests, not chain APIs. EIP-191, EIP-712, etc. belong in
ows-signer-utils. - Host Layer must never embed the Signing Layer directly. Always Host → Branding → Signing.
- Signing Layer accepts
postMessageonly whenevent.source === window.parentandwindow.parent !== window.top. - Do not vendor Postmate. Use the
postmatepackage from npm. Set iframeallow(WebAuthn, clipboard) before navigation — seeOWSProxy/createSignerIframe. - Examples are not published.
Prefer methods on objects over standalone exported functions when the logic belongs to a single class or module (e.g. serialize an RPC envelope inside RpcHostClient, not as a public createRpcRequest helper). Export free functions only when they are genuinely shared utilities with multiple independent call sites.
No deprecations during active development. OWS is pre-1.0 and not yet published for external consumers. When a pattern is renamed or superseded, update all call sites to the new API — do not leave @deprecated aliases, re-exports, or compatibility shims. Remove old names once migrations are complete.
Prefer Typescript interfaces over generic types. Prefix all interfaces with I, ie IHolderSigner. HolderSigner would be an implementation. Prefer enums over discriminated strings. Prefix enums with E, ie EEip1193Method.
Prefer constructor parameter properties for DTO / event / value-object classes instead of declaring fields and assigning them separately — it is shorter and easier to read:
// Prefer
class AccountCreatedEvent extends OWSAnalyticsEvent {
constructor(
hostDomain: DomainString,
public readonly accountAddress: EVMAccountAddress,
) {
super("AccountCreated", hostDomain);
}
}
// Avoid
class AccountCreatedEvent extends OWSAnalyticsEvent {
readonly accountAddress: EVMAccountAddress;
constructor(hostDomain: DomainString, accountAddress: EVMAccountAddress) {
super("AccountCreated", hostDomain);
this.accountAddress = accountAddress;
}
}Use branded primitives from @1shotapi/ows-types (ts-brand) anywhere a value is semantically more than a raw string, number, or bigint.
Always prefer a branded primitive over an unbranded string, number, or bigint. Do not use raw primitives for domain values (addresses, call IDs, chain IDs, amounts, etc.) when a branded type exists or should exist in ows-types.
Never union a branded type with its base primitive (e.g. EVMAccountAddress | string, DomainString | string, UnixTimestamp | number). That defeats the brand — callers can pass unvalidated strings/numbers and TypeScript will not catch it. Accept only the branded type at API boundaries; brand at the validation/derivation site before calling.
- Define each primitive in
packages/ows-types/src/primitives/<Name>.tsusing the same pattern asRPCCallId(type alias +make()constructor). - Export from
packages/ows-types/src/primitives/index.ts. - Examples:
EVMAccountAddress(not0x${string}or viemAddress),EVMSignatureHex,SolanaAccountAddress,SolanaProgramAddress,SolanaMintAddress,BitcoinSegwitAccountAddress,BitcoinChainId("Bitcoin"/"BitcoinTestnet"),SolanaChainId("solana:mainnet"/"solana:devnet"/"solana:testnet"),RPCCallId. - Narrow
OWSChainIdwithChainUtils.isEVMChainId/isBitcoinChainId/isSolanaChainId(seepackages/ows-types/src/utils/ChainUtils.ts). - Brand at the point of validation or derivation (e.g. Zod
.transform(EVMAccountAddress), or afterpublicKeyToAddress). - For polymorphic APIs (e.g. EIP-1193
request), use a mapped method table (EIP1193Requestsinows-types) with a conditional generic onrequest()so callers get inferred branded results withoutascasts.
Do not introduce parallel branded types in consumer packages — add or extend primitives in ows-types instead.
- Node.js 22+, npm workspaces
- TypeScript packages:
tsc→dist/ - Changesets for
@1shotapi/*independent versioning - On-chain: https://github.com/TtheBC01/eip-8244
npm install && npm run build && npm test
npm run dev -w @1shotapi/ows-example-general-wallet
npm run dev -w @1shotapi/ows-example-host
npm run dev -w @1shotapi/ows-example-credential-issuer
npm run dev -w @1shotapi/ows-example-credential-verifierBootstrapped; OWS signer logic and SDKs not yet implemented.
First-party integrator skills live in 1Shot-API/skills (not in this repo). Install globally for local Cursor:
npm run skills:install
# ows-branding-layer + 1shot-wallet → ~/.cursor/skillsWhen you change OWS packages, Branding/Signing protocol, or Host integration surfaces in a way that affects integrators, update the matching skill in 1Shot-API/skills (ows-branding-layer, and 1shot-wallet when Host-facing guidance must change) in the same effort.