ckETH: a canister-issued Ether twin token on the IC

Hello everybody!

I would like to start a forum thread on ckETH, where we can discuss the design and post updates wrt progress.

What is ckETH?

  • Chain-key Ether, or ckETH, is an IC-native token that represents Ether (ETH), the native token of Ethereum. Anybody that has $ETH can use a canister smart contract on the IC to convert that into ckETH. Vice-versa, any holder of ckETH can choose to receive the underlying ETH instead. All ETH backing ckETH is held by a canister smart contract using the threshold ECDSA functionality.
  • Since ckETH is an IC-native token, it can be transacted very fast and with low fees, and is easy to use from other IC dapps. ckETH would follow the ICRC1 and ICRC2 standards.
  • If you’re familiar with ckBTC, then you can understand ckETH as the equivalent of ckBTC but for Ether instead of Bitcoin.

How do we propose to build it?

DFINITY is working on an implementation for ckETH, with the following components.

  • A ledger canister, an archive canister, and an index canister. These three canisters are used for many ledgers on the IC, including all SNS tokens. One change here is that they need to support 256 bit amounts.
  • an Ethereum smart contract to help make converting ETH into ckETH easy.
  • a “ckETH minter” canister. This canister is responsible for converting between ETH and ckETH. It has minting capabilities on the ckETH ledger, and holds all the ETH using tECDSA.

How would converting ETH into ckETH work?

We plan a smart contract that exposes a function “deposit”, which takes as argument a byte string. To convert x ETH into ckETH, a user would call this deposit function and encode its IC principal as bytes in the argument. It attaches x ETH to the call. This smart contract emits an event for every deposit call.

The ckETH minter canister, using the Ethereum integration, periodically looks up the finalized height of the Ethereum blockchain, and looks if there are new deposit events registered. If so, then for each deposit event, it mints the corresponding amount of ckETH to the principal that was encoded in the deposit call.

How would converting ckETH into ETH work?

A user holding ckETH that wants to convert x ckETH into native ETH would first have to approve the ckETH minter canister to take at least x of its ckETH, by doing an ICRC2 approve. The user then calls the ckETH minter canister, saying it wants to convert x ckETH into ETH, and specifies an Ethereum address where it wants to receive the ETH. The ckETH minter canister then tries to burn x ckETH from the user (using an icrc2_transfer_from which uses the previous approval from the user), and then construct a native Ethereum transaction sending the user ETH. We are planning that the user is responsible for the gas fees on Ethereum, so in this example, the user would receive x minus gas fees ETH.

How does the ckETH minter communicate with Ethereum?

In the long run, the plan is to use the native Ethereum integration, where IC replicas run Ethereum nodes. While we are in “phase 1” of Ethereum Integration, the ckETH minter will communicate with Ethereum by contacting providers that support the Ethereum JSON-RPC API using HTTPS outcalls. To ensure no single point of failure, the minter will contact multiple providers for any security relevant state-changing operation (e.g., minting or withdrawal) and only proceed if all received responses are consistent. How many and which providers will be contacted is still to be defined.

Discussion & people involved

We hope this forum topic can serve as a good place to discuss all things around ckETH, and keep everybody up to date on our progress. From DFINITY’s side, we have @0rions, @gregory-demay, @roman-kashitsyn, @dieter.sommer, @bogwar, @benji, and myself (@manu) involved.

So please feel free to ask questions here, challenge the design / propose alternatives, we look forward to discussing ckETH!

40 Likes

This is beneficial to icp ecology.

1 Like

Why 256 bits for amounts? 128 are enough, or?

2 Likes

For the ETH → ckETH the user needs a wallet with call abilities (not just ETH transfer ability). So sending from some wallets and exchanges won’t work, right?

3 Likes

If we can send coins with call data. Would that be easier? Just like a memo and clearly declaring the deposit event. Then we can get tx response and see if the events matching the payload.

Similar to: Introducing Ethscriptions - Ethscriptions

3 Likes

If we can send coins with call data. Would that be easier?

We considered this design as our first option. Unfortunately, it has a severe drawback: the raw Ethereum JSON RPC interface does not provide a way to efficiently query transfers by the receiver address. Scanning the entire Ethereum chain from a canister is too expensive to be practical, and using third-party proprietary APIs (Etherscan, Alchemy) would undermine the decentralized nature of ckETH. So, the deposits would have to function in a very non-user-friendly way:

  1. The user submits a transaction with their principal encoded in the TX data.
  2. The user waits for the transaction to finalize.
  3. The user obtains the transaction hash and notifies the ckETH Minter canister about the incoming transaction.
  4. The minter gets the transaction receipt and mints ckETH tokens to the principal encoded in the TX data.

That’s a lot of complexity compared to executing a single deposit on a smart contract and letting the minter take care of the rest.

7 Likes

Why 256 bits for amounts?

Ethereum transactions use 256 bits for amounts.

1 Like

I don’t understand. You mean in the interface?

There are 120m ETH * 10^18 = 1.2 * 10^26 wei in circulation which is 87 bits. It’s safe to assume that this number will always be <128 bits. To represent a wei balance in the ledger 128 bits are therefore sufficient. It shouldn’t matter what Ethereum uses internally.

I don’t understand. You mean in the interface?

Yes, Ethereum yellow paper specifies the transaction amount as being a value in N_256 (“the set of all non-negative integers smaller than 2^{256}”)

There are 120m ETH * 10^18 = 1.2 * 10^26 wei in circulation which is 87 bits. It’s safe to assume that this number will always be <128 bits. To represent a wei balance in the ledger 128 bits are therefore sufficient.

Even if the total amount of Wei in circulation is always smaller that 128 bits, it makes sense in my opinion to follow the specification. This does make it more future-proof (no assumption on the state of Ethereum) and also ease inter-operability with other crates that also represent transaction amount with 256 bits, like for example ethers-rs

1 Like

U256 is a custom implementation from the ethereum_types crate. Do you know how it is laid out in memory? The balances are probably going to be responsible for the biggest part of memory use of the ledger. Just afraid you are going to store 16 bytes of zeros for each balance.

The yellow paper describes the EVM which stores all integers (whether they represent balances or something else) in 256 bit. I don’t think it means much for how wallet code should store ETH balances.

Since in the OP it is mentioned as a change away from the existing ledger implementations, I was thinking it would be more beneficial to not alter the existing ledger implementation (compared to being compatible with the ethereum crate you mentioned).

U256 is a custom implementation from the ethereum_types crate. Do you know how it is laid out in memory?

U256 from ethereum_types is defined as an array of 4 u64 (it uses the construct_unint! macro). Another implementation I know of U256 is from ethnum-rs and uses 2 u128.

Since in the OP it is mentioned as a change away from the existing ledger implementations, I was thinking it would be more beneficial to not alter the existing ledger implementation.

To my knowledge all the other icrc1 ledgers use 64 bits amounts (which as you pointed out is not sufficient for Ethereum) and only the ckETH ledger uses 256 bits. Their representation in canister memory is constant size (i.e., 8-byte array for u64 and 32-byte array for u256, see for example the implementation of Storable on U256). @roman-kashitsyn knows for sure here more than I do.

The balances are probably going to be responsible for the biggest part of memory use of the ledger. Just afraid you are going to store 16 bytes of zeros for each balance.

I think you definitively have a point here and the amounts could be stored as u128 on the ledger. I do have the impression that this will only ever be a problem if we start having that many transactions/accounts on the ledger, which would actually be nice :grinning:. Maybe @roman-kashitsyn has some further inputs regarding u128 vs. u256 to store amounts on the ckETH ledger?

1 Like

Seems like the ICRC-1 standard uses nat for amount which should support values without any limit, so the spec seems to support it without any issue.

On the Rust side of the canister, changes will indeed be needed since it only seems to support up to 128 bit at the moment. Rust doesn’t natively support u256, hopefully this can be abstracted within the Candid::Nat implementation so users don’t need to implement arrays of u128 or u64.

Edit: I see that U256 support in the ICRC-1 ledger is already implemented.

Regarding transaction history storage canister, the ICRC-3 spec defines nat storage as leb128 so there shouldn’t be any limit there and even with 256 bits it should be less than 32 bytes due to leb128 encoding.

I assume the ledger canister itself also stores amounts as leb128 bytes instead of u64/u128/u256?

Thanks @Manu . When do you expect ckETH and ckERC20 tokens will be rolled out?

1 Like

Is it possible to support l2 in the same time?

1 Like

Hey Timo!

So sending from some wallets and exchanges won’t work, right?

That is correct. This is clearly a downside, and we did think a lot about this. We propose to do it this way because it makes things much easier:

  • we want that all funds backing ckETH end up in a single ETH wallet
  • we must know which principal to credit for a deposit

So if we want to do deposit purely with an ETH transaction, it will be user’s eth wallet → intermediate address → ckETH pot, so two ETH transactions. This is much more annoying wrt UX and fees etc. For that reason, we thought it’s better to start with the flow based on a smart contract call, such that we can launch ckETH asap. We can then always add another layer onto it which allows for user deposits by just doing an ETH transfer. Happy to hear your thoughts on this.

@dfisher Our rough timeline is ckETH end of 2023, and ckERC20 Q1 2024, but of course plans may change and we may run into setbacks.

@famouscat8 we currently don’t have plans for ck-tokens for Ethereum L2s, we want to focus on the Ethereum assets now. But I think the code required for that would be very similar, so it’s something we could consider in the future, or the community could pick up.

1 Like

Thanks @manu. I would strongly suggest rolling out ckUSDC with ckETH in Q4 if that’s possible. I would argue ckUSDC will be just as if not more valuable than ckETH given we don’t have any stable coins on the IC and defi needs to ramp up … the other ckERC20 coins are less pressing

4 Likes

First of all really enjoyed reading the simplistic nature of this complex project and the answers of the questions that were raised thus far.

Hypothetically speaking from the future, would the transfer mechanism always use “icrc2_transfer_from” method or function? Or does that depend on how the project would be maintained; Say in a few years developers start using ICRC3, for the project to work efficiently, would the transfer method/function need to change as well?

1 Like

Providing both (and one of them first) seems like a good idea to me.

You can’t wrap centrally issued tokens in this way like you can with BTC, ETH, etc. The problem is the issuer can freeze the whole pool and they will if there is one transaction going on that they don’t like, making all ckUSDC worthless even if they aren’t related to that transaction. Central issuers must issue natively on ICP.

6 Likes