We recently looked into the problem related to the identification of the ChainFusion-controlled addresses on other chains and calculating the overall TVL that they hold. Here we are proposing multiple potential solutions together with their up- and downsides and hoping for the feedback from the community and actual canister developers.
Technical problem definition
The Externally Owned Addresses (EOAs) operated by the canister (through the threshold signing capability of ChainFusion) are generated based on the code of the canister. A canister developer can even dynamically define a derivation path used to generate a new key pair, which makes public addresses impossible to determine externally.
Potential solutions
Solution A: a derivation path indexer
Perhaps the most theoretically straightforward implementation would’ve been to parse the derivation path and any other relevant information from the ICP blocks, similar to how the ICP Sub-Account Indexer works, but in relation to canisters that are interfacing with ChainFusion to sign external transactions.
Technical details
Since EVM or Bitcoin-like blockchains are fully deterministic and store all historical data onchain, it seemed that extracting the derivation path from the ICP block data would’ve been as well possible given known 1) code of the canister 2) state of the canister at that block 3) function being called together with 4) exact arguments with which the function is being called. But given the nature of the ICP and a state info not being readily available, this option is technically impossible (in addition to being very compute-intensive to simulate and parse traces, even in theory).
Evaluation
+ Does not require participation from canister developers
- Isn’t technically feasible on the ICP
Conclusion
This particular solution is technically impossible.
Solution B: modification of the management canister
Theoretically, the management canister could either modify existing implementations for signing (sign_with_ecdsa, sign_with_schnorr) or expose additional methods (sign_with_ecdsa_public and sign_with_schnorr_public) that will store derivation_path, key_id and canister_id (sufficient to later reconstruct the public key) or even directly store the reconstructed address itself.
Technical details
The idea that management “canister” can be stateful (in some way collect and expose submitted data) is inspired by the design on the Display standard in SUI. It is of course rather a design decision for the protocol developers to have such capabilities or leave them for 3rd parties to implement on the dapp level or offchain.
Evaluation
+ Does not require participation from canister developers
- Likely requires modifying more than just a management canister, but capability of the management canister to be stateful
Conclusion
This is the easiest solution for developers, as it would only require changing the methods to generate signatures, while offloading most of the work to the capability of the protocol itself.
Solution C: a new canister standard
This solution would require defining a new standard for exposing derivation paths. The technical implementation would require submitting a formal ICP Proposal and facilitating its adoption by providing example implementations in popular languages.
Technical details
As soon as a canister signs a message for the external chain (i.e. uses sign_with_ecdsa or sign_with_schnorr) using unique derivation path, the canister itself can expose a standard public functions returning the derivation_path and the key_id used in signing. For example:
public type ExternalAddress = {
derivation_path // byte strings array with at most 255 items
key_id // struct specifying both an algorithm and a name exactly as used for signing
};
public shared query func get_schnorr_addresses() : async [ExternalAddress] {};
public shared query func get_ecdsa_addresses() : async [ExternalAddress] {};
// ... and potentially other query functions
This information fetched externally will then be enough to determine public keys by either calling the management canister (using schnorr_public_key and ecdsa_public_key methods) or by potentially computing public keys completely offline using existing ic-pub-key Rust or Typescript libraries.
In order to make indexing of the TVL more straight-forward, a potential improvement of the standard would be to include a specific Chain ID into the collected data that follows caip2 (or any other cross-chain standard). For example:
public type ExternalAddress = {
derivation_path // byte strings array with at most 255 items
key_id // struct specifying both an algorithm and a name exactly as used for signing
chain_id // chain id following a cross-chain standard
};
Evaluation
- Requires participation from canister developers to adopt
- Collecting data across all canisters (in order to calculate overall TVL of ChainFusion) would require external indexer
- Since the exposed data cannot be the address itself (as it will be possible to provide any address), the address has to be later derived from the provided data. Therefore, it will be harder to integrate derivation logic into services like Dune which does not offer custom code execution
Conclusion
As it’s not possible to expose a simple address + chain pair by the canister itself, this solution makes it complicated for regular users to work with the exposed data.
Solution D: a new “registry” canister
This solution boils down to the implementation and deployment to the ICP of a canister acting as a central registry which will accept calls from other canisters and expose collected information. Basically, it’s a middle ground between options B and C described above. This solution would also require a formal ICP Proposal, but the main details will be encoded into the actual code of the registry canister.
Technical details
We can develop and deploy a new canister which can be called by any other canister when they want to publicly register their ChainFusion-controlled addresses . The canister will expose methods such as:
// @param derivation_path – byte strings array with at most 255 items
// @param key_id – struct specifying both an algorithm and a name
// @param chain_id – chain id following a cross-chain standard
public shared func submit_external_address(derivation_path, key_id, chain_id) : async () {};
The canister will then reconstruct the public key (using schnorr_public_key or ecdsa_public_key, depending on the provided key_id) and store the actual address in an array of other addresses
public type ExternalAddress = {
canister_id // canister that called the function above
chain_id // chain id following a cross-chain standard
address // address reconstructed from the derivation_path and key_id
};
public shared query func get_schnorr_addresses() : async [ExternalAddress] {};
public shared query func get_schnorr_addresses_by_canister_id() : async [ExternalAddress] {};
public shared query func get_ecdsa_addresses() : async [ExternalAddress] {};
public shared query func get_ecdsa_addresses_by_canister_id() : async [ExternalAddress] {};
// ... and potentially other query functions
Potentially, as a measure against DoS and flooding attacks, we can add a pre-requirement that the submission comes with a small payment or submitted address holds any amount of gas or have transaction history. This pre-requirement can also be validated at the time of the submit_external_address call or after a certain delay.
Evaluation
- Requires participation from canister developers to adopt
+ Defines a central place where all ChainFusion-based addresses are collected and exposed
+ The most offchain-friendly option that allows easy integration with 3rd party tools like Dune that can directly use exposed addresses, join with their balances and easily calculate the TVL
Conclusion
It is the most balanced technical solution which requires no modifications to the management canister or introduces breaking changes, integrates well with Dune and similar indexers, and provides the least overhead for the canister developers.
Solution E: Offchain API
An offchain version of D. Implementation of a HTTPS API that accepts calls from anyone wanting to proof cross-chain ownership. This solution would not require formal ICP proposal, but likely a simple notification on the forum. The developed API and minimal UI will need to be maintained after the grant to make collected data accessible.
Technical details
We can develop a simple offchain API which will have 2 public endpoints:
addresses/submit accepting multiple required arguments:
- address – the address to verify
- canister_id – canister id for which we’re proving the address
- derivation_path – byte strings array with at most 255 items
- key_id – struct specifying both an algorithm and a name
- chain_id – chain id following a cross-chain standard
This data is then:
- Reconstructed into a public address
- Compared with the submitted address
- Checked that the address contains any know tokens
- If checks above pass, submitted data is added to the database
addresses/list that simply returns checked data from the database
Additionally, a minimal UI will be developed to submit new address and list or search collected addresses. Both the data to reconstruct addresses and the source code for doing the verification will be public and open-source.
Evaluation
- Requires participation from canister developers
- While being offchain, requires server maintenance
+ Defines a central place where all ChainFusion-based addresses are collected and exposed
+ Can be “integrated” with Dune if the API will provide CSV-like export
Conclusion
This solution is probably the simplest to develop, with the only downside being long-term maintenance of the server after the development and protection against potential DoS attacks.
Overall recommendation
We think that the simplest to develop solution that will solve the problem is (E): offchain API, with (D) being the best decentralized solution. Although the easiest for the canister developers would certainly be (B): to extend ChainFusion management canister with the new methods and a state.
Additionally, for any chosen solution, a documentation with examples would be a good addition to educate developers on how to utilise collected data and calculate TVL using either raw RPC calls to the relevant chains or offchain services, indexers and block scanners.
Next steps
We ask the community (developers working with ChainFusion, protocol developers) to provide feedback on the solutions in general and recommend the solution they see the most suitable.