Purpose: This document is written for LLM agents (Claude, etc.) to read and deterministically apply all required code changes when migrating from the OLD
MeshWallet(from@meshsdk/walletor@meshsdk/core) to the NEWMeshCardanoHeadlessWallet(from@meshsdk/wallet).Instructions to agent: Read this entire document before making any changes. Apply changes file-by-file. Each section contains SEARCH patterns and REPLACE rules. Follow them exactly.
- Architecture Overview
- Import Changes
- Construction Pattern Migration
- Address Methods: bech32 to hex Default
- signTx Split Into Two Methods
- Return Type Changes: High-Level to CBOR Hex
- signData Parameter Order Change
- Removed Methods and Replacements
- Type Reference Updates
- Static Method Changes
- Quick Reference: Method Migration Map
- Gotchas and Silent Failures
- Class:
MeshWalletimplementsIWallet - Package:
@meshsdk/walletor re-exported from@meshsdk/core - Internal engine:
EmbeddedWallet - Initialization:
new MeshWallet(options)thenawait wallet.init() - Address format default: bech32 (e.g.,
addr_test1qz...) - Dependencies:
@meshsdk/core-cst
- Base class:
CardanoHeadlessWalletimplementsICardanoWallet - Convenience class:
MeshCardanoHeadlessWalletextendsCardanoHeadlessWallet - Package:
@meshsdk/wallet - Internal engine:
AddressManager+CardanoSigner - Initialization: Static async factory methods (no constructor, no init)
- Address format default: hex (raw bytes)
- Dependencies:
@cardano-sdk/core+@cardano-sdk/util
The base class (CardanoHeadlessWallet) follows CIP-30 strictly — all methods return raw hex/CBOR. The convenience class (MeshCardanoHeadlessWallet) adds *Bech32() and *Mesh() methods that return human-friendly formats matching the OLD behavior.
IMPORTANT: You almost certainly want to use MeshCardanoHeadlessWallet, not CardanoHeadlessWallet, as it provides the convenience methods that match old MeshWallet behavior.
SEARCH for any of these import patterns:
import { MeshWallet } from "@meshsdk/wallet";
import { MeshWallet } from "@meshsdk/core";
import { MeshWallet, ... } from "@meshsdk/core";
import { MeshWallet, ... } from "@meshsdk/wallet";REPLACE with:
import { MeshCardanoHeadlessWallet } from "@meshsdk/wallet";IMPORTANT NOTES:
- If the old import was from
@meshsdk/core, change the package to@meshsdk/wallet - If other items were imported alongside
MeshWalletfrom@meshsdk/core(likeBlockfrostProvider,OfflineFetcher), keep those imports separate:import { MeshCardanoHeadlessWallet } from "@meshsdk/wallet"; import { BlockfrostProvider, OfflineFetcher } from "@meshsdk/core";
- You may also need to import
AddressTypefor the construction changes:import { MeshCardanoHeadlessWallet, AddressType } from "@meshsdk/wallet";
The OLD pattern used a constructor with a key object and required calling init() afterward. The NEW pattern uses static async factory methods that handle initialization internally.
NEW required config field: walletAddressType — must be either AddressType.Base (includes staking) or AddressType.Enterprise (no staking). If the old code did not specify accountType or used "payment", use AddressType.Base.
SEARCH:
const wallet = new MeshWallet({
networkId: <NETWORK_ID>,
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
key: {
type: "mnemonic",
words: <MNEMONIC_WORDS>,
},
});
await wallet.init();REPLACE:
const wallet = await MeshCardanoHeadlessWallet.fromMnemonic({
mnemonic: <MNEMONIC_WORDS>,
networkId: <NETWORK_ID>,
walletAddressType: AddressType.Base,
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
});NOTES:
<MNEMONIC_WORDS>must bestring[](same as before).- If the old code had
mnemonic.split(" "), keep that — the type is stillstring[]. - Remove any standalone
await wallet.init()calls — initialization is handled by the factory. - If
fetcherorsubmitterwereundefined, you can omit them.
SEARCH:
new MeshWallet({
networkId: <NETWORK_ID>,
key: {
type: "root",
bech32: <BECH32_KEY>,
},
...
});REPLACE:
await MeshCardanoHeadlessWallet.fromBip32Root({
bech32: <BECH32_KEY>,
networkId: <NETWORK_ID>,
walletAddressType: AddressType.Base,
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
});SEARCH:
new MeshWallet({
networkId: <NETWORK_ID>,
key: {
type: "bip32Bytes",
bip32Bytes: <BYTES>,
},
...
});REPLACE:
await MeshCardanoHeadlessWallet.fromBip32RootHex({
hex: <HEX_STRING>, // NOTE: now expects hex string, not Uint8Array
networkId: <NETWORK_ID>,
walletAddressType: AddressType.Base,
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
});NOTE: If the old code passed Uint8Array, convert to hex string first.
SEARCH:
new MeshWallet({
networkId: <NETWORK_ID>,
key: {
type: "cli",
payment: <PAYMENT_KEY>,
stake: <STAKE_KEY>,
},
...
});REPLACE:
await MeshCardanoHeadlessWallet.fromCredentialSources({
paymentCredentialSource: <PAYMENT_CREDENTIAL_SOURCE>,
stakeCredentialSource: <STAKE_CREDENTIAL_SOURCE>,
networkId: <NETWORK_ID>,
walletAddressType: AddressType.Base,
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
});NOTE: The credential source format differs from the old CLI key format. You will need to construct appropriate CredentialSource objects from the CLI keys. Consult the @meshsdk/wallet docs for CredentialSource types.
SEARCH:
new MeshWallet({
networkId: <NETWORK_ID>,
key: {
type: "address",
address: <BECH32_ADDRESS>,
},
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
});REPLACE:
await MeshCardanoHeadlessWallet.fromCredentialSources({
paymentCredentialSource: { type: "keyHash", hash: <PAYMENT_KEY_HASH> },
stakeCredentialSource: { type: "keyHash", hash: <STAKE_KEY_HASH> },
networkId: <NETWORK_ID>,
walletAddressType: AddressType.Base,
fetcher: <FETCHER>,
submitter: <SUBMITTER>,
});NOTE: You must decompose the bech32 address into its payment and stake key hashes. The old MeshWallet did this internally via buildAddressFromBech32Address(). You may need a helper to extract these hashes using @cardano-sdk/core:
import { Cardano } from "@cardano-sdk/core";
const addr = Cardano.Address.fromBech32(bech32Address);
const baseAddr = addr.asBase();
const paymentHash = baseAddr?.getPaymentCredential().hash;
const stakeHash = baseAddr?.getStakeCredential().hash;SEARCH for any standalone calls:
await wallet.init();
await cardanoWallet.init();REPLACE: Delete these lines entirely. Factory methods handle initialization.
In the OLD API, address methods returned bech32 strings by default (e.g., addr_test1qz...).
In the NEW API, the same method names return hex strings. New *Bech32() methods return bech32.
If your code expects bech32 addresses (which is almost always the case for display, transaction building, or API calls), you MUST switch to the Bech32 variant.
| OLD call | NEW call |
|---|---|
wallet.getChangeAddress() |
wallet.getChangeAddressBech32() |
wallet.getUsedAddresses() |
wallet.getUsedAddressesBech32() |
wallet.getUnusedAddresses() |
wallet.getUnusedAddressesBech32() |
wallet.getRewardAddresses() |
wallet.getRewardAddressesBech32() |
SEARCH for these patterns (with or without await):
await wallet.getChangeAddress()
await wallet.getChangeAddress("payment")
wallet.getChangeAddress()REPLACE with:
await wallet.getChangeAddressBech32()SEARCH:
await wallet.getUsedAddresses()REPLACE:
await wallet.getUsedAddressesBech32()SEARCH:
await wallet.getUnusedAddresses()REPLACE:
await wallet.getUnusedAddressesBech32()SEARCH:
await wallet.getRewardAddresses()REPLACE:
await wallet.getRewardAddressesBech32()NOTE on addressType parameter: The OLD getChangeAddress() accepted an optional addressType parameter ("payment" | "enterprise"). The NEW getChangeAddressBech32() does NOT accept this parameter. The address type is now determined at wallet construction time via walletAddressType. If you had code passing "enterprise", you need to construct the wallet with AddressType.Enterprise instead.
NOTE on the OLD *Hex() methods: The old getChangeAddressHex(), getUsedAddressesHex(), getUnusedAddressesHex(), and getRewardAddressesHex() methods are REMOVED. The base getChangeAddress(), getUsedAddresses(), etc. now return hex by default, so if you actually need hex, use the base methods.
OLD: signTx(unsignedTx, partialSign?, returnFullTx?) — by default returns the full signed transaction (returnFullTx=true).
NEW: Two separate methods:
signTx(tx, partialSign?)— returns witness set CBOR only (just the signatures)signTxReturnFullTx(tx, partialSign?)— returns the full signed transaction
Case A: signTx with default behavior (returnFullTx=true) or explicit returnFullTx=true
This is the most common case. The caller expects a full signed transaction back.
SEARCH:
await wallet.signTx(unsignedTx)
await wallet.signTx(unsignedTx, false)
await wallet.signTx(unsignedTx, true)
await wallet.signTx(unsignedTx, false, true)
await wallet.signTx(unsignedTx, true, true)REPLACE:
await wallet.signTxReturnFullTx(unsignedTx)
await wallet.signTxReturnFullTx(unsignedTx, false)
await wallet.signTxReturnFullTx(unsignedTx, true)
await wallet.signTxReturnFullTx(unsignedTx, false)
await wallet.signTxReturnFullTx(unsignedTx, true)Case B: signTx with returnFullTx=false (witness set only)
The caller explicitly wanted just the witness set.
SEARCH:
await wallet.signTx(unsignedTx, false, false)
await wallet.signTx(unsignedTx, true, false)REPLACE:
await wallet.signTx(unsignedTx, false)
await wallet.signTx(unsignedTx, true)SEARCH:
await wallet.signTxs(unsignedTxs)
await wallet.signTxs(unsignedTxs, partialSign)
await wallet.signTxs(unsignedTxs, partialSign, returnFullTx)REPLACE: The signTxs method may not exist on the new class. Implement manually:
const signedTxs = [];
for (const unsignedTx of unsignedTxs) {
signedTxs.push(await wallet.signTxReturnFullTx(unsignedTx, partialSign));
}Both the old and new signTx return Promise<string>. TypeScript will NOT catch this at compile time. If you forget to change signTx to signTxReturnFullTx, your code will compile but fail at runtime because you'll be submitting a witness set CBOR as if it were a full transaction.
OLD: Returns Asset[] — array of { unit: string, quantity: string } objects.
NEW base: Returns string — CBOR hex encoded value.
NEW Mesh: getBalanceMesh() returns Asset[] (matches old behavior).
SEARCH:
await wallet.getBalance()REPLACE (if you need Asset[] format):
await wallet.getBalanceMesh()OLD: Returns UTxO[] — array of Mesh UTxO objects.
NEW base: Returns string[] — array of CBOR hex strings.
NEW Mesh: getUtxosMesh() returns UTxO[] (matches old behavior).
SEARCH:
await wallet.getUtxos()
await wallet.getUtxos("payment")
await wallet.getUtxos("enterprise")REPLACE (if you need UTxO[] format):
await wallet.getUtxosMesh()NOTE: The addressType parameter is removed. Address type is determined at construction.
OLD: Returns UTxO[] — array of Mesh UTxO objects.
NEW base: Returns string[] — array of CBOR hex strings.
NEW Mesh: getCollateralMesh() returns UTxO[] (matches old behavior).
SEARCH:
await wallet.getCollateral()
await wallet.getCollateral("payment")REPLACE (if you need UTxO[] format):
await wallet.getCollateralMesh()OLD: signData(payload: string, address?: string) — payload first, address optional (defaults to change address).
NEW: signData(addressBech32: string, data: string) — address first and REQUIRED, data second.
SEARCH:
await wallet.signData(payload)
await wallet.signData(payload, address)REPLACE:
// If no address was provided, you must now explicitly get the address first:
const address = await wallet.getChangeAddressBech32();
await wallet.signData(address, payload)
// If address was provided:
await wallet.signData(address, payload) // note: swapped orderDANGER: Both parameters are string, so TypeScript will NOT catch a parameter swap at compile time.
OLD: wallet.getAddresses() — synchronous, returns object with all address variants.
NEW: No direct equivalent. Use individual async methods.
SEARCH:
wallet.getAddresses()
const addresses = wallet.getAddresses()
const addresses = cardanoWallet.getAddresses()REPLACE with individual calls as needed:
const baseAddressBech32 = await wallet.getChangeAddressBech32();
const rewardAddressBech32 = (await wallet.getRewardAddressesBech32())[0];
// Access hex variants via base class methods:
const baseAddressHex = await wallet.getChangeAddress();
const rewardAddressHex = (await wallet.getRewardAddresses())[0];NOTE: The old getAddresses() was synchronous. The new individual methods are all async. Callers must be updated to use await.
If the old code accessed specific properties like:
const addresses = wallet.getAddresses();
addresses.baseAddressBech32 // → await wallet.getChangeAddressBech32()
addresses.enterpriseAddressBech32 // → construct wallet with AddressType.Enterprise, then getChangeAddressBech32()
addresses.rewardAddressBech32 // → (await wallet.getRewardAddressesBech32())[0]OLD: wallet.getUsedAddress(addressType?) — synchronous, returns Address object.
NEW: No direct equivalent.
SEARCH:
wallet.getUsedAddress()
wallet.getUsedAddress("payment")
wallet.cardano.getUsedAddress()REPLACE:
// If you need a bech32 string:
(await wallet.getUsedAddressesBech32())[0]
// If you need a hex string:
(await wallet.getUsedAddresses())[0]NOTE: This was synchronous in the old API. The replacement is async. Update calling code accordingly.
SEARCH:
await wallet.getUsedUTxOs()
await wallet.getUsedUTxOs("payment")REPLACE:
await wallet.getUtxosMesh()SEARCH:
await wallet.getAssets()REPLACE (manual derivation):
import { POLICY_ID_LENGTH, resolveFingerprint, toUTF8 } from "@meshsdk/common";
const balance = await wallet.getBalanceMesh();
const assets = balance
.filter((v) => v.unit !== "lovelace")
.map((v) => {
const policyId = v.unit.slice(0, POLICY_ID_LENGTH);
const assetName = v.unit.slice(POLICY_ID_LENGTH);
const fingerprint = resolveFingerprint(policyId, assetName);
return { unit: v.unit, policyId, assetName: toUTF8(assetName), fingerprint, quantity: v.quantity };
});SEARCH:
await wallet.getLovelace()REPLACE:
const balance = await wallet.getBalanceMesh();
const lovelace = balance.find((v) => v.unit === "lovelace")?.quantity ?? "0";SEARCH:
await wallet.getPolicyIdAssets(policyId)REPLACE: Derive from getBalanceMesh() + filter by policyId prefix.
SEARCH:
await wallet.getPolicyIds()REPLACE: Derive from getBalanceMesh() + extract unique policy ID prefixes.
SEARCH:
await wallet.createCollateral()REPLACE: Build the transaction manually:
// Build a transaction sending 5 ADA to yourself
const address = await wallet.getChangeAddressBech32();
// Use your transaction builder to send 5000000 lovelace to `address`
// Sign with signTxReturnFullTx, then submitTxSEARCH:
wallet.getPubDRepKey()REPLACE: Not yet available in the new API. If needed, track upstream for DRep support.
SEARCH:
await wallet.getDRep()REPLACE: Not yet available in the new API.
SEARCH:
await wallet.getExtensions()REPLACE: Not yet available. Return [] if needed.
These were stubs returning undefined in the old code. Remove any calls.
REPLACE: Use getCollateralMesh() for UTxO[] or getCollateral() for CBOR hex.
REPLACE: Use fetchAccountUtxos() (now public on CardanoHeadlessWallet) for UTxO[], or getUtxos() for CBOR hex.
SEARCH in type positions (variable declarations, function parameters, return types, generics):
MeshWalletREPLACE:
MeshCardanoHeadlessWalletCommon patterns:
// OLD
wallet: MeshWallet
cardanoWallet: MeshWallet
cardano: MeshWallet
Promise<MeshWallet>
// NEW
wallet: MeshCardanoHeadlessWallet
cardanoWallet: MeshCardanoHeadlessWallet
cardano: MeshCardanoHeadlessWallet
Promise<MeshCardanoHeadlessWallet>OLD:
const mnemonic = MeshWallet.brew() as string[]; // generates mnemonic
const privateKey = MeshWallet.brew(true) as string; // generates private key
const mnemonic = MeshWallet.brew(false, 256) as string[]; // custom strengthREPLACE: Use an external mnemonic generation library (e.g., bip39):
import { generateMnemonic } from "bip39";
const mnemonic = generateMnemonic(256).split(" ");Or check if @meshsdk/common or @meshsdk/core-cst still exports a mnemonic generation utility.
| OLD Method | NEW Method | Notes |
|---|---|---|
new MeshWallet({...}) |
await MeshCardanoHeadlessWallet.fromMnemonic({...}) |
See Section 3 for all factory methods |
wallet.init() |
(removed) | Factory methods handle init |
wallet.getChangeAddress() |
wallet.getChangeAddressBech32() |
Was bech32, base now returns hex |
wallet.getChangeAddressHex() |
wallet.getChangeAddress() |
Base method now returns hex |
wallet.getUsedAddresses() |
wallet.getUsedAddressesBech32() |
Was bech32, base now returns hex |
wallet.getUsedAddressesHex() |
wallet.getUsedAddresses() |
Base method now returns hex |
wallet.getUnusedAddresses() |
wallet.getUnusedAddressesBech32() |
Was bech32, base now returns hex |
wallet.getUnusedAddressesHex() |
wallet.getUnusedAddresses() |
Base method now returns hex |
wallet.getRewardAddresses() |
wallet.getRewardAddressesBech32() |
Was bech32, base now returns hex |
wallet.getRewardAddressesHex() |
wallet.getRewardAddresses() |
Base method now returns hex |
wallet.signTx(tx) |
wallet.signTxReturnFullTx(tx) |
Old default was full tx; new signTx returns witness set only |
wallet.signTx(tx, partial, false) |
wallet.signTx(tx, partial) |
Witness-set-only behavior |
wallet.signTx(tx, partial, true) |
wallet.signTxReturnFullTx(tx, partial) |
Full tx behavior |
wallet.signTxs(txs, partial) |
(removed) | Implement loop with signTxReturnFullTx |
wallet.signData(payload, addr?) |
wallet.signData(addr, payload) |
Parameter order swapped, addr required |
wallet.getBalance() |
wallet.getBalanceMesh() |
Old returned Asset[], new base returns CBOR hex |
wallet.getUtxos() |
wallet.getUtxosMesh() |
Old returned UTxO[], new base returns CBOR hex strings |
wallet.getUtxosHex() |
wallet.getUtxos() |
Base method now returns CBOR hex |
wallet.getCollateral() |
wallet.getCollateralMesh() |
Old returned UTxO[], new base returns CBOR hex strings |
wallet.getCollateralHex() |
wallet.getCollateral() |
Base method now returns CBOR hex |
wallet.getAddresses() |
(removed) | Use individual getChangeAddressBech32(), etc. |
wallet.getUsedAddress() |
(await wallet.getUsedAddressesBech32())[0] |
Was sync, now async |
wallet.getUsedUTxOs() |
wallet.getUtxosMesh() |
Renamed |
wallet.getUnspentOutputs() |
wallet.fetchAccountUtxos() |
Now public |
wallet.getAssets() |
(removed) | Derive from getBalanceMesh() |
wallet.getLovelace() |
(removed) | Derive from getBalanceMesh() |
wallet.getPolicyIdAssets(id) |
(removed) | Derive from getBalanceMesh() |
wallet.getPolicyIds() |
(removed) | Derive from getBalanceMesh() |
wallet.createCollateral() |
(removed) | Build tx manually |
wallet.getPubDRepKey() |
(removed) | Not yet available |
wallet.getDRep() |
(removed) | Not yet available |
wallet.getExtensions() |
(removed) | Not yet available |
MeshWallet.brew() |
(removed) | Use bip39 or similar |
wallet.getNetworkId() |
wallet.getNetworkId() |
Unchanged |
wallet.submitTx(tx) |
wallet.submitTx(tx) |
Unchanged |
These are the most dangerous changes because they compile fine but fail at runtime:
- Symptom: Transaction submission fails with deserialization error
- Cause:
signTx()now returns a witness set CBOR string, not a full transaction - Fix: Use
signTxReturnFullTx()instead
- Symptom: Address validation fails, blockchain queries return empty results, or "invalid address" errors
- Cause: Hex string passed where bech32 was expected
- Fix: Use
getChangeAddressBech32()instead
- Symptom: Signing fails or produces invalid signature
- Cause:
signData(payload, address)was called but new API expectssignData(address, payload) - Fix: Swap the parameters
- Symptom: Code tries to
.find()or.filter()on a string - Cause:
getBalance()now returns a single CBOR hex string - Fix: Use
getBalanceMesh()for Asset[] format
- Symptom:
walletis a Promise, not a wallet instance; methods fail with "not a function" - Cause: Factory methods return
Promise<MeshCardanoHeadlessWallet>, must be awaited - Fix: Ensure
awaiton the factory method call
- Old MeshWallet source:
https://github.com/MeshJS/mesh/blob/main/packages/mesh-wallet/src/mesh/index.ts - New MeshCardanoHeadlessWallet source:
https://github.com/MeshJS/wallet/blob/main/src/cardano/wallet/mesh/mesh-wallet.ts - New CardanoHeadlessWallet base:
https://github.com/MeshJS/wallet/blob/main/src/cardano/wallet/mesh/cardano-base-wallet.ts