3b: A Frontend for your Smart Contract

Jeff Scott
Lamden
Published in
10 min readJun 4, 2020
Part B

…this is a continuation from A Frontend for your Smart Contract : A

frontend/src/routes/users/[user].svelte continued

Our smart contract doesn’t just store balances. It also gives our users the ability to send tokens to other users by calling the “transfer” method.

In Part 2 we created an API endpoint to be able to call this transfer method and provide the arguments.

What we need now is a form on our website that lets the user fill in the required arguments (receiver and amount). And then we need to feed that information to the API.

This is the end goal. Let’s start by making the form section in the purple box.

This form contains 4 elements. A message and 3 input elements (two text boxes and a submit button)

Add the code in bold under the existing Token Balance <h2> tag.

<!-- frontend/src/routes/users/[user].svelte -->...
<h2>Token Balance: {value}</h2>
<form on:submit|preventDefault={transfer}>
<h3>Make a transfer</h3>
<label for="to">To</label>
<input type="text" name="to" bind:value={receiver} required="true"/>
<label for="amount">Token Amount</label>
<input type="number" name="amount" bind:value={amount} required="true"/>
<input class="button" type="submit" value="send"/>
</form>

...

Let’s take a look at the code we pasted in. It should look very familiar to the form code we created in Part 3A.

<form on:submit|preventDefault={transfer}>
<h3>Make a transfer</h3>
<label for="to">To</label>
<input type="text" name="to" bind:value={receiver} required="true"/>
<label for="amount">Token Amount</label>
<input type="number" name="amount" bind:value={amount} required="true"/>
<input class="button" type="submit" value="send"/>
</form>

Bind the form’s submit event to a function we will create later called transfer.

<input type="text" name="to" bind:value={receiver} required="true"/>

Create a text input box and bind its value to a variable we will create later called receiver.

<input type="number" name="amount" bind:value={amount} required="true"/>

Create a number input box and bind its value to a variable we will create later called amount.

In our JavaScript <script> section we need to create these 3 bindings before our webpage will render again.

Add the bold code below inside your <script> tag below the export statements.

<!-- frontend/src/routes/users/[user].svelte -->...
<script>
export let value;
export let user;

let receiver = "";
let amount = 0;

const transfer = async () => {
const transaction = {
sender: user,
contract: 'my_token',
method: 'transfer',
args: {
receiver,
amount
}
}

const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(transaction)
}

const res = await fetch(`http://localhost:3737/`, options)
const data = await res.json();
if (data.error) {
alert(data.error);
}else{
alert("You sent " + amount + " token(s) to " + receiver + "!");
}
}

</script>
... let receiver = "";
let amount = 0;

Here we create our two variables to satisfy the bindings from the input boxes.

const transfer = async () => {

Here we define the async function that our form’s submit event will call.

const transaction = {
sender: user,
contract: 'my_token',
method: 'transfer',
args: {
receiver,
amount
}
}

This creates a transaction object that we will send to our contracting server API endpoint. This transaction object has all the information the server needs to process the transfer for us. Let’s break down the information.

  • sender: This is the account of the person who is SENDING the transaction. This becomes ctx.caller in our smart contract transaction method. If it’s been a while you should open the smart contract code and refresh your memory.
  • contract: The name of our smart contract. We set the API server up to process ANY smart contract. So, we need to tell it specifically which one we are trying to interact with.
  • method: Which method in our smart contract to execute. In this case our smart contract only has one method, but we still need to specify it.
  • args: The arguments to supply to the method being called. Specifically, the transfer method we designed takes two arguments, amount and receiver. It’s important here that we pass our variables with the correct names and types as to what our smart contract expects.
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(transaction)
}

This is a standard object created to send a “POST” request. You can learn more about HTTP requests here if you’re curious, but it’s outside this tutorial’s scope. All you really need to know is to send data (our transaction object) to the API server we need to create a POST request. All our previous calls to the API server were GET requests which are generally used to GET information and are the default request type.

  • method: We want to tell the server this is a POST request. Our API endpoint is setup to expect this type of request (go back and look at the contracting server, “/” endpoint code to verify.
  • headers: We’re going to send a “Content-Type” header to tell the server what type of data we are sending so it can decode it on the other-side. In this case we are sending a JSON string, so we send ‘application/json’ as the type.
  • body: This is the main payload of our request in which we will send the transaction object we created above. We can’t send just the JavaScript object we have to encode it as a string. To do that we encode it as a JSON string which is a universally understood method of encoding. This ensures the server can decode the information properly on the other end. You can read up on JSON here if you are unfamiliar.
const res = await fetch(`http://localhost:3737/`, options)

Here we call fetch again and supply it with the URL of our contracting server then we feed the options object as fetch’s second argument.

Because we created transfer as an async function we have the ability to await the fetch request result and we store the result in the res variable.

const data = await res.json();

Just like we sent our data to the server serialized as a JSON string, the server sent us some data also serialized in JOSN. We can call the res object’s .json() method to decode that JSON back into a JavaScript object for us and we store it in a variable called data. The .json() method is asynchronous so we need to await the result, or our code will pass right by it before it’s finished decoding.

if (data.error) {
alert(data.error);
}else{
alert("You sent " + amount + " token(s) to " + receiver + "!");
}

Now we can look at the data returned from our API server and decide how we want to handle it.

This if statement looks to see if our data object has a property called error. We know that if our API server has an issue with processing the smart contract that it will pass that message back in the error property. If the error property exists, then we will create a browser alert box that will show that error to the user.

If there is no error property, then we can assume our smart contract completed. In a real blockchain situation we would get a success/failure status code back from our hash result to know for sure. But since smart contracts either fail or complete we can assume that if there was no error that our transaction was successful and pass the user a “success” message to let them know their transaction completed.

Now if you refresh your token site http://localhost:3000, log in as me, you should see this screen.

Well, all of our elements are there, but it’s pretty ugly. We need some styling. Add the following CSS between the <style></style> tags. Then refresh your page.

<!-- frontend/src/routes/users/[user].svelte -->...
</script>
<style>
p{
font-size: 1.2em;
}
.shadowbox{
padding: 0.5rem 20px;
}
form{
padding: 50px;
color: #461BC2;
display:flex;
flex-direction: column;
border: none;
box-sizing: border-box;
}
form > h2{
margin: 0;
font-weight: 600;
line-height: 2.2;
letter-spacing: 1px;
}
form > input{
margin-bottom: 1rem;
}
input[type="submit"] {
margin-right: 20px;
}

.buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 1rem;
}

</style>
...

Much better!

Let’s test. Type the word you in the To box. Type 100 in the Token Amount box and click the SEND button.

You should get this message.

That’s great! Everything is working perfectly so far. Let’s try and make a legitimate transfer now.

Type the word you in the To box. Enter 5 in the Token Amount box and click SEND.

This is great! We have confirmation that we sent 5 tokens to you.

But it’s not all perfect. When we click OK to close the popup the user interface still shows us we have 50 tokens. If we refresh the page, we will see the Token Balance is now 45. This is because the webpage updates the value again from the server when we manually refresh it. But it’s not a good design to ask the user to refresh the page every time they send a transaction.

Additionally, we should reset both input box values to get the user ready for a new transfer.

What should happen here, is that after the transfer is deemed complete, the webpage should make a request to the API server again to get the user’s new balance and display it.

We already know how to do this because we did it in the module before the page loads!

Let’s make a new function in our <script> tag, under the transfer function.

<!-- frontend/src/routes/users/[user].svelte -->
...
<script>
...

const transfer = async () => {
// code hidden
}

const refreshBalance = async () => {
const res = await fetch("http://localhost:3737/contracts/my_token/S?key=" + user)
let data = await res.json();
value = data.value;
}


</script>
...

This async refreshBalance function awaits the result of a fetch call to our <variables> endpoint just like we did in the module before the page was loaded. We can then update the webpages value variable with the value from the data response. Svelte will handle making sure that value gets re-rendered.

The second function we need to create is one to reset our bound input variables back to the defaults. Create this one under the refreshBalances function.

<!-- frontend/src/routes/users/[user].svelte -->...
const refreshBalance = async () => {
const res = await fetch("http://localhost:3737/contracts/my_token/S?key=" + user)
let data = await res.json();
value = data.value;
}

const clearInputs = () => {
receiver = ""
amount = 0
}

</script>
...

This clearInputs is NOT defined as async because we don’t need to await the result of anything.

Now let’s call these two functions under the success alert at the end of the transfer function so they are called on the event of a successful transfer.

<!-- frontend/src/routes/users/[user].svelte -->...
if (data.error) {
alert(data.error);
}else{
alert("You sent " + amount + " token(s) to " + receiver + "!");
clearInputs();
refreshBalance();

}
}

const refreshBalance = async () => {
...

Let’s try another transfer of 5 tokens to you and hopefully the page will re-render the token amount to 40 automatically.

Perfect! Everything is now working good from a UX (user experience) perspective.

The final thing we will add is a sign out button.

This will be almost identical to the sign in button created in Part A. We will use Sapper’s goto function to repoint the browser back to the sign in page.

Below our form add the following button element inside a new <div> tag.

<!-- frontend/src/routes/users/[user].svelte -->...
<form on:submit|preventDefault={transfer}>
<h2>Enter Transfer Details</h2>
<label for="to">To</label>
<input type="text" name="to" bind:value={receiver} required="true"/>
<label for="amount">Token Amount</label>
<input type="number" name="amount" bind:value={amount} required="true"/>
<div class="buttons">
<input class="button" type="submit" value="send"/>
<button class="button" on:click={logout}>sign out </button>
</div>
</form>
...

This button has a binding on the click event to run a new function we will create called logout.

Add a statement at the top of the <script> tag (not the module script tag) to import the goto function from Sapper.

<!-- frontend/src/routes/users/[user].svelte -->...
<script>
import { goto } from '@sapper/app';

export let value;
export let user;

let receiver = "";
let amount = 0;
...

Then create a new function to call goto and point the browser to the root of our project.

<!-- frontend/src/routes/users/[user].svelte -->...
const clearInputs = () => {
receiver = ""
amount = 0
}

const logout = () => {
goto(`.`);
}

</script>
...
  • Refresh the page and click the new sign out button
  • Sign in as user you and check their token balance. 10 whole tokens!

Finished!

You now have a fully functioning web frontend with a python smart contract backend.

You can check out the completed git repo for this part of the tutorial here.

What’s next?

Now that we have a tested and functioning smart contract with a functioning proof of concept frontend the next tutorials will be about adding the public blockchain to mix. This brings with it some new concepts, but I will tackle them one by one to make it understandable.

This is what to expect in the coming weeks:

  • Upload our working smart contract to the blockchain
  • Add user authentication via the Lamden Wallet
  • Accessing our smart contract variables from the blockchain. This is still going to be API driven but it will require just a slight modification to our code.
  • Creating transactions will be very similar to our API server requests except this time we will send them through the Lamden Wallet which will return our transaction results which we can process in largely the same way.

Check out Part 4: Uploading your smart contract to the blockchain

I’m available to chat on twitter, telegram or linkedIn

--

--