We've already managed to tackle two-thirds of our website, the faucetting, the ability to check for 1
balance. 2
The final section we want to tackle is to transfer some sort of amount from our account, so the user 3
who's either logged in or using the website, to another account. 4
And that's what we're going to be doing in this lesson. Heading back into our main.mo, 5
we're going to create a new public function and this one is going to be called transfer. 6
Again, let's make sure that we cap off the previous functions with any semicolons that we need. 7
And the first thing I want to show you within this is a bit of a demo, because we know that when we 8
use that shared message, we can get hold of the caller of the function. 9
But who is the caller when a function is being called from another function? 10
If I go ahead and just comment out all of this apart from a single return so that it doesn't get angry 11
at me, and I'm going to debug print who the message caller is in this case. From the transfer function, 12
I'm going to call the payout function. 13
What do you think will happen? 14
What do you think will actually get printed? 15
Let's hit save. 16
Let's deploy our canister. 17
And let's go ahead and check out what we're going to get printed in the console from our payOut function. 18
As we saw previously, we could call that function from the command line, in which case the msg. 19
caller is going to be our user principal ID. 20
So let's try that again. 21
I'm going to call dfx canister call token payOut. 22
And again, notice that you've got the correct spelling for your method. 23
In my case, I have a capital 'O' here for payOut. 24
So when I go ahead and hit enter, it's going to call this function and that msg.caller is going 25
to be logged here. 26
And as you can see, that is my user principal. 27
This is my principal ID for the user that controls this command line. 28
Now, the other way we saw that we could call this function was from our frontend. 29
We could go ahead and click on this button, 30
and when that's pressed, this function also gets triggered 31
but the caller in this case is the user that is using this frontend website, which is this ID. 32
So now we've got the final one. 33
What if we call this transfer method, which then calls the payOut method? 34
So in this case, who is the message caller? 35
Well, let's try that here. 36
So we're going to say dfx canister call token, which is the name of our canister, and then we're going 37
to call the name of the method, which is transfer. 38
Now when I go ahead and hit enter, it's going to trigger this function which is going to trigger 39
this function, which is going to print out the message 40
caller. 41
Now, I want you to just think for a moment, what is it going to print? 42
Who will be the caller? 43
Let's go ahead and hit enter and find out. 44
Well, you can see that this ID is neither the frontend principal ID when we clicked the button to call 45
that method, nor is it the user principal that we called previously from our command line. 46
Instead, it's this. And you might be a little bit familiar with this because every time we do dfx deploy, 47
you'll see it. 48
It says deploying all canisters. All canisters have been created. 49
And then it says something about upgrading code for canister token with the canister ID, and there it 50
is. 51
It matches this. 52
Essentially what's happening here is when you call a method from another method, it's this canister 53
that's actually calling this method. 54
So in this case, the msg.caller is this entire actor, this token canister. 55
That's why we get that canister ID being printed here. 56
This is really important in order for us to make our transfer function work. 57
Let's go ahead and restore some of this code for our faucet function and let's work on our transfer 58
function for real this time. 59
Firstly, we're also going to make this a public 60
shared function with the parameter msg. 61
And we're also going to give it a async return, which is going to be a piece of text similar to before. 62
Now, what do we want to do when we transfer some token from one account to another? 63
Well, we want to subtract the amount that they want to transfer from the account that's being transferred 64
from, 65
and then we want to add that amount to the account that's being transferred to. 66
Let's delete this line which we don't need. 67
That was just for testing 68
to show you what actually happens. In this transfer function, we need to add two parameters and that 69
is the to, 70
so who are we going to transfer to, which is going to be a principal ID type, 71
and then we also need to have the amount which is going to be the amount that we're going to transfer 72
to that particular account. 73
Now you'll notice that I don't have the transfer from in this function. 74
Why is that? 75
Well, the transfer from is going to be the message 76
caller. 77
So whoever triggers this function is going to be the one where the money is going to be taken from. 78
Because if you think about it, when we called this method from the frontend, when we clicked on that 79
button, the debug_show showed us that the msg.caller is the principal ID of the frontend user. 80
When we do that here, when we click on this button, it's also going to take the from account as the 81
principal of the person using the website and then they're going to provide the principal ID of the 82
account they want to transfer to, and the amount they want to transfer. 83
So then it's just a matter of a little bit of math. 84
The first thing we can figure out is what is the from balance? 85
So the person who is going to be doing the transfer. 86
Well, we can work that out by using our balanceOf method and we can pass in the msg.caller 87
as the principal. 88
Now, if you're worried about these error messages, it's because we're expecting a return here of type 89
text. 90
If we just add a return success at the very bottom, then we can actually write our code in peace. 91
Once we've gotten hold of the balance of the account we're transferring from, well then we can do 92
a quick check to see if the from balance is greater than the amount that we want to transfer because 93
otherwise we can't really do the transfer, right? 94
If they have enough money, we're going to return success. 95
But otherwise we're going to return insufficient funds. 96
And remember that this balanceOf returns asynchronously. 97
So in order to fix this error, we have to make sure we have the await in front here. 98
What we're doing here is we're going to get the balance of the caller of this transfer method, and we're 99
going to wait for it until it's done. 100
And then we're going to check to see if they actually have enough money, if they have more money than 101
they want to transfer. 102
And if they do, then we're going to actually make the transfer go through. 103
So what does that actually mean in terms of math? 104
Well, we're going to subtract this amount from their balance and add this amount to the person they 105
want to transfer to. 106
It's pretty simple. 107
Just a bit of addition and subtraction. 108
So let's create a new constant. 109
Let newFromBalance = fromBbalance - the amount. 110
And because Motoko thinks that this can go below zero, even though we definitely know it can't, we 111
have to add a data type explicitly here and that will make that warning go away. 112
Now, once we figured out the new balance of the account that we're transferring from, then we can 113
update our ledger of balances to this new amount for this user, which is msg.caller. 114
So we can say balances.put, and we're basically going to replace the entry here. 115
So we're going to put in the msg.caller's ID and then this is the key. 116
And then for them we're going to update their value to the newFromBalance. 117
And this basically just does the subtraction from the account. 118
The other aspect is to add the amount to the account that is being transferred to. 119
Let's first figure out what is the balance of the owner that we're transferring the money to. So we can 120
say let toBalance equal balanceOf, and then we pass in the principal ID, which comes from this to 121
here, which basically the user is actually going to type in or paste in. And it's going to be the long 122
form sort of principal ID of the account that they want to transfer to. 123
Now, once we figured out their balance and you'll notice that in our balanceOf method, we've already 124
taken care of the case where that user doesn't actually exist. 125
Then we're actually just going to assign them a balance of zero. 126
Once we've done that, the next step is to add the amount that we want to transfer, 127
so the new toBalance, which is the toBalance + the amount that we want to transfer. 128
And again, we have to await for the results of balanceOf before we can execute this line, 129
so adding the await keyword before the balanceOf. 130
And finally, we're going to put into our HashMap the new updated balance. 131
The key is the principal ID of the to account, and the value is the new toBalance. In those few lines 132
of code 133
we've just done a little bit of simple math. 134
We've subtracted the amount from the balance of the sender, and we have added the amount to the balance 135
of the recipient. 136
And then we've basically updated our ledger with this new information. And then we returned success 137
if they had enough balance, which means all of this should succeed, but otherwise we return insufficient 138
funds. 139
So now let's make sure that we hook this up with our frontend and that we actually call that method 140
in our handleClick. When the user presses the transfer button, 141
we want to call that method in our token canister, so let's import our token canister. 142
So ../ three of them /declarations/token. 143
And then we can call await token.transfer and I believe it is spelt lowercase. And then we have 144
to provide the two inputs that are required. 145
So the first one is the to. That is going to come from the first input field where we've got the transfer 146
to, which is this one here, and we're going to do this in the same way that we did for our balance 147
checking. 148
So we had a value and then we had an onChange listener. 149
So the first thing we need to use our useState hook in order to hook up our input so that it has a value 150
and it has an onChange listener. 151
The value is going to be tied to a constant, which I'm going to call the recipient 152
Id 153
and I'm going to have a setter called setId. 154
And this is going to of course, useState. 155
And we're going to set it to an initial value of an empty string. 156
So now we can set this value to the recipientId constant, 157
and when that gets changed, we're going to use that event listener to update the ID by using our set 158
Id setter. 159
And we're going to set it to the e.target.value, which will basically give us the value 160
of the input at the moment when it changes. 161
It's going to keep updating this recipientId 162
and then in the moment when we click on the button, then we'll be able to take the final value of the 163
recipientId and pass it over to that token.transfer method. 164
The other thing that we need to set up is the amount. 165
We're going to do something pretty similar. 166
We're going to have our value and we're going to have our onChange. 167
That means that we again have to set up a new constant. 168
So I'm going to call that the amount and setAmount. 169
And it's going to start off with a value of, again, an empty string. 170
And we can update this in here in the same way that we've done before. 171
And I've just noticed that I've misspelt my amount, so I have to make sure it's correct in both places, 172
otherwise it won't work. 173
All right. 174
So now we should be able to bring the values inside these inputs in, and I should be able to get hold 175
of it here and use it in my transfer function. 176
Now there's a couple of problems with using the recipientId or the amount straight from the inputs. 177
And this is because the amount is going to come in as a piece of text and we call the transfer function. 178
We actually need it to be a number. 179
And similarly, the recipientId needs to be a valid Principal type rather than a simple piece of text. 180
Let's go ahead and fix that by importing the Principal data type. 181
And let's go ahead and create the recipient as the Principal.fromText. 182
And then we're going to pass in the recipientId that we got from our state hook. 183
And similarly, we're going to convert our amount as a new constant amountToTransfer, and we're going 184
to convert it into a number from the string. 185
So now we can replace this recipientId with our new recipient const and the amount with our new amount 186
ToTransfer const. 187
And now, finally, we can test our code. 188
Make sure you run dfx deploy. 189
Let's copy the principal ID of the frontend. 190
If I first check my sender balance, you can see I've got zero DANG because we did dfx deploy 191
so the ledge has been reset. 192
So let's just reclaim it from the faucet. 193
And now if we check it, you can see we have 10,000 DANG. And I'm going to transfer it to my user's principal 194
ID and I'm going to transfer 50 DANG. 195
Now if I click transfer and if I check my balance again, you can see 50 has now been subtracted and 196
if I check the balance of the recipient, you can see that 50 has been added to its balance. 197
So our transfer method finally works. 198
Now that we've got the functionality down, let's go ahead and add some of the user experience touches 199
that'll improve the way that our website works. 200
Now the first thing we're going to do is we're going to make this button disabled when the transfer 201
is happening. And once we get the results back, then we can enable it again. 202
So we'll go ahead and set the disabled attribute to a new constant. 203
We'll call it isDisabled and we can create our constant up here. 204
And we'll also add a setter for setDisable. 205
And we'll start it out with false so that we can actually tap on the button, and then as soon as the button 206
gets clicked, we'll set it to true. 207
And once the process has completed, then we can set it back to false again. 208
In addition, when we call our token transfer, we actually get a result that comes back in the form 209
of text. 210
And if you remember, it can either be success or it can be insufficient funds. 211
Now we want to go ahead and capture that. 212
So let me create a const called result and set it equal to the result or the output of this transfer 213
function call. 214
Now we want to display that in a paragraph element. 215
So just above the last two divs, let's go ahead and add a paragraph element. 216
And inside this paragraph, we're going to add some feedback. 217
Now, this feedback is going to be a new constant, 218
starting out with an empty string. 219
But then as soon as we get the results back from our token.transfer, then we're going to set the feedback 220
to whatever that result is. 221
This feedback probably shouldn't exist to begin with, so we can create another const, which is called 222
isHidden, and we can use the setHidden setter to hide that paragraph element. 223
And it's going to start out with true. 224
So we're going to start out by having this paragraph element have a hidden attribute set to the constant 225
we've got isHidden. 226
And then as soon as we get our result back and we set our feedback, then we can go ahead and set the 227
hidden to false. 228
And the next time the user clicks on the button, we're going to set the hidden to true again. 229
So we can copy our principal ID, go back and try to make another transfer. 230
Click Transfer and you can see this time the transfer button goes gray and becomes disabled when that 231
process is going through. 232
And once it's done, it gives us success. 233
And if I try to transfer some amount that my account doesn't have, because remember, I only have 10,000 234
and I try to transfer, say, 100,000, then it's going to think a little bit and then it's going to tell 235
me insufficient funds.