Skip to content

Releases: ChainSafe/canton-middleware

v0.5.3

12 Jun 15:01
6e07f1e

Choose a tag to compare

0.5.3 (2026-06-12)

Bug Fixes

  • ethrpc: persist failed transaction receipts as status=0 (#309) (6dfca79)

v0.5.2

10 Jun 11:18
79a861d

Choose a tag to compare

0.5.2 (2026-06-09)

Bug Fixes

  • relayer: chunk eth log queries and honor configured start block (#306) (b0dc75f)

v0.5.1

08 Jun 14:52
ef8e454

Choose a tag to compare

0.5.1 (2026-06-08)

Bug Fixes

  • ethrpc: report zero native balance and gas (#304) (6d552df)
  • run all server goroutines under one errgroup (#303) (16ffa3e)
  • serve /health at the configured health_check_url (#301) (9a5ccbd)
  • stop running balance reconciler in the API server (#300) (f9de530)

v0.5.0

08 Jun 07:00
90bc5b4

Choose a tag to compare

0.5.0 (2026-06-08)

Features

  • api and indexer instrumented with metrics (#221) (6dd9d7d)
  • ci: add release-please workflow with reusable docker build (#288) (1f95bf2)
  • ci: use GraphQL createCommitOnBranch for signed commits (#286) (38ad918)
  • metric: instrument submitter (#295) (57602c1)
  • metrics: eth rpc instrumented (#297) (9a0044e)
  • metrics: instrument accept worker (#299) (661d817)

Bug Fixes

  • relayer: minimize eth_getLogs requests limit (#289) (3731441)

v0.4.2

27 May 08:00
1431b48

Choose a tag to compare

What's Changed

Full Changelog: v0.4.1...v0.4.2

v0.4.1

20 May 16:08
95a07a7

Choose a tag to compare

fix: USDCx registry — match DA hosted token-standard API shape (#276)

* fix: USDCx registry — match DA hosted token-standard API shape

The Splice transfer-factory endpoint hosted by DA's utilities API
(api.utilities.digitalasset-dev.com) mounts the registry per-registrar
under /api/token-standard/v0/registrars/{registrar}/, and expects the
on-ledger choice arguments wrapped in `choiceArguments` with a structured
`instrumentId` plus `executeBefore`/`requestedAt`/`extraArgs` fields.

The SDK was emitting the legacy unprefixed path with a flat body, so
transfers against the deployed devnet failed with `registry returned
404` from nginx. The local devstack happened to register the legacy
path, masking the bug. Update both the SDK client and the devstack
sidecar to the spec-conformant shape, and reuse the registry call's
requestedAt/executeBefore in the on-ledger TransferFactory_Transfer
choice so the timestamps stay consistent.

* fix: unwrap nested choiceContext envelope from DA's registry response

DA's hosted token-standard registry returns the AnyValue choice context
and disclosed contracts both nested inside `choiceContext` (mirroring
the receiver-side AcceptContextResponse shape):

  { "factoryId": ..., "transferKind": ...,
    "choiceContext": { "choiceContextData": {"values": {...}},
                       "disclosedContracts": [...] } }

The previous PR fixed the request side but left the SDK's RegistryResponse
expecting a flat shape (choiceContext = AnyValue map directly,
disclosedContracts at top-level). Against DA's response that would put
the whole {choiceContextData, disclosedContracts} object into
ChoiceContext and leave DisclosedContracts null; ConvertAnyValueChoiceContext
finds no `values` key at that level, falls through to ConvertChoiceContext,
which fails with "json: cannot unmarshal object into Go value of type string".

GetTransferFactory now decodes into a wire struct and lifts choiceContextData
and disclosedContracts back into the flat RegistryResponse the downstream
converters expect. The local devstack mock is updated to emit the same
nested shape, so it actually exercises the unwrap path.

Also URL-escape the registrar party in the request path so future party IDs
with reserved characters don't silently misroute (Gemini's review).

Diagnosis and fix by salindne.

* Update pkg/cantonsdk/token/client.go

Co-authored-by: Sebastian Lindner <33971232+salindne@users.noreply.github.com>

* fix: strip trailing whitespace from comment (gofmt)

---------

Co-authored-by: Sebastian Lindner <33971232+salindne@users.noreply.github.com>

v0.4.0 - USDCx Support

12 May 16:02
1406bd6

Choose a tag to compare

feat: E2E devstack and tests for USDCx offer/accept transfer flow (#271)

* fix: replace CIP56TransferFactory with AllocationFactory in USDCx bootstrap

Switch from CIP56TransferFactory (atomic, local-only) to DA's AllocationFactory
from utility_registry_app_v0. This factory creates TransferOffer contracts on
TransferFactory_Transfer, matching devnet behaviour where the receiver must
exercise TransferInstruction_Accept to complete the transfer.

Also bootstraps TransferRule and InstrumentConfiguration (with empty
holderRequirements) which are required as choiceContextData when the receiver
accepts the transfer offer.

Closes #258

* feat: E2E devstack and tests for USDCx offer/accept transfer flow (#263)

- Canton interface: add FindPendingInboundTransferInstructions and AcceptTransferInstruction
- CantonShim: add usdcxTokenClient (ExternalTokens + RegistryClient) and implement both methods
- Canton2Shim: implement both methods returning errP2NotSupported
- ServiceManifest: add USDCxRegistryHTTP; ServiceDiscovery resolves usdcx-registry:8090
- DSL: add WaitForPendingTransferOffer helper (polls until TransferOffer appears in ACS)
- Tests: remove t.Skip, add accept step after each TransferToken call so the
  AllocationFactory offer/accept flow is exercised end-to-end

* fix: use api-server for USDCx incoming transfer accept flow

Replace the direct-Canton accept approach with API-based acceptance:
- APIServer interface: add ListIncomingTransfers, PrepareAcceptTransfer,
  ExecuteAcceptTransfer
- APIServerShim: implement the three new methods with EIP-191 auth
- http.go: add getAuth helper for authenticated GET requests
- DSL: replace WaitForPendingTransferOffer (Canton-direct) with
  WaitForIncomingTransferOffer (polls GET /api/v2/transfer/incoming)
- Revert Canton interface / shim changes from first approach (no direct
  AcceptTransferInstruction / FindPendingInboundTransferInstructions)
- Revert ServiceManifest.USDCxRegistryHTTP and discovery.go port 8090
- Tests: split into custodial (accept worker auto-accepts) and non-custodial
  (receiver accepts via PrepareAcceptTransfer / ExecuteAcceptTransfer API)

* fix: regenerate custodial mock and address lint issues

- pkg/custodial/mocks: regenerate mock_canton_token.go to include
  PrepareAcceptTransfer (added to token.Token interface in PR #269)
- docker/discovery.go, shim/canton2.go: gofmt alignment fixes
- cross_transfer_test.go: fix err variable shadow in ExecuteAcceptTransfer calls

* fix: fall back to AllocationFactory when no CIP56TransferFactory found

USDCx on P2 deploys an AllocationFactory (utility_registry_app_v0) instead
of a CIP56TransferFactory. getTransferFactoryCID now retries with
GetAllocationFactory when ErrTransferFactoryNotFound is returned and
UtilityRegistryAppPackageID is configured.

The exercise command is unchanged — buildTransferCommand already uses the
Splice interface template ID (TransferInstructionV1:TransferFactory) which
is implemented by both concrete templates.

- token/client.go: add AllocationFactory constants and GetAllocationFactory;
  modify getTransferFactoryCID to fall back
- shim/canton.go: add utilityRegistryAppPackageID constant
- shim/canton2.go: pass UtilityRegistryAppPackageID to token.New

* fix: usdcx support and test fixed

* fix: optimized implementation

* fix: dar download before devstack up

* fix: removed unused changes

* fix: removed unused changes

* fix: enable indexer Holding/Offer capture against real Canton chains

Three fixes verified against ChainSafe DevNet (the indexer now correctly
indexes a real USDCx Holding owned by an external party):

- decoder.go: NewHoldingDecoder and NewOfferDecoder filtered events by
  ev.PackageID == configPackageID. That equality fails whenever the config
  uses a package-name reference (#name), because Canton 3.x accepts those
  in stream filters but events arrive carrying the resolved package hash.
  Match by module+entity only, mirroring the CIP56 decoder; the stream-
  level template filter already narrows the wire to the right template.

- migrations/indexerdb/5,6: CreateModelIndexes is variadic — each string
  becomes one index. Both migrations passed a comma-joined column list as
  a single argument, producing ERROR: column "a,b,c" does not exist.
  Split into separate args.

- config.indexer.docker.yaml: switch package fields to package-name
  references so the same indexer config layout works on the local devstack
  and on real chains (devnet/mainnet reject raw hashes in stream filters
  with INVALID_FIELD).

* fix: config updated

* fix: config updated for devnet

---------

Co-authored-by: Sebastian Lindner <33971232+salindne@users.noreply.github.com>

v0.2.1

07 Jan 14:28
c8c0a77

Choose a tag to compare

Merge pull request #46 from ChainSafe/salindne/fix-reconcile-balance-bug

fix: reconciliation no longer overwrites user balances

v0.2

18 Dec 22:01
f1f6476

Choose a tag to compare

Release Notes v0.2

Overview

This release introduces the ERC-20 API Server as a new service and includes significant improvements to the Relayer.


New: ERC-20 API Server

A new JSON-RPC 2.0 API server providing ERC-20-like interface for Canton-based tokens.

Features

  • Token Metadata Methods (public):

    • erc20_name - Get token name
    • erc20_symbol - Get token symbol
    • erc20_decimals - Get token decimals
    • erc20_totalSupply - Get total supply from cache
  • Authenticated Methods (require JWT or EVM signature):

    • erc20_balanceOf - Get user balance
    • erc20_transfer - Transfer tokens between registered users
    • user_register - Register new user with EVM signature

Architecture

  • Issuer-centric model: All users share the relayer party, differentiated by fingerprint mapping
  • Balance caching system: PostgreSQL-backed cache with periodic reconciliation
  • Whitelist support: Address-based registration whitelist
  • Graceful shutdown: Configurable timeout with proper cleanup

Configuration

New configuration file: config.api-server.docker.yaml


Relayer Improvements (since v0.1.7)

Features

  • Issuer-centric model support - Aligns with API Server architecture
  • Docker local testing setup - Full end-to-end testing with auto package ID detection
  • Docker build and release workflow - Automated GHCR publishing
  • JWT renewal for Canton streamer - Automatic token refresh
  • Bridge activity sorting - Events ordered newest to oldest
  • Deposit idempotency - Prevents duplicate deposits

Fixes

  • Withdrawal balance cache - Now correctly uses EVM address
  • Package ID extraction - Updated for v2 packages
  • Fingerprint lookups - Fixed cache updates
  • Transfer pattern - Uses Burn+Mint instead of non-existent Transfer choice
  • DepositReceipt tracking - Properly tracked in raw events
  • Healthcheck - Uses GET instead of HEAD
  • Streamer error handling - Fixed lastOffset usage and error responses

Maintenance

  • Updated canton-erc20 submodule to v2 with renamed packages
  • Removed legacy Canton bridge methods
  • Dynamic DAR discovery with version 1.0.2 support
  • Eliminated DAR build warnings
  • Cleaned up deprecated test scripts
  • Simplified .dockerignore

Breaking Changes

  • Package IDs updated for refactored Daml packages (v2)
  • Removed legacy Canton bridge methods - use new API patterns

Upgrade Notes

  1. Update configuration files with new package IDs
  2. Rebuild DAR files with updated canton-erc20 submodule
  3. Run database migrations for API Server balance cache tables
  4. Configure relayer_party for issuer-centric model

Docker Images

# Relayer
docker pull ghcr.io/chainsafe/canton-middleware:v0.2

# API Server
docker pull ghcr.io/chainsafe/canton-erc20-api:v0.2

v0.1.7

15 Dec 17:25
v0.1.7
2af60de

Choose a tag to compare

Canton-Ethereum Token Bridge - Release Notes v1.0

Overview

A centralized token bridge connecting CIP-56 tokens on Canton Network with ERC-20 tokens on Ethereum Mainnet, enabling bidirectional cross-chain transfers.

Core Features

Relayer Service

  • Go-based relayer node running as a sidecar to Canton Network Partner Node
  • Bidirectional event streaming from both Canton and Ethereum
  • Persistent offset tracking with PostgreSQL for crash recovery
  • Automatic reconciliation of pending transfers (every 5 minutes)
  • Readiness probes for Kubernetes deployment

Deposit Flow (EVM → Canton)

  • Monitors Deposit events on Ethereum bridge contract
  • Creates PendingDeposit on Canton via Ledger API
  • Resolves user fingerprint mapping and processes deposit
  • Unlocks/mints tokens to Canton user's holding

Withdrawal Flow (Canton → EVM)

  • Streams WithdrawalEvent contracts from Canton
  • Submits withdrawFromCanton transaction on Ethereum
  • Marks withdrawal complete on Canton for reconciliation
  • Idempotency protection via Canton tx hash tracking

Smart Contracts

  • Ethereum: Solidity bridge contract for ERC-20 lock/unlock and wrapped token mint/burn
  • Canton: DAML contracts for CIP-56 token management and bridge operations

Operational Scripts

  • bootstrap-bridge.go - Initialize Canton bridge contracts
  • register-user.go - Register user fingerprint mappings
  • query-holdings.go - Query Canton token holdings
  • initiate-withdrawal.go - Initiate Canton→EVM withdrawals
  • cleanup-withdrawals.go - Reconcile incomplete withdrawals

Configuration

  • YAML-based configuration with environment variable overrides
  • Support for local, devnet, and mainnet environments
  • Docker Compose deployments included

Observability

  • Prometheus metrics for pending transfers
  • Structured logging with zap
  • Health/readiness endpoints