diff --git a/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx b/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx index 0a9918c478..42f61abcfe 100644 --- a/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx +++ b/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx @@ -1,6 +1,6 @@ --- -title: "Pooled accounts: muxed accounts and memos" -description: Use muxed accounts to differentiate between individual accounts in a pooled account. +title: "Muxed accounts and pooled accounts" +description: Use muxed addresses to differentiate between individual accounts in a pooled account. sidebar_position: 60 --- @@ -8,85 +8,542 @@ import { CodeExample } from "@site/src/components/CodeExample"; When building an application or service on Stellar, one of the first things you have to decide is how to handle user accounts. -You can create a Stellar account for each user, but most custodial services, including cryptocurrency exchanges, choose to use a single pooled Stellar account to handle transactions on behalf of their users. In these cases, the muxed account feature can map transactions to individual accounts via an internal customer database. +You can create a Stellar account for each user, but most custodial services, including cryptocurrency exchanges, choose to use a single pooled Stellar account to handle transactions on behalf of their users. In these cases, muxing lets you map transactions to individual users via an internal customer database, without creating a separate on-ledger account for each user. + +This page explains how pooled accounts work, how muxed accounts and muxed addresses relate to each other, and how to migrate from memo-based routing—especially as you adopt smart contract token flows ([SEP-0041: Soroban Token Interface](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0041.md)). + +## Terminology + +There are a few closely related concepts that are easy to mix up. + +- **Muxed account** – a virtual sub-account defined by: + - a base Stellar account (`G...` address), and + - a **64-bit muxed ID** (unsigned integer). + + The muxed account itself does not exist as a separate ledger entry; balances and authorization live on the base `G...` account. + +- **Muxed ID** – a 64-bit integer that distinguishes one muxed account from another under the same `G...` account. + It appears: + - inside `M...` **muxed addresses** (for classic payments), and + - in **SEP-41 token events** (e.g., as `to_muxed_id` in transfer events). + +- **Muxed address** – an `M...` strkey that encodes: + - a base `G...` account, and + - a muxed ID (64-bit integer) + + It’s used as a destination/source in certain classic payment operations. You can always decode an `M...` address to recover the underlying `G...` and the muxed ID. + +- **Memo ID** – a 64-bit memo (`MEMO_ID`) historically used by exchanges as a per-user identifier for routing transactions. + Conceptually, for many custodial systems, **memo ID and muxed ID are the same kind of “customer key,”** just carried in different places. :::note -We used memos in the past for this purpose, however, using muxed accounts is better in the long term. At this time, there isn't support for muxed accounts by all wallets, exchanges, and anchors, so you may want to support both memos and muxed accounts, at least for a while. +Historically, many services used memos to distinguish users—for example: “send to this `G...` address with this memo ID.” In the long term, muxed accounts are preferable: they reduce user error, make UX more consistent, and integrate cleanly with SDKs and smart contracts. However, not all wallets, exchanges, and anchors support muxed addresses yet, so you may want to support both memos and muxed addresses during a transition period. + +For smart contract flows, memos are not a viable routing mechanism: contracts cannot read the transaction memo field. If you need per-user routing in contract-based token flows, use muxed accounts (for example, a muxed ID in the token transfer) or explicit contract parameters instead of memos. ::: ## Pooled accounts -A pooled account allows a single Stellar account ID to be shared across many users. Generally, services that use pooled accounts track their customers in a separate, internal database and use the muxed accounts feature to map an incoming and outgoing payment to the corresponding internal customer. +A **pooled account** allows a single Stellar account ID to be shared across many users. Services that use pooled accounts track customers in a separate, internal database and use some combination of: + +- memo IDs, +- muxed IDs, and/or +- muxed addresses (`M...`) + +to map incoming and outgoing payments to the corresponding internal customer. + +The benefits of a pooled account are: + +- Lower costs – no [base reserve](../../../learn/fundamentals/lumens.mdx#base-reserves) is required per end user. +- Simpler key management – you only manage one account keypair (or a small set of custodian keys). + +The trade-off is that you assume responsibility for: + +- Tracking all individual customer balances. +- Handling errors, atomicity, and refunds in your own systems. +- Detecting and preventing misuse or fraud at the application layer. + +## Muxed accounts and muxed addresses + +Muxed (“multiplexed”) addresses are embedded into the protocol for convenience and standardization. They distinguish individual logical accounts that all exist under a single, traditional Stellar account. + +At the protocol level: + +- A **muxed account** = `G...` base account + 64-bit muxed ID. +- A **muxed address** = the `M...` strkey encoding of that pair for use in classic operations. + +Key properties: + +- **Virtual only**: muxed accounts don’t exist as separate ledger entries; only the underlying `G...` account does. +- **Specs**: + - Muxed accounts are defined in [CAP-0027: First-class multiplexed accounts](https://stellar.org/protocol/cap-27). + - Their string representation (`M...`) is defined in [SEP-23: Strkeys](https://stellar.org/protocol/sep-23). + +It is safe for all wallets and services to implement sending to muxed addresses where supported. If you wish to receive deposits to muxed addresses, remember that not every wallet or exchange supports them yet, so you may need to support both memos and `M...` addresses during migration. + +### Why use muxed accounts and muxed addresses + +Most exchanges historically used the **pooled account + memo** model: all users deposit and withdraw from a single `G...` address, and the service tracks individual balances off-chain. A memo (often a numeric ID) tells the exchange which internal user to credit. + +This model works, but has well-known problems: + +- Users forget to include the memo. +- Users reuse another user’s memo or type it incorrectly. +- Support teams must manually reconcile “missing memo” deposits. + +Muxed addresses solve this for classic payments by embedding the ID directly into the address: + +- Each user gets a unique `M...` address (muxed address) that still resolves to the same pooled `G...` address. +- Payments to a user’s `M...` address deterministically map to a single internal customer row (via the muxed ID). +- No memo is required, so deposits and withdrawals become memo-less and less error-prone. + +Benefits for exchanges and custodians: + +- Greatly reduces user error, missed memos, and support tickets. +- Provides a standardized interface for wallets, custodians, and smart contract flows. +- Cleanly extends to smart contract token transfers as multiplexing becomes standard in contracts (via muxed IDs and `MuxedAddress`). + +Importantly, you can adopt muxed accounts in your backend and contracts (using muxed IDs) without immediately changing your UI to show `M...` addresses. + +## Migrating from memos to muxed accounts + +Memos are still widely supported and will continue to exist at the protocol level for classic transactions. However, exchanges are strongly encouraged to migrate toward muxed accounts, either by: + +- using muxed IDs in contract-based token flows, +- exposing `M...` addresses in your UI for classic payments, or +- both — + +especially for new integrations and for smart contract-based token flows where multiplexing is first-class. + +:::note + +Smart contracts cannot access transaction memos, so any contract-based integration that currently relies on memos for routing will need to migrate to muxed accounts (for example, muxed IDs in token calls or events) or other contract arguments instead. + +::: + +### Two adoption paths for exchanges + +For exchanges and custodial platforms, there are two main adoption paths. + +#### 1. Backend-only muxing (no UI changes) + +- Keep your **deposit UI** exactly as it is today: + - show a pooled `G...` deposit address, and + - a **memo ID** that represents the customer (e.g., `memo_id = 12345`). + +- For **SEP-41 token transfers** (Stellar Asset Contract and other SEP-41-compliant tokens): + - Wallets call `transfer(from, to, amount)` where `to` is a `MuxedAddress`. + - The token emits transfer events that include a **muxed ID** (for example, `to_muxed_id: Option`). + +- On your backend: + - Index the **muxed ID from token events** (e.g., `to_muxed_id`). + - Treat that muxed ID as the new **per-user key**, in the same way you previously used the memo ID. + +In practice, this means: + +> For contract token deposits, **muxed ID replaces memo ID** as the customer identifier, but your UI continues to show the familiar `G...` + memo deposit instructions. + +You are using **muxed accounts** (concept + IDs) without exposing `M...` addresses to end users. + +#### 2. Full M-address UX (classic payments) + +In the more advanced migration, you also update your UI to hand out `M...` addresses directly for classic payments: + +1. **Accept both memos and `M...` addresses for deposits** + - Allow incoming deposits to: + - your existing `G...` deposit address with a memo, or + - a user-specific `M...` address. + - Map both to the same internal customer record. + +2. **Support withdrawals to `G...` and `M...`** + - When a user withdraws, allow: + - a raw `G...` address (optionally with a memo for legacy recipients), or + - a muxed `M...` address where no memo is needed. + - Many wallets and platforms display or require `M...` addresses for custodial flows. + +3. **Update validation code** + - If you currently validate addresses as “must start with `G`”, update that logic to support both `G` and `M` strkeys. + - All actively maintained Stellar SDKs (JavaScript, Go, Python, Java, Elixir, etc.) support muxed addresses out of the box, including utilities to decode/encode between `G` and `M` forms. + +4. **Phase down memo usage** + - Continue supporting memos only for external platforms that have not yet implemented muxed addresses. + - Encourage power users and institutional clients to switch to `M...` addresses to reduce support friction. + +## Payments workflow for exchanges + +When combining pooled accounts with muxed accounts, the typical exchange workflow depends on whether you are dealing with **classic payments** or **contract tokens.** + +### Classic payments (XLM / classic assets) + +#### Receiving deposits (classic) + +- Generate a unique muxed account per user (or per sub-account). +- Represent it as a muxed address (`M...`) where supported. +- Display this `M...` address (or `G...` + memo during migration) to the user in your UI. +- When a deposit arrives: + - The on-chain balance increases for the pooled `G...` account. + - Your off-chain system decodes the muxed ID (or uses the memo ID) and credits the correct customer balance. + +No memo is required when users deposit directly to an `M...` address, and you can use the same pooled account for many customers and assets. + +#### Sending withdrawals (classic) + +- Allow users to withdraw to either: + - a regular `G...` address (optionally with a memo if the recipient is using the legacy model), or + - a muxed `M...` address. +- If the destination is muxed, your system: + - parses the muxed address, + - uses the underlying `G...` account as the destination in the transaction, + - and records the muxed ID in your internal ledger or events. + +#### Tracking balances and activity + +- Always load and display on-chain balances using the base `G...` account. +- When listing transactions internally, map: + - any source or destination `M...` address back to the associated internal customer record, and/or + - log the muxed ID in your own events or analytics pipeline. -The benefits of using a pooled account are lower costs – no base reserves are needed for each account – and lower key complexity – you only need to manage one account keypair. However, with a single pooled account, it is now your responsibility to manage all individual customer balances and payments. You can no longer rely on the Stellar ledger to accumulate value, handle errors and atomicity, or manage transactions on an account-by-account basis. +#### Common classic patterns -## Muxed accounts +- **Standard payment (G → G)**: Deposit from an external wallet to your exchange’s pooled account with a memo (legacy pattern). +- **Muxed-to-unmuxed (M → G)**: Withdrawal from a specific exchange customer to an external wallet, without any memo. +- **Muxed-to-muxed (M → M)**: Internal transfer between two exchange customers that share the same pooled account. On-chain, this looks like the account sending assets to itself; only fees change. You may want to recognize and aggregate such transfers to avoid unnecessary on-chain churn. -Muxed accounts are embedded into the protocol for convenience and standardization. They distinguish individual accounts that all exist under a single, traditional Stellar account. They combine the familiar `GABC…` address with a 64-bit integer ID. +#### Common classic patterns -Muxed accounts do not exist on the ledger, but their shared underlying `GABC…` account does. +- **Standard payment (G → G)**: Deposit from an external wallet to your exchange’s pooled account with a memo (legacy pattern). +- **Muxed-to-unmuxed (M → G)**: Withdrawal from a specific exchange customer to an external wallet, without any memo. +- **Muxed-to-muxed (M → M)**: Internal transfer between two exchange customers that share the same pooled account. On-chain, this looks like the account sending assets to itself; only fees change. You may want to recognize and aggregate such transfers to avoid unnecessary on-chain churn. -Muxed accounts are defined in [CAP-0027](https://stellar.org/protocol/cap-27), introduced in Protocol 13, and their string representation is described in [SEP-0023](https://stellar.org/protocol/sep-23). +### Contract token deposits with existing `G...` + memo UIs -It is safe for all wallets to implement sending to muxed accounts. +For **SEP-41 token deposits**, the flow changes slightly, because contracts cannot see memos. A common pattern is: -If you wish to receive deposits to muxed accounts please keep in mind that they are not yet supported by all wallets and exchanges. +1. **Exchange deposit instructions stay the same** + - The exchange still shows: + - a pooled `G...` deposit address, and + - a **memo ID** (e.g., `12345`) that identifies the internal customer. + +2. **User enters the instructions into a smart-contract-aware wallet** + - The user copies the `G...` address and memo ID into their wallet. + - The wallet knows that: + - the user is sending a **contract token transfer** (e.g., SEP-41 / SAC), and + - memos are not usable for contract routing. + +3. **Wallet generates a muxed address under the hood** + - Instead of attaching the memo, the wallet: + - constructs a **muxed address** `M...` from: + - the pooled `G...` address, and + - the memo ID (treated as the muxed ID), and + - passes that `M...` destination as a `MuxedAddress` in the token’s `transfer` call. + +4. **Exchange indexes by muxed ID instead of memo** + - On-chain: + - The token contract sees a `MuxedAddress` destination. + - Transfer events include the **base `G...` address** and a **muxed ID** (for example, `to_muxed_id`). + - The exchange’s backend: + - reads the transfer events, + - uses the **muxed ID** as the per-user identifier (just like the old memo ID), + - credits the corresponding internal customer balance. + +From the exchange’s perspective, the **main difference** is: + +> For contract token deposits, you look at the **muxed ID in events** rather than the memo field to identify the sending customer. + +This is exactly the “change nothing in the UI, treat muxed ID as the new memo ID” path. + +## Muxed addresses in smart contracts + +Stellar extends multiplexing into the smart contract world so that token transfers and contract calls can carry the same “virtual account” semantics that exchanges use today. + +Key points: + +- The Rust `soroban-sdk` exposes a `MuxedAddress` type: + - It represents either a regular `Address` or a multiplexed address (`Address + u64 id`). + - The `address()` method returns the underlying `Address` used for authorization and storage. + - The `id()` method returns the optional muxed ID, which is typically used only in events so indexers and off-chain systems can distinguish virtual sub-accounts. + +- `MuxedAddress` is interface-compatible with `Address`: + - If a contract accepts `MuxedAddress`, callers can still pass a plain `Address` without breaking. + - This lets contracts upgrade from `Address` to `MuxedAddress` without disrupting existing clients. + +- Only regular Stellar accounts can be multiplexed; multiplexed contract addresses do not exist. + +- Client SDKs (such as the JavaScript `Address` class) understand muxed forms: + - `new Address("M...")` can represent a muxed address as a contract argument. + - Under the hood, SDKs convert `M...` addresses into appropriate XDR types (`ScAddress`, etc.) when invoking contracts. + +**SEP-41-compliant token contracts** use `MuxedAddress` for the `to` parameter of `transfer`, and emit transfer events that include an optional muxed ID (e.g., `to_muxed_id`). This is what enables exchanges to treat the muxed ID as a per-user routing key for contract token deposits. + +Typical smart contract use cases: + +| Contract Function | Muxed Support | SDK Usage Example | +| ----------------- | ------------- | -------------------------------------- | +| Payment/Transfer | Yes | Pass `M...` address as destination | +| Path Payment | Yes | Use `M...` address for route endpoints | +| Account Merge | Yes | Merge to `M...` address for refunds | +| Asset Clawback | Yes | Set `M...` address as clawback target | + +For most contracts you can continue to use `Address` everywhere. Reach for `MuxedAddress` only when you specifically need to model virtual users under a pooled account (for example, exchange token deposits and withdrawals). + +For exchanges that already route deposits using memo IDs, the easiest way to adopt contract-level muxing is to keep the UI unchanged and simply index SEP-41 token transfer events by the **muxed ID**. You can store that muxed ID in the same place you previously stored memo IDs, gaining muxed account semantics for contract token flows without introducing `M...` addresses in the frontend. + +Read more about muxed accounts as they relate to smart contracts in GitHub: [CAP-0067: Unified Asset Events](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0067.md#multiplexing-support). ### Address format -Muxed accounts have their own address format that starts with an M prefix. For example, from a traditional Stellar account address: `GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ`, we can create new muxed accounts with different IDs. The IDs are embedded into the address itself- when you parse the muxed account addresses, you get the G address from above plus another number. +Muxed addresses have their own address format that starts with an `M` prefix. For example, from a traditional Stellar account address: + +`GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ` + +you can derive multiple muxed addresses with different IDs: -- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ` has the ID 0, while -- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4` has the ID 420. +- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ` has ID 0 +- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4` has ID 420 -Both of these addresses will act on the underlying `GA7Q…` address when used with one of the supported operations. +Some useful properties: + +- M-addresses are longer than G-addresses (69 characters vs. 56). +- You can always parse a muxed address to recover: + - the underlying `G...` address, and + - the 64-bit integer ID. +- M-addresses are a client-side representation: + - All operations ultimately act on the underlying `G...` account. + - Authorization and signing always use the base account; muxed addresses don’t have their own secret keys. + +Both example M-addresses above act on the same underlying `GA7Q…` account when used with supported operations. ### Supported operations -Not all operations can be used with muxed accounts. Here is what you can use them for: +Not all operations accept muxed addresses, but they can be used for: + +- The source account of any operation or transaction. +- The fee source of a fee-bump transaction. +- The destination of the three payment operations: + - `Payment` + - `PathPaymentStrictSend` + - `PathPaymentStrictReceive` +- The destination of an `AccountMerge`. +- The target (`from` field) of a `Clawback` operation. + +In practice, for exchanges and custodial services this covers: -- The source account of any operation or transaction; -- The fee source of a fee-bump transaction; -- The destination of all three types of payments: - - `Payment`, - - `PathPaymentStrictSend`, and - - `PathPaymentStrictReceive`; -- The destination of an AccountMerge; and -- The target of a `Clawback` operation (i.e. the from field). +- User deposits into a pooled account (M → G). +- User withdrawals from the pooled account (G or M → external G or M). +- Internal transfers between customers on the same pooled account (M → M). +- Clawing back assets from a muxed customer balance (where the asset is clawback-enabled). +- Contract token transfers where the exchange indexes deposits by muxed ID instead of memo ID. -We will demonstrate some of these in the examples section. +There is no validation on the ID at the protocol level. As far as the Stellar network is concerned, all supported operations behave exactly as if you had not used a muxed account; the ID is for your off-chain bookkeeping. + +### SDK integrations + +All official SDKs expose helpers for creating, parsing, and using muxed addresses. Here are a couple of minimal examples. + +#### JavaScript SDK: + +```js +import * as sdk from "@stellar/stellar-sdk"; + +const server = new sdk.rpc.Server("https://soroban-testnet.stellar.org"); +const custodianGAddress = "GA..."; // pooled account -There’s no validation on IDs and as far as the Stellar network is concerned, all supported operations operate exactly as if you did not use a muxed account. +// Load the underlying account once so we have its sequence number +const custodianAccount = await server.getAccount(custodianGAddress); + +// Create a muxed account for customer ID "12345" +const customerId = "12345"; // stringified uint64 +const muxed = new sdk.MuxedAccount(custodianAccount, customerId); + +console.log(muxed.accountId()); // prints an M-address like "MA7QY..." +``` + +Parsing an existing muxed address: + +```js +const mAddress = muxed.accountId(); + +// You need the base account's current sequence number to parse it +const sequenceNum = custodianAccount.sequenceNumber(); +const parsed = sdk.MuxedAccount.fromAddress(mAddress, sequenceNum); + +console.log(parsed.baseAccount().accountId()); // underlying G-address +console.log(parsed.id()); // "12345" +``` + +The same `MuxedAccount` object can be passed anywhere an account source or destination is expected when building transactions with `TransactionBuilder`. + +#### Elixir SDK: + +```elixir +# pooled G-address +g_address = "GA..." + +# create a muxed account for internal ID 12345 +m_address = StellarSdk.Account.create_muxed(g_address, 12345) + +# build an Account struct from an M- or G-address +account = StellarSdk.Account.new(m_address) +``` + +The Elixir SDK takes care of encoding/decoding the ID portion as you build operations and transactions. ## Examples -The following examples demonstrate how muxed accounts work in practice, showing the three main payment scenarios: standard account-to-account, muxed-to-unmuxed, and muxed-to-muxed payments. +The following examples demonstrate how muxed addresses work in practice, showing three common payment scenarios: -**_Note:_** _The complete code implementation for all examples follows after this section. You can run individual examples using their respective functions or execute all three sequentially using the main function._ +1. A standard account-to-account payment (G → G) +2. A muxed-to-unmuxed payment (M → G) +3. A muxed-to-muxed payment between two customers of the same pooled account (M → M) -Muxed accounts (M...) are a client-side abstraction that don't actually exist on the Stellar ledger — only the underlying G account lives on-chain. This means operations like loading account data must always use the base G account, while payment operations can still provide either M or G address. The code diverges only slightly to handle this distinction between the logical muxed account and the actual account. +:::note + +The examples here are described conceptually; the actual JavaScript implementation on the docs site uses Stellar RPC and`@stellar/stellar-sdk` to construct and submit real transactions. You can keep the existing code sample and simply update the surrounding explanation with the concepts from this page. -**Transaction Fees:** All transactions incur a fee (typically 100 stroops or 0.0000100 XLM). Since the custodian account is the source of all transactions in these examples, fees are always debited from the custodian's balance. You'll see this reflected in the before/after balance comparisons, where the custodian account decreases by both the payment amount and the transaction fee. +::: -### Example 1 - Basic Payment (G → G) +### Example 1 – Basic payment (G → G) -This example establishes baseline behavior with a standard payment between two regular Stellar accounts. Both accounts exist directly on the ledger, demonstrating traditional Stellar payment flow without muxed account abstractions. The custodian account balance decreases by 10 XLM plus transaction fees, while the outsider account increases by exactly 10 XLM. +This example establishes baseline behavior with a standard payment between two regular Stellar accounts. Both accounts exist directly on the ledger, demonstrating traditional Stellar payment flow without muxed account abstractions. The custodian account balance decreases by the payment amount plus transaction fees; the recipient increases by the payment amount. -### Example 2 - Muxed to Unmuxed Payment (M → G) +### Example 2 – Muxed to unmuxed payment (M → G) -This demonstrates a payment from a muxed account (representing a custodial customer) to a regular Stellar account. The underlying custodian account executes the payment, but the transaction includes the muxed address to identify which customer initiated it. +This example demonstrates a payment from a muxed address (representing a custodial customer) to a regular Stellar account: -The transaction is signed with custodian keys since muxed accounts have no secret keys. The custodian account balance decreases by 10 XLM plus fees. +- The underlying pooled custodian account is the actual source on chain. +- The transaction includes the muxed source so your off-chain system knows which user initiated it. +- The transaction is signed with the custodian keys, since muxed addresses have no secret keys. +- The custodian balance decreases by the payment amount plus fees. -### Example 3 - Muxed to Muxed Payment (M → M) +### Example 3 – Muxed to muxed payment (M → M) -When two muxed accounts share the same underlying account, payments between them are essentially the account sending to itself. The payment amount (+10 XLM and -10 XLM) cancels out, but transaction fees are still charged since the operation is recorded on the ledger. +When two muxed addresses share the same underlying pooled account: -The account balance decreases only by the transaction fee. You may want to detect these transactions in your application to avoid unnecessary fees. Note that payments between muxed accounts with different underlying accounts would behave like normal G-to-G payments. +- On-chain, the account is effectively sending assets to itself. +- The net asset balance doesn’t change (the debit and credit cancel out). +- You still pay transaction fees, since the payment is recorded on the ledger. ---- +In practice, these “internal transfers” are often better handled purely off-chain, or batched, to avoid unnecessary fees and ledger noise. + +## FAQs + +**1. Are M-addresses backwards compatible?** + +Yes. An M-address is just another strkey encoding of the same underlying `G...` account plus a 64-bit ID. Services that don’t yet understand muxed addresses can still operate on the underlying `G...` account; they just won’t make use of the ID. During migration, exchanges should support both: + +- incoming deposits to `G...` + memo, and +- incoming deposits to `M...` (muxed) addresses. + +**2. Do customers have secret keys for muxed addresses?** + +No. The secret key belongs to the underlying `G...` account. Muxed accounts are purely virtual views over that on-chain account. + +For custodial exchanges, this matches the usual model: the exchange signs transactions on behalf of users; users control their balances via the exchange’s application, not on-chain keys. + +**3. How do I troubleshoot errors or unsupported muxed addresses?** + +If you encounter errors like “destination is invalid” or `op_malformed` when using an `M...` destination, it usually means muxed addresses aren’t fully supported somewhere in your stack. + +Try the following: + +- Upgrade your tooling: Make sure you’re on a recent version of your Stellar SDKs and Horizon/RPC client. In some SDKs, muxed addresses were initially hidden behind a feature flag; newer versions enable them by default. + +- Check for muxing feature flags / options: If your SDK mentions “enable muxing” (for example, the JavaScript SDK’s + +```text +“destination is invalid; did you forget to enable muxing?” +message), enable that option or upgrade to a version where it’s no longer required. +``` + +- Validate both `G...` and `M...` prefixes: Ensure your own validation logic allows both regular (`G...`) and muxed (`M...`) addresses instead of rejecting anything that doesn’t start with `G`. + +Decode when necessary: If a remote service doesn’t support muxed addresses, decode the `M...` address into its base `G...` address and, if they require it, include the muxed ID as a memo or internal reference instead. + +**4. How do smart contracts interact with muxed addresses?** + +For Stellar smart contracts, you generally work with the `Address` type, and optionally `MuxedAddress` in Rust (`soroban-sdk`) or the `Address` helper in the JavaScript SDK: + +- Callers can pass either `G...` or `M...` addresses into contract functions. +- The contract can expose `Address` or `MuxedAddress` parameters; SDKs will handle conversions from strkeys to the on-chain representation. +- The muxed ID is usually surfaced in events for off-chain systems, not stored directly in contract state. + +This allows you to build smart contracts that integrate cleanly with pooled account exchanges, while keeping the on-chain logic simple and consistent. + +**5. How do I handle platforms that don’t support muxed addresses?** + +There are two common scenarios when muxed addresst support is missing. + +1. You pay a muxed address, but the recipient doesn’t support muxed addresses + +In general, you should avoid sending payments to muxed addresses on platforms that don’t support them, since they won’t be able to provide or interpret `M...` destinations correctly. + +If this does happen anyway: + +- With an out-of-date SDK, parsing the muxed destination might fail and you’ll see a helpful error (for example, + +```text +“destination is invalid; did you forget to enable muxing?”). +Upgrade your SDK. +``` + +- With an up-to-date SDK, the `M...` address will parse successfully. What happens next is up to your application logic. +- On the network, the operation itself will still succeed: the destination’s underlying `G...` account receives the funds, even if the muxed ID is ignored by the recipient. + +2. You want to pay a muxed address, but your platform doesn’t support them + +In this case, you should not try to use a muxed address at all: + +- Your platform or SDK will likely fail to construct the operation, or reject the address format. +- Instead, fall back to the legacy pattern: + - use the regular `G...` address, and + - include an appropriate memo or other reference so the recipient can route the payment internally. + +**6. What do I do if I receive a transaction with muxed addresses and a memo ID?** + +In an ideal world, this situation would never happen. You can determine whether or not the underlying IDs are equal; if they aren’t, this is a malformed transaction and we recommend not submitting it to the network. + +**7. What happens if I pass a muxed address to an incompatible operation?** + +Only certain operations allow muxed addresses, as described above. Passing a muxed address to an incompatible parameter with an up-to-date SDK should result in a compilation or runtime error at the time of use. + +For example, when using the JavaScript SDK incorrectly: + + + +```js +const mAddress = + "MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4"; +transactionBuilder.addOperation( + Operation.setTrustLineFlags({ + trustor: mAddress, // wrong! + asset: someAsset, + flags: { clawbackEnabled: false }, + }), +); +``` + + + +The runtime result would be: + +“Error: invalid version byte. expected 48, got 96” + +This error message indicates that the `trustor` failed to parse as a Stellar account ID (`G...`). In other words, your code will fail and the invalid operation will never reach the network. + +**8. How do I validate Stellar addresses?** + +You should use the validation methods provided by your SDK or carefully adhere to [SEP-23](https://stellar.org/protocol/sep-23). For example, the JavaScript SDK provides the following methods for validating Stellar addresses: + +```ts +namespace StrKey { + function isValidEd25519PublicKey(publicKey: string): boolean; + function isValidMed25519PublicKey(publicKey: string): boolean; +} +``` + +There are also abstractions for constructing and managing both muxed and regular accounts; consult your SDK documentation for details. ## Code Implementation @@ -354,107 +811,20 @@ runAllMuxedExamples(); -### Running the Examples +### Running the examples - **All Examples**: `runAllMuxedExamples()` - Runs setup once and executes all three examples - **Individual Examples**: -- `runUnmuxedExample()` - Basic G→G payment -- `runMuxedToUnmuxedExample()` - Muxed→Unmuxed payment -- `runMuxedToMuxedExample()` - Muxed→Muxed payment - -**_Note:_** _When running individual examples, ensure you call `preamble()` first to set up and fund the accounts._ - -### More Examples - -As is the case for most protocol-level features, you can find more usage examples and inspiration in the relevant test suite for your favorite SDK. For example, [here](https://github.com/stellar/js-stellar-base/blob/master/test/unit/muxed_account_test.js) are some of the JavaScript test cases. - -### FAQs - -**What happens if I pay a muxed address, but the recipient doesn’t support them?** - -In general, you should not send payments to muxed addresses on platforms that do not support them. These platforms will not be able to provide muxed destination addresses in the first place. - -Even still, if this does occur, parsing a transaction with a muxed parameter without handling them will lead to one of two things occurring: - -- If your SDK is out-of-date, parsing will error out. You should upgrade your SDK. For example, the JavaScript SDK will throw a helpful message: - -``` - -“destination is invalid; did you forget to enable muxing?” - -``` - -- If your SDK is up-to-date, you will see the muxed (`M...`) address parsed out. What happens next depends on your application. - -Note, however, that the operation will succeed on the network. In the case of payments, for example, the destination’s parent address will still receive the funds. - -**What happens if I want to pay a muxed account, but my platform does not support them?** - -In this case, do not use a muxed address. The platform will likely fail to create the operation. You probably want to use the legacy method of including a transaction memo, instead. - -**What do I do if I receive a transaction with muxed addresses and a memo ID?** + - `runUnmuxedExample()` - Basic G→G payment + - `runMuxedToUnmuxedExample()` - Muxed→Unmuxed payment + - `runMuxedToMuxedExample()` - Muxed→Muxed payment -In an ideal world, this situation would never happen. You can determine whether or not the underlying IDs are equal; if they aren’t, this is a malformed transaction and we recommend not submitting it to the network. - -**What happens if I get errors when using muxed accounts?** - -In up-to-date versions of Stellar SDKs, muxed accounts are natively supported by default. If you are using an older version of an SDK, however, they may still be hidden behind a feature flag. - -If you get errors when using muxed addresses on supported operations like: “destination is invalid; did you enable muxing?” - -We recommend upgrading to the latest version of any and all Stellar SDKs you use. However, if that’s not possible for some reason, you will need to enable the feature flag before interacting with muxed accounts. Consult your SDK’s documentation for details. - -**What happens if I pass a muxed address to an incompatible operation?** - -Only certain operations allow muxed accounts, as described above. Passing a muxed address to an incompatible parameter with an up-to-date SDK should result in a compilation or runtime error at the time of use. - -For example, when using the JavaScript SDK incorrectly: - - - -```js -const mAddress = - "MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4"; -transactionBuilder.addOperation( - Operation.setTrustLineFlags({ - trustor: mAddress, // wrong! - asset: someAsset, - flags: { clawbackEnabled: false }, - }), -); -``` - - - -The runtime result would be: - -“Error: invalid version byte. expected 48, got 96” - -This error message indicates that the `trustor` failed to parse as a Stellar account ID (`G...`). In other words, your code will fail and the invalid operation will never reach the network. - -**How do I validate Stellar addresses?** - -You should use the validation methods provided by your SDK or carefully adhere to [SEP-23](https://stellar.org/protocol/sep-23). For example, the JavaScript SDK provides the following methods for validating Stellar addresses: - -```ts -namespace StrKey { - function isValidEd25519PublicKey(publicKey: string): boolean; - function isValidMed25519PublicKey(publicKey: string): boolean; -} -``` - -There are also abstractions for constructing and managing both muxed and regular accounts; consult your SDK documentation for details. - -## Memo - differentiated accounts - -Prior to the introduction of muxed accounts, products and services that relied on pooled accounts often used transaction memos to differentiate between users. Supporting muxed accounts is better in the long term, but for now you may want to support both memos and muxed accounts as all exchanges, anchors, and wallets may not support muxed accounts. +:::note -To learn about what other purposes memos can be used for, see our [Memos section](../../../learn/fundamentals/transactions/operations-and-transactions.mdx#memo). +When running individual examples, ensure you call `preamble()` first to set up and fund the accounts. -## Why are muxed accounts better in the long term? +::: -Muxed accounts are a better approach to differentiating between individuals in a pooled account because they have better: +### More examples -- Shareability — rather than worrying about error-prone things like copy-pasting memo IDs, you can just share your M... address. -- SDK support — the various SDKs support this abstraction natively, letting you create, manage, and work with muxed accounts easily. This means that you may see muxed addresses appear when parsing any of the fields that support them, so you should be ready to handle them. Refer to your SDK’s documentation for details; for example, [v7.0.0](https://github.com/stellar/js-stellar-base/releases/tag/v7.0.0) of the JavaScript SDK library stellar-base describes all of the fields and functions that relate to muxed accounts. -- Efficiency — by combining related virtual accounts under a single account’s umbrella, you can avoid holding reserves and paying fees for all of them to exist in the ledger individually. You can also combine multiple payments to multiple destinations within a single transaction since you do not need the per-transaction memo field anymore. +As is the case for most protocol-level features, you can find more usage examples and inspiration in the relevant test suite for your favorite SDK. For example, [here](https://github.com/stellar/js-stellar-base/blob/master/test/unit/muxed_account_test.js) are some of the JavaScript test cases.