This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Run all tests (Cadence + Go)
make test
# Run only Cadence tests (faster, preferred for contract changes)
flow test --cover --covercode="contracts" tests/*.cdc
# Run only Go tests
cd lib/go && make test
# Run a single Go test by name
cd lib/go/test && go test -v -run TestTokenForwarding
# Regenerate Go assets after changing any .cdc file (required before Go tests will reflect contract changes)
cd lib/go && make generateThe make generate step embeds .cdc files as Go byte arrays via go-bindata. If you edit a contract or transaction and then run Go tests without regenerating, the tests will use stale code.
The .cdc files in contracts/ and transactions/ are the canonical source. They are used by both:
- Cadence tests (
tests/*.cdc) — run directly byflow testagainst the Flow testing framework. These are the primary tests and easiest to write/read. All new tests should preferably be written in Cadence. - Go tests (
lib/go/test/) — usego-bindata-embedded copies of the same.cdcfiles (vialib/go/contracts/internal/assets/). The Go layer also has template helpers inlib/go/templates/that inject contract addresses into transaction/script strings at test time.
All new tests should be written in Cadence unless an old Go test can be easily modified.
If any changes are made to any of the Cadence code, all the tests in make test should pass before finishing. make ci should also pass before finishing any tasks.
FungibleToken.cdc is a contract interface (not a concrete contract). It defines the Vault resource interface, Withdraw entitlement, and standard events (Withdrawn, Deposited, Burned). Crucially, it enforces pre/post conditions on withdraw and deposit at the interface level — implementations get these for free.
ExampleToken.cdc is the reference implementation of FungibleToken. It is the token used in all tests and transaction templates.
Burner.cdc is a standalone utility that provides the burn() function and Burnable interface. Vaults implement burnCallback() (called by Burner.burn()) to update total supply when tokens are destroyed. Direct destroy on a vault is not the correct pattern — always use Burner.burn().
TokenForwarding.cdc— AForwarderresource that implementsFungibleToken.Receiverand forwards all deposits to a configured recipient capability. Used to redirect tokens transparently.PrivateReceiverForwarder.cdc— LikeTokenForwardingbut thedepositisaccess(contract), so only a co-deployedSenderresource (held by an admin) can push tokens in. Used for privacy-preserving airdrops.FungibleTokenSwitchboard.cdc— ASwitchboardresource that acts as a singleReceivercapable of routing deposits to multiple underlying vaults by vault type. The generic receiver path/public/GenericFTReceiverpoints here.FungibleTokenMetadataViews.cdc— Defines theFTView,FTDisplay,FTVaultData, andTotalSupplymetadata view structs.FTVaultDatais particularly important: it carries storage/public paths and acreateEmptyVaultfunction, enabling generic account setup transactions without importing the token contract directly.
flow.json maps contract names to source files and network aliases (deployed addresses). In Cadence source, contracts are imported by name string (e.g. import "FungibleToken"). The Flow CLI resolves these names to addresses at deploy/test time using the aliases. The testing network alias points to 0000000000000007 (the test account). When adding a new contract, it needs an entry in both flow.json contracts and deployments sections.
imports/ contains pinned copies of external contracts (MetadataViews, ViewResolver, FlowToken, etc.) fetched via flow dependencies. These are resolved by flow.json aliases and should not be edited manually.
transactions/generic_transfer_with_address.cdc and transactions/generic_transfer_with_paths.cdc allow transferring any FT without a token-specific import. They read FTVaultData from the contract's metadata views to find the correct storage/receiver paths. generic_transfer_with_address.cdc includes a post-withdraw type assertion to guard against malicious tokens returning incorrect metadata (see contracts/test/MaliciousToken.cdc for the attack this prevents).
- Vault deposit: Always force-cast the incoming vault to the concrete type before incrementing balance:
let vault <- from as! @ExampleToken.Vault. The interface pre-condition already type-checks, so the cast is guaranteed to succeed. - Capability access control: Use Cadence entitlements (
access(Owner),access(FungibleToken.Withdraw)) rather than capability types alone to restrict sensitive functions. - Account setup: The recommended setup transaction is
transactions/metadata/setup_account_from_address.cdc, which usesFTVaultDatato set up an account for any token without importing that token's contract.
- If something goes sideways, STOP and re-plan immediately - don't keep pushing
- Write detailed specs upfront to reduce ambiguity
- Offload research, exploration, and parallel analysis to subagents
- For complex problems, throw more compute at it via subagents
- One task per subagent for focused execution
- After ANY correction from the user: update 'tasks/lessons.md' with the pattern
- Write rules for yourself that prevent the same mistake
- Ruthlessly iterate on these lessons until mistake rate drops
- Review lessons at session start for relevant project
- Never mark a task complete without proving it works
- Diff behavior between main and your changes when relevant
- Ask yourself: "Would a staff engineer approve this?"
- Run tests, check logs, demonstrate correctness
- For non-trivial changes: pause and ask "is there a more elegant way?"
- If a fix feels hacky: "Knowing everything I know now, implement the elegant solution"
- Skip this for simple, obvious fixes - don't over-engineer
- Challenge your own work before presenting it
- When fixing a big, point at logs, errors, failing tests -> then resolve them
- Zero context switching required from the user
- Go fix failing CI tests without being told how
- Plan First: Write plan to 'tasks/todo.md' with checkable items
- Verify Plan: Check in before starting implementation
- Track Progress: Mark items complete as you go
- Explain Changes: High-level summary at each step
- Document Results: Add review to 'tasks/todo.md'
- Capture Lessons: Update 'tasks/lessons.md' after corrections
- Simplicity First: Make every change as simple as possible. Impact minimal code.
- No Laziness: Find root causes. No temporary fixes. Senior developer standards.
- Minimal Impact: Changes should only touch what's necessary. Avoid introducing bugs.