diff --git a/docs-main/integrations/exchanges/guidance.mdx b/docs-main/integrations/exchanges/guidance.mdx
index 39509492..663b0907 100644
--- a/docs-main/integrations/exchanges/guidance.mdx
+++ b/docs-main/integrations/exchanges/guidance.mdx
@@ -11,7 +11,7 @@ import ExchangeIntegrationTransferInstructionAcceptFull from '/snippets/daml-doc
# Exchange Integration Guide
-The pages below guide you how to integrate an exchange with Canton for the purpose of trading Canton Network Token Standard compliant tokens like Canton Coin.
+The pages below guide you on how to integrate an exchange with Canton for the purpose of trading Canton Network Token Standard-compliant tokens like Canton Coin.
The core of such an integration is automating the deposits and withdrawals of tokens to and from the exchange. The guide can thus also be used for services other than exchanges that want to support deposits and withdrawals of Canton Network tokens.
@@ -45,32 +45,32 @@ The guide is intentionally structured such that you can use a learning-by-doing
- Support for all CN Tokens, not just CC.
- Earning additional application rewards for all CN tokens.
-The following dependency diagrams shows the work items for each milestone.
+The following dependency diagrams show the work items for each milestone.

-**CC with 1-step withdrawal only**: this milestone allows you to support deposits and withdrawals of CC. It includes earning app rewards for all CC deposits. The workflows build on the Canton Network Token [Standard]() which is the foundation for supporting all CN tokens in the next milestone. We consider it an intermediate milestone, as it does not support:
+**CC with 1-step withdrawal only**: this milestone allows you to support deposits and withdrawals of CC. It includes earning app rewards for all CC deposits. The workflows build on the Canton Network Token [Standard](), which is the foundation for supporting all CN tokens in the next milestone. We consider it an intermediate milestone, as it does not support:
- all CN tokens
-- CC users that prefer to control the receipt of transfers, and thus do not want to setup preapprovals
+- CC users who prefer to control the receipt of transfers, and thus do not want to set up preapprovals
- earning app rewards for all deposits and withdrawals
See the following sections for details on the work items it depends on.
-- Setup DevNet node and/or use LocalNet
+- Set up DevNet node and/or use LocalNet
- `exchange-parties-setup`
- `one-step-deposit-workflow`
- `one-step-withdrawal-workflow`
-- Support restore from validator node backup
+- Support restoring from the validator node backup
- Support hard synchronizer migration
**MVP for all CN Tokens**: this milestone allows you to support deposits and withdrawals of all CN tokens. It comes with the limitation that application rewards are only earned on deposits of CC, but not on deposits of other CN tokens. It depends on the MVP for CC and the following additional work items:
- `multi-step-deposit-workflow`
-- `multi-step-withdrawal-workflow`, which resolves the limitation that users must setup a CC transfer preapproval to receive withdrawals.
+- `multi-step-withdrawal-workflow`, which resolves the limitation that users must set up a CC transfer preapproval to receive withdrawals.
- `token-onboarding`
-**Earn app rewards for all CN tokens**: is a milestone that improves the profitability of the integration by implementing changes so the exchange earns application rewards on both withdrawals and deposits of all CN tokens. Sharing application rewards is an optional steps.
+**Earn app rewards for all CN tokens**: is a milestone that improves the profitability of the integration by implementing changes so the exchange earns application rewards on both withdrawals and deposits of all CN tokens. Sharing application rewards is an optional step.
- `withdrawal-app-rewards`
- `deposit-app-rewards`
@@ -100,11 +100,11 @@ merge, link, align this brief summary with the overview in the wallet integratio
-If you have integrated your exchange with other BTC and other UTXO-based chains, the architecture presented here will be familiar and you will be able to reuse existing components and patterns. Before jumping into the discussion, it is important to map your preexisting concepts using the following mapping:
+If you have integrated your exchange with other BTC and other UTXO-based chains, the architecture presented here will be familiar, and you will be able to reuse existing components and patterns. Before jumping into the discussion, it is important to map your preexisting concepts using the following mapping:
- Transactions are identified in Canton using their globally unique **update-id**.
-- Each transaction is committed at specific **record time** that is assigned by the synchronizer used to commit the transaction.
-- Blockheight in BTC can be mapped to the record time of the Global Synchronizer in Canton Network.
+- Each transaction is committed at a specific **record time** that is assigned by the synchronizer used to commit the transaction.
+- Blockheight in BTC can be mapped to the record time of the Global Synchronizer in the Canton Network.
- BTC UTXOs map to what are usually called **active contracts** in Canton. Every Canton contract carries data of a specific Daml template type. For ease of understanding, we often refer to active contracts as "UTXOs" in this guide.
- BTC addresses map to **parties** in Canton.
- Validator nodes host parties and store their private data. Validator nodes also expose the **Ledger API** (LAPI), which can be used by an owner of a party to read their party's state and transactions.
@@ -113,11 +113,11 @@ If you have integrated your exchange with other BTC and other UTXO-based chains,
- Transactions in Canton have a hierarchical structure that reflects the nested execution and visibility of Daml choices. This hierarchical structure guarantees privacy between parties in the same transaction. Different validator nodes may see different sub-trees of the same transaction depending on which parties they host.
- **Memos** are stored in the transfer metadata using the `splice.lfdecentralizedtrust.org/reason` key. The Canton Network Token [Standard]() defines this key and a way to parse these memo tags and other transfer information from transactions.
-This guide provides a sample architecture and workflows for integrating an exchange with Canton. The expectation is that the integration components are reasonably thin wrappers over the functionality provided by the wallet SDK. The guide expects you to provide these components since they are mostly concerned integrating with your exchange's internal systems and its requirements.
+This guide provides a sample architecture and workflows for integrating an exchange with Canton. The expectation is that the integration components are reasonably thin wrappers over the functionality provided by the wallet SDK. The guide expects you to provide these components since they are mostly concerned with integrating with your exchange's internal systems and its requirements.
## Component Overview
-The following diagram shows the components to integrate an exchange's internal systems with Canton Network. We explain the components in the subsections below.
+The following diagram shows the components to integrate an exchange's internal systems with the Canton Network. We explain the components in the subsections below.

@@ -131,8 +131,8 @@ The guide's assumptions might not perfectly match your exchange's actual archite
There are five Canton integration components:
-- The **Exchange Validator Node** is a Splice validator node that hosts your `treasuryParty`, which is the party you setup to control funds, receive deposits, and execute transfers for withdrawals. See `exchange-parties-setup` for details on how to setup the `treasuryParty`. You can deploy and operate a validator [yourself]() or use a node-as-a-service provider to operate it for you.
-- The **Canton Integration DB** is used to keep track of the state of withdrawals and the customer-attribution of the funds held by the `treasuryParty`. It is shown as a separate component in the diagram, but it could be part of an existing databases.
+- The **Exchange Validator Node** is a Splice validator node that hosts your `treasuryParty`, which is the party you set up to control funds, receive deposits, and execute transfers for withdrawals. See `exchange-parties-setup` for details on how to set up the `treasuryParty`. You can deploy and operate a validator [yourself]() or use a node-as-a-service provider to operate it for you.
+- The **Canton Integration DB** is used to keep track of the state of withdrawals and the customer-attribution of the funds held by the `treasuryParty`. It is shown as a separate component in the diagram, but it could be part of an existing database.
- The **Tx History Ingestion** service uses the JSON Ledger [API]() exposed by the Exchange Validator Node to read Daml transactions affecting the `treasuryParty`. It parses these transactions and updates the Canton Integration DB with the effect of these transactions (e.g. a successful deposit to a customer account).
- The **Withdrawal Automation** service is responsible for executing withdrawals requested by the Exchange Internal Systems via the Canton Integration DB.
- The **Multi-Step Deposit Automation** service is responsible for accepting or rejecting transfers from customers to their exchange accounts for CN tokens that do not support direct transfers. It is not necessary for an integration with Canton Coin, which does support direct, 1-step transfers.
@@ -144,10 +144,10 @@ You are expected to provide the three services and DBs listed above in a way tha
The purpose of the third-party components in the diagram above (in gray) is:
- The **Global Synchronizer** serves the validator nodes to commit Daml transactions in a decentralized and fault-tolerant manner.
-- The **Customer Validator Node** is the validator node that hosts the `customerParty` which is used by the Customer to hold and transfer their funds.
+- The **Customer Validator Node** is the validator node that hosts the `customerParty,` which is used by the Customer to hold and transfer their funds.
- The **Customer Wallet** is the wallet used by the customer to manage their funds and make transactions.
-- The **Admin Validator Node** is the validator node used by the token administrator to track the ownership records of the token and validate changes to them. We use the `adminParty` to refer to the party that represents them on ledger. Note that the `adminParty` for a decentralized token is hosted on multiple validator nodes. For example the `adminParty` for Canton Coin is hosted on every SV node.
-- The **Registry API Server** provides access to extra context to execute token transfers. This context is often only known to the token administrator, which is why access is provided to it off-ledger. The OpenAPI specification of the Registry API is maintained as part the Canton Network Token Standard [definitions]() in the Splice repository.
+- The **Admin Validator Node** is the validator node used by the token administrator to track the ownership records of the token and validate changes to them. We use the `adminParty` to refer to the party that represents them on the ledger. Note that the `adminParty` for a decentralized token is hosted on multiple validator nodes. For example, the `adminParty` for Canton Coin is hosted on every SV node.
+- The **Registry API Server** provides access to extra context to execute token transfers. This context is often only known to the token administrator, which is why access is provided to it off-ledger. The OpenAPI specification of the Registry API is maintained as part of the Canton Network Token Standard [definitions]() in the Splice repository.
## Information Flows
@@ -159,19 +159,19 @@ There are three main information flows:
1. **Tx History Ingestion**: ingests the transactions (tx) affecting the `treasuryParty` from the Exchange Validator Node into the Canton Integration DB. Arrow 1.a represents the transaction data being read using the `/v2/updates/flats` Ledger API endpoint using either plain [HTTP]() or [websockets](). It is parsed by the Tx History Ingestion service to update the status of funds, deposits, and withdrawals in the Canton Integration DB (Arrow 1.b).
- This data is queried by Exchange Internal Systems (Arrow 1.c), for example to serve the Exchange UI. For brevity, the diagram shows direct access to the Canton Integration DB by the Exchange Internal Systems. However using a micro-services architecture, the Exchange Internal Systems would typically access the Canton Integration DB through a dedicated API layer. Choose whatever architecture best fits your exchange's needs.
+ This data is queried by Exchange Internal Systems (Arrow 1.c), for example to serve the Exchange UI. For brevity, the diagram shows direct access to the Canton Integration DB by the Exchange Internal Systems. However, using a micro-services architecture, the Exchange Internal Systems would typically access the Canton Integration DB through a dedicated API layer. Choose whatever architecture best fits your exchange's needs.
2. **Withdrawal Automation**: starts with the Exchange Internal Systems writing a withdrawal request to the Canton Integration DB (Arrow 2.a). The Withdrawal Automation service reads the request from the DB (Arrow 2.b), and prepares, signs, and executes a Canton Network Token standard transfer corresponding to the withdrawal request using the Ledger API (Arrow 2.c).
- Note that the status of transfers becomes visible in the transaction history ingested by the Tx History Ingestion service; and is communicated to both the Exchange Internal Systems and the Withdrawal Automation service via the Canton Integration DB. This routing of information through the Canton Integration DB is intentional to simplify disaster recovery.
+ Note that the status of transfers becomes visible in the transaction history ingested by the Tx History Ingestion service, and is communicated to both the Exchange Internal Systems and the Withdrawal Automation service via the Canton Integration DB. This routing of information through the Canton Integration DB is intentional to simplify disaster recovery.
Note also that the Withdrawal Automation may write back to the Canton Integration DB to mark a withdrawal as failed.
3. **Multi-Step Deposit Automation**: is required to support offer-and-accept style transfers for tokens that do not support direct transfers. It relies on the Tx Ingestion Service to ingest transfer offers as part of Arrow 1.c.
- The workflow starts with the Multi-Step Deposit Automation service querying the Canton Integration DB to see whether there are pending transfers for deposits from customers (Arrow 3.a). The service then checks whether the deposit address specified in the transfer is known. If yes, it prepares, signs, and executes an accept transaction using the Ledger API (Arrow 3.b). If no, then it takes no action, and lets the transfer offer expire or be withdrawn by the sender.
+ The workflow starts with the Multi-Step Deposit Automation service querying the Canton Integration DB to see whether there are pending transfers for deposits from customers (Arrow 3.a). The service then checks whether the deposit address specified in the transfer is known. If yes, it prepares, signs, and executes an accept transaction using the Ledger API (Arrow 3.b). If no, then it takes no action and lets the transfer offer expire or be withdrawn by the sender.
- Note that there is an arrow from Multi-Step Deposit Automation back to the Canton Integration DB, as the Multi-Step Deposit Automation may write back to the Canton Integration DB to store that the transaction to accept the deposit could not be committed even after retrying multiple times.
+ Note that there is an arrow from Multi-Step Deposit Automation back to the Canton Integration DB, as the Multi-Step Deposit Automation may write back to the Canton Integration DB to indicate that the transaction to accept the deposit could not be committed even after retrying multiple times.
The other information flows interact with the main flows as part of a deposit or withdrawal. We explain them in the `integration-workflows` section.
@@ -188,13 +188,13 @@ The other information flows interact with the main flows as part of a deposit or
The workflows below are grouped into two milestones.
- `mvp-for-cc` contains the minimum viable product (MVP) workflows for integrating Canton Coin (CC) into the exchange. It comes with the limitation that both the exchange and the customers need to set up a `TransferPreapproval` contract to enable 1-step transfers of CC.
-- `mvp-for-cn-tokens` contains the additional workflows required to support all CN tokens. They are the workflows to onboard a new token and to support multi-step transfers for both deposits and withdrawals. Multi-step transfers gives the receiver a choice to: reject an incoming transfer as well as enable additional asynchronous checks on transfers by the token admin (e.g. KYC/AML checks).
+- `mvp-for-cn-tokens` contains the additional workflows required to support all CN tokens. They are the workflows to onboard a new token and to support multi-step transfers for both deposits and withdrawals. Multi-step transfers give the receiver a choice to: reject an incoming transfer, as well as enable additional asynchronous checks on transfers by the token admin (e.g. KYC/AML checks).
Further extensions of these two MVPs to address Day-2 requirements are explored in `integration-extensions`.
>
>
-> add these functions. potentially using sphinx-tabs to allow switching between SDK function view and higher-level description
+> add these functions. potentially using sphinx-tabs to allow switching between the SDK function view and higher-level description
>
>
@@ -214,7 +214,7 @@ The diagrams in the sections below adapt the diagram from the `information-flows
Assumptions:
- The Exchange has set up a CC `TransferPreapproval` for their `treasuryParty` as explained in `treasury-party-setup`.
-- The Exchange has associated deposit account “abc123” with the Customer in the Canton Integration DB.
+- The Exchange has associated the deposit account “abc123” with the Customer in the Canton Integration DB.
Example flow:
@@ -259,7 +259,7 @@ Example flow:
3. Withdrawal Automation queries the Canton Integration DB in a polling fashion, observes the pending withdrawal `wid123`, and commits the corresponding CC transfer as follows.
1. Withdrawal Automation queries Canton Coin Scan to retrieve the `TransferFactory` for CC and extra transfer context.
- 2. Withdrawal automation checks that transfer is indeed a 1-step transfer by checking that `transfer_kind` = `"direct"` in the response from Canton Coin Scan. If that is not the case, then it marks the withdrawal as failed in the Canton Integration DB with reason "lack of CC transfer-preapproval for `customerParty`" and stops processing.
+ 2. Withdrawal automation checks that the transfer is indeed a 1-step transfer by checking that `transfer_kind` = `"direct"` in the response from Canton Coin Scan. If that is not the case, then it marks the withdrawal as failed in the Canton Integration DB with reason "lack of CC transfer-preapproval for `customerParty`" and stops processing.
3. Withdrawal Automation prepares, signs, and submits the command to exercise the `TransferFactory_Transfer` choice with the exclusive upper-bound for the record time of the commit set to `trecTgt`. It also sets the value for key `splice.lfdecentralizedtrust.org/reason` in the `Transfer` metadata to `wid123`.
@@ -280,14 +280,14 @@ Example flow:
- The deduction of 100 CC from the Customer's trading account.
- The archival of the CC `Holding` UTXOs `coids`.
- The new CC `Holding` UTXO `coid789` for the change returned after funding the CC transfer.
-5. Customer Wallet observes `upd567` at `t1` with offset `off2` on the Customer Validator Node, parses it using the token standard tx history parser and updates its UI as follows:
+5. Customer Wallet observes `upd567` at `t1` with offset `off2` on the Customer Validator Node, parses it using the token standard tx history parser, and updates its UI as follows:
- Its tx history shows the receipt of 100 CC from `exchangeParty` with “Reason” `wid123` that was committed as update `upd567` at `t1`.
- Its holding listing shows the new CC `Holding` with contract id `coid345`.
6. Customer observes the completion of the withdrawal at `t1` in the Exchange UI and the receipt of the expected funds in their Customer Wallet.
### UTXO Selection and Management
-Executing a withdrawal requires selecting `Holding` UTXOs to fund the withdrawal, as described for example in `one-step-withdrawal-workflow`. You likely already have a UTXO management strategy in place for your existing UTXO-chain integrations. Here some considerations to take into account when adapting your strategy to work with Canton:
+Executing a withdrawal requires selecting `Holding` UTXOs to fund the withdrawal, as described for example in `one-step-withdrawal-workflow`. You likely already have a UTXO management strategy in place for your existing UTXO-chain integrations. Here are some considerations to take into account when adapting your strategy to work with Canton:
- Canton Coin charges a small holding fee of about \$1 per year for each `Holding` UTXO to allow archiving dust [coins]() once their holding fee surpasses their value.
- Canton Coin limits the number of UTXOs for a single transfer to 100 `Holding` UTXOs to avoid large transactions that are expensive to process.
@@ -297,10 +297,10 @@ Executing a withdrawal requires selecting `Holding` UTXOs to fund the withdrawal
We therefore recommend the following approach:
-- Limit the number of input UTXOs to less than 100 UTXOs per transfer. Thus staying with the Canton Coin limits and keeping transaction size small, which also helps you to reduce your traffic spend when having to retry transaction execution.
+- Limit the number of input UTXOs to less than 100 UTXOs per transfer. Thus, staying within the Canton Coin limits and keeping transaction size small, which also helps you to reduce your traffic spend when having to retry transaction execution.
- Consider using a UTXO selection strategy for withdrawals that favors smaller UTXOs so that they get merged automatically as part of executing transfers.
- Consider keeping a pool of `k` large amount UTXOs to be able to execute up to `k` withdrawals at the same time. Run a periodic background job to manage this pool using self-transfers.
- - From an implementation perspective, these self-transfers are a special kind of withdrawal. We thus recommend to implement them using the same code path as withdrawals: start with writing the self-transfer request into the Canton Integration DB and have the Withdrawal Automation execute it.
+ - From an implementation perspective, these self-transfers are a special kind of withdrawal. We thus recommend implementing them using the same code path as withdrawals: start with writing the self-transfer request into the Canton Integration DB and have the Withdrawal Automation execute it.
## MVP for all Canton Network Tokens
@@ -360,7 +360,7 @@ This is where the main difference to the `one-step-deposit-workflow` starts. The
> - It archives the locked 100 AcmeToken `Holding` UTXO `coid234` owned by the `customerParty`.
> - It creates a 100 AcmeToken `Holding` UTXO `coid999` owned by the `treasuryParty`.
-At this point the workflow again proceeds the same way as the `one-step-deposit-workflow`.
+At this point, the workflow again proceeds the same way as the `one-step-deposit-workflow`.
6. Tx History Ingestion observes `upd789` at `t2` with offset `off3` and updates the Canton Integration DB as follows.
1. Tx History Ingestion parses `upd789` using the token standard tx history parser from the Wallet SDK to determine:
@@ -370,7 +370,7 @@ At this point the workflow again proceeds the same way as the `one-step-deposit-
- The latest ingested update-id `upd789`, its record time `t2` and offset `off3`.
- The new AcmeToken `Holding` UTXO `coid999` for the 100 AcmeToken that was received.
- The credit of 100 AcmeToken on the Customer's account at the exchange.
-7. Customer Wallet observes `upd789` at `t2` on the Customer Validator Node, parses it using the token standard tx history parser and updates its UI as follows:
+7. Customer Wallet observes `upd789` at `t2` on the Customer Validator Node, parses it using the token standard tx history parser, and updates its UI as follows:
- Its tx history shows the successful transfer of 100 AcmeToken to `exchangeParty` with “Reason” `wid123` that was committed as update `upd789` at `t2`.
8. Customer observes the successful deposit in their Exchange UI, whose data is retrieved from the Canton Integration DB via the Exchange Internal Systems.
@@ -393,7 +393,7 @@ The flow uses essentially the same initial six steps as the `one-step-withdrawal
- The deduction of 100 AcmeToken from the Customer's trading account.
- The pending withdrawal with id `wid123` of 100 AcmeToken to `customerParty`.
- The AcmeToken `Holding` UTXOs `coids` to use to fund the transfer to `customerParty` for `wid123`. See `utxo-management` for more information.
- - The target record time `trecTgt` on the Global Synchronizer until which the transaction for the AcmeToken transfer must be committed using the `coids` UTXOs for funding `wid123`. The `coids` are considered to be reserved to funding this transfer until `trecTgt` has passed.
+ - The target record time `trecTgt` on the Global Synchronizer until which the transaction for the AcmeToken transfer must be committed using the `coids` UTXOs for funding `wid123`. The `coids` are considered to be reserved for funding this transfer until `trecTgt` has passed.
3. Withdrawal Automation queries the Canton Integration DB in a polling fashion, observes the pending withdrawal `wid123`, and commits the corresponding AcmeToken transfer as follows.
1. Withdrawal Automation retrieves the URL for Acme's Registry API Server from the Canton Integration DB.
2. Withdrawal Automation queries Acme's Registry API Server to retrieve the `TransferFactory` for AcmeToken and extra transfer context.
@@ -420,7 +420,7 @@ The flow uses essentially the same initial six steps as the `one-step-withdrawal
6. Customer Wallet observes update with update-id `upd567` at `t1` with offset `off2` on the Customer Validator Node.
1. It parses the transaction using the token standard transaction history parser and updates its UI so that its transaction history shows the offer for a transfer of 100 AcmeToken from `exchangeParty` with “Reason” `wid123` that was committed as update `upd567` at `t1`.
-This is where the main difference to the `one-step-withdrawal-workflow` starts. The customer has a choice whether to accept or reject the transfer offer. Here they choose to accept it.
+This is where the main difference from the `one-step-withdrawal-workflow` starts. The customer has a choice whether to accept or reject the transfer offer. In this case, they choose to accept it.
7. Customer uses their Customer Wallet to accept the offer using the `TransferInstruction_Accept` choice.
1. The resulting transaction is committed across Exchange, Acme, and Customer validator nodes and assigned update-id `upd789` and record time `t2`. The transaction has the following effects:
@@ -444,7 +444,7 @@ The Customer might decide to reject the offer in Step 7 in the example above. Th
> - archive the `TransferInstruction` UTXO `coid567`, and
> - create a new 100 AcmeToken `Holding` UTXO `coid999` owned by the `treasuryParty`.
-Steps 8 - 10 are largely the same as for the successful acceptance with the difference that Tx History Ingestion will see this transaction and update the Canton Integration DB to such that
+Steps 8 - 10 are largely the same as for the successful acceptance, with the difference that Tx History Ingestion will see this transaction and update the Canton Integration DB so that
> - withdrawal `wid123` is marked as failed because the customer rejected the offer, and
> - the customer account is credited back the 100 AcmeToken, potentially minus a fee for the failed withdrawal.
@@ -452,7 +452,7 @@ Steps 8 - 10 are largely the same as for the successful acceptance with the diff
And the user will ultimately see in both the Exchange UI and the Customer Wallet that the transfer was offered, but rejected by them.
The following code is available to help you implement your own parsing logic:
@@ -537,19 +537,19 @@ curl -sSL --fail-with-body http://json-api-url/v2/updates/update-by-id \
-You can parse such transactions using the token standard history parser provided in the wallet SDK to extract the deposit amount, account and holding contract ids. Note that one-step deposits are more complex to parse than two-step transfers as the token standard does not provide an interface choice visible to the receiver. If you prefer implementing your own implementation, you can parse this as follows:
+You can parse such transactions using the token standard history parser provided in the wallet SDK to extract the deposit amount, account, and holding contract ids. Note that one-step deposits are more complex to parse than two-step transfers, as the token standard does not provide an interface choice visible to the receiver. If you prefer implementing your own implementation, you can parse this as follows:
1. Go over the list of events ordered by `nodeId` that you see in the transaction.
-2. For each exercised event, check the exercise result. If it has a field called `` `meta `` with a `"splice.lfdecentralizedtrust.org/tx-kind": "transfer"` field you found a transfer. In the example here, this is the event with `nodeId` 4 which exercises the `TransferPreapproval_Send` choice. Note that this choice is specific to Canton Coin so rely on the existence of the `meta` field which is standardized instead of the specific choice name.
+2. For each exercised event, check the exercise result. If it contains a `` `meta`` field with `"splice.lfdecentralizedtrust.org/tx-kind": "transfer"`, a transfer has been identified. In this example, this corresponds to the event with `nodeId` 4, which exercises the `TransferPreapproval_Send` choice. Note that this choice is specific to Canton Coin, so rely on the presence of the standardized meta field rather than the specific choice name.
-3. Extract the `"splice.lfdecentralizedtrust.org/reason"` to get the deposit account. In this example it is `deposit-account-id`.
+3. Extract the `"splice.lfdecentralizedtrust.org/reason"` to get the deposit account. In this example, it is `deposit-account-id`.
4. Go over all events whose `nodeId` is larger than the `nodeId` of the transfer (4 in the example here) and smaller than the `lastDescendantNodeId` of the transfer (12 in the example here).
5. Find all `CreatedEvents` in that range that create a `Holding` with `"owner": "<treasury-party>"` and sum up the amounts for each `instrumentId`. In this example, we have two events that create holdings, `nodeId` 11 and 12. However, only 12 has `"owner": "<treasury-party>"`. Therefore, we extract that the transfer created `200.0000000000` for the token with instrument id `{"admin": "DSO::12204b8b621ec1dedd51ee2510085f8164cad194953496494d32f541f3f2c170e962", "id": "Amulet"}`.
-6. Find all `ExercisedEvents` with `implementedInterfaces` containing the `Holding` interface and `consuming: true`. In the example here, this is the event with `nodeId:: 8`. For each of them get the `contractId` and lookup the contract payload through the event query service as shown below. If you get a 404, it's a holding for a different party so you can ignore it. If you get back an event, check if `"owner": "<treasury-party>"`. If so, sum up all events for which this is the case. In the example here, we get a 404 as it is a holding of the sender not treasury-party.
+6. Find all `ExercisedEvents` with `implementedInterfaces` containing the `Holding` interface and `consuming: true`. In this example, this is the event with `nodeId:: 8`. For each of them, get the `contractId` and lookup the contract payload through the event query service as shown below. If you get a 404, it's a holding for a different party so you can ignore it. If you get back an event, check if `"owner": "<treasury-party>"`. If so, sum up all events for which this is the case. In this example, we get a 404 because it is a holding of the sender, not the treasury party.
```bash
curl -sSL --fail-with-body http://json-api-url/v2/events/events-by-contract-id \
@@ -569,15 +569,15 @@ You can parse such transactions using the token standard history parser provided
}'
```
-7. Subtract the sum of archived holdings for the treasury-party from the sum of created holdings. This gives you the deposit amount for each instrument id. You now extracted the deposit amount from the created and exercised events, the UTXOs from the created events and the deposit acount from the `splice.lfdecentralizedtrust.org/reason` field.
+7. Subtract the sum of archived holdings for the treasury-party from the sum of created holdings. This gives you the deposit amount for each instrument id. You now extracted the deposit amount from the created and exercised events, the UTXOs from the created events, and the deposit account from the `splice.lfdecentralizedtrust.org/reason` field.
-8. Continue with the events starting at node id `lastDescendantNodeId + 1`. Note that in this example this skips over the event with `nodeId: 5` which exercises `AmuletRules_Transfer`. This is important as you already accounted for this event through the parent event at node id 4. Note that one transaction can contain multiple deposits including mixing 1 and 2-step deposits in the same transaction.
+8. Continue with the events starting at node id `lastDescendantNodeId + 1`. Note that in this example, this skips over the event with `nodeId: 5` which exercises `AmuletRules_Transfer`. This is important as you already accounted for this event through the parent event at node id 4. Note that one transaction can contain multiple deposits, including mixing 1 and 2-step deposits in the same transaction.
#### Differences between 1-Step Deposits and Withdrawals
-The example we discussed above, shows a deposit. A withdrawal is essentially the same transaction but sender and receiver are swapped. For a withdrawal, the sender, i.e. the treasury party for an exchange, will also see the `TransferFactory_Transfer` choice as a parent and you can extract the amount and reason from that instead of looking for the `meta` field in exercise results.
+The example we discussed above shows a deposit. A withdrawal is essentially the same transaction, but the sender and receiver are swapped. For a withdrawal, the sender, i.e. the treasury party for an exchange, will also see the `TransferFactory_Transfer` choice as a parent, and you can extract the amount and reason from that instead of looking for the `meta` field in exercise results.
-Note however, that for Canton Coin the `amount` in the `TransferFactory_Transfer` argument will be higher than the difference of holdings archived and created for the treasury party due to Canton Coin usage fees. Once the CIP for CC fee removal is implemented, this distinction goes away. Currently Canton Coin is the only token on Canton Network charging such fees.
+Note, however, that for Canton Coin the `amount` in the `TransferFactory_Transfer` argument will be higher than the difference of holdings archived and created for the treasury party due to Canton Coin usage fees. Once the CIP for CC fee removal is implemented, this distinction goes away. Currently, Canton Coin is the only token on Canton Network charging such fees.
### Multi-Step Transfers
@@ -613,16 +613,16 @@ curl -sSL --fail-with-body http://json-api-url/v2/updates/update-by-id \
-You can parse such transactions using the token standard history parser provided in the wallet SDK to extract the deposit amount, account and holding contract ids. If you prefer implementing your own implementation, you can parse this as follows:
+You can parse such transactions using the token standard history parser provided in the wallet SDK to extract the deposit amount, account, and holding contract ids. If you prefer implementing your own implementation, you can parse this as follows:
1. Go over the list of events ordered by `nodeId` that you see in the transaction.
-2. Look for all `CreatedEvents` of the `TransferInstruction` interface with `"receiver": "<treasury-party>"`. Each of these represents a deposit offer that can be accepted or rejected. In the example this is only one event with node id `0`. Extract the `instrument`, the `amount` and the `splice.lfdecentralizedtrust.org/reason` field from the `interfaceView` and the contract id of the `TransferInstruction`. Note that one transaction can contain multiple deposits including mixing 1 and 2-step deposits in the same transaction.
+2. Look for all `CreatedEvents` of the `TransferInstruction` interface with `"receiver": "<treasury-party>"`. Each of these represents a deposit offer that can be accepted or rejected. In the example, this is only one event with node id `0`. Extract the `instrument`, the `amount` and the `splice.lfdecentralizedtrust.org/reason` field from the `interfaceView` and the contract id of the `TransferInstruction`. Note that one transaction can contain multiple deposits, including mixing 1 and 2-step deposits in the same transaction.
After accepting the deposit offer through your automation, Tx History Ingestion can then observe and process acceptance. An example of such a transaction can be seen below.
-To parse this proceed as follows:
+To parse this, proceed as follows:
1. Go over the list of events ordered by `nodeId` that you see in the transaction.
@@ -646,13 +646,13 @@ To parse this proceed as follows:
}'
```
- If you get a 404, the instruction is not for your treasury party so you can ignore it. If you get back an event, it has the same structure that we've seen above when a transfer offer is created and you can again extract the amount, instrument id and deposit account from it.
+ If you get a 404, the instruction is not for your treasury party, so you can ignore it. If you get back an event, it has the same structure that we've seen above when a transfer offer is created, and you can again extract the amount, instrument id and deposit account from it.
#### Differences between Multi-Step Deposits and Withdrawals
-Analogously to 1-step transfers, the sender that creates the withdrawal offer, i.e., the treasury party sees a `TransferFactory_Transfer` exercise node and can extract amount and reason from that.
+Analogously to 1-step transfers, the sender that creates the withdrawal offer, i.e., the treasury party sees a `TransferFactory_Transfer` exercise node and can extract the amount and reason from that.
-For Canton Coin, both the creation of the `TransferInstruction` as well as the acceptance currently charge fees so the amount specified in the transfer is smaller than the holdings change of the treasury party. Once the CIP for CC fee removal is implemented, this distinction goes away. Currently Canton Coin is the only token on Canton Network charging such fees.
+For Canton Coin, both the creation of the `TransferInstruction` as well as the acceptance currently charge fees so the amount specified in the transfer is smaller than the holdings change of the treasury party. Once the CIP for CC fee removal is implemented, this distinction goes away. Currently, Canton Coin is the only token on Canton Network charging such fees.
{/* COPIED_END */}
@@ -666,33 +666,33 @@ Recall the architecture diagram from the `integration-architecture` section:

-Below you learn how to handle crashes of the integration components, how to handle RPC errors, and how to perform disaster recovery for the Exchange Validator Node.
+Below, you will learn how to handle crashes of the integration components, how to handle RPC errors, and how to perform disaster recovery for the Exchange Validator Node.
## Handling Crashes
-Validator nodes are crash-fault tolerant and do not lose data shared on the Ledger API in case of a crash. Thus a restart is sufficient to recover from crashes. Likewise, we assume that the Canton Integration DB is backed by a crash-fault tolerant database (e.g., PostgreSQL or MySQL).
+Validator nodes are crash-fault tolerant and do not lose data shared on the Ledger API in case of a crash. Thus, a restart is sufficient to recover from crashes. Likewise, we assume that the Canton Integration DB is backed by a crash-fault-tolerant database (e.g., PostgreSQL or MySQL).
For the integration components that you build, we recommend the following strategy to handle crashes and restarts:
- **Tx History Ingestion**: keep track of the last ingested offset in the Canton Integration DB. On restart, continue from that offset. If none is set, then that means it never ingested any transaction. In that case, start from the beginning of the transaction history, i.e., start from offset `0`.
- For this to work, it is important that you store the ingested offset in the same transaction as you store the ingested data. See the individual `integration-workflows` descriptions for details.
+ For this to work, it is important to store the ingested offset in the same transaction as you store the ingested data. See the individual `integration-workflows` descriptions for details.
-- **Withdrawal Automation**: make it stateless, so that it can just restart. This is in line with how we recommend to implement both the `one-step-withdrawal-workflow` and the `multi-step-withdrawal-workflow`.
+- **Withdrawal Automation**: make it stateless so that it can restart cleanly. This aligns with the recommended implementation of both the `one-step-withdrawal-workflow` and the `multi-step-withdrawal-workflow`.
-- **Multi-Step Deposit Automation**: make it stateless, so that it can just restart. This is in line with how we recommend to implement the `multi-step-deposit-workflow`.
+- **Multi-Step Deposit Automation**: make it stateless so that it can restart cleanly. This aligns with the recommended implementation of the `multi-step-deposit-workflow`.
## Handling RPC Errors
-Below we explain our recommendation for handling RPC errors in the integration components you are building. We focus on handling errors from interacting with the Ledger API and the Registry API Servers of the token admins. We do not cover handling errors from accessing DBs or other internal systems, as we assume you have strategies in place for those.
+Below, we explain our recommendation for handling RPC errors in the integration components you are building. We focus on handling errors from interacting with the Ledger API and the Registry API Servers of the token admins. We do not cover handling errors from accessing DBs or other internal systems, as we assume you have strategies in place for those.
-- **Tx History Ingestion**: only reads from the Ledger API. We recommend to retry these reads a bounded number of times on `retryable-errors`. Wait at least a few seconds between retries and consider using exponential [backoff]() to avoid overloading the Validator Node. Consider crashing the ingestion component if the bounded number of retries is exceeded to recover from bugs in the in-memory state of the ingestion component.
+- **Tx History Ingestion**: only reads from the Ledger API. We recommend retrying these reads a bounded number of times on `retryable-errors`. Wait at least a few seconds between retries and consider using exponential [backoff]() to avoid overloading the Validator Node. Consider crashing the ingestion component if the bounded number of retries is exceeded to recover from bugs in the in-memory state of the ingestion component.
- **Withdrawal Automation**: recall from `one-step-withdrawal-workflow` that the Withdrawal Automation first retrieves extra context from the Registry API Server of the token admin and then prepares, signs, and executes the transaction to submit the transfer for the withdrawal using the `/v2/interactive-submission/execute` endpoint of the Ledger API.
- The endpoint is asynchronous and observing its response only means that the transaction has been accepted for processing. You can retrieve the status of the execution via the `/v2/commands/completions` endpoint of the Ledger API; or alternatively, by observing the effect of the execution via the Tx History Ingestion component. The latter option is more robust, as it ensures that you observe the effect of the execution in a persistent manner.
+ The endpoint is asynchronous, and observing its response only means that the transaction has been accepted for processing. You can retrieve the status of the execution via the `/v2/commands/completions` endpoint of the Ledger API; alternatively, by observing the effect of the execution via the Tx History Ingestion component. The latter option is more robust, as it ensures that you observe the effect of the execution in a persistent manner.
We recommend that you retry the steps from the start when not observing the successful completion of the withdrawal within the expected time or when encountering a retryable error on the execution itself. You thereby ensure that you prepare the withdrawal transaction using the latest state of the Validator Node and the latest extra context from the Registry API Server. Use a bounded number of retries with at least a few seconds between retries and consider using exponential backoff.
@@ -702,13 +702,13 @@ Below we explain our recommendation for handling RPC errors in the integration c
- **Multi-Step Deposit Automation**: the approach is analogous to the one for Withdrawal Automation.
- Recall from `multi-step-deposit-workflow` that the Multi-Step Deposit Automation discovers a pending deposit by reading from the Canton Integration DB, then retrieves extra context from the Registry API Server of the token admin and finally prepares, signs, and executes the transaction to accept the transfer offer using the `/v2/interactive-submission/execute` endpoint of the Ledger API.
+ Recall from `multi-step-deposit-workflow` that the Multi-Step Deposit Automation discovers a pending deposit by reading from the Canton Integration DB, then retrieves extra context from the Registry API Server of the token admin, and finally prepares, signs, and executes the transaction to accept the transfer offer using the `/v2/interactive-submission/execute` endpoint of the Ledger API.
- The endpoint is asynchronous and observing its response only means that the transaction has been accepted for processing. You can retrieve the status of the execution via the `/v2/commands/completions` endpoint of the Ledger API; or alternatively, by observing the effect of the execution via the Tx History Ingestion component. The latter option is more robust, as it ensures that you observe the effect of the execution in a persistent manner.
+ The endpoint is asynchronous, and observing its response only means that the transaction has been accepted for processing. You can retrieve the status of the execution via the `/v2/commands/completions` endpoint of the Ledger API; alternatively, by observing the effect of the execution via the Tx History Ingestion component. The latter option is more robust, as it ensures that you observe the effect of the execution in a persistent manner.
We recommend that you retry the steps from the start when not observing the successful completion of the transfer offer acceptance within the expected time or when encountering a retryable error on the execution itself. You thereby ensure that you prepare the transaction to accept the transfer offer using the latest state of the Validator Node and the latest extra context from the Registry API Server. Use a bounded number of retries with at least a few seconds between retries and consider using exponential backoff.
- Retrying all steps is safe from a consistency perspective because the accept transaction is idempotent, as it archives the transfer offer once it is accepted. You nevertheless want to avoid retrying too often, as executing a transaction costs traffic.
+ Retrying all steps is safe from a consistency perspective because the accepted transaction is idempotent, as it archives the transfer offer once it is accepted. You nevertheless want to avoid retrying too often, as executing a transaction costs traffic.
You can stop retrying after a bounded number of retries. The sender can reclaim their funds at any point by withdrawing the offer. The Multi-Step Deposit Automation will learn about the withdrawal of the offer via the Tx History Ingestion component, which will mark the transfer offer as withdrawn in the Canton Integration DB.
@@ -716,7 +716,7 @@ Below we explain our recommendation for handling RPC errors in the integration c
### Retryable errors
-For increased robustness and fault tolerance, we recommend to retry by default on all errors and manage an exclude list of non-retryable errors. As a starting opint, we suggest to exclude the following HTTP error codes from retries:
+For increased robustness and fault tolerance, we recommend retrying by default on all errors and managing an exclude list of non-retryable errors. As a starting point, we suggest excluding the following HTTP error codes from retries:
- 401 Unauthorized
- 403 Forbidden
@@ -729,7 +729,7 @@ As explained in `mvp-for-cc`, the Registry API Server of the token admin for Can
For convenience, every Validator Node provides a Scan proxy [service]() to read from the Scan instances run by SVs with Byzantine fault tolerance. The Scan proxy service also implements the Token Standard Registry API for Canton Coin.
-We recommend to use Scan proxy service of the Exchange Validator Node to retrieve the extra context for Canton Coin transfers.
+We recommend using the Scan proxy service of the Exchange Validator Node to retrieve the extra context for Canton Coin transfers.
If that is not possible, then you can read from a random Canton Coin Scan instance for the purpose of retrieving extra context for Canton Coin transfers. The on-ledger validation of the transfers ensures that you do not need to trust the Scan instance for correctness. Ensure that you read from a different Scan instance on every retry to avoid being affected by a faulty Scan instance for too long.
@@ -751,7 +751,7 @@ We thus recommend the following setup as a starting point to mint rewards and au
1. Use the validator operator party as your featured `exchangeParty`. Follow `exchange-party-setup` to get it featured.
2. `treasury-party-setup` to create a `treasuryParty` with a transfer preapproval managed by your `exchangeParty`.
-3. Setup automatic traffic purchases in the validator [app]().
+3. Set up automatic traffic purchases in the validator [app]().
4. Optional: setup [auto-sweep]() from the `exchangParty` to your `treasuryParty` to limit the funds managed directly by the validator node.
As a starting point for the automatic traffic purchase configuration, set `targetThroughput` to 2kB/s and `minTopupInterval` to 1 minute, which should be sufficient to execute about one withdrawal or deposit acceptance every 10 seconds. Please test this with your expected traffic pattern and adjust as needed. See this FAQ to measure the traffic spent on an individual [transaction]().
@@ -760,7 +760,7 @@ As a starting point for the automatic traffic purchase configuration, set `targe
### Setup the featured exchange party
-As explained above in `reward-minting-and-traffic-funding`, we recommend to use the validator operator party as your featured `exchangeParty`. This party is automatically created when you deploy your validator [node](). Thus the only setup step is to get it featured by the SVs:
+As explained above in `reward-minting-and-traffic-funding`, we recommend using the validator operator party as your featured `exchangeParty`. This party is automatically created when you deploy your validator [node](). Thus, the only setup step is to get it featured by the SVs:
**On DevNet**, you can self-feature your validator operator party as follows:
@@ -779,16 +779,16 @@ That's all. Continue with setting up your treasury party.
**On MainNet**, apply for featured status for your validator operator party as follows:
1. Log into the wallet UI for the validator [user]() on your MainNet validator node.
-2. Copy the party-id of your validator operator party using the copy button right of the abbreviated `"google-oaut.."` party name in the screenshot above.
+2. Copy the party-id of your validator operator party using the copy button to the right of the abbreviated `"google-oaut.."` party name in the screenshot above.
3. Apply for featured application status using this link: [https://sync.global/featured-app-request/](https://sync.global/featured-app-request/)
Wait until your application is approved. The validator node will automatically pick up the featured status via the corresponding `FeaturedAppRight` contract issued by the DSO party for its validator operator party.
-**On TestNet** there is currently no official process, but you should be able to use the same procedure as the one for MainNet.
+**On TestNet**, there is currently no official process, but you should be able to use the same procedure as the one for MainNet.
### Setup the treasury party
-Setup the `treasuryParty` as follows with a transfer preapproval managed by your `exchangeParty`:
+Set up the `treasuryParty` as follows, with a transfer preapproval managed by your `exchangeParty`:
1. Create the `treasuryParty` using the wallet SDK to `create-an-external-party` with a key managed in a system of your choice
@@ -802,8 +802,8 @@ Setup the `treasuryParty` as follows with a transfer preapproval managed by your
You can test the party setup on LocalNet or DevNet as follows:
-1. Setup your `exchangeParty` and `treasuryParty` as explained above.
-2. Setup an additional `testParty` representing a customer.
+1. Set up your `exchangeParty` and `treasuryParty` as explained above.
+2. Set up an additional `testParty` representing a customer.
3. Transfer some CC from the `testParty` to the `treasuryParty` to simulate a deposit.
4. Observe the successful deposit by listing holdings of the `treasuryParty`.
5. Observe about 30' later in the Splice Wallet UI of your validator operator user that the `exchangeParty` minted app rewards for this deposit. It takes 30', as activity recording and rewards minting happen in different phases of a minting round.
@@ -812,9 +812,9 @@ You can test the party setup on LocalNet or DevNet as follows:
Clients need to authenticate as a Ledger API [user]() to access the Ledger API of your Exchange Validator Node. You can manage Ledger API users and their rights using the `/v2/users/...` endpoints of the Ledger [API]().
-You will need to authenticate as an existing user that has `participant_admin` rights to create additional users and grant rights. One option is to authenticate as the `ledger-api-user` that you configured when setting up authentication for your validator [node](). Another option is to log-in to your Splice Wallet UI for the validator operatory [party]() and use the JWT token used by the UI.
+You will need to authenticate as an existing user who has `participant_admin` rights to create additional users and grant rights. One option is to authenticate as the `ledger-api-user` that you configured when setting up authentication for your validator [node](). Another option is to log in to your Splice Wallet UI for the validator operatory [party]() and use the JWT token used by the UI.
-We recommend that you setup one user per service that needs to access the Ledger API. This way you can easily manage permissions and access rights for each service independently. The [rights]() required by the integration components are as follows:
+We recommend that you set up one user per service that needs to access the Ledger API. This way, you can easily manage permissions and access rights for each service independently. The [rights]() required by the integration components are as follows:
| Component | Required Rights | Purpose |
|---------------------------------------------------------------------|---------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
@@ -841,7 +841,7 @@ See the Splice documentation for guidance on how to monitor your validator [node
## Rolling out Major Splice Upgrades
-For major protocol changes, the global sychronizer undergoes a Major Upgrade Procedure. The schedule for these upgrades is published by the Super Validators and also announced in the `#validator-operations` slack channel.
+For major protocol changes, the global synchronizer undergoes a Major Upgrade Procedure. The upgrade schedule is published by the Super Validators and announced in the `#validator-operations` Slack channel.
As part of this procedure, the old synchronizer is paused, all validator operators create an export of the state of their validator, and deploy a new validator connected to the new synchronizer and import their state again. For a more detailed overview, refer to the Splice [docs]().
@@ -855,7 +855,7 @@ From an integration perspective, there are a few things to keep in mind:
### Runbook
-We recommend to roll-out the upgrade as follows:
+We recommend rolling out the upgrade as follows:
1. Wait for the synchronizer to be paused and your node to have written the migration dump as described in the Splice [docs]().
@@ -929,7 +929,7 @@ Follow your internal guidance and best practices on what DB system to use and ho
Follow the Splice documentation on how to restore a validator node from a [backup]() to restore the Exchange Validator Node from a backup that is less than 30 days old.
-The node will resubscribe to transaction data from the synchronizer and recover all committed transactions and the corresponding changes to the set of active contracts (i.e. UTXOs). However validator-node local data written after the backup will be lost, as described on the Canton documentation [page]().
+The node will resubscribe to transaction data from the synchronizer and recover all committed transactions and the corresponding changes to the set of active contracts (i.e. UTXOs). However, validator-node local data written after the backup will be lost, as described on the Canton documentation [page]().
In the context of the recommended `integration-workflows`, this data loss affects:
@@ -981,7 +981,7 @@ Once Tx History Ingestion has caught up, the integration workflows will continue
These steps assume that record times assigned to transactions are unique, which is the case unless you are using participant-local operations that modify the transaction history. These are ACS imports, party migrations, party replication, or repair [commands](). Multi-hosting a party from the start does not lead to non-unique record times.
-If your are using participant-local operations that modify the transaction history, then you we recommend adjusting Step 5 as follows to deal with the rare case of a partial ingestion of transactions with the same record time:
+If you are using participant-local operations that modify the transaction history, then we recommend adjusting Step 5 as follows to deal with the rare case of a partial ingestion of transactions with the same record time:
> 1. Lookup the recovery offset `offRecovery` as of `tRecovery - 1 microsecond`.
> 2. Start ingesting from offset `offRecovery`, but filter out all transactions whose update-id is already known in the Canton Integration DB because they have been ingested before Tx History Ingestion was stopped in Step 1.
@@ -995,18 +995,18 @@ From a data consistency perspective, all writes to the Canton Integration DB by
Likewise, the write in Step 3 of the `one-step-withdrawal-workflow` to mark a withdrawal as failed due to the lack of a CC transfer-preapproval is safe to redo, as it is idempotent.
-Thus the only data loss that you need to handle is the loss of data written by your Exchange Internal Systems to the Canton Integration DB to request the execution of a withdrawal. This data consists in particular of the withdrawal-id, the UTXO reservation state, and the reservation of user funds for the withdrawal. See Step 2 in the `one-step-withdrawal-workflow` and Step 2 in the `multi-step-withdrawal-workflow` for details.
+Thus, the only data loss that you need to handle is the loss of data written by your Exchange Internal Systems to the Canton Integration DB to request the execution of a withdrawal. This data consists in particular of the withdrawal-id, the UTXO reservation state, and the reservation of user funds for the withdrawal. See Step 2 in the `one-step-withdrawal-workflow` and Step 2 in the `multi-step-withdrawal-workflow` for details.
-The problem to avoid is for the user to initiate another withdrawal of the funds whose withdrawal might be in-flight on Canton. You can do so as follows:
+The problem to avoid is for the user to initiate another withdrawal of the funds, whose withdrawal might be in-flight on Canton. You can do so as follows:
1. Disable initiating withdrawals of CN tokens in your Exchange Internal Systems and stop the Withdrawal Automation component.
2. Restore the Canton Integration DB from the backup.
3. Wait until Tx History Ingestion has ingested a record time `tSafe` that is larger than the largest target record time `trecTgt` of all in-flight withdrawals. Assuming you use a constant `ttl` to compute the `trecTgt` of a withdrawal, you can estimate `tSafe` as `now + ttl`.
4. Enable withdrawal creation in your Exchange Internal Systems and start the Withdrawal Automation component. The integration is operational again.
-Step 3 takes care to resynchronize the state of the Canton Integration DB with the state of in-flight withdrawals on Canton. For this to work it is important that you implement Tx History Ingestion such that it can handle ingesting withdrawal transfers whose withdrawal-id cannot be resolved because the corresponding withdrawal request was lost in the restore.
+Step 3 takes care to resynchronize the state of the Canton Integration DB with the state of in-flight withdrawals on Canton. For this to work, it is important that you implement Tx History Ingestion such that it can handle ingesting withdrawal transfers whose withdrawal-id cannot be resolved because the corresponding withdrawal request was lost in the restore.
-We recommend doing so by having the Tx History Ingestion re-create the withdrawal request record from the on-chain data. Likely not all fields can be recovered, so consider either marking the withdrawal as "recovered" and leaving them blank. Alternatively, you can store these fields in additional metadata on the transfer record when creating the withdrawal transfer on-chain. This will though cost additional traffic and may leak information to your customer and the token admin.
+We recommend doing so by having the Tx History Ingestion re-create the withdrawal request record from the on-chain data. Likely not all fields can be recovered, so consider either marking the withdrawal as "recovered" and leaving them blank. Alternatively, you can store these fields in additional metadata on the transfer record when creating the withdrawal transfer on-chain. This will though, cost additional traffic and may leak information to your customer and the token admin.
{/* COPIED_END */}
@@ -1018,9 +1018,9 @@ We recommend doing so by having the Tx History Ingestion re-create the withdrawa
## Test Node Setup
-When testing on your laptop or in CI, we recommend using Splice's [LocalNet](), which is a Docker-Compose based local deployment of a Global Synchronizer and Canton Coin. Automate the exchange parties setup as part of your test setup, so that you can start from a clean state for each test run while reusing the same LocalNet. Thereby achieving test isolation without the overhead of starting and stopping LocalNet for each test run.
+When testing on your laptop or in CI, we recommend using Splice's [LocalNet](), which is a Docker Compose-based local deployment of a Global Synchronizer and Canton Coin. Automate the exchange parties setup as part of your test setup, so that you can start from a clean state for each test run while reusing the same LocalNet. Thereby achieving test isolation without the overhead of starting and stopping LocalNet for each test run.
-Alternatively you can consider setting up a DevNet validator node using either Docker-Compose or k8s as documented in [Splice]() and using that for testing.
+Alternatively, you can consider setting up a DevNet validator node using either Docker Compose or k8s as documented in [Splice]() and using that for testing.
## Test Scenarios
@@ -1030,23 +1030,23 @@ Apart from testing functional correctness, we recommend testing for robustness a
- successfully continuing Tx History Ingestion after a crash
- successfully continuing Withdrawal Automation after a crash
- handling the case that two withdrawal transfers are initiated for the same withdrawal request: test that only one of them succeeds because they spend the same UTXOs, which is detected by Canton
- - successfully continuing of the Multi-Step Deposit Automation after a crash, and handling the case that the deposit offer was accepted while the Multi-Step Deposit Automation was down
+ - successfully continuing the Multi-Step Deposit Automation after a crash, and handling the case that the deposit offer was accepted while the Multi-Step Deposit Automation was down
- Retrying on RPC errors, in particular:
- retries that succeed after a few attempts
- retries that do not succeed within the bounded number of retries, and how the integration code marks the withdrawal or deposit offer as failed.
-- Does your integration code deal well with high rates of deposits and withdrawals. We recommend to determine target throughput rates for deposits and withdrawals and test that your integration code can handle those rates without falling behind. In particular, test:
- - Can Tx History Ingestion keep up with the rate of deposits and withdrawals.
- - Can `utxo-management` deal with the case that there are no UTXOs available to fund a withdrawal.
- - Does your integration code rate limit executing transactions on the Validator Node to avoid running out of traffic with your automatic traffic configuration. See the `validator-node-monitoring` section for more information.
- - Does your `utxo-management` code handle the case where there are only small UTXOs available, and they first have to be merged before they can be used to fund a withdrawal.
- - Does your integration code properly rate limit bursts of deposits and withdrawals above the target throughput rate.
- - Does your integration code gracefully handle a crash when under full load.
-- Does your integration code recover from data loss due to
+- Does your integration code deal well with high rates of deposits and withdrawals? We recommend determining target throughput rates for deposits and withdrawals and testing that your integration code can handle those rates without falling behind. In particular, test:
+ - Can Tx History Ingestion keep up with the rate of deposits and withdrawals?
+ - Can `utxo-management` deal with the case that there are no UTXOs available to fund a withdrawal?
+ - Does your integration code rate limit executing transactions on the Validator Node to avoid running out of traffic with your automatic traffic configuration? See the `validator-node-monitoring` section for more information.
+ - Does your `utxo-management` code handle the case where there are only small UTXOs available, and they first have to be merged before they can be used to fund a withdrawal?
+ - Does your integration code properly rate-limit bursts of deposits and withdrawals above the target throughput rate?
+ - Does your integration code gracefully handle a crash when under full load?
+- Does your integration code recover from data loss due to:
- `validator_backup_restore`
- `restore-canton-integration-db`
-- Does your integration code handle `hard-synchronizer-migration`. Note that simulating a major Splice upgrade is not easily possible with LocalNet. We thus recommend to the check the schedule for major Splice upgrades and ensure that you are ready to handle the first one on DevNet.
+- Does your integration code handle `hard-synchronizer-migration`? Note that simulating a major Splice upgrade is not easily possible with LocalNet. We thus recommend checking the schedule for major Splice upgrades and ensuring that you are ready to handle the first one on DevNet.
-Where possible, we recommend to automate these tests as part of your CI pipeline so that you can run them frequently and with little overhead.
+Where possible, we recommend automating these tests as part of your CI pipeline so that you can run them frequently and with little overhead.
{/* COPIED_END */}
@@ -1060,11 +1060,11 @@ This page describes the following additional features that you can consider addi
## Optimizing App Rewards
-The MVP for all CN tokens described in the `exchange-integration-overview` section comes with the limitation that application rewards are only earned on deposits of CC, but not on deposits of other CN tokens. We recommend to lift this limitation and to improve the profitability of the integration using Canton Coin's featured application activity marker [mechanism](). It allows tagging transactions with a featured application activity marker and earn application rewards for them.
+The MVP for all CN tokens described in the `exchange-integration-overview` section comes with the limitation that application rewards are only earned on deposits of CC, but not on deposits of other CN tokens. We recommend lifting this limitation and improving the profitability of the integration using Canton Coin's featured application activity marker [mechanism](). It allows tagging transactions with a featured application activity marker and earning application rewards for them.
-The idea is to tag both the initatiation of withdrawals and the acceptance of deposit offers with a featured application activity marker to attribute the transaction to the `exchangeParty`. Tagging these transactions is compliant with the guidance given in the Splice [documentation](), as they correspond to transfers and create value for the network.
+The idea is to tag both the initiation of withdrawals and the acceptance of deposit offers with a featured application activity marker to attribute the transaction to the `exchangeParty`. Tagging these transactions is compliant with the guidance given in the Splice [documentation](), as they correspond to transfers and create value for the network.
-In order for the `treasuryParty` to create featured application activity markers in the name of the `exchangeParty`, a delegation contract is required. A suitable delegation [template]() called `DelegateProxy` is part of the [splice-util-featured-app-proxies]() package. We recommend to use this package and template as explained in the sections below.
+In order for the `treasuryParty` to create featured application activity markers in the name of the `exchangeParty`, a delegation contract is required. A suitable delegation [template]() called `DelegateProxy` is part of the [splice-util-featured-app-proxies]() package. We recommend using this package and template as explained in the sections below.
### Earning App Rewards for Withdrawals
@@ -1079,7 +1079,7 @@ The following steps describe how to adjust the Withdrawal Automation to tag with
3. Change the Ledger API user setup such that the
1. the user used by Withdrawal Automation also has the `readAs(exchangeParty)` right
- 2. the user that performs the exchange parties setup also has the `canActAs(exchangeParty)` right.
+ 2. the user that performs the exchange party setup also has the `canActAs(exchangeParty)` right.
4. Add a step to the treasury party setup to also create a `DelegateProxy` contract with `provider = exchangeParty` and `delegate = treasuryParty`.
@@ -1094,7 +1094,7 @@ The following steps describe how to adjust the Withdrawal Automation to tag with
The call to the choice takes the `proxyCid` and the `featuredAppRightCid` as parameters alongside the actual transfer parameters. Pass in the `featuredAppRightEventBlob` as an additional disclosed [contract]().
-The Tx History Ingestion as described here does not need changing, as it descends into the `TransferFactory_Transfer` choice that is called by the `DelegateProxy_TransferFactory_Transfer` choice.
+The Tx History Ingestion, as described here, does not need changing, as it descends into the `TransferFactory_Transfer` choice that is called by the `DelegateProxy_TransferFactory_Transfer` choice.
### Earning App Rewards for Deposits
@@ -1114,19 +1114,19 @@ Sharding your treasury over multiple treasury parties may be interesting to redu
You can shard your treasury over multiple parties as follows:
-1. Setup multiple treasury parties instead of using a single `treasuryParty`. Use the setup described in the `treasury-party-setup` section for each of them.
+1. Set up multiple treasury parties instead of using a single `treasuryParty`. Use the setup described in the `treasury-party-setup` section for each of them.
2. Run one instance of Tx History Ingestion, Withdrawal Automation, and Multi-Step Deposit Automation for each treasury party.
3. Share the Canton Integration DB across all instances, but adjust the schema such that UTXOs and pending multi-step transfers are tracked per treasury party.
4. Change your Exchange Internal Systems such that they select the treasury party as well as the `Holding` UTXOs to use for funding a withdrawal. For large withdrawals that surpass the funds available to a single treasury party, you can either rebalance the funds across multiple treasury parties or split the withdrawal into multiple smaller ones.
## Multi-Hosting the Treasury Party
-The documentation on setting up the exchange party describes how to setup a party with a single confirming node. This can be sufficient but the confirming nodes for the party are essential to keep your party secure and compromise of them could lead to loss of funds. Refer to the trust model trust model for more details.
+The documentation on setting up the exchange party describes how to set up a party with a single confirming node. This can be sufficient, but the confirming nodes for the party are essential to keep your party secure and compromise of them could lead to loss of funds. Refer to the trust model for more details.
-To guard against compromise of the confirming nodes, you can setup your `treasuryParty` with multiple confirming nodes and a threshold N \> 1. As long as less than N nodes are compromised, your party is still secured. Common setups are:
+To guard against compromise of the confirming nodes, you can configure your `treasuryParty` with multiple confirming nodes and a threshold N \> 1. As long as fewer than N nodes are compromised, your party is still secured. Common setups are:
1. Two confirming nodes with a threshold of 2. This provides security against a single node being compromised. However, if one of the two nodes is down, transactions for the party will fail.
-2. Three confirming nodes with a threshold of 2. This extends the previous setup to also provide availability in case one of the nodes goes down or gets compromised as the other two nodes are still functional.
+2. Three confirming nodes with a threshold of 2. This extends the previous setup to also provide availability in case one of the nodes goes down or gets compromised, as the other two nodes are still functional.
### Party Setup
@@ -1136,7 +1136,7 @@ To guard against compromise of the confirming nodes, you can setup your `treasur
-As part of the initial treasury party setup, you generate the `PartyToParticipant` topology transaction which lists both the confirming nodes and the confirmation threshold. To host a party on multiple nodes, you need to include all confirming nodes in the `PartyToParticipant` mapping when you setup the party initially. Note that at this point, the wallet SDK library does not yet support this so you must go directly through the Canton APIs. This is expected to change soon.
+As part of the initial treasury party setup, you generate the `PartyToParticipant` topology transaction, which lists both the confirming nodes and the confirmation threshold. To host a party on multiple nodes, you need to include all confirming nodes in the `PartyToParticipant` mapping when you set up the party initially. Note that at this point, the wallet SDK library does not yet support this, so you must go directly through the Canton APIs. This is expected to change soon.
Until then, the easiest way to do so at the moment is through the Canton console. You can find a full reference for all required steps in the integration test. Note in particular that you must sign the `PartyToParticipant` mapping not just by your party's key but also by all confirming participants. This is accomplished through the `participant2.topology.transactions.authorize` step in the test.
@@ -1146,9 +1146,9 @@ Any .dar file that you upload, both as part of the initial setup but also whenev
### Reading Data and Submitting Transactions
-Both nodes serve all transactions for the `treasuryParty` and can thus be used in principle to read them. However, offsets are not comparable across nodes so it is recommended that to run Tx History Ingestion against the same node under normal operations. If you do need to switch nodes, you can do so following the same procedure used for restoring a validator from a backup to resynchronize Tx History Ingestion against the offsets of the new node.
+Both nodes serve all transactions for the `treasuryParty` and can thus be used in principle to read them. However, offsets are not comparable across nodes, so it is recommended to run Tx History Ingestion against the same node under normal operations. If you do need to switch nodes, you can do so following the same procedure used for restoring a validator from a backup to resynchronize Tx History Ingestion against the offsets of the new node.
-Preparation and execution of transactions can also be done against any of the confirming nodes of the party. However, Command Deduplication is only performed by the executing node so if you submit across nodes you cannot rely on it. It is therefore recommend \_[not]() to rely on command deduplication at all in favor of UTXO and max record time based deuplication.
+Preparation and execution of transactions can also be done against any of the confirming nodes of the party. However, Command Deduplication is only performed by the executing node, so if you submit across nodes, you cannot rely on it. It is therefore recommended \_[not]() to rely on command deduplication at all in favor of UTXO and max record time-based deduplication.
@@ -1160,11 +1160,11 @@ Link to recommended deduplication strategy [https://github.com/canton-network/wa
There are some limitations on changing the set of confirming nodes:
-Removing confirming nodes is possible by submitting a new `PartyToParticipant` topology transaction. However, this can leave the nodes that you remove in a broken state so this should be limited to cases where that node got compromised or is no longer needed for other purposes.
+Removing confirming nodes is possible by submitting a new `PartyToParticipant` topology transaction. However, this can leave the nodes that you remove in a broken state, so this should be limited to cases where that node got compromised or is no longer needed for other purposes.
Adding new confirming nodes is not currently possible. If this is required, you need to instead:
-1. Setup a new treasury party with the desired set of confirming nodes.
+1. Set up a new treasury party with the desired set of confirming nodes.
2. Either transfer all funds from the existing treasury party to the new one and switch only to the new treasury party or rely on `treasury-sharding` to use both treasury parties until you are ready to phase out the old party.
Changing the confirmation threshold is possible at any point by submitting a new `PartyToParticipant` topology transaction with the updated threshold.