1

So I am mainly backend focused and new to Solana trying to become a full stack dev. I am very new to frontend development and have been facing some problems there.

So I've been working on my first Solana program made with the Anchor framework using Anchor 0.30.0. It initializes the Solana program, increments an unsigned integer, decrements an unsigned integer, and sets a value of 100.

On the Rust on chain side of things I have my Anchor workspace setup, solana-test-validator running, and everything has been going pretty smoothly. Here is my on chain code lib.rs:

use anchor_lang::prelude::*;

declare_id!("B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf");

#[program]
mod basic_1 {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = 0;
        Ok(())
    }

    pub fn update(ctx: Context<Update>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }

    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data += 1;
        Ok(())
    }

    pub fn decrement(ctx: Context<Decrement>) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data -= 1;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 8)]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}

#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}

#[derive(Accounts)]
pub struct Decrement<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}

#[account]
pub struct MyAccount {
    pub data: u64,
}

and here is the Anchor toml file:

[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[programs.localnet]
basic_1 = "B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf"

[scripts]
test = "yarn run mocha -t 1000000 tests/"

And here is my basic-1.js used for Anchor Test:

const assert = require("assert");
const anchor = require("@coral-xyz/anchor");
const { SystemProgram } = anchor.web3;

describe("basic-1", () => {
  // Use a local provider.
  const provider = anchor.AnchorProvider.local();

  // Configure the client to use the local cluster.
  anchor.setProvider(provider);

  let _myAccount = null;

  it("Creates and initializes an account in a single atomic transaction (simplified)", async () => {
    // #region code-simplified
    // The program to execute.
    const program = anchor.workspace.Basic1;

    // The Account to create.
    const myAccount = anchor.web3.Keypair.generate();

    // Create the new account and initialize it with the program.
    // #region code-simplified
    await program.methods
      .initialize()
      .accounts({
        myAccount: myAccount.publicKey,
        user: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      })
      .signers([myAccount])
      .rpc();
    // #endregion code-simplified

    // Fetch the newly created account from the cluster.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was initialized.
    assert.equal(account.data, 0);

    // Store the account for the next test.
    _myAccount = myAccount;
  });

  it("Updates a previously created account", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;

    // Invoke the update rpc.
    await program.methods
      .update(new anchor.BN(100))
      .accounts({
        myAccount: myAccount.publicKey,
      })
      .rpc();

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.equal(account.data, 100);

    // #endregion update-test
  });

  it("Increment", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;

    // Invoke the update rpc.
    await program.methods
      .increment()
      .accounts({
        myAccount: myAccount.publicKey,
      })
      .rpc();

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.equal(account.data, 101);

    // #endregion update-test
  });

  it("Decrement", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;

    // Invoke the update rpc.
    await program.methods
      .decrement()
      .accounts({
        myAccount: myAccount.publicKey,
      })
      .rpc();

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.equal(account.data, 100);

    // #endregion update-test
  });
});
  1. So I generated a new account with solana-keygen new, then used yarn install to install:

    "dependencies": { "@coral-xyz/anchor": "^0.30.0" }

  2. Then I used anchor build

  3. It crossed compiled into bytecode successfully and also generated a keypair. Then I used solana address -k basic_1-keypair.json to get the Solana program address which is B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf and I added that to lib.rs in declare_id, as well as to Anchor.toml.

  4. I run anchor build again and after that 'anchor deploy'

  5. I run 'anchor test --skip-local-validator' (solana-test-validator running on separate terminal window since step 1) and the tests pass:

  Finished `test` profile [unoptimized + debuginfo] target(s) in 2.70s
     Running unittests src/lib.rs (/home/candid/anchor/examples/tutorial/basic-1/target/debug/deps/basic_1-f9c92d2eded147cd)
Deploying cluster: http://127.0.0.1:8899
Upgrade authority: /home/candid/.config/solana/id.json
Deploying program "basic_1"...
Program path: /home/candid/anchor/examples/tutorial/basic-1/target/deploy/basic_1.so...
Program Id: B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf

Deploy success

Found a 'test' script in the Anchor.toml. Running it as a test suite!

Running test suite: 

"/home/candid/anchor/examples/tutorial/basic-1/Anchor.toml"
    
    yarn run v1.22.22
    $ /home/candid/anchor/examples/tutorial/node_modules/.bin/mocha -t 1000000 tests/
    
    
      basic-1
        ✔ Creates and initializes an account in a single atomic transaction (simplified) (233ms)
        ✔ Updates a previously created account (400ms)
        ✔ Increment (405ms)
        ✔ Decrement (407ms)
    
    
      4 passing (1s)
    
    Done in 2.68s.

Ok it all looks good. Time to setup the frontend so I can interact with that smart contract using a web browser and the phantom wallet set to devmode localnet.

  1. I created a new workspace and cloned https://github.com/anza-xyz/wallet-adapter/.

  2. I use the following template: https://github.com/anza-xyz/wallet-adapter/tree/master/packages/starter/create-react-app-starter and copy over my generated idl file from my Anchor Rust workspace to /src/ of the wallet-adapter workspace and name it idl.json, here is the idl file data:

    {
       "address": "B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf",
       "metadata": {
         "name": "basic_1",
         "version": "0.1.0",
         "spec": "0.1.0",
         "description": "Created with Anchor"
       },
       "instructions": [
         {
           "name": "decrement",
           "discriminator": [
             106,
             227,
             168,
             59,
             248,
             27,
             150,
             101
           ],
           "accounts": [
             {
               "name": "my_account",
               "writable": true
             }
           ],
           "args": []
         },
         {
           "name": "increment",
           "discriminator": [
             11,
             18,
             104,
             9,
             104,
             174,
             59,
             33
           ],
           "accounts": [
             {
               "name": "my_account",
               "writable": true
             }
           ],
           "args": []
         },
         {
           "name": "initialize",
           "discriminator": [
             175,
             175,
             109,
             31,
             13,
             152,
             155,
             237
           ],
           "accounts": [
             {
               "name": "my_account",
               "writable": true,
               "signer": true
             },
             {
               "name": "user",
               "writable": true,
               "signer": true
             },
             {
               "name": "system_program",
               "address": "11111111111111111111111111111111"
             }
           ],
           "args": []
         },
         {
           "name": "update",
           "discriminator": [
             219,
             200,
             88,
             176,
             158,
             63,
             253,
             127
           ],
           "accounts": [
             {
               "name": "my_account",
               "writable": true
             }
           ],
           "args": [
             {
               "name": "data",
               "type": "u64"
             }
           ]
         }
       ],
       "accounts": [
         {
           "name": "MyAccount",
           "discriminator": [
             246,
             28,
             6,
             87,
             251,
             45,
             50,
             42
           ]
         }
       ],
       "types": [
         {
           "name": "MyAccount",
           "type": {
             "kind": "struct",
             "fields": [
               {
                 "name": "data",
                 "type": "u64"
               }
             ]
           }
         }
       ]
     } 
    
  3. Here is what package.json contains in the /create-react-app-starter directory:

    {
         "private": true,
         "name": "@solana/wallet-adapter",
         "author": "Solana Maintainers <[email protected]>",
         "repository": "https://github.com/anza-xyz/wallet-adapter",
         "license": "Apache-2.0",
         "engines": {
             "node": ">=16",
             "pnpm": ">=8"
         },
         "type": "module",
         "sideEffects": false,
         "scripts": {
             "nuke": "shx rm -rf packages/*/*/node_modules node_modules pnpm-lock.yaml || true",
             "reinstall": "pnpm run nuke && pnpm install",
             "clean": "pnpm --recursive --workspace-concurrency=0 run clean && shx rm -rf **/*.tsbuildinfo",
             "build": "turbo run build --concurrency=100%",
             "build:clean": "pnpm run clean && pnpm run build",
             "release": "pnpm run build:clean && pnpm test && changeset publish && git push --follow-tags && git status",
             "watch": "tsc --build --verbose --watch tsconfig.all.json",
             "fmt": "prettier --write '{*,**/*}.{ts,tsx,js,jsx,json}'",
             "lint": "turbo run lint --concurrency=100%",
             "lint:fix": "pnpm run fmt && eslint --fix .",
             "test": "turbo run test --concurrency=100%",
             "deploy": "pnpm run deploy:docs && pnpm run deploy:example",
             "docs": "shx rm -rf docs && NODE_OPTIONS=--max_old_space_size=16000 typedoc && shx cp ./{.nojekyll,wallets.png} docs/",
             "deploy:docs": "pnpm run docs && gh-pages --dist docs --dotfiles",
             "example": "pnpm run --filter {packages/starter/example} export",
             "deploy:example": "pnpm run example && gh-pages --dist packages/starter/example/out --dest example --dotfiles"
         },
         "devDependencies": {
             "@changesets/cli": "^2.26.1",
             "@types/node": "^18.16.18",
             "@typescript-eslint/eslint-plugin": "^5.60.0",
             "@typescript-eslint/parser": "^5.60.0",
             "eslint": "8.22.0",
             "eslint-config-prettier": "^8.8.0",
             "eslint-plugin-prettier": "^4.2.1",
             "eslint-plugin-react": "^7.32.2",
             "eslint-plugin-react-hooks": "^4.6.0",
             "eslint-plugin-require-extensions": "^0.1.3",
             "gh-pages": "^4.0.0",
             "pnpm": "^8.6.3",
             "prettier": "^2.8.8",
             "shx": "^0.3.4",
             "turbo": "^1.13.3",
             "typedoc": "^0.23.28",
             "typescript": "~4.7.4"
         },
         "overrides": {
             "@ledgerhq/devices": "6.27.1",
             "@ledgerhq/hw-transport": "6.27.1",
             "@ledgerhq/hw-transport-webhid": "6.27.1",
             "@solana/wallet-adapter-base": "^0.9.23",
             "@types/web": "npm:typescript@~4.7.4",
             "eslint": "8.22.0",
             "@ngraveio/bc-ur": "1.1.12"
         },
         "resolutions": {
             "@ledgerhq/devices": "6.27.1",
             "@ledgerhq/hw-transport": "6.27.1",
             "@ledgerhq/hw-transport-webhid": "6.27.1",
             "@solana/wallet-adapter-base": "^0.9.23",
             "@types/web": "npm:typescript@~4.7.4",
             "eslint": "8.22.0",
             "@ngraveio/bc-ur": "1.1.12"
         }
     }
    
  4. Finally here is the modified app.tsx:


import { WalletAdapterNetwork } from '@solana/wallet-adapter-base';
import { ConnectionProvider, WalletProvider, useAnchorWallet } from '@solana/wallet-adapter-react';
import { WalletModalProvider, WalletMultiButton } from '@solana/wallet-adapter-react-ui';
import { 
    PhantomWalletAdapter,
} from '@solana/wallet-adapter-wallets';
import {
    Program, AnchorProvider, web3, BN
} from '@coral-xyz/anchor';
import { clusterApiUrl, Connection } from '@solana/web3.js';
import React, { FC, ReactNode, useMemo } from 'react';
import idl from './idl.json';
require('./App.css');
require('@solana/wallet-adapter-react-ui/styles.css');

const App: FC = () => {
    return (
        <Context>
            <Content />
        </Context>
    );
};
export default App;

const Context: FC<{ children: ReactNode }> = ({ children }) => {
    // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'.
    const network = WalletAdapterNetwork.Devnet;

    // You can also provide a custom RPC endpoint.
    const endpoint = useMemo(() => clusterApiUrl(network), [network]);

    const wallets = useMemo(
        () => [
            /**
             * Wallets that implement either of these standards will be available automatically.
             *
             *   - Solana Mobile Stack Mobile Wallet Adapter Protocol
             *     (https://github.com/solana-mobile/mobile-wallet-adapter)
             *   - Solana Wallet Standard
             *     (https://github.com/solana-labs/wallet-standard)
             *
             * If you wish to support a wallet that supports neither of those standards,
             * instantiate its legacy wallet adapter here. Common legacy adapters can be found
             * in the npm package `@solana/wallet-adapter-wallets`.
             */
            new PhantomWalletAdapter(),
        ],
        // eslint-disable-next-line react-hooks/exhaustive-deps
        [network]
    );

    return (
        <ConnectionProvider endpoint={endpoint}>
            <WalletProvider wallets={wallets} autoConnect>
                <WalletModalProvider>{children}</WalletModalProvider>
            </WalletProvider>
        </ConnectionProvider>
    );
};

const Content: FC = () => {
    const wallet = useAnchorWallet();
    const baseAccount = web3.Keypair.generate();

    function getProvider () {

        if (!wallet) {
            return null;
        }
        /*Create the provider and return it to the caller*/
        /*network set to localnet for testing purposes*/

        const network = "http://127.0.0.1:8899";
        const connection = new Connection(network, "processed");

        const provider = new AnchorProvider(
          connection, wallet, {"preflightCommitment": "processed"},
        );

        return provider;

    }

    async function createCounter() {
        const provider = getProvider()
        console.log('Provider:', provider);

        if (!provider) {
            throw("Provider not found");
        }

        /*Create the program interface combining the idl, program ID, and provider*/

        /*Bug with default importing of idl.json*/

        const a = JSON.stringify(idl);
        const b = JSON.parse(a);
        console.log('Parsed IDL:', b);
        console.log('IDL Address:', idl.address);
        const program = new Program(b, provider);
        console.log('Program Object:', program);

        try { 
            /*interact with on chain program using RPC*/
            console.log('Trying to initialize program');

            await program.methods
                .initialize()
                .accounts({
                    myAccount: baseAccount.publicKey,
                    user: provider.wallet.publicKey,
                    systemProgram: web3.SystemProgram.programId,
                })
                .signers([baseAccount])
                .rpc();
            

            const account = await program.account.myAccount.fetch(baseAccount.publicKey);
            console.log('account: ', account);

        } catch(err) {
            console.log("Transaction error: ", err);

        }          


    }

    async function increment() {
        const provider = getProvider()

        if (!provider) {
            throw("Provider not found");
        }

        /*Create the program interface combining the idl, program ID, and provider*/

        /*Bug with default importing of idl.json*/

        const a = JSON.stringify(idl);
        const b = JSON.parse(a);
        const program = new Program(b, provider);

        try { 
            /*interact with on chain program using RPC*/

            await program.methods
                .increment()
                .accounts({
                    myAccount: baseAccount.publicKey,
                })
                .rpc(); 
            

            const account = await program.account.myAccount.fetch(baseAccount.publicKey);
            console.log('account: ', account.data.toString());

        } catch(err) {
            console.log("Transaction error: ", err);

        }          


    }

    async function decrement() {
        const provider = getProvider()

        if (!provider) {
            throw("Provider not found");
        }

        /*Create the program interface combining the idl, program ID, and provider*/

        /*Bug with default importing of idl.json*/

        const a = JSON.stringify(idl);
        const b = JSON.parse(a);
        const program = new Program(b, provider);

        try { 
            /*interact with on chain program using RPC*/

            await program.methods
            .decrement()
            .accounts({
              myAccount: baseAccount.publicKey,
            })
            .rpc();
            

            const account = await program.account.myAccount.fetch(baseAccount.publicKey);
            console.log('account: ', account.data.toString());

        } catch(err) {
            console.log("Transaction error: ", err);

        }          


    }

    async function update() {
        const provider = getProvider()

        if (!provider) {
            throw("Provider not found");
        }

        /*Create the program interface combining the idl, program ID, and provider*/

        /*Bug with default importing of idl.json*/

        const a = JSON.stringify(idl);
        const b = JSON.parse(a);
        const program = new Program(b, provider);

        try { 
            /*interact with on chain program using RPC*/

            await program.methods
                .update(new BN(100))
                .accounts({
                    myAccount: baseAccount.publicKey,
                })
                .rpc();

            const account = await program.account.myAccount.fetch(baseAccount.publicKey);
            console.log('account: ', account.data.toString());

        } catch(err) {
            console.log("Transaction error: ", err);

        }          


    }

    return (
        <div className="App">
            <button onClick={createCounter}>Initialize</button>
            <button onClick={increment}>Increment</button>
            <button onClick={decrement}>Decrement</button>
            <button onClick={update}>Update</button>
            <WalletMultiButton />
        </div>
    );
};

  1. So after I finish installing dependencies using npm install I get 4 errors of regarding const account = await program.account.myAccount.fetch(baseAccount.publicKey); which state "Property 'myAccount' does not exist on type 'AccountNamespace'."

If I ignore that error and use npm start it results in a partially working frontend which appear to blow through the compute-budget:

If I use click the Initialize button it pops up Phantom wallet with the message "Failed to simulate the results of this request. Confirming is unsafe and may lead to losses."

When I open the console in Chrome Developer Tools, this is what I see:

App.tsx:98 Parsed IDL: {address: 'B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf', metadata: {…}, instructions: Array(4), accounts: Array(1), types: Array(1)}
App.tsx:99 IDL Address: B2jtR6svyXAnnsg94LGPrfL1pVCHsmMmFaFXs4L5aLcf
App.tsx:101 Program Object: Program {_idl: {…}, _rawIdl: {…}, _provider: AnchorProvider, _programId: PublicKey, _coder: BorshCoder, …}
App.tsx:105 Trying to initialize program
App.tsx:122 Transaction error:  Proxy(SendTransactionError) {logs: Array(7), programErrorStack: ProgramErrorStack, stack: 'Error: failed to send transaction: Transaction sim…(http://localhost:3000/static/js/bundle.js:148:7)', message: 'failed to send transaction: Transaction simulation…ocessing Instruction 0: custom program error: 0x0'}[[Handler]]: Object[[Target]]: Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x0
    at Connection.sendEncodedTransaction (http://localhost:3000/static/js/bundle.js:17380:13)
    at async Connection.sendRawTransaction (http://localhost:3000/static/js/bundle.js:17345:20)
    at async sendAndConfirmRawTransaction (http://localhost:3000/static/js/bundle.js:1623:21)
    at async AnchorProvider.sendAndConfirm (http://localhost:3000/static/js/bundle.js:1485:14)
    at async MethodsBuilder.rpc [as _rpcFn] (http://localhost:3000/static/js/bundle.js:5250:16)
    at async createCounter (http://localhost:3000/static/js/bundle.js:148:7)[[IsRevoked]]: false

If I tried to use Increment (which obviously won't work since Initialize failed) I get the following error in my Developer Tools console once I confirm transaction with the Phantom wallet:

App.tsx:105 Trying to initialize program
App.tsx:122 Transaction error:  Proxy(SendTransactionError) {logs: Array(7), programErrorStack: ProgramErrorStack, stack: 'Error: failed to send transaction: Transaction sim…(http://localhost:3000/static/js/bundle.js:148:7)', message: 'failed to send transaction: Transaction simulation…ocessing Instruction 0: custom program error: 0x0'}[[Handler]]: Object[[Target]]: Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x0
    at Connection.sendEncodedTransaction (http://localhost:3000/static/js/bundle.js:17380:13)
    at async Connection.sendRawTransaction (http://localhost:3000/static/js/bundle.js:17345:20)
    at async sendAndConfirmRawTransaction (http://localhost:3000/static/js/bundle.js:1623:21)
    at async AnchorProvider.sendAndConfirm (http://localhost:3000/static/js/bundle.js:1485:14)
    at async MethodsBuilder.rpc [as _rpcFn] (http://localhost:3000/static/js/bundle.js:5250:16)
    at async createCounter (http://localhost:3000/static/js/bundle.js:148:7)[[IsRevoked]]: false
App.tsx:159 Transaction error:  Proxy(SendTransactionError) {logs: Array(8), programErrorStack: ProgramErrorStack, stack: 'Error: failed to send transaction: Transaction sim…(http://localhost:3000/static/js/bundle.js:175:7)', message: 'failed to send transaction: Transaction simulation…sing Instruction 2: Computational budget exceeded'}[[Handler]]: Object[[Target]]: Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 2: Computational budget exceeded
    at Connection.sendEncodedTransaction (http://localhost:3000/static/js/bundle.js:17380:13)
    at async Connection.sendRawTransaction (http://localhost:3000/static/js/bundle.js:17345:20)
    at async sendAndConfirmRawTransaction (http://localhost:3000/static/js/bundle.js:1623:21)
    at async AnchorProvider.sendAndConfirm (http://localhost:3000/static/js/bundle.js:1485:14)
    at async MethodsBuilder.rpc [as _rpcFn] (http://localhost:3000/static/js/bundle.js:5250:16)
    at async increment (http://localhost:3000/static/js/bundle.js:175:7)[[IsRevoked]]: false

Expected Behavior:

The expected behavior was that I use my phantom wallet to initialize the Solana program, and then increment, decrement, and finally set the value to 100.

Instead I am getting hit with cryptic errors starting with the issue of const account = await program.account.myAccount.fetch(baseAccount.publicKey); which state "Property 'myAccount' does not exist on type 'AccountNamespace'." despite this working fine in the basic-1.js file used for my Anchor Test.

Could a kind soul assist in what I could be missing that is preventing the frontend from working?

1
  • I am using Anchor 0.30.0
    – Mark
    Commented May 14 at 9:34

2 Answers 2

0

Can you try entering my_account or MyAccount instead of myAccount? I've previously faced this issue where types were telling me to enter myAccount, but in reality, I had to enter my_account with // @ts-ignore on top.

If it still does not work, please ensure your IDL is upto date. You can open your IDL JSON which you are using in the frontend to verify your account name. From your IDL, it does appear that the naming convention that is being used it MyAccount but types say otherwise, so just ignore them.

2
0

I have been trying the same using Phantom wallet with Solana localnet and anchor with no luck. Deployed to Devnet and that works.

Following...

Not the answer you're looking for? Browse other questions tagged or ask your own question.