From f2b9c4e242df0dc08dec25cc8ca53b4f54d6eb92 Mon Sep 17 00:00:00 2001
From: Bri Wylde <92327786+briwylde08@users.noreply.github.com>
Date: Wed, 8 Oct 2025 15:36:12 -0600
Subject: [PATCH 1/6] Add content on Soroban support for muxed accounts
---
.../pooled-accounts-muxed-accounts-memos.mdx | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
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..4743fab13a 100644
--- a/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
+++ b/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
@@ -34,6 +34,31 @@ It is safe for all wallets to implement sending to muxed accounts.
If you wish to receive deposits to muxed accounts please keep in mind that they are not yet supported by all wallets and exchanges.
+### Muxed accounts in smart contracts
+
+Muxing information is represented in smart contract events and extends the multiplexing functionality to Stellar smart contract operations.
+
+#### `MuxedAddressObject`
+
+`MuxedAddressObject` is a new host object type that represents muxed account addresses in smart contracts. It extends the address model to support accounts that include a 64-bit identifier.
+
+- Regular account and contract addresses (`SC_ADDRESS_TYPE_ACCOUNT` and `SC_ADDRESS_TYPE_CONTRACT`) still map to `AddressObject`.
+- Muxed accounts (`SC_ADDRESS_TYPE_MUXED_ACCOUNT`) now map to `MuxedAddressObject`.
+- Claimable balance and liquidity pool addresses are not supported and any attempt to use them will fail.
+
+Unlike `AddressObject`, `MuxedAddressObject` is not interchangeable with other address types—contracts expecting a standard `AddressObject` will fail if a muxed address is provided.
+
+Two host functions allow access to its components:
+
+- `get_address_from_muxed_address()` → returns the base account address
+- `get_id_from_muxed_address()` → returns the 64-bit muxed ID
+
+#### Asset transfers and events
+
+The transfer function of the Stellar Asset Contract accepts muxed addresses.
+
+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.
From 89901a5fbb03e68505fcf3eb8224bea06f9b68e3 Mon Sep 17 00:00:00 2001
From: Bri Wylde <92327786+briwylde08@users.noreply.github.com>
Date: Fri, 10 Oct 2025 08:44:08 -0600
Subject: [PATCH 2/6] add link to GH
---
.../transactions/pooled-accounts-muxed-accounts-memos.mdx | 2 ++
1 file changed, 2 insertions(+)
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 4743fab13a..1c623ea96e 100644
--- a/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
+++ b/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
@@ -53,6 +53,8 @@ Two host functions allow access to its components:
- `get_address_from_muxed_address()` → returns the base account address
- `get_id_from_muxed_address()` → returns the 64-bit muxed ID
+Read more about muxed account support in smart contracts in [GitHub](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0067.md#multiplexing-support).
+
#### Asset transfers and events
The transfer function of the Stellar Asset Contract accepts muxed addresses.
From 79d084fef4af34e698c719243fae9e6b615ceded Mon Sep 17 00:00:00 2001
From: Bri Wylde <92327786+briwylde08@users.noreply.github.com>
Date: Wed, 19 Nov 2025 09:44:45 -0700
Subject: [PATCH 3/6] Update Muxed Accounts
---
.../pooled-accounts-muxed-accounts-memos.mdx | 519 +++++++++++++-----
1 file changed, 373 insertions(+), 146 deletions(-)
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 1c623ea96e..ccf581a59b 100644
--- a/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
+++ b/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
@@ -1,5 +1,5 @@
---
-title: "Pooled accounts: muxed accounts and memos"
+title: "Muxed accounts and pooled accounts"
description: Use muxed accounts to differentiate between individual accounts in a pooled account.
sidebar_position: 60
---
@@ -12,108 +12,422 @@ You can create a Stellar account for each user, but most custodial services, inc
:::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, e.g., “send to this G-address with this memo.” In the long term, muxed accounts are preferable: they reduce user error, make UX more consistent, and integrate cleanly with modern SDKs and Stellar smart contracts. However, not all wallets, exchanges, and anchors support muxed accounts yet, so you may want to support both memos and muxed accounts during a transition period.
:::
## 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 muxed accounts to map incoming and outgoing payments to the corresponding internal customer.
-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.
+The benefits of a pooled account are:
-## Muxed accounts
+- Lower costs – no base reserve 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:
-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.
+- 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 do not exist on the ledger, but their shared underlying `GABC…` account does.
+## Muxed accounts
-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).
+Muxed accounts (“multiplexed accounts”) are embedded into the protocol for convenience and standardization. They distinguish individual logical accounts that all exist under a single, traditional Stellar account. A muxed account combines a familiar `GABC…` account address with a 64-bit integer ID.
-It is safe for all wallets to implement sending to muxed accounts.
+- Virtual only: muxed accounts themselves do not exist on the ledger; only the shared underlying `GABC…` account does.
+- Spec: muxed accounts are defined in [CAP-0027: First-class multiplexed accounts](https://stellar.org/protocol/cap-27) and their string representation in [SEP-23: Strkeys](https://stellar.org/protocol/sep-23).
-If you wish to receive deposits to muxed accounts please keep in mind that they are not yet supported by all wallets and exchanges.
+It is safe for all wallets and services to implement sending to muxed accounts. If you wish to receive deposits to muxed accounts, remember that not every wallet or exchange supports them yet, so you may need to support both memos and M-addresses during migration.
-### Muxed accounts in smart contracts
+### Why exchanges and custodians use muxed accounts
-Muxing information is represented in smart contract events and extends the multiplexing functionality to Stellar smart contract operations.
+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.
-#### `MuxedAddressObject`
+This model works, but has well-known problems:
-`MuxedAddressObject` is a new host object type that represents muxed account addresses in smart contracts. It extends the address model to support accounts that include a 64-bit identifier.
+- Users forget to include the memo.
+- Users reuse another user’s memo or type it incorrectly.
+- Support teams must manually reconcile “missing memo” deposits.
-- Regular account and contract addresses (`SC_ADDRESS_TYPE_ACCOUNT` and `SC_ADDRESS_TYPE_CONTRACT`) still map to `AddressObject`.
-- Muxed accounts (`SC_ADDRESS_TYPE_MUXED_ACCOUNT`) now map to `MuxedAddressObject`.
-- Claimable balance and liquidity pool addresses are not supported and any attempt to use them will fail.
+Muxed accounts solve this by embedding a distinct numeric ID directly into the address:
-Unlike `AddressObject`, `MuxedAddressObject` is not interchangeable with other address types—contracts expecting a standard `AddressObject` will fail if a muxed address is provided.
+- Each user gets a unique M-address (muxed account) that still resolves to the same pooled `G...` address.
+- Payments to a user’s M-address deterministically map to a single internal customer row.
+- No memo is required, so deposits and withdrawals become memo-less and less error-prone.
-Two host functions allow access to its components:
+Benefits for exchanges and custodians:
-- `get_address_from_muxed_address()` → returns the base account address
-- `get_id_from_muxed_address()` → returns the 64-bit muxed ID
+- 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.
-Read more about muxed account support in smart contracts in [GitHub](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0067.md#multiplexing-support).
+### Address format
-#### Asset transfers and events
+Muxed accounts have their own address format that starts with an `M` prefix. For example, from a traditional Stellar account address:
-The transfer function of the Stellar Asset Contract accepts muxed addresses.
+```text
+GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
+```
-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).
+you can derive multiple muxed accounts with different IDs:
-### Address format
+- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ` has ID 0
+- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4` has ID 420
-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.
+Some useful properties:
-- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ` has the ID 0, while
-- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4` has the ID 420.
+- 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 accounts don’t have their own secret keys.
-Both of these addresses will act on the underlying `GA7Q…` address when used with one of the supported operations.
+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 accounts, 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:
+
+- 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).
+
+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.
+
+### Migrating from memos to muxed accounts
+
+Memos are still widely supported and will continue to exist at the protocol level for classic transactions, but exchanges are strongly encouraged to migrate toward muxed accounts, especially for new integrations and for Soroban-based token flows where multiplexing is first-class.
+
+A practical migration strategy:
-- 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).
+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 now 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 accounts 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 accounts.
+- Encourage power users and institutional clients to switch to M-addresses to reduce support friction.
+
+### SDK integrations
+
+All official SDKs expose helpers for creating, parsing, and using muxed accounts. 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
-We will demonstrate some of these in the examples section.
+// Load the underlying account once so we have its sequence number
+const custodianAccount = await server.getAccount(custodianGAddress);
-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.
+// 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.
+
+### Payments workflow for exchanges
+
+When combining pooled accounts with muxed accounts, the typical exchange workflow looks like this:
+
+#### Receiving deposits
+
+- Generate a unique M-address per user (or per sub-account).
+- Display this M-address 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 and credits the correct customer balance.
+
+No memo is required, and you can use the same pooled account for many customers and assets.
+
+#### Sending withdrawals
+
+- 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 ID,
+ - uses the underlying `G...` address 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.
+
+#### Common patterns
+
+- Standard payment (G → G): Deposit from an external wallet to your exchange’s pooled account (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 funds to itself; only fees change. You may want to recognize and aggregate such transfers to avoid unnecessary on-chain churn.
+
+### Muxed accounts 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 account as a contract argument.
+ - Under the hood, SDKs convert M-addresses into appropriate XDR types (`ScAddress`, etc.) when invoking contracts.
+
+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).
+
+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).
## 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 accounts 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
-**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.
+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.
-### 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.
+### Example 1 – Basic payment (G → G)
-### Example 2 - Muxed to Unmuxed Payment (M → 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 the payment amount plus transaction fees; the recipient increases by the payment amount.
-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.
+### Example 2 – Muxed to unmuxed payment (M → G)
-The transaction is signed with custodian keys since muxed accounts have no secret keys. The custodian account balance decreases by 10 XLM plus fees.
+This example demonstrates a payment from a muxed account (representing a custodial customer) to a regular Stellar account:
-### Example 3 - Muxed to Muxed Payment (M → M)
+- 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 accounts have no secret keys.
+- The custodian balance decreases by the payment amount plus fees.
-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.
+### Example 3 – Muxed to muxed payment (M → M)
-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.
+When two muxed accounts share the same underlying pooled account:
----
+- 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 accounts 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 accounts?**
+
+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 accounts?**
+
+If you encounter errors like “destination is invalid” or `op_malformed` when using an `M...` destination, it usually means muxed accounts 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 accounts 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 accounts, 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 accounts?**
+
+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 accounts?**
+
+There are two common scenarios when muxed account support is missing.
+
+1. You pay a muxed address, but the recipient doesn’t support muxed accounts
+
+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 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.
+
+**7. 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.
+
+**8. 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.
+
+**9. 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.
+
+**10. 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
@@ -389,99 +703,12 @@ runAllMuxedExamples();
- `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?**
-
-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.
From a731fa7c29423f702de39280d89522dc158220fb Mon Sep 17 00:00:00 2001
From: Bri Wylde <92327786+briwylde08@users.noreply.github.com>
Date: Wed, 19 Nov 2025 12:40:15 -0700
Subject: [PATCH 4/6] nits
---
.../pooled-accounts-muxed-accounts-memos.mdx | 34 ++++++-------------
1 file changed, 11 insertions(+), 23 deletions(-)
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 ccf581a59b..ef6b0ce6a8 100644
--- a/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
+++ b/docs/build/guides/transactions/pooled-accounts-muxed-accounts-memos.mdx
@@ -12,7 +12,7 @@ You can create a Stellar account for each user, but most custodial services, inc
:::note
-Historically, many services used memos to distinguish users, e.g., “send to this G-address with this memo.” In the long term, muxed accounts are preferable: they reduce user error, make UX more consistent, and integrate cleanly with modern SDKs and Stellar smart contracts. However, not all wallets, exchanges, and anchors support muxed accounts yet, so you may want to support both memos and muxed accounts during a transition period.
+Historically, many services used memos to distinguish users, e.g., “send to this G-address with this memo.” 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 accounts yet, so you may want to support both memos and muxed accounts during a transition period.
:::
@@ -85,7 +85,7 @@ Some useful properties:
- All operations ultimately act on the underlying `G...` account.
- Authorization and signing always use the base account; muxed accounts don’t have their own secret keys.
-Both example M-addresses above act on the same underlying GA7Q… account when used with supported operations.
+Both example M-addresses above act on the same underlying `GA7Q…` account when used with supported operations.
### Supported operations
@@ -111,7 +111,7 @@ There is no validation on the ID at the protocol level. As far as the Stellar ne
### Migrating from memos to muxed accounts
-Memos are still widely supported and will continue to exist at the protocol level for classic transactions, but exchanges are strongly encouraged to migrate toward muxed accounts, especially for new integrations and for Soroban-based token flows where multiplexing is first-class.
+Memos are still widely supported and will continue to exist at the protocol level for classic transactions, but exchanges are strongly encouraged to migrate toward muxed accounts, especially for new integrations and for smart contract-based token flows where multiplexing is first-class.
A practical migration strategy:
@@ -372,23 +372,11 @@ In this case, you should not try to use a muxed address at all:
- use the regular `G...` address, and
- include an appropriate memo or other reference so the recipient can route the payment internally.
-**6. 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.
-
-**7. What do I do if I receive a transaction with muxed addresses and a memo ID?**
+**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.
-**8. 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.
-
-**9. What happens if I pass a muxed address to an incompatible operation?**
+**7. 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.
@@ -416,7 +404,7 @@ The runtime result would be:
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.
-**10. How do I validate Stellar addresses?**
+**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:
@@ -695,13 +683,13 @@ 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
+ - `runUnmuxedExample()` - Basic G→G payment
+ - `runMuxedToUnmuxedExample()` - Muxed→Unmuxed payment
+ - `runMuxedToMuxedExample()` - Muxed→Muxed payment
:::note
@@ -709,6 +697,6 @@ When running individual examples, ensure you call `preamble()` first to set up a
:::
-### More Examples
+### 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.
From 616588240fb7a89e1ee863f1a5ade0fc52c3d225 Mon Sep 17 00:00:00 2001
From: Bri Wylde <92327786+briwylde08@users.noreply.github.com>
Date: Wed, 19 Nov 2025 13:59:59 -0700
Subject: [PATCH 5/6] reorganizing and implementing feedback
---
.../pooled-accounts-muxed-accounts-memos.mdx | 273 +++++++++---------
1 file changed, 141 insertions(+), 132 deletions(-)
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 ef6b0ce6a8..99af7902e6 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: "Muxed accounts and pooled accounts"
-description: Use muxed accounts to differentiate between individual accounts in a pooled account.
+title: "Muxed addresses and pooled accounts"
+description: Use muxed addresses to differentiate between individual accounts in a pooled account.
sidebar_position: 60
---
@@ -8,17 +8,19 @@ 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, the muxed address (can also be known as muxed account) feature can map transactions to individual accounts via an internal customer database.
:::note
-Historically, many services used memos to distinguish users, e.g., “send to this G-address with this memo.” 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 accounts yet, so you may want to support both memos and muxed accounts during a transition period.
+Historically, many services used memos to distinguish users, e.g., “send to this G-address with this memo.” In the long term, muxed addresses 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 addresses or explicit contract parameters instead of memos.
:::
## Pooled accounts
-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 muxed accounts to map incoming and outgoing payments 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 muxed addresses to map incoming and outgoing payments to the corresponding internal customer.
The benefits of a pooled account are:
@@ -31,16 +33,16 @@ The trade-off is that you assume responsibility for:
- Handling errors, atomicity, and refunds in your own systems.
- Detecting and preventing misuse or fraud at the application layer.
-## Muxed accounts
+## Muxed addresses
-Muxed accounts (“multiplexed accounts”) are embedded into the protocol for convenience and standardization. They distinguish individual logical accounts that all exist under a single, traditional Stellar account. A muxed account combines a familiar `GABC…` account address with a 64-bit integer ID.
+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. A muxed address combines a familiar `GABC…` account address with a 64-bit integer ID.
-- Virtual only: muxed accounts themselves do not exist on the ledger; only the shared underlying `GABC…` account does.
-- Spec: muxed accounts are defined in [CAP-0027: First-class multiplexed accounts](https://stellar.org/protocol/cap-27) and their string representation in [SEP-23: Strkeys](https://stellar.org/protocol/sep-23).
+- Virtual only: muxed addresses themselves do not exist on the ledger; only the shared underlying `GABC…` account does.
+- Spec: muxed addresses are defined in [CAP-0027: First-class multiplexed accounts](https://stellar.org/protocol/cap-27) and their string representation in [SEP-23: Strkeys](https://stellar.org/protocol/sep-23).
-It is safe for all wallets and services to implement sending to muxed accounts. If you wish to receive deposits to muxed accounts, remember that not every wallet or exchange supports them yet, so you may need to support both memos and M-addresses during migration.
+It is safe for all wallets and services to implement sending to muxed addresses. 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 exchanges and custodians use muxed accounts
+### Why use 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.
@@ -50,9 +52,9 @@ This model works, but has well-known problems:
- Users reuse another user’s memo or type it incorrectly.
- Support teams must manually reconcile “missing memo” deposits.
-Muxed accounts solve this by embedding a distinct numeric ID directly into the address:
+Muxed addresses solve this by embedding a distinct numeric ID directly into the address:
-- Each user gets a unique M-address (muxed account) that still resolves to the same pooled `G...` 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.
- No memo is required, so deposits and withdrawals become memo-less and less error-prone.
@@ -62,56 +64,15 @@ Benefits for exchanges and custodians:
- Provides a standardized interface for wallets, custodians, and smart contract flows.
- Cleanly extends to smart contract token transfers as multiplexing becomes standard in contracts.
-### Address format
-
-Muxed accounts have their own address format that starts with an `M` prefix. For example, from a traditional Stellar account address:
-
-```text
-GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
-```
-
-you can derive multiple muxed accounts with different IDs:
-
-- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ` has ID 0
-- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4` has ID 420
-
-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 accounts 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
+### Migrating from memos to muxed addresses
-Not all operations accept muxed accounts, but they can be used for:
+Memos are still widely supported and will continue to exist at the protocol level for classic transactions, but exchanges are strongly encouraged to migrate toward muxed addresses, especially for new integrations and for smart contract-based token flows where multiplexing is first-class.
-- 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:
-
-- 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).
-
-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.
+:::note
-### Migrating from memos to muxed accounts
+Smart contracts cannot access transaction memos, so any contract-based integration that currently relies on memos for routing will need to migrate to muxed addresses (or other contract arguments) instead.
-Memos are still widely supported and will continue to exist at the protocol level for classic transactions, but exchanges are strongly encouraged to migrate toward muxed accounts, especially for new integrations and for smart contract-based token flows where multiplexing is first-class.
+:::
A practical migration strategy:
@@ -132,68 +93,16 @@ A practical migration strategy:
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 accounts out of the box, including utilities to decode/encode between `G` and `M` forms.
+- 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 accounts.
+- 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.
-### SDK integrations
-
-All official SDKs expose helpers for creating, parsing, and using muxed accounts. 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
-
-// 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.
-
### Payments workflow for exchanges
-When combining pooled accounts with muxed accounts, the typical exchange workflow looks like this:
+When combining pooled accounts with muxed addresses, the typical exchange workflow looks like this:
#### Receiving deposits
@@ -230,7 +139,7 @@ No memo is required, and you can use the same pooled account for many customers
- 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 funds to itself; only fees change. You may want to recognize and aggregate such transfers to avoid unnecessary on-chain churn.
-### Muxed accounts in smart contracts
+### 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.
@@ -246,7 +155,7 @@ Key points:
- 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 account as a contract argument.
+ - `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.
Typical smart contract use cases:
@@ -260,11 +169,111 @@ Typical smart contract use cases:
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).
-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).
+Read more about muxed addresses 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 addresses have their own address format that starts with an `M` prefix. For example, from a traditional Stellar account address:
+
+```text
+GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
+```
+
+you can derive multiple muxed addresses with different IDs:
+
+- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAAAACJUQ` has ID 0
+- `MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUAAAAAAAAAABUTGI4` has ID 420
+
+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 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:
+
+- 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).
+- [SEP-41-compliant token contracts](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0041.md) accept muxed addresses in the `to` (destination) field of transfer, via the `MuxedAddress` type. When you pass an `M...` address as to, SDKs parse it into the appropriate on-chain representation, and the muxed ID is exposed in token events so indexers and off-chain systems can track virtual sub-accounts.
+
+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
+
+// 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 three common payment scenarios:
+The following examples demonstrate how muxed addresses work in practice, showing three common payment scenarios:
1. A standard account-to-account payment (G → G)
2. A muxed-to-unmuxed payment (M → G)
@@ -282,16 +291,16 @@ This example establishes baseline behavior with a standard payment between two r
### Example 2 – Muxed to unmuxed payment (M → G)
-This example demonstrates a payment from a muxed account (representing a custodial customer) to a regular Stellar account:
+This example demonstrates a payment from a muxed address (representing a custodial customer) to a regular Stellar account:
- 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 accounts have no secret keys.
+- 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)
-When two muxed accounts share the same underlying pooled account:
+When two muxed addresses share the same underlying pooled account:
- On-chain, the account is effectively sending assets to itself.
- The net asset balance doesn’t change (the debit and credit cancel out).
@@ -303,24 +312,24 @@ In practice, these “internal transfers” are often better handled purely off-
**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 accounts can still operate on the underlying `G...` account; they just won’t make use of the ID. During migration, exchanges should support both:
+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 accounts?**
+**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 accounts?**
+**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 accounts aren’t fully supported somewhere in your stack.
+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 accounts were initially hidden behind a feature flag; newer versions enable them by default.
+- 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
@@ -331,9 +340,9 @@ message), enable that option or upgrade to a version where it’s no longer requ
- 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 accounts, 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.
+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 accounts?**
+**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:
@@ -343,11 +352,11 @@ For Stellar smart contracts, you generally work with the `Address` type, and opt
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 accounts?**
+**5. How do I handle platforms that don’t support muxed addresses?**
-There are two common scenarios when muxed account support is missing.
+There are two common scenarios when muxed addresst support is missing.
-1. You pay a muxed address, but the recipient doesn’t support muxed accounts
+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.
@@ -378,7 +387,7 @@ In an ideal world, this situation would never happen. You can determine whether
**7. 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.
+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:
From 6b1d6f83bb7e696e93d9bdb9229ea5f8c8e73c17 Mon Sep 17 00:00:00 2001
From: Bri Wylde <92327786+briwylde08@users.noreply.github.com>
Date: Thu, 20 Nov 2025 12:16:27 -0700
Subject: [PATCH 6/6] terminology & editing exchange instructions
---
.../pooled-accounts-muxed-accounts-memos.mdx | 247 +++++++++++++-----
1 file changed, 183 insertions(+), 64 deletions(-)
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 99af7902e6..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,5 +1,5 @@
---
-title: "Muxed addresses and pooled accounts"
+title: "Muxed accounts and pooled accounts"
description: Use muxed addresses to differentiate between individual accounts in a pooled account.
sidebar_position: 60
---
@@ -8,23 +8,55 @@ 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 address (can also be known as 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
-Historically, many services used memos to distinguish users, e.g., “send to this G-address with this memo.” In the long term, muxed addresses 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.
+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 addresses or explicit contract parameters instead of memos.
+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. Services that use pooled accounts track customers in a separate, internal database and use muxed addresses to map incoming and outgoing payments 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 is required per end user.
+- 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:
@@ -33,16 +65,25 @@ The trade-off is that you assume responsibility for:
- Handling errors, atomicity, and refunds in your own systems.
- Detecting and preventing misuse or fraud at the application layer.
-## Muxed addresses
+## 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:
-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. A muxed address combines a familiar `GABC…` account address with a 64-bit integer ID.
+- 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.
-- Virtual only: muxed addresses themselves do not exist on the ledger; only the shared underlying `GABC…` account does.
-- Spec: muxed addresses are defined in [CAP-0027: First-class multiplexed accounts](https://stellar.org/protocol/cap-27) and their string representation in [SEP-23: Strkeys](https://stellar.org/protocol/sep-23).
+Key properties:
-It is safe for all wallets and services to implement sending to muxed addresses. 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.
+- **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).
-### Why use muxed addresses
+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.
@@ -52,94 +93,168 @@ This model works, but has well-known problems:
- Users reuse another user’s memo or type it incorrectly.
- Support teams must manually reconcile “missing memo” deposits.
-Muxed addresses solve this by embedding a distinct numeric ID directly into the address:
+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.
+- 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.
+- 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 addresses
+## Migrating from memos to muxed accounts
-Memos are still widely supported and will continue to exist at the protocol level for classic transactions, but exchanges are strongly encouraged to migrate toward muxed addresses, especially for new integrations and for smart contract-based token flows where multiplexing is first-class.
+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 addresses (or other contract arguments) instead.
+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.
:::
-A practical migration strategy:
+### 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`).
-1. Accept both memos and M-addresses for deposits
+- 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`).
-- 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.
+- 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.
-2. Support withdrawals to `G...` and `M...`
+In practice, this means:
-- 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 now display or require M-addresses for custodial flows.
+> 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.
-3. Update validation code
+You are using **muxed accounts** (concept + IDs) without exposing `M...` addresses to end users.
-- 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.
+#### 2. Full M-address UX (classic payments)
-4. Phase down memo usage
+In the more advanced migration, you also update your UI to hand out `M...` addresses directly for classic payments:
-- 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.
+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.
-### Payments workflow for exchanges
+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.
-When combining pooled accounts with muxed addresses, the typical exchange workflow looks like this:
+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.
-#### Receiving deposits
+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.
-- Generate a unique M-address per user (or per sub-account).
-- Display this M-address to the user in your UI.
+## 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 and credits the correct customer balance.
+ - Your off-chain system decodes the muxed ID (or uses the memo ID) and credits the correct customer balance.
-No memo is required, and you can use the same pooled account for many customers and assets.
+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
+#### 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 ID,
- - uses the underlying `G...` address as the destination in the transaction,
+ - 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
+ - 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.
-#### Common patterns
+#### Common classic patterns
+
+- **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.
+
+#### Common classic patterns
+
+- **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.
+
+### Contract token deposits with existing `G...` + memo UIs
+
+For **SEP-41 token deposits**, the flow changes slightly, because contracts cannot see memos. A common pattern is:
+
+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.
-- Standard payment (G → G): Deposit from an external wallet to your exchange’s pooled account (legacy pattern).
+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.
-- Muxed-to-unmuxed (M → G): Withdrawal from a specific exchange customer to an external wallet, without any memo.
+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.
-- 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 funds to itself; only fees change. You may want to recognize and aggregate such transfers to avoid unnecessary on-chain churn.
+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.
-### Muxed addresses in smart contracts
+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.
@@ -153,31 +268,35 @@ Key points:
- `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.
+ - 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 |
+| 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).
-Read more about muxed addresses 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).
+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 addresses have their own address format that starts with an `M` prefix. For example, from a traditional Stellar account address:
-```text
-GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ
-```
+`GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ`
you can derive multiple muxed addresses with different IDs:
@@ -215,7 +334,7 @@ In practice, for exchanges and custodial services this covers:
- 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).
-- [SEP-41-compliant token contracts](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0041.md) accept muxed addresses in the `to` (destination) field of transfer, via the `MuxedAddress` type. When you pass an `M...` address as to, SDKs parse it into the appropriate on-chain representation, and the muxed ID is exposed in token events so indexers and off-chain systems can track virtual sub-accounts.
+- Contract token transfers where the exchange indexes deposits by muxed ID instead of memo ID.
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.