refactor(motoko/icrc2-swap): import the ICRC .did directly (moc 1.13.0 idl:) - #1454
Conversation
….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>
There was a problem hiding this comment.
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.didfor the ICRC-1/ICRC-2 method subset used by the example and commits the generated Motoko binding inbindings/ICRC.mo. - Updates the backend to import and use
ICRC.Selffrom the generated bindings, while preserving the runtime-selected-ledger pattern. - Bumps
mo:corefrom2.5.0to2.6.0and applies minor map-update tidy-ups (swap→add).
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.
|
Yes, I think once dfinity/candid#729 lands it would make sense for mops to adopt this tool. |
…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>
idl:)
There was a problem hiding this comment.
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 bindand refers to “generated bindings”, which contradicts this PR’sidl: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>
What
Types
icrc2-swap's token-ledger calls against the ICRC-1/ICRC-2 Candid interface imported directly via Motoko'sidl:import (moc 1.13.0+), replacing the hand-writtenbackend/ICRC.motype module. No bindings are generated or committed, and no extra tooling (didc) is needed.Why this pattern for ICRC
icrc2-swapholds 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. Theidl:import gives types only (ICRC.Self+ named types likeICRC.Account,ICRC.TransferArgs); the backend supplies its ownactor(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:
--actor-id-alias--actor-env-aliasidl:import +actor(id)Changes
candid/icrc.did— the ICRC-1/ICRC-2 interface (focused plain service; types from theic-icrc1-ledgerreference wasm). Committed source of truth, imported directly.backend/app.mo—import ICRC "idl:../candid/icrc.did"; types areICRC.Self/ICRC.Account/ICRC.TransferArgs/…; hand-writtenICRC.moremoved.mops.toml—moc1.9.0 → 1.13.0 (required for theidl:import);mo:core2.5.0 → 2.6.0.swap(return always discarded) →add.didc.On tooling (cc @Kamirus)
Earlier revisions of this PR leaned on
didc bind+ a committedbindings/ICRC.mo, and flagged dfinity/candid#729 (PascalCase Motoko output) plus the idea ofmopsgenerating bindings as a pre-build step. moc 1.13.0's native.didimport largely supersedes that for this use case: the compiler reads the.diddirectly, applies the PascalCase convention itself, and there's nothing to generate, commit, or install. Only the committed.didremains — which is exactly the "commit the interface, not the generated code" end state we were aiming for.Testing
bash deploy.sh && bash test.sh— all 7 tests pass (deposit viaicrc2_transfer_from, atomic swap, withdraw viaicrc1_transferwith correct fee-adjusted balance deltas), confirming theidl:-imported types are wire-compatible with the real ledger.🤖 Generated with Claude Code