Skip to content

Review: the four RI architecture reports, round 3 (comments only, do not merge) - #83

Open
pepebndc wants to merge 1 commit into
review-base/ri-reports-r3from
review/ri-reports-r3
Open

Review: the four RI architecture reports, round 3 (comments only, do not merge)#83
pepebndc wants to merge 1 commit into
review-base/ri-reports-r3from
review/ri-reports-r3

Conversation

@pepebndc

@pepebndc pepebndc commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

What this is

A review-only PR to collect a fresh round of external comments on the four OpenZeppelin Canton Reference Implementation (RI) architecture reports, as they currently stand on main. The base branch is a temporary copy of main with the reports removed, so the full text of each report appears here as an added-file diff and supports inline comments.

Do not merge. The reports already live on main under docs/reference-architectures/. When this review round is done the PR will be closed and the temp branches deleted.

The previous round was collected in #40.

Files under review

  • docs/reference-architectures/dex.md (Privacy-Preserving DEX)
  • docs/reference-architectures/lending.md (Lending Protocol)
  • docs/reference-architectures/cross-chain-stablecoin.md (Cross-Chain Stablecoin)
  • docs/reference-architectures/confidential-auction.md (Confidential Auction Launchpad)

How to review

Open the Files changed tab and leave inline comments directly on the report lines. General remarks are welcome as regular PR comments.

igingu pushed a commit that referenced this pull request Aug 13, 2026
## What this does

Adds a GitHub Actions workflow that aligns the review-only PR #83 with
`main` on demand.

The workflow runs only on manual trigger (`workflow_dispatch`, the **Run
workflow** button in the Actions tab). When triggered, it:

1. Rebuilds `review-base/ri-reports-r3` from the `main` tip, with the
four RI reports removed.
2. Rebuilds `review/ri-reports-r3` on top of it, with the reports
re-added from `main`.
3. Force-pushes both branches, so PR #83 shows the current `main`
content of the reports as an added-file diff.

The workflow exits without changes when `review/ri-reports-r3` no longer
exists, so it becomes a no-op once the review round closes and the
branches are deleted.

## Why manual instead of automatic

Reviewers left feedback on an outdated version of the reports because
the review PR had fallen behind `main`, with no easy way to update it.
This gives us a one-click update.

An automatic push-triggered sync was considered and rejected: a review
can take several days, and an automatic sync would change the content
under the reviewer's feet mid-review ([Slack
thread](https://openzeppelin.slack.com/archives/C0AENAASAUR/p1786636513166079)).
With the manual trigger we sync deliberately, between review passes.

## Trade-off

When a synced report line differs from the previous snapshot, inline
comments on that line become outdated in PR #83. They stay visible in
the conversation tab.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@github-actions
github-actions Bot force-pushed the review/ri-reports-r3 branch from 6399c09 to fa835c3 Compare August 14, 2026 14:55
@github-actions
github-actions Bot force-pushed the review-base/ri-reports-r3 branch 2 times, most recently from 8c89ea0 to ad35ab1 Compare August 19, 2026 14:32
@github-actions
github-actions Bot force-pushed the review/ri-reports-r3 branch from fa835c3 to e9b941a Compare August 19, 2026 14:32
@github-actions
github-actions Bot force-pushed the review-base/ri-reports-r3 branch from ad35ab1 to 3ed5b48 Compare August 20, 2026 13:04
@github-actions
github-actions Bot force-pushed the review/ri-reports-r3 branch 2 times, most recently from b117f67 to 9512a8b Compare August 20, 2026 13:07
@github-actions
github-actions Bot force-pushed the review-base/ri-reports-r3 branch 2 times, most recently from e016867 to 6736262 Compare August 20, 2026 13:25
@github-actions
github-actions Bot force-pushed the review/ri-reports-r3 branch from 9512a8b to f4a6cce Compare August 20, 2026 13:25
@igingu
igingu requested a review from hrischuk-da August 20, 2026 13:45
`minCollateralRatio`, and a position that later falls under
`liquidationRatio` becomes liquidatable. The surplus is the buffer that
keeps the debt fully covered through collateral price movement.
- **Permissioned.** Every participant acts under a verified identity:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- **Permissioned.** Every participant acts under a verified identity:
- **Permissioned.** Every party acts under a verified identity:

Comment thread docs/reference-architectures/lending.md Outdated

State changes by archive-and-recreate rather than in-place mutation, with every signatory co-authorizing the transition (Daml's propose-and-accept pattern). That is why the design resolves the `Vault`, `PriceOracle`, `PauseState`, and the trusted-attester and trusted-issuer registries by **contract key** (reintroduced in [Canton 3.5.1+](https://github.com/digital-asset/canton/releases/tag/v3.5.1)): a key is the identity that survives each recreate. Keys are not unique - the platform accepts two contracts sharing one - so uniqueness stays an application obligation. The vault creation should perform checks against duplicate positions.

Keys are the design target, not what runs today. The experiment code sits on the workspace's pinned SDK baseline and is keyless, so each choice takes a caller-supplied registry contract id and asserts it shares the factory's admin. By-key resolution lands with the 3.5.1+ SDK migration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't understand why contract keys aren't supported. Contract key support is on Dev/Test/MainNet. See https://docs.canton.network/shared/version-compatibility-dashboard.

@igingu igingu Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da To clarify: going forward, all our implementations will use contract keys where applicable, since they are supported and the recommended practice. We will also retrofit the existing code, i.e. the current PauseState template does not use keys yet. That is what the paragraph intends to convey: our production implementations will make full use of contract keys; the code we have experimented with and referenced here as examples simply has not adopted them yet, since they were not supported when all these experiments were created.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da changed to Contract keys are supported on the network today and our production implementation will use them throughout. The experiment code referenced here predates the workspace's move to the 3.5.1+ SDK and is still keyless, so choices may taks a caller-supplied registry contract id and assert it shares the factory's admin. The experiments and the existing components (such as PauseState) will migrate to by-key resolution with the SDK upgrade..

Comment thread docs/reference-architectures/lending.md Outdated
edges are runtime interactions, dashed edges are standing governance or
trust relationships, and keyed contracts are marked with their key.

The first diagram shows the actors and the lending application's own

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should the Custodian and Registry be shown in the first diagram?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da It would be clearer indeed, I added them.

Comment thread docs/reference-architectures/lending.md Outdated

- **Vault Admin / Stablecoin Issuer (`VAULT_ADMIN`)** - underwrites the **stablecoin (debt) token**: operates the `VaultFactory`, configures `VaultParams`, the `TrustedIssuerRegistry` (accepted KYC issuers), and the `TrustedAttesterRegistry` (accepted compliance attesters), and issues the grants that authorize custodian seizure sweeps. The admin can mint the stablecoin, never the collateral, and only inside the vault choices ([section 3](#3-target-design)). The protocol gives the vault admin no path to issuing unbacked stablecoin.
- **Borrower (`BORROWER`)** - the entity locking collateral and drawing debt. Only the borrower can lock their own holdings into an allocation. To interact with the protocol, the borrower must hold a valid `KycClaim`, verified at vault creation and fetched live by each value-moving vault choice. Visibility is limited to the borrower's own vaults and the public configuration contracts.
- **Liquidator (`LIQUIDATOR`)** - a role granted via `openzeppelin-access-control-v1`. Each granted liquidator is placed in the observer set of the vaults it polices, so it can monitor the `PriceOracle` and vault solvency off-ledger from its own projection; authorized to liquidate only after the margin-call grace period has elapsed on a flagged, still-unhealthy vault, and only proportionally to the stablecoin it repays.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please adjust Liquidator because it is incredibly hard to parse.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da thank you for spotting this, adjusted.

Comment thread docs/reference-architectures/lending.md Outdated
- **Borrower (`BORROWER`)** - the entity locking collateral and drawing debt. Only the borrower can lock their own holdings into an allocation. To interact with the protocol, the borrower must hold a valid `KycClaim`, verified at vault creation and fetched live by each value-moving vault choice. Visibility is limited to the borrower's own vaults and the public configuration contracts.
- **Liquidator (`LIQUIDATOR`)** - a role granted via `openzeppelin-access-control-v1`. Each granted liquidator is placed in the observer set of the vaults it polices, so it can monitor the `PriceOracle` and vault solvency off-ledger from its own projection; authorized to liquidate only after the margin-call grace period has elapsed on a flagged, still-unhealthy vault, and only proportionally to the stablecoin it repays.
- **Oracle Operator(s) (`ORACLE_PROVIDER`)** - the implementation-defined party set that updates the `PriceOracle`, bound by the interface requirements ([section 4](#43-component-price-oracle-interface)): no single party, not even the vault admin, should be able to move or stall the published price.
- **Insurance Fund (`INSURANCE_FUND`)** - the party that collects protocol revenue: it mints the interest revenue against the vaults' `feeReceivable` records; the accumulated fund is the first absorber of recognized bad debt.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the insurance fund collecting a portion or all of the revenue?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da in this design, it's all of the revenue. Note it does not include the liquidation bonus, which is a reward for the liquidator.

Comment thread docs/reference-architectures/lending.md Outdated
the vault's choices both signatures arrive automatically, inherited from the
`Vault`'s own signatories.

For roles that need to submit routinely (the insurance fund collecting fees), we envision keeping the confirmation threshold at 1, with each such role's powers bounded on-ledger.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The confirmation threshold is about the trust architecture. Are you alright with a malicious agent that captures the insurance fund's key being able to steal all of the funds?
I don't think efficiency is the key driver so you may want to adjust the rationale.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da good point, the insurance funds should have a higher confirmation threshold.


Whatever update mechanism the **oracle operators** run ([section 4](#43-component-price-oracle-interface)), an all-of-M quorum should be deliberately avoided: a single offline member, or one whose participant node has unvetted the protocol DAR, would stall every price update until the staleness guard freezes the protocol.

The **pause authority** is multi-hosted so the brake is always reachable, but its confirmation threshold stays at 1: an emergency stop must be instant, and a quorum would slow it down. The price of that choice is a griefing window: a malicious pauser can freeze in-flight settlements until their deadlines lapse. This griefing is capped by the authorizer's right to reclaim the allocated funds after the expiration deadline. The pause brings the additional risk of not being solvency-neutral: it freezes liquidation while collateral keeps repricing, an exposure tracked in [section 7](#7-open-design-questions).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't know why but when I see "griefing window" I read "grieving widow". :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da fair point :) to be fair, griefing usually comes together with grieving, by someone at least.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment can be resolved.

Comment thread docs/reference-architectures/lending.md Outdated
Taking each element of the codeblock in turn:

- **Proportional seizure.** `debtRepaid` is the amount the liquidator's own exercise burns in the same transaction, never the vault's full accrued debt, so a liquidator can never take more collateral than their payment (plus bonus) buys.
- **Restorable vault (`collateralRatio > 1 + liquidationBonus`).** Repaying `x` leaves debt `accruedDebt - x` and collateral value `collateralAmount · price - x · (1 + liquidationBonus)`; `restoreAmount` is the `x` that sets their ratio to exactly `minCollateralRatio`. In this regime every repaid unit improves the ratio, so a payment below the cap partially cures, a payment at the cap fully cures, and nothing beyond it can be taken (no overshoot). The target is `minCollateralRatio`, not `liquidationRatio`, so a cured vault does not restart on the liquidation boundary.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This line is unreadable. Please adjust.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da noted, I adjusted it to be more clear.

Comment thread docs/reference-architectures/lending.md Outdated

### Data and State Flow

The diagrams below show the four vault flows: **A** collateral deposit, **B** borrow, **C** repay and close, **D** margin call and liquidation. Atomic settlement appears only in the collateral deposit; repayment and liquidation payments burn in place, and everything the protocol releases (minted stablecoin, returned or seized collateral) moves by direct transfer under the vault's joint authority in the same transaction. In each, the `Compliance gate` node stands for the compliance-attestation check and the live KYC-claim fetch ([section 3](#compliance-is-re-checked-on-every-operation)), and keyed contracts are marked with their key.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It isn't clear what business operations are atomic. This sentence structure is very strange.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da all of the flows are designed to be atomic. The mistake I made here is to say "atomic settlement", where I actually meant "the settlement rail"... the paragraph means to say that only the collateral deposit requires the settlement rail (i.e. create an allocation and settle), the rest can use plain transfers from CIP112.

Comment thread docs/reference-architectures/lending.md Outdated
Factory -.->|"first deposit:<br/>create the Vault"| Vault
```

**B. Borrow (mint coupled to debt).** The borrower asks the vault for stablecoin; the vault checks compliance, reads the current price, and assesses whether the locked collateral is worth enough to cover the new debt. If so, it mints the stablecoin to the borrower and records the higher debt, all in one transaction. This flow does not need atomic settlement: the stablecoin's issuer and the borrower both already stand behind the vault, so the vault choice itself carries every signature the mint needs.

@hrischuk-da hrischuk-da Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This flow does not need atomic settlement: the stablecoin's issuer and the borrower both already stand behind the vault, so the vault choice itself carries every signature the mint needs.

But isn't it atomic based on the prior sentence? If so, why have this sentence?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da It is atomic, I meant it does not need the settlement rail. I made adjustments for here and the above comment.

Comment thread docs/reference-architectures/lending.md Outdated
Coin -->|"to borrower"| Borrower
```

**C. Repay and close.** The borrower pays down debt: the vault checks compliance, burns the whole payment out of the borrower's wallet (the borrower and the stablecoin issuer both already stand behind the vault, so no settlement is needed), records the lower debt, and adds the interest portion to the insurance fund's fee receivable. The insurance fund mints its accumulated fees on its own schedule, backed by that record. On close, the vault hands the remaining collateral back to the borrower in the same transaction.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would think that two operations are needed for this:

  1. Pay down some debt.
  2. Close the debt fully, including calculated fees and interest.

These seem separate to me because I would think 2. has the step of the borrower asking for what repayment is needed to fully close the account.

Am I missing something?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da we have two operations indeed:

  1. repayment, which decreases debt.
  2. close, which only works if all the debt is repaid. Closing the position will return all the collateral inside. I changed the text to clarify these two steps.

I would think 2. has the step of the borrower asking for what repayment is needed to fully close the account.
As we envision it, the borrower does not need to ask how much repayment is needed - he has visibility into the vault, and the backend app/wallet can compute this for him, and initiate the payment.

3. **Repay and close.** `Vault_BurnStablecoin` burns the payment via [`TokenRules_Burn`](https://github.com/OpenZeppelin/canton-contracts/blob/7696749737885e25cd88422847105f890f03b00d/experiments/token/tokenCIP112-v1/daml/OpenZeppelin/TokenCIP112V1/Registry.daml#L170), reduces `debtAmount`, and increments `feeReceivable`. The insurance fund collects via `Vault_CollectFees`; `Vault_Close` winds the position down, parking any uncollected receivable in an admin-signed contract for later collection.
4. **Margin call and liquidation.** `Vault_FlagForLiquidation` opens the grace window; once it elapses on a still-unhealthy vault, `Vault_Liquidate` names `debtRepaid` (capped by the health-restore formula), consumes a compliance attestation checking the liquidator, burns the payment, releases the proportional collateral, and recreates the residual `Vault`.

The sequence diagram below traces the margin-call and liquidation flow end to end:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This sequence diagram is a work of art.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da thank you for the kind words! Let me know if you see any other places that could benefit from a diagram.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This comment can be resolved

Comment thread docs/reference-architectures/lending.md Outdated
Assumptions and important notes:

- Between a deposit's allocate and its settle the borrower's collateral is
locked; the lock is time-bounded and the borrower always has a unilateral

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why is the lock time bound? Does that mean the collateral asset is returned to the borrower before the borrower has repaid?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@hrischuk-da this lock refers to the lock on the collateral allocation that goes through the settlement rail. Once settled, the only way for the borrower to recuperate the collateral is to pay any outstanding debt and close. I changed the text so this is clear.

Please note all the changes are on a separate PR that we didn't merge yet, so you will not see them in your ongoing review, yet. Thanks Curtis!


The debt paid on repay, close, or liquidation is `principal + accrued interest`; all flows burn the full payment via the registry's `TokenRules_Burn` and record the interest portion in the vault's `feeReceivable`; the insurance fund realises that revenue later by exercising `Vault_CollectFees`, which mints up to the recorded receivable. Those fees are protocol revenue, the on-ledger analogue of interest paid to the lender, and the accumulated fees are the first absorber of any liquidation shortfall.

Interest creates a structural liquidity gap: the protocol mints only principal, yet borrowers owe principal plus interest, so aggregate debt always exceeds circulating supply by the accrued interest. The stablecoin that pays interest must come from other borrowers' minted principal or from the insurance fund's re-minted fees re-entering circulation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How is the interest paid for with the stablecoin? How does that stablecoin enter the liquidity of this app? I'm confused by this paragraph.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey @hrischuk-da! In the current design, the protocol mints only principal, however borrowers owe principal plus interest. This means that aggregate debt always exceeds the protocol-minted supply by the accrued interest. A borrower sources the interest portion from stablecoin already in circulation: other borrowers' spent principal, or fees the insurance fund collected and re-spent.

I am not entirely sure if this is an economically-sound model, hence left it as an open-question for the implementation phase. I am also rewriting the above paragraph to clarify.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems very strange. Do you know of any lending platforms that behave like this in production?
This seems unstable and will have startup issues.
Can't a borrower can transfer in stablecoin to cover their interest?

### 2.2 Privacy and Visibility

Target visibility per record. Every record belongs to the wTOK registry
unless the row says otherwise. A party outside a row sees that record only

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is this row thing?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry, will update to make it clear that this is referring to the rows of the table right below the paragraph.

unless the row says otherwise. A party outside a row sees that record only
transiently, when a transaction it witnesses divulges it.

| Record | Signatories | Observers |

@hrischuk-da hrischuk-da Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is a record a contract? If so, replace everywhere please?

@0xNeshi 0xNeshi Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yes, I missed this when I was aligning the AI-generated terminology. Will update the whole document where appropriate to use "contract"

Comment thread docs/reference-architectures/cross-chain-stablecoin.md
membership is therefore a privacy decision as well as a compliance one.
- **The Custodian sees nothing until a seizure.** The seizure mark carries the
custodian destination as a data field and not as an observer entry.
- **A gate makes the party that runs it a stakeholder.** A fetch needs

@hrischuk-da hrischuk-da Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is that gate or gateway?
This paragraph is confusing and needs clarification please.

@0xNeshi 0xNeshi Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"gate" sometimes refers to "checks" (e.g. "D1 gate"). I agree it can seem confusing, will update to avoid using it unnecessarily. Will make the paragraph clearer.

([section 6](#6-open-design-questions)). The submitting relayer still
witnesses the credential transiently, because a fetch divulges to whoever
witnesses the exercise.
- **Settlement outcomes arrive as events, not as a queryable record.** The event

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is confusing because the terminology is non-starndard:

  • update or continuation stream
  • not sure what the 'host' is
  • what 'exercise node'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fair points, these really are non-standard.

  • "transfer-events stream" referred to Ledger API's update stream
  • "host" is definitely very ambiguous, it referred to the EventLog contract
  • "exercise node" referred to the transaction node in which EventLog_HoldingsChange was invoked (i.e. exercised).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Found other expressions that have equivalent Canton-native counterparts, updating them too

| Role | Target posture | Why |
|---|---|---|
| Attesters | Several independent parties in the attester registry, threshold N-of-M, never all-of-M | One unavailable or unvetted attester must not halt the rail, and one malicious attester must not mint |
| Bridge relayer | Multi-hosted on several participants, confirmation threshold 1 | It holds no minting trust and is the most submission-heavy role in the design. Integrity comes from the attester split, and relay should ultimately be permissionless, so no single party gates liveness |

@hrischuk-da hrischuk-da Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The Bridge relayer would need to manage the dedup of requests given this approach. How is this considered?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If you're referring to the case where multiple relayer participants submit the same lock attestation, this is an off-ledger concern. The off-ledger relayer backend should have a way to partition the locks or elect a "leader" or something else.
I agree that the document should say something about this case, will add it.

Comment thread docs/reference-architectures/cross-chain-stablecoin.md

**Rejected alternative: lock-and-unlock.** It pays the recipient from liquidity
held on the destination side, which adds a liquidity-provider role and an
inventory-imbalance surface that a reference rail does not need. The messaging

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does that last sentence add to the thought? I'm not sure.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It says the rejected alternative (lock-and-unlock) can be implemented behind the messaging gateway without changing the gateway itself.
Arguably, the sentence here adds no new information, as this has been mentioned before, so will remove it anyway.

Comment thread docs/reference-architectures/cross-chain-stablecoin.md
A message that re-drives a credit from the external chain would need
multi-round message passing, with its own delay, cost, and failure surface. What
remains is structural and fail-closed. Command deduplication over 24 hours makes
the three relayer commands safe to resubmit after a crash, and a stall blocks

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

But it must be resubmitted to the same validator. Dedup is only for a given validator and not across the synchronizer.


The inbound flow of [section 3.1](#31-inbound-credit) settles a payment
privately. This section binds that mint to the backing locked on the external
chain. That binding makes the rail a bridge.

@hrischuk-da hrischuk-da Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't understand why this is here?

That binding makes the rail a bridge.

off the same lock, which is what lets a quorum sign one message
([section 2.3](#23-decentralization-and-trust-topology)).

The registry therefore records credits. A flow that stalls or lapses leaves the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This paragraph is not clear.

The mint reads the credited-lock registry on-ledger, so the check holds even
when the whole attester quorum signs that second message.

**Reserve invariant.** Each lock attestation states the amount that the source

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm sorry but I don't understand this paragraph.

Chain->>Chain: Verify the quorum with the redemption verifier,<br/>then refund the originator
```

### 3.2 Reserve and Lock Attestation

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I was not able to understand this section.

Relayer->>Redeem: Archive the claim
```

1. **Burn on Canton.** The holder asks for redemption and names the

@hrischuk-da hrischuk-da Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Am I right in assuming that the locked amount is released in full and there are no partial release amounts? For example, I lock 100 ETH and I cannot unlock 50 ETH but only the whole 100 ETH.

external-chain destination. The burn destroys the wrapped holding and
produces a typed **redemption attestation** that carries three fields:

- the instrument the burn removed supply from;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would additional information be needed for audit purposes? For example, the canton party that is having the amount released?


### 3.4 Registry Uniqueness Under Non-Unique Keys

The pause state, the trusted-issuer list, the credited-lock registry, and the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't understand this paragraph. This sentences is really stragne "A credited-lock registry that lacks an entry lets a lock that already credited credit again"

instrument comes from the attested message. The caller supplies no part of the
key.

Uniqueness then rests on authority. A key's maintainer signs the contract, so

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it possible for a malicious party to manufacture the key by intelligent guessing and use it? Is there some entropy to avoid this?


| Record | Key | Maintainer |
|---|---|---|
| Credited-lock registry | The admin, and the instrument | wTOK admin |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please identify which 'admin' in each row.

An upgrade can neither add nor remove a key field, so each key carries every
scope field the rail can ever need ([section 3.7](#37-upgrade-path)).

**Rotation.** A new version of a record arrives through an action on the live

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please explain this.

The attester set keeps its observer entry on the credited-lock registry for its
own reads.

**Residual.** Nothing stops a maintainer from holding two live versions of a

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This needs to be more specific because there are several key maintainers identified.

against
([section 3.4](#34-registry-uniqueness-under-non-unique-keys)). The check sits
on the only path that reaches a settlement, so a settle that omits the
attestation fails. The attester registry's admin must be the wTOK admin,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do you expect the wTOK admin to be a multi-hosted/decentralized party to avoid a single malicious user?

attestation names, so a relayer cannot route a credit to an account that holds
no credential.

The check runs under the authority of the party that runs it, and that party has

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this first sentence correct?

| Custodian and lawful-process authority | Sweep only under a bounded mark and, past the settlement deadline, only under a lawful-process order. A colluding pair can move locked value to the preset account inside the deadline window. |
| KYC issuers | Bind a credential to the recipient and maintain expiry and revocation. The trusted-issuer list is only as strict as its most permissive issuer. |
| Pause authority | Sets the pause state for an incident, and not to grief. A malicious pause authority stalls inbound settlement until the deadlines lapse, and the senders then reclaim. |
| Gateway admin | Operates the gateway and reads the records its own checks depend on. Its authority covers the gateway transaction, so a faulty gateway admin delays inbound credits and leaves a credited lock closed. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The term record is ambiguous. Please replace with the concrete data.

| Unbacked mint | A relayer, or anyone without attester authorization, mints wTOK with no real external-chain lock. | The wTOK admin co-authorizes every mint, so a relayer cannot mint at all. Two sources of unbacked supply remain: an attester quorum that signs a lock which never happened, and the admin key, which signs every holding of its own instrument and can create one directly. |
| Fabricated redemption claim | The wTOK admin creates a redemption attestation with no burn behind it, names real lock attestations, and drains that backing on the external chain while Canton supply stays untouched. | The holder is a signatory of the attestation, so an admin-only create carries no authority and only the gateway's burn-and-create transaction produces a claim ([section 3.3](#33-outbound-redemption)). The residual is a holder that colludes, which costs that holder its own holding. |
| Replay of a used lock | A consumed message, or a second message for the same lock, is submitted again to mint twice. | One-time message consumption, and then the credited-lock registry that the mint writes as it credits. A nonce the registry already holds is rejected even if the attesters misbehave. |
| Shadowing registry duplicate | Two versions of one keyed record are live under the same key, and the submitter presents whichever suits it. The record may be a credited-lock registry, a trusted-issuer list, or an attester registry. | A key names the party that maintains it, so no other party creates a second version, and a rotation archives the version it replaces. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is this key? Is it a contract key? Is it a key that signs external transactions? This is not clear.

The credited-lock registry serializes every inbound mint of the rail, because
each record archives and recreates that one contract. Its key scopes it to one
instrument ([section 3.4](#34-registry-uniqueness-under-non-unique-keys)), so
the rail has one shard, and that shard is the throughput ceiling. Splitting an

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please define shard.

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