The nice thing about Cardano wallet integrations in 2025 is that you don’t really “integrate Nami” or “integrate Eternl” anymore. You integrate CIP‑30, and you get Nami, Eternl, Flint, Lace, Yoroi, and friends almost for free.(Cardano Improvement Proposals)
In this article I’ll walk through how CIP‑30 actually works, how Nami/Eternl/Flint inject their APIs, and how I structure a React + TypeScript dApp so the wallet layer stays boring and predictable.
1. Mental Model: CIP‑30 as the Web3 Bridge
CIP‑30 defines a browser bridge between dApps and wallets:
Browser tab
|
v
window.cardano
|
+-- .nami
+-- .eternl
+-- .flint
+-- .lace
+-- .yoroi
...
Each wallet injects a namespaced provider under window.cardano.{walletName} and must expose:
- An initial API:
enable(),isEnabled(),name,icon,apiVersion. - A full API (returned by
enable()), with methods likegetNetworkId,getUtxos,getBalance,signTx,signData,submitTx.(Cardano Improvement Proposals)
CIP‑30 treats wallets and dApps as two separate security domains:
dApp --(enable request)--> wallet
(user approves)
dApp <--(API object)------ wallet
Nothing wallet‑specific leaks until the user approves enable(). That’s why all the Nami / Eternl / Flint docs say “call window.cardano.{walletName}.enable() first.” (Cardano Improvement Proposals)
Production note: I always treat CIP‑30 as a capability system. The only thing your dApp should ever persist is “this origin is allowed to call
enable()without nagging the user again,” not raw keys or secrets.
2. What the CIP‑30 API Actually Gives You
CIP‑30 is small on purpose. The full API returned by enable() looks like this (simplified):(Cardano Improvement Proposals)
Initial API (on window.cardano.{walletName})
- enable({ extensions }): Promise
- isEnabled(): Promise
- apiVersion: string // "1"
- name: string // e.g. "Nami"
- icon: string // data URL or https://...
- supportedExtensions: Extension[]
Full API (the "api" object)
- getExtensions(): Promise
- getNetworkId(): Promise // 0 = testnet, 1 = mainnet
- getUtxos(amount, paginate): Promise
- getBalance(): Promise>
- getUsedAddresses(paginate): Promise
- getUnusedAddresses(): Promise
- getChangeAddress(): Promise
- getRewardAddresses(): Promise
- signTx(tx: cbor, partialSign?: boolean): Promise>
- signData(addr: Address, payload: Bytes): Promise
- submitTx(tx: cbor): Promise
All “payload” types are hex‑encoded CBOR strings, not JSON. The spec leans heavily on the Shelley binary CDDL and CIP‑8 for message signing.(Cardano Improvement Proposals)
So a typical dApp stack looks like:
CIP-30 wallet API <---> cardano-serialization-lib / Lucid / Mesh
| |
+----------> your React app <----+
You use CSL / Lucid / Mesh to build transactions and interpret CBOR, and you use CIP‑30 only as transport + signing + submit.(Cardano Developer Portal)
3. Nami, Eternl, Flint: How They Inject
CIP‑30 itself lists the main implementors: Eternl, Flint, Nami, Lace, Yoroi, Gero, NuFi and others.(Cardano Improvement Proposals)
In practice:
Nami -> window.cardano.nami
Eternl -> window.cardano.eternl (and legacy window.cardano.ccvault)
Flint -> window.cardano.flint
You can see this pattern in official repos and docs: Nami’s README explicitly shows window.cardano.nami.enable(), Eternl’s docs use window.cardano.eternl, and example connectors iterate over window.cardano to discover wallets.(GitHub)
Two practical gotchas show up over and over:
- Injection timing:
window.cardano may be undefined on first render.
Extensions inject after the page loads.
- Duplicate names:
Eternl is exposed as both "ccvault" and "eternl" for
backward compatibility.
You should assume that window.cardano is optional, late, and a bit messy, and wrap it behind a clean React abstraction.
4. React Architecture: One Wallet Context, Many Wallets
The pattern I use is:
+-----------------------------------------+
| |
| - scans window.cardano |
| - exposes: |
| availableWallets[] |
| currentWallet |
| api (CIP-30 api) |
| connect(walletId) |
| disconnect() |
| networkId, addresses, balance |
+------------------+----------------------+
|
used by the rest of the app
This gives you:
- A single place that knows about CIP‑30 details.
- A dumb “Connect Wallet” button that just calls
connect(id). - A stable source of truth for
networkIdand addresses.
React hooks libraries like cardano‑connect‑with‑wallet and starter kits like txpipe’s wallet‑connect‑starter‑kit implement exactly this idea; if you don’t want to write it all from scratch, they’re worth reading or using directly.(GitHub)
Production note: I’m ruthless about keeping CSL / Lucid out of the WalletProvider. The provider knows how to talk to wallets; separate services know how to build transactions. That separation makes testing and refactors much easier.
5. Discovering Wallets in the Browser
At runtime, you discover wallets by inspecting window.cardano and reading the CIP‑30 metadata fields (name, icon, apiVersion).(Cardano Improvement Proposals)
In TypeScript, I like a tiny utility:
type RawWalletEntry = {
apiVersion?: string;
name?: string;
icon?: string;
enable?: (args?: unknown) => Promise<unknown>;
};
function listCip30Wallets() {
const w = (window as any).cardano as Record<string, RawWalletEntry> | undefined;
if (!w) return [];
return Object.entries(w)
.filter(([_, v]) => v typeof v.enable === 'function')
.map(([id, v]) => ({
id, // "nami", "eternl", "flint", ...
name: v.name id,
icon: v.icon '',
apiVersion: v.apiVersion '1',
}));
}
This matches CIP‑30’s requirement that each wallet namespace object exposes enable, name, icon, and apiVersion.(Cardano Improvement Proposals)
From there you can render a simple picker:
[ Connect Wallet ]
|
v
+-------------------------------+
| Choose a wallet |
| [ Nami (icon) ] |
| [ Eternl (icon) ] |
| [ Flint (icon) ] |
+-------------------------------+
Clicking one just calls your connect(id) with the selected wallet id.
6. Handshake Flow: enable(), isEnabled(), getNetworkId()
The CIP‑30 handshake is always the same, regardless of wallet:(Cardano Improvement Proposals)
1. Detect wallet provider: window.cardano.{walletName}
2. Optional: isEnabled() to see if user already granted access.
3. Call enable({ extensions }) to request the full API.
4. Wallet shows permission popup.
5. If user accepts, dApp receives "api" object with full methods.
6. dApp calls getNetworkId(), getUsedAddresses(), etc.
In React + TypeScript, the core connect logic looks like:
async function connect(walletId: string) {
const provider = (window as any).cardano.[walletId];
if (!provider) throw new Error(`Wallet ${walletId} not found`);
const api = await provider.enable(); // user may get a popup
const networkId = await api.getNetworkId(); // 0 = testnet, 1 = mainnet
const addresses = await api.getUsedAddresses();
const rewardAddrs = await api.getRewardAddresses();
// store in your WalletProvider state
}
This follows the spec’s initial API and full API exactly: enable() returns the api object, and getNetworkId, getUsedAddresses, getRewardAddresses are part of the full API.(Cardano Improvement Proposals)
On the UX side, you should:
- Reject connections if the wallet’s
networkIddoesn’t match your target network. - Show the active wallet name and a short bech32‑truncated address in the header.
- Offer a “Disconnect” that simply drops your in‑memory
apireference and state.
7. Working with UTxOs and Balances
Once you have api, you can load wallet state.
CIP‑30 gives you two ways to look at funds:(Cardano Improvement Proposals)
- getUtxos(amount, paginate) -> CBOR'ed TransactionUnspentOutput[]
- getBalance() -> CBOR'ed Value
The signatures are:
getUtxos(
amount?: cbor,
paginate?: { page: number; limit: number }
): Promise;
getBalance(): Promise>;
Remember: everything is hex CBOR, not JSON. You hand this straight to:
cardano-serialization-lib(browser / Node).- Or a higher‑level library like Lucid / Mesh that wraps CSL.(Cardano Developer Portal)
Production note: I avoid building transactions directly from
getBalance(). For real dApps, I take the UTxOs, filter them according to my needs (collateral, token selection, min ADA), and let CSL or Lucid do proper coin selection.getBalance()is useful for dashboards, not for transaction building.
8. Signing Transactions and Submitting Through the Wallet
CIP‑30 splits transaction building from signing:
1. dApp builds an unsigned transaction (CBOR).
2. dApp calls api.signTx(unsignedTxCbor, partialSign).
3. Wallet shows a signing popup and returns only the witness set.
4. dApp merges witness set with transaction body.
5. dApp calls api.submitTx(signedTxCbor).
The spec is explicit: signTx returns just the witness set, not a full transaction.(Cardano Improvement Proposals)
So your stack is:
[ React + TypeScript ]
|
v
[ CSL / Lucid / Mesh ] -- build tx body (CBOR)
|
v
[ CIP-30 api.signTx ] -- get witness set CBOR
|
v
[ CSL / Lucid / Mesh ] -- attach witness set
|
v
[ CIP-30 api.submitTx ] -- send signed tx
Open‑source examples like cardano‑wallet‑connector, txpipe’s starter kit, and Lucid’s docs all follow exactly this approach: they use CIP‑30 for signing and submitting, and CSL/Lucid for transaction construction.(GitHub)
If you also use api.signData (CIP‑8) for off‑chain authentication, keep in mind:
- It signs a COSE envelope, not raw text.
- The wallet returns both the signature and a COSE key structure.(Cardano Improvement Proposals)
That’s perfect for “Sign in with Cardano” flows in Kotlin/Java backends as well.
9. Handling Events, Errors, and Edge Cases
CIP‑30 defines a small set of error types: APIError, TxSignError, TxSendError, DataSignError, and PaginateError, with strongly typed codes such as Refused, UserDeclined, AccountChange.(Cardano Improvement Proposals)
Practical things I always handle in the dApp:
- User declined:
signTx / signData return TxSignError.UserDeclined or DataSignError.UserDeclined.
UI should show a non-scary message: “Signature cancelled.”
- Account / network change:
some wallets use experimental events like:
window.cardano.nami.experimental.on('accountChange', ...)
CIP-30 itself suggests using APIError.AccountChange to force a fresh enable().
- Rate limiting / failed submission:
TxSendError.Refused vs TxSendError.Failure give you a hint
whether to retry, switch relays, or show the wallet’s error.
Wallets like Eternl also expose experimental methods (e.g. window.cardano.eternl.experimental.feeAddress) under an experimental namespace, as encouraged by the spec. That pattern is explicitly allowed for non‑standard features.(Cardano Improvement Proposals)
Production note: In production UIs I always treat experimental APIs as “nice to have”. If they fail or disappear, the dApp should silently fall back to pure CIP‑30 behavior.
10. React Example: Minimal Wallet Connector
Here’s a stripped‑down React pattern that supports Nami, Eternl, and Flint via CIP‑30.
A simple hook to manage connection:
import { useEffect, useState } from "react";
type Cip30Api = {
getNetworkId(): Promise<number>;
getUsedAddresses(): Promise<string[]>;
getBalance(): Promise<string>;
// ... add what you actually use
};
type ConnectedWallet = {
id: string;
name: string;
icon: string;
api: Cip30Api;
networkId: number;
addresses: string[];
};
export function useCip30Wallet() {
const [available, setAvailable] = useState<
{ id: string; name: string; icon: string }[]
>([]);
const [current, setCurrent] = useState<ConnectedWallet | null>(null);
useEffect(() => {
function scan() {
const cardano = (window as any).cardano;
if (!cardano) return;
const entries = Object.entries(cardano)
.filter(([_, v]) => v typeof v.enable === "function")
.map(([id, v]: [string, any]) => ({
id,
name: v.name id,
icon: v.icon "",
}));
setAvailable(entries);
}
// initial scan + delayed re-scan to handle late injection
scan();
const id = setTimeout(scan, 1500);
return () => clearTimeout(id);
}, []);
async function connect(id: string) {
const provider = (window as any).cardano.[id];
if (!provider) throw new Error(`Wallet ${id} not found`);
const api: Cip30Api = await provider.enable();
const networkId = await api.getNetworkId();
const addresses = await api.getUsedAddresses();
setCurrent({
id,
name: provider.name id,
icon: provider.icon "",
api,
networkId,
addresses,
});
}
function disconnect() {
setCurrent(null);
}
return { available, current, connect, disconnect };
}
This is essentially what txpipe’s starter kit and Cardano Foundation’s connect-with-wallet library do behind the scenes, just spelled out in one place.(GitHub)
Your UI then becomes:
function ConnectWalletButton() {
const { available, current, connect, disconnect } = useCip30Wallet();
if (current) {
const shortAddr = current.addresses[0].slice(0, 8) + "…";
return (
<button onClick={disconnect}>
{current.name} ({shortAddr}) – Disconnect
</button>
);
}
if (!available.length) {
return <span>No CIP-30 wallet detected</span>;
}
return (
<div>
{available.map((w) => (
<button key={w.id} onClick={() => connect(w.id)}>
{w.icon <img src={w.icon} alt={w.name} width={16} height={16} />}{" "}
{w.name}
</button>
))}
</div>
);
}
From here you plug current.api into your transaction‑building services (CSL, Lucid, Mesh, TS‑SDK) and never touch window.cardano anywhere else in the app.(Cardano Developer Portal)
11. Mobile and Advanced Topics
CIP‑30 started in the desktop browser space, but:
- Some mobile wallets (Flint mobile, Eternl mobile) already support CIP‑30 dApps in mobile browsers or via embedded dApp browsers.(Cardano Stack Exchange)
- New CIPs (CIP‑45, CIP‑95, CIP‑103, CIP‑106, etc.) extend the web‑wallet bridge with WalletConnect‑style flows, account keys, bulk signing, multisig, and Conway‑era governance features.(Cardano Improvement Proposals)
The right approach is to:
- Treat CIP-30 as your baseline.
- Use enable({ extensions: [...] }) to request extra capabilities
once you need them.
- Feature-detect extensions via getExtensions() and fail gracefully.
That way your Nami/Eternl/Flint integration remains valid even as newer wallets start adopting CIP‑95+ features.
Conclusion
Integrating Cardano wallets today is less about writing wallet‑specific glue and more about doing CIP‑30 well:
- Understand the two‑stage API:
window.cardano.{walletName}→enable()→ fullapi. - Use the standard methods (
getNetworkId,getUtxos,signTx,submitTx) and delegate CBOR handling to CSL/Lucid/Mesh. - Wrap everything in a single React context / hook, so the rest of your app never touches
window.cardanodirectly. - Treat errors, account changes, and experimental extensions as first‑class citizens rather than afterthoughts.
Once you have that in place, supporting Nami, Eternl, Flint, Yoroi, Lace and the next five wallets on cardano‑caniuse becomes mostly a matter of testing, not rewrites.(Stack Overflow)
References and Further Reading
- CIP‑30: Cardano dApp‑Wallet Web Bridge (full spec, methods, errors, extensions).(Cardano Improvement Proposals)
- Wallet docs and examples for Nami, Eternl, Flint (injection patterns, sample code).(GitHub)
- txpipe wallet‑connect‑starter‑kit – React example using CIP‑30 with multiple wallets.(GitHub)
- Cardano Foundation connect‑with‑wallet hooks – reusable React hooks for CIP‑30 integration.(Cardano Foundation)
- Mesh, Lucid, Marlowe TS‑SDK – higher‑level TypeScript SDKs that sit on top of CIP‑30.(Cardano Developer Portal)
Completion scope and production contract
This completion review turns the earlier conceptual treatment into a release-oriented engineering contract. It treats Integrating Cardano Wallets: CIP-30 Implementation Guide as a Cardano ledger, node, wallet, or Plutus component, follows a era-tagged block, transaction, UTXO entry, script purpose, or protocol message through validation and durable state, and separates normative requirements from implementation policy. The normative baseline is the relevant Cardano ledger era specification, CIP, and network protocol; deployment defaults, caching, retry limits, and operator thresholds are explicitly local policy. 9
The intended audience is experienced developers and architects. Readers should understand the surrounding chain or application model, typed data structures, persistence, and basic security engineering. The scope includes correctness, implementation boundaries, deterministic tests, failure recovery, security, performance, and observability. It does not claim that the educational companion is a drop-in replacement for a maintained protocol or cryptographic library. Production adoption requires an independent threat model, compatibility testing against the authoritative implementation, and operational ownership. 10
The mental model used throughout is deliberately strict: untrusted input crosses network mini-protocol, ledger, wallet, script, and persistence boundaries; a validator derives facts under the relevant Cardano ledger era specification, CIP, and network protocol; accepted transitions update era-aware ledger, stake, protocol-parameter, and chain state; and observers consume committed facts, never optimistic intermediate mutations. A guarantee is stated only when it follows from those rules and assumptions. Heuristics such as fee selection, caching, peer scoring, timeouts, user messaging, or alert thresholds remain policy and may be tuned without redefining validity. 11
Reader contract and scope
For Integrating Cardano Wallets: CIP-30 Implementation Guide, this review makes the exact user decision and the prerequisites needed to make it safely explicit. Start from one era-tagged block, transaction, UTXO entry, script purpose, or protocol message and write down its origin, canonical representation, validation context, authority, and durable outcome. The Cardano ledger, node, wallet, or Plutus component must not infer a stronger fact from transport success, cache presence, or an upstream acknowledgement. Its authoritative state is era-aware ledger, stake, protocol-parameter, and chain state, and every projection must remain rebuildable or reconcilable from committed facts. This framing distinguishes a protocol guarantee from an operational convenience and gives reviewers a concrete place to challenge an assumption. 9
The principal failure to design against is an attractive example being mistaken for a complete production design. Address it before optimizing by defining a narrow ownership boundary, stable identities, bounded resource use, and a machine-readable outcome for every rejected transition. Record a scope statement, excluded concerns, and a reviewable acceptance criterion. A reviewer should be able to trace each accepted result to input bytes, rule or policy version, prior state, and commit identity without relying on prose logs. When the authority cannot be reached or context is incomplete, return an explicit unavailable or pending state; do not convert uncertainty into acceptance.
Precise vocabulary and authority
Treat precise vocabulary and authority as part of the executable design of Integrating Cardano Wallets: CIP-30 Implementation Guide, not as documentation added after coding. The relevant operating envelope includes epoch transitions, chain synchronization, rollbacks, transaction submission, and script evaluation. For each mode, identify which state is authoritative, which work may be retried, what is bounded, and which observation proves progress. This is especially important across network mini-protocol, ledger, wallet, script, and persistence boundaries, where delivery and processing are different events and where local time or arrival order may not reflect authoritative order. 10
A useful review asks how the design behaves under era mismatch, invalid CBOR, stale protocol parameters, rollback, budget exhaustion, and cryptographic verification failure. The unsafe outcome is teams using the same word for incompatible states or guarantees. Prevent it with explicit preconditions and postconditions, and retain a glossary tied to the normative authority for every overloaded term as release evidence. Use stable codes rather than exception text, keep policy configuration versioned, and attach the accepted policy or rule version to durable results. An node, wallet, or application operator must be able to stop intake, drain or quarantine work, compare local state with authority, and resume without inventing a second side effect.
Trust assumptions
The implementation of Integrating Cardano Wallets: CIP-30 Implementation Guide should expose which actors, clocks, stores, libraries, and upstream systems may fail or act maliciously through types and module boundaries. Parse external representations once, preserve the original identity when audit or replay needs it, and pass validated domain values inward. Mutations of era-aware ledger, stake, protocol-parameter, and chain state belong behind one authoritative transition function or transaction boundary. Network clients, storage adapters, user interfaces, and telemetry exporters must not duplicate consensus or business rules. That separation keeps deterministic logic testable and prevents a library upgrade from silently redefining validity. 11
Assume that an implicit trusted component invalidating the claimed guarantee will eventually occur in a staging fault test or production incident. The control is not a catch-all retry: classify the outcome, decide whether the identical operation is safe to repeat, bound attempts and elapsed time, and surface terminal work for reconciliation. The minimum evidence is a trust-boundary diagram and an assumption register with owners. Keep signing keys, KES periods, VRF proofs, wallet permissions, datum, and redeemers out of ordinary logs, and prefer hashes, version identifiers, counts, and sanitized reason codes. Any emergency bypass must be narrow, time-limited, approved, and observable.
Architecture and ownership
Verification for Integrating Cardano Wallets: CIP-30 Implementation Guide must demonstrate component responsibilities and the direction in which facts and commands move at several layers. Small tests cover deterministic transforms and boundary values; contract tests pin serialized forms; integration tests exercise real adapters; replay tests compare state roots or projections; and fault tests interrupt work at every commit boundary. The dataset must include normal history, malformed input, duplicates, reordering, maximum-size values, and changes of rule or schema version. A passing example is evidence about that environment and dataset, not a universal performance claim. 9
Make two components both believing they own the same transition a named negative test. The release packet should retain a context diagram, ownership table, and dependency rule, exact dependency and tool versions, the deterministic command, and its result. For performance work, report warm-up, repetitions, concurrency, percentiles, resource limits, and the point where backpressure begins. For security work, include abuse cases and independent review. A change is ready only when failures leave era-aware ledger, stake, protocol-parameter, and chain state safe, recovery is rehearsed, and telemetry explains both user-visible outcome and operator action.
Canonical representation
For Integrating Cardano Wallets: CIP-30 Implementation Guide, this review makes the byte-level or schema-level representation used for hashing, comparison, storage, and transport explicit. Start from one era-tagged block, transaction, UTXO entry, script purpose, or protocol message and write down its origin, canonical representation, validation context, authority, and durable outcome. The Cardano ledger, node, wallet, or Plutus component must not infer a stronger fact from transport success, cache presence, or an upstream acknowledgement. Its authoritative state is era-aware ledger, stake, protocol-parameter, and chain state, and every projection must remain rebuildable or reconcilable from committed facts. This framing distinguishes a protocol guarantee from an operational convenience and gives reviewers a concrete place to challenge an assumption. 10
The principal failure to design against is semantically equal values producing different identifiers or verification outcomes. Address it before optimizing by defining a narrow ownership boundary, stable identities, bounded resource use, and a machine-readable outcome for every rejected transition. Record golden encodings, round-trip tests, and rejection of non-canonical forms. A reviewer should be able to trace each accepted result to input bytes, rule or policy version, prior state, and commit identity without relying on prose logs. When the authority cannot be reached or context is incomplete, return an explicit unavailable or pending state; do not convert uncertainty into acceptance.
State-machine model
Treat state-machine model as part of the executable design of Integrating Cardano Wallets: CIP-30 Implementation Guide, not as documentation added after coding. The relevant operating envelope includes epoch transitions, chain synchronization, rollbacks, transaction submission, and script evaluation. For each mode, identify which state is authoritative, which work may be retried, what is bounded, and which observation proves progress. This is especially important across network mini-protocol, ledger, wallet, script, and persistence boundaries, where delivery and processing are different events and where local time or arrival order may not reflect authoritative order. 11
A useful review asks how the design behaves under era mismatch, invalid CBOR, stale protocol parameters, rollback, budget exhaustion, and cryptographic verification failure. The unsafe outcome is an impossible intermediate state becoming durable after interruption. Prevent it with explicit preconditions and postconditions, and retain a transition table exercised by positive, negative, and replay tests as release evidence. Use stable codes rather than exception text, keep policy configuration versioned, and attach the accepted policy or rule version to durable results. An node, wallet, or application operator must be able to stop intake, drain or quarantine work, compare local state with authority, and resume without inventing a second side effect.
Invariants
The implementation of Integrating Cardano Wallets: CIP-30 Implementation Guide should expose properties that must hold before and after every accepted operation through types and module boundaries. Parse external representations once, preserve the original identity when audit or replay needs it, and pass validated domain values inward. Mutations of era-aware ledger, stake, protocol-parameter, and chain state belong behind one authoritative transition function or transaction boundary. Network clients, storage adapters, user interfaces, and telemetry exporters must not duplicate consensus or business rules. That separation keeps deterministic logic testable and prevents a library upgrade from silently redefining validity. 9
Assume that local success concealing corruption in a related aggregate or index will eventually occur in a staging fault test or production incident. The control is not a catch-all retry: classify the outcome, decide whether the identical operation is safe to repeat, bound attempts and elapsed time, and surface terminal work for reconciliation. The minimum evidence is executable assertions at the narrowest authoritative boundary. Keep signing keys, KES periods, VRF proofs, wallet permissions, datum, and redeemers out of ordinary logs, and prefer hashes, version identifiers, counts, and sanitized reason codes. Any emergency bypass must be narrow, time-limited, approved, and observable.
Validation pipeline
Verification for Integrating Cardano Wallets: CIP-30 Implementation Guide must demonstrate cheap structural checks, contextual checks, authoritative verification, and commit order at several layers. Small tests cover deterministic transforms and boundary values; contract tests pin serialized forms; integration tests exercise real adapters; replay tests compare state roots or projections; and fault tests interrupt work at every commit boundary. The dataset must include normal history, malformed input, duplicates, reordering, maximum-size values, and changes of rule or schema version. A passing example is evidence about that environment and dataset, not a universal performance claim. 10
Make expensive or stateful work running before malformed input is rejected a named negative test. The release packet should retain ordered validation stages with stable machine-readable rejection codes, exact dependency and tool versions, the deterministic command, and its result. For performance work, report warm-up, repetitions, concurrency, percentiles, resource limits, and the point where backpressure begins. For security work, include abuse cases and independent review. A change is ready only when failures leave era-aware ledger, stake, protocol-parameter, and chain state safe, recovery is rehearsed, and telemetry explains both user-visible outcome and operator action.
Error semantics
For Integrating Cardano Wallets: CIP-30 Implementation Guide, this review makes the distinction between invalid input, conflict, unavailable dependency, retryable interruption, and internal defect explicit. Start from one era-tagged block, transaction, UTXO entry, script purpose, or protocol message and write down its origin, canonical representation, validation context, authority, and durable outcome. The Cardano ledger, node, wallet, or Plutus component must not infer a stronger fact from transport success, cache presence, or an upstream acknowledgement. Its authoritative state is era-aware ledger, stake, protocol-parameter, and chain state, and every projection must remain rebuildable or reconcilable from committed facts. This framing distinguishes a protocol guarantee from an operational convenience and gives reviewers a concrete place to challenge an assumption. 11
The principal failure to design against is blind retries amplifying a permanent failure or changing user intent. Address it before optimizing by defining a narrow ownership boundary, stable identities, bounded resource use, and a machine-readable outcome for every rejected transition. Record typed errors mapped consistently across logs, metrics, APIs, and queues. A reviewer should be able to trace each accepted result to input bytes, rule or policy version, prior state, and commit identity without relying on prose logs. When the authority cannot be reached or context is incomplete, return an explicit unavailable or pending state; do not convert uncertainty into acceptance.
Concurrency control
Treat concurrency control as part of the executable design of Integrating Cardano Wallets: CIP-30 Implementation Guide, not as documentation added after coding. The relevant operating envelope includes epoch transitions, chain synchronization, rollbacks, transaction submission, and script evaluation. For each mode, identify which state is authoritative, which work may be retried, what is bounded, and which observation proves progress. This is especially important across network mini-protocol, ledger, wallet, script, and persistence boundaries, where delivery and processing are different events and where local time or arrival order may not reflect authoritative order. 9
A useful review asks how the design behaves under era mismatch, invalid CBOR, stale protocol parameters, rollback, budget exhaustion, and cryptographic verification failure. The unsafe outcome is a check-then-act race accepting two individually plausible operations. Prevent it with explicit preconditions and postconditions, and retain a linearization argument plus stress tests at the chosen contention boundary as release evidence. Use stable codes rather than exception text, keep policy configuration versioned, and attach the accepted policy or rule version to durable results. An node, wallet, or application operator must be able to stop intake, drain or quarantine work, compare local state with authority, and resume without inventing a second side effect.
Idempotency and replay
The implementation of Integrating Cardano Wallets: CIP-30 Implementation Guide should expose how duplicate delivery, process restart, and historical backfill preserve the same result through types and module boundaries. Parse external representations once, preserve the original identity when audit or replay needs it, and pass validated domain values inward. Mutations of era-aware ledger, stake, protocol-parameter, and chain state belong behind one authoritative transition function or transaction boundary. Network clients, storage adapters, user interfaces, and telemetry exporters must not duplicate consensus or business rules. That separation keeps deterministic logic testable and prevents a library upgrade from silently redefining validity. 10
Assume that at-least-once delivery creating a second side effect will eventually occur in a staging fault test or production incident. The control is not a catch-all retry: classify the outcome, decide whether the identical operation is safe to repeat, bound attempts and elapsed time, and surface terminal work for reconciliation. The minimum evidence is stable operation identities, deduplication state, and deterministic replay fixtures. Keep signing keys, KES periods, VRF proofs, wallet permissions, datum, and redeemers out of ordinary logs, and prefer hashes, version identifiers, counts, and sanitized reason codes. Any emergency bypass must be narrow, time-limited, approved, and observable.
Persistence and atomicity
Verification for Integrating Cardano Wallets: CIP-30 Implementation Guide must demonstrate which facts commit together and how derived views catch up at several layers. Small tests cover deterministic transforms and boundary values; contract tests pin serialized forms; integration tests exercise real adapters; replay tests compare state roots or projections; and fault tests interrupt work at every commit boundary. The dataset must include normal history, malformed input, duplicates, reordering, maximum-size values, and changes of rule or schema version. A passing example is evidence about that environment and dataset, not a universal performance claim. 11
Make a crash exposing a cursor that claims work whose state was not committed a named negative test. The release packet should retain transaction boundaries, durable checkpoints, and reconciliation queries, exact dependency and tool versions, the deterministic command, and its result. For performance work, report warm-up, repetitions, concurrency, percentiles, resource limits, and the point where backpressure begins. For security work, include abuse cases and independent review. A change is ready only when failures leave era-aware ledger, stake, protocol-parameter, and chain state safe, recovery is rehearsed, and telemetry explains both user-visible outcome and operator action.
API and schema contracts
For Integrating Cardano Wallets: CIP-30 Implementation Guide, this review makes input limits, optionality, pagination, versioning, and compatibility behavior explicit. Start from one era-tagged block, transaction, UTXO entry, script purpose, or protocol message and write down its origin, canonical representation, validation context, authority, and durable outcome. The Cardano ledger, node, wallet, or Plutus component must not infer a stronger fact from transport success, cache presence, or an upstream acknowledgement. Its authoritative state is era-aware ledger, stake, protocol-parameter, and chain state, and every projection must remain rebuildable or reconcilable from committed facts. This framing distinguishes a protocol guarantee from an operational convenience and gives reviewers a concrete place to challenge an assumption. 9
The principal failure to design against is a technically valid deployment silently changing a consumer-visible meaning. Address it before optimizing by defining a narrow ownership boundary, stable identities, bounded resource use, and a machine-readable outcome for every rejected transition. Record consumer fixtures, schema-diff checks, and explicit deprecation windows. A reviewer should be able to trace each accepted result to input bytes, rule or policy version, prior state, and commit identity without relying on prose logs. When the authority cannot be reached or context is incomplete, return an explicit unavailable or pending state; do not convert uncertainty into acceptance.