cChannels are a series of reusable payment channel contracts that support both Ether and ERC20 payments. When the funds are deposited into a cChannel, they are then locked into Compound and are constantly earning interest for the depositor, while being spent off chain. Users can directly add more funds to the contract or even borrow against different collateral types supported by Compound to fund their channel (Borrow DAI against ETH to fund a DAI cChannel). Compound works well with payment channels because (excluding a black swan event) the worst outcome is an interest rate of 0% while the channel is open. So effectively, as the sender you can have your money working for you while locked up in a payment channel and as the recipient you can be reasonably confident that the channel will be solvent by the time you go to close it and claim your funds.
How It's Made
Compound: The interest accrued is generated by having the underlying assets of the cChannels be locked into Compound and held as cTokens. Users can borrow against a different asset to fund their channels (ex Eth for Dai), but they must be sure that the amount they borrow is not too high so not to be liquidated. As a result, the interface tells the user a safe borrow max. The function that calculates the safe borrow max receives data from Compound's Comptroller and Price Oracle Contracts and calculates what the max amount that the user is able to borrow, then returns 75% of that value as a safe max. When the channel is ready to be closed, the channel converts its cToken back into the underlying assets. Signatures: EIP712, which is the same scheme used by 0x for signing orders, is used by the cChannel contracts to verify signatures that are generated by using eth_signTypedData_v3 on the frontend. The signatures take into account two fields. First, the amount that the user wants to sign over and second, the current value of the contract's channelNonce. The channelNonce is what allows the channels be reusable. Every time the channel is closed or forced closed, the channelNonce is incremented, as a result, once it is incremented all the signatures signed with the previous channelNonce become invalid. The signature verification scheme also takes into account the address of the channel, so that signatures can't be spent in different channels with the same sender address. Lastly, the signature verification scheme takes into account which network the user is on, so signatures that are meant for a channel on Kovan can't be spent on a different chain. Closing a cChannel: There are two different ways to close a cChannel. First, the recipient calls the close function and provides a valid signature and a corresponding amount. The contract then makes a call to the factory contract providing it with the signature, the amount that was inputted as well as the channel's address and current channelNonce. It then checks against this data if the address that signed the signature is the same address as the sender using function from the OpenZeppelin cryptography library. If it is a valid signature, then the Factory returns a true value and the close function continues to run. If it doesn't, then the Factory returns a false and the transaction reverts. Second, when the channel is first created, the sender specifies and endTime which is the max amount of time that the channel can be open. If the recipient hasn't submitted a signature before the endTime, then the sender is able to call the force close function and withdraw their funds. This forceClose function is meant to act as a method of last resort if for some reason the recipient is not able to close the channel. Also, the endTime can be extended by calling the extendEndTime as long as the msg.sender for the transaction is sender's address and the proposed endTime is greater than the current endTime. The Factory Contract: This contract serves as a single point of record for all the cChannels that have been created. Users call a method in the factory contract to create a new cChannel. Which one they call depends on what the underlying asset of the channel is. One function is explicitly for creating an ETHcChannel and one is for an ERC20cChannel. Two different channel contracts are required because the way Ether and ERC20 token are handled are different. When a new channel is created, it is logged in the factory's channel registries which are formatted like this: mapping(address => address) public senderRegistry; mapping(address => address) public recipientRegistry; In addition, the factory keeps track of the number of channels an address is associated with so that it is possible to know how many elements are stored in each address' registry array. Formatted like below: mapping(address => uint8) public senderCount; mapping(address => uint8) public recipientCount; Creating New Channels: In order to reduce gas costs and redundancy, cChannels uses EIP 1167 to create news channels. This EIP allows there be only to be one contract deployed, which holds all the functions of the channel contracts. The cChannels that are clone from that contract, but have their own unique addresses and state. Every time someone makes a function call to a cChannel, the channel makes a delegate call to the original contract they were cloned from and updates state accordingly. Early benchmarking has shown that creating a new cChannel using this EIP costs a little over 300,000 gas.