# Create a Basic Wallet

{% hint style="info" %}
In this tutorial, the goal is to get to a place where a user can create, store, and access their Bantu account using an intuitive pincode encryption method.
{% endhint %}

## User Flow

Because we've decided to build a non-custodial wallet, we don’t need to communicate with a server or database at all: everything can happen locally right on a user’s device. We’ll be doing all our work inside of `src/components/wallet/`, so head over there now. We’re going to use the `StellarSdk.Keypair.random()` from the StellarSdk to generate a new valid Bantu keypair. That's the easy part. The hard work will be storing that vital information in a secure yet accessible manner.

The user flow we're building will work like this: Click “Create Account” → UI modal popup asking for a pincode → Enter pincode, click “OK” → App encrypts a new Bantu keypair secret key with pincode → App saves encrypted secret to `localStorage`. On page reload, we’ll fetch the `publicKey` to “login” the user, but for any protected action such as “Copy Secret”, the modal will pop back up asking for the original pincode.

## Create a Popup Modal

To start, let's look at at the popup modal. We’ll be mimicking the browser’s `prompt` functionality with our own new, more powerful component. First things first we should generate a new component:

```bash
npm run generate
```

Call it `bantu-prompt`, and deselect both test files leaving only the styling. Once you have that, open `src/components/prompt/` and rename the `.css` file to `.scss`. Fill that style file with this:

```css
@import "../../global/style.scss";

:host {
  display: block;
  font-family: $font-family;
  font-size: 15px;

  .prompt-wrapper {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    align-content: center;
    min-height: 100vh;
    min-width: 100vw;
    background-color: rgba(black, 0.2);
    z-index: 1;
  }
  .prompt {
    background-color: white;
    padding: 20px;
    max-width: 350px;
    width: 100%;
    position: relative;

    p {
      margin-bottom: 10px;
    }
    input {
      width: 100%;
      margin: 0;
      padding: 5px;
      outline: none;
      border: 1px solid black;
      text-transform: uppercase;

      &:focus {
        border-color: blue;
      }
    }
  }
  .select-wrapper {
    position: relative;
    display: inline-flex;

    select {
      border-color: blue;
      padding: 0 10px;
      min-width: 100px;
    }

    &:after,
    &:before {
      font-size: 12px;
      position: absolute;
      right: 10px;
      color: blue;
    }
    &:after {
      content: "◀";
      top: calc(50% - 5px);
      transform: translate(0, -50%) rotate(90deg);
    }
    &:before {
      content: "▶";
      top: calc(50% + 5px);
      transform: translate(0, -50%) rotate(90deg);
    }
  }
  .actions {
    display: flex;
    justify-content: flex-end;
    margin-top: 10px;

    button {
      margin: 0;
      min-width: 50px;
    }
    .cancel {
      background: none;
      border: 1px solid blue;
      color: blue;
    }
    .submit {
      margin-left: 10px;
    }
  }
}
```

Replace the `prompt.tsx` contents with this.

```typescript
import { Component, Prop, Element, Watch, h, State } from "@stencil/core";
import { defer as loDefer } from "lodash-es";

export interface Prompter {
  show: boolean;
  message?: string;
  placeholder?: string;
  options?: Array<any>;
  resolve?: Function;
  reject?: Function;
}

@Component({
  tag: "bantu-prompt",
  styleUrl: "prompt.scss",
  shadow: true,
})
export class Prompt {
  @Element() private element: HTMLElement;

  @Prop({ mutable: true }) prompter: Prompter;

  @State() private input: string;

  @Watch("prompter")
  watchHandler(newValue: Prompter, oldValue: Prompter) {
    if (newValue.show === oldValue.show) return;

    if (newValue.show) {
      this.input = null;

      if (newValue.options)
        this.input =
          this.input ||
          `${newValue.options[0].code}:${newValue.options[0].issuer}`;
      else
        loDefer(() => this.element.shadowRoot.querySelector("input").focus());
    } else {
      this.prompter.message = null;
      this.prompter.placeholder = null;
      this.prompter.options = null;
    }
  }

  componentDidLoad() {
    addEventListener("keyup", (e: KeyboardEvent) => {
      if (this.prompter.show)
        e.keyCode === 13
          ? this.submit(e)
          : e.keyCode === 27
          ? this.cancel(e)
          : null;
    });
  }

  cancel(e: Event) {
    e.preventDefault();

    this.prompter = {
      ...this.prompter,
      show: false,
    };
    this.prompter.reject(null);
  }

  submit(e: Event) {
    e.preventDefault();

    this.prompter = {
      ...this.prompter,
      show: false,
    };
    this.prompter.resolve(this.input);
  }

  update(e) {
    this.input = e.target.value.toUpperCase();
  }

  render() {
    return this.prompter.show ? (
      <div class="prompt-wrapper">
        <div class="prompt">
          {this.prompter.message ? <p>{this.prompter.message}</p> : null}

          {this.prompter.options ? (
            <div class="select-wrapper">
              <select onInput={(e) => this.update(e)}>
                {" "}
                {this.prompter.options.map((option) => (
                  <option
                    value={`${option.code}:${option.issuer}`}
                    selected={this.input === `${option.code}:${option.issuer}`}
                  >
                    {option.code}
                  </option>
                ))}
              </select>
            </div>
          ) : (
            <input
              type="text"
              placeholder={this.prompter.placeholder}
              value={this.input}
              onInput={(e) => this.update(e)}
            ></input>
          )}

          <div class="actions">
            <button
              class="cancel"
              type="button"
              onClick={(e) => this.cancel(e)}
            >
              Cancel
            </button>
            <button
              class="submit"
              type="button"
              onClick={(e) => this.submit(e)}
            >
              OK
            </button>
          </div>
        </div>
      </div>
    ) : null;
  }
}
```

One of the first things you’ll notice is the use of `lodash-es`. Let’s make sure we’ve got that imported before moving forward:

```bash
npm i -D lodash-es
```

There’s a lot going on in this file, but since this isn’t a Stencil tutorial we’ll skip the details. What this allows us to do it to use a `<bantu-prompt prompter={this.prompter} />` component elsewhere in our project. It's worth noting the variables available to us in the `prompter` property.

```typescript
export interface Prompter {
  show: boolean;
  message?: string;
  placeholder?: string;
  options?: Array<any>;
  resolve?: Function;
  reject?: Function;
}
```

The values we’ll be making most use of are those first three: `show`, `message` and `placeholder`. The last two—`resolve` and `reject`—are for promisifying the prompt so we can await a response before continuing with further logic. Don't worry: that statement will make more sense in a moment once we include this component in `src/components/wallet/`. Speaking of, let’s swing over to that component now.

We’ve got a lot of work to do in here so I’ll just paste the code in all its glory and we’ll walk through it block by block:

```typescript
import { Component, State } from "@stencil/core";

import componentWillLoad from "./events/componentWillLoad";
import render from "./events/render";

import createAccount from "./methods/createAccount";
import copyAddress from "./methods/copyAddress";
import copySecret from "./methods/copySecret";
import signOut from "./methods/signOut";
import setPrompt from "./methods/setPrompt";

import { Prompter } from "@prompt/prompt";

interface BantuAccount {
  publicKey: string;
  keystore: string;
}

@Component({
  tag: "bantu-wallet",
  styleUrl: "wallet.scss",
  shadow: true,
})
export class Wallet {
  @State() account: BantuAccount;
  @State() prompter: Prompter = { show: false };
  @State() error: any = null;

  // Component events
  componentWillLoad() {}
  render() {}

  // Stellar methods
  createAccount = createAccount;
  copyAddress = copyAddress;
  copySecret = copySecret;
  signOut = signOut;

  // Misc methods
  setPrompt = setPrompt;
}

Wallet.prototype.componentWillLoad = componentWillLoad;
Wallet.prototype.render = render;
```

They say the beginning is a good place to start. Let’s do that:

```javascript
import { Component, State } from "@stencil/core";

import componentWillLoad from "./events/componentWillLoad";
import render from "./events/render";

import createAccount from "./methods/createAccount";
import copyAddress from "./methods/copyAddress";
import copySecret from "./methods/copySecret";
import signOut from "./methods/signOut";
import setPrompt from "./methods/setPrompt";

import { Prompter } from "@prompt/prompt";
```

Just one import from a library we should already have installed.

The other relative path imports are all the *events* and *methods* we’ll create here in a moment. For now, just generate all those files in their appropriate directories. Ensure your console is at the root of the `bantu-wallet` project before running this string of commands:

```bash
mkdir -p src/components/wallet/{events,methods}
touch src/components/wallet/events/{componentWillLoad.ts,render.tsx}
touch src/components/wallet/methods/{createAccount,copyAddress,copySecret,signOut,setPrompt}.ts
```

Next we have this funky line which may seem like an npm team import, but is actually a fancy typescript module alias path.

```typescript
import { Prompter } from "@prompt/prompt";
```

This allows us to avoid long error prone `../../../` paths and just use `@{alias}/{path?}/{module}`. In order to get this past both the linter and compiler we’ll need to modify a couple files.

First, modify the `tsconfig.json` file to include these values in the `compilerOptions` object.

```javascript
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@prompt/*": ["components/prompt/*"],
      "@services/*": ["services/*"]
    },
    // ...
  },
  // ...
}
```

Next,modify the `package.json` file to include a `_moduleAliases` key at the root of the object.

```javascript
{
  // ...
  "_moduleAliases": {
    "@prompt": "dist/collection/components/prompt",
    "@services": "dist/collection/services"
  }
}
```

Finally, install the `module-alias` package and add it to the top of the `src/index.ts` file.

```bash
npm i -D module-alias
```

```typescript
import "module-alias/register";
export * from "./components";
```

Cool! With any luck we should be able to use these slick alias imports for the prompt and services directories now.

## Create Bantu Account Class

```typescript
interface BantuAccount {
  publicKey: string;
  keystore: string;
}
```

`interface` is just the TypeScript way of setting up a tidy typed class. `BantuAccount` will be our account class. It includes the `publicKey` for easy reference later in Horizon or Astrograph calls and the Top Secret `keystore` key containing the encrypted account secret cipher.

```javascript
@Component({
  tag: 'bantu-wallet',
  styleUrl: 'wallet.scss',
  shadow: true
})
export class Wallet {
  @State() account: BantuAccount
  @State() prompter: Prompter = {show: false}
  @State() error: any = null

  ...
}
```

Pretty standard boring bits, setting up the `@Component` with its defining values and initializing with some `@State` and `@Prop` data. You can see we’re setting up an `account` state with our `BantuAccount` class as well as a `prompter` state with that `Prompter` class from the `bantu-prompt` we imported earlier. We’re initializing that `prompter` state with a `show` value of `false` so the prompt modal rendereth not initially.

Everything after this is the assignment of our imported events and methods from up above. Let’s begin with the `./events/componentWillLoad.ts`

```typescript
import { handleError } from "@services/error";
import { get } from "@services/storage";

export default async function componentWillLoad() {
  try {
    let keystore = await get("keyStore");

    this.error = null;

    if (keystore) {
      keystore = atob(keystore);

      const { publicKey } = JSON.parse(atob(JSON.parse(keystore).adata));

      this.account = {
        publicKey,
        keystore,
      };
    }
  } catch (err) {
    this.error = handleError(err);
  }
}
```

`componentWillLoad` is the Stencil way of pre-filling the state and prop values before actually rendering the component. In our case we’ll use this method to populate the `account` `@State` with the saved storage `keyStore` value if there is one. At first there won’t be, so we’ll come back to this once we’ve actually gone over how to create and save accounts. For now just know it’s here, and since you’re smart, I imagine you can already kind of see how it works.

“But wait!” you say, “What are the `@services/error` and `@services/storage` packages?” Fine, yes, we should go over those. Remember the module alias stuff from earlier? Well one was for `@prompt` and the other was for `@services`. Go ahead and create these two files and add them to the `src/services` directory.

```bash
mkdir -p src/services
touch src/services/{error,storage}.ts
```

`error.ts` will look like this.

```typescript
import { get as loGet } from "lodash-es";

export function handleError(err: any) {
  return loGet(err, "response.data", loGet(err, "message", err));
}
```

Nothing fancy, just a clean little error handler we’ll make use of later when processing API requests.

## Set Up Key Storage

Next is `storage.ts`.

```javascript
import { Plugins } from "@capacitor/core";

const { Storage } = Plugins;

export async function set(key: string, value: any): Promise<void> {
  await Storage.set({
    key,
    value,
  });
}

export async function get(key: string): Promise<any> {
  const item = await Storage.get({ key });
  return item.value;
}

export async function remove(key: string): Promise<void> {
  await Storage.remove({ key });
}
```

You’ll notice a new package `@capacitor/core`. Let’s install and set that up.

```bash
# Install dependencies
npm i -D @capacitor/core @capacitor/cli

# Initialize Capacitor
npx cap init
```

```bash
? App name Bantu Wallet
? App Package ID (in Java package format, no dashes) com.wallet.stellar
? Which npm client would you like to use? npm
✔ Initializing Capacitor project in /Users/Desktop/Web/Clients/bantu/bantu-demo-wallet in 1.91ms


🎉   Your Capacitor project is ready to go!  🎉

Add platforms using "npx cap add":

  npx cap add android
  npx cap add ios
  npx cap add electron

Follow the Developer Workflow guide to get building:
https://capacitor.ionicframework.com/docs/basics/workflow
```

We’re not really making full use of [ Capacitor ](https://capacitor.ionicframework.com/), but it is an amazing service so be sure and check it out! For now we just need it to make storing and retrieving our data a bit more stable.

This storage service is simply a key setter and getter helper for storing and retrieving data. We’ll use this for any persistent data we want to store. For now, that's our Bantu account data.

## Set Up Event Handling

That’s everything we need for the `componentWillLoad` event. On to the `./events/render.tsx` file.

```typescript
import { h } from "@stencil/core";

export default function render() {
  return [
    <bantu-prompt prompter={this.prompter} />,
    this.account ? (
      [
        <div class="account-key">
          <p>{this.account.publicKey}</p>
          <button
            class="small"
            type="button"
            onClick={(e) => this.copyAddress(e)}
          >
            Copy Address
          </button>
          <button
            class="small"
            type="button"
            onClick={(e) => this.copySecret(e)}
          >
            Copy Secret
          </button>
        </div>,
      ]
    ) : (
      <button type="button" onClick={(e) => this.createAccount(e)}>
        Create Account
      </button>
    ),

    this.error ? (
      <pre class="error">{JSON.stringify(this.error, null, 2)}</pre>
    ) : null,
    this.account ? (
      <button type="button" onClick={(e) => this.signOut(e)}>
        Sign Out
      </button>
    ) : null,
  ];
}
```

It looks messy, but it’s actually a pretty simple `.tsx` file rendering out our DOM based off a series of conditional values. You can see we’re including the `bantu-prompt` component, and setting the prompter prop to our `this.prompter` state. We then have a ternary operation toggling between a Create Account button and a basic account UI. If `this.account` has a truthy value, we’ll print out the account’s `publicKey` along with some interaction buttons. If `this.account` is falsey, we’ll print out a singular Create Account button connected to, you guessed it, the `createAccount` method. After that logic, we print out an error if there is one, and finally a Sign Out button if there’s an account to sign out of. Those are the two `Wallet` `@Component` events.

## Create Methods

Let’s look at the methods now beginning with the `./methods/createAccount.ts` file.

```typescript
import sjcl from "@tinyanvil/sjcl";
import { Keypair } from "stellar-sdk";

import { handleError } from "@services/error";
import { set } from "@services/storage";

export default async function createAccount(e: Event) {
  try {
    e.preventDefault();

    const pincode_1 = await this.setPrompt("Enter a keystore pincode");
    const pincode_2 = await this.setPrompt("Enter keystore pincode again");

    if (!pincode_1 || !pincode_2 || pincode_1 !== pincode_2)
      throw "Invalid pincode";

    this.error = null;

    const keypair = Keypair.random();

    this.account = {
      publicKey: keypair.publicKey(),
      keystore: sjcl.encrypt(pincode_1, keypair.secret(), {
        adata: JSON.stringify({
          publicKey: keypair.publicKey(),
        }),
      }),
    };

    await set("keyStore", btoa(this.account.keystore));
  } catch (err) {
    this.error = handleError(err);
  }
}
```

Aha! Finally something interesting. This method forms the meat of our component. Before we dive into it, though let’s install the missing `@tinyanvil/sjcl` package.

```bash
npm i -D @tinyanvil/sjcl
```

## Create an Account

Essentially all we’re doing is making a request to create an account, which triggers the Prompt modal to ask for a pincode. That pincode will be used in the `sjcl.encrypt` method to encrypt the secret key from the `Keypair.random()` method. We set the `this.account` with the `publicKey`, which encrypted the `keystore` cipher, and now we're storing that cipher in base64 format in `localStorage` via our `set('keyStore')` method for easy retrieval when the browser reloads. We could also encode that cipher into a QR code or a link to share with other devices. Since it requires the pincode that encrypted cipher, it's as secure as the pincode you encrypt it with.

## Copy Address

Now that we’ve created an account, there are three more actions we'll enable: `copyAddress`, `copySecret`, and `signOut`.

First `./methods/copyAddress.ts`

```typescript
import copy from "copy-to-clipboard";

export default async function copyAddress(e: Event) {
  e.preventDefault();
  copy(this.account.publicKey);
}
```

Well there you go, the easiest code you’ll see all day. Just `copy` the `publicKey` from the `this.account` object to the clipboard. Before we jump though don’t forget to install that `copy-to-clipboard` package.

```bash
npm i -D copy-to-clipboard
```

## Copy Secret

Next `./methods/copySecret.ts`

```typescript
import sjcl from "@tinyanvil/sjcl";
import copy from "copy-to-clipboard";

import { handleError } from "@services/error";

export default async function copySecret(e: Event) {
  try {
    e.preventDefault();

    const pincode = await this.setPrompt("Enter your keystore pincode");

    if (!pincode) return;

    this.error = null;

    const secret = sjcl.decrypt(pincode, this.account.keystore);
    copy(secret);
  } catch (err) {
    this.error = handleError(err);
  }
}
```

You may not actually include this in your production wallet, but for now it's a simple demonstration of how to programmatically gain access to the secret key at a later date for making payments, creating trustlines, etc. It’s essentially the `createAccount` in reverse: it asks for the pincode to decrypt the keystore which, once decrypted, we `copy` into the clipboard.

## Sign Out

Finally `./methods/signOut.ts`

```typescript
import { remove } from "@services/storage";
import { handleError } from "@services/error";

export default async function signOut(e: Event) {
  try {
    e.preventDefault();

    const confirmNuke = await this.setPrompt(
      "Are you sure? This will nuke your account",
      "Enter NUKE to confirm",
    );

    if (!confirm || !/nuke/gi.test(confirmNuke)) return;

    this.error = null;

    await remove("keyStore");
    location.reload();
  } catch (err) {
    this.error = handleError(err);
  }
}
```

It’s important to allow users to nuke their account, but we need to be careful to confirm that action with our faithful `setPrompt`. Once they opt to “NUKE” the account we can remove the `keyStore` and reload the app.

## Set Prompt

Speaking of `setPrompt` the last method in our `wallet.ts` file is `./methods/setPrompt.ts`.

```typescript
export default function setPrompt(
  message: string,
  placeholder?: string,
  options?: Array<any>,
): Promise<string> {
  this.prompter = {
    ...this.prompter,
    show: true,
    message,
    placeholder,
    options,
  };

  return new Promise((resolve, reject) => {
    this.prompter.resolve = resolve;
    this.prompter.reject = reject;
  });
}
```

In `setPrompt`, we see how the prompt state is set, and how the Promise is set up to allow us to wait on the prompt whenever we call this method. It’s actually pretty slick, and it might be worth looking back at the `src/components/prompt/prompt.tsx` to see how the `resolve` and `reject` functions get called. It’s not central to our wallet creation, but it’s a pretty handy little component that will serve us well in the future as we continue to request input from the user.

That’s it folks! Restart the server with `npm start` and you’ve got a perfectly legitimate, minimal Bantu wallet key creation and storage Web Component! It's a solid foundation for a non-custodial wallet that relies on a simple pincode.
