Skip to content

🔁 Refresh channel accounts section - #2703

Open
JFWooten4 wants to merge 10 commits into
stellar:mainfrom
JFWooten4:refresh-channel-accounts
Open

🔁 Refresh channel accounts section#2703
JFWooten4 wants to merge 10 commits into
stellar:mainfrom
JFWooten4:refresh-channel-accounts

Conversation

@JFWooten4

Copy link
Copy Markdown
Contributor

from #723

⚠️ Relies on images from #2400

Copilot AI review requested due to automatic review settings July 27, 2026 16:05
Co-authored-by: Codex <noreply@openai.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Expands the channel accounts guide with architecture, security, throughput, and multi-SDK implementation guidance.

Changes:

  • Adds diagrams and channel-account concepts.
  • Adds Python, JavaScript, Java, and Go examples.
  • Documents rotation, fees, capacity, and operational considerations.

Recommendation: NEEDS-CHANGES — fix the non-runnable SDK examples, unsafe rotation logic, and inaccurate fee/capacity guidance.

Comments suppressed due to low confidence (8)

docs/build/guides/transactions/channel-accounts.mdx:129

  • StellarSdk is not defined by the destructuring import. Use the imported Operation symbol here as well.
  transaction2.addOperation(StellarSdk.Operation.payment({

docs/build/guides/transactions/channel-accounts.mdx:475

  • This second Java tab also uses the removed Transaction.Builder and new PaymentOperation.Builder(...) APIs, so it cannot compile with Java SDK 4.x. Migrate it to TransactionBuilder and PaymentOperation.builder() before publishing the refreshed guide.
  Transaction.Builder transactionBuilder = new Transaction.Builder(channelAccount, Network.TESTNET)
    .setBaseFee(Transaction.MIN_BASE_FEE);

  for (String recipient : allRecipients.get(i)) {
    transactionBuilder.addOperation(
      new PaymentOperation.Builder(recipient, AssetTypeNative.INSTANCE, "10")
        .setSourceAccount(primaryKeypair.getAccountId())

docs/build/guides/transactions/channel-accounts.mdx:714

  • This JavaScript loop has the same rotation deadlock: once every channel is active, no code marks one available until after this loop exits, which cannot happen while recipients remain. Process and await each wave before assigning another batch.
while (recipientIndex < allRecipients.length) {
  for (const channelData of channelAccountsTracker) {
    if (recipientIndex >= allRecipients.length) break;
    if (channelData.state === "available") {
      channelData.state = "active";

docs/build/guides/transactions/channel-accounts.mdx:878

  • This Go loop also deadlocks after one batch per channel because channel states are reset only in the submission loop below. If recipients remain after every channel becomes active, neither index nor state can change.
	for recipientIndex < len(allRecipients) {
		for _, channelData := range channelAccountsTracker {
			if recipientIndex >= len(allRecipients) {
				break
			}
			if channelData["state"] == "available" {

docs/build/guides/transactions/channel-accounts.mdx:783

  • The Java rotation loop cannot handle more than one batch per channel: after all channels become active, no state is reset until the later submission loop, so this while loop has no way to make progress. Submit and confirm each wave before continuing allocation.
  while (recipientIndex < allRecipients.length) {
    for (ChannelAccount channelData : channelAccountsTracker) {
      if (recipientIndex >= allRecipients.length) break;
      if (channelData.state.equals("available")) {
        channelData.state = "active";

docs/build/guides/transactions/channel-accounts.mdx:759

  • Even after awaiting submission, this unconditional release is unsafe on rejection or an unknown transport outcome: the local account sequence was advanced when the transaction was built, but the network may not have consumed it. Query/reload the channel account and reconcile the transaction before setting it available.
  } finally {
    channelData.state = "available";

docs/build/guides/transactions/channel-accounts.mdx:823

  • This releases the Java channel even when submission failed or its outcome is unknown. Because transaction construction advances the local account sequence, the next build can use a sequence that does not match the network; reconcile/reload the account before making it available.
    } finally {
        txBundle.channelData.state = "available";

docs/build/guides/transactions/channel-accounts.mdx:943

  • This marks the channel available after both success and failure without reconciling its sequence. When submission fails or times out, IncrementSequenceNum may have advanced only the local account, so reusing it can produce a sequence gap. Reload/verify the network account before release on non-confirmed outcomes.
		output.channelData["state"] = "available"

Comment thread docs/build/guides/transactions/channel-accounts.mdx Outdated
Comment thread docs/build/guides/transactions/channel-accounts.mdx
Comment on lines +94 to +102
const {
Server,
Keypair,
TransactionBuilder,
Networks,
Asset,
} = require("stellar-sdk");

const server = new Server('https://horizon-testnet.stellar.org');
Comment thread docs/build/guides/transactions/channel-accounts.mdx Outdated
Comment on lines +162 to +166
Transaction.Builder transactionBuilder1 = new Transaction.Builder(sourceAccount, Network.TESTNET)
.setBaseFee(100).set_timeout(3600);

Transaction.Builder transactionBuilder2 = new Transaction.Builder(sourceAccount, Network.TESTNET)
.setBaseFee(100).set_timeout(3600);
Comment on lines +881 to +883
sourceAccount := channelData["account"].(*txnbuild.SimpleAccount)
txBundle := txnbuild.TransactionParams{
SourceAccount: sourceAccount,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


In our example, we assume the bulk payment operations greatly exceed a single transaction, must occur promptly, and come pre-signed by $A_P$. Here, the main constraint is network capacity itself, as referenced earlier with payment channels. Accordingly, it is best practice to considerately send channel transactions based on how much you want to pay in fees.

If you fill up the ledger with all of your own transactions, you can expect to pay [exponentially-higher fees](../../../learn/fundamentals/fees-resource-limits-metering.mdx#surge-pricing) than dynamic bundle sizes which spread operations out over time. This chiefly depends on the rate you want to submit transactions. As long as you aren't consistently above 100 operations per ledger, each channel can just submit a transaction each ledger.

:::note Block Size

You can create as many channel accounts as needed to maintain your desired transaction rate. However, one ledger [can only fit](https://stellar.expert/explorer/public/protocol-history) 1,000 operations from all peers. To keep [fees](../../../learn/fundamentals/fees-resource-limits-metering.mdx#inclusion-fee) within reason and minimize congestion, it is best practice to limit yourself to 300 operations per ledger. For higher throughput in non-emergency applications, consider using other scaling solutions like [Starlight](../../../learn/glossary.mdx#starlight).
Comment on lines +692 to +693
finally:
channelData["state"] = "available"

### Fee Sponsorships

While we've discussed sending lumens directly to the channel accounts, you can also have transaction fees sponsored by the primary account. In a [custodial solution](../../../platforms/anchor-platform/admin-guide/assets-and-client-wallets.mdx), you may prefer that channel accounts hold no assets at all, maintaining trustlines with [sponsored reserves](./sponsored-reserves.mdx) for example. While you can use payments from $A_P$ to $A_C$ to cover fees each transaction, it is best practice to simply leave channels with adequate funding.
JFWooten4 and others added 9 commits July 27, 2026 12:38
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants