Conversation
There was a problem hiding this comment.
Pull request overview
Adds architecture documentation intended to serve as the design reference for the ckSOL canister suite (especially the ckSOL minter).
Changes:
- Added a comprehensive
ARCHITECTURE.mddescribing deposit/withdrawal flows, fee model, and operational mechanisms. - Added
EXAMPLE_TRANSACTION.mdcontaining a concrete Solana deposit transaction example and corresponding RPC output.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 13 comments.
| File | Description |
|---|---|
| ARCHITECTURE.md | New end-to-end architecture/design document for ckSOL minter + ledger interactions and Solana RPC usage. |
| EXAMPLE_TRANSACTION.md | New reference example of a Solana deposit transaction and getTransaction response payload. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| In order to mitigate the risk of a denial-of-service attack, each account has a certain **quota** for the automatic flow, which consists of a quota for `getSignaturesForAddress` calls and a quota for `getTransaction` calls. Initially, the quotas are `MAX_GET_SIGNATURES_CALLS` and `MAX_RETRIEVED_TRANSACTIONS`, respectively. Calls of either type are only made if there is a positive remaining quota. | ||
|
|
||
| Since each IC account gets a newly derived deposit address, these addresses are likely involved in deposits only, i.e., there should not be many `getTransaction` calls in vain in the common case. Whenever there is a successful call that results in a mint, both quotas are increased by 2 for the following reason: They are both bumped by 1 so that calls that result in a mint do not count against the quotas. The additional bump of each quota serves to ensure that an occasional call that does not result in a mint operation does not slowly drain the free quota. Note that RPC calls may sporadically fail for various reasons such as network issues or RPC providers being unavailable. There is a ceiling of `MAX_GET_SIGNATURES_CALLS` and `MAX_RETRIEVED_TRANSACTIONS` for the `getSignaturesForAddress` quota and the `getTransaction` quota, respectively. | ||
| The mechanism to replenish a depleted quota is discussed in the next section. | ||
|
|
||
| The quota is meant as a deterrent but does not stop an attacker from triggering many `getTransaction` calls for different addresses. In order to limit the impact of such an attack, the constant `MAX_MONITORED_ADDRESSES` specifies the global limit on the number of addresses for which automatic deposits are allowed at any given time. This constant also limits the memory consumption. Furthermore, `update_balance` only takes a subaccount parameter, i.e., it is not possible to call the function for other principals, preventing drainage attacks against the quotas of other users. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces architecture documentation intended to be the single source of truth for the ckSOL canister suite design (with emphasis on the ckSOL minter), and adds a concrete Solana deposit transaction example used by the docs.
Changes:
- Added
ARCHITECTURE.mddescribing deposit/withdrawal flows, timers/quotas, consolidation, fees, and the exposed API. - Added
EXAMPLE_TRANSACTION.mdwith a realgetTransactionRPC example and response payload for a SOL deposit transaction.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| ARCHITECTURE.md | New end-to-end architecture/spec doc for ckSOL minter flows, parameters, and public API. |
| EXAMPLE_TRANSACTION.md | Reference Solana transaction + RPC output used by the architecture doc. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 1. `get_deposit_address(opt principal, opt subaccount)`: Returns the Solana address derived from the provided principal ID and subaccount. If no principal ID is provided, the principal ID of the caller is used. | ||
| 2. `update_balance(opt subaccount)`: Returns `ok` if the address derived from the caller’s principal ID and the provided subaccount, if any, is being tracked. | ||
| 3. `process_deposit(opt principal, opt subaccount, signature)`: Processes the transaction for the given signature. If the transaction is processed successfully, the deposit status is returned. Otherwise, an error is returned. | ||
| 4. `withdraw(opt subaccount, amount, address)`: Burns the given amount of ckSOL from the user’s account and transfers the same amount minus a fee in SOL to the given user address. Returns the block index of the burn operation on the ckSOL ledger in case of success. Otherwise, an error is returned. | ||
| 5. `withdrawal_status(block_index)`: Returns the withdrawal status (`NotFound`, `Pending`, `TxSent`, `TxFinalized`) for the withdrawal identified by the given block index. | ||
| 6. `get_minter_info`: Returns information about the ckSOL minter, specifically the various fees, the minimum depot and withdrawal amounts, and the current balance of the ckSOL minter. No newline at end of file |
|
|
||
| The **manual deposit fee** must only cover the cost of the signature in the consolidation transaction where the funds are transferred to the main account. This cost is 5000 lamports. Adding again a safety margin, the manual deposit fee could be set to 10,000 lamports, i.e., **0.00001 SOL**. | ||
|
|
||
| For either depot flow, when `x` SOL are deposited in a deposit address, the user receives `x` SOL minus the (automatic or manual) deposit fee in his/her account. |
| RPC-->>Minter: transaction_id | ||
| ``` | ||
|
|
||
| All transactions are created on a timer. Since a transaction must contain a recent block hash, such a block hash must be obtained first: A `getSlot` call is used to get a recent slot, followed by a `getBlock` call to retrieve block details, in particular the block hash, for the slot received in the first step. Note that it is possible that there is no block for a certain slot, in which case `getSlot` needs to be called again, followed by another call to `getBlock`. The figure only shows the happy path of one call each. Given a recent block hash, the transaction is built, obtaining an EDDSA signature for each transfer to be made within that transaction. Once the transaction is signed and serialized, it is sent to the SOL RPC canister, which forwards it to the RPC providers. |
| The mechanism to replenish a depleted quota is discussed in the next section. | ||
|
|
||
| The quota is meant as a deterrent but does not stop an attacker from triggering many `getTransaction` calls for different addresses. In order to limit the impact of such an attack, the constant `MAX_MONITORED_ADDRESSES` specifies the global limit on the number of addresses for which automatic deposits are allowed at any given time. This constant also limits the memory consumption. Furthermore, `update_balance` only takes a subaccount parameter, i.e., it is not possible to call the function for other principals, preventing drainage attacks against the quotas of other users. | ||
|
|
|
|
||
| The manual flow is triggered by calling `process_deposit` with the user’s account (principal ID and subaccount) and the signature identifying the transaction as parameters. This endpoint requires cycles to be attached. As specified in Section 2.3.2., **1T cycles** must be attached to the call. | ||
|
|
| Let `x`T cycles denote the left-over cycles. As shown in Section 2.3.2., depleting the quotas consumes roughly 0.44T cycles. Given that `x` is at least 0.9738, there are enough cycles to replenish both quotas, i.e., after this operation, both quotas are again at their respective maximum values. | ||
| The cost of replenishing the number of allowed `getSignaturesForAddress` calls from `y` to `MAX_GET_SIGNATURES_CALLS` is `(MAX_GET_SIGNATURES_CALLS-y)*5` G cycles (rounding up the derived cost of 4.3G). Similarly, the cost of replenishing the number of allowed `getTransaction` calls from `z` to `MAX_RETRIEVED_SIGNATURES` is `(MAX_RETRIEVED_SIGNATURES-z)*8` G cycles (rounding up the derived cost of 7.5G). | ||
| Given the suggested parameters, the cost is at most 0.45T cycles. Thus, at least 0.5238T cycles are refunded. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This PR adds the architecture documentation as the single source of truth for the design of the ckSOL canister suite, in particular the ckSOL minter.