Skip to content

Instantly share code, notes, and snippets.

@dabit3
Last active November 16, 2022 05:46
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save dabit3/7cbd18b8bc4b495c4831f8674902eb42 to your computer and use it in GitHub Desktop.
Save dabit3/7cbd18b8bc4b495c4831f8674902eb42 to your computer and use it in GitHub Desktop.
Persisting a keypair for reading across clients
/*
* this file includes both a node.js script for creating a keypair
* as well as the client code for using it
*/
/* createKeypair.js */
const fs = require('fs')
const anchor = require("@project-serum/anchor");
const web3 = require('@solana/web3.js')
const account = anchor.web3.Keypair.generate();
fs.writeFileSync('./app/src/keypair.json', JSON.stringify(account))
/* App.js */
import './App.css';
import { useEffect, useState } from 'react';
import {
Program,
Provider,
web3,
} from '@project-serum/anchor'
import {
Connection,
PublicKey,
} from '@solana/web3.js'
import idl from './idl.json'
import kp from './keypair.json'
const arr = Object.values(kp._keypair.secretKey)
const secret = new Uint8Array(arr)
const pair = web3.Keypair.fromSecretKey(secret)
const opts = {
preflightCommitment: "processed"
}
const { SystemProgram } = web3
const programID = new PublicKey(idl.metadata.address)
function App() {
const [value, setValue] = useState(null)
const [connected, setConnected] = useState(false)
useEffect(() => {
console.log('solana:', window.solana)
if (window.solana) {
window.solana.on("connect", () => {
console.log('updated...')
})
}
return () => {
window.solana.disconnect();
}
}, [])
async function getProvider() {
const wallet = window.solana
const network = "http://127.0.0.1:8899"
const connection = new Connection(network, opts.preflightCommitment);
const provider = new Provider(
connection, wallet, opts.preflightCommitment,
)
return provider
}
async function createCounter() {
const provider = await getProvider()
const program = new Program(idl, programID, provider);
try {
await program.rpc.create({
accounts: {
baseAccount: pair.publicKey,
user: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [pair]
})
const account = await program.account.baseAccount.fetch(pair.publicKey)
console.log('account: ', account)
setValue(account.count.toString())
} catch (err) {
console.log("Transaction error: ", err)
}
}
async function increment() {
const provider = await getProvider()
const program = new Program(idl, programID, provider);
await program.rpc.increment({
accounts: {
baseAccount: pair.publicKey
}
});
const account = await program.account.baseAccount.fetch(pair.publicKey);
console.log('acc: ', account)
setValue(account.count.toString())
}
async function getWallet() {
await window.solana.connect();
try {
const wallet = typeof window !== 'undefined' && window.solana;
await wallet.connect()
setConnected(true)
} catch (err) {
console.log('err: ', err)
}
}
console.log("value:", value)
if (!connected) {
return (
<div className="App">
<button onClick={getWallet}>Connect wallet</button>
</div>
)
}
if (connected) return (
<div className="App">
<header>
<button onClick={createCounter}>Create counter</button>
<button onClick={increment}>Increment counter</button>
{
value >= 0 ? (
<h2>{value}</h2>
) : (
<h2>Please create the counter.</h2>
)
}
</header>
</div>
);
}
export default App;
@mwrites
Copy link

mwrites commented Dec 10, 2021

@dabit3 great stuff, still new to this but I have a question regarding security

Is there a way to not leak the secret key in the frontend code?
const baseAccount = web3.Keypair.fromSecretKey(secret);

In my web2 mind I thought about wrapping the fetch account behind a web2 api but in your experience have you found best practices to prevent having your baseAccount's secret key on the frontend code? I have tried injecting with env variables but of course it will still end up in the front end code and can be inspected by looking at the page source.

@knivets
Copy link

knivets commented Dec 18, 2021

@matanwrites the solution here is to use a program derived address (derived from user public key) and not generating any private key on the client.

@mwrites
Copy link

mwrites commented Jan 5, 2022

@knivets triple kudos! thanks for the hint!

Your articles and this one was especially helpful https://www.brianfriel.xyz/understanding-program-derived-addresses/

JS side

const initialize = async () => {
  const { pda, bump } = await getProgramDerivedAddress();
  const program = await getProgram();
  try {
    await program.rpc.initialize(new BN(bump), {
      accounts: {
        user: getProvider().wallet.publicKey,
        baseAccount: pda,
        systemProgram: web3.SystemProgram.programId,
      },
    });
  }
  ...
};

const getProgramDerivedAddress = async () => {
  const [pda, bump] = await PublicKey.findProgramAddress(
    [Buffer.from('my_seed')],
    programAddress
  );
  console.log(`Got ProgramDerivedAddress: bump: ${bump}, pubkey: ${pda.toBase58()}`);
  return { pda, bump };
};

const getBaseAccount = async () => {
  const { pda } = await getProgramDerivedAddress();
  const program = await getProgram();
  try {
    return await program.account.baseAccount.fetch(pda);;
  }
  ...
};

Solana side

#[program]
pub mod moon {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, base_account_bump: u8) -> ProgramResult {
        ctx.accounts.base_account.bump = base_account_bump;
        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(base_account_bump: u8)]
pub struct Initialize<'info> {
    // space: depends on what you gonna store and is required if you use a vec or array
    #[account(init, seeds = [b"my_seed".as_ref()], bump = base_account_bump, payer = user, space = 9000)]
    pub base_account: Account<'info, BaseAccount>,
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
#[derive(Default)]
pub struct BaseAccount {
    pub bump: u8,
    ...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment