Skip to content

Improve lesson codebase #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# solana-npx-client-template
# solana-versioned-transactions

This repository contains a set of tools for creating and verifying versioned transactions on the Solana blockchain.

> **Note**: This project uses `esrun` for running scripts. You can install it globally by running `npm install -g esrun`.
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
"description": "Starter template for a Solana client script",
"main": "index.ts",
"scripts": {
"start": "ts-node src/index.ts"
"start": "esrun src/index.ts"
},
"keywords": [],
"author": "test",
"license": "ISC",
"devDependencies": {
"ts-node": "^10.8.0",
"typescript": "^4.6.4"
},
"dependencies": {
"@solana/spl-token": "^0.3.6",
"@solana/web3.js": "^1.73.0",
"dotenv": "^16.0.1"
"@solana/web3.js": "^1.95.3",
"dotenv": "^16.4.5",
"esrun": "^3.2.26"
}
}
78 changes: 43 additions & 35 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,72 @@
import { initializeKeypair } from "./initializeKeypair"
import * as web3 from "@solana/web3.js"
import { initializeKeypair } from "./initializeKeypair";
import {
clusterApiUrl,
Keypair,
LAMPORTS_PER_SOL,
SystemProgram,
TransactionMessage,
VersionedTransaction,
Connection,
} from "@solana/web3.js";

async function main() {
try {
// Connect to the devnet cluster
const connection = new web3.Connection(web3.clusterApiUrl("devnet"))
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

// Initialize the user's keypair
const user = await initializeKeypair(connection)
console.log("PublicKey:", user.publicKey.toBase58())
const user = await initializeKeypair(connection);
console.log("Public Key:", user.publicKey.toBase58());

// Generate 22 addresses
const addresses = []
const recipients = [];
for (let i = 0; i < 22; i++) {
addresses.push(web3.Keypair.generate().publicKey)
recipients.push(Keypair.generate().publicKey);
}

// Get the minimum balance required to be exempt from rent
const minRent = await connection.getMinimumBalanceForRentExemption(0)

// Create an array of transfer instructions
const transferInstructions = []
const transferInstructions = [];

// Add a transfer instruction for each address
for (const address of addresses) {
for (const address of recipients) {
transferInstructions.push(
web3.SystemProgram.transfer({
SystemProgram.transfer({
fromPubkey: user.publicKey, // The payer (i.e., the account that will pay for the transaction fees)
toPubkey: address, // The destination account for the transfer
lamports: minRent, // The amount of lamports to transfer
lamports: LAMPORTS_PER_SOL * 0.01, // Transfer 0.01 SOL to each recipient
})
)
);
}

// Create a transaction and add the transfer instructions
const transaction = new web3.Transaction().add(...transferInstructions)

// Send the transaction to the cluster (this will fail in this example if addresses > 21)
const txid = await connection.sendTransaction(transaction, [user])

// Get the latest blockhash and last valid block height
const { lastValidBlockHeight, blockhash } =
await connection.getLatestBlockhash()
await connection.getLatestBlockhash();

// Create the transaction message
const message = new TransactionMessage({
payerKey: user.publicKey, // Public key of the account that will pay for the transaction
recentBlockhash: blockhash, // Latest blockhash
instructions: transferInstructions, // Instructions included in transaction
}).compileToV0Message();

// Create the versioned transaction using the message
const transaction = new VersionedTransaction(message);

// Sign the transaction
transaction.sign([user]);

// Send the transaction to the cluster (this will fail in this example if addresses > 21)
const txid = await connection.sendTransaction(transaction);

// Confirm the transaction
await connection.confirmTransaction({
blockhash: blockhash,
lastValidBlockHeight: lastValidBlockHeight,
signature: txid,
})
});

// Log the transaction URL on the Solana Explorer
console.log(`https://explorer.solana.com/tx/${txid}?cluster=devnet`)
console.log(`https://explorer.solana.com/tx/${txid}?cluster=devnet`);
console.log("Finished successfully");
} catch (error) {
console.log(error);
}

main()
.then(() => {
console.log("Finished successfully")
process.exit(0)
})
.catch((error) => {
console.log(error)
process.exit(1)
})
57 changes: 27 additions & 30 deletions src/initializeKeypair.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,39 @@
import * as web3 from "@solana/web3.js"
import * as fs from "fs"
import dotenv from "dotenv"
dotenv.config()
import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { writeFileSync } from "fs";
import dotenv from "dotenv";
dotenv.config();

export async function initializeKeypair(
connection: web3.Connection
): Promise<web3.Keypair> {
connection: Connection
): Promise<Keypair> {
if (!process.env.PRIVATE_KEY) {
console.log("Creating .env file")
const signer = web3.Keypair.generate()
fs.writeFileSync(".env", `PRIVATE_KEY=[${signer.secretKey.toString()}]`)
await airdropSolIfNeeded(signer, connection)
console.log("Creating .env file");
const signer = Keypair.generate();
writeFileSync(".env", `PRIVATE_KEY=[${signer.secretKey.toString()}]`);
await airdropSolIfNeeded(signer, connection);

return signer
return signer;
}

const secret = JSON.parse(process.env.PRIVATE_KEY ?? "") as number[]
const secretKey = Uint8Array.from(secret)
const keypairFromSecretKey = web3.Keypair.fromSecretKey(secretKey)
await airdropSolIfNeeded(keypairFromSecretKey, connection)
return keypairFromSecretKey
const secret = JSON.parse(process.env.PRIVATE_KEY ?? "") as number[];
const secretKey = Uint8Array.from(secret);
const keypairFromSecretKey = Keypair.fromSecretKey(secretKey);
await airdropSolIfNeeded(keypairFromSecretKey, connection);
return keypairFromSecretKey;
}

async function airdropSolIfNeeded(
signer: web3.Keypair,
connection: web3.Connection
) {
const balance = await connection.getBalance(signer.publicKey)
console.log("Current balance is", balance / web3.LAMPORTS_PER_SOL)
async function airdropSolIfNeeded(signer: Keypair, connection: Connection) {
const balance = await connection.getBalance(signer.publicKey);
console.log("Current balance is", balance / LAMPORTS_PER_SOL);

if (balance < web3.LAMPORTS_PER_SOL) {
console.log("Airdropping 1 SOL...")
if (balance < LAMPORTS_PER_SOL) {
console.log("Airdropping 1 SOL...");
const airdropSignature = await connection.requestAirdrop(
signer.publicKey,
web3.LAMPORTS_PER_SOL
)
LAMPORTS_PER_SOL
);

const latestBlockHash = await connection.getLatestBlockhash()
const latestBlockHash = await connection.getLatestBlockhash();

await connection.confirmTransaction(
{
Expand All @@ -45,9 +42,9 @@ async function airdropSolIfNeeded(
signature: airdropSignature,
},
"finalized"
)
);

const newBalance = await connection.getBalance(signer.publicKey)
console.log("New balance is", newBalance / web3.LAMPORTS_PER_SOL)
const newBalance = await connection.getBalance(signer.publicKey);
console.log("New balance is", newBalance / LAMPORTS_PER_SOL);
}
}
6 changes: 3 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */

/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
Expand All @@ -24,9 +24,9 @@
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */

/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"module": "nodenext", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
Expand Down
Loading