Skip to content

refactor(motoko/icrc2-swap): import the ICRC .did directly (moc 1.13.0 idl:) - #1454

Merged
raymondk merged 3 commits into
masterfrom
motoko-icrc2-swap-generated-bindings
Aug 4, 2026
Merged

refactor(motoko/icrc2-swap): import the ICRC .did directly (moc 1.13.0 idl:)#1454
raymondk merged 3 commits into
masterfrom
motoko-icrc2-swap-generated-bindings

Conversation

@marc0olo

@marc0olo marc0olo commented Jul 28, 2026

Copy link
Copy Markdown
Member

What

Types icrc2-swap's token-ledger calls against the ICRC-1/ICRC-2 Candid interface imported directly via Motoko's idl: import (moc 1.13.0+), replacing the hand-written backend/ICRC.mo type module. No bindings are generated or committed, and no extra tooling (didc) is needed.

import ICRC "idl:../candid/icrc.did";

Update: this PR originally committed a didc-generated bindings/ICRC.mo. moc 1.13.0 (release, docs) now imports a .did directly, so the generated bindings and the didc step are gone — only the committed candid/icrc.did remains.

Why this pattern for ICRC

icrc2-swap holds two ledgers at once (token_a, token_b) and picks the target per call (actor(args.token.toText()) : ICRC.Self). That's the "one interface, many ledgers, runtime-chosen principal" case, which --actor-env-alias/--actor-id-alias (which bind one import to one principal) can't express. The idl: import gives types only (ICRC.Self + named types like ICRC.Account, ICRC.TransferArgs); the backend supplies its own actor(principal) per token. This generalizes to any DeFi-shaped backend that talks to many ICRC ledgers (ICP, ckBTC/ckETH, SNS tokens, …).

This completes the inter-canister taxonomy across the Motoko examples:

Situation Mechanism Example
id fixed & universal --actor-id-alias icp_transfer (#1455)
id varies per environment, injected --actor-env-alias parallel_calls, pub-sub, ic-pos, evm_block_explorer, llm_chatbot
id chosen at runtime / many targets idl: import + actor(id) icrc2-swap (this PR)

Changes

  • candid/icrc.did — the ICRC-1/ICRC-2 interface (focused plain service; types from the ic-icrc1-ledger reference wasm). Committed source of truth, imported directly.
  • backend/app.moimport ICRC "idl:../candid/icrc.did"; types are ICRC.Self/ICRC.Account/ICRC.TransferArgs/…; hand-written ICRC.mo removed.
  • mops.tomlmoc 1.9.0 → 1.13.0 (required for the idl: import); mo:core 2.5.0 → 2.6.0.
  • Unrelated tidy-up in the same example: Map swap (return always discarded) → add.
  • README updated: the interface is imported directly; nothing to regenerate, no didc.

On tooling (cc @Kamirus)

Earlier revisions of this PR leaned on didc bind + a committed bindings/ICRC.mo, and flagged dfinity/candid#729 (PascalCase Motoko output) plus the idea of mops generating bindings as a pre-build step. moc 1.13.0's native .did import largely supersedes that for this use case: the compiler reads the .did directly, applies the PascalCase convention itself, and there's nothing to generate, commit, or install. Only the committed .did remains — which is exactly the "commit the interface, not the generated code" end state we were aiming for.

Testing

bash deploy.sh && bash test.shall 7 tests pass (deposit via icrc2_transfer_from, atomic swap, withdraw via icrc1_transfer with correct fee-adjusted balance deltas), confirming the idl:-imported types are wire-compatible with the real ledger.

🤖 Generated with Claude Code

….did

Replace the hand-written `backend/ICRC.mo` type module with Motoko bindings
generated by `didc` from a committed ICRC-1/ICRC-2 interface, demonstrating
the pattern for a backend that calls *many* ledgers with the ledger chosen at
runtime — the complement to the single-fixed-target `--actor-env-alias`
examples.

- candid/icrc.did      — the ICRC-1/ICRC-2 interface the backend calls
                          (types taken from the ic-icrc1-ledger reference wasm,
                          as a focused plain service). Regen source.
- bindings/ICRC.mo     — `didc bind -t mo candid/icrc.did`; committed so
                          building needs no extra tooling.
- backend/app.mo       — import the generated bindings; `actor(<token>) : ICRC.Self`
                          keeps the runtime-chosen-principal pattern.
- removed backend/ICRC.mo (hand-written types).

Also, unrelated tidy-ups in the same file/example:
- Map `swap` (return value always discarded) -> `add` (`ignore swap`), 6 sites.
- bump mo:core 2.5.0 -> 2.6.0.

Verified end-to-end: `bash deploy.sh && bash test.sh` — all 7 tests pass
(deposit via icrc2_transfer_from, atomic swap, withdraw via icrc1_transfer),
confirming the generated bindings are wire-compatible with the real ledger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@marc0olo
marc0olo requested a review from a team as a code owner July 28, 2026 19:36
@marc0olo
marc0olo requested a review from Copilot July 28, 2026 20:03

Copilot AI 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.

Pull request overview

This PR refactors the motoko/icrc2-swap example to use a committed, didc-generated Motoko binding for the standard ICRC-1/ICRC-2 ledger interface, allowing the backend to type ledgers selected by principal at runtime (actor(<principal>) : ICRC.Self) without maintaining a hand-written interface module.

Changes:

  • Adds a focused candid/icrc.did for the ICRC-1/ICRC-2 method subset used by the example and commits the generated Motoko binding in bindings/ICRC.mo.
  • Updates the backend to import and use ICRC.Self from the generated bindings, while preserving the runtime-selected-ledger pattern.
  • Bumps mo:core from 2.5.0 to 2.6.0 and applies minor map-update tidy-ups (swapadd).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
motoko/icrc2-swap/README.md Updates architecture docs and adds binding-regeneration instructions using didc.
motoko/icrc2-swap/mops.toml Bumps mo:core dependency version.
motoko/icrc2-swap/candid/icrc.did Introduces the committed Candid interface used as the binding source-of-truth.
motoko/icrc2-swap/bindings/ICRC.mo Adds committed, generated Motoko service/types binding (ICRC.Self, records, variants).
motoko/icrc2-swap/backend/app.mo Switches backend to the generated binding import and updates types/usages accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Kamirus

Kamirus commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Yes, I think once dfinity/candid#729 lands it would make sense for mops to adopt this tool.
Mops would handle fetching it via mops toolchain and we could wrap it with mops generate ... command

…0 idl:), drop generated bindings

moc 1.13.0 can import a Candid interface file directly, so the didc-generated
`bindings/ICRC.mo` is no longer needed. Replace it with a native `idl:` import
of the committed interface:

    import ICRC "idl:../candid/icrc.did";

This yields the named types and the service type `ICRC.Self` (snake_case
Candid names → PascalCase automatically); the backend still supplies its own
`actor(<principal>) : ICRC.Self` per token, so the runtime-chosen-ledger
pattern is unchanged.

- mops.toml: moc 1.9.0 -> 1.13.0.
- backend/app.mo: import `idl:../candid/icrc.did` instead of the generated module.
- removed bindings/ICRC.mo (no committed bindings, no didc dependency).
- candid/icrc.did unchanged (still the committed source of truth).
- README updated: the interface is imported directly; nothing to regenerate.

Verified end-to-end: `bash deploy.sh && bash test.sh` — all 7 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@marc0olo marc0olo changed the title refactor(motoko/icrc2-swap): generate ICRC bindings from a committed .did refactor(motoko/icrc2-swap): import the ICRC .did directly (moc 1.13.0 idl:) Aug 4, 2026
@marc0olo
marc0olo requested a lite review from Copilot August 4, 2026 12:47

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (1)

motoko/icrc2-swap/candid/icrc.did:10

  • The header comment still instructs generating Motoko bindings with didc bind and refers to “generated bindings”, which contradicts this PR’s idl: import approach (no bindings/tooling). Updating the comment will avoid confusion for readers following the README guidance.
// The type definitions match the ICRC-1 ledger reference implementation
// (dfinity/ic, ledger-suite-icrc). Generate the Motoko bindings with:
//   didc bind -t mo candid/icrc.did > bindings/ICRC.mo

…did header

The interface file's header still instructed generating Motoko bindings with
`didc bind` — a leftover from before the switch to moc's `idl:` import. Update
it to reflect that the .did is imported directly (no generated bindings, no
didc). Addresses Copilot review feedback on #1454.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@raymondk
raymondk merged commit d83e96f into master Aug 4, 2026
6 checks passed
@raymondk
raymondk deleted the motoko-icrc2-swap-generated-bindings branch August 4, 2026 13:34
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.

4 participants