Skip to content

feat: add USDCx to supported tokens config#229

Merged
salindne merged 2 commits intofeat/218-unified-balance-queryfrom
feat/213-usdcx-config
Apr 23, 2026
Merged

feat: add USDCx to supported tokens config#229
salindne merged 2 commits intofeat/218-unified-balance-queryfrom
feat/213-usdcx-config

Conversation

@salindne
Copy link
Copy Markdown
Contributor

Summary

Add USDCx (USD Coin, 6 decimals) to supported_tokens in all config YAML files:

  • e2e-local, docker, local-devnet, mainnet configs
  • Test fixture configs

This enables:

  • MetaMask can add USDCx by contract address (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
  • eth_call for symbol(), decimals(), name() returns correct USDCx values
  • USDCx appears in the non-custodial transfer allowlist (auto-derived from supported_tokens)

Mainnet uses ${CANTON_USDCX_INSTRUMENT_ADMIN} env var for Circle's Bridge-Operator party.

Depends on #228 (unified balance query) and #227 (instrument_id config)
Closes #213

Test plan

  • go build ./... passes
  • Config validation tests pass (all YAML files load correctly)
  • Lint: 0 issues

Add USDCx (USD Coin, 6 decimals) to supported_tokens in all config
YAML files. This enables MetaMask to query balanceOf, symbol, decimals,
and name for USDCx through the ERC-20 facade.

USDCx is automatically added to the non-custodial transfer allowlist
since it is derived from supported_tokens.

Mainnet config uses ${CANTON_USDCX_INSTRUMENT_ADMIN} env var for the
instrument admin (Circle's Bridge-Operator party).

Closes #213
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request adds configuration for the USDCx token across multiple environment and test configuration files, including the addition of the CANTON_USDCX_INSTRUMENT_ADMIN environment variable in tests. The review feedback suggests adding the instrument_admin field to config.e2e-local.yaml and config.api-server.docker.yaml to ensure consistency with other environment configurations where this field is explicitly defined.

Comment thread config.e2e-local.yaml
Comment thread pkg/config/defaults/config.api-server.docker.yaml
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 19, 2026

Codecov Report

❌ Patch coverage is 0% with 148 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.53%. Comparing base (1e2594f) to head (61c6eab).

Files with missing lines Patch % Lines
pkg/cantonsdk/token/client.go 0.00% 80 Missing ⚠️
pkg/cantonsdk/token/registry_client.go 0.00% 54 Missing ⚠️
pkg/cantonsdk/values/meta.go 0.00% 7 Missing ⚠️
pkg/cantonsdk/client/client.go 0.00% 4 Missing ⚠️
pkg/cantonsdk/token/options.go 0.00% 2 Missing ⚠️
pkg/cantonsdk/token/encode.go 0.00% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (0.00%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@                        Coverage Diff                         @@
##           feat/218-unified-balance-query     #229      +/-   ##
==================================================================
- Coverage                           32.90%   32.53%   -0.38%     
==================================================================
  Files                                 123      124       +1     
  Lines                                8551     8643      +92     
==================================================================
- Hits                                 2814     2812       -2     
- Misses                               5489     5582      +93     
- Partials                              248      249       +1     
Flag Coverage Δ
unittests 32.53% <0.00%> (-0.38%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/cantonsdk/token/config.go 0.00% <ø> (ø)
pkg/cantonsdk/token/encode.go 0.00% <0.00%> (ø)
pkg/cantonsdk/token/options.go 0.00% <0.00%> (ø)
pkg/cantonsdk/client/client.go 0.00% <0.00%> (ø)
pkg/cantonsdk/values/meta.go 0.00% <0.00%> (ø)
pkg/cantonsdk/token/registry_client.go 0.00% <0.00%> (ø)
pkg/cantonsdk/token/client.go 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

symbol: "DEMO"
decimals: 18
instrument_id: "DEMO"
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48":
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.

Are we able to test this on local yet?

…sfers (#230)

Add support for transferring tokens issued by external parties (e.g.,
USDCx from Circle) by routing factory discovery based on InstrumentAdmin:

- Local tokens (InstrumentAdmin == IssuerParty): existing ACS query
- External tokens: POST to Transfer Factory Registry API

Key changes:
- New RegistryClient (HTTP POST) calls Splice Transfer Factory Registry
  and parses factoryId, choiceContext, disclosedContracts from response
- resolveTransferFactory routes by InstrumentAdmin automatically
- DisclosedContracts from registry response threaded through
  PrepareSubmissionRequest for both custodial and non-custodial paths
- EncodeExtraArgs now accepts choiceContext from registry response
- ReadAs conditionally set: IssuerParty for local, empty for external
- ExternalTokens config maps InstrumentAdmin → registry URL
- WithRegistryClient option on token client constructor

No breaking changes to public API (TransferByPartyID, PrepareTransfer).
Routing is internal based on holdings data.

Closes #214
@salindne salindne merged commit 757baeb into feat/218-unified-balance-query Apr 23, 2026
2 checks passed
@salindne salindne deleted the feat/213-usdcx-config branch April 23, 2026 16:46
salindne added a commit that referenced this pull request Apr 23, 2026
* feat: unified balance query via Splice HoldingV1 interface

Add GetHoldingsByParty method that queries Canton using the Splice
HoldingV1 interface as the user's own party, instead of querying
CIP56Holding template as IssuerParty.

This enables visibility of tokens issued by third parties (e.g., USDCx
from Circle) where our IssuerParty is not a stakeholder on the holdings.

GetHoldings now delegates to GetHoldingsByParty. GetAllHoldings retains
the IssuerParty/template path for totalSupply and indexer use.

Closes #218

* docs: clarify that CreateArguments is always populated for interface queries

Canton Ledger API v2 proto marks create_arguments as Required on
CreatedEvent, so decodeHolding works identically for both template-based
and interface-based queries. Add comment to address review concern.

* chore: add TODO for GetHoldingsByParty unit tests

* feat: add USDCx to supported tokens config (#229)

* feat: add USDCx to supported tokens config

Add USDCx (USD Coin, 6 decimals) to supported_tokens in all config
YAML files. This enables MetaMask to query balanceOf, symbol, decimals,
and name for USDCx through the ERC-20 facade.

USDCx is automatically added to the non-custodial transfer allowlist
since it is derived from supported_tokens.

Mainnet config uses ${CANTON_USDCX_INSTRUMENT_ADMIN} env var for the
instrument admin (Circle's Bridge-Operator party).

Closes #213

* feat: integrate Transfer Factory Registry API for external token transfers (#230)

Add support for transferring tokens issued by external parties (e.g.,
USDCx from Circle) by routing factory discovery based on InstrumentAdmin:

- Local tokens (InstrumentAdmin == IssuerParty): existing ACS query
- External tokens: POST to Transfer Factory Registry API

Key changes:
- New RegistryClient (HTTP POST) calls Splice Transfer Factory Registry
  and parses factoryId, choiceContext, disclosedContracts from response
- resolveTransferFactory routes by InstrumentAdmin automatically
- DisclosedContracts from registry response threaded through
  PrepareSubmissionRequest for both custodial and non-custodial paths
- EncodeExtraArgs now accepts choiceContext from registry response
- ReadAs conditionally set: IssuerParty for local, empty for external
- ExternalTokens config maps InstrumentAdmin → registry URL
- WithRegistryClient option on token client constructor

No breaking changes to public API (TransferByPartyID, PrepareTransfer).
Routing is internal based on holdings data.

Closes #214
salindne added a commit that referenced this pull request Apr 28, 2026
…#227)

* feat: add instrument_id to token config and splice_holding_package_id

Add InstrumentID and InstrumentAdmin fields to ERC20Token struct
for per-token Canton instrument mapping. InstrumentID is required;
InstrumentAdmin defaults to IssuerParty when empty.

Add SpliceHoldingPackageID to cantonsdk/token.Config for Splice
HoldingV1 interface queries (needed by #218 unified balance query).

Update all config YAML files (defaults, test fixtures, e2e-local,
devnet) with instrument_id per token and splice_holding_package_id.

Closes #212

* fix: pass stack.Token by pointer in WaitForAPIBalance to fix hugeParam lint

* fix: use env vars for mainnet splice_holding_package_id, add test env

Address review feedback:
- Use ${CANTON_SPLICE_HOLDING_PACKAGE_ID} env var in mainnet configs
  (consistent with other package ID fields)
- Add CANTON_SPLICE_HOLDING_PACKAGE_ID to setDefaultConfigEnv test helper

* fix: update remaining WaitForAPIBalance callers to pass Token by pointer

* feat: unified balance query via Splice HoldingV1 interface (#228)

* feat: unified balance query via Splice HoldingV1 interface

Add GetHoldingsByParty method that queries Canton using the Splice
HoldingV1 interface as the user's own party, instead of querying
CIP56Holding template as IssuerParty.

This enables visibility of tokens issued by third parties (e.g., USDCx
from Circle) where our IssuerParty is not a stakeholder on the holdings.

GetHoldings now delegates to GetHoldingsByParty. GetAllHoldings retains
the IssuerParty/template path for totalSupply and indexer use.

Closes #218

* docs: clarify that CreateArguments is always populated for interface queries

Canton Ledger API v2 proto marks create_arguments as Required on
CreatedEvent, so decodeHolding works identically for both template-based
and interface-based queries. Add comment to address review concern.

* chore: add TODO for GetHoldingsByParty unit tests

* feat: add USDCx to supported tokens config (#229)

* feat: add USDCx to supported tokens config

Add USDCx (USD Coin, 6 decimals) to supported_tokens in all config
YAML files. This enables MetaMask to query balanceOf, symbol, decimals,
and name for USDCx through the ERC-20 facade.

USDCx is automatically added to the non-custodial transfer allowlist
since it is derived from supported_tokens.

Mainnet config uses ${CANTON_USDCX_INSTRUMENT_ADMIN} env var for the
instrument admin (Circle's Bridge-Operator party).

Closes #213

* feat: integrate Transfer Factory Registry API for external token transfers (#230)

Add support for transferring tokens issued by external parties (e.g.,
USDCx from Circle) by routing factory discovery based on InstrumentAdmin:

- Local tokens (InstrumentAdmin == IssuerParty): existing ACS query
- External tokens: POST to Transfer Factory Registry API

Key changes:
- New RegistryClient (HTTP POST) calls Splice Transfer Factory Registry
  and parses factoryId, choiceContext, disclosedContracts from response
- resolveTransferFactory routes by InstrumentAdmin automatically
- DisclosedContracts from registry response threaded through
  PrepareSubmissionRequest for both custodial and non-custodial paths
- EncodeExtraArgs now accepts choiceContext from registry response
- ReadAs conditionally set: IssuerParty for local, empty for external
- ExternalTokens config maps InstrumentAdmin → registry URL
- WithRegistryClient option on token client constructor

No breaking changes to public API (TransferByPartyID, PrepareTransfer).
Routing is internal based on holdings data.

Closes #214

* remove unused InstrumentAdmin field from ERC20Token

The field was never read anywhere in the codebase. Transfer-factory routing
uses InstrumentAdmin from the on-chain holding contract, not config. External
tokens are handled via cantonsdk/token.Config.ExternalTokens keyed by admin
party ID after #228.

Removes the field, the instrument_admin yaml keys in local-devnet and mainnet
configs, and the CANTON_USDCX_INSTRUMENT_ADMIN env var from config tests.

* fix(e2e): set splice_holding_package_id in canton shim token configs

PR 228 made SpliceHoldingPackageID required on cantonsdk/token.Config.
The e2e canton shim was missing it on both the DEMO and PROMPT token
clients, causing every test to fail with 'splice_holding_package_id is
required' at api/full stack init.

Adds the constant alongside the other devnet package IDs (matches the
value in config.api-server.local-devnet.yaml) and wires it into both
token.Config instances.
salindne added a commit that referenced this pull request Apr 29, 2026
* feat: add instrument_id to token config and splice_holding_package_id

Add InstrumentID and InstrumentAdmin fields to ERC20Token struct
for per-token Canton instrument mapping. InstrumentID is required;
InstrumentAdmin defaults to IssuerParty when empty.

Add SpliceHoldingPackageID to cantonsdk/token.Config for Splice
HoldingV1 interface queries (needed by #218 unified balance query).

Update all config YAML files (defaults, test fixtures, e2e-local,
devnet) with instrument_id per token and splice_holding_package_id.

Closes #212

* fix: pass stack.Token by pointer in WaitForAPIBalance to fix hugeParam lint

* fix: use env vars for mainnet splice_holding_package_id, add test env

Address review feedback:
- Use ${CANTON_SPLICE_HOLDING_PACKAGE_ID} env var in mainnet configs
  (consistent with other package ID fields)
- Add CANTON_SPLICE_HOLDING_PACKAGE_ID to setDefaultConfigEnv test helper

* fix: update remaining WaitForAPIBalance callers to pass Token by pointer

* feat(indexer): support external token indexing

* updated comment

* review fixes

* review fixes

* feat: unified balance query via Splice HoldingV1 interface (#228)

* feat: unified balance query via Splice HoldingV1 interface

Add GetHoldingsByParty method that queries Canton using the Splice
HoldingV1 interface as the user's own party, instead of querying
CIP56Holding template as IssuerParty.

This enables visibility of tokens issued by third parties (e.g., USDCx
from Circle) where our IssuerParty is not a stakeholder on the holdings.

GetHoldings now delegates to GetHoldingsByParty. GetAllHoldings retains
the IssuerParty/template path for totalSupply and indexer use.

Closes #218

* docs: clarify that CreateArguments is always populated for interface queries

Canton Ledger API v2 proto marks create_arguments as Required on
CreatedEvent, so decodeHolding works identically for both template-based
and interface-based queries. Add comment to address review concern.

* chore: add TODO for GetHoldingsByParty unit tests

* feat: add USDCx to supported tokens config (#229)

* feat: add USDCx to supported tokens config

Add USDCx (USD Coin, 6 decimals) to supported_tokens in all config
YAML files. This enables MetaMask to query balanceOf, symbol, decimals,
and name for USDCx through the ERC-20 facade.

USDCx is automatically added to the non-custodial transfer allowlist
since it is derived from supported_tokens.

Mainnet config uses ${CANTON_USDCX_INSTRUMENT_ADMIN} env var for the
instrument admin (Circle's Bridge-Operator party).

Closes #213

* feat: integrate Transfer Factory Registry API for external token transfers (#230)

Add support for transferring tokens issued by external parties (e.g.,
USDCx from Circle) by routing factory discovery based on InstrumentAdmin:

- Local tokens (InstrumentAdmin == IssuerParty): existing ACS query
- External tokens: POST to Transfer Factory Registry API

Key changes:
- New RegistryClient (HTTP POST) calls Splice Transfer Factory Registry
  and parses factoryId, choiceContext, disclosedContracts from response
- resolveTransferFactory routes by InstrumentAdmin automatically
- DisclosedContracts from registry response threaded through
  PrepareSubmissionRequest for both custodial and non-custodial paths
- EncodeExtraArgs now accepts choiceContext from registry response
- ReadAs conditionally set: IssuerParty for local, empty for external
- ExternalTokens config maps InstrumentAdmin → registry URL
- WithRegistryClient option on token client constructor

No breaking changes to public API (TransferByPartyID, PrepareTransfer).
Routing is internal based on holdings data.

Closes #214

* remove unused InstrumentAdmin field from ERC20Token

The field was never read anywhere in the codebase. Transfer-factory routing
uses InstrumentAdmin from the on-chain holding contract, not config. External
tokens are handled via cantonsdk/token.Config.ExternalTokens keyed by admin
party ID after #228.

Removes the field, the instrument_admin yaml keys in local-devnet and mainnet
configs, and the CANTON_USDCX_INSTRUMENT_ADMIN env var from config tests.

* fix(e2e): set splice_holding_package_id in canton shim token configs

PR 228 made SpliceHoldingPackageID required on cantonsdk/token.Config.
The e2e canton shim was missing it on both the DEMO and PROMPT token
clients, causing every test to fail with 'splice_holding_package_id is
required' at api/full stack init.

Adds the constant alongside the other devnet package IDs (matches the
value in config.api-server.local-devnet.yaml) and wires it into both
token.Config instances.

* usdcx contracts deployments on local docker setup

* usdcx contracts deployments on local docker setup

* usdcx contracts deployments on local docker setup

* feat(cantonsdk/token): add TransferInternalByPartyID for internal Canton party transfers

Closes #247

* feat(e2e): add USDCx cross-participant transfer test and multi-participant devstack

* fix(e2e): lint cleanup in Canton2Shim and types

- gofmt canton2.go imports
- drop unused-receiver names in stub methods
- nosec annotation on USDCxTokenVirtualAddr (public EVM address, not a credential)

* health check added for p2

* fix(ci): fix P2 package vetting race in USDCx bootstrap

Use CMD-SHELL list form for canton healthcheck test to avoid YAML fold
ambiguity, and add a 10s vetting propagation wait to the P2 bootstrap
section — mirroring the equivalent wait already done for P1 — so that
bootstrap-usdcx runs only after P2 packages are fully vetted on the
synchronizer.

---------

Co-authored-by: Sebastian Lindner <33971232+salindne@users.noreply.github.com>
Co-authored-by: Arun Dhyani <dhyaniarun7@gmail.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.

3 participants