Forráskód Böngészése

improve examples (#36)

* refactor: renamed dir

* refactor: renamed file

* docs: comment

* chore: add web3js v2 for comparison

* feat: comparison

* docs: add readme

* docs: updates

* docs: updates
Nick Frostbutter 9 hónapja
szülő
commit
e04ce7344a

+ 19 - 7
README.md

@@ -200,10 +200,9 @@ const transaction = createTransaction({
 If your transaction already has the latest blockhash lifetime set via `createTransaction`:
 
 ```typescript
-import {
-  signTransactionMessageWithSigners,
-  setTransactionMessageLifetimeUsingBlockhash,
-} from "gill";
+import { createTransaction, signTransactionMessageWithSigners } from "gill";
+
+const transaction = createTransaction(...);
 
 const signedTransaction = await signTransactionMessageWithSigners(transaction);
 ```
@@ -213,14 +212,19 @@ must set the latest blockhash lifetime before (or during) the signing operation:
 
 ```typescript
 import {
+  createTransaction,
+  createSolanaClient,
   signTransactionMessageWithSigners,
   setTransactionMessageLifetimeUsingBlockhash,
 } from "gill";
 
+const { rpc } = createSolanaClient(...);
+const transaction = createTransaction(...);
+
 const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
 
 const signedTransaction = await signTransactionMessageWithSigners(
-  setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+  setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, transaction),
 );
 ```
 
@@ -230,9 +234,9 @@ To send and confirm a transaction to the blockchain, you can use the `sendAndCon
 function initialized from `createSolanaClient`.
 
 ```typescript
-import { createSolanaClient, createTransaction, signTransactionMessageWithSigners } from "gill";
+import { ... } from "gill";
 
-const { rpc, rpcSubscriptions, sendAndConfirmTransaction } = createSolanaClient({
+const { sendAndConfirmTransaction } = createSolanaClient({
   urlOrMoniker: "mainnet",
 });
 
@@ -241,6 +245,8 @@ const transaction = createTransaction(...);
 const signedTransaction = await signTransactionMessageWithSigners(transaction);
 const signature: string = getSignatureFromTransaction(signedTransaction);
 
+console.log(getExplorerLink({ transaction: signature }));
+
 // default commitment level of `confirmed`
 await sendAndConfirmTransaction(signedTransaction)
 ```
@@ -360,6 +366,9 @@ const rent: bigint = getMinimumBalanceForRentExemption();
 
 // same as
 // getMinimumBalanceForRentExemption(0);
+
+// same as, but this requires a network call
+// const rent = await rpc.getMinimumBalanceForRentExemption(0n).send();
 ```
 
 ```typescript
@@ -367,6 +376,9 @@ import { getMinimumBalanceForRentExemption } from "gill";
 
 const rent: bigint = getMinimumBalanceForRentExemption(50 /* 50 bytes */);
 // Expected value: 1_238_880n
+
+// same as, but this requires a network call
+// const rent = await rpc.getMinimumBalanceForRentExemption(50n).send();
 ```
 
 > Note: At this time, the minimum rent amount for an account is calculated based on static values in

+ 0 - 0
examples/esrun/.gitignore → examples/get-started/.gitignore


+ 11 - 0
examples/get-started/.prettierrc

@@ -0,0 +1,11 @@
+{
+  "tabWidth": 2,
+  "useTabs": false,
+  "singleQuote": false,
+  "bracketSpacing": true,
+  "semi": true,
+  "trailingComma": "all",
+  "proseWrap": "always",
+  "arrowParens": "always",
+  "printWidth": 80
+}

+ 104 - 0
examples/get-started/README.md

@@ -0,0 +1,104 @@
+# gill examples
+
+Gill is aimed at abstracting away many of the complexities and boilerplate
+required to perform common interactions with the Solana blockchain, while still
+offering the low level "escape hatches" when developers need (or want)
+fine-grain control.
+
+Take a look through these examples to see how gill works and even
+[how it compares](#comparison-of-gill-vs-web3js-v2) to using the vanilla web3js
+v2 library.
+
+## Tech stack used
+
+- TypeScript and NodeJS
+- Package manger: `pnpm`
+- Running the scripts: `esrun`
+
+## Setup locally
+
+1. Clone this repo to your local system
+2. Install the packages via `pnpm  install`
+3. Change into this directory: `cd examples/get-started`
+
+### Running the included scripts with esrun
+
+Once setup locally, you will be able to run the scripts included within this
+repo using `esrun`:
+
+```shell
+npx esrun ./src/<script>
+pnpx esrun ./src/<script>
+```
+
+> From the [esrun](https://www.npmjs.com/package/esrun) readme:
+>
+> esrun is a "work out of the box" library to execute Typescript (as well as
+> modern Javascript with decorators and stuff) without having to use a bundler.
+> This is useful for quick demonstrations or when launching your tests written
+> in Typescript.
+
+## Recommended flow to explore this repo
+
+After getting setup locally, we recommend exploring the code of the following
+files (in order):
+
+- [`intro.ts`](./src/intro.ts)
+- [`tokens.ts`](./src/tokens.ts)
+
+#### `intro.ts`
+
+A brief introduction to the `gill` library. Demonstrating and explaining the
+commonly used tasks involved to interact with the Solana blockchain, including:
+
+- load a keypair signer from the local filesystem
+- create an rpc connection to the blockchain
+- creating basic instructions (like the memo instruction)
+- getting the latest blockhash
+- building a complete transaction
+- signing the transaction with the loaded local keypair signer
+- getting the signature of a transaction (even before it is sent)
+- logging Solana Explorer links
+- sending and confirming a transaction
+
+These are all the most basic tasks required for any application sending
+transaction to the Solana blockchain.
+
+#### `tokens.ts`
+
+Demonstrating how to use gill's "transaction builders" to create a brand new
+Solana token (with onchain metadata) and then mint tokens to another user's
+wallet:
+
+- load a keypair signer from the local filesystem
+- create an rpc connection to the blockchain
+- getting the latest blockhash
+- build an optimized transaction to create a token
+- sign, send, and confirm that "create token" transaction
+- build an optimized transaction to mint
+- sign, send, and confirm that "mint tokens" transaction
+
+#### `2.complexTransaction.ts`
+
+An introduction to more complex transactions using Solana web3.js. Demonstrates
+how to build a more complex transaction, with multiple instructions.
+
+## Comparison of gill vs web3js v2
+
+You can find comparison scripts that demonstrates some of the differences
+between [gill](https://github.com/solana-foundation/gill) and
+[web3.js v2](https://github.com/anza-xyz/solana-web3.js).
+
+Both scripts accomplish the same task: send an optimized transaction to the
+Solana blockchain.
+
+- Using gill - [`basic.ts`](./src/basic.ts)
+- Using web3js v2 - [`basic-compare.ts`](./src/basic-compare.ts)
+
+Both will load a keypair file from your local filesystem (the one used by the
+Solana CLI).
+
+Both are written with honest intentions, best practices, and attempt to be as
+concise as possible in accomplishing the same task.
+
+You decide which you prefer :)

+ 8 - 3
examples/esrun/package.json → examples/get-started/package.json

@@ -1,5 +1,5 @@
 {
-  "name": "gill-examples-esrun",
+  "name": "gill-examples-basic",
   "private": true,
   "version": "0.1.0",
   "type": "module",
@@ -10,8 +10,13 @@
   "author": "",
   "license": "MIT",
   "dependencies": {
-    "gill": "workspace:*",
-    "dotenv": "^16.4.5"
+    "@solana-program/compute-budget": "^0.6.1",
+    "@solana-program/memo": "^0.6.1",
+    "@solana/web3.js": "2.0.0",
+    "fastestsmallesttextencoderdecoder": "^1.0.22",
+    "dotenv": "^16.4.5",
+    "ws": "^8.18.0",
+    "gill": "workspace:*"
   },
   "devDependencies": {
     "@types/node": "^22.9.0",

+ 81 - 0
examples/get-started/src/basic-compare.ts

@@ -0,0 +1,81 @@
+import { readFileSync } from "node:fs";
+import { homedir } from "node:os";
+import { resolve } from "node:path";
+import {
+  pipe,
+  devnet,
+  createSolanaRpc,
+  createKeyPairFromBytes,
+  createSignerFromKeyPair,
+  createTransactionMessage,
+  getSignatureFromTransaction,
+  createSolanaRpcSubscriptions,
+  sendAndConfirmTransactionFactory,
+  signTransactionMessageWithSigners,
+  setTransactionMessageFeePayerSigner,
+  appendTransactionMessageInstructions,
+  setTransactionMessageLifetimeUsingBlockhash,
+} from "@solana/web3.js";
+import { getAddMemoInstruction } from "@solana-program/memo";
+import {
+  getSetComputeUnitLimitInstruction,
+  getSetComputeUnitPriceInstruction,
+} from "@solana-program/compute-budget";
+
+const rpc = createSolanaRpc(devnet("https://api.devnet.solana.com"));
+
+const rpcSubscriptions = createSolanaRpcSubscriptions(
+  devnet("wss://api.devnet.solana.com"),
+);
+
+const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
+  rpc,
+  rpcSubscriptions,
+});
+
+const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
+
+const keypairFilePath = "~/.config/solana/id.json";
+
+const resolvedKeypairPath = resolve(keypairFilePath.replace("~", homedir()));
+
+const keypair = await createKeyPairFromBytes(
+  Uint8Array.from(JSON.parse(readFileSync(resolvedKeypairPath, "utf8"))),
+);
+
+const signer = await createSignerFromKeyPair(keypair);
+
+const tx = pipe(
+  createTransactionMessage({ version: "legacy" }),
+  (tx) => setTransactionMessageFeePayerSigner(signer, tx),
+  (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+  (tx) =>
+    appendTransactionMessageInstructions(
+      [
+        getAddMemoInstruction({
+          memo: "gm world!",
+        }),
+        getSetComputeUnitLimitInstruction({ units: 5000 }),
+        getSetComputeUnitPriceInstruction({ microLamports: 1000 }),
+      ],
+      tx,
+    ),
+);
+
+const signedTransaction = await signTransactionMessageWithSigners(tx);
+
+try {
+  console.log(
+    "Sending transaction:",
+    `https://explorer.solana.com/tx/${getSignatureFromTransaction(signedTransaction)}?cluster=devnet`,
+  );
+
+  await sendAndConfirmTransaction(signedTransaction, {
+    commitment: "confirmed",
+  });
+
+  console.log("Transaction confirmed!");
+} catch (err) {
+  console.error("Unable to send and confirm the transaction");
+  console.error(err);
+}

+ 49 - 0
examples/get-started/src/basic.ts

@@ -0,0 +1,49 @@
+import {
+  getExplorerLink,
+  createTransaction,
+  createSolanaClient,
+  getSignatureFromTransaction,
+  signTransactionMessageWithSigners,
+} from "gill";
+import { loadKeypairSignerFromFile } from "gill/node";
+import { getAddMemoInstruction } from "gill/programs";
+
+const { rpc, sendAndConfirmTransaction } = createSolanaClient({
+  urlOrMoniker: "devnet",
+});
+
+const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
+
+const signer = await loadKeypairSignerFromFile();
+
+const tx = createTransaction({
+  version: "legacy",
+  feePayer: signer,
+  instructions: [
+    getAddMemoInstruction({
+      memo: "gm world!",
+    }),
+  ],
+  latestBlockhash,
+  computeUnitLimit: 5000,
+  computeUnitPrice: 1000,
+});
+
+const signedTransaction = await signTransactionMessageWithSigners(tx);
+
+try {
+  console.log(
+    "Sending transaction:",
+    getExplorerLink({
+      cluster: "devnet",
+      transaction: getSignatureFromTransaction(signedTransaction),
+    }),
+  );
+
+  await sendAndConfirmTransaction(signedTransaction);
+
+  console.log("Transaction confirmed!");
+} catch (err) {
+  console.error("Unable to send and confirm the transaction");
+  console.error(err);
+}

+ 1 - 0
examples/esrun/src/index.ts → examples/get-started/src/intro.ts

@@ -89,6 +89,7 @@ try {
    */
   await sendAndConfirmTransaction(signedTransaction);
 
+  // you can also manually define additional settings for sending your transaction
   // await sendAndConfirmTransaction(signedTransaction, {
   //   commitment: "confirmed",
   //   skipPreflight: true,

+ 30 - 4
examples/esrun/src/tokens.ts → examples/get-started/src/tokens.ts

@@ -15,8 +15,22 @@ import {
 } from "gill/programs/token";
 
 /** Turn on debug mode */
+global.__GILL_DEBUG__ = true;
+
+/** Set the debug mode log level (default: `info`) */
 global.__GILL_DEBUG_LEVEL__ = "debug";
 
+/**
+ * With debug mode enabled and the log level of `debug`:
+ *
+ * `sendAndConfirmTransaction` will now auto log the following:
+ * - explorer link to view the transaction
+ * - serialized base64 transaction, to inspect on the Solana Explorer's Inspector
+ *   https://explorer.solana.com/tx/inspector
+ *
+ * This can greatly assist troubleshooting efforts
+ */
+
 /**
  * Load a keypair signer from the local filesystem
  *
@@ -60,9 +74,9 @@ console.log("latestBlockhash:", latestBlockhash);
  * - this will use the original SPL token by default (`TOKEN_PROGRAM_ADDRESS`)
  */
 const createTokenTx = await buildCreateTokenTransaction({
-  mint,
-  latestBlockhash,
   feePayer: signer,
+  latestBlockhash,
+  mint,
   // mintAuthority, // default=same as the `feePayer`
   metadata: {
     isMutable: true, // if the `updateAuthority` can change this metadata in the future
@@ -70,8 +84,13 @@ const createTokenTx = await buildCreateTokenTransaction({
     symbol: "OPOS",
     uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/Climate/metadata.json",
   },
+  // updateAuthority, // default=same as the `feePayer`
   decimals: 2, // default=9,
-  tokenProgram, //default=TOKEN_PROGRAM_ADDRESS
+  tokenProgram, // default=TOKEN_PROGRAM_ADDRESS, token22 also supported
+  // default cu limit set to be optimized, but can be overriden here
+  // computeUnitLimit?: number,
+  // obtain from your favorite priority fee api
+  // computeUnitPrice?: number, // no default set
 });
 
 /**
@@ -123,7 +142,12 @@ const mintTokensTx = await buildMintTokensTransaction({
   // if decimals=2 => this will mint 10.00 tokens
   // if decimals=4 => this will mint 0.100 tokens
   destination,
+  // use the correct token program for the `mint`
   tokenProgram, // default=TOKEN_PROGRAM_ADDRESS
+  // default cu limit set to be optimized, but can be overriden here
+  // computeUnitLimit?: number,
+  // obtain from your favorite priority fee api
+  // computeUnitPrice?: number, // no default set
 });
 
 console.log("Transaction to mint tokens:");
@@ -135,7 +159,9 @@ console.log(mintTokensTx);
 signedTransaction = await signTransactionMessageWithSigners(mintTokensTx);
 signature = getSignatureFromTransaction(signedTransaction);
 
-console.log("\nExplorer Link (for minting the tokens to the destination wallet):");
+console.log(
+  "\nExplorer Link (for minting the tokens to the destination wallet):",
+);
 console.log(
   getExplorerLink({
     cluster,

+ 1 - 1
examples/esrun/tsconfig.json → examples/get-started/tsconfig.json

@@ -6,7 +6,7 @@
     "noEmit": true,
     "target": "ESNext"
   },
-  "display": "gill-examples-esrun",
+  "display": "gill-examples-basic",
   "extends": "../../packages/tsconfig/base.json",
   "include": ["src"]
 }

+ 19 - 7
packages/gill/README.md

@@ -200,10 +200,9 @@ const transaction = createTransaction({
 If your transaction already has the latest blockhash lifetime set via `createTransaction`:
 
 ```typescript
-import {
-  signTransactionMessageWithSigners,
-  setTransactionMessageLifetimeUsingBlockhash,
-} from "gill";
+import { createTransaction, signTransactionMessageWithSigners } from "gill";
+
+const transaction = createTransaction(...);
 
 const signedTransaction = await signTransactionMessageWithSigners(transaction);
 ```
@@ -213,14 +212,19 @@ must set the latest blockhash lifetime before (or during) the signing operation:
 
 ```typescript
 import {
+  createTransaction,
+  createSolanaClient,
   signTransactionMessageWithSigners,
   setTransactionMessageLifetimeUsingBlockhash,
 } from "gill";
 
+const { rpc } = createSolanaClient(...);
+const transaction = createTransaction(...);
+
 const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
 
 const signedTransaction = await signTransactionMessageWithSigners(
-  setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
+  setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, transaction),
 );
 ```
 
@@ -230,9 +234,9 @@ To send and confirm a transaction to the blockchain, you can use the `sendAndCon
 function initialized from `createSolanaClient`.
 
 ```typescript
-import { createSolanaClient, createTransaction, signTransactionMessageWithSigners } from "gill";
+import { ... } from "gill";
 
-const { rpc, rpcSubscriptions, sendAndConfirmTransaction } = createSolanaClient({
+const { sendAndConfirmTransaction } = createSolanaClient({
   urlOrMoniker: "mainnet",
 });
 
@@ -241,6 +245,8 @@ const transaction = createTransaction(...);
 const signedTransaction = await signTransactionMessageWithSigners(transaction);
 const signature: string = getSignatureFromTransaction(signedTransaction);
 
+console.log(getExplorerLink({ transaction: signature }));
+
 // default commitment level of `confirmed`
 await sendAndConfirmTransaction(signedTransaction)
 ```
@@ -360,6 +366,9 @@ const rent: bigint = getMinimumBalanceForRentExemption();
 
 // same as
 // getMinimumBalanceForRentExemption(0);
+
+// same as, but this requires a network call
+// const rent = await rpc.getMinimumBalanceForRentExemption(0n).send();
 ```
 
 ```typescript
@@ -367,6 +376,9 @@ import { getMinimumBalanceForRentExemption } from "gill";
 
 const rent: bigint = getMinimumBalanceForRentExemption(50 /* 50 bytes */);
 // Expected value: 1_238_880n
+
+// same as, but this requires a network call
+// const rent = await rpc.getMinimumBalanceForRentExemption(50n).send();
 ```
 
 > Note: At this time, the minimum rent amount for an account is calculated based on static values in

+ 16 - 1
pnpm-lock.yaml

@@ -108,14 +108,29 @@ importers:
         specifier: ^2.3.1
         version: 2.3.1
 
-  examples/esrun:
+  examples/get-started:
     dependencies:
+      '@solana-program/compute-budget':
+        specifier: ^0.6.1
+        version: 0.6.1(@solana/web3.js@2.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
+      '@solana-program/memo':
+        specifier: ^0.6.1
+        version: 0.6.1(@solana/web3.js@2.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)))
+      '@solana/web3.js':
+        specifier: 2.0.0
+        version: 2.0.0(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.6.3)(ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))
       dotenv:
         specifier: ^16.4.5
         version: 16.4.7
+      fastestsmallesttextencoderdecoder:
+        specifier: ^1.0.22
+        version: 1.0.22
       gill:
         specifier: workspace:*
         version: link:../../packages/gill
+      ws:
+        specifier: ^8.18.0
+        version: 8.18.0(bufferutil@4.0.8)(utf-8-validate@5.0.10)
     devDependencies:
       '@types/node':
         specifier: ^22.9.0