Building a simple on-chain point of sale with Solana, Anchor and React
Tags: react, rust, solana, javascript
Published: 2021-11-26 22:19:38

**Note**: All the code for this post can be found in this [github repo](https://github.com/fmendez/ticketing-system).

A few days ago, I started playing with the Solana blockchain. I was initially interested because it was built on rust (I freaking love rust!). To explore it, I decided to build a basic point of sales (POS) for event tickets.

I initially started reading the code on the [Solana Program Library](https://github.com/solana-labs/solana-program-library) and experimenting but decided to go with Anchor just get started building something more quickly.

I'm not going to describe how to install Solana or Anchor. There is already a fantastic guide written [here](https://dev.to/dabit3/the-complete-guide-to-full-stack-solana-development-with-react-anchor-rust-and-phantom-3291)

The first thing I really love about Anchor is that I was able to start with a test-driven development approach. I started with the first test:

```go
 describe("ticketing-system", () => {
  const anchor = require("@project-serum/anchor");
  const assert = require("assert");

  const { SystemProgram } = anchor.web3;
  // Configure the client to use the local cluster.
  const provider = anchor.Provider.env();
  anchor.setProvider(provider);
  const program = anchor.workspace.TicketingSystem;
  const _ticketingSystem = anchor.web3.Keypair.generate();
  const tickets = [1111, 2222, 3333];

  it("Is initializes the ticketing system", async () => {
    const ticketingSystem = _ticketingSystem;
    await program.rpc.initialize(tickets, {
      accounts: {
        ticketingSystem: ticketingSystem.publicKey,
        user: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [ticketingSystem],
    });

    const account = await program.account.ticketingSystem.fetch(
      ticketingSystem.publicKey
    );

    assert.ok(account.tickets.length === 3);
    assert.ok(
      account.tickets[0].owner.toBase58() ==
      ticketingSystem.publicKey.toBase58()
    );
  });
});
```

With this, I'm testing the ability to create 3 tickets, store it on-chain and ensure that all of them are owned by the program account.

To make the test pass, we have to work on the program account (e.g., `lib.rs`). First, let's create the structs that represent both our Ticket and the TicketingSystem

```go
#[account]
#[derive(Default)]
pub struct TicketingSystem {
    pub tickets: [Ticket; 3],
}

#[derive(AnchorSerialize, AnchorDeserialize, Default, Clone, Copy)]
pub struct Ticket {
    pub owner: Pubkey,
    pub id: u32,
    pub available: bool,
    pub idx: u32,
}
```

The `#[account]` on the `TicketingSystem` automatically prepend the first 8 bytes of the SHA256 of the account’s Rust ident (e.g., what's inside the `declare_id`). This is a security check that ensures that a malicious actor could not just inject a different type and pretend to be that program account.

We are creating an array of `Ticket`, so we have to make it serializable. The other thing to note is that I'm specifying the owner to be of type `Pubkey`. The idea is that upon creation, the ticket will be initially owned by the program and when I make a purchase the ownership will be transferred.

The remaining structures:

```go
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user)]
    pub ticketing_system: Account<'info, TicketingSystem>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct PurchaseTicket<'info> {
    #[account(mut)]
    pub ticketing_system: Account<'info, TicketingSystem>,
    pub user: Signer<'info>,
}
```

The `#[derive(Accounts)]` implements an `Accounts` deserializer. This applies any constraints specified by the `#[account(...)]` attributes. For instance, on the `Initialize` struct we have had the `payer = user` constrains specifying who's paying for the initialization cost (e.g., when the program is deploying).

The following code handles the actual initialization:

```go
    pub fn initialize(ctx: Context<Initialize>, tickets: Vec<u32>) -> ProgramResult {
        let ticketingSystem = &mut ctx.accounts.ticketing_system;
        let owner = ticketingSystem.to_account_info().key;

        for (idx, ticket) in tickets.iter().enumerate() {
            ticketingSystem.tickets[idx] = Ticket {
                owner: *owner,
                id: *ticket,
                available: true,
                idx: idx as u32,
            };
        }
        Ok(())
    }
```

After some fiddling and debugging, I finally get a passing test with `anchor test`:

```go
  ticketing-system
    ✔ Is initializes the ticketing system (422ms)


  1 passing (426ms)

✨  Done in 8.37s.
```

Now that I have a list of on-chain Tickets I can retrieve, I want to see them. I decide to create a React app for this. Anchor already created an `/app` folder, let's use it.

The overall setup is very much like the one [here](https://dev.to/dabit3/the-complete-guide-to-full-stack-solana-development-with-react-anchor-rust-and-phantom-3291), with the difference that I'm using Typescript.

The next React code will be shown without the imports. You can find the full code [here](https://github.com/fmendez/ticketing-system):

The `App.tsx` contains code to detect if we're connected to a wallet or not:

```javascript
...
function App() {
  const wallet = useWallet();

  if (!wallet.connected) {
    return (
      <div className="main-container p-4">
        <div className="flex flex-col lg:w-1/4 sm:w-full md:w-1/2">
          <WalletMultiButton />
        </div>

      </div>
    );
  } else {
    return (
      <div className="main-container">
        <div className="border-b-4 border-brand-border self-stretch">
          <h1 className="font-bold text-4xl text-center p-4 text-brand-border">Ticket Sales</h1>
        </div>
        <Tickets />
      </div>
    );
  }
}

export default App;
```

I created a few components for `Ticket` and `Tickets`. I also used `tailwindcss` to style them.

This is what `Tickets` look like:

```javascript
function Tickets() {
  const wallet = useWallet();

  const [tickets, setTickets] = useState<TicketInfo[]>([]);
  const initializeTicketingSystem = async () => {
    const provider = await getProvider((wallet as any) as NodeWallet);
    const program = new Program((idl as any) as Idl, programID, provider);

    try {
      await program.rpc.initialize(generateTickets(3), {
        accounts: {
          ticketingSystem: ticketingSystem.publicKey,
          user: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,
        },
        signers: [ticketingSystem],
      });
      const account = await program.account.ticketingSystem.fetch(
        ticketingSystem.publicKey
      );
      setTickets(account.tickets);
    } catch (err) {
      console.log("Transaction error: ", err);
    }
  };

  return (
    <div>
      {tickets.length === 0 && (
        <button className="bg-brand-btn rounded-xl font-bold text-xl m-4 p-2 hover:bg-brand-btn-active" onClick={initializeTicketingSystem}>
          Generate Tickets
        </button>
      )}
      {tickets.map((ticket) => (
        <Ticket
          key={ticket.id}
          ticket={ticket}
          ticketingSystem={ticketingSystem}
          setTickets={setTickets}
        />
      ))}
    </div>
  );
}

export default Tickets;
```

Here, we provide a `Generate Tickets` button that will initialize the tickets on-chain. These RPC calls could be moved to an API file, but I'll keep there since it is the only place that needs it. The code for the `Ticket` is similar in structure. Here will call the `purchase` RPC call:

```javascript
  ....
  const purchase = async (ticket: TicketInfo) => {
    const provider = await getProvider((wallet as any) as NodeWallet);
    const program = new Program((idl as any) as Idl, programID, provider);
    try {
      await program.rpc.purchase(ticket.id, ticket.idx, {
        accounts: {
          ticketingSystem: ticketingSystem.publicKey,
          user: provider.wallet.publicKey,
        },
      });

      const account = await program.account.ticketingSystem.fetch(
        ticketingSystem.publicKey
      );
      setTickets(account.tickets);
    } catch (err) {
      console.log("Transaction error: ", err);
    }
  };
  ....
```

All the styled components look like this:


![Ticket Sales](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot4bh1o6in5pfmchtc5s.png)
![Tickets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aspxwkgwl88ab1iwwarw.png)
![Purchase](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtyvzfmzx27r10u6vnrl.png)

A gif showing it in action:

![Using the app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7ge35x9abod4oejsyz8.gif)

You can try a live version ( pointing to the testnet.api ) [here] (https://61a366f7510b850007e580bd--elastic-shaw-61795a.netlify.app/)

For fun, I added a QR code that's based on the ticket number and the account that made the purchase.

Overall, this was a fun experiment. Based on my initial experimentation using the Solana SDK directly, there's a lot that `Anchor` is abstracting away. There's also good practices built into it (e.g., the 8 bytes discriminator for the program's account, lack of order when accessing accounts, etc.). I'll be spending more time with both Anchor and the Solana SDK itself to make sure I understand what's being abstracted away.

Finally, there are a few troubleshooting tips that might help you when using Anchor.

- Remember to `anchor build` and `anchor deploy` before running `anchor test`. That ensures that you have the latest bytecode on the runtime. You will encounter a serialization error if you don't.
- When you encounter custom errors such as this: `"Transaction simulation failed: Error processing Instruction 0: custom program error: 0x66"`. Convert the number from hex -> integer, if the number is >=300 it's an error from your program, look into the errors section of the idl that gets generated when building your anchor project. If it is < 300, then search the matching error number [here](https://github.com/project-serum/anchor/blob/master/lang/src/error.rs#L5)
- When you get this type of error: `"error: Error: 163: Failed to deserialize the account"`. Very often it's because you haven't allocated enough space (anchor tried to write the account back out to storage and failed). This is solved by allocating more space during the initialization.

For example, had to bump this to 64 to solve the issue. Was initially at 8:

```go
  ...
  #[account(init, payer = user, space = 64 + 64)]
  pub ticketing_system: Account<'info, TicketingSystem>,
  ...
```

Alternatively (and the recommended option from what I've gathered) is to leave the space out for Anchor to calculate it for you. The exception is if you're dealing with a complex of Custom types that Anchor can't calculate for some reason.

- If you for whatever reason you need to generate a new program ID (e.g., a fail deployment to `devent` or `testdeve` made that account address in use and is not upgradeable). You can simply delete the `/deploy` folder under target (e.g `/root-of-your-anchor-project/target/deploy`) and run `anchor build` again. That will regenerate the `/deploy` folder. After that, you just need to run this from your root project directory `solana address -k target/deploy/name-of-your-file-keypair.json`. You can take that output and upgrade both the `declare_id()` in your `lib.rs` and `Anchor.toml` with the new program ID. Finally, you have to run `anchor build` again to rebuild with the new program ID.

I still have a lot to explore, I find both `Anchor` and the current Solana ecosystem very exciting. Will continue to post my progress. Until the next time.