A little over a month ago, I wrote part 1 of this multi-part series, detailing my journey into the world of Stellar Smart Contracts (SSC’s).
Since then, I have received some wonderful feedback, compliments, doubts, concerns, etc.
Many of these concerns were valid and today, with a completely free schedule, a warm cup of coffee, and a fully-charged computer, I sat down and looked to build off of the basics I described in pt. 1.
I will first define a few basic concepts so that we are all on the same page. If you are already familiar with the basic building blocks of SSC’s feel free to skip this section.
Every account has a sequence number that is used to define the ordering of transactions within the Stellar Network for this account. Each time an account constructs a transaction, it gives that transaction a sequence number. This number must be strictly increasing — if account A has a sequence number of 1, the ONLY valid transaction for Account A is a transaction with sequence number 2.
There are 11 (soon to be 12) operations defined in the Stellar Network. An operation is defined as: an individual command that mutates the ledger. While the most common operations are payment and create account there are many other useful operations such as set options which allows you to change various settings for your account.
Every operation has an associated threshold — low, medium, high. This defines the signature weight required to perform an operation.
In Stellar, every account has a list of signers and each signer has an associated weight. For an operation to be valid, the weight of all signers added to a transaction must be greater than or equal to the threshold weight. For example, if a threshold is set to 2 and each signer on an account has a weight of 1, we need at least 2 of these signatures for this transaction to be valid.
Signers can be added and removed with ease, allowing for the trivial creation of n-of-m multi-signature accounts.
There are three different types of signers that can be added to an account — account id (public key), pre-authorized transactions, and Hash(X). In this article, we will ignore Hash(X)
Technology is nothing. What’s important is that you have a faith in people, that they’re basically good and smart, and if you give them tools, they’ll do wonderful things with them. — Steve Jobs
So yeah in the spirit of Steve Jobs and innovation, let’s venture into the weeds and begin to use some of Stellar’s lesser known features.
A pre-authorized transaction is exactly what it sounds like — it is a transaction that has been constructed and signed, allowing the holder of that transaction to publish it to the network whenever he/she feels (however, don’t forget that the sequence number must still be valid). Cool? No?
Well it gets better!
This pre-authorized transaction can be added as a signer to an account. If the correct weight is given to this signature, this allows you to broadcast a transaction without any other signatures. Here is an example:
Account Abalance: 9sequence #: 3low threshold: 1med threshold: 1high threshold: 1
TX A:Sequence #5Pay B 5 XLM(NO SIGNATURE NEEDED)
TX B:Sequence #4Set Options:Signer: HASH of TX ASigner_weight: 1
Now:
Note: Once transaction B has been submitted, it will be removed as a signer from Account A.
Still not cool yet?
No worries, the usefulness of this tool will be obvious soon :)
Let’s apply this knowledge to the example from last time. As a quick reminder, here is an overview of the problem I am looking to solve:
Consider the user A who seeks to pay B,C,D for a service. If the service succeeds, B,C, and D are paid. If the service fails, only B and C are paid. B is a trusted party, capable of determining whether the service succeeds or fails.
And here is the solution from last time:
Account Abalance: 9 XLMsequence #: 3
A constructs two transactions:
**_TX A:_**Sequence #4 Pay B 3 XLMpay C 3 XLMPay D 3 XLM
**_TX B:_**Sequence #4Pay B 3 XLMpay C 3 XLM
A signs both these transactions and gives them to B. Once the service is finished, B submits TX A or TX B depending on the success of the service.
However, note one of the major flaws I did not address in the previous example (thanks to Alexander for the comment):
Alice [A in the example above] could just issue another transaction that increases the sequence number
Unfortunately, yes she could.
So how can we lock these funds and prevent Alice from spending the money she has set aside to pay B, C, and/or D?
To do this, we will use our new tool: pre-authorized transactions.
Let’s jump right in.
A creates an account E with her 9 XLM and sets all thresholds to 1.
Account Ebalance: 9 XLMsequence #: 1low threshold: 1med threshold: 1high threshold: 1
B constructs two transactions, just like A did last time, except this time B will set the sequence number to be sequence number+2:
**_TX A:_**Sequence #3 Pay B 3 XLMpay C 3 XLMPay D 3 XLM
**_TX B:_**Sequence #3Pay B 3 XLMpay C 3 XLM
A will construct a third transaction with the hashes of the transactions given to her by B:
**_TX SETUP:_**Sequence #2Master Key weight: 0Add TX A HASH as a signer w/ weight: 1Add TX B HASH as a signer w/ weight: 1
A signs TX SETUP with Master Key E and submits it to the network.
Woah, woah, woah, what just happened?!?!
We now essentially have the same setup as before, but this time, the 9 XLM are locked — A has no way of accessing her funds. Even more important, since A cannot access account E, TX A and TX B are both guaranteed to be valid until one of them is spent. As a bonus, since B constructed both transactions and only sent the hashes to A, only B has the power to submit the final transaction.
As per one of the comments on pt. 1 (thanks to Xavi):
It would be interesting if you implemented time-bounds to those operations for when transaction A and transaction B can be sent by Bob (e.g. after 30 days, the house should be built).
A can construct a third transaction, TX C, that will refund her the 9 XLM if B doesn’t submit either transaction before a certain date. Here is the edited setup:
A creates an account E with her 9 XLM and sets all thresholds to 1.
Account Ebalance: 9 XLMsequence #: 1low threshold: 1med threshold: 1high threshold: 1
B constructs two transactions, just like A did the last time, except this time B will set the sequence number to be sequence number+2:
**_TX A:_**Sequence #3 Pay B 3 XLMpay C 3 XLMPay D 3 XLM
**_TX B:_**Sequence #3Pay B 3 XLMpay C 3 XLM
A will construct a third, refund, transaction that is only valid after X days:
**_TX C:_**Sequence #3Pay A 9 XLMTime bound: X days
A will construct a fourth transaction with the hashes of the transactions given to her from B and from her own refund transaction:
**_TX SETUP:_**Sequence #2Master Key weight: 0Add TX A HASH as a signer w/ weight: 1Add TX B HASH as a signer w/ weight: 1Add TX C HASH as a signer w/ weight: 1
A signs TX SETUP with Master Key E and submits it to the network.
So where does the trust lie?
Yep, that is about it…
Consider the alternative in Ethereum, NEO, EOS, etc. At some point, you would need to introduce trust into the system — someone has to make a final judgement on whether or not the service has been completed.
Thus, for a simple, binary operation like this, where we are distributing payment based on an observable (but not necessarily programmatically observable) outcome, we can use a Stellar Smart Contract.
SO BOOM! You now have a pretty complex smart contract implementable in JavaScript, Go, C#, Ruby, Python, Scala, Swift, and (soon to be) Rust, deployable for an affordable 0.00001 XLM/operation.
THE CODE: Here is a basic example of adding a pre-authorized transaction as a signer written in JavaScript.
Disclaimer: While I am a current employee of Stellar.org, these views represent my own and not those of SDF. That being said, these conclusions were developed while experimenting during my free time and are based on real code rather than personal biases.
Disclaimer continued: While I stand by my words, I am a novice smart contract programmer and may be incorrect in my assumptions. If so, please comment below — I am always looking for feedback.