In this tutorial we’re going to modify our base wallet app to include functionality to send XBN to other Bantu accounts.
In the Build a Basic Wallet section, we did the hard work of wiring up a secure client and rock-solid key creation and storage structure with a clear plan for key use and management. Now that we have a method for creating an account — and for storing that account's secrets so they're safe and easy to use — we're ready to start making payments. We'll start with XBN payments since they're a little simpler; in the next section, we'll look at how to support payments for other assets.
There isn’t too much that's new or complicated here: we'll be building on what we already have. Again, most of the work will be in src/components/wallet/; however, before we dive into that file let’s clean up our project just a touch and add some helpful polish. Namely a loader component.
Add Loader Component
Hopefully by now you’re familiar with how to generate new Stencil components.
npmrungenerate
We'll call the component bantu-loader, and deselect both test files leaving only the styling. Once you've done that, open src/components/loader/ and rename the .css file to .scss. Then replace the loader.tsx contents with this:
Imports galore! Nothing really noteworthy here other than you’ll notice we’re importing ./methods/updateAccount and ./methods/makePayment methods which we’ll be creating soon. There are a number of updates in the other methods from previous tutorials, and we’ll walk through all of those in a moment as well.
Here we set up our @State’s, those dynamic properties with values that will change and alter the DOM of the component, and our @Prop, which in this case will hold a static reference to our Bantu server instance.
Update Component Events
Next let’s update our two component events ./events/componentWillLoad.ts and ./events/render.tsx:
In Stencil’s componentWillLoad method, we set up default values for the States and Props we initialized earlier. Most notably, we’re setting our server and account. You’ll notice we’re using the public Expansion-testnet for now — we're just learning, and not ready to send live XBN — but in production you’d want to change this to the public Expansion endpoint or, if you're running your own Expansion, to one of your own Expansion API endpoints.
For the account we’re simply checking to see if a keyStore value has been stored, and if so we’re grabbing the public key and keystore from it and adding those to the account @State. The state value, which is optional, is not set here as we’ll need to run the method updateAccount() to find if the account exists, and if so what the state of that account looks like. We’ll get to that method shortly. For now let’s update our render.tsx method:
Yikers amirite!? Don’t worry though: it’s actually quite simple if you’ve spent any time with HTML in JS before. What we're looking at are a few ternary operators toggling the UI between different states based on the account status. Basically just a bunch of buttons wired up to their subsequent actions. Turns out creating those buttons is next on our to-do list!
Create Buttons
updateAccount and makePayment are new; createAccount will just need a few tweaks. Let’s start with that one.
The only new thing we’re adding here — other than a loading state and an initial this.updateAccount() call at the end — is the call to Friendbot, which is a testnet tool that we can use to automatically fund our new testnet account with 10,000 XBN. Nice little shortcut to kickstart our development.
In production, you would have to find an actual source for funding the account. Some wallets fund users' accounts for them; some require the user to supply the funds.
So that’s the updated ./methods/createAccount.ts file.
Update Account Method
Now let’s create two new files for updating the account and making XBN payments.
All we’re doing here is looking up the state of the Bantu account on the ledger and saving it to the this.account.state. You’ll also notice we’re omitting several fields from the account and balances for easier readability. You may choose to save these and selectively display the values you care about, but in our example we’re just displaying the raw JSON, so cleaning things up a little is the right move.
this.account = {...this.account, state: loOmit(account, ['id', ...])} may feel odd, but it’s just the Stencil way of updating a state’s object key to trigger a re-render of the DOM. You’ll notice this.loading follows the same pattern. We’ll make use of this data further down in the render method, but for now just know this is how we would grab ahold of the account to get the latest state.
Make Payment Method
Next let’s break down the main subject method for this tutorial, ./methods/makePayment.ts:
We’re going to need a couple pieces of info from the user: the amount of XBN to send, what address to send it to, and the pincode for unlocking the keystore file. We request those asynchronously and cancel out of the method if they aren’t provided.
From there we call the keypair account to retrieve its current sequence number so we can prepare a transaction with a payment operation. We set the destination and amount using the instructions from the prompt we collected and split earlier. Finally, we build, sign, and submit that transaction to the Bantu Expansion API server.
.catch((err) => {if ( // Paying an account which doesn't exist, create it insteadloHas(err,'response.data.extras.result_codes.operations')&&err.response.data.status ===400&&err.response.data.extras.result_codes.operations.indexOf('op_no_destination') !==-1 ) {consttransaction=newTransactionBuilder(account, { fee:BASE_FEE, networkPassphrase:Networks.TESTNET }).addOperation(Operation.createAccount({ destination: instructions[1], startingBalance: instructions[0] })).setTimeout(0).build()transaction.sign(keypair)returnthis.server.submitTransaction(transaction) }elsethrow err })
If the account we want to send XBN is unfunded (and therefore doesn't yet exist on the ledger), we'll get back op_no_destination. This catch handles that issue by trying again with a createAccount operation. For any other issues we just pass the error on unmodified.
Finally, we log any success transaction, kill the loader, and updateAccount to reflect the new balance in our account after successfully sending XBN. We also have our catch block that passes to the handleError service. We will render that in a nice error block in the UI.
There we go! That wasn’t so bad right? Pretty simple, and yet from this tutorial we have the power to hold and observe balances, and to make payments using the power of the Bantu ledger. Amazing!