To transfer an asset — other than Lumens — between two Stellar accounts, both accounts must have a trust line for that asset. This constraint has been a consistent source of friction for users. In Protocol 15, we are realizing our vision that money should move like email by making it easy to transfer assets to an account without a trust line.
Suppose that you live near the border between Canada and the United States. Your wallet contains a mixture of United States dollars (USD) and Canadian dollars (CAD). This allows you to easily shop in both countries. In order to keep track of your finances, you maintain a ledger with two columns. Each night, you record the amount of USD that you own in the first column and the amount of CAD that you own in the second column.
A trust line is the representation of a column from your hypothetical ledger on the Stellar blockchain. Specifically, a trust line records the balance of a single asset held by a single account. If an account does not have a trust line for a certain asset, then that account cannot hold that asset because there would not be a place to record the balance. The only exception to this rule is for Lumens. Because every account can always hold Lumens, the balance of Lumens for an account is stored in the account itself.
A trust line actually stores more information than just the balance. It also stores
At first glance, it seems weird to set a limit: wouldn’t I always be happier with a higher balance? The world is not quite so simple. The name trust line is a reflection of the fact that assets on Stellar are actually credits; you trust the issuer to redeem them for the underlying assets. If you don’t think the issuer is very trustworthy, then you probably want to limit how much of their assets you own. Not having a trust line at all is the ultimate statement of lack of trust, because you are saying that you refuse to hold any of that asset.
Trust lines often make it difficult to do things that should be easy. Suppose that you go out for drinks with a friend. The bar is crowded, so your friend offers to put your drinks on his tab. You agree, and offer to settle later on Stellar since you both have accounts. But when you go to settle your bill later, things are not so simple. You attempt to send USD from your account to your friend's account. The payment fails because the USD that you hold was issued by a different account than the USD that your friend holds.
Being an avid Stellar user, you are not deterred. You check which USD asset your friend holds, then send a path payment which converts your USD to your friend's USD. To your surprise, this fails as well. It turns out that your friend has a low limit on his USD trust line because he doesn't think the issuer is highly credit-worthy. Your payment would send him over the limit.
You no longer know how to satisfy your agreement with your friend. You are frustrated, because you always keep your word. You call your friend, who tells you that you can just settle in cash the next time you see each other. It is very clear to you that money is not moving like email.
In your frustration, you wonder why the trust line is not created automatically when you send a payment. This proposal was actually heavily debated within the Stellar community. While it seems like a straightforward solution, it has a number of complications.
The first complication is that this proposal alone is not enough to solve the problem in the general case. As a concrete example, your second attempt failed because the trust line limit was too low not because the trust line did not exist. This problem could be avoided by simply ignoring the trust line limit entirely. Automatically creating trust lines implies this, because a trust line that does not exist is conceptually equivalent to a limit of 0.
The second complication is that this proposal does not work for authorization required assets. A trust line that is created automatically cannot be authorized automatically, because authorization is at the discretion of the issuer. But the balance of a trust line that is not authorized can never increase. So even though the trust line could be created automatically, it still would not permit the payment to succeed.
The third complication is that this proposal ignores the fact that creating a trust line also creates a reserve requirement. In order to mitigate denial-of-service attacks on the Stellar network, users must pay for each piece of information that is stored on the ledger. If a payment automatically creates a trust line, who should pay for the reserve? The sender or the recipient? If the recipient pays the reserve requirement, then it would be possible to attack accounts by creating many "spam" trust lines which lock up balance. This is clearly unacceptable. But if the sender pays the reserve requirement, then we have created a new source of friction because reserve requirements are much higher than transaction fees.
There are more challenges with this seemingly straightforward proposal. But already at this point, it appears that this problem is not so simple after all. The debate around this proposal roused the passions of the community because it was easy to see the importance of solving the problem, but hard to find a solution that was sufficiently robust. This created a storm of new ideas.
This discussion inspired me. Constraints produce creativity. How could I create a favorable user experience that works in many circumstances without introducing new problems? The first step was to cast the problem into a format that was easier to evaluate. I settled on the following question: can you create a mechanism on Stellar where payments can be sent successfully without considering the receiving account, and where payments can be received successfully without considering the sending account? Because the sender of a payment cannot make any guarantees about the receiving account, such a payment must create some kind of intermediate state. Sending the payment becomes “create some intermediate state” and receiving the payment becomes “act on the intermediate state by consuming it”. This observation was the foundation for what I would subsequently refer to as two-part payments.
But an observation is not a proposal — no matter how much we might want it to be. Now the hard work would begin. What were the requirements on the intermediate state? First, we have to handle the complications listed above:
Requirement (2) leads to a new and interesting problem. If the intermediate state can hold authorization required assets, then there must be an authorization check before the user can incorporate the balance into their trust line. As noted above, authorization is at the discretion of the issuer. If an issuer refuses to authorize the recipient, then the funds are stuck. The sender would likely want to recover the funds in that case. My initial solution was to add an expiration time, where the receiver could accept the payment before the expiration time and the sender could recover the payment after the expiration time.
As noted above in my discussion of the third complication, having the sender pay the reserve is a new source of friction. To mitigate that friction, the reserve would have to be refunded to the sender when the payment is completed. If the sender wants to be able to recover the reserve quickly, then they could simply set an expiration time that was in the near future.
Once I had a vision for how to solve the problem, I turned my attention to making the proposal better. Much of the proposal was rigidly fixed by the requirements, but the expiration time stood out to me as an opportunity for improvement. I found myself asking the following questions:
I had no good answers, so I set out to reinvent the expiration time. I settled on the motivating question: can two-part payments be extended to enable entirely new payment primitives on Stellar? Some simple payment primitives that are interesting include
Some combinations of these may also be interesting, for example a multi-recipient recoverable payment. A good design should not restrict the possible primitives to a few types but should instead make them customizable. Simple payments and recoverable payments are already possible with the two-part payments described above, but the design is too rigid to allow either cancellable payments or multi-recipient payments. The solution required two changes.
I first had to abstract away the idea of a sender and a recipient. The intermediate state should instead contain a list of possible recipients. If the sender is a recipient, then it is just a specific instance of this more general framework. No separate logic is required to handle the case where the sender is a recipient, so it is not even a special case.
Second, I had to abstract away the idea of an expiration time. Instead, the expiration time was viewed as a specific example of a generic predicate. A predicate is any statement about which you can ask, "Is this true or false?" For example, the statements “This is a coin,” “This is a quarter,” and “This is a dime” are all predicates. A more complex predicate is “This is a coin, and this is not a quarter or a dime.” This predicate is true for a nickel, and false for a dollar. The interesting observation is that the complex predicate was formed from the three simpler predicates using the boolean operators “and”, “or”, and “not”.
Now I just needed a way to represent the possible predicates. In order to achieve flexibility, I wanted to create some simple predicates which could be combined using logical operators. For the initial release of two-part payments, there was a consensus to only include predicates that are time-based. An example time-based predicate is “The current ledger close time is before November 1st, 2020.” The beauty of the design, however, is that it would be easy to add new predicates. It would be only a small modification to add predicates that are based on ledger sequence instead. As in the example above, the predicates can be combined with and, or, and not.
The final design of two-part payments pairs every recipient with a predicate. In the context of two-part payments, the recipients are called “claimants” because they have the potential to claim the intermediate state. Similarly, the predicates are called “claim predicates” because they determine whether that claimant can actually claim the intermediate state.
Let’s return to the example of settling a bill on Stellar. You weren’t able to repay your friend using the existing payment primitives that Stellar provides. But you will be able to do so effortlessly with two-part payments.
All you have to do is send a two-part payment to your friend. As introduced in CAP-0023, a two-part payment is sent with the CreateClaimableBalance operation. Your friend can take whatever actions are necessary to prepare his account, then use the ClaimClaimableBalance operation to receive the funds. You probably want to include yourself as a claimant in case your friend is unable to accept the payment, refuses to accept the payment, or if you end up settling in cash. Remember that a two-part payment cannot be deleted. One of the claimants has to claim the funds, or the money cannot be recovered.
We set out to reduce friction related to trust lines. By solving that problem, we also created a new framework for powerful payment primitives. Challenges are just new opportunities.
[Interested in learning more about Protocol 15 and beyond? Don't miss Jon Jove's technical keynote at Meridian 2020.]