Overview
Magic Money Wallet exposes standard wallet interfaces instead of a custom-only API. EVM dApps use EIP-1193 and EIP-6963, Solana dApps use the Wallet Standard and legacy window.solana, Cardano dApps use window.cardano.magicmoney, Bitcoin and Ordinals dApps use window.unisat or window.btc_providers, and QR or URI based flows use WalletConnect v2.
Detect Magic Money alongside MetaMask, Rabby, Coinbase Wallet, and other injected wallets.
Supports connect, message signing, transaction signing, and sign-and-send flows.
Connect at window.cardano.magicmoney with UTxO, balance, signing, and submit methods.
Connect payment and ordinals addresses, sign messages, sign PSBTs, broadcast, and send BTC.
Quick Start
For the widest compatibility, detect Magic Money through standards first and only fall back to direct globals when the dApp framework requires it.
async function connectMagicMoneyEvm() {
const provider = window.ethereum;
if (!provider) throw new Error("No EVM wallet found");
const accounts = await provider.request({
method: "eth_requestAccounts"
});
const chainId = await provider.request({ method: "eth_chainId" });
return { address: accounts[0], chainId };
}
async function connectMagicMoneyCardano() {
const wallet = window.cardano?.magicmoney;
if (!wallet) throw new Error("Magic Money Cardano provider not found");
const api = await wallet.enable();
const networkId = await api.getNetworkId();
const changeAddressHex = await api.getChangeAddress();
return { api, networkId, changeAddressHex };
}
async function connectMagicMoneyBitcoin() {
const provider = window.unisat;
if (!provider?.isMagicMoney) throw new Error("Magic Money Bitcoin provider not found");
const [paymentAddress] = await provider.requestAccounts();
const publicKey = await provider.getPublicKey();
const balance = await provider.getBalance();
return { paymentAddress, publicKey, balance };
}
Detect Wallet
On EVM pages, prefer EIP-6963. Magic Money announces with the reverse DNS identifier info.chainlens.magicmoney. If multiple wallets are installed, show the user a picker.
const discoveredWallets = [];
window.addEventListener("eip6963:announceProvider", (event) => {
const { info, provider } = event.detail;
discoveredWallets.push({ info, provider });
if (info.rdns === "info.chainlens.magicmoney") {
console.log("Magic Money Wallet found", info.name);
}
});
window.dispatchEvent(new Event("eip6963:requestProvider"));
window.ethereum belongs to Magic Money. Browser users may have several wallets installed, and another extension can own that global.
EVM Provider
Magic Money implements the common EIP-1193 request shape. User consent is required for account access and signing requests.
| Method | Use | Notes |
|---|---|---|
eth_requestAccounts | Connect wallet | Prompts the user for origin approval. |
eth_accounts | Read connected accounts | Returns an empty array until approved. |
eth_chainId | Read active chain | Returns a hex chain ID such as 0x1. |
wallet_switchEthereumChain | Switch chain | Supports configured EVM chains in Magic Money. |
wallet_addEthereumChain | Add or select chain | Used for dApps that provide chain metadata. |
personal_sign | Message signing | Shown to the user before signing. |
eth_signTypedData_v4 | EIP-712 typed data | Use this for structured login and permit flows. |
eth_sendTransaction | Send transaction | Transaction is reviewed, signed locally, then broadcast. |
await provider.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x2105" }] // Base mainnet
});
Solana Provider
Magic Money supports Solana dApps through Wallet Standard discovery and a legacy provider shape compatible with common Solana wallet adapter flows.
async function connectMagicMoneySolana() {
const provider = window.solana;
if (!provider) throw new Error("No Solana wallet found");
const result = await provider.connect();
const publicKey = result.publicKey.toString();
const message = new TextEncoder().encode("Sign in to my dApp");
const signed = await provider.signMessage(message, "utf8");
return { publicKey, signature: signed.signature };
}
Transaction flows should use signTransaction, signAllTransactions, or signAndSendTransaction depending on whether your app or the wallet should broadcast.
Cardano CIP-30
Cardano is exposed at window.cardano.magicmoney. The provider follows the CIP-30 pattern where enable() returns the full API object after user approval.
| Method | Purpose |
|---|---|
isEnabled() | Check if the current origin has permission. |
enable() | Request access and return the full wallet API. |
getNetworkId() | Returns 1 for mainnet. |
getBalance() | Returns balance CBOR hex. |
getUtxos() | Returns UTxO CBOR hex values. |
getChangeAddress() | Returns the change address as hex. |
getRewardAddresses() | Returns staking reward addresses as hex. |
signTx(txHex, partialSign) | Returns witness set CBOR hex. |
signData(addressHex, payloadHex) | Returns a signed data payload. |
submitTx(txHex) | Submits a signed transaction. |
const wallet = window.cardano?.magicmoney;
const api = await wallet.enable();
const txHex = "84a4..."; // unsigned transaction CBOR hex
const witnessSetHex = await api.signTx(txHex, true);
console.log({ witnessSetHex });
Bitcoin Provider
Magic Money exposes Bitcoin dApp connectivity for payment, Ordinals, Runes, and BRC-20 style apps. It supports a UniSat-compatible window.unisat surface and a sats-connect/WBIP style provider through window.btc_providers.
Bitcoin access is approval-gated by origin. The wallet returns a native SegWit payment address for BTC transfers and a Taproot address for ordinals workflows.
| Address | Purpose | Type |
|---|---|---|
| Payment | BTC send/receive, PSBT payment inputs | p2wpkh native SegWit, bc1q... |
| Ordinals | Ordinals, inscriptions, Runes, BRC-20 | p2tr Taproot, bc1p... |
UniSat-compatible API
Use window.unisat when your dApp already supports UniSat-style Bitcoin wallets. Magic Money only claims this global when another Bitcoin wallet has not already locked it, so dApps should still allow wallet selection when multiple wallets are available.
| Method | Purpose |
|---|---|
requestAccounts() | Prompt the user and return the payment address array. |
getAccounts() | Return connected payment accounts after approval. |
getPublicKey() | Return the payment public key as hex. |
getNetwork() | Returns livenet. |
getBalance() | Returns confirmed, unconfirmed, and total satoshi balances. |
signMessage(message, type) | Sign a message after user approval. Default type is ecdsa. |
signPsbt(psbtHex, options) | Sign one PSBT and return signed PSBT hex. |
signPsbts(psbtHexArray, options) | Sign multiple PSBTs. |
pushPsbt(psbtHex) | Broadcast an extracted raw transaction hex. |
pushTx(rawtx) | Broadcast a raw transaction hex. |
sendBitcoin(to, satoshis) | Build, sign, and broadcast a BTC transfer from the payment address. |
const provider = window.unisat;
const [address] = await provider.requestAccounts();
const signature = await provider.signMessage(
"Sign in to my Bitcoin dApp",
"ecdsa"
);
console.log({ address, signature });
const signedPsbtHex = await window.unisat.signPsbt(psbtHex, {
autoFinalized: true,
broadcast: false
});
// If your app extracts a raw transaction from the signed PSBT:
const txid = await window.unisat.pushTx({ rawtx: rawTxHex });
const sats = 25_000;
const txid = await window.unisat.sendBitcoin(
"bc1qexamplepaymentaddress...",
sats
);
sats-connect / WBIP provider
Apps that use WBIP-style providers can discover Magic Money through window.btc_providers and then call the matching provider from window.MagicMoneyProviders.BitcoinProvider or window.BitcoinProvider.
const entry = window.btc_providers?.find(
(provider) => provider.id === "MagicMoneyProviders.BitcoinProvider"
);
if (!entry) throw new Error("Magic Money Bitcoin provider not found");
const provider = window.MagicMoneyProviders.BitcoinProvider;
const addressesResult = await provider.request("getAddresses", {
purposes: ["payment", "ordinals"]
});
const psbtResult = await provider.request("signPsbt", {
psbt: psbtBase64,
signInputs: {},
broadcast: false,
finalize: true
});
WalletConnect
Magic Money uses WalletConnect v2 for dApps that prefer URI based pairing or for embedded ChainLens contexts where direct provider injection is not available.
| Namespace | Chains | Typical methods |
|---|---|---|
eip155 | All supported EVM chains | personal_sign, eth_sendTransaction, eth_signTypedData_v4 |
solana | Solana mainnet-beta | Sign message, sign transaction, sign and send |
cardano | Planned for finalized WC Cardano namespace support | CIP-30 remains the primary Cardano browser path. |
Networks
Magic Money derives addresses from one seed phrase and groups them into one portfolio. EVM networks share the same EOA; non-EVM ecosystems have their own derivation paths and address formats.
Security Model
- The renderer, popup, and dApp pages only receive public addresses, balances, metadata, and user-approved signatures.
- Private keys are derived transiently inside the privileged runtime for signing and are not exposed to dApp JavaScript.
- Desktop runs with
contextIsolation, sandboxing, and no renderernodeIntegration. - The extension inject script provides wallet APIs in the page, but storage and signing live behind the extension message bridge.
- Each origin must be approved before account access or signing requests can proceed.
API Proxy
Keyed providers are routed through a hosted Cloudflare Worker so API secrets are not shipped in the desktop app or browser extension bundle. The Worker injects provider keys server side, adds cache behavior for expensive reads, and keeps normal users from needing any local configuration.
Advanced operators can self-host the Worker and point Magic Money at their own endpoint. Public, keyless services like CoinGecko, DexScreener, DefiLlama, mempool.space, and Magic Eden can remain direct to avoid collapsing all users onto one shared proxy IP.
Build Notes
The wallet codebase ships as Electron desktop and Chrome Manifest V3 extension builds. Node.js 20+ and npm 10+ are expected for local development.
npm install
# Desktop app with hot reload
npm run dev
# Browser extension build
npm run build:extension
# Typecheck and tests
npm run typecheck
npm test
The dApp injection preloads are built through a separate inject bundle step. Keep that step attached to development, production, and package commands so EVM, Solana, and Cardano provider injection remains available.
Troubleshooting
| Problem | Check |
|---|---|
| Magic Money is not in your wallet list. | Listen for EIP-6963 announcements and dispatch eip6963:requestProvider after your listener is registered. |
eth_accounts returns empty. | Call eth_requestAccounts and wait for user approval. |
| Cardano methods are missing. | Use window.cardano.magicmoney.enable(); the full CIP-30 API is returned after approval. |
| Bitcoin provider is not visible. | Check window.unisat, window.btc_providers, and window.MagicMoneyProviders.BitcoinProvider. Another wallet may own window.unisat. |
| Bitcoin signing fails with permission errors. | Call requestAccounts() or getAddresses first so the origin is approved before message or PSBT signing. |
| Ordinals app gets the payment address only. | Use the WBIP getAddresses path with purposes: ["payment", "ordinals"] to receive the Taproot ordinals address. |
| Signing request is rejected. | Treat user rejection as normal control flow and let the user retry from your dApp UI. |
| Wrong EVM chain. | Call wallet_switchEthereumChain before building chain-specific transactions. |