import { Keypair , Connection , PublicKey , LAMPORTS_PER_SOL , SystemProgram , TransactionInstruction , Transaction , sendAndConfirmTransaction , } from '@solana/web3.js' ; import fs from 'mz/fs' ; import path from 'path' ; import * as borsh from 'borsh' ; import { getPayer , getRpcUrl , createKeypairFromFile } from './utils' ; /**
 * Connection to the network
 */ let connection ; /**
 * Keypair associated to the fees' payer
 */ let payer ; /**
 * Hello world's program id
 */ let programId ; /**
 * The public key of the account we are saying hello to
 */ let greetedPubkey ; /**
 * Path to program files
 */ const PROGRAM_PATH = path . resolve ( __dirname , '../../dist/program' ) ; /**
 * Path to program shared object file which should be deployed on chain.
 * This file is created when running either:
 *   - `npm run build:program-c`
 *   - `npm run build:program-rust`
 */ const PROGRAM_SO_PATH = path . join ( PROGRAM_PATH , 'helloworld.so' ) ; /**
 * Path to the keypair of the deployed program.
 * This file is created when running `solana program deploy dist/program/helloworld.so`
 */ const PROGRAM_KEYPAIR_PATH = path . join ( PROGRAM_PATH , 'helloworld-keypair.json' ) ; /**
 * The state of a greeting account managed by the hello world program
 */ class GreetingAccount { counter = 0 ; constructor ( fields = undefined ) { if ( fields ) { this . counter = fields . counter ; } } } /**
 * Borsh schema definition for greeting accounts
 */ const GreetingSchema = new Map ( [ [ GreetingAccount , { kind : 'struct' , fields : [ [ 'counter' , 'u32' ] ] } ] , ] ) ; /**
 * The expected size of each greeting account.
 */ const GREETING_SIZE = borsh . serialize ( GreetingSchema , new GreetingAccount ( ) ) . length ; /**
 * Establish a connection to the cluster
 */ export async function establishConnection ( ) { const rpcUrl = await getRpcUrl ( ) ; connection = new Connection ( rpcUrl , 'confirmed' ) ; const version = await connection . getVersion ( ) ; console . log ( 'Connection to cluster established:' , rpcUrl , version ) ; } /**
 * Establish an account to pay for everything
 */ export async function establishPayer ( ) { let fees = 0 ; if ( ! payer ) { const { feeCalculator } = await connection . getRecentBlockhash ( ) ; fees += await connection . getMinimumBalanceForRentExemption ( GREETING_SIZE ) ; fees += feeCalculator . lamportsPerSignature * 100 ; payer = await getPayer ( ) ; } let lamports = await connection . getBalance ( payer . publicKey ) ; if ( lamports < fees ) { const sig = await connection . requestAirdrop ( payer . publicKey , fees - lamports ) ; await connection . confirmTransaction ( sig ) ; lamports = await connection . getBalance ( payer . publicKey ) ; } console . log ( 'Using account' , payer . publicKey . toBase58 ( ) , 'containing' , lamports / LAMPORTS_PER_SOL , 'SOL to pay for fees' ) ; } /**
 * Check if the hello world BPF program has been deployed
 */ export async function checkProgram ( ) { try { const programKeypair = await createKeypairFromFile ( PROGRAM_KEYPAIR_PATH ) ; programId = programKeypair . publicKey ; } catch ( err ) { const errMsg = ( err as Error ) . message ; throw new Error ( `Failed to read program keypair at '${ PROGRAM_KEYPAIR_PATH }' due to error: ${ errMsg }. Program may need to be deployed with \`solana program deploy dist/program/helloworld.so\`` ) ; } const programInfo = await connection . getAccountInfo ( programId ) ; if ( programInfo === null ) { if ( fs . existsSync ( PROGRAM_SO_PATH ) ) { throw new Error ( 'Program needs to be deployed with `solana program deploy dist/program/helloworld.so`' ) ; } else { throw new Error ( 'Program needs to be built and deployed' ) ; } } else if ( ! programInfo . executable ) { throw new Error ( `Program is not executable` ) ; } console . log ( `Using program ${ programId . toBase58 ( ) }` ) ; const GREETING_SEED = 'hello' ; greetedPubkey = await PublicKey . createWithSeed ( payer . publicKey , GREETING_SEED , programId ) ; const greetedAccount = await connection . getAccountInfo ( greetedPubkey ) ; if ( greetedAccount === null ) { console . log ( 'Creating account' , greetedPubkey . toBase58 ( ) , 'to say hello to' ) ; const lamports = await connection . getMinimumBalanceForRentExemption ( GREETING_SIZE ) ; const transaction = new Transaction ( ) . add ( SystemProgram . createAccountWithSeed ( { fromPubkey : payer . publicKey , basePubkey : payer . publicKey , seed : GREETING_SEED , newAccountPubkey : greetedPubkey , lamports , space : GREETING_SIZE , programId , } ) ) ; await sendAndConfirmTransaction ( connection , transaction , [ payer ] ) ; } } /**
 * Say hello
 */ export async function sayHello ( ) { console . log ( 'Saying hello to' , greetedPubkey . toBase58 ( ) ) ; const instruction = new TransactionInstruction ( { keys : [ { pubkey : greetedPubkey , isSigner : false , isWritable : true } ] , programId , data : Buffer . alloc ( 0 ) , } ) ; await sendAndConfirmTransaction ( connection , new Transaction ( ) . add ( instruction ) , [ payer ] ) ; } /**
 * Report the number of times the greeted account has been said hello to
 */ export async function reportGreetings ( ) { const accountInfo = await connection . getAccountInfo ( greetedPubkey ) ; if ( accountInfo === null ) { throw 'Error: cannot find the greeted account' ; } const greeting = borsh . deserialize ( GreetingSchema , GreetingAccount , accountInfo . data ) ; console . log ( greetedPubkey . toBase58 ( ) , 'has been greeted' , greeting . counter , 'time(s)' ) ; }