Skip to the content.

Bread Wallet by Miden — Privacy Policy

Last updated: 2026-10-19

Bread Wallet by Miden (“the App”) is a non-custodial cryptocurrency wallet for the Miden blockchain, published by Miden.

Who we are

The App is published by Miden Technologies (US) Corp., a Delaware corporation (file number 10598340), 850 New Burton Road, Suite 201, Dover, Delaware 19904-5786, United States (“Miden”, “we”). Miden is the controller of the personal data described in this policy. For anything about your data, write to privacy@miden.team or call +1 707 813 2275.

The Miden network itself is operated by the Miden Foundation, a foundation organised under the laws of the Republic of Panama (registration number 25068980), Via Ricardo J. Alfaro, Edificio PH The Century Tower, Oficina 317, Suite 742, Corregimiento Betania, Distrito Panamá, Provincia Panamá, Republic of Panama (the “Foundation”). The Foundation is a separate party from us: it operates the network’s sequencer, one of its validators and the USDCx contract, itself or through its contractor Gateway.fm (GATEWAY.FM AS). The Miden swap solver is operated by 0D Labs Limited, a Seychelles company (LEI 254900SN1Y7P2AYYLK22), also a separate party from us. Where this policy describes what the network, a validator or the solver does with your data, it describes those parties’ processing for transparency; we do not control it.

This policy covers two different kinds of data. The first is telemetry: crash reports and optional usage data the App sends to us. The sections from “Data we collect” to “What scrubbing can and cannot promise” describe it, and the short version is that it carries no identifier for you. The second is data that leaves the App to make it work: what the Miden network, your Guardian Operator, and the providers behind swaps, bridges, Earn and fiat on-ramp and off-ramp receive. The section “Data that leaves the App for reasons other than telemetry” describes that. The statements about telemetry are not statements about the second kind.

Data we collect

The App sends crash reports so we can fix what breaks. They are described under “Crash reports” below: the error and where it happened in the code, scrubbed of sensitive data before sending, with no identifier for you or your device. Everything else is collected only if you turn on Share usage data. If you never turn it on, the App sends no product events.

You are asked once, after you create or import a wallet, and you can say no. Whatever you choose then, you can change it at any time in Settings → General → Help improve Wallet. Turning it off stops sending usage data: the setting is checked before every single send, so nothing that was waiting gets sent later. Crash reports are sent whether this setting is on or off.

On Firefox, Firefox’s own switch also applies. Firefox asks whether an extension may collect technical and interaction data, and where it has said no, the App sends neither crash reports nor usage data. Other browsers and the mobile and desktop apps have no equivalent switch.

Usage data, below, is sent only while that setting is on. Crash reports are sent either way.

Usage data

When the setting is on, the App reports the start and the end of a small set of activities. Each report is a single message with no free-form text in it — there is no field an address, an amount, or an error message could occupy. A message contains only these:

What Values
Which activity One of: opening the app, unlocking, creating a wallet, importing a wallet, recovering a wallet, returning to the app, funding, sharing your receive address, sending, swapping, earning, connecting a dApp, approving a dApp request, changing your guardian, handling an incoming note, viewing activity
Which kind of work, for work the App did on your behalf One of: sending, receiving, swapping, earning, bridging, a guardian change, a dApp’s transaction, another transaction, proving, or one of three services being unreachable. A category of work, never a recipient, an amount, an asset, a dApp, or an endpoint. See “Work the App does on your behalf” below
Whether it started or ended started or ended, or settled for work the App did on your behalf
How it ended completed, cancelled, or errored
Broad error category, if it failed One of: network, rpc, proving, validation, storage, auth, timeout, unknown
How long it took A duration in milliseconds
How far you got For an activity, the name of the furthest screen it reached, from a fixed list — for example select_amount or review. The screen’s name only, never anything you typed on it. For work the App did on your behalf, the stage it reached instead — for example proving or submitting
App version For example, 1.15.21
Platform extension, ios, or android
Network mainnet, testnet, devnet, or localnet
A short random number for that one activity Explained under “Identifiers” below
A short random number for that run of the App Explained under “Identifiers” below

That is the complete list. There is no other field, and the error category is chosen from the eight names above — the underlying error message is read to pick a category and is never sent.

Work the App does on your behalf

The App also reports whether the work it does on your behalf actually succeeded. When you send, swap, claim, earn, bridge, or change your guardian, the App has to prove and submit a transaction after you have finished asking for it, and that part can fail for reasons that have nothing to do with you — a prover or a node being down or unreachable. Without this, a failure affecting everyone would be invisible to us, because by then the activity above has already been reported as finished.

Those reports use the same fields as the table above and add nothing that describes you or your money. Nothing about the transaction itself travels with them: not who you sent to, not how much, not which asset, not which dApp, not which endpoint.

They also carry no activity identifier, which means we cannot tell which of your activities a piece of work belonged to. That is deliberate rather than an oversight: linking the two would mean writing one of those random values onto the transaction record the App keeps on disk, and a value written to disk outlives the run it was made for — which is exactly the durable identifier this design refuses to hold.

Three more things travel with each message as part of the technical envelope our analytics provider defines: the time the message was sent, a flag saying whether the App was a development build or a released one, and a fixed name and version number for the piece of our own code that sent it (miden-wallet-aptabase@1.0.0 — the same string for everyone). None of them varies with who you are or what device you use.

We deliberately leave three of the provider’s fields empty. Aptabase’s format has slots for your operating system version, your language or locale, and your device model, and their own libraries fill all three in. We send none of them. Each one is a fingerprinting signal — a combination of them narrows a crowd down to a person — and none is needed to find out where the wallet breaks. Automated tests fail the build if any of the three ever appears in an outgoing message, which is what stops someone filling them in later because the field was there.

Crash reports

Handled transaction failures are also reported, including failures after submission and transactions whose network outcome remains unknown. These reports contain only fixed transaction, stage and execution-route categories, bundled software versions, the network the App was on, known public protocol error identities, the fixed failure reasons, refusal codes and claim origins listed below, and authentication-code compatibility, preparation, layout and commitment-match categories calculated locally. They never include the transaction request, an account or transaction identifier, addresses, amounts, signatures, salts, commitments or dApp URLs. Unknown assertion numbers and raw failure text are not sent. Historical rows imported from a backup do not generate new failure reports.

A failure report can also say why the transaction failed, using only these fixed values:

None of these carries the text of the refusal, the guardian’s response, or the address of the server that sent it.

A failed claim of an incoming transfer also says what queued the claim, using only these fixed values: auto_consume (the App claimed the transfer on its own), claim (you accepted one transfer), claim_all (you accepted several transfers at once), dapp (a connected site asked to claim it), bridge_reclaim (you took back the funds of a failed bridge transfer), swap_settlement (the App settled one of your swaps), rotation_funding (the App claimed it to activate the account’s everyday key), or arcade_gc_purchase (you bought Game Coins in Arcade). A claim queued before the App recorded this is sent as unrecorded; any unlisted value is sent as other. It never names the transfer, the site or the account.

A failed inbound bridge, a deposit from another network into Miden, also carries these fixed values:

The native observations are sent only for an instrumented Android or iOS Reown request failure on an inbound bridge. On iOS, only a failure attached directly to its request can report an origin: unconfigured, argument_validation, request_construction_error or request_call_error. These mean the local configuration or arguments were unavailable, the request could not be constructed, or the awaited request call threw; they do not prove what the other wallet saw. iOS reports unknown request age and unobserved publication, with no wallet response or background observation. When the connected wallet refuses to add or switch to the bridge’s network, the failure that follows carries the observations of that refused request. Native origin names the rejection site: argument validation, missing configuration, publishing, a wallet error response, SDK request expiry, session deletion or another site. Wallet response code is present only for a wallet error response. It identifies a user rejection, invalid parameters, internal error, lack of authorization, unsupported method, disconnected provider or chain, failed chain switch, invalid request, unavailable or rate-limited RPC, or transaction rejection. An absent or unlisted code is reported as other. Request age and publication observation are required for a native group: the age is a bucket of monotonic elapsed time, with unknown when no start was recorded, and publication is confirmed only if that request’s publication callback ran; unobserved does not prove that publication failed. Background observation says whether Bread went to the background while the request was pending; it is omitted when observation was unavailable or continuity broke. Pre-start failures have unknown age, unobserved publication and no background answer. Older builds, other platforms and errors without a valid native group omit the observations. Publication and backgrounding prove neither that a wallet prompt appeared nor that the user abandoned it. These five observations are searchable tags and extra diagnostic fields and preserve grouping; only the exact permissions EVM class intentionally starts a different report title. Numeric response codes, raw age, timestamps, identifiers, payloads, message text and endpoints are not sent.

When a native session precheck refuses the bridge network and the failure can hold that record, the report names the failed condition and the add-and-switch attempt with only the fixed values in that list. A plain value is rethrown unchanged, and the report names neither the condition nor the attempt. When the report names them, those words are part of the report title, so each pair starts its own crash-report issue. A chain id, chain name, topic, account, address, amount, URL, or the text of the error is not included.

A crash sends the error type, the error’s message, and the stack trace (the list of code locations the error passed through), plus the app version. Where one error was caused by another, each error in that chain is included. Text is scrubbed before it leaves your device: addresses, amounts, long hex values, credentials in URLs, and values under names like password or seed are replaced with [redacted]. A message that scrubs down to nothing useful is dropped rather than sent as a row of markers, and if anything in the report looks like a recovery phrase, the whole report is thrown away rather than sent.

Every crash report also names the network the App was on: mainnet, testnet, devnet, or localnet.

Every crash report also says how the error reached the reporter: reported by the App itself (generic, or react when a screen failed to show), or never caught and picked up by the App’s global error handlers (onerror or onunhandledrejection). It names the platform (extension, ios or android) and the build the App came from: which build it was (extension, mobile, desktop or background, the extension’s background part), the commit of the App’s source code it was built from, which is the same for everyone running that build, and whether that source had changes not in the commit (true or false). A crash in one of three of the App’s own operations names it: its messaging between its windows and its background (intercom_request), a dApp approval window reading its request (confirm_payload), or starting the built-in WebAssembly module (sdk_initialize). When a dApp approval window cannot read its request, the report says whether the window’s address had no approval id at all or an empty one (missing or empty).

An existing crash report can also include a storage owner (wallet_transactions, wallet_limits or wallet_database) and a storage operation (get, query, count, cursor, mutate, transaction or open). The first two owners identify the wallet’s own transaction and spending-limit tables using fixed labels, never a name copied from storage. wallet_database identifies a failure of the wallet database itself, such as opening, upgrading or a transaction involving more than one table, when no single table is known. Storage fields are omitted when no validated annotation is available. They include no database names, keys, primary keys, rows, record fields, raw error text, ids, addresses or amounts.

Every crash report also carries page visibility (visible, hidden or unknown), read once when the report is captured in the window or background context sending it. unknown means that context has no document, its visibility cannot be read, or the value is unrecognized. A visible page does not prove the native app resumed. Storage values are extra diagnostic fields and per-report tags; page visibility is an extra diagnostic field. These values do not change report titles or issue grouping, and they do not trigger new reports.

If the App’s built-in WebAssembly module fails while starting, the report also says which startup step failed (wasm_ready, client_create, mock_import or mock_create), when client creation failed whether it was the primary client or the realm reader (primary or realm_reader), and whether that client ran in a worker (enabled or disabled). When the failure is a WebAssembly trap, it adds whether a fresh one-page block of shared memory could still grow (grew, refused or unavailable) and how many of the App’s own pages were open, counting its hidden background page (1, 2-3, 4+ or unavailable). None of these is an identifier.

A crash in the App’s own messaging between its windows and its background also names the kind of request that failed, from the App’s fixed list of request names, and, in the browser extension, whether the window had reconnected to the background before it failed (true or false). When a dApp approval window’s request finds nothing in the background to answer it, the report also names the state the background held for that approval (open, answered, window_closed, expired, payload_failed or unknown), the shape of the id the request carried (string, array_with_string, array_without_string, missing or other), what the background still remembered of recent approvals (empty, holding or pruned, meaning it had dropped older ones), how long the background had been running (under_10s, 10_to_60s, 60_to_300s, over_300s or unknown) and how the approval window was last loaded (navigate, reload, back_forward, prerender or unknown). None of these includes the approval’s id, the dApp, or anything the request carried.

A crash that is the App’s connection to the WalletConnect relay failing is sent as a warning, at most once for each range of such failures in a window (the first, the second to fifth, and the sixth onward), so at most three times per window. Every such failure is counted, but none is sent while the device is offline: a range is sent with the first failure in it that happens while the device is online. Its message is WalletConnect’s fixed wording with only the relay’s host, none of the errors that caused it is sent, and it also carries these fixed values:

When the App shows you an error message, it also reports that it did: which message it was (activityHiddenNotesError, agglayerTokenUnsupported, amountExceedsTokenDecimals, amountMustBeLessThanBalance, arcadeGcPurchaseAboveInventory, arcadeGcPurchaseBelowMinimum, biometricFailed, biometricHardwareUnavailable, biometricLockedOut, biometricNotEnrolled, biometricNotEnrolledWork, biometricNotStrong, biometricPasscodeNotSet, biometricSecurityUpdate, biometricStrongNotEnrolled, biometricStrongNotEnrolledWork, biometricUnavailable, biometricUnavailableIOS, bridgeDepositBalanceCapExceeded, bridgeDepositBalanceCapUnpriced, bridgeDepositBalanceReadFailed, bridgeDepositBalanceTooLow, bridgeFeatureUnavailableBody, cameraPermissionDenied, cannotSendToSelf, confirmError, connectivityGuardianBody, connectivityNetworkBody, connectivityNodeBody, contactAlreadySaved, contactIsYourAccount, couldNotCheckUnlockMethod, couldNotDecodeTransaction, couldNotVerifyBySimulation, customGuardianUnreachable, dappLoadFailed, deadletteredNotesBody, deadletteredNotesBodyOne, devEndpointSaveFailed, earnCollateralUnavailable, earnFailedToOpenPosition, earnGaslessWithdrawalFailed, earnNoEvmAddress, earnPositionsLoadError, earnUnavailableOnNetwork, earnVaultLoadError, earnWithdrawAboveAvailable, earnWithdrawNotOwned, earnWithdrawPositive, earnWithdrawPrecision, earnWithdrawQuoteUnavailable, earnWithdrawRefresh, encryptedWalletFileExportFailedAccount, encryptedWalletFileExportFailedDesc, encryptedWalletFileJsonOnly, encryptedWalletFileMalformed, encryptedWalletFileReadFailed, encryptedWalletFileRestoreFailed, encryptedWalletFileSelectOne, encryptedWalletFileTooLarge, encryptedWalletFileUnsupportedVersion, encryptedWalletFileWrongNetwork, evmChainNotApproved, externalPageLoadFailed, failedToParseSigningPayload, faucetOverCap, faucetRateLimited, faucetRateLimitedWait, feeReserveBlocksSend, guardianAccountNotFound, guardianAuthenticationUnavailable, guardianEndpointUnchanged, guardianHistoryFailed, guardianHistoryPartial, guardianSwitchAlreadyInProgress, guardianUrlMismatch, guardianUrlNoOnChainGuardian, guardianUrlStaleRetry, guardianUrlUnreachable, hiddenTokensError, hiddenTokensUnreadable, historyDetailsLoadError, hotKeyHardwareErrorPromptBody, hotKeyRotationFailedGeneric, hotKeyRotationFundingClaimFailed, hotKeyRotationFundingClaimUnconfirmed, hotKeyRotationPromptBody, hotKeyRotationUnconfirmedBody, importHotKeyError, importHotKeyInvalid, importHotKeyNoAccount, importSeedPhraseError, importSeedPhraseTooLong, incorrectPasscode, incorrectPassword, insufficientFeeAsset, insufficientFeeAssetShort, invalidAddress, invalidAmount, invalidEthereumAddress, invalidMidenAccountId, invalidMidenAddress, invalidPayload, invalidQrImage, invalidRecoverySeed, invalidUrl, justValidPreGeneratedMnemonic, mainnetAccessCheckFailed, mainnetAccessCodeRejected, mainnetAccessPasteFailed, meetGuardianNoneReachable, midenAddressWrongNetwork, noCameraFound, noQrCodeFound, noteClaimFailedRetry, nothingToPaste, oneClickBalanceUnavailable, oneClickBroadcastTrackingPending, oneClickQuoteUnavailable, oneClickSubmitFailed, passcodesDoNotMatch, passwordsDoNotMatch, raw_account_export_failed, raw_arcade_gc_purchase_failed, raw_bridge_balance_failed, raw_bridge_claim_failed, raw_bridge_connect_failed, raw_bridge_deposit_failed, raw_bridge_deposit_submit_failed, raw_bridge_reclaim_failed, raw_choose_guardian_submit_failed, raw_confirm_approval_failed, raw_confirm_payload_failed, raw_contact_delete_failed, raw_contact_save_failed, raw_contact_scan_failed, raw_dapp_approval_failed, raw_dapp_permission_denied, raw_earn_deposit_submit_failed, raw_earn_withdraw_failed, raw_earn_withdraw_submit_failed, raw_encrypted_file_unlock_failed, raw_faucet_download_failed, raw_faucet_id_save_failed, raw_faucet_share_failed, raw_guardian_replace_hot_key_failed, raw_guardian_switch_failed, raw_guardian_url_save_failed, raw_history_cancel_failed, raw_history_details_load_failed, raw_history_retry_failed, raw_history_swap_cancel_failed, raw_home_faucet_failed, raw_hot_key_rotation_failed, raw_hot_key_rotation_funding_claim_failed, raw_import_hot_key_scan_failed, raw_import_recovery_lookup_failed, raw_onboarding_recovery_failed, raw_options_reload_failed, raw_pending_activity_retry_failed, raw_recovery_seed_submit_failed, raw_secret_reveal_failed, raw_seed_remove_failed, raw_seed_reveal_failed, raw_seed_verification_reveal_failed, raw_send_submit_failed, raw_startup_init_failed, raw_startup_root_missing, raw_swap_submit_failed, raw_transaction_failed, raw_transaction_retry_failed, raw_usdcx_execute_failed, recallTimeInPast, required, resetDidNotFinish, runtimeUnsupportedBody, seedBackupSaveFailed, seedRemovalFailed, smthWentWrong, spendingLimitInvalidAmount, spendingLimitLoadFailed, spendingLimitSaveFailed, submissionBlockedByIssuer, submissionInterrupted, submissionRejectedByGuardian, submissionRejectedByNetwork, submissionUnanswered, swapInvalidAmounts, swapPairNotListed, swapPriceMoved, swapPriceUnavailable, swapSameToken, swapSlippageRange, swapVerdictNoMarket, swapVerdictNoPrice, tokenActivityLoadError, tokenDecimalsInvalid, tokenMetadataSaveError, tokenNameInvalid, tokenSymbolInvalid, transactionErrorDescription, transactionFailedNotificationBody, transactionNotConfirmedNotificationBody, unknownTokenScale, unlockPasswordErrorDelay, updateNotificationActionFailed, usdcxBelowMinimumBurn, usdcxBurnFeeUnavailable, usdcxDepositFeeAboveBalance, usdcxDomainNotRegistered, usdcxFaucetUnavailable, usdcxForwardQuoteUnavailable, usdcxInvalidAmount, usdcxSendMoreThanPayoutFee, usdcxUnsupportedFaucet, verifyStepWrong, walletAccessUnavailableBody, walletSetupDidNotComplete) and which kind of display showed it (inline, field, passcode, notice, review, dialog, status_screen, prompt, startup, browser_alert, os_notification or native_toast). It never sends the text you saw or typed. These reports use the same crash-report permission, with at most one report a minute for one message on one kind of display and 30 reports in ten minutes in each running App window or worker.

The App also sends a short diagnostic history of each swap, so that a failed swap can be traced to the step it failed at. These records go to the same crash-reporting service on the same terms as crash reports: they are sent whether or not you share usage data, never where Firefox’s own switch says no, and nothing waiting is sent once crash reporting stops. Each record is one of two fixed events (operation_step or operation_end) and carries only these fixed values: the operation it is about (swap); for a step, the step it describes (service_prepare, request_build, request_persist, proposal_create or proposal_execute) and how that step ended (ok, failed, timeout, aborted or locked); for the end of a swap, how the operation ended (completed, failed, timeout, cancelled, interrupted or unconfirmed); and how long it took, as a range (under_1s, 1_to_5s, 5_to_30s, 30s_to_2m, 2_to_10m or over_10m). Each record also carries the time it was made, a severity level, the app version, a number that orders records made in the same instant, and a random number created in memory when crash reporting starts, never written to disk, which the crash reports sent in that run of the App also carry, so a failure can be read beside the records before it. A record never contains an error message, an address, an amount, an identifier of an account, a transaction or a note, or a web address. The App sends at most 10 records for one operation in any 10 seconds and 60 in any 10 minutes, and drops the rest.

Crash reports do not include the page you were on, your browser’s user-agent string, your language, your timezone, or a recording of your screen or clicks. Alongside the error, each report carries the time it happened, a severity level, its own random report id generated for that one report, and the name and version of the reporting library — the standard technical envelope, with nothing about you in it.

Please read “What scrubbing can and cannot promise” below, which is the honest limit of this.

What we never collect

Not when the setting is on, not when it is off, not ever:

And nothing collected is ever sold, or shared with data brokers or ad networks.

We also do not call this data anonymous. Sending anything over the internet means the receiving server sees the IP address the request came from, exactly as it does for any other network request — and an IP address indicates a rough region. Our processors are configured not to store it. A processor may still derive something coarse from it before discarding it, such as which country a request came from, and keep that alongside the message; we are not going to describe data as anonymous when a server saw where it came from.

Identifiers

The App holds no persistent identifier for you — no user id, no device id, no install id, no cookie, no advertising identifier. Nothing in the usage data or the crash reports says “this is the same person as last week”, or even “as yesterday”.

Two short random numbers are involved, and both live only in memory:

What that second number does and does not allow, stated plainly. Within one run of the App, the activities you performed can be seen as belonging together. Across runs — and across devices, and across people — nothing can. This is a deliberate trade: the earlier design used the per-activity number for both purposes, and the result was data in which every session held exactly one activity and lasted zero seconds, which could not answer the only question the feature exists to answer, namely where people get stuck.

One thing worth spelling out, because our provider’s naming suggests otherwise. Aptabase calls the field this number travels in a “session id”, and their own libraries store it on your device and reuse it on a four-hour timeout — which makes it survive restarts. Ours does not exist anywhere but memory. That is also the only option available to us: the App has no way to store such a value at all, and a test asserts it stays that way.

What that means for deleting your data

It means we cannot delete your data on request, and we would rather say so than promise otherwise.

There is no identifier in this data by which we could find your records. If you asked us to delete your usage data or your crash reports, we would have no way to tell yours apart from everyone else’s — the records simply do not say who they came from.

This is a deliberate trade-off, not a way of avoiding the request. The alternative is to attach a durable identifier to every message so that we could find your data later, which would mean building the exact tracking capability we chose not to have. We decided that not being able to identify you is worth more than being able to service a deletion request, and this paragraph is the cost of that decision stated plainly.

Two things do follow from it, in your favour:

Everything the App stores about your wallet — your keys, accounts, balances, and history — is on your device, and you can delete it yourself with “Delete wallet” in Settings, or by uninstalling the App. That is unaffected by any of the above.

What scrubbing can and cannot promise

The scrubbing described under “Crash reports” is thorough but it is a filter, not a guarantee, and you should know its limits: crash reports are sent whether or not you share usage data.

It works by recognising what secrets look like. That is reliable for the things that have a recognisable shape: a Miden address, a long hex value, a run of digits, a credential in a URL, or any value stored under a name like password. Three kinds of thing can get past it:

None of them is a known leak. No part of the App puts a recovery phrase or a password into an error, and automated tests run on every change assert that. They are the residual risk if some future code did, and we would rather name them than imply the filter is perfect.

Who processes this data

Crash reports are sent either way; usage data only if the setting is on:

Data Processor Where
Usage data Aptabase European Union (Germany)
Crash reports Sentry European Union
Feature eligibility check Cloudflare and MaxMind Connection address for the lookup; short-lived service records as described below

Usage data goes straight from the App to Aptabase’s European endpoint, one message per request, with no server of ours in between and nothing batched up in the meantime. We use neither company’s own tracking library: the App builds each message itself, out of the fixed list of fields above, and sends it directly.

Aptabase is configured to delete data after 90 days, and Sentry deletes it after the periods under “How long we keep data”. Both are configured to store no IP addresses and to forward nothing onward. Neither is permitted to use the data for its own purposes, and no third party receives it for advertising or resale. The feature eligibility service runs on Cloudflare and uses MaxMind to check the connection’s IP address. The App receives only whether wallet access, Earn and Swap are allowed, plus the network and policy version. The service stores no raw IP address or geographic answer; it stores daily rotating keyed hashes of connection addresses, request counters and feature decisions. Feature decisions expire after five minutes, and expired caller records are removed in batches.

How the App uses your device

For transparency about how the App functions on your device (this is not data we receive or have access to):

Biometric authentication (Face ID, Touch ID, Fingerprint)

The App offers optional biometric unlock using your device’s built-in authentication — Face ID or Touch ID on iOS, fingerprint or face authentication on Android.

The App does not collect, access, receive, store, transmit, or share any face data, fingerprint data, or other biometric data.

Biometric matching is performed entirely by your device’s operating system inside secure hardware (Apple’s Secure Enclave / Android’s hardware-backed keystore). Your biometric templates are created, stored, and managed solely by iOS/Android and are never made available to the App. When you unlock with biometrics, the App receives only a yes/no result indicating whether authentication succeeded — it never receives any biometric data itself.

Data that leaves the App for reasons other than telemetry

Everything in this section is data the App must send for a feature to work. None of it is telemetry, none of it is optional once you use the feature, and most of it is received by someone other than us.

The Miden network and its operator

To transact, the App sends transaction proofs and related data to the Miden network through its RPC endpoint and the note transport service. Miden is designed to support private transaction execution. During the network’s current operating period, however, the transaction data you submit, including data associated with private accounts and private notes, is stored by the network’s validators in encrypted form, and any two validators acting together can decrypt it. There are three validators, operated by the Foundation, Gateway.fm and LambdaClass. We operate no validator and cannot decrypt your data. The Foundation cannot decrypt it on its own either: decryption requires two validators to cooperate, so at least one party independent of the Foundation must agree, and it takes place only for network-integrity, compliance and legal purposes, including in response to a lawful request. This is a property of the network in its current phase, not of the App; it applies whatever wallet you use.

We retain compliance-related records, including block, unblock and legal-process records, for at least five years, and may disclose information where required by law or lawful process. Data written to the blockchain itself is permanent and cannot be deleted by anyone, including us.

Your Guardian Operator

If you enable a Guardian, your Guardian Operator receives and stores an encrypted copy of your account state, and nothing else about you. It does not receive your Recovery Phrase, your Everyday Key, or any identity or contact data. At launch the Guardian Operators are LambdaClass (FOLTREK SA), KODA (Korea Digital Asset Co., Ltd.), Para (Capsule Labs Inc.) and Gateway.fm (GATEWAY.FM AS), all separate parties from us. When you create a wallet the App measures each operator’s response time and assigns you the fastest one; the App shows you which operator you have, and you can change it at any time. Each operator handles this data under its own privacy notice.

Fiat on-ramp and off-ramp (Transak)

Buying crypto with fiat and selling crypto for fiat in the App is provided by Transak through a widget. When you use it you open an account with Transak and deal with Transak directly. Transak verifies your identity before you can transact and collects identity documents, bank details and other personal data for that purpose, under Transak’s privacy policy. We do not receive your identity documents or bank details. Transak pays crypto to, and receives crypto from, the Ethereum address the App derives for you (see “How the App uses your device”), so Transak can link your verified identity to that address, and transfers between that address and your Miden accounts are visible on the networks involved.

Swaps and bridges

A Miden swap is filled by the solver operated by 0D Labs Limited, a cross-chain swap by a third-party solver, and a bridge transfer is processed by a third-party bridge operator and the other network. Each of them sees the transaction it processes: the assets, the amounts and the addresses involved. Before that, the App requests quotes: a swap quote sends the two tokens and their amounts to the swap-quote service, which for a Miden swap is run by 0D Labs Limited, never your address, and tracking a bridge transfer sends its destination address to the bridge’s status service (both named under “Network traffic”).

Earn

Earn is provided by Epoch (SYNC TECH LLC, St. Vincent and the Grenadines). When you use it, Epoch receives your Miden account, the Ethereum address the App derives for you, and the transactions you sign for the lending market on the Ethereum network that holds your position. The position is recorded at your Ethereum address in a third-party lending market, which sees that address and the amounts. The note that funds a deposit is public on Miden, so the deposit amount and your Miden account are visible on the network. Epoch handles this data under its own privacy notice.

Geographic restrictions

The App asks for no location permission and reads no location sensor. The App and the Miden network are, however, not available from comprehensively sanctioned jurisdictions, and some features are not offered in every place. At startup, in the background, the App asks a service running on Cloudflare whether wallet access, Earn and Swap are allowed for its connection. It keeps those decisions in memory for the page session and does not repeat the check while the wallet is open. Where a global or enabled feature restriction needs a place check, the service sends the connection’s IP address to MaxMind and reads the country and region. The App receives no location and stores no IP address. The service retains only the short-lived records described above. The same address is also used for the ordinary operation of the connection, and the network’s sequencer endpoint checks the country of a request.

Wallet access, including withdrawals, is unavailable in Cuba, Iran, North Korea, Russia, Belarus, Crimea, Sevastopol and the whole Donetsk, Luhansk, Kherson and Zaporizhzhia oblasts. New Earn deposits are additionally unavailable in New York and the UK. Without a successful startup eligibility decision, the wallet opens with Earn and Swap unavailable.

Network traffic

The App connects to the public Miden blockchain RPC endpoint (rpc.testnet.miden.io) and the Miden note transport service (transport.miden.io) to send and receive on-chain transactions. These requests contain only data needed to interact with the blockchain (transaction payloads, public account state) - never your private keys or recovery phrase.

It also downloads public data. Miden’s verified-token list and update notices come from GitHub (raw.githubusercontent.com) and carry nothing about you. The logos of listed tokens come from the same GitHub list, so GitHub sees which listed tokens’ logos the App draws (the listed tokens you hold or open), never your account, address or balance. Explore’s catalog of apps and their icons come from GitHub too, so GitHub sees which network’s catalog and which of its icons the App requests, never your account, address or balance. Token prices and charts come from Binance (data-api.binance.vision) and, for tokens without a supported Binance market, Kraken (api.kraken.com). These providers see the requested markets from the verified token list or bridge configuration and the charts the App opens, never your address, keys or balances.

Three features send the service behind them only what that feature needs. Requesting test tokens sends your account address and the amount to the Miden faucet (faucet-api.testnet.miden.io). Tracking a bridge transfer sends its destination address to the bridge’s status service (miden-testnet-bridge.dev.eu-north-3.gateway.fm). A swap quote sends the two tokens and their amounts to the swap-quote service (35-175-40-181.sslip.io), never your address.

The App reaches Sentry to send crash reports. If Share usage data is on, it also reaches Aptabase; if it is off, it does not contact Aptabase at all.

Apart from those token logos and Explore icons, and the site icons the dApp browser on mobile fetches from Google’s favicon service, the App loads no fonts, scripts, or images from third-party servers. Everything else it needs to draw itself ships inside the App.

Permissions

The App requests the following Android permissions:

Children

The App is not directed at anyone under 18, and our Terms of Service require users to be at least 18. We do not knowingly collect any information from children.

Purpose Data Legal basis (GDPR Article 6)
Running the App and submitting your transactions Transaction data sent to the network, Guardian data Performance of our contract with you (the Terms of Service)
Fixing crashes Crash reports Our legitimate interest in keeping the App working
Understanding where people get stuck Usage data Your consent, which you give by turning on “Share usage data” and can withdraw at any time in the same place
Sanctions compliance, geographic restrictions, record-keeping, responding to lawful requests IP country and region check, block and unblock records, legal-process records Compliance with legal obligations, and our legitimate interest in operating a lawful network
Restricting new Earn deposits by region IP country or region check as part of the wallet eligibility request Our legitimate interest in not offering new Earn deposits where we have chosen not to, and compliance with the laws of those places

How long we keep data

Where data goes

Crash reports and usage data are processed in the European Union (see “Who processes this data”). Your Guardian Operator and Transak may be located outside the European Union and the United Kingdom.

Your rights

If you are in the European Union, the European Economic Area or the United Kingdom, you have the right to access the personal data we hold about you, to have it corrected or erased, to restrict or object to its processing, to receive it in a portable format, and to withdraw consent where processing rests on consent. You also have the right to complain to a data protection supervisory authority, in particular in the country where you live.

Two limits follow from how the App is built, and we would rather state them than imply otherwise:

To exercise a right, write to privacy@miden.team.

Changes

Material changes to this policy will be reflected on this page with an updated “Last updated” date.

Contact

Open an issue at github.com/0xMiden/wallet/issues or email privacy@miden.team.

Closed terminal transaction observations

These independent observations add no raw error text, identifiers, row contents, timestamps, request bytes or account/authentication data and are not diagnoses or funds outcomes. They use existing producer boundaries without extra SDK, database or network work.