Transcript
This transcript was autogenerated. To make changes, submit a PR.
Everyone, welcome at my talk, typesafe client
for smart contracts. Actually, I told
a little bit different in the intro, but actually that's what
we're going to look at. So we're going to look
at how we can parse smart contracts and generate typescript
definitions for that. Now, first of all,
my name is Albert Rotede. I'm the architect at Kadena
for developers Experience squad.
We work with developers and the core teams to achieve
the most convenient way possible
for our end users. But first,
let's go into what this is. So what we actually built is Kadena
client. Kadena client is a multipurpose tool to
build transactions, sign transactions, and submit
those transactions to the blockchain and listen to any events
that happen after they have been submitted.
But first, something else. Who knows
what this is? Who knows the series? Whats this is?
This is actually a very nice
series that I like. I like the environments
and the way it's portrayed. And actually,
this is, anyone who knows
already know whats it's whats this is. This is
actually the expanse. So let's imagine
that you are a consumer and
you are paying youre bills for what you
consume. In this case, Netflix is the
one that is the receiver
of a certain amount of money from youre.
Now, we can actually do this in a smart contract.
Imagine building a service where you offer subscriptions
to give users access to
all the streams that you have available. And let's imagine that we
do this on the blockchain. So how would youre go about that?
Well, first we would create a function called a subscribe.
So here in the subscribe function, youre can pass two things.
One is the account and the months.
Well, actually, the subscribe function in the smart contract holds
some logic that automates secrets
and enforces some predefined arguments without the
need of an intermediate party or centralized authority.
Except this intermediate party is the blockchain, which is a decentralized
one. In the case of subscription, we have
two arguments. One argument is called accounts and one
month. Each of those represents a part of the agreement
that we make between two parties.
Account and months have to be filed somewhere.
In this case, the months and the
account is being written to a record. We have
a function that's available in the smart tv smart
contract. Netflix's smart contracts. It writes
a record and then retrieves it by the key of the account name.
And this is then stored in a database. And for
the amount of months, we multiply that with a certain amount of money,
which is then paid for. So we think of smart contracts
as sort of digital agreements that automatically
execute themselves when some predefined conditions are met,
and we want to record that payment in the blockchain.
So to do that, we execute inside
the subscribe function body another write
record, which actually is something from a different smart
contract. The smart contract called coin,
where we transfer money. In this case, we transfer money
from the sender, represented by the account of
the user, to the receiver.
In this case, whoever accepts this payment,
and then some amount, which is the amount
that's calculated, times some price under
the hood. When this call happens, it will include the
coin module. The coin module itself has its own schema that
will hold the amount of coins that someone
owns. Whenever the transfer function is
being called, the sender's balance is checked
whether it's sufficient in order to deduce the balance such that it
doesn't go below zero. In this case,
the key of each record is the account name, and the
value in this case is the balance.
So, a smart contract is
a piece of code that is stored on a decentralized
platform. A smart contract contains
a definition of a schema and definitions.
To interact with a schema, a function
can then restrict the operations that you can
do in that schema. In short,
a smart contract is a set of secure,
serverless functions that can interact with a database
on a decentralized platform. So how do we do that?
We do that by using pact. Pact is a turing
incomplete language that provides an interface to the Kadena
blockchain. Now, what does Turing incomplete mean?
Turing incomplete means that there's no way to
do recursion or loops, which actually
makes the language a lot more safe.
It means that you cannot make the mistakes that you can make with other languages.
For example, you cannot get into an infinite loop, and the loop
is limited to the size of whatever you're working with.
So you can only apply a function on a set of items
in, for example, an array or a list. You can
also not get into stack overflow errors when
you are doing a recursion or a while loop that's never
stopping. But because of these limitations,
we can introduce something called model checking.
Model checking is the way we can make
our smart contracts 100% testable,
and this is done by executing former verification.
Let's look at that. To give an example,
the transfer function in this case has a conserve
mass property. The conserve mass property
means that in the table of the smart contract,
all the end results of all the balances accumulated
needs to be zero. I mean, it should conserve mass
youre cannot introduce new money, you cannot deduct new money
from the system as a whole. Now if
a certain function, for example the transfer function in this case
would be able to remove
money from the system or print money into the system,
then this conserve mass model would fail.
So the combination of a language being Turing
incomplete and whats form of verification makes it
a very very smart contract language.
And we actually have a nice overview of
the things that could be saved
by Kadena. When youre would use the
packed smart contract language instead of know competing
blockchain language that is not Turing incomplete,
you would have been able to save a lot of money.
So quick recap onto smart
contracts. A piece of code that is stored on a decentralized
platform. You could say a serverless function
contains a definition of a schema and functions to interact with a schema and
a function can restrict these operations. How do we do that?
Let's go into this one first.
Here we see that we have the
coin schema. So this is a schema definition of
what this particular smart contract will work with.
In this case, the coin smart contract will work with a
key value pair where the value is defined
as balance and guard. And the
key is actually the id that we're working with,
which is the account name. So it looks like this every record
in the table. Whats a key by which it can be looked up.
So when youre retrieve values, you don't search for an account or use a where
statement, but you look it up by key. This makes it really fast
and performant. In this case, the key is the account name.
The account name can be an arbitrary string with some limitations.
Now the value is the rest of the schema and the
balance and the guard. The guard is as
simple as a function that just returns true or false
when a set of conditions are met. In this case, it's a key guard.
We'll go into that detail a little bit later. Now let's
look at the functions that can interact with the schema.
So these functions need to limit the interaction
in a certain way that it's safe to work with it.
Now how do we do that? So let's go over the function first.
So this function is called a diff on,
and the first part is the name of the function
and its return value. The second part is
actually the arguments that we have. Then there
are some guards that will limit how
you can interact with this function. So if the sender
is the same as the receiver, then you cannot be both
the sender and the receiver. For transfer, what it enforces
is not sender and receiver. Then we
validate account. We see if the sender account and the receiver
account exist and if the amount is above zero,
because if you would make a transfer to someone else minus
something, you would receive money, which is of course that's something
that we don't want to do. So this is some business logic that
we work with. Now let's check out how a function
can restrict the operations to the record of that schema.
So how do we do we do that? By introducing a with capability
statement. A with capability statement takes
two arguments. If you look closely, the first
argument is this section, and the second argument
is the rest of the function, which is kind of like a callback
in JavaScript. What does it do? So when the
capability is executed, when its expression is
being interpreted and run, it checks for two things.
Did the signer sign for this capability?
In this case, the sender. Did the sender sign that
this capability can be executed?
If so, execute the code.
Now, the execution of the code is this part.
Here we see that we debit the sender a
certain amount, and then we credit the receiver
a certain amount. First we debit the sender by
the amount of what the user wants to send to the other
user. And then based on some
information that we read from the database, we then credit the receiver
some amount. So this is what it looks like.
The function is being executed and the changes are being
introduced. So this is the function,
coin transfer from Albert to John,
133.7 kda,
for example. It first checks that
the balance is sufficient and
then it will transfer this money. So after this it will be
done and John will be credited with more money than he
had before. Now let's take a look at what the transaction looks like
in the blockchain. So the transaction is actually a
JSON with two parts. In this case.
One part is the code that's being executed, and the
second part is the signers that need to sign for this code.
We see that there is a matching public key. The public key is
part of whoever owns and
wants to sign for this capability.
What we see that needs to be signed is the coin transfer
with its arguments. When this is signed, it will
allow the function to be executed for these specific arguments
to transfer money from Albert to John with
the amount of 133.7 kda.
Now if we take a closer look at the transaction, there needs to be a
signature as well. We sign for the transaction
by seeing which public key matches to which signature.
So whenever we sign something, we will make sure that
Albert Pubkey will be the owner of
whatever is being signed here. So why do we need
a Javascript client? Well, let's go
back a little bit and take a look at how we do this in
the back end world or in the regular web
two world where we have a back end and SQL and a database
table. So let's imagine you have a node JS service where you
have some business logic and you want to write some SQL to update
a database table. Similarly, we have
a front end and we build up our transactions and
use an API to send it to the smart contract and
then it will retain its value in the
blockchain database. Well actually it's a little bit more complex
because when we are working with the wallet,
when we are working with a transaction, we need to sign it as well.
Now in order to make sure that it's signed, we only
then can send it to the blockchain. So what we actually do is we build
a transaction, we sign the transaction,
then we send it to the blockchain and we wait for it to be validated.
So this is a whole bunch of logic that we want to abstract away from
our general user. So first part is writing,
building the transaction. We want to write the code coin transfer
with the correct parameters, we want to add the
relevant authorizations and then we want to set the
correct parameters, for example network id, chain id,
who is going to pay for the transaction cost, et cetera.
Now let's go back to youre backend example.
We have now a Prisma client.
If you know what Prisma is, you know that it takes the information
from the database table and it will convert it to a typesafe client
that can interact with your database based
on SQL. So whenever you generate this code,
you will get a typesafe client that can
interact with your database through SQL
and it will even optimize your queries if you want.
Now that's exactly what we want to do with this Kadena client
as well. We want our front end to be able to work
with the transactions API without the
need to write youre own jSon.
So how do we do that in JavaScript? We have
a packed builder. What we actually want is to
have some intellisense. We want to have
information from the blockchain
to work with the smart contract. So imagine
this to be a rest endpoint or a table in the database.
And then whenever you type this out, youre will see that it
hints for all the functions that you can call from
the coin module and you get also information
on the arguments that you get. And it's
typesafe. Now because of this one,
we also need to add a signer,
and we need to sign for the transaction that we built here. So here
we say, okay, this is the command I want to execute,
and this one, the add signer part, will then
make sure that you have to include this. And this
is also being done type safely, which is really neat.
And then we have some plumbing logic in order to make
sure that we can send this transaction to the blockchain successfully.
So how do we do that? How do we go from a smart contract
on one hand to a typescript definition that can be used
in this library to build an awesome
client? First we need to look,
take the smart contract, put it into the parser,
and this is where we actually go through the wormhole and
check out how a parser works. I want to take a little
bit of a sidestep here and I want to get your attention to
a parser. So what we describe in a parser is
that we say this is the structure of our program.
So in this case the structure of a number is
something whats we see here. But how do we explain
to the machine and to something
that interprets that whats we actually are working with a
number. So we say, okay, number is
a regular expression of zero
to nine with a certain amount
certain times. So let's see, this one is already matching,
as you can see, but we have more numbers.
We can create a number of multiple individual numbers which
is still the same number. So we use star for that.
Now when we want to do some processing, we want to
see that this number is actually something we
can join together. As you saw here, it's still a set
of individual parts, but actually we want to take all
these individual parts and make it one number. So that's
how we do whats. So we add some Javascript logic that then takes the first
argument of the result and uses
it to join them together. Because the first argument of the
result whats actually already an array, we take
this one and we join it together into single
element. So now we have an array of a single element,
but we can even do that like multiple times. Maybe we can
do it like this, but we need to write our grammar for
that. So whats we can say is, okay, we have a white
space here and then another number.
How do we deal with that? We need to add a white space.
So a white space is usually composed of an
underscore. Were we see that now we actually
have it working. But we want, of course,
this part to be flexible. We can have two
numbers and then we can have subsequent numbers. So what we
can do is we can wrap this around and say,
okay, you can have multiples of those. And now,
as you see, we have a set of numbers, the first set,
second set, and the third set. So this is basically
the grammar of a language. And in this
case, the language is a programming language which sums some
things together. Now we want to introduce an
operator. So we want to actually make sure that we can work with a
plus symbol and ideally also with
an underscore. Now, an operator can be something like
a plus. And this is actually how we do that.
So we now have number, operator number. And then also
we can say, okay, in this case, the white
space is optional, so we can make it like this.
And then we also have the double underscore to identify that
it should be mandatory. So here we say, okay, this one
is optional, which means that now we can also write this one.
Now, in order to process the operator in a logical way,
we can add some processing logic where we say, okay,
we have an operator, for example o.
And then we will return a type operator.
And the operator in this case for the plus is called sum.
What we see now is that this element is now converted
to a sum element to an object, whats is a sum.
And of course we want to ignore this object.
So we just strip them out. And now wherever they
are used, we can leave them out so we can add a
filter. And now we have a nice part were we
can work with. But actually we want
to do something more. We want to add another operator,
because operator is not just a plus,
but also a minus, for example,
that we can do this way. And now this one works as well.
And then finally we can
merge all these parts together and have only
one object. Whats says first
operator in last?
This is how we can do that. In this case, we have left,
which is the one that we see here. This is the first
element. We have d two,
which is the third element, one, two, three.
And then we have the operator, the value of
the operator function. Or we can leave it like an object. And then we
have left right operator. And the operator in
this case is a sum. But as we already
have defined the operator here, it's nice to use
the value. All right, let's go back to the wormhole
and into our presentation.
So we saw that there is a smart contract with
a transfer function and a capability,
and we actually want to convert these into the
parts that we actually need. So we
need to define that it's a function. So we
need to find all the functions that are here, like the methods.
Then we need to find the parts that correspond
to the name of the function and its return type.
We need to identify its arguments. So how do we
do that? All right, let's look at the right side.
Here we have a parser. And a parser is actually
a set of rules that we combine in order to find
what the thing is that we are looking at. So how this works
is a method. Whatever we put in were
has to start with a unlock. Now, a block
is matching two parentheses. So each code
unlock impact is a starting and an ending parentheses.
This will work for mostly anything. Then it will see
whether there is a kind. Well, it's not jquery,
but what we actually do here is we use this part to create
the elements that we want to extract from the
code. So here you see the matching properties
that correspond to the matching statements.
So, for example, kind whats a statement
that we match with, et cetera, et cetera.
Now, how this works is it will go through every
parser and see how much of this part
will match to whatever I'm looking at. So when
we have a unlock, then I expect to find a
kind. If I'm working with a function, actually,
I'm expecting to find one of
the types that we have here, in this case, a defin. And if it
matches, then that's good. Then if
we find an atom, it still matches to
what we call a method. If we find an atom,
we want to store it. We store it here in the transfer, in the
name property. Now,
we could potentially have a return type, and a
return type is a type rule because there is a column at the
start and then a value.
So the type rule is actually a combination of atom and the column
in front. A type rule means this is the
definition of a type rule, which is colon and
then atom. And if we have it, we want to store it
as the return type. And now we're actually going a little
bit deep, and we're going to look at if
we get a sequence. So we still say, okay, we have a
block. This part is the block, starting with
apprentices opening and closing parentheses. And then
it should maybe repeat with
a set that has an atom and a type rule.
So an atom and a type rule,
and this can be repeated multiple times, can even
be omitted. The type rule can be omitted,
and that's why we say maybe. But if these
rules are still matching to whatever we see, we are confident
that these are parameters for the method.
So here you see that we see a sender
which matches here receiver that
we are going to store here, and then of course also add a decimal amount.
Now we store this in the parameter section and
then we want to look at how we can add the
information that we need to sign for this transaction.
So let's look at the partial output, and it's a little bit bigger
than we just looked at. So this is actually the parser output.
And on the other side we have a generated typescript definition.
So how do we go from the parser output
that we have defined here that we created from this parser and
go to a typescript definition, because the typescript definition
is then in the end what we need to use, because here you see actually
the matching part of the function call that we had. Well,
we can basically just take that and interpret it and
make a big string concatenation. So we have a
kind which is called a module. So if we find a module, then we
are going to use the coin part, the name of it, and use it
in the interface. This is actually matching
with the pack modules coin part. So this coin
matches to this coin which matches to this coin
which matched to the parsed smart contract.
Inside the smart contract is a set of
functions annotated by an array and then
an object in that array. And here we see that the kind
is a defin because that's how pact defines functions.
So we write it down in our generated typescript definitions
and then it matches to what we can use in the
client code. The same thing we do for
the parameters and we can include type information.
If the type information was omitted, we could write
sender is any for example.
And now because these are matching, we can also create the same
part for the client code. Now actually the parser
output is a little bit bigger because we also have the with capabilities.
And in this case this function introduces the capability
transfer. Now the capability transfer is
the one whats we return from here,
because that's going to be used in the wrapper function,
in the builder, this case. And here
we can see that the capability is this capability with
the coin transfer as a name. And if you are going
to add a coin transfer, you want to sign for
the coin transfer which has to have these arguments.
So we are kind of recreating everything that is possible
in pact, but then from JavaScript,
and this is then the completed transaction builder.
So here you can see that by sending money
using the coin transfer function with a sender receiver
and some amount we can then say okay,
we have a signer and we are going to sign for in
this case the gas which is the transaction fee and
the transfer which is actually the
authorization that we want to give to the blockchain
to deduct some amount from the record where
sender is the key and credit some amount for the record were
receiver is the key. So this is basically
transaction building with javascript type save.
We automatically know what we need to fill in.
Beautiful. So let's make a quick
recap. So the first thing that we built was an expression builder.
Sorry, let me just take a quick sip. So we create
an expression builders to create packed expressions
in a typesafe manner by extracting
the defense from the smart contract.
Then we have a transaction builder.
The transaction builder is the plumbing around the transaction,
around the code. Actually, to make sure that this
code can be executed, a few things that are needed
are adding a signer, for example adding
some metadata, which network you want to use, et cetera.
And then we can sign this transaction.
And for that we have some utilities. For example sign with Chainweaver or
sign with wallet, connect or sign with something else.
Then we want to submit it to the blockchain.
Inside. Kadena client is also a fetch
interface that we can use to communicate with the blockchain.
And finally we have some information to submit it
and also to listen to the
transaction and wait for the transaction to be
verified. And once the transaction is verified we know
that the receiver has received his money.
Now I said we have signed with Chainweaver, but there is also
a bunch of other tools that we can use to sign with.
So thank youre very much. We have built a
blockchain client to read
and write and listen to transactions on
the blockchain. This way front end is closer
to the blockchain than you thought previously.
Thank you very much.