Conf42 JavaScript 2023 - Online

Typesafe Client for Smart Contracts

Video size:

Abstract

Get ready to supercharge your integration in the blockchain! At Kadena, we’re thrilled to introduce our groundbreaking creation: the @kadena/client library. Designed to simplify the integration of smart contracts into all your javascript software.

Discover how we expertly parse smart contracts written in Pact and generate TypeScript definitions that unlock a world of type safety. What’s even more impressive is that our library is entirely focused on type definitions, meaning no runtime code generation is necessary. This gives great flexibility for us, the generator builders, to add improvements without breaking changes.

Join to unravel the secrets behind @kadena/client’s flexibility, adaptability, and seamless integration with the blockchain. Whether you’re a blockchain enthusiast or a Javascript developer eager for new possibilities, this talk explains to you the principles of retrieving data from the blockchain.

Summary

  • Albert Rotede is the architect at Kadena for developers Experience squad. Kadena client is a multipurpose tool to build transactions, sign transactions, and submit those transactions to the blockchain. The talk will look at how we can parse smart contracts and generate typescript definitions.
  • A smart contract is a set of secure, serverless functions that can interact with a database on a decentralized platform. Pact is a turing incomplete language that provides an interface to the Kadena blockchain. Model checking is the way we can make our smart contracts 100% testable.
  • A smart contract is a piece of code that is stored on a decentralized platform. A serverless function contains a definition of a schema and functions to interact with a schema. A function can restrict these operations. Let's take a look at what the transaction looks like in the blockchain.
  • A parser is basically the grammar of a language. In this case, the language is a programming language which sums some things together. We want to make sure that we can work with a plus symbol and ideally also with an underscore. To process the operator in a logical way, we can add some processing logic.
  • A smart contract with a transfer function and a capability. We actually want to convert these into the parts that we actually need. Here we have a parser and a type rule. We use this part to create the elements that we want to extract from the code. Now we can use these elements in the client code.

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.
...

Albert Groothedde

Architect DevEx @ Kadena

Albert Groothedde's LinkedIn account Albert Groothedde's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways