Skip to content

feat(token): add token crate with generic CIP-56 operations - #27

Open
hubagaspar91 wants to merge 25 commits into
mainfrom
feat/token/init-token-crate
Open

feat(token): add token crate with generic CIP-56 operations#27
hubagaspar91 wants to merge 25 commits into
mainfrom
feat/token/init-token-crate

Conversation

@hubagaspar91

@hubagaspar91 hubagaspar91 commented Aug 2, 2026

Copy link
Copy Markdown

What

This PR adds a new token crate to the workspace. The crate faithfully ports the CIP-56 operation modules from cbtc-lib and removes everything CBTC-specific. The caller now supplies the instrument for every operation.

The crate provides:

  • Free functions for token operations: transfer, accept, reject, cancel offers, split, consolidate, distribute, allocations, and CSV-driven batches.
  • Environment checks: credentials verifies party rights, and dar_check compares installed DAR versions against bundled fixture files.
  • TokenClient, a wrapper that holds the repeated configuration: ledger host, registry URL, instrument, acting party, and Keycloak credentials. It forwards to the free functions and refreshes the access token between calls.

The PR also consolidates integration test between the token crate and the rest of the workspace:

  • Integration tests are named integration_* and marked #[ignore], so cargo test --workspace passes offline.
  • One command runs the live suite: cargo test --workspace -- --ignored --test-threads=1 integration_.
  • Integration tests in every crate read the same environment variables (PARTY_ID_1, KEYCLOAK_URL, and so on), so they can run as one suite. .env.example and a new README section document them.
  • Test suites for token crate base functionality (transfer flows, batches, etc.) is not more comprehensive

Why

The token operations lived in cbtc-lib and were hardcoded to CBTC. Moving them into canton-lib makes them reusable for any CIP-56 instrument.

How tested

  • cargo test --workspace runs the unit tests. They need no network access.
  • cargo test --workspace -- --ignored --test-threads=1 integration_ runs the live suite against devnet with two test parties.

Key decisions

  • The first commit copies the cbtc-lib modules verbatim. Later commits genericize them, so the reviewer can diff the actual code changes in the port.
  • The live integration suite runs single-threaded because the tests mutate shared wallet state - sadly, it's still problematic to run them on CI, as parallel runs can result in falkiness.
  • Introduced a new shared layer to expose CIP-0056 base functionality and cache auth credentials across calls called TokenClient, for easier external use

New dependencies

canton-api-client — generated types and client for the Canton JSON Ledger API; the core of the port.
csv — parses the batch-distribution input files.
zip + semver — dar_check opens DAR archives (they are zip files) and compares their versions.
uuid — generates unique command identifiers.
log / env_logger — logging in the crate and in the test runs.

hubagaspar91 and others added 21 commits July 31, 2026 20:52
Add a new token crate seeded with an unmodified copy of cbtc-lib's
CIP-56 modules so the genericization diff stays reviewable in the
follow-up commits. mint_redeem is excluded as cbtc-specific; the
Holding struct consolidate.rs needs is lifted from its models into
holding.rs. Filtering still hardcodes cbtc at this point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K2Sr8YQFWVULxp3kTG8V8
Replace the hardcoded cbtc ACS filters with checks against an
InstrumentId supplied by the caller, threaded through the param
structs that fetch holdings or pending transfers. The instrument id
is matched case-insensitively and the admin party exactly: the admin
is always known to callers (it is a registry-API input, not something
queried from it), and without it holdings of a same-ticker token from
another admin would be selected as inputs and fail at expectedAdmin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K2Sr8YQFWVULxp3kTG8V8
Reword doc comments and examples for any token; examples switch to
the token crate name and neutral placeholder ids. The two no_run
examples become ignore: they never compiled in cbtc-lib either
(nonexistent withdraw module, top-level await), matching the ignore
fences the other examples already use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K2Sr8YQFWVULxp3kTG8V8
Live integration tests read INSTRUMENT_ID and DECENTRALIZED_PARTY_ID
from the environment instead of hardcoding CBTC devnet values, in
line with the .env convention the other crates' live tests use.
Pure unit-test fixtures keep their original literals to keep the
diff against cbtc-lib minimal. The DAR-scanning test gets vendored
token-standard and utility DARs as its fixture directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K2Sr8YQFWVULxp3kTG8V8
Thin convenience client bound to one token, party, and participant.
It caches the Keycloak credentials (auto-refreshing the access token
via transfer::TokenState) and the instrument config, and forwards to
the free functions in the operation modules. Purely additive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K2Sr8YQFWVULxp3kTG8V8
TokenClient::send takes an optional chrono::Duration for the offer's
execute-before window. The one-week default stays when the caller
passes None.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019K2Sr8YQFWVULxp3kTG8V8
Records the approved design: two test suites, the env-driven
IntegrationTestState fixture, the three client-level flows, and the
accepted assumptions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2oS4Ke7N1pBVgjqdCeEzv
The 17 env-driven tests fail in a plain checkout, which hides real
regressions. Marking them ignored makes 'cargo test --workspace' a
pure unit suite that always runs green. Each test still runs when
named explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2oS4Ke7N1pBVgjqdCeEzv
Adds three env-driven flows against devnet: offer/accept round trip,
cancel and reject, and distribute with cleanup. Tests tag transfers
with a per-run uuid reference, so ACS checks only see their own
contracts and a successful run restores both wallet balances.

The tests are ignored and name-prefixed, which splits the workspace
into two suites: 'cargo test --workspace' for units, and
'cargo test --workspace -- --ignored --test-threads=1 integration_'
for live tests. A static lock also serializes the tests themselves,
so they never mutate the shared wallets concurrently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2oS4Ke7N1pBVgjqdCeEzv
The consolidate, split and credential live tests used their own env
var names and raw module calls. The consolidate and split flows now
run through TokenClient in client.rs, next to the other client-level
integration tests, with balance and UTXO assertions. The credential
tests stay in credentials.rs, because TokenClient has no credential
methods, but they now use IntegrationTestState and SERIAL_LOCK.
Party 1 is the acting party everywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q3gjQvX6nBshnhoa2RSg25
…roved clarity and environment variable handling
…en mining rounds with improved clarity and environment variable handling
@hubagaspar91
hubagaspar91 requested a review from Copilot August 2, 2026 05:20
@socket-security

socket-security Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedzip@​2.4.28210093100100
Addedcsv@​1.4.09310093100100
Addedsemver@​1.0.2810010093100100

View full report

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

This PR introduces a new token crate to the workspace that provides reusable, instrument-agnostic CIP-56 token operations and a convenience TokenClient, and it standardizes how live integration tests are organized and configured across the workspace.

Changes:

  • Added the new crates/token crate with CIP-56 operation modules (transfer, accept/reject/cancel, split/consolidate, distribute/batch, allocation, credentials, dar_check) plus a TokenClient wrapper.
  • Consolidated live integration test conventions (naming, #[ignore], shared env var scheme, .env loading) across ledger, registry, and wallet.
  • Updated workspace dependencies and developer documentation/examples to support the new crate and test workflow.

Reviewed changes

Copilot reviewed 37 out of 58 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
README.md Documents unit vs live integration test workflows and required env vars.
crates/wallet/src/mining_rounds.rs Renames live test module, marks test ignored, switches to standardized env vars and .env loading.
crates/wallet/src/amulet_rules.rs Same integration test standardization for wallet rules query.
crates/token/src/utils.rs Adds helpers for amount extraction and fetching incoming/outgoing transfer offers; includes typed response fixtures for parser unit tests.
crates/token/src/transfer.rs Implements transfer submission plus sequential chained transfers and response parsing; includes token refresh state and parser tests.
crates/token/src/test_utils.rs Adds shared integration test setup/state and helper functions for token crate live tests.
crates/token/src/split.rs Implements split (merge-split self-transfer) and response parsing plus parser unit tests.
crates/token/src/reject.rs Adds receiver-side rejection operation with direct registry reject choice-context fetch.
crates/token/src/lib.rs Exposes token crate modules and re-exports TokenClient types.
crates/token/src/holding.rs Adds a typed Holding parser and lock-check helper for holding contracts.
crates/token/src/event_helpers.rs Centralizes event variant matching for generated Ledger API event types.
crates/token/src/distribute.rs Implements multi-recipient distribution via sequential chained transfers with optional callbacks and reference IDs.
crates/token/src/dar_check.rs Adds DAR scanning/version selection and participant package presence checks (plus unit tests).
crates/token/src/credentials.rs Adds credential/offer listing and acceptance helpers, plus live integration tests and parser tests.
crates/token/src/consolidate.rs Adds UTXO consolidation/check-and-consolidate operations with parser tests.
crates/token/src/client.rs Implements TokenClient wrapper that refreshes auth between calls and forwards to operation modules; includes live integration tests.
crates/token/src/cancel_offers.rs Adds withdraw/cancel operations including batched and “withdraw all” flows.
crates/token/src/batch.rs Adds CSV-driven batch distribution plus a live integration test.
crates/token/src/allocation.rs Adds CIP-56 allocation operations and unit tests around choice wiring.
crates/token/src/active_contracts.rs Adds holdings query helper scoped to an instrument, plus integration test.
crates/token/src/accept.rs Adds accept and accept-all flows (with batching) for pending transfer offers.
crates/token/Cargo.toml Declares the new token crate and its dependencies/dev-dependencies.
crates/registry/src/transfer_factory.rs Updates live test to standardized env vars and makes it instrument-agnostic.
crates/ledger/src/websocket/update.rs Renames and ignores live websocket test; standardizes auth/env vars and adds timeout semantics.
crates/ledger/src/websocket/active_contracts.rs Same live websocket ACS test standardization and adds non-empty assertion.
crates/ledger/src/submit.rs Formatting-only tweak in a unit test.
crates/ledger/src/ledger_end.rs Renames and ignores live test; standardizes client-credentials env vars and .env loading.
crates/ledger/src/common.rs Formatting-only changes in identifier-filter conversion and tests.
crates/ledger/src/active_contracts.rs Standardizes live test module naming/env vars and improves assertions; minor log formatting.
crates/examples/src/delete_executed_transfers.rs Formatting-only import cleanup.
crates/common/src/transfer.rs Formatting-only test assertion change.
crates/common/src/transfer_factory.rs Formatting-only changes in tests.
crates/common/src/submission.rs Formatting-only attribute formatting.
crates/common/src/filters.rs Formatting-only changes mirroring ledger/common conversion tests.
crates/common/src/decimal.rs Formatting-only changes in decimal tests.
Cargo.toml Adds crates/token to the workspace and introduces new shared workspace dependencies (canton-api-client, csv, semver, zip, uuid, log, env_logger, etc.).
Cargo.lock Locks dependency graph including new token crate dependencies.
.env.example Updates and standardizes integration test environment variables and documentation.

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

Comment thread crates/token/src/transfer.rs
Comment thread crates/token/src/transfer.rs Outdated
Comment thread crates/token/src/split.rs
Comment thread crates/token/src/event_helpers.rs Outdated
result.value(),
Decimal::from_str("0.0152415788").unwrap()
);
assert_eq!(result.value(), Decimal::from_str("0.0152415788").unwrap());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see a lot of whitespace changes. Was clippy/rustfmt not configured correctly until now and this is the correct one, or is your machine misconfigured? This lib should have strict formatting rules like all other repos, I wonder how this would happen.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Claude says, that the project uses rust fmt defaults, which are pinned by:

  • edition = "2024" in Cargo.toml
  • channel = "1.94.1" in rust-toolchain.toml

So unless others working on a project have a user-level rustfmt.toml, the formatting changes are simply due to the fact that noone really ran fmt for some time.

@hubagaspar91
hubagaspar91 force-pushed the feat/token/init-token-crate branch from 988c5fd to 3c95cf4 Compare August 5, 2026 05:27
@hubagaspar91
hubagaspar91 marked this pull request as ready for review August 5, 2026 10:05
@hubagaspar91
hubagaspar91 requested review from a team, scolear and sosaucily August 5, 2026 10:05
Comment thread README.md
|----------|---------|
| `PARTY_ID_1` | First test party |
| `PARTY_ID_2` | Second test party |
| `DECENTRALIZED_PARTY_ID` | Registrar (admin) of the instrument |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if we should rename this (with some graceful fallback deprecation warning period) to INSTRUMENT_ADMIN, now that it's not necessarily a decparty at all, if this library can be used with any CIP56 token.

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.

3 participants