Skip to content

docs(tron): USDT vault tutorial and agentic development guide - #248

Open
stevep0z wants to merge 9 commits into
mainfrom
devrel/tron-guides
Open

stevep0z wants to merge 9 commits into
mainfrom
devrel/tron-guides

Conversation

@stevep0z

@stevep0z stevep0z commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Adds the TRON vault tutorial under the Guides section, plus its nav entry.

Build a USDT vault on TRON with Hardhat

A TRC-4626 vault over USDT-TRON with entry and exit fees, tested on a local TRON node and deployed to Nile. TVM divergences are taught at the point they bite rather than front-loaded: USDT's transfer returning false on success, 6-decimal amounts, the three address formats, and energy instead of gas.

  • Compiles with tron-solc 0.8.26 (46 files, cancun) and passes 11/11 tests against the stock tronbox/tre:dev image, no patched node required.
  • A project built from only this page's code blocks compiles and passes, so the guide is self-contained and there is nothing to clone. Every code block is byte-identical to a file in that project, apart from two deliberately staged excerpts (the pre-fees vault, the pre-upgrades config).
  • Deployed to Nile and exercised end to end. Vault TGmwKo2Tz46uPGsy75eAPNfTRtkZkeFNFg, creation tx faed6c72…, block 72028163. Deploy and interact output are pasted verbatim.

Running it yourself needs Docker up and nothing else. The plugin spawns a real java-tron container at test start and tears it down at the end. Set tre.keepRunning: true if you want it to persist between runs.

Review feedback

@luiz-lvj's fifteen inline comments are all applied, each checked against source before being taken: the TRC4626Fees ordering, the viaIR and metadata removal (verified: 46 files compile and 11/11 pass without them), the compiler-pin rationale, the safeTransferChecked failure path, the fee-rounding and test comments, the custom-error paths, the autoStart priming explanation, the energy and bandwidth figures, and the Tronscan flattening step.

Open items

  • Standalone example repo. The DevRel docs standard asks for a forkable repo per tutorial. This guide was deliberately built to need none: everything appears inline and a reader types it out as they go. That tension is the one decision blocking merge.
  • Node version wording. Prerequisites say 22 or newer (the plugin's engines.node), while the "Tested with" line says 24.13.0. Both true, but a reader could take 24 as the floor.

Still worth a look

  1. TRC4626Fees is vendored into the example because the npm package excludes contracts/mocks/**. The code matches the copy on /tron-contracts/trc4626. Shout if it should link out instead.

Notes

  • The testnet-token step points at TronFAQBot on Discord rather than the nileex.io web faucet, which does not load: DNS resolves and port 443 accepts, but no HTTP response comes back, while api.nileex.io answers in under 300ms. The bot's documented limit of 2,000 per token per 24 hours matches what this run received.
  • Hardhat 2 throughout, with @hh2 tags on the install. No Hardhat 3 references, per the current plan.
  • Branched from main.

Adds two pages under the TRON "Guides" section:

- guide-usdt-vault: a TRC-4626 vault over USDT-TRON with entry/exit fees,
  tested on a local TRON node (TRE) and deployed to Nile. Teaches the TVM
  divergences at the point they bite: USDT's transfer returning false on
  success, 6-decimal amounts, the three address formats, and energy
  instead of gas.
- guide-agentic-development: connecting the OpenZeppelin MCP server and
  coding agents to TRON contract work. Install and review sections are
  complete; the walkthrough is a skeleton pending tools that do not exist.

The vault example is verified: it compiles with tron-solc 0.8.26 and passes
11/11 tests against the stock tronbox/tre:dev image. A project built from
only this page's code blocks compiles and passes, so the guide is
self-contained with nothing to clone.

Not ready to merge. Six TODO(verify) markers remain, all Nile items that
need a funded testnet account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for openzeppelin-docs-v2 ready!

Name Link
🔨 Latest commit 262175f
🔍 Latest deploy log https://app.netlify.com/projects/openzeppelin-docs-v2/deploys/6ad93a8c63ab3f63dccfd39e
😎 Deploy Preview https://deploy-preview-248--openzeppelin-docs-v2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

- Interact section contradicted itself: it said the USDT balance ends
  lower by the fees, then said the fees come straight back because the
  treasury is the reader's own address. The second is correct; the
  balance returns roughly unchanged and what is actually spent is TRX
  for energy.
- Inflation-attack description said shares "round down to zero", which
  describes a naive vault. TRC4626 carries virtual shares, so they do
  not; the guide's own test shows the victim recovering 666.666667 of
  1,000 at offset 0. Reworded to match.
- Removed an unmeasured TRX cost estimate that sat in the same sentence
  as the TODO asking for it to be measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@luiz-lvj luiz-lvj left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good, left some minor comments and a few corrections.

});
```

TODO(verify): confirm `TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf` is the test USDT that the Nile faucet and TronFAQBot hand out, and that its `transfer` returns `false` like mainnet USDT.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is the correct address.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Great, I've removed the comment and confirmed that it also returns false on success.

Comment on lines +27 to +31
V->>Tr: safeTransferChecked(entry fee)
V-->>U: mint vUSDT shares
U->>V: redeem(shares, receiver, owner)
V->>Tr: safeTransferChecked(exit fee)
V->>T: safeTransferChecked(assets to user)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The order doesn't match TRC4626Fees: super._deposit pulls and mints before the entry fee, and super._withdraw pays the receiver before the exit fee.

Suggested change
V->>Tr: safeTransferChecked(entry fee)
V-->>U: mint vUSDT shares
U->>V: redeem(shares, receiver, owner)
V->>Tr: safeTransferChecked(exit fee)
V->>T: safeTransferChecked(assets to user)
V-->>U: mint vUSDT shares
V->>Tr: safeTransferChecked(entry fee)
U->>V: redeem(shares, receiver, owner)
V->>T: safeTransferChecked(assets to user)
V->>Tr: safeTransferChecked(exit fee)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed and applied.

Comment on lines +85 to +89
evmVersion: 'cancun',
viaIR: true,
// Embed the source as literal text in the metadata so Tronscan can
// reconstruct it deterministically when you verify.
metadata: { bytecodeHash: 'ipfs', useLiteralContent: true },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The project compiles without viaIR (all 46 files), and Tronscan's verification form has no viaIR field, so keeping it makes the contract unverifiable there. The metadata block doesn't help either: Tronscan recompiles from the uploaded source and has no useLiteralContent setting, so this only makes the bytecode differ from what Tronscan produces.

Suggested change
evmVersion: 'cancun',
viaIR: true,
// Embed the source as literal text in the metadata so Tronscan can
// reconstruct it deterministically when you verify.
metadata: { bytecodeHash: 'ipfs', useLiteralContent: true },
evmVersion: 'cancun',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Applied!

- `tron: true` on a network routes `hre.ethers` calls through TronWeb, so ethers-style tests and scripts work against the TVM.

<Callout>
The compiler is pinned to Solidity 0.8.26 with `evmVersion: 'cancun'` because that is what `tron-solc` and the current TVM support. Don't raise it to match your Ethereum projects.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

tron-solc ships up to 0.8.31, so that isn't the reason for the pin.

Suggested change
The compiler is pinned to Solidity 0.8.26 with `evmVersion: 'cancun'` because that is what `tron-solc` and the current TVM support. Don't raise it to match your Ethereum projects.
The compiler is pinned to Solidity 0.8.26 with `evmVersion: 'cancun'` because that is what OpenZeppelin Contracts for TRON is compiled and tested with. Don't raise it to match your Ethereum projects.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Applied!


## Add an entry and exit fee

A common customization is a fee that goes to a treasury. The fee logic below is the example from the [TRC-4626 page](/tron-contracts/trc4626#custom-behavior-adding-fees-to-the-vault), unchanged. It lives in the library's `contracts/mocks/docs/` folder, which the npm package doesn't ship, so you can't import it. Add it to your project as `contracts/TRC4626Fees.sol`:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
A common customization is a fee that goes to a treasury. The fee logic below is the example from the [TRC-4626 page](/tron-contracts/trc4626#custom-behavior-adding-fees-to-the-vault), unchanged. It lives in the library's `contracts/mocks/docs/` folder, which the npm package doesn't ship, so you can't import it. Add it to your project as `contracts/TRC4626Fees.sol`:
A common customization is a fee that goes to a treasury. The fee logic below is the example from the [TRC-4626 page](/tron-contracts/trc4626#custom-behavior-adding-fees-to-the-vault), with only the comments and import paths changed. It lives in the library's `contracts/mocks/docs/` folder, which the npm package doesn't ship, so you can't import it. Add it to your project as `contracts/TRC4626Fees.sol`:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Applied!

Comment on lines +637 to +641
Custom-error assertions are not always safe on the TVM. The library documents three paths where the payload doesn't survive:

- An **out-of-gas bubble**. The inner call burns the whole energy budget, and the custom error is dropped on the way out.
- A **`CREATE2` collision**.
- A **panic code**, whose selector doesn't reliably survive TVM's revert-data field.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The CREATE2 collision is the out-of-gas case again (the collision burns almost all energy), and the panic bullet should name the Panic(uint256) payload, not a selector.

Suggested change
Custom-error assertions are not always safe on the TVM. The library documents three paths where the payload doesn't survive:
- An **out-of-gas bubble**. The inner call burns the whole energy budget, and the custom error is dropped on the way out.
- A **`CREATE2` collision**.
- A **panic code**, whose selector doesn't reliably survive TVM's revert-data field.
Custom-error assertions are not always safe on the TVM. The library documents two paths where the payload doesn't survive:
- An **out-of-gas bubble**. The inner call burns the whole energy budget, and the custom error is dropped on the way out. A `CREATE2` collision is the same case, since it burns almost all energy.
- A **`Panic(uint256)` revert**, whose payload doesn't reliably survive TVM's revert-data field.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sounds good, applied!

<Callout>
To keep the node running between runs and skip the startup wait, set `tre.keepRunning: true`.

Starting the container yourself with `tre.autoStart: false` is a sharper edge than it looks. The plugin mines one priming block when it spawns the container, and skips that step for a node it merely found already running. Without the priming block, the first deploy fails inside TronWeb with `Unable to get params: Cannot read properties of undefined (reading 'toString')`, because java-tron omits the zero-valued block number at height 0. Prefer `keepRunning` over managing the container by hand.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The plugin also primes a node it finds already running. The step is skipped because with autoStart: false the plugin's lifecycle never runs at all.

Suggested change
Starting the container yourself with `tre.autoStart: false` is a sharper edge than it looks. The plugin mines one priming block when it spawns the container, and skips that step for a node it merely found already running. Without the priming block, the first deploy fails inside TronWeb with `Unable to get params: Cannot read properties of undefined (reading 'toString')`, because java-tron omits the zero-valued block number at height 0. Prefer `keepRunning` over managing the container by hand.
Starting the container yourself with `tre.autoStart: false` is a sharper edge than it looks. The plugin mines one priming block before your tests, but only when it manages the node; with `autoStart: false` that step never runs. Without the priming block, the first deploy fails inside TronWeb with `Unable to get params: Cannot read properties of undefined (reading 'toString')`, because java-tron omits the zero-valued block number at height 0. Prefer `keepRunning` over managing the container by hand.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Applied. This makes sense thanks!

TODO(verify): paste real output from a Nile deploy.

<Callout>
TRON has no gas. Transactions use **bandwidth** (for transaction size) and **energy** (for contract execution). Your account gets some free bandwidth each day. Energy you haven't staked TRX for is paid by burning TRX. The plugin sets a fee limit of 1,000 TRX (`1_000_000_000` SUN) per transaction. That is a cap, not a charge: you pay only for the energy used. TODO(verify): measure the actual energy and TRX cost of this deploy. To raise or lower the cap for a call, pass `{ gasLimit }`; the plugin converts it into a fee limit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A few corrections: free bandwidth is 600 points a day and this deploy is about 6.7 KB, so most of it burns TRX; the fee limit caps only the energy spend; a transaction that exhausts the limit fails with OUT_OF_ENERGY and is still charged up to it; and { gasLimit } can only lower the cap, since the plugin clamps at 1,000 TRX. The energy figure is from simulating this exact bytecode on Nile (1,065,275 energy at 100 sun per energy).

Suggested change
TRON has no gas. Transactions use **bandwidth** (for transaction size) and **energy** (for contract execution). Your account gets some free bandwidth each day. Energy you haven't staked TRX for is paid by burning TRX. The plugin sets a fee limit of 1,000 TRX (`1_000_000_000` SUN) per transaction. That is a cap, not a charge: you pay only for the energy used. TODO(verify): measure the actual energy and TRX cost of this deploy. To raise or lower the cap for a call, pass `{ gasLimit }`; the plugin converts it into a fee limit.
TRON has no gas. Transactions use **bandwidth** (for transaction size) and **energy** (for contract execution). Your account gets 600 free bandwidth points a day; beyond that, and for any energy you haven't staked TRX for, TRX is burned. The plugin sets a fee limit of 1,000 TRX (`1_000_000_000` SUN) per transaction. That is a cap on the energy you burn, not a charge: a transaction that succeeds or reverts pays only for the energy it used, while one that exhausts the cap fails and is charged up to it. Deploying this vault takes about 1.07M energy, roughly 107 TRX if none of it is staked. To lower the cap for a call, pass `{ gasLimit }`; the plugin converts it into a fee limit and never goes above 1,000 TRX.

@stevep0z stevep0z Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Great, thanks for the corrections and providing the simulated figure.

VAULT_ADDRESS=<T... address from deploy.js> npx hardhat run scripts/interact.js --network nile
```

Your USDT balance comes back close to where it started. The vault charged both fees, but the treasury is your own address in this tutorial, so they came straight back. What you actually spent is TRX for energy. Point the treasury at a different address and you would end up lower by the two fees. The redeem worked even though Nile's USDT returns `false` from `transfer`, because `TRC4626` paid it out with `safeTransferChecked`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bandwidth is paid in TRX too once the free daily quota is used up.

Suggested change
Your USDT balance comes back close to where it started. The vault charged both fees, but the treasury is your own address in this tutorial, so they came straight back. What you actually spent is TRX for energy. Point the treasury at a different address and you would end up lower by the two fees. The redeem worked even though Nile's USDT returns `false` from `transfer`, because `TRC4626` paid it out with `safeTransferChecked`.
Your USDT balance comes back close to where it started. The vault charged both fees, but the treasury is your own address in this tutorial, so they came straight back. What you actually spent is TRX for energy and bandwidth. Point the treasury at a different address and you would end up lower by the two fees. The redeem worked even though Nile's USDT returns `false` from `transfer`, because `TRC4626` paid it out with `safeTransferChecked`.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

- Each transaction's **Resources consumed**, showing the energy and bandwidth it used.

<Callout>
Tronscan source verification isn't automated by `hardhat-tron` yet. To verify the contract, upload the source on Tronscan's contract verification page with compiler `0.8.26`, optimizer on with 200 runs, and `viaIR` enabled. TODO(verify): Tronscan's current flow for `viaIR` / standard-JSON input.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tronscan recompiles from the upload and doesn't accept subdirectory layouts, so the project has to be flattened first. Its form has no viaIR setting, which is why dropping viaIR from the config above matters.

Suggested change
Tronscan source verification isn't automated by `hardhat-tron` yet. To verify the contract, upload the source on Tronscan's contract verification page with compiler `0.8.26`, optimizer on with 200 runs, and `viaIR` enabled. TODO(verify): Tronscan's current flow for `viaIR` / standard-JSON input.
Tronscan source verification isn't automated by `hardhat-tron` yet. To verify the contract, flatten it into one file (`npx hardhat flatten contracts/UsdtVault.sol > UsdtVault.flat.sol`) and upload that on Tronscan's contract verification page with compiler `0.8.26` and optimizer on with 200 runs. Tronscan doesn't accept subdirectory layouts or a `viaIR` setting.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Updated.

stevep0z and others added 3 commits October 21, 2026 10:57
Fifteen corrections from the library engineer's review, all verified
locally before applying:

- Mermaid order now matches TRC4626Fees: super._deposit/_withdraw run
  before the fee transfer, so shares mint before the entry fee and the
  receiver is paid before the exit fee.
- Drop viaIR and the metadata block. The project compiles (46 files) and
  passes 11/11 without them, and Tronscan has no viaIR field, so keeping
  them only made the bytecode unverifiable there.
- Pin rationale corrected: tron-solc ships up to 0.8.31, so 0.8.26 is what
  the library is tested with, not a toolchain ceiling.
- safeTransferChecked: the balance check, not USDT's own revert, is what
  catches a token that returns false without moving anything.
- Fee rounding favours the treasury, not the vault; only rounding dust
  stays behind. Test comments corrected to match.
- Custom-error callout: a CREATE2 collision is the out-of-gas case, and
  the panic path is a Panic(uint256) payload, not a selector.
- autoStart: the plugin does prime a node it finds running. The lifecycle
  is skipped entirely when autoStart is false, which is why it breaks.
- Energy: 600 free bandwidth points a day, the fee limit caps energy only
  and is charged in full on OUT_OF_ENERGY, and { gasLimit } can only lower
  it since the plugin clamps at 1,000 TRX. Deploy measured at ~1.07M
  energy, roughly 107 TRX.
- Tronscan verification needs a flattened file; it rejects subdirectories.

Resolves two TODO(verify) markers (energy cost, Tronscan flow) and
confirms the Nile test USDT address. Four remain, all Nile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answers the open question on review comment #1. Probed both tokens over
TronGrid with a constant call to transfer(addr, 0) from a neutral caller:
both return 0x00...00, i.e. false, on a call that succeeds. Both report
the contract name TetherToken with an ABI declaring returns (bool).

Their deployed bytecode is byte-for-byte identical (15,575 bytes,
sha256 2866816060782939f36b0f42...), so the Nile token is the same
contract, not a lookalike.

Removes the TODO(verify) and states the fact, since this is the reason
the Nile run exercises the payout path at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page needs further internal review before it goes in front of the
client, so it leaves this PR and comes back as its own draft later.

Sixteen of its sections are TODO(tool) placeholders for tooling that does
not exist yet (TRC-4626 generator, scaffold, test, deploy, verify, and a
TRON agent skill). Published as-is that list reads as a roadmap, which is
the wrong signal while the TRON library is in maintenance mode.

Removes the page, its nav entry, and the cross-link from the vault
guide's Next steps. The draft is kept outside the repo so it can be
picked up on its own.

This PR is now the vault tutorial and its nav entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@stevep0z
stevep0z marked this pull request as ready for review October 21, 2026 18:25
@stevep0z
stevep0z requested a review from a team as a code owner October 21, 2026 18:25
stevep0z and others added 4 commits October 21, 2026 13:29
The {/* DRAFT ... */} comment at the top was a note to reviewers, not
content. It never rendered, and the page now matches its siblings:
frontmatter, then prose.

Three TODO(verify) markers are still in the body and are unaffected by
this. They are visible text and clear with one Nile deploy session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deployed and exercised the vault on Nile, then read the numbers back off
chain rather than estimating them.

- Faucet: one request returned 2,000 TRX and 2,000 test USDT.
- Deploy output pasted verbatim. Vault at
  TGmwKo2Tz46uPGsy75eAPNfTRtkZkeFNFg, tx faed6c72..., block 72028163.
- Cost measured: 1,145,938 energy for 114.59 TRX, plus 9.62 TRX of
  bandwidth once the free 600 points ran out, 124.22 TRX in total. The
  earlier 107 TRX figure came from an energy-only simulation and was
  about 16% low, which is the bandwidth the review flagged at L799.
- Interact output pasted verbatim. tx.hash is the TVM transaction ID
  Tronscan indexes and .wait() resolves on inclusion, both as on
  Ethereum.
- The run ended on exactly 2,000.000000 USDT, confirming the fee
  accounting: every fee returned because the treasury is the deployer.

Also splits the VAULT_ADDRESS command onto two lines. The single-line
form is correct shell but wraps badly when copied, and the resulting
error says VAULT_ADDRESS is unset right after the reader set it.

No TODO(verify) markers remain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The nileex.io web faucet does not load. DNS resolves and port 443
accepts, but no HTTP response comes back, while api.nileex.io answers
in under 300ms, so the chain is healthy and only the site is down.
Leading a tutorial with a link that hangs is worse than not listing it,
so the web faucet is gone and the bot is the primary route.

Discord is first because it is the one confirmed working here. Telegram
is named as an alternative. Both links checked, both return 200.

Records the real limit: 2,000 of each token per address per 24 hours,
which matches both TRON's own docs and the grant this guide's Nile run
used. Also states what the tutorial actually costs, 124 TRX and 10 USDT,
so a reader can see one grant is ample.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Verified the deployed vault on Nile by hand and hit three fields the
guide never mentioned: Main Contract, ViaIR and VM version. A reader
reaches that form at the very end of the tutorial, having already
deployed, so a missing field there is an expensive place to get stuck.

Lists all six settings in a table and explains the two that actually
decide the outcome. Main Contract is a selector, not a label: the
flattened file holds every inherited contract and Tronscan uses the
name to choose which one to compile. ViaIR must be off to match the
bytecode the config above produces.

The flatten command moves into its own code block rather than sitting
inline in a sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.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