Exploring Program Derive Addresses (PDA's) with Solana, Anchor and React
Tags: react, solana, crypto, programming
Published: 2021-12-13 20:15:25


**Note:** As of the time of this writing (**Monday, December 13, 2021**), Solana's `testnet` environment (faucet/airdrop) seems to be having issues. Please select the `devnet` on the selector (or just don't change it, since is the default value). Remember to change your wallet to point to the `devnet` network.

**Note**: all the code for this post can be found [here](https://github.com/fmendez/animal-trading-farm-public).  There's a demo [here](https://jolly-aryabhata-ecfd8b.netlify.app/) showcasing the concepts in this post.

## Use cases

Let's image the following scenarios. You built an dApp that uses tokens you created / minted. For testing purposes, you want to allow users to self-airdrop some amount of those tokens (on testings environments). The problem is -since you minted the tokens- the one with the authority to both mint more tokens or transfer them is you. That means you have to sign every transaction dealing with those mints.

Another scenario is a user wanting to trade some items with other users. For safety, the items to trade should be put in some sort of temporary account (escrow account) and only be released to a 3rd party they accept the offer. The difficulty is, if the escrow account belongs to the user, they need to approve / sign the transaction for the tokens to be released. We don't want the user to be involved in the release of the items directly. 

In both scenarios, we need to have a sort of "proxy" that can sign a transaction on behalf of the owner of the program. For that, we'll need Program Derive Addresses (PDA's).

In the scenarios that I described above, we would need to use [Cross-Program Invocations](https://docs.solana.com/developing/programming-model/calling-between-programs#cross-program-invocations). In both scenarios, we would interact with the [Token Program](https://spl.solana.com/token). For the case of airdropping, we will `mint` more of the existing tokens to a user and in the second case we will `transfer` tokens.

 In both of these scenarios, it is the PDA that would have the authority to sign these transactions in our behalf. 

### PDA's defined

These are accounts that are owned by a program and not controlled by a private key like other accounts. `Pubkey::create_program_address` generates these addresses. This method will hash the seeds with program ID to create a new 32-byte address. There's a change (50%) that this may be a point on the [ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) curve. That means there is a private key associated with this address. In such cases, the safety of the Solana programming model would be compromised. The `Pubkey::create_program_address` will fail in case the generated address lie on the ed25519 curve.

To simplify things, the method `Pubkey::find_program_address` will [internally](https://github.com/solana-labs/solana/blob/f32216588dfdbc7a7160c26331ce657a90f95ae7/sdk/program/src/pubkey.rs#L439) call the `create_program_address` as many times as necessary until it finds a valid one for us.


### PDAs in action


![demo app home](https://fmendezblogbuckets3.s3.us-east-2.amazonaws.com/pda-with-solana-react/pda-with-solana-react-main-page.png)

To explore PDA's further, I decided to build a farm animal trading app. The animals that you trade are tokens. In this app, PDA are used in 2 different ways.  The first way is an escrow account. Users put away (escrow) the tokens they are offering. These tokens will be release if either some other user accept the offer or the user initiating the offer decides to cancel it. In both cases, it is the escrow account itself that has the authority to sign the transferring of tokens to the destination.

**Note:** For the code snippets, I'll only be showing the relevant sections, and I'll be linking the line number on the repo. All the code can be found [here](https://github.com/fmendez/animal-trading-farm-public).

### Escrow accounts

First, we need to derive an address. This will be our escrow account([code](https://github.com/fmendez/animal-trading-farm-public/blob/main/app/src/api.ts#L79)).

```js
    const offer = anchor.web3.Keypair.generate();
    const [escrowedTokensOfOfferMaker, escrowedTokensOfOfferMakerBump] = await anchor.web3.PublicKey.findProgramAddress(
      [offer.publicKey.toBuffer()],
      program.programId
    )
```

We store the bump so that we don't have to keep recalculating it by call the `findProgrammAddress` method and having to pass it down from the frontend.

In the `contract / program` this is how we use it ([here](https://github.com/fmendez/animal-trading-farm-public/blob/main/programs/animal-trading-farm/src/instructions/make_offer.rs#L37) you'll find the entire file). Here, we're creating an offer: 

```rust
    anchor_spl::token::transfer(
        CpiContext::new(
            ctx.accounts.token_program.to_account_info(),
            anchor_spl::token::Transfer {
                from: ctx
                    .accounts
                    .token_account_from_who_made_the_offer
                    .to_account_info(),
                to: ctx
                    .accounts
                    .escrowed_tokens_of_offer_maker
                    .to_account_info(),
                authority: ctx.accounts.who_made_the_offer.to_account_info(),
            },
        ),
        im_offering_this_much,
    )
```

We're transferring the tokens from the account initiating the offer to the escrow account. We're also specifying how much we're transferring.

At this point, we can either accept or cancel an offer. For the cancelling [part](https://github.com/fmendez/animal-trading-farm-public/blob/main/programs/animal-trading-farm/src/instructions/cancel_offer.rs#L31):

```rust
    // Transfer what's on the escrowed account to the offer reciever.
    anchor_spl::token::transfer(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            anchor_spl::token::Transfer {
                from: ctx
                    .accounts
                    .escrowed_tokens_of_offer_maker
                    .to_account_info(),
                to: ctx
                    .accounts
                    .where_the_escrowed_account_was_funded_from
                    .to_account_info(),
                authority: ctx
                    .accounts
                    .escrowed_tokens_of_offer_maker
                    .to_account_info(),
            },
            &[&[
                ctx.accounts.offer.key().as_ref(),
                &[ctx.accounts.offer.escrowed_tokens_of_offer_maker_bump],
            ]],
        ),
        ctx.accounts.escrowed_tokens_of_offer_maker.amount,
    )?;

    // Close the escrow account
    anchor_spl::token::close_account(CpiContext::new_with_signer(
        ctx.accounts.token_program.to_account_info(),
        anchor_spl::token::CloseAccount {
            account: ctx
                .accounts
                .escrowed_tokens_of_offer_maker
                .to_account_info(),
            destination: ctx.accounts.who_made_the_offer.to_account_info(),
            authority: ctx
                .accounts
                .escrowed_tokens_of_offer_maker
                .to_account_info(),
        },
        &[&[
            ctx.accounts.offer.key().as_ref(),
            &[ctx.accounts.offer.escrowed_tokens_of_offer_maker_bump],
        ]],
    ))
```
 
We're sending the tokens back to the account that initiated the offer. Notice that the authority that's signing-off the transaction is the PDA, since it "owns" the tokens. We're also closing the escrow account since it no longer needed.

The last relevant part is the "swapping" of tokens after accepting an offer: 

```rust
        // Transfer token to who started the offer
        anchor_spl::token::transfer(
            CpiContext::new(
                ctx.accounts.token_program.to_account_info(),
                anchor_spl::token::Transfer {
                    from: ctx
                        .accounts
                        .account_holding_what_receiver_will_give
                        .to_account_info(),
                    to: ctx
                        .accounts
                        .account_holding_what_maker_will_get
                        .to_account_info(),
                    authority: ctx.accounts.who_is_taking_the_offer.to_account_info(),
                },
            ),
            ctx.accounts.offer.amount_received_if_offer_accepted,
        )?;

        // Transfer what's on the escrowed account to the offer reciever.
        anchor_spl::token::transfer(
            CpiContext::new_with_signer(
                ctx.accounts.token_program.to_account_info(),
                anchor_spl::token::Transfer {
                    from: ctx
                        .accounts
                        .escrowed_tokens_of_offer_maker
                        .to_account_info(),
                    to: ctx
                        .accounts
                        .account_holding_what_receiver_will_get
                        .to_account_info(),
                    authority: ctx
                        .accounts
                        .escrowed_tokens_of_offer_maker
                        .to_account_info(),
                },
                &[&[
                    ctx.accounts.offer.key().as_ref(),
                    &[ctx.accounts.offer.escrowed_tokens_of_offer_maker_bump],
                ]],
            ),
            ctx.accounts.escrowed_tokens_of_offer_maker.amount,
        )?;

        // Close the escrow account
        anchor_spl::token::close_account(CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            anchor_spl::token::CloseAccount {
                account: ctx
                    .accounts
                    .escrowed_tokens_of_offer_maker
                    .to_account_info(),
                destination: ctx.accounts.who_made_the_offer.to_account_info(),
                authority: ctx
                    .accounts
                    .escrowed_tokens_of_offer_maker
                    .to_account_info(),
            },
            &[&[
                ctx.accounts.offer.key().as_ref(),
                &[ctx.accounts.offer.escrowed_tokens_of_offer_maker_bump],
            ]],
        ))
```

We do this is 3 steps.  First, we send the tokens wanted to the user that initiated the offer. We then transfer the escrowed tokens to the user accepting the offer. Then, as with the last snipped, we're closing the escrow account since it no longer required.


### Airdropping

The other way the application uses PDA is with airdropping. In this case, we want to allow users to self-mint (airdrop) a limited amount of something we own (the tokens). In those cases, the PDA has the authority to sign the minting of new tokens on our behalf. 

Same as before, we're using the `findProgramAddress` to get a PDA:

```rust
    const cowSeed = Buffer.from(anchor.utils.bytes.utf8.encode("cow-mint-faucet"));
    const pigSeed = Buffer.from(anchor.utils.bytes.utf8.encode("pig-mint-faucet"));

    const [cowMintPda, cowMintPdaBump] = await anchor.web3.PublicKey.findProgramAddress(
      [cowSeed],
      program.programId);

    const [pigMintPda, pigMintPdaBump] = await anchor.web3.PublicKey.findProgramAddress(
      [pigSeed],
      program.programId);

```


The airdrop code simplifies to this: 

```rust
    anchor_spl::token::mint_to(
        CpiContext::new_with_signer(
            ctx.accounts.token_program.to_account_info(),
            anchor_spl::token::MintTo {
                mint: ctx.accounts.mint.to_account_info(),
                to: ctx.accounts.destination.to_account_info(),
                authority: ctx.accounts.mint.to_account_info(),
            },
            &[&[&mint_seed, &[mint_bump]]],
        ),
        amount,
    )?;
```

Same as before, the most important thing to notice here is that the PDA itself has the authority to sign off transactions.

### Putting all together.

There's a demo app deployed [here](https://jolly-aryabhata-ecfd8b.netlify.app/). Both `devnet` and `testnet` have the app deployed. You can use the selector on the page to change between the two (if you do, remember to change what network you're pointing in your walled). 

 You can airdrop some SOL if you don't have any. Furthermore, you can airdrop some farm animals to start trading. 

**Note:** Every 20 seconds, I'm pulling to an off-chain db to display the full list of offers available to all users.

### Final thoughts.

This was another fun experiment with Solana. I wanted to keep everything on chain but ended up having an off-chain DB with all offers created to make them available to all users. I'll explore putting all the offers on-chain. 

Overall, I'm enjoying my time playing with Solana. I'll keep experimenting and reporting back. Until the next time.

## Resources

- The animal icons came from this absolutely amazing creator's site: [https://kenney.nl/](https://kenney.nl/)
-  Background image sourced from: [https://www.pixilart.com/art/pixel-farm-bb3c119b728eafd](https://www.pixilart.com/art/pixel-farm-bb3c119b728eafd)
-  Learned more about PDA implementations from ([https://github.com/cqfd/quidproquo](https://github.com/cqfd/quidproquo)) and ([https://github.com/project-serum/anchor/tree/master/tests/escrow](https://github.com/project-serum/anchor/tree/master/tests/escrow))
- [https://spl.solana.com/](https://spl.solana.com/)