diff --git a/.github/workflows/code:test.yml b/.github/workflows/code:test.yml new file mode 100644 index 000000000..d641fb587 --- /dev/null +++ b/.github/workflows/code:test.yml @@ -0,0 +1,84 @@ +name: Code Snippets testing + +on: + pull_request: + branches: [main] + paths: + - "code/**/*.test.ts" # Only trigger on test file changes + schedule: + - cron: "0 0 * * *" # Run daily at midnight UTC + +jobs: + snippets-tests: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20] + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get changed test files + id: changed-files + uses: tj-actions/changed-files@v39 + with: + files: | + code/**/*.test.ts + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" + + - name: Install dependencies + run: pnpm install + + - name: Change Directory + run: cd code + + - name: Run tests on changed files + if: github.event_name == 'pull_request' + run: | + CHANGED_FILES="${{ steps.changed-files.outputs.all_changed_files }}" + + if [ -z "$CHANGED_FILES" ]; then + echo "No test files were changed" + exit 0 + fi + + # Create array for test files + declare -a test_files=() + + # Collect only .test.ts files + for file in $CHANGED_FILES; do + if [[ $file == *.test.ts ]]; then + test_files+=("$file") + echo "Added test file: $file" + fi + done + + # If we found test files, run them together + if [ ${#test_files[@]} -gt 0 ]; then + echo "Running tests for the following files:" + printf '%s\n' "${test_files[@]}" + + # Run all test files in a single command + node --import tsx --test "${test_files[@]}" || exit 1 + else + echo "No test files to run" + fi + + - name: Run all tests + if: github.event_name == 'schedule' + run: | + echo "Running all tests in code directory" + pnpm turbo test diff --git a/.github/workflows/contentlayer.yml b/.github/workflows/contentlayer.yml index 6371b2ead..bed29849b 100644 --- a/.github/workflows/contentlayer.yml +++ b/.github/workflows/contentlayer.yml @@ -16,8 +16,6 @@ jobs: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 906dd536d..69569cbf7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,8 +18,6 @@ jobs: - uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Install Vercel CLI and pnpm run: npm install --global vercel@latest - name: Pull Vercel Environment Information diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index b9c8ad1dc..8cd048900 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -16,8 +16,6 @@ jobs: uses: actions/checkout@v4 - name: Install pnpm uses: pnpm/action-setup@v4 - with: - version: 9 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: diff --git a/.gitignore b/.gitignore index 374bbf4f0..08446528f 100644 --- a/.gitignore +++ b/.gitignore @@ -106,6 +106,8 @@ package-lock.json # translations are stored in the `i18n` via crowdin i18n +# turborepo +.turbo # code-import .turbo diff --git a/.prettierrc b/.prettierrc index d2f009f9d..a7238cef1 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,10 +1,39 @@ { + "printWidth": 80, "tabWidth": 2, "useTabs": false, + "semi": true, "singleQuote": false, - "printWidth": 80, - "trailingComma": "all", - "arrowParens": "avoid", - "endOfLine": "auto", - "proseWrap": "always" -} + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false, + "proseWrap": "preserve", + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto", + "singleAttributePerLine": false, + "overrides": [ + { + "files": ["*.md", "*.mdx"], + "options": { + "parser": "mdx", + "printWidth": 80, + "proseWrap": "preserve", + "singleQuote": false, + "jsxSingleQuote": false, + "bracketSameLine": false, + "singleAttributePerLine": false + } + }, + { + "files": ["*.{ts,tsx,js,jsx}"], + "options": { + "printWidth": 80, + "semi": true, + "singleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false + } + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01fe405a2..eba18fe58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,7 @@ transparent as possible, whether it's: - content translations are supported via Crowdin - code blocks must use code-import for file snippets (via filesystem) - code file should be [tests](https://nodejs.org/api/test.html) and should add - code ranges instead of whole test file + code ranges instead of whole file ## Style guidelines @@ -285,8 +285,8 @@ source files. To use code-import, follow these steps: -Ensure your code file is a test file located in the appropriate directory within -the repo. Use the following syntax to import code snippets: +Ensure your code file is located in the appropriate directory within the repo. +Use the following syntax to import code snippets: ```javascript file="/path/to/your/file.ts#L1-L10,#L15-L20" @@ -1012,3 +1012,71 @@ pnpm dev ``` > Note: The developer content API normally runs locally on port `3001` + +4. Installing other packages, since the repo is a pnpm workspace. + +`pnpm i -D remark-mdx -w solana-developer-content-api` `remarkmdx` + +## Testing Code Snippets + +The repository uses Node's native test runner for testing code examples. This +ensures all code snippets are correct and working as expected. + +### Test Structure + +There are two supported ways to structure your tests: + +1. **Adjacent test files** - For simpler modules with a single code file: + + ``` + pda/ + ├── index.ts + └── index.test.ts + ``` + +2. **Tests directory** - For modules with multiple code files or complex test + cases: + ``` + pda/ + ├── index.ts + ├── utils.ts + └── tests/ + ├── index.test.ts + └── utils.test.ts + ``` + +Choose the structure that best fits your needs: + +- Use adjacent test files (`index.test.ts`) when you have a simple 1:1 + relationship between code and test files +- Use a `tests` directory when you have multiple code files that need testing or + need multiple test files for a single code file + +### Running Tests + +You can run tests in two ways: + +1. Test the entire codebase: + + ```shell + pnpm install + pnpm turbo test + ``` + +2. Test specific code imports: + ```shell + pnpm install + cd code + pnpm turbo test + ``` + +### Writing Tests + +We use [Behavior-Driven Development (BDD)](https://cucumber.io/docs/bdd/) style +testing, which uses `describe` and `it` blocks to create readable test +descriptions. When tests run, they read like natural sentences. + +Example: +[generate-mnemonic.test.ts](github.com/solana-foundation/developer-content/tree/main/code/content/cookbook/wallets/tests/generate-mnemonic.test.ts) + +All tests should handle both success and failure (error) cases appropriately. diff --git a/code/cookbook/wallets/check-public-key.ts b/code/content/web3jsv1/cookbook/wallets/check-publickey.ts similarity index 57% rename from code/cookbook/wallets/check-public-key.ts rename to code/content/web3jsv1/cookbook/wallets/check-publickey.ts index f5b4c8392..1ab7fcf98 100644 --- a/code/cookbook/wallets/check-public-key.ts +++ b/code/content/web3jsv1/cookbook/wallets/check-publickey.ts @@ -1,4 +1,4 @@ -import { PublicKey } from "@solana/web3.js"; +import { PublicKey, Keypair } from "@solana/web3.js"; // Note that Keypair.generate() will always give a public key that is valid for users @@ -9,11 +9,20 @@ console.log(PublicKey.isOnCurve(key.toBytes())); // Valid public key const offCurveAddress = new PublicKey( - "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e", + "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e" ); // Not on the ed25519 curve, therefore not suitable for users console.log(PublicKey.isOnCurve(offCurveAddress.toBytes())); -// Not a valid public key -const errorPubkey = new PublicKey("testPubkey"); +let errorPubkey; +try { + // Not a valid public key + errorPubkey = new PublicKey("testPubkey"); +} catch (err) { + // Error will be caught here +} + +const onCurve = PublicKey.isOnCurve(key.toBytes()); + +export { key, onCurve, offCurveAddress, errorPubkey }; diff --git a/code/content/web3jsv1/cookbook/wallets/create-keypair.ts b/code/content/web3jsv1/cookbook/wallets/create-keypair.ts new file mode 100644 index 000000000..d08330522 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/create-keypair.ts @@ -0,0 +1,5 @@ +import { Keypair } from "@solana/web3.js"; + +const keypair = Keypair.generate(); + +export { keypair }; diff --git a/code/content/web3jsv1/cookbook/wallets/generate-mnemonic.ts b/code/content/web3jsv1/cookbook/wallets/generate-mnemonic.ts new file mode 100644 index 000000000..03d398d7b --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/generate-mnemonic.ts @@ -0,0 +1,5 @@ +import * as bip39 from "bip39"; + +const mnemonic = bip39.generateMnemonic(); + +export { mnemonic }; diff --git a/code/content/web3jsv1/cookbook/wallets/generate-vanity-address.sh b/code/content/web3jsv1/cookbook/wallets/generate-vanity-address.sh new file mode 100644 index 000000000..8dfe31ecf --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/generate-vanity-address.sh @@ -0,0 +1 @@ +solana-keygen grind --starts-with e1v1s:1 \ No newline at end of file diff --git a/code/content/web3jsv1/cookbook/wallets/restore-bip39-mnemonic.ts b/code/content/web3jsv1/cookbook/wallets/restore-bip39-mnemonic.ts new file mode 100644 index 000000000..38a2f6af3 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/restore-bip39-mnemonic.ts @@ -0,0 +1,15 @@ +import { Keypair } from "@solana/web3.js"; +import * as bip39 from "bip39"; + +const mnemonic = + "pill tomorrow foster begin walnut borrow virtual kick shift mutual shoe scatter"; + +// arguments: (mnemonic, password) +const seed = bip39.mnemonicToSeedSync(mnemonic, ""); +const keypair = Keypair.fromSeed(seed.slice(0, 32)); + +console.log(`${keypair.publicKey.toBase58()}`); + +// output: 5ZWj7a1f8tWkjBESHKgrLmXshuXxqeY9SYcfbshpAqPG + +export { keypair }; diff --git a/code/content/web3jsv1/cookbook/wallets/restore-bip44-mnemonic.ts b/code/content/web3jsv1/cookbook/wallets/restore-bip44-mnemonic.ts new file mode 100644 index 000000000..2e04f446c --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/restore-bip44-mnemonic.ts @@ -0,0 +1,29 @@ +import { Keypair } from "@solana/web3.js"; +import { HDKey } from "micro-key-producer/slip10.js"; +import * as bip39 from "bip39"; + +type Wallet = { + path: string; + keypair: Keypair; + publicKey: string; +}; + +const mnemonic = + "neither lonely flavor argue grass remind eye tag avocado spot unusual intact"; + +const seed = bip39.mnemonicToSeedSync(mnemonic, ""); +const hd = HDKey.fromMasterSeed(seed.toString("hex")); + +const wallets: Wallet[] = []; + +for (let i = 0; i < 10; i++) { + const path = `m/44'/501'/${i}'/0'`; + const keypair = Keypair.fromSeed(hd.derive(path).privateKey); + wallets.push({ + path, + keypair, + publicKey: keypair.publicKey.toBase58(), + }); +} + +export { mnemonic, wallets }; diff --git a/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bs58.ts b/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bs58.ts new file mode 100644 index 000000000..a50a8eae4 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bs58.ts @@ -0,0 +1,10 @@ +import { Keypair } from "@solana/web3.js"; +import bs58 from "bs58"; + +const keypair = Keypair.fromSecretKey( + bs58.decode( + "4UzFMkVbk1q6ApxvDS8inUxg4cMBxCQRVXRx5msqQyktbi1QkJkt574Jda6BjZThSJi54CHfVoLFdVFX8XFn233L" + ) +); + +export { keypair as bs58Keypair }; diff --git a/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bytes.ts b/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bytes.ts new file mode 100644 index 000000000..f11aeb297 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bytes.ts @@ -0,0 +1,12 @@ +import { Keypair } from "@solana/web3.js"; + +const keypair = Keypair.fromSecretKey( + Uint8Array.from([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]) +); + +export { keypair as bytesKeypair }; diff --git a/code/content/web3jsv1/cookbook/wallets/sign-message.ts b/code/content/web3jsv1/cookbook/wallets/sign-message.ts new file mode 100644 index 000000000..20dde0a04 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/sign-message.ts @@ -0,0 +1,27 @@ +import { Keypair } from "@solana/web3.js"; +import * as ed from "@noble/ed25519"; +import { sha512 } from "@noble/hashes/sha512"; +import { utf8ToBytes } from "@noble/hashes/utils"; + +// Enable synchronous methods for noble-ed25519 +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); + +const keypair = Keypair.fromSecretKey( + Uint8Array.from([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]) +); + +const message = "The quick brown fox jumps over the lazy dog"; +const messageBytes = utf8ToBytes(message); + +// Sign using noble-ed25519 +const signature = ed.sign(messageBytes, keypair.secretKey.slice(0, 32)); + +// Verify using noble-ed25519 +const result = ed.verify(signature, messageBytes, keypair.publicKey.toBytes()); + +export { keypair, message, messageBytes, signature, result }; diff --git a/code/content/web3jsv1/cookbook/wallets/tests/check-publickey.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/check-publickey.test.ts new file mode 100644 index 000000000..c373cb0ec --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/check-publickey.test.ts @@ -0,0 +1,121 @@ +import { describe, test } from "node:test"; +import { strict as assert } from "node:assert"; +import { PublicKey } from "@solana/web3.js"; +import { key, onCurve, offCurveAddress } from "../check-publickey"; + +describe("Check PublicKey", async () => { + test("should properly instantiate valid key", () => { + assert.ok(key instanceof PublicKey, "key should be a PublicKey instance"); + assert.equal( + key.toString(), + "5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY", + "key should match the expected value" + ); + }); + + test("should verify key is on ed25519 curve", () => { + assert.equal(onCurve, true, "key should be on the ed25519 curve"); + // Double-check the curve status directly + assert.equal( + PublicKey.isOnCurve(key.toBytes()), + true, + "key should be verified on curve through direct check" + ); + }); + + test("should identify valid address not on curve", () => { + assert.ok( + offCurveAddress instanceof PublicKey, + "offCurveAddress should be a PublicKey instance" + ); + assert.equal( + offCurveAddress.toString(), + "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e", + "offCurveAddress should match the expected value" + ); + assert.equal( + PublicKey.isOnCurve(offCurveAddress.toBytes()), + false, + "offCurveAddress should not be on the curve" + ); + }); + + test("should throw error for invalid public key format", () => { + assert.throws( + () => { + new PublicKey("testPubkey"); + }, + { + name: "Error", + message: /Invalid public key/, + }, + "Should throw an error for invalid public key format" + ); + }); + + test("should throw error for empty string public key", () => { + assert.throws( + () => { + new PublicKey(""); + }, + { + name: "Error", + message: /Invalid public key/, + }, + "Should throw an error for empty string" + ); + }); + + test("should handle conversion between different formats", () => { + const keyString = key.toString(); + const keyBytes = key.toBytes(); + const keyBase58 = key.toBase58(); + + // Test string conversion + assert.equal( + new PublicKey(keyString).toString(), + keyString, + "should maintain equality when converting through string" + ); + + // Test bytes conversion + assert.deepEqual( + new PublicKey(keyBytes).toBytes(), + keyBytes, + "should maintain equality when converting through bytes" + ); + + // Test base58 conversion + assert.equal( + new PublicKey(keyBase58).toBase58(), + keyBase58, + "should maintain equality when converting through base58" + ); + }); + + test("should verify key bytes are correct length", () => { + const keyBytes = key.toBytes(); + assert.equal( + keyBytes.length, + 32, + "public key bytes should be exactly 32 bytes" + ); + }); + + test("should maintain equality for same public key", () => { + const sameKey = new PublicKey(key.toString()); + assert.ok( + key.equals(sameKey), + "same public key should be equal when compared" + ); + }); + + test("should identify different public keys as not equal", () => { + const differentKey = new PublicKey(offCurveAddress.toString()); + assert.equal( + key.equals(differentKey), + false, + "different public keys should not be equal" + ); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/tests/create-keypair.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/create-keypair.test.ts new file mode 100644 index 000000000..32f2cfbf5 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/create-keypair.test.ts @@ -0,0 +1,103 @@ +import { describe, test } from "node:test"; +import assert from "node:assert"; +import { keypair } from "../create-keypair"; +import { Keypair, PublicKey } from "@solana/web3.js"; + +describe("Create Keypair", async () => { + test("should be a valid Keypair instance", () => { + assert.ok( + keypair instanceof Keypair, + "keypair should be instance of Keypair" + ); + }); + + test("should have correct secretKey length", () => { + assert.equal(keypair.secretKey.length, 64, "secretKey should be 64 bytes"); + }); + + test("should have correct publicKey length", () => { + assert.equal( + keypair.publicKey.toBytes().length, + 32, + "publicKey should be 32 bytes" + ); + }); + + test("should have valid public key instance", () => { + assert.ok( + keypair.publicKey instanceof PublicKey, + "publicKey should be instance of PublicKey" + ); + }); + + test("should derive same public key from secret key", () => { + const derivedKeypair = Keypair.fromSecretKey(keypair.secretKey); + assert.ok( + keypair.publicKey.equals(derivedKeypair.publicKey), + "public key should match derived public key" + ); + }); + + test("should generate correct base58 public key string", () => { + const pubkeyStr = keypair.publicKey.toBase58(); + assert.ok( + pubkeyStr.length === 44 || pubkeyStr.length === 43, + "public key string should be 43 or 44 characters" + ); + }); + + test("should have valid secret key format", () => { + assert.ok( + keypair.secretKey instanceof Uint8Array, + "secretKey should be Uint8Array" + ); + }); + + test("should have matching public key in last 32 bytes of secret key", () => { + const pubkeyFromSecret = keypair.secretKey.slice(32); + assert.deepEqual( + pubkeyFromSecret, + keypair.publicKey.toBytes(), + "last 32 bytes of secret key should match public key" + ); + }); + + test("should generate unique keypairs", () => { + const anotherKeypair = Keypair.generate(); + assert.ok( + !keypair.publicKey.equals(anotherKeypair.publicKey), + "different keypairs should have different public keys" + ); + assert.ok( + !Buffer.from(keypair.secretKey).equals( + Buffer.from(anotherKeypair.secretKey) + ), + "different keypairs should have different secret keys" + ); + }); + + test("should maintain consistency when serializing and deserializing", () => { + const secretKeyBytes = keypair.secretKey; + const restoredKeypair = Keypair.fromSecretKey(secretKeyBytes); + + assert.ok( + keypair.publicKey.equals(restoredKeypair.publicKey), + "restored keypair should have same public key" + ); + assert.deepEqual( + keypair.secretKey, + restoredKeypair.secretKey, + "restored keypair should have same secret key" + ); + }); + + test("should correctly handle base58 encoding/decoding of public key", () => { + const base58Pubkey = keypair.publicKey.toBase58(); + const decodedPubkey = new PublicKey(base58Pubkey); + + assert.ok( + keypair.publicKey.equals(decodedPubkey), + "public key should maintain equality after base58 encoding/decoding" + ); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/tests/generate-mnemonic.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/generate-mnemonic.test.ts new file mode 100644 index 000000000..15b8529b5 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/generate-mnemonic.test.ts @@ -0,0 +1,49 @@ +import { strict as assert } from "node:assert"; +import { describe, it } from "node:test"; +import * as bip39 from "bip39"; +import { mnemonic } from "../generate-mnemonic"; + +describe("Mnemonic Generation", async () => { + it("should generate a valid mnemonic phrase", () => { + assert.ok( + bip39.validateMnemonic(mnemonic), + "Generated mnemonic should be valid" + ); + }); + + it("should generate a 12-word mnemonic by default", () => { + const words = mnemonic.split(" "); + assert.equal(words.length, 12, "Mnemonic should contain exactly 12 words"); + }); + + it("should use only valid BIP39 words", () => { + const wordList = bip39.wordlists.english; + const mnemonicWords = mnemonic.split(" "); + + mnemonicWords.forEach((word) => { + assert.ok( + wordList.includes(word), + `Word "${word}" should be in the BIP39 wordlist` + ); + }); + }); + + it("should generate unique mnemonics on each call", () => { + const anotherMnemonic = bip39.generateMnemonic(); + assert.notEqual( + mnemonic, + anotherMnemonic, + "Generated mnemonics should be unique" + ); + }); + + it("should generate mnemonic with correct entropy", () => { + // BIP39 mnemonics are generated from 128 bits of entropy for 12 words + const entropy = bip39.mnemonicToEntropy(mnemonic); + assert.equal( + entropy.length, + 32, // 128 bits = 32 hex characters + "Mnemonic should be generated from 128 bits of entropy" + ); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/tests/restore-from-mnemonic.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/restore-from-mnemonic.test.ts new file mode 100644 index 000000000..cb6716a0e --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/restore-from-mnemonic.test.ts @@ -0,0 +1,120 @@ +import { strict as assert } from "node:assert"; +import { describe, test } from "node:test"; +import { mnemonic, wallets } from "../restore-bip44-mnemonic"; +import { keypair } from "../restore-bip39-mnemonic"; +import * as bip39 from "bip39"; +import { Keypair } from "@solana/web3.js"; + +describe("Restore Keypair from Mnemonic words", async () => { + test("should generate correct deterministic keypair from bip 39", () => { + // Known correct public key for the given mnemonic + const expectedPublicKey = "5ZWj7a1f8tWkjBESHKgrLmXshuXxqeY9SYcfbshpAqPG"; + const mnemonic = + "pill tomorrow foster begin walnut borrow virtual kick shift mutual shoe scatter"; + + // Test generated public key matches expected + assert.equal( + keypair.publicKey.toBase58(), + expectedPublicKey, + "Generated public key should match expected value" + ); + + // Verify mnemonic is valid + assert.ok( + bip39.validateMnemonic(mnemonic), + "Mnemonic should be valid BIP39 phrase" + ); + + // Verify keypair secret key properties + assert.equal(keypair.secretKey.length, 64, "Secret key should be 64 bytes"); + + // Regenerate keypair to verify deterministic generation + const seedVerify = bip39.mnemonicToSeedSync(mnemonic, ""); + const keypairVerify = Keypair.fromSeed( + new Uint8Array(seedVerify).slice(0, 32) + ); + + assert.equal( + keypairVerify.publicKey.toBase58(), + keypair.publicKey.toBase58(), + "Keypair generation should be deterministic" + ); + + // Verify secret key is Uint8Array + assert.ok( + keypair.secretKey instanceof Uint8Array, + "Secret key should be Uint8Array" + ); + + // Verify first 32 bytes of secret key match seed slice + const seedSlice = new Uint8Array( + bip39.mnemonicToSeedSync(mnemonic, "") + ).slice(0, 32); + assert.deepEqual( + keypair.secretKey.slice(0, 32), + seedSlice, + "First 32 bytes of secret key should match seed slice" + ); + }); + + test("should generate correct set of deterministic wallets from bip 44", () => { + // Known correct public keys for the first three wallets + const expectedPublicKeys = [ + "5vftMkHL72JaJG6ExQfGAsT2uGVHpRR7oTNUPMs68Y2N", + "GcXbfQ5yY3uxCyBNDPBbR5FjumHf89E7YHXuULfGDBBv", + "7QPgyQwNLqnoSwHEuK8wKy2Y3Ani6EHoZRihTuWkwxbc", + ]; + + // Test the total number of wallets + assert.equal(wallets.length, 10, "Should generate exactly 10 wallets"); + + // Verify mnemonic is valid + assert.ok( + bip39.validateMnemonic(mnemonic), + "Mnemonic should be valid BIP39 phrase" + ); + + // Test the first three wallets match expected public keys + expectedPublicKeys.forEach((expectedKey, index) => { + assert.equal( + wallets[index].publicKey, + expectedKey, + `Wallet ${index} should have correct public key` + ); + }); + + // Test each wallet's properties + wallets.forEach((wallet, index) => { + // Verify derivation path format + assert.equal( + wallet.path, + `m/44'/501'/${index}'/0'`, + `Wallet ${index} should have correct derivation path` + ); + + // Verify keypair secret key length + assert.equal( + wallet.keypair.secretKey.length, + 64, + `Wallet ${index} secret key should be 64 bytes` + ); + + // Verify public key matches keypair + assert.equal( + wallet.keypair.publicKey.toBase58(), + wallet.publicKey, + `Wallet ${index} public key should match keypair` + ); + + // Verify public keys are unique + const duplicates = wallets.filter( + (w) => w.publicKey === wallet.publicKey + ); + assert.equal( + duplicates.length, + 1, + `Wallet ${index} public key should be unique` + ); + }); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/tests/restore-keypair.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/restore-keypair.test.ts new file mode 100644 index 000000000..038b94a4d --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/restore-keypair.test.ts @@ -0,0 +1,68 @@ +import { strict as assert } from "node:assert"; +import { describe, it } from "node:test"; +import { bytesKeypair } from "../restore-keypair-from-bytes"; +import { bs58Keypair } from "../restore-keypair-from-bs58"; +import { PublicKey } from "@solana/web3.js"; + +describe("Restore Keypair", async () => { + it("should correctly generate keypairs from both byte array and base58", () => { + // Expected values + const expectedPublicKey = new PublicKey( + "24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p" + ); + const expectedSecretKeyLength = 64; + + // Test byte array keypair + assert.equal( + bytesKeypair.publicKey.toBase58(), + expectedPublicKey.toBase58(), + "Byte array keypair should have correct public key" + ); + + assert.equal( + bytesKeypair.secretKey.length, + expectedSecretKeyLength, + "Byte array keypair secret key should be 64 bytes" + ); + + // Verify byte array matches original + const originalBytes = [ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]; + assert.deepEqual( + Array.from(bytesKeypair.secretKey), + originalBytes, + "Byte array keypair should match original bytes" + ); + + // Test base58 keypair + assert.equal( + bs58Keypair.publicKey.toBase58(), + expectedPublicKey.toBase58(), + "Base58 keypair should have correct public key" + ); + + assert.equal( + bs58Keypair.secretKey.length, + expectedSecretKeyLength, + "Base58 keypair secret key should be 64 bytes" + ); + + // Verify both keypairs generate the same key + assert.equal( + bytesKeypair.publicKey.toBase58(), + bs58Keypair.publicKey.toBase58(), + "Both keypairs should generate identical public keys" + ); + + // Verify secret keys match + assert.deepEqual( + bytesKeypair.secretKey, + bs58Keypair.secretKey, + "Both keypairs should have identical secret keys" + ); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/tests/sign-message.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/sign-message.test.ts new file mode 100644 index 000000000..947db10e1 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/sign-message.test.ts @@ -0,0 +1,122 @@ +import { describe, test } from "node:test"; +import assert from "node:assert"; +import * as ed from "@noble/ed25519"; +import { sha512 } from "@noble/hashes/sha512"; +import { utf8ToBytes } from "@noble/hashes/utils"; +import { + keypair, + message, + messageBytes, + signature, + result, +} from "../sign-message"; + +// Enable synchronous methods for noble-ed25519 +ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); + +describe("Sign and Verify Message", async () => { + test("keypair should have valid secretKey format and length", () => { + assert( + keypair.secretKey instanceof Uint8Array, + "secretKey should be Uint8Array" + ); + assert.equal(keypair.secretKey.length, 64, "secretKey should be 64 bytes"); + }); + + test("keypair should have valid publicKey format and length", () => { + assert( + keypair.publicKey.toBytes() instanceof Uint8Array, + "publicKey should convert to Uint8Array" + ); + assert.equal( + keypair.publicKey.toBytes().length, + 32, + "publicKey should be 32 bytes" + ); + }); + + test("message should convert to correct byte format", () => { + const testMessageBytes = utf8ToBytes(message); + assert.deepStrictEqual( + messageBytes, + testMessageBytes, + "messageBytes should match expected conversion" + ); + }); + + test("signature should have correct format and length", () => { + assert(signature instanceof Uint8Array, "signature should be Uint8Array"); + assert.equal(signature.length, 64, "signature should be 64 bytes"); + }); + + test("signature should verify successfully with correct inputs", () => { + const verificationResult = ed.verify( + signature, + messageBytes, + keypair.publicKey.toBytes() + ); + assert.strictEqual( + verificationResult, + true, + "signature should verify successfully" + ); + assert.strictEqual(result, true, "exported result should be true"); + }); + + test("signature should fail verification with wrong message", () => { + const wrongMessage = utf8ToBytes("Wrong message"); + assert.strictEqual( + ed.verify(signature, wrongMessage, keypair.publicKey.toBytes()), + false, + "should fail with wrong message" + ); + }); + + test("signature should fail verification with wrong public key", () => { + const wrongPubkey = ed.getPublicKey(ed.utils.randomPrivateKey()); + assert.strictEqual( + ed.verify(signature, messageBytes, wrongPubkey), + false, + "should fail with wrong public key" + ); + }); + + test("signature should fail verification with wrong signature", () => { + const wrongSignature = new Uint8Array(64).fill(1); + assert.strictEqual( + ed.verify(wrongSignature, messageBytes, keypair.publicKey.toBytes()), + false, + "should fail with wrong signature" + ); + }); + + test("signature generation should be deterministic", () => { + const newSignature = ed.sign(messageBytes, keypair.secretKey.slice(0, 32)); + assert.deepStrictEqual( + signature, + newSignature, + "regenerated signature should match original" + ); + }); + + test("should throw error for invalid signature length", () => { + assert.throws( + () => + ed.verify( + new Uint8Array(63), + messageBytes, + keypair.publicKey.toBytes() + ), + /Uint8Array/i, + "should throw on invalid signature length" + ); + }); + + test("should throw error for invalid message format", () => { + assert.throws( + () => ed.sign(null as any, keypair.secretKey.slice(0, 32)), + /expected/i, + "should throw on invalid message format" + ); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/tests/verify-keypair.test.ts b/code/content/web3jsv1/cookbook/wallets/tests/verify-keypair.test.ts new file mode 100644 index 000000000..337450a08 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/tests/verify-keypair.test.ts @@ -0,0 +1,26 @@ +import { strict as assert } from "node:assert"; +import { describe, it } from "node:test"; +import { PublicKey } from "@solana/web3.js"; +import { keypair } from "../verify-keypair"; + +describe("Verify Keypair", async () => { + it("keypair should match public key", () => { + const expectedPublicKey = new PublicKey( + "24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p" + ); + + // Verify the keypair's public key matches the expected public key + assert.equal( + keypair.publicKey.toBase58(), + expectedPublicKey.toBase58(), + "Generated keypair should have the expected public key" + ); + + // Additional verification of the secret key length + assert.equal( + keypair.secretKey.length, + 64, + "Secret key should be 64 bytes long" + ); + }); +}); diff --git a/code/content/web3jsv1/cookbook/wallets/verify-keypair.ts b/code/content/web3jsv1/cookbook/wallets/verify-keypair.ts new file mode 100644 index 000000000..e9e544fb6 --- /dev/null +++ b/code/content/web3jsv1/cookbook/wallets/verify-keypair.ts @@ -0,0 +1,17 @@ +import { Keypair, PublicKey } from "@solana/web3.js"; + +const publicKey = new PublicKey("24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p"); + +const keypair = Keypair.fromSecretKey( + Uint8Array.from([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]) +); + +console.log(keypair.publicKey.toBase58() === publicKey.toBase58()); +// output: true + +export { keypair }; diff --git a/code/content/web3jsv1/package.json b/code/content/web3jsv1/package.json new file mode 100644 index 000000000..2126029cf --- /dev/null +++ b/code/content/web3jsv1/package.json @@ -0,0 +1,16 @@ +{ + "name": "web3jsv1", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "node --import tsx --test '**/*.test.ts' --trace-deprecation", + "test:coverage": "node --import tsx --experimental-test-coverage --test '**/*.test.ts'" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@solana/web3.js": "^1.98.0" + } +} diff --git a/code/content/web3jsv2/cookbook/wallets/check-publickey.ts b/code/content/web3jsv2/cookbook/wallets/check-publickey.ts new file mode 100644 index 000000000..197835d12 --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/check-publickey.ts @@ -0,0 +1,54 @@ +import { + isAddress, + isProgramDerivedAddress, + Address, + createAddressWithSeed, +} from "@solana/web3.js"; + +export type AddressValidationResult = { + onCurveAddress: { + address: string; + isValid: boolean; + }; + offCurveAddress: { + address: string; + isPDA: boolean; + seed: string; + }; + invalidAddress: { + address: string; + isValid: boolean; + }; +}; + +export async function validateAddresses(): Promise { + // Valid public key that lies on the ed25519 curve (suitable for users) + const key = "5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY" as Address; + + // Valid public key that's off curve (suitable for programs) + const seed = "21"; + const offCurveAddress = await createAddressWithSeed({ + baseAddress: key, + programAddress: "11111111111111111111111111111111" as Address, + seed, + }); + + // Invalid public key for testing + const errorPubkey = "testPubkey"; + + return { + onCurveAddress: { + address: key, + isValid: isAddress(key), + }, + offCurveAddress: { + address: offCurveAddress, + isPDA: isProgramDerivedAddress([offCurveAddress, 21]), + seed, + }, + invalidAddress: { + address: errorPubkey, + isValid: isAddress(errorPubkey), + }, + }; +} diff --git a/code/content/web3jsv2/cookbook/wallets/create-keypair.ts b/code/content/web3jsv2/cookbook/wallets/create-keypair.ts new file mode 100644 index 000000000..ce6e2441a --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/create-keypair.ts @@ -0,0 +1,22 @@ +import { generateKeyPair, generateKeyPairSigner } from "@solana/web3.js"; + +// Secret key is never exported or exposed. + +export const createKeypair = async (): Promise<{ address: string }> => { + // KeyPairs are low-level and use the native Crypto API directly, + // This means you can conveniently pass them to transaction pipelines and they will be used to sign your transactions. + const keypair = await generateKeyPair(); + + return { address: keypair.publicKey.toString() }; +}; + +export const createKeypairSigner = async (): Promise<{ address: string }> => { + // The Signer instance just wraps the KeyPair instance and uses it for signing using the native Crypto API when required. + // whereas Signers is a higher-level abstraction over the concept of signing transactions and messages + // (this could be using a keypair, using a wallet in the browser, using a ledger API directly, whatever you want). + // Therefore KeyPairSigners are Signers that wrap the KeyPair API. + + const signer = await generateKeyPairSigner(); + + return { address: signer.address }; +}; diff --git a/code/content/web3jsv2/cookbook/wallets/restore-keypair.ts b/code/content/web3jsv2/cookbook/wallets/restore-keypair.ts new file mode 100644 index 000000000..6e658057d --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/restore-keypair.ts @@ -0,0 +1,25 @@ +import { + createKeyPairFromBytes, + createKeyPairSignerFromBytes, + getBase58Codec, +} from "@solana/web3.js"; + +export async function restoreKeypairBytes() { + const keypairBytes = new Uint8Array([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]); + + // create a signer keypair from the bytes + return await createKeyPairSignerFromBytes(keypairBytes, true); +} + +export async function restoreKeypairBase58() { + const keypairBase58 = + "5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG"; + const keypairBytes = getBase58Codec().encode(keypairBase58); + + return await createKeyPairFromBytes(keypairBytes); +} diff --git a/code/content/web3jsv2/cookbook/wallets/sign-message.ts b/code/content/web3jsv2/cookbook/wallets/sign-message.ts new file mode 100644 index 000000000..91caf84cc --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/sign-message.ts @@ -0,0 +1,28 @@ +import { + signBytes, + verifySignature, + getUtf8Encoder, + getBase58Decoder, + Address, +} from "@solana/web3.js"; + +export async function signMessage( + keys: CryptoKeyPair, + message: string = "Hello, World!" +) { + const encodedMessage = getUtf8Encoder().encode(message); + const signedBytes = await signBytes(keys.privateKey, encodedMessage); + + const decoded = getBase58Decoder().decode(signedBytes); + const verified = await verifySignature( + keys.publicKey, + signedBytes, + encodedMessage + ); + + return { + signature: signedBytes, + decodedSignature: decoded, + verified, + }; +} diff --git a/code/content/web3jsv2/cookbook/wallets/tests/check-publickey.test.ts b/code/content/web3jsv2/cookbook/wallets/tests/check-publickey.test.ts new file mode 100644 index 000000000..4edd62abf --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/tests/check-publickey.test.ts @@ -0,0 +1,60 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { validateAddresses } from "../check-publickey"; +import { isAddress, isProgramDerivedAddress } from "@solana/web3.js"; + +describe("checkPublickey", async () => { + test("should validate different types of addresses correctly", async () => { + const results = await validateAddresses(); + + // Check on-curve address + assert.ok( + results.onCurveAddress.isValid, + "On-curve address should be valid" + ); + assert.ok( + isAddress(results.onCurveAddress.address), + "On-curve address should be a valid Solana address" + ); + + // Check off-curve address + assert.ok( + results.offCurveAddress.isPDA, + "Off-curve address should be a valid PDA" + ); + assert.ok( + isAddress(results.offCurveAddress.address), + "Off-curve address should still be a valid address" + ); + + // Check invalid address + assert.ok( + !results.invalidAddress.isValid, + "Invalid address should be marked as invalid" + ); + assert.ok( + !isAddress(results.invalidAddress.address), + "Invalid address should fail address validation" + ); + }); + + test("should return consistent address strings", async () => { + const results = await validateAddresses(); + + assert.equal( + results.onCurveAddress.address, + "5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY", + "Should return correct on-curve address" + ); + assert.equal( + results.offCurveAddress.address, + "FEbdEwuYGBvzyJTm7zzYREFLgC94NEVLhDRLJ4KzPvAb", + "Should return correct off-curve address" + ); + assert.equal( + results.invalidAddress.address, + "testPubkey", + "Should return correct invalid address" + ); + }); +}); diff --git a/code/content/web3jsv2/cookbook/wallets/tests/create-keypair.test.ts b/code/content/web3jsv2/cookbook/wallets/tests/create-keypair.test.ts new file mode 100644 index 000000000..a6999ffe9 --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/tests/create-keypair.test.ts @@ -0,0 +1,13 @@ +import { describe, test } from "node:test"; +import assert from "node:assert"; +import { createKeypairSigner, createKeypair } from "../create-keypair"; + +describe("Create Keypair", () => { + test("should generate a valid keypair", async () => { + const signer = await createKeypairSigner(); + const address = await createKeypair(); + + assert.ok(signer.address); + assert.ok(address); + }); +}); diff --git a/code/content/web3jsv2/cookbook/wallets/tests/restore-keypair.test.ts b/code/content/web3jsv2/cookbook/wallets/tests/restore-keypair.test.ts new file mode 100644 index 000000000..5c1bde77f --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/tests/restore-keypair.test.ts @@ -0,0 +1,39 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { + restoreKeypairBase58, + restoreKeypairBytes, +} from "../restore-keypair.js"; +import { + isKeyPairSigner, + signBytes, + verifySignature, + getUtf8Encoder, + getBase58Decoder, +} from "@solana/web3.js"; + +describe("restoreKeypair", async () => { + test("should restore a valid Solana keypair", async () => { + const signer = await restoreKeypairBytes(); + + assert.ok(isKeyPairSigner(signer), "Should be a valid keypair signer"); + + assert.equal(signer.address.length, 44, "Public key should be 44 chars"); + }); + + test("keypair should be able to sign and verify messages", async () => { + const keypair = await restoreKeypairBase58(); + const message = getUtf8Encoder().encode("Hello, World!"); + + const signedBytes = await signBytes(keypair.privateKey, message); + const verified = await verifySignature( + keypair.publicKey, + signedBytes, + message + ); + + const decoded = getBase58Decoder().decode(signedBytes); + assert.equal(decoded.length, 88, "Signature should be 64 bytes"); + assert.ok(verified, "Signature should be verified successfully"); + }); +}); diff --git a/code/content/web3jsv2/cookbook/wallets/tests/sign-message.test.ts b/code/content/web3jsv2/cookbook/wallets/tests/sign-message.test.ts new file mode 100644 index 000000000..d4c36f977 --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/tests/sign-message.test.ts @@ -0,0 +1,51 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { signMessage } from "../sign-message"; +import { createKeyPairFromBytes } from "@solana/web3.js"; + +describe("signMessage", async () => { + const keypairBytes = new Uint8Array([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]); + + test("should sign and verify a message", async () => { + const signer = await createKeyPairFromBytes(keypairBytes); + const message = "Hello, World!"; + + const result = await signMessage(signer, message); + + assert.ok(result.signature, "Should produce a signature"); + assert.equal( + result.decodedSignature.length, + 88, + "Decoded signature should be 88 bytes" + ); + assert.ok(result.verified, "Signature should be verified"); + }); + + test("should handle empty messages", async () => { + const signer = await createKeyPairFromBytes(keypairBytes); + const message = ""; + + const result = await signMessage(signer, message); + + assert.ok(result.signature, "Should sign empty message"); + assert.ok(result.verified, "Should verify empty message signature"); + }); + + test("should handle special characters", async () => { + const signer = await createKeyPairFromBytes(keypairBytes); + const message = "Hello 👋 World! ❤️ 🌍 #@$%^&*"; + + const result = await signMessage(signer, message); + + assert.ok(result.signature, "Should sign message with special characters"); + assert.ok( + result.verified, + "Should verify signature with special characters" + ); + }); +}); diff --git a/code/content/web3jsv2/cookbook/wallets/tests/verify-keypair.test.ts b/code/content/web3jsv2/cookbook/wallets/tests/verify-keypair.test.ts new file mode 100644 index 000000000..2d67ed240 --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/tests/verify-keypair.test.ts @@ -0,0 +1,28 @@ +import { test, describe } from "node:test"; +import assert from "node:assert/strict"; +import { verifyKeypair } from "../verify-keypair"; +import { isAddress, Address } from "@solana/web3.js"; + +describe("verifyKeypair", async () => { + test("should verify keypair correctly", async () => { + const results = await verifyKeypair(); + + assert.ok(results.isValidSigner, "Should be a valid signer"); + assert.ok(results.isValidAddress, "Should be a valid address"); + }); + + test("should validate address format", async () => { + const validAddress = + "24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p" as Address; + const invalidAddress = "invalid-address"; + + assert.ok( + isAddress(validAddress), + "Should validate correct address format" + ); + assert.ok( + !isAddress(invalidAddress), + "Should reject invalid address format" + ); + }); +}); diff --git a/code/content/web3jsv2/cookbook/wallets/verify-keypair.ts b/code/content/web3jsv2/cookbook/wallets/verify-keypair.ts new file mode 100644 index 000000000..e8b3493f8 --- /dev/null +++ b/code/content/web3jsv2/cookbook/wallets/verify-keypair.ts @@ -0,0 +1,26 @@ +import { + isAddress, + isKeyPairSigner, + createKeyPairSignerFromBytes, + Address, +} from "@solana/web3.js"; + +export async function verifyKeypair() { + const publicKey = + "24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p" as Address; + + const keypairBytes = new Uint8Array([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]); + + const signer = await createKeyPairSignerFromBytes(keypairBytes); + + return { + hasMatchingPublicKey: signer.address === publicKey, + isValidAddress: isAddress(signer.address), + isValidSigner: isKeyPairSigner(signer), + }; +} diff --git a/code/content/web3jsv2/package.json b/code/content/web3jsv2/package.json new file mode 100644 index 000000000..9200202c3 --- /dev/null +++ b/code/content/web3jsv2/package.json @@ -0,0 +1,16 @@ +{ + "name": "web3jsv2", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "node --import tsx --test '**/*.test.ts' --trace-deprecation", + "test:coverage": "node --import tsx --experimental-test-coverage --test '**/*.test.ts'" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@solana/web3.js": "2" + } +} diff --git a/code/package.json b/code/package.json index 4429cdec8..77f64778f 100644 --- a/code/package.json +++ b/code/package.json @@ -1,15 +1,20 @@ { - "name": "code", + "name": "solana-developer-content-code", "version": "1.0.0", + "private": true, "description": "", "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, "keywords": [], "author": "", "license": "ISC", "dependencies": { - "@solana/web3.js": "^1.95.2" + "@noble/ed25519": "^2.1.0", + "@noble/hashes": "^1.5.0", + "bip39": "^3.1.0", + "bs58": "^6.0.0", + "micro-key-producer": "^0.7.1" + }, + "devDependencies": { + "tsx": "^4.19.1" } } diff --git a/code/tsconfig.json b/code/tsconfig.json new file mode 100644 index 000000000..03cc08ce3 --- /dev/null +++ b/code/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "esModuleInterop": true, + "lib": ["es2022", "dom"], + "module": "commonjs", + "outDir": "build", + "resolveJsonModule": true, + "strict": true, + "target": "es2022" + }, + "include": ["./**/*.ts"], + "exclude": ["./build"] +} diff --git a/coder.ts b/coder.ts index ae53d4693..464c392e9 100644 --- a/coder.ts +++ b/coder.ts @@ -5,35 +5,45 @@ import { unified } from "unified"; import remarkParse from "remark-parse"; import remarkStringify from "remark-stringify"; import remarkFrontmatter from "remark-frontmatter"; +import remarkMDX from "remark-mdx"; import { visit } from "unist-util-visit"; import ignore, { type Ignore } from "ignore"; -import importCode from "./src/utils/code-import"; +import codeImport from "./src/utils/code-import"; import chokidar from "chokidar"; let debugMode = false; -const debug = (...args: string[]) => { +const debug = (...args: any[]) => { if (debugMode) { console.log("[DEBUG]", ...args); } }; const hasCodeComponentWithFileMeta = async ( - filePath: string, + filePath: string ): Promise => { - const content = await fs.readFile(filePath, "utf8"); - let hasMatch = false; + try { + const content = await fs.readFile(filePath, "utf8"); + let hasMatch = false; - const tree = unified().use(remarkParse).use(remarkFrontmatter).parse(content); + const tree = unified() + .use(remarkParse) + .use(remarkMDX) + .use(remarkFrontmatter) + .parse(content); - visit(tree, "code", node => { - if (node.meta?.includes("file=")) { - hasMatch = true; - return false; // Stop visiting - } - }); + visit(tree, "code", (node) => { + if (node.meta?.includes("file=")) { + hasMatch = true; + return false; // Stop visiting + } + }); - return hasMatch; + return hasMatch; + } catch (error) { + debug(`Error checking file ${filePath}:`, error); + return false; + } }; const getIgnore = async (directory: string): Promise => { @@ -42,7 +52,7 @@ const getIgnore = async (directory: string): Promise => { try { const gitignoreContent = await fs.readFile( path.join(directory, ".gitignore"), - "utf8", + "utf8" ); ig.add(gitignoreContent); // ignore all dotfiles @@ -65,7 +75,7 @@ const getMarkdownAndMDXFiles = async (directory: string): Promise => { const walkDir = async (dir: string): Promise => { const entries = await fs.readdir(dir, { withFileTypes: true }); const files = await Promise.all( - entries.map(async entry => { + entries.map(async (entry) => { const res = path.resolve(dir, entry.name); const relativePath = path.relative(directory, res); @@ -85,14 +95,15 @@ const getMarkdownAndMDXFiles = async (directory: string): Promise => { if (await hasCodeComponentWithFileMeta(res)) { debug(`Found file with code component: ${relativePath}`); return res; + } else { + debug( + `Skipping file (no code component with file meta): ${relativePath}` + ); } - debug( - `Skipping file (no code component with file meta): ${relativePath}`, - ); } return []; - }), + }) ); return files.flat(); }; @@ -102,14 +113,16 @@ const getMarkdownAndMDXFiles = async (directory: string): Promise => { const processContent = async ( content: string, - filePath: string, + filePath: string ): Promise => { try { const file = await unified() .use(remarkParse) + .use(remarkMDX) .use(remarkFrontmatter) - .use(importCode, { - preserveTrailingNewline: false, + // @ts-expect-error + .use(codeImport, { + preserveTrailingNewline: true, removeRedundantIndentations: true, rootDir: process.cwd(), }) @@ -127,8 +140,9 @@ const processContent = async ( return String(file); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") { + const filePath = (error as NodeJS.ErrnoException).path; throw new Error( - `File not found: ${(error as NodeJS.ErrnoException).path}`, + `File not found: ${filePath}\nMake sure the file exists and the path is correct relative to the project root.` ); } throw error; @@ -158,7 +172,7 @@ const processFile = async (filePath: string): Promise => { const processInChunks = async ( items: T[], processItem: (item: T) => Promise, - chunkSize: number, + chunkSize: number ): Promise => { for (let i = 0; i < items.length; i += chunkSize) { const chunk = items.slice(i, i + chunkSize); @@ -169,12 +183,11 @@ const processInChunks = async ( const watchFiles = async (directory: string): Promise => { const watcher = chokidar.watch(["**/*.md", "**/*.mdx"], { ignored: [ - "**.**", /(^|[\/\\])\../, "**/node_modules/**", "**/.git/**", ".gitignore", - ], // ignore dotfiles, node_modules, .git, and .gitignore + ], persistent: true, cwd: directory, }); @@ -182,9 +195,11 @@ const watchFiles = async (directory: string): Promise => { console.log("Watch mode started. Waiting for file changes..."); watcher - .on("add", filePath => processFile(path.join(directory, filePath))) - .on("change", filePath => processFile(path.join(directory, filePath))) - .on("unlink", filePath => console.log(`File ${filePath} has been removed`)); + .on("add", (filePath) => processFile(path.join(directory, filePath))) + .on("change", (filePath) => processFile(path.join(directory, filePath))) + .on("unlink", (filePath) => + console.log(`File ${filePath} has been removed`) + ); }; const main = async (): Promise => { @@ -197,7 +212,7 @@ const main = async (): Promise => { console.log("Debug mode enabled"); } - if (filePath && !watchMode && !debugMode) { + if (filePath && !watchMode) { // Process single file const absolutePath = path.resolve(process.cwd(), filePath); console.log(`Processing single file: ${absolutePath}`); diff --git a/content/cookbook/accounts/create-account.md b/content/cookbook/accounts/create-account.md index 9ed042bf3..c8289c29d 100644 --- a/content/cookbook/accounts/create-account.md +++ b/content/cookbook/accounts/create-account.md @@ -38,7 +38,7 @@ import { const rpc = createSolanaRpc("https://api.devnet.solana.com"); const rpcSubscriptions = createSolanaRpcSubscriptions( - "wss://api.devnet.solana.com", + "wss://api.devnet.solana.com" ); const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({ @@ -61,9 +61,9 @@ const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); const transactionMessage = pipe( createTransactionMessage({ version: "legacy" }), - tx => setTransactionMessageFeePayerSigner(signer, tx), - tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), - tx => + (tx) => setTransactionMessageFeePayerSigner(signer, tx), + (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), + (tx) => appendTransactionMessageInstructions( [ // add a priority fee @@ -80,8 +80,8 @@ const transactionMessage = pipe( programAddress: SYSTEM_PROGRAM_ADDRESS, }), ], - tx, - ), + tx + ) ); const signedTransaction = @@ -115,7 +115,7 @@ const fromPubkey = Keypair.generate(); // Airdrop SOL for transferring lamports to the created account const airdropSignature = await connection.requestAirdrop( fromPubkey.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); @@ -136,7 +136,7 @@ const createAccountParams = { }; const createAccountTransaction = new Transaction().add( - SystemProgram.createAccount(createAccountParams), + SystemProgram.createAccount(createAccountParams) ); await sendAndConfirmTransaction(connection, createAccountTransaction, [ diff --git a/content/cookbook/accounts/create-pda-account.md b/content/cookbook/accounts/create-pda-account.md index 9465de687..6a4930f11 100644 --- a/content/cookbook/accounts/create-pda-account.md +++ b/content/cookbook/accounts/create-pda-account.md @@ -23,7 +23,7 @@ const programId = new PublicKey("G1DCNUQTSGHehwdLCAmRyAG8hf51eCHrLNUqkgGKYASj"); let [pda, bump] = PublicKey.findProgramAddressSync( [Buffer.from("test")], - programId, + programId ); console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`); // you will find the result is different from `createProgramAddress`. @@ -95,7 +95,7 @@ import { (async () => { // program id const programId = new PublicKey( - "7ZP42kRwUQ2zgbqXoaXzAFaiQnDyp6swNktTSv8mNQGN", + "7ZP42kRwUQ2zgbqXoaXzAFaiQnDyp6swNktTSv8mNQGN" ); // connection @@ -105,14 +105,14 @@ import { const feePayer = Keypair.generate(); const feePayerAirdropSignature = await connection.requestAirdrop( feePayer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(feePayerAirdropSignature); // setup pda let [pda, bump] = await PublicKey.findProgramAddress( [feePayer.publicKey.toBuffer()], - programId, + programId ); console.log(`bump: ${bump}, pubkey: ${pda.toBase58()}`); @@ -144,7 +144,7 @@ import { ], data: Buffer.from(new Uint8Array([data_size, bump])), programId: programId, - }), + }) ); console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`); diff --git a/content/cookbook/development/load-keypair-from-file.md b/content/cookbook/development/load-keypair-from-file.md index 6c7083cbc..d38fa5829 100644 --- a/content/cookbook/development/load-keypair-from-file.md +++ b/content/cookbook/development/load-keypair-from-file.md @@ -41,12 +41,12 @@ export async function loadDefaultKeypair(): Promise> { } export async function loadDefaultKeypairWithAirdrop( - cluster: string, + cluster: string ): Promise> { const keypair = await loadDefaultKeypair(); const rpc = createSolanaRpc(devnet(`https://api.${cluster}.solana.com`)); const rpcSubscriptions = createSolanaRpcSubscriptions( - devnet(`wss://api.${cluster}.solana.com`), + devnet(`wss://api.${cluster}.solana.com`) ); try { const result = await rpc.getBalance(keypair.address).send(); @@ -68,14 +68,14 @@ export async function loadDefaultKeypairWithAirdrop( } export async function loadKeypairFromFile( - filePath: string, + filePath: string ): Promise> { // This is here so you can also load the default keypair from the file system. const resolvedPath = path.resolve( - filePath.startsWith("~") ? filePath.replace("~", os.homedir()) : filePath, + filePath.startsWith("~") ? filePath.replace("~", os.homedir()) : filePath ); const loadedKeyBytes = Uint8Array.from( - JSON.parse(fs.readFileSync(resolvedPath, "utf8")), + JSON.parse(fs.readFileSync(resolvedPath, "utf8")) ); // Here you can also set the second parameter to true in case you need to extract your private key. const keypairSigner = await createKeyPairSignerFromBytes(loadedKeyBytes); diff --git a/content/cookbook/development/subscribing-events.md b/content/cookbook/development/subscribing-events.md index 8cc158a3d..a9bddb923 100644 --- a/content/cookbook/development/subscribing-events.md +++ b/content/cookbook/development/subscribing-events.md @@ -38,7 +38,7 @@ import { clusterApiUrl, Connection, Keypair } from "@solana/web3.js"; wallet.publicKey, (updatedAccountInfo, context) => console.log("Updated account info: ", updatedAccountInfo), - "confirmed", + "confirmed" ); })(); ``` diff --git a/content/cookbook/development/test-sol.md b/content/cookbook/development/test-sol.md index ec88e95bd..e317a2588 100644 --- a/content/cookbook/development/test-sol.md +++ b/content/cookbook/development/test-sol.md @@ -17,7 +17,7 @@ import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; const signature = await connection.requestAirdrop( keypair.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); diff --git a/content/cookbook/programs/clock.md b/content/cookbook/programs/clock.md index 2aa0ec7bf..03f2ac1d2 100644 --- a/content/cookbook/programs/clock.md +++ b/content/cookbook/programs/clock.md @@ -86,7 +86,7 @@ import { (async () => { const programId = new PublicKey( - "77ezihTV6mTh2Uf3ggwbYF2NyGJJ5HHah1GrdowWJVD3", + "77ezihTV6mTh2Uf3ggwbYF2NyGJJ5HHah1GrdowWJVD3" ); const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); @@ -100,10 +100,10 @@ import { lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, signature: await connection.requestAirdrop( feePayer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ), }, - "confirmed", + "confirmed" ); // Hello state account @@ -227,7 +227,7 @@ import { (async () => { const programId = new PublicKey( - "4ZEdbCtb5UyCSiAMHV5eSHfyjq3QwbG3yXb6oHD7RYjk", + "4ZEdbCtb5UyCSiAMHV5eSHfyjq3QwbG3yXb6oHD7RYjk" ); const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); @@ -241,10 +241,10 @@ import { lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, signature: await connection.requestAirdrop( feePayer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ), }, - "confirmed", + "confirmed" ); // Hello state account diff --git a/content/cookbook/programs/create-pda.md b/content/cookbook/programs/create-pda.md index c8e1be78b..7b8643328 100644 --- a/content/cookbook/programs/create-pda.md +++ b/content/cookbook/programs/create-pda.md @@ -1,8 +1,7 @@ --- title: How to create a Program Derived Address sidebarSortOrder: 5 -description: - "Learn how to create a Program Derived Address (PDA) in a Solana program." +description: "Learn how to create a Program Derived Address (PDA) in a Solana program." --- A Program Derived Address is simply an account owned by the program, but has @@ -125,7 +124,7 @@ const PAYER_KEYPAIR = Keypair.generate(); const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const latestBlockHash = await connection.getLatestBlockhash(); const programId = new PublicKey( - "6eW5nnSosr2LpkUGCdznsjRGDhVb26tLmiM1P8RV1QQp", + "6eW5nnSosr2LpkUGCdznsjRGDhVb26tLmiM1P8RV1QQp" ); // Airdrop to Payer @@ -135,15 +134,15 @@ const PAYER_KEYPAIR = Keypair.generate(); lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, signature: await connection.requestAirdrop( PAYER_KEYPAIR.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ), }, - "confirmed", + "confirmed" ); const [pda, bump] = await PublicKey.findProgramAddress( [Buffer.from("customaddress"), PAYER_KEYPAIR.publicKey.toBuffer()], - programId, + programId ); console.log(`PDA Pubkey: ${pda.toString()}`); diff --git a/content/cookbook/programs/cross-program-invocation.md b/content/cookbook/programs/cross-program-invocation.md index 57dd12aa7..ad843b959 100644 --- a/content/cookbook/programs/cross-program-invocation.md +++ b/content/cookbook/programs/cross-program-invocation.md @@ -418,14 +418,14 @@ const PAYER_KEYPAIR = Keypair.generate(); const GENERAL_STATE_KEYPAIR = Keypair.generate(); const ACCOUNT_SPACE_BUFFER = Buffer.from( - Uint8Array.of(...new BN(100).toArray("le", 8)), + Uint8Array.of(...new BN(100).toArray("le", 8)) ); (async () => { const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const latestBlockHash = await connection.getLatestBlockhash(); const programId = new PublicKey( - "DkuQ5wsndkzXfgqDB6Lgf4sDjBi4gkLSak1dM5Mn2RuQ", + "DkuQ5wsndkzXfgqDB6Lgf4sDjBi4gkLSak1dM5Mn2RuQ" ); // Airdropping 1 SOL @@ -436,10 +436,10 @@ const ACCOUNT_SPACE_BUFFER = Buffer.from( lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, signature: await connection.requestAirdrop( feePayer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ), }, - "confirmed", + "confirmed" ); // Our program's CPI instruction (create_account) diff --git a/content/cookbook/tokens/approve-token-delegate.md b/content/cookbook/tokens/approve-token-delegate.md index b64d457b1..45f58e6d7 100644 --- a/content/cookbook/tokens/approve-token-delegate.md +++ b/content/cookbook/tokens/approve-token-delegate.md @@ -33,24 +33,24 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const randomGuy = Keypair.generate(); const mintPubkey = new PublicKey( - "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV", + "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV" ); const tokenAccountPubkey = new PublicKey( - "GMxZfDmpR1b3vdJYXHzdF5noVLQogZuUAsDHHQ3ytPfV", + "GMxZfDmpR1b3vdJYXHzdF5noVLQogZuUAsDHHQ3ytPfV" ); // 1) use build-in function @@ -63,7 +63,7 @@ import bs58 from "bs58"; randomGuy.publicKey, // delegate alice, // owner of token account 1e8, // amount, if your decimals is 8, 10^8 for 1 token - 8, // decimals + 8 // decimals ); console.log(`txhash: ${txhash}`); } @@ -78,14 +78,14 @@ import bs58 from "bs58"; randomGuy.publicKey, // delegate alice.publicKey, // owner of token account 1e8, // amount, if your decimals is 8, 10^8 for 1 token - 8, // decimals - ), + 8 // decimals + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + owner */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/tokens/burn-tokens.md b/content/cookbook/tokens/burn-tokens.md index 486b297af..1f1aaf7e4 100644 --- a/content/cookbook/tokens/burn-tokens.md +++ b/content/cookbook/tokens/burn-tokens.md @@ -25,23 +25,23 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const mintPubkey = new PublicKey( - "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV", + "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV" ); const tokenAccountPubkey = new PublicKey( - "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5", + "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5" ); // 1) use build-in function @@ -53,7 +53,7 @@ import bs58 from "bs58"; mintPubkey, // mint alice, // owner 1e8, // amount, if your decimals is 8, 10^8 for 1 token - 8, + 8 ); console.log(`txhash: ${txhash}`); } @@ -68,14 +68,14 @@ import bs58 from "bs58"; mintPubkey, // mint alice.publicKey, // owner of token account 1e8, // amount, if your decimals is 8, 10^8 for 1 token - 8, // decimals - ), + 8 // decimals + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + token authority */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/tokens/close-token-accounts.md b/content/cookbook/tokens/close-token-accounts.md index b8001292f..76efcdfd6 100644 --- a/content/cookbook/tokens/close-token-accounts.md +++ b/content/cookbook/tokens/close-token-accounts.md @@ -31,19 +31,19 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const tokenAccountPubkey = new PublicKey( - "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5", + "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5" ); // 1) use build-in function @@ -53,7 +53,7 @@ import bs58 from "bs58"; feePayer, // payer tokenAccountPubkey, // token account which you want to close alice.publicKey, // destination - alice, // owner of token account + alice // owner of token account ); console.log(`txhash: ${txhash}`); } @@ -66,14 +66,14 @@ import bs58 from "bs58"; createCloseAccountInstruction( tokenAccountPubkey, // token account which you want to close alice.publicKey, // destination - alice.publicKey, // owner of token account - ), + alice.publicKey // owner of token account + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + owner */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/tokens/create-mint-account.md b/content/cookbook/tokens/create-mint-account.md index a65119321..658ea843e 100644 --- a/content/cookbook/tokens/create-mint-account.md +++ b/content/cookbook/tokens/create-mint-account.md @@ -35,15 +35,15 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); // 1) use build-in function @@ -52,7 +52,7 @@ import bs58 from "bs58"; feePayer, // fee payer alice.publicKey, // mint authority alice.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again) - 8, // decimals + 8 // decimals ); console.log(`mint: ${mintPubkey.toBase58()}`); @@ -76,15 +76,15 @@ import bs58 from "bs58"; mint.publicKey, // mint pubkey 8, // decimals alice.publicKey, // mint authority - alice.publicKey, // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again) - ), + alice.publicKey // freeze authority (you can use `null` to disable it. when you disable it, you can't turn it on again) + ) ); // Send transaction const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [feePayer, mint], // Signers + [feePayer, mint] // Signers ); console.log(`txhash: ${transactionSignature}`); diff --git a/content/cookbook/tokens/create-nft.md b/content/cookbook/tokens/create-nft.md index 06f2d5666..90383ec07 100644 --- a/content/cookbook/tokens/create-nft.md +++ b/content/cookbook/tokens/create-nft.md @@ -139,7 +139,7 @@ import "dotenv/config"; const umi = createUmi(clusterApiUrl("devnet")); const keypair = umi.eddsa.createKeypairFromSecretKey( - new Uint8Array(privateKey), + new Uint8Array(privateKey) ); // Use keypairIdentity to set the keypair as the signer diff --git a/content/cookbook/tokens/create-token-account.md b/content/cookbook/tokens/create-token-account.md index 8b03e3182..3bcfa0444 100644 --- a/content/cookbook/tokens/create-token-account.md +++ b/content/cookbook/tokens/create-token-account.md @@ -1,8 +1,7 @@ --- title: How to Create a Token Account sidebarSortOrder: 3 -description: - "Learn to create Solana token accounts, which hold tokens for users." +description: "Learn to create Solana token accounts, which hold tokens for users." --- A token account is required for a user to hold tokens. @@ -35,19 +34,19 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const mintPubkey = new PublicKey( - "2SKpuBU9ksneBZD4nqbZkw75NE11HsSHsGRtW2BZh5aQ", + "2SKpuBU9ksneBZD4nqbZkw75NE11HsSHsGRtW2BZh5aQ" ); // 1) use build-in function @@ -56,7 +55,7 @@ import bs58 from "bs58"; connection, // connection feePayer, // fee payer mintPubkey, // mint - alice.publicKey, // owner, + alice.publicKey // owner, ); console.log(`ATA: ${ata.toBase58()}`); } @@ -68,7 +67,7 @@ import bs58 from "bs58"; // calculate ATA let ata = await getAssociatedTokenAddress( mintPubkey, // mint - alice.publicKey, // owner + alice.publicKey // owner ); console.log(`ATA: ${ata.toBase58()}`); @@ -84,14 +83,14 @@ import bs58 from "bs58"; feePayer.publicKey, // payer ata, // ata alice.publicKey, // owner - mintPubkey, // mint - ), + mintPubkey // mint + ) ); const signature = await sendAndConfirmTransaction( connection, transaction, - [feePayer], // Signers + [feePayer] // Signers ); console.log(`txhash: ${await signature}`); diff --git a/content/cookbook/tokens/fetch-all-nfts.md b/content/cookbook/tokens/fetch-all-nfts.md index 9c07bf8a7..72109c242 100644 --- a/content/cookbook/tokens/fetch-all-nfts.md +++ b/content/cookbook/tokens/fetch-all-nfts.md @@ -1,8 +1,7 @@ --- title: How to get all NFTs from a wallet? sidebarSortOrder: 18 -description: - "Learn how to fetch all non-fungible tokens (NFTs) from a wallet on Solana." +description: "Learn how to fetch all non-fungible tokens (NFTs) from a wallet on Solana." --- ```typescript filename="get-nfts-by-wallet.ts" @@ -22,13 +21,13 @@ BigInt.prototype.toJSON = function () { // The owner's public key const ownerPublicKey = publicKey( - "2R4bHmSBHkHAskerTHE6GE1Fxbn31kaD5gHqpsPySVd7", + "2R4bHmSBHkHAskerTHE6GE1Fxbn31kaD5gHqpsPySVd7" ); console.log("Fetching NFTs..."); const allNFTs = await fetchAllDigitalAssetWithTokenByOwner( umi, - ownerPublicKey, + ownerPublicKey ); console.log(`Found ${allNFTs.length} NFTs for the owner:`); diff --git a/content/cookbook/tokens/fetch-nft-metadata.md b/content/cookbook/tokens/fetch-nft-metadata.md index 2987d7792..754eb0295 100644 --- a/content/cookbook/tokens/fetch-nft-metadata.md +++ b/content/cookbook/tokens/fetch-nft-metadata.md @@ -1,8 +1,7 @@ --- title: How to Fetch the NFT Metadata sidebarSortOrder: 16 -description: - "Learn how to fetch the metadata of a non-fungible token (NFT) on Solana." +description: "Learn how to fetch the metadata of a non-fungible token (NFT) on Solana." --- ```typescript filename="get-nft-metadata.ts" @@ -32,7 +31,7 @@ import { PublicKey } from "@metaplex-foundation/js"; // The mint address of the NFT you want to fetch const mintAddress = new PublicKey( - "Ay1U9DWphDgc7hq58Yj1yHabt91zTzvV2YJbAWkPNbaK", + "Ay1U9DWphDgc7hq58Yj1yHabt91zTzvV2YJbAWkPNbaK" ); console.log("Fetching NFT metadata..."); diff --git a/content/cookbook/tokens/get-all-token-accounts.md b/content/cookbook/tokens/get-all-token-accounts.md index ad78f04ce..0007ab87d 100644 --- a/content/cookbook/tokens/get-all-token-accounts.md +++ b/content/cookbook/tokens/get-all-token-accounts.md @@ -23,17 +23,17 @@ import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; programId: TOKEN_PROGRAM_ID, }); - response.value.forEach(accountInfo => { + response.value.forEach((accountInfo) => { console.log(`pubkey: ${accountInfo.pubkey.toBase58()}`); console.log(`mint: ${accountInfo.account.data["parsed"]["info"]["mint"]}`); console.log( - `owner: ${accountInfo.account.data["parsed"]["info"]["owner"]}`, + `owner: ${accountInfo.account.data["parsed"]["info"]["owner"]}` ); console.log( - `decimals: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["decimals"]}`, + `decimals: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["decimals"]}` ); console.log( - `amount: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"]}`, + `amount: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"]}` ); console.log("===================="); }); @@ -56,17 +56,17 @@ import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; mint: mint, }); - response.value.forEach(accountInfo => { + response.value.forEach((accountInfo) => { console.log(`pubkey: ${accountInfo.pubkey.toBase58()}`); console.log(`mint: ${accountInfo.account.data["parsed"]["info"]["mint"]}`); console.log( - `owner: ${accountInfo.account.data["parsed"]["info"]["owner"]}`, + `owner: ${accountInfo.account.data["parsed"]["info"]["owner"]}` ); console.log( - `decimals: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["decimals"]}`, + `decimals: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["decimals"]}` ); console.log( - `amount: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"]}`, + `amount: ${accountInfo.account.data["parsed"]["info"]["tokenAmount"]["amount"]}` ); console.log("===================="); }); diff --git a/content/cookbook/tokens/get-nft-owner.md b/content/cookbook/tokens/get-nft-owner.md index a8d15d134..0db133253 100644 --- a/content/cookbook/tokens/get-nft-owner.md +++ b/content/cookbook/tokens/get-nft-owner.md @@ -1,8 +1,7 @@ --- title: How to get the owner of an NFT sidebarSortOrder: 17 -description: - "Learn how to get the owner of a non-fungible token (NFT) on Solana." +description: "Learn how to get the owner of a non-fungible token (NFT) on Solana." --- If you have the mint key of an NFT, you can find its current owner by @@ -22,10 +21,10 @@ import { Connection, PublicKey } from "@solana/web3.js"; const tokenMint = "9ARngHhVaCtH5JFieRdSS5Y8cdZk2TMF4tfGSWFB9iSK"; const largestAccounts = await connection.getTokenLargestAccounts( - new PublicKey(tokenMint), + new PublicKey(tokenMint) ); const largestAccountInfo = await connection.getParsedAccountInfo( - largestAccounts.value[0].address, + largestAccounts.value[0].address ); console.log(largestAccountInfo?.value?.data); diff --git a/content/cookbook/tokens/get-token-account.md b/content/cookbook/tokens/get-token-account.md index 2c676b6df..854dbd5ed 100644 --- a/content/cookbook/tokens/get-token-account.md +++ b/content/cookbook/tokens/get-token-account.md @@ -17,7 +17,7 @@ import { getAccount } from "@solana/spl-token"; const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const tokenAccountPubkey = new PublicKey( - "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5", + "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5" ); let tokenAccount = await getAccount(connection, tokenAccountPubkey); diff --git a/content/cookbook/tokens/get-token-balance.md b/content/cookbook/tokens/get-token-balance.md index 0253a372d..c26a242b6 100644 --- a/content/cookbook/tokens/get-token-balance.md +++ b/content/cookbook/tokens/get-token-balance.md @@ -16,7 +16,7 @@ import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js"; const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const tokenAccount = new PublicKey( - "FWZedVtyKQtP4CXhT7XDnLidRADrJknmZGA2qNjpTPg8", + "FWZedVtyKQtP4CXhT7XDnLidRADrJknmZGA2qNjpTPg8" ); let tokenAmount = await connection.getTokenAccountBalance(tokenAccount); @@ -45,5 +45,6 @@ fn main() { ``` - A token account can only hold one kind of mint. When you specify a token account, you also specific a mint too. + A token account can only hold one kind of mint. When you specify a token + account, you also specific a mint too. diff --git a/content/cookbook/tokens/get-token-mint.md b/content/cookbook/tokens/get-token-mint.md index 8d5cc188b..46421c2e5 100644 --- a/content/cookbook/tokens/get-token-mint.md +++ b/content/cookbook/tokens/get-token-mint.md @@ -17,7 +17,7 @@ import { getMint } from "@solana/spl-token"; const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const mintAccountPublicKey = new PublicKey( - "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV", + "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV" ); let mintAccount = await getMint(connection, mintAccountPublicKey); diff --git a/content/cookbook/tokens/manage-wrapped-sol.md b/content/cookbook/tokens/manage-wrapped-sol.md index 6138c4ad8..3659aac38 100644 --- a/content/cookbook/tokens/manage-wrapped-sol.md +++ b/content/cookbook/tokens/manage-wrapped-sol.md @@ -48,21 +48,21 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); // remember to create ATA first let ata = await getAssociatedTokenAddress( NATIVE_MINT, // mint - alice.publicKey, // owner + alice.publicKey // owner ); let amount = 1 * 1e9; /* Wrapped SOL's decimals is 9 */ @@ -75,10 +75,10 @@ import bs58 from "bs58"; lamports: amount, }), // sync wrapped SOL balance - createSyncNativeInstruction(ata), + createSyncNativeInstruction(ata) ); console.log( - `txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, alice])}`, + `txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, alice])}` ); })(); ``` @@ -113,21 +113,21 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); // remember to create ATA first let ata = await getAssociatedTokenAddress( NATIVE_MINT, // mint - alice.publicKey, // owner + alice.publicKey // owner ); let auxAccount = Keypair.generate(); @@ -147,21 +147,21 @@ import bs58 from "bs58"; createInitializeAccountInstruction( auxAccount.publicKey, NATIVE_MINT, - alice.publicKey, + alice.publicKey ), // transfer WSOL createTransferInstruction( auxAccount.publicKey, ata, alice.publicKey, - amount, + amount ), // close aux account createCloseAccountInstruction( auxAccount.publicKey, alice.publicKey, - alice.publicKey, - ), + alice.publicKey + ) ); console.log( @@ -169,7 +169,7 @@ import bs58 from "bs58"; feePayer, auxAccount, alice, - ])}`, + ])}` ); })(); ``` diff --git a/content/cookbook/tokens/mint-tokens.md b/content/cookbook/tokens/mint-tokens.md index 191055d3b..83dc0d448 100644 --- a/content/cookbook/tokens/mint-tokens.md +++ b/content/cookbook/tokens/mint-tokens.md @@ -31,23 +31,23 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const mintPubkey = new PublicKey( - "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV", + "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV" ); const tokenAccountPubkey = new PublicKey( - "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5", + "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5" ); // 1) use build-in function @@ -59,7 +59,7 @@ import bs58 from "bs58"; tokenAccountPubkey, // receiver (should be a token account) alice, // mint authority 1e8, // amount. if your decimals is 8, you mint 10^8 for 1 token. - 8, // decimals + 8 // decimals ); console.log(`txhash: ${txhash}`); @@ -86,15 +86,15 @@ import bs58 from "bs58"; tokenAccountPubkey, // receiver (should be a token account) alice.publicKey, // mint authority 1e8, // amount. if your decimals is 8, you mint 10^8 for 1 token. - 8, // decimals + 8 // decimals // [signer1, signer2 ...], // only multisig account will use - ), + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + mint authority */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/tokens/revoke-token-delegate.md b/content/cookbook/tokens/revoke-token-delegate.md index d235436fe..c62542c90 100644 --- a/content/cookbook/tokens/revoke-token-delegate.md +++ b/content/cookbook/tokens/revoke-token-delegate.md @@ -1,8 +1,7 @@ --- title: How to Revoke a Token Delegate sidebarSortOrder: 12 -description: - "Learn how to revoke a token delegate on Solana, resetting delegate +description: "Learn how to revoke a token delegate on Solana, resetting delegate permissions and amounts." --- @@ -27,19 +26,19 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const tokenAccountPubkey = new PublicKey( - "DRS5CSgPQp4uvPPcUA34tckfYFNUPNBJi77fVbnSfQHr", + "DRS5CSgPQp4uvPPcUA34tckfYFNUPNBJi77fVbnSfQHr" ); // 1) use build-in function @@ -48,7 +47,7 @@ import bs58 from "bs58"; connection, // connection feePayer, // payer tokenAccountPubkey, // token account - alice, // owner of token account + alice // owner of token account ); console.log(`txhash: ${txhash}`); } @@ -60,14 +59,14 @@ import bs58 from "bs58"; let tx = new Transaction().add( createRevokeInstruction( tokenAccountPubkey, // token account - alice.publicKey, // owner of token account - ), + alice.publicKey // owner of token account + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + origin auth */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/tokens/set-update-token-authority.md b/content/cookbook/tokens/set-update-token-authority.md index 032b64358..f98f3908d 100644 --- a/content/cookbook/tokens/set-update-token-authority.md +++ b/content/cookbook/tokens/set-update-token-authority.md @@ -36,22 +36,22 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const randomGuy = Keypair.generate(); console.log(`random guy: ${randomGuy.publicKey.toBase58()}`); const mintPubkey = new PublicKey( - "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV", + "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV" ); // authority type @@ -72,7 +72,7 @@ import bs58 from "bs58"; mintPubkey, // mint account || token account alice, // current authority AuthorityType.MintTokens, // authority type - randomGuy.publicKey, // new authority (you can pass `null` to close it) + randomGuy.publicKey // new authority (you can pass `null` to close it) ); console.log(`txhash: ${txhash}`); } @@ -86,14 +86,14 @@ import bs58 from "bs58"; mintPubkey, // mint account || token account alice.publicKey, // current auth AuthorityType.MintTokens, // authority type - feePayer.publicKey, // new auth (you can pass `null` to close it) - ), + feePayer.publicKey // new auth (you can pass `null` to close it) + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + origin auth */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/tokens/transfer-tokens.md b/content/cookbook/tokens/transfer-tokens.md index 29e120d34..32e5532e6 100644 --- a/content/cookbook/tokens/transfer-tokens.md +++ b/content/cookbook/tokens/transfer-tokens.md @@ -29,26 +29,26 @@ import bs58 from "bs58"; // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 const feePayer = Keypair.fromSecretKey( bs58.decode( - "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2", - ), + "588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2" + ) ); // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY const alice = Keypair.fromSecretKey( bs58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const mintPubkey = new PublicKey( - "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV", + "8mAKLjGGmjKTnmcXeyr3pr7iX13xXVjJJiL6RujDbSPV" ); const tokenAccountXPubkey = new PublicKey( - "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5", + "2XYiFjmU1pCXmC2QfEAghk6S7UADseupkNQdnRBXszD5" ); const tokenAccountYPubkey = new PublicKey( - "GMxZfDmpR1b3vdJYXHzdF5noVLQogZuUAsDHHQ3ytPfV", + "GMxZfDmpR1b3vdJYXHzdF5noVLQogZuUAsDHHQ3ytPfV" ); // 1) use build-in function @@ -61,7 +61,7 @@ import bs58 from "bs58"; tokenAccountYPubkey, // to (should be a token account) alice, // from's owner 1e8, // amount, if your decimals is 8, send 10^8 for 1 token - 8, // decimals + 8 // decimals ); console.log(`txhash: ${txhash}`); } @@ -77,14 +77,14 @@ import bs58 from "bs58"; tokenAccountYPubkey, // to (should be a token account) alice.publicKey, // from's owner 1e8, // amount, if your decimals is 8, send 10^8 for 1 token - 8, // decimals - ), + 8 // decimals + ) ); console.log( `txhash: ${await sendAndConfirmTransaction(connection, tx, [ feePayer, alice /* fee payer + owner */, - ])}`, + ])}` ); } })(); diff --git a/content/cookbook/transactions/add-memo.md b/content/cookbook/transactions/add-memo.md index 490566307..285942f20 100644 --- a/content/cookbook/transactions/add-memo.md +++ b/content/cookbook/transactions/add-memo.md @@ -46,7 +46,7 @@ async function writeMemo(message: string) { const CLUSTER = "devnet"; const rpc = createSolanaRpc(devnet(`https://api.${CLUSTER}.solana.com`)); const rpcSubscriptions = createSolanaRpcSubscriptions( - devnet(`wss://api.${CLUSTER}.solana.com`), + devnet(`wss://api.${CLUSTER}.solana.com`) ); // Create an airdrop function. @@ -78,16 +78,16 @@ async function writeMemo(message: string) { const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); const transactionMessage = pipe( createTransactionMessage({ version: "legacy" }), - m => setTransactionMessageFeePayerSigner(keypairSigner, m), - m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), - m => + (m) => setTransactionMessageFeePayerSigner(keypairSigner, m), + (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), + (m) => appendTransactionMessageInstructions( [ getSetComputeUnitPriceInstruction({ microLamports: 5000n }), getAddMemoInstruction({ memo: message }), ], - m, - ), + m + ) ); // Figure out how many compute units to budget for this transaction @@ -97,24 +97,24 @@ async function writeMemo(message: string) { const estimatedComputeUnits = await getComputeUnitEstimate(transactionMessage); console.log( - `Transaction is estimated to consume ${estimatedComputeUnits} compute units`, + `Transaction is estimated to consume ${estimatedComputeUnits} compute units` ); const budgetedTransactionMessage = prependTransactionMessageInstructions( [getSetComputeUnitLimitInstruction({ units: estimatedComputeUnits })], - transactionMessage, + transactionMessage ); // Sign and send the transaction. console.log("Signing and sending the transaction"); const signedTx = await signTransactionMessageWithSigners( - budgetedTransactionMessage, + budgetedTransactionMessage ); const signature = getSignatureFromTransaction(signedTx); console.log( "Sending transaction https://explorer.solana.com/tx/" + signature + "/?cluster=" + - CLUSTER, + CLUSTER ); await sendAndConfirmTransaction(signedTx, { commitment: "confirmed" }); console.log("Transaction confirmed"); @@ -145,12 +145,12 @@ import { const connection = new Connection( "https://api.devnet.solana.com", - "confirmed", + "confirmed" ); const airdropSignature = await connection.requestAirdrop( fromKeypair.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); @@ -162,7 +162,7 @@ import { fromPubkey: fromKeypair.publicKey, toPubkey: toKeypair.publicKey, lamports: lamportsToSend, - }), + }) ); transferTransaction.add( @@ -172,7 +172,7 @@ import { ], data: Buffer.from("Memo message to send in this transaction", "utf-8"), programId: new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), - }), + }) ); await sendAndConfirmTransaction(connection, transferTransaction, [ diff --git a/content/cookbook/transactions/add-priority-fees.md b/content/cookbook/transactions/add-priority-fees.md index ea02cb42f..83e12c727 100644 --- a/content/cookbook/transactions/add-priority-fees.md +++ b/content/cookbook/transactions/add-priority-fees.md @@ -63,7 +63,7 @@ async function writeMemoWithPriorityFees(message: string) { const CLUSTER = "devnet"; const rpc = createSolanaRpc(devnet(`https://api.${CLUSTER}.solana.com`)); const rpcSubscriptions = createSolanaRpcSubscriptions( - devnet(`wss://api.${CLUSTER}.solana.com`), + devnet(`wss://api.${CLUSTER}.solana.com`) ); // Create an airdrop function. @@ -95,16 +95,16 @@ async function writeMemoWithPriorityFees(message: string) { const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); const transactionMessage = pipe( createTransactionMessage({ version: "legacy" }), - m => setTransactionMessageFeePayerSigner(keypairSigner, m), - m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), - m => + (m) => setTransactionMessageFeePayerSigner(keypairSigner, m), + (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), + (m) => appendTransactionMessageInstructions( [ getSetComputeUnitPriceInstruction({ microLamports: 5000n }), getAddMemoInstruction({ memo: message }), ], - m, - ), + m + ) ); // Figure out how many compute units to budget for this transaction @@ -121,24 +121,24 @@ async function writeMemoWithPriorityFees(message: string) { // You can read more about the issue here: https://github.com/solana-labs/solana-web3.js/tree/master/packages/library#getcomputeunitestimatefortransactionmessagefactoryrpc console.log( - `Transaction is estimated to consume ${estimatedComputeUnits} compute units`, + `Transaction is estimated to consume ${estimatedComputeUnits} compute units` ); const budgetedTransactionMessage = prependTransactionMessageInstructions( [getSetComputeUnitLimitInstruction({ units: estimatedComputeUnits })], - transactionMessage, + transactionMessage ); // Sign and send the transaction. console.log("Signing and sending the transaction"); const signedTx = await signTransactionMessageWithSigners( - budgetedTransactionMessage, + budgetedTransactionMessage ); const signature = getSignatureFromTransaction(signedTx); console.log( "Sending transaction https://explorer.solana.com/tx/" + signature + "/?cluster=" + - CLUSTER, + CLUSTER ); await sendAndConfirmTransaction(signedTx, { commitment: "confirmed" }); console.log("Transaction confirmed"); @@ -171,7 +171,7 @@ import { const airdropSignature = await connection.requestAirdrop( payer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); @@ -195,7 +195,7 @@ import { fromPubkey: payer.publicKey, toPubkey: toAccount, lamports: 10000000, - }), + }) ); const signature = await sendAndConfirmTransaction(connection, transaction, [ diff --git a/content/cookbook/transactions/calculate-cost.md b/content/cookbook/transactions/calculate-cost.md index 9bf42df2f..aad940a18 100644 --- a/content/cookbook/transactions/calculate-cost.md +++ b/content/cookbook/transactions/calculate-cost.md @@ -51,7 +51,7 @@ async function calculateCost(message: string) { const CLUSTER = "devnet"; const rpc = createSolanaRpc(devnet(`https://api.${CLUSTER}.solana.com`)); const rpcSubscriptions = createSolanaRpcSubscriptions( - devnet(`wss://api.${CLUSTER}.solana.com`), + devnet(`wss://api.${CLUSTER}.solana.com`) ); // Create a utility that estimates a transaction message's compute consumption. @@ -83,16 +83,16 @@ async function calculateCost(message: string) { const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); const transactionMessage = pipe( createTransactionMessage({ version: "legacy" }), - m => setTransactionMessageFeePayerSigner(signer, m), - m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), - m => + (m) => setTransactionMessageFeePayerSigner(signer, m), + (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), + (m) => appendTransactionMessageInstructions( [ getSetComputeUnitPriceInstruction({ microLamports: 5000n }), getAddMemoInstruction({ memo: message }), ], - m, - ), + m + ) ); // Figure out how many compute units to budget for this transaction @@ -102,12 +102,12 @@ async function calculateCost(message: string) { const estimatedComputeUnits = await getComputeUnitEstimate(transactionMessage); console.log( - `Transaction is estimated to consume ${estimatedComputeUnits} compute units`, + `Transaction is estimated to consume ${estimatedComputeUnits} compute units` ); const budgetedTransactionMessage = prependTransactionMessageInstructions( [getSetComputeUnitLimitInstruction({ units: estimatedComputeUnits })], - transactionMessage, + transactionMessage ); const base64EncodedMessage = pipe( @@ -121,7 +121,7 @@ async function calculateCost(message: string) { getCompiledTransactionMessageEncoder().encode, // Encode that byte array as a base64 string. - getBase64Decoder().decode, + getBase64Decoder().decode ) as TransactionMessageBytesBase64; const transactionCost = await rpc @@ -129,20 +129,20 @@ async function calculateCost(message: string) { .send(); console.log( - "Transaction is estimated to cost " + transactionCost.value + " lamports", + "Transaction is estimated to cost " + transactionCost.value + " lamports" ); // Sign and send the transaction. console.log("Signing and sending the transaction"); const signedTx = await signTransactionMessageWithSigners( - budgetedTransactionMessage, + budgetedTransactionMessage ); const signature = getSignatureFromTransaction(signedTx); console.log( "Sending transaction https://explorer.solana.com/tx/" + signature + "/?cluster=" + - CLUSTER, + CLUSTER ); await sendAndConfirmTransaction(signedTx, { commitment: "confirmed" }); console.log("Transaction confirmed"); diff --git a/content/cookbook/transactions/offline-transactions.md b/content/cookbook/transactions/offline-transactions.md index 793f66d2c..665adb0cb 100644 --- a/content/cookbook/transactions/offline-transactions.md +++ b/content/cookbook/transactions/offline-transactions.md @@ -36,11 +36,11 @@ import * as bs58 from "bs58"; // alice and feePayer are signer in this tx const feePayer = Keypair.generate(); await connection.confirmTransaction( - await connection.requestAirdrop(feePayer.publicKey, LAMPORTS_PER_SOL), + await connection.requestAirdrop(feePayer.publicKey, LAMPORTS_PER_SOL) ); const alice = Keypair.generate(); await connection.confirmTransaction( - await connection.requestAirdrop(alice.publicKey, LAMPORTS_PER_SOL), + await connection.requestAirdrop(alice.publicKey, LAMPORTS_PER_SOL) ); const bob = Keypair.generate(); @@ -50,7 +50,7 @@ import * as bs58 from "bs58"; fromPubkey: alice.publicKey, toPubkey: bob.publicKey, lamports: 0.1 * LAMPORTS_PER_SOL, - }), + }) ); tx.recentBlockhash = (await connection.getRecentBlockhash()).blockhash; tx.feePayer = feePayer.publicKey; @@ -61,7 +61,7 @@ import * as bs58 from "bs58"; // the return signature should be 64 bytes. let feePayerSignature = nacl.sign.detached( realDataNeedToSign, - feePayer.secretKey, + feePayer.secretKey ); let aliceSignature = nacl.sign.detached(realDataNeedToSign, alice.secretKey); @@ -71,14 +71,14 @@ import * as bs58 from "bs58"; let verifyFeePayerSignatureResult = nacl.sign.detached.verify( realDataNeedToSign, feePayerSignature, - feePayer.publicKey.toBytes(), // you should use the raw pubkey (32 bytes) to verify + feePayer.publicKey.toBytes() // you should use the raw pubkey (32 bytes) to verify ); console.log(`verify feePayer signature: ${verifyFeePayerSignatureResult}`); let verifyAliceSignatureResult = nacl.sign.detached.verify( realDataNeedToSign, aliceSignature, - alice.publicKey.toBytes(), + alice.publicKey.toBytes() ); console.log(`verify alice signature: ${verifyAliceSignatureResult}`); @@ -91,7 +91,7 @@ import * as bs58 from "bs58"; // 4. Send transaction console.log( - `txhash: ${await connection.sendRawTransaction(recoverTx.serialize())}`, + `txhash: ${await connection.sendRawTransaction(recoverTx.serialize())}` ); } @@ -106,7 +106,7 @@ import * as bs58 from "bs58"; // 4. Send transaction console.log( - `txhash: ${await connection.sendRawTransaction(recoverTx.serialize())}`, + `txhash: ${await connection.sendRawTransaction(recoverTx.serialize())}` ); } @@ -156,19 +156,19 @@ import base58 from "bs58"; const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const alicePublicKey = new PublicKey( - "5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8", + "5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8" ); const bobKeypair = Keypair.fromSecretKey( base58.decode( - "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp", - ), + "4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp" + ) ); const tokenAddress = new PublicKey( - "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr" ); const bobTokenAddress = await getAssociatedTokenAddress( tokenAddress, - bobKeypair.publicKey, + bobKeypair.publicKey ); // Alice may not have a token account, so Bob creates one if not @@ -176,7 +176,7 @@ import base58 from "bs58"; connection, bobKeypair, // Bob pays the fee to create it tokenAddress, // which token the account is for - alicePublicKey, // who the token account is for + alicePublicKey // who the token account is for ); // Get the details about the token mint @@ -197,7 +197,7 @@ import base58 from "bs58"; fromPubkey: alicePublicKey, toPubkey: bobKeypair.publicKey, lamports: 0.01 * LAMPORTS_PER_SOL, - }), + }) ); // Transfer 1 token from Bob -> Alice @@ -208,8 +208,8 @@ import base58 from "bs58"; aliceTokenAccount.address, // destination bobKeypair.publicKey, // owner of source account 1 * 10 ** tokenMint.decimals, // amount to transfer - tokenMint.decimals, // decimals of token - ), + tokenMint.decimals // decimals of token + ) ); // Partial sign as Bob @@ -225,7 +225,7 @@ import base58 from "bs58"; // The caller of this can convert it back to a transaction object: const recoveredTransaction = Transaction.from( - Buffer.from(transactionBase64, "base64"), + Buffer.from(transactionBase64, "base64") ); })(); ``` @@ -262,7 +262,7 @@ import { // Fund our wallet with 1 SOL const airdropSignature = await connection.requestAirdrop( feePayer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); @@ -280,7 +280,7 @@ import { newAccountPubkey: nonceAccount.publicKey, lamports: await connection.getMinimumBalanceForRentExemption( - NONCE_ACCOUNT_LENGTH, + NONCE_ACCOUNT_LENGTH ), space: NONCE_ACCOUNT_LENGTH, programId: SystemProgram.programId, @@ -289,11 +289,11 @@ import { SystemProgram.nonceInitialize({ noncePubkey: nonceAccount.publicKey, // nonce account pubkey authorizedPubkey: nonceAccountAuth.publicKey, // nonce account authority (for advance and close) - }), + }) ); console.log( - `txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, nonceAccount])}`, + `txhash: ${await sendAndConfirmTransaction(connection, tx, [feePayer, nonceAccount])}` ); })(); ``` @@ -313,7 +313,7 @@ import { const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); const nonceAccountPubkey = new PublicKey( - "7H18z3v3rZEoKiwY3kh8DLn9eFT6nFCQ2m4kiC7RZ3a4", + "7H18z3v3rZEoKiwY3kh8DLn9eFT6nFCQ2m4kiC7RZ3a4" ); let accountInfo = await connection.getAccountInfo(nonceAccountPubkey); @@ -348,7 +348,7 @@ import { getKeypairFromFile } from "@solana-developers/helpers"; // Fund our wallet with 1 SOL const airdropSignature = await connection.requestAirdrop( feePayer.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); @@ -358,7 +358,7 @@ import { getKeypairFromFile } from "@solana-developers/helpers"; const nonceAccountAuth = await getKeypairFromFile(); const nonceAccountPubkey = new PublicKey( - "7H18z3v3rZEoKiwY3kh8DLn9eFT6nFCQ2m4kiC7RZ3a4", + "7H18z3v3rZEoKiwY3kh8DLn9eFT6nFCQ2m4kiC7RZ3a4" ); let nonceAccountInfo = await connection.getAccountInfo(nonceAccountPubkey); let nonceAccount = NonceAccount.fromAccountData(nonceAccountInfo.data); @@ -374,14 +374,14 @@ import { getKeypairFromFile } from "@solana-developers/helpers"; fromPubkey: feePayer.publicKey, toPubkey: nonceAccountAuth.publicKey, lamports: 1, - }), + }) ); // assign `nonce` as recentBlockhash tx.recentBlockhash = nonceAccount.nonce; tx.feePayer = feePayer.publicKey; tx.sign( feePayer, - nonceAccountAuth, + nonceAccountAuth ); /* fee payer + nonce account authority + ... */ console.log(`txhash: ${await connection.sendRawTransaction(tx.serialize())}`); diff --git a/content/cookbook/transactions/optimize-compute.md b/content/cookbook/transactions/optimize-compute.md index 969e890db..928cf4779 100644 --- a/content/cookbook/transactions/optimize-compute.md +++ b/content/cookbook/transactions/optimize-compute.md @@ -20,7 +20,7 @@ async function buildOptimalTransaction( connection: Connection, instructions: Array, signer: Signer, - lookupTables: Array, + lookupTables: Array ) { const [microLamports, units, recentBlockhash] = await Promise.all([ 100 /* Get optimal priority fees - https://solana.com/developers/guides/advanced/how-to-use-priority-fees*/, @@ -28,13 +28,13 @@ async function buildOptimalTransaction( connection, instructions, signer.publicKey, - lookupTables, + lookupTables ), connection.getLatestBlockhash(), ]); instructions.unshift( - ComputeBudgetProgram.setComputeUnitPrice({ microLamports }), + ComputeBudgetProgram.setComputeUnitPrice({ microLamports }) ); if (units) { // probably should add some margin of error to units @@ -46,7 +46,7 @@ async function buildOptimalTransaction( instructions, recentBlockhash: recentBlockhash.blockhash, payerKey: signer.publicKey, - }).compileToV0Message(lookupTables), + }).compileToV0Message(lookupTables) ), recentBlockhash, }; diff --git a/content/cookbook/transactions/send-sol.md b/content/cookbook/transactions/send-sol.md index 2af20caf5..daeddb85f 100644 --- a/content/cookbook/transactions/send-sol.md +++ b/content/cookbook/transactions/send-sol.md @@ -44,7 +44,7 @@ async function transferSol() { const CLUSTER = "devnet"; const rpc = createSolanaRpc(devnet(`https://api.${CLUSTER}.solana.com`)); const rpcSubscriptions = createSolanaRpcSubscriptions( - devnet(`wss://api.${CLUSTER}.solana.com`), + devnet(`wss://api.${CLUSTER}.solana.com`) ); // Create an airdrop function. @@ -76,9 +76,9 @@ async function transferSol() { const { value: latestBlockhash } = await rpc.getLatestBlockhash().send(); const transactionMessage = pipe( createTransactionMessage({ version: "legacy" }), - m => setTransactionMessageFeePayerSigner(keypairSigner, m), - m => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), - m => + (m) => setTransactionMessageFeePayerSigner(keypairSigner, m), + (m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m), + (m) => appendTransactionMessageInstructions( [ getSetComputeUnitPriceInstruction({ microLamports: 5000n }), @@ -88,8 +88,8 @@ async function transferSol() { amount: lamports(1_000_000n), }), ], - m, - ), + m + ) ); // Figure out how many compute units to budget for this transaction @@ -99,24 +99,24 @@ async function transferSol() { const estimatedComputeUnits = await getComputeUnitEstimate(transactionMessage); console.log( - `Transaction is estimated to consume ${estimatedComputeUnits} compute units`, + `Transaction is estimated to consume ${estimatedComputeUnits} compute units` ); const budgetedTransactionMessage = prependTransactionMessageInstructions( [getSetComputeUnitLimitInstruction({ units: estimatedComputeUnits })], - transactionMessage, + transactionMessage ); // Sign and send the transaction. console.log("Signing and sending the transaction"); const signedTx = await signTransactionMessageWithSigners( - budgetedTransactionMessage, + budgetedTransactionMessage ); const signature = getSignatureFromTransaction(signedTx); console.log( "Sending transaction https://explorer.solana.com/tx/" + signature + "/?cluster=" + - CLUSTER, + CLUSTER ); await sendAndConfirmTransaction(signedTx, { commitment: "confirmed" }); console.log("Transaction confirmed"); @@ -145,12 +145,12 @@ import { const connection = new Connection( "https://api.devnet.solana.com", - "confirmed", + "confirmed" ); const airdropSignature = await connection.requestAirdrop( fromKeypair.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(airdropSignature); @@ -162,7 +162,7 @@ import { fromPubkey: fromKeypair.publicKey, toPubkey: toKeypair.publicKey, lamports: lamportsToSend, - }), + }) ); await sendAndConfirmTransaction(connection, transferTransaction, [ diff --git a/content/cookbook/transactions/send-tokens.md b/content/cookbook/transactions/send-tokens.md index 7b7c84e16..e8db162a2 100644 --- a/content/cookbook/transactions/send-tokens.md +++ b/content/cookbook/transactions/send-tokens.md @@ -34,7 +34,7 @@ import { const fromWallet = Keypair.generate(); const fromAirdropSignature = await connection.requestAirdrop( fromWallet.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); // Wait for airdrop confirmation await connection.confirmTransaction(fromAirdropSignature); @@ -48,7 +48,7 @@ import { fromWallet, fromWallet.publicKey, null, - 9, + 9 ); // Get the token account of the fromWallet Solana address, if it does not exist, create it @@ -56,7 +56,7 @@ import { connection, fromWallet, mint, - fromWallet.publicKey, + fromWallet.publicKey ); //get the token account of the toWallet Solana address, if it does not exist, create it @@ -64,7 +64,7 @@ import { connection, fromWallet, mint, - toWallet.publicKey, + toWallet.publicKey ); // Minting 1 new token to the "fromTokenAccount" account we just returned/created @@ -75,7 +75,7 @@ import { fromTokenAccount.address, fromWallet.publicKey, 1000000000, // it's 1 token, but in lamports - [], + [] ); // Add token transfer instructions to transaction @@ -84,8 +84,8 @@ import { fromTokenAccount.address, toTokenAccount.address, fromWallet.publicKey, - 1, - ), + 1 + ) ); // Sign transaction, broadcast, and confirm diff --git a/content/cookbook/wallets/check-publickey.md b/content/cookbook/wallets/check-publickey.md index 3aa440c73..1c65e2219 100644 --- a/content/cookbook/wallets/check-publickey.md +++ b/content/cookbook/wallets/check-publickey.md @@ -12,56 +12,88 @@ the public key lies on the ed25519 curve. Only public keys that lie on the curve can be controlled by users with wallets. - - - -```typescript -import { isAddress } from "@solana/web3.js"; - -// Note that generateKeyPair() will always give a public key that is valid for users - -// Valid public key -const key = "5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY"; - -// Lies on the ed25519 curve and is suitable for users -console.log("Valid Address: ", isAddress(key)); - -// // Valid public key -const offCurveAddress = "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e"; - -// // Not on the ed25519 curve, therefore not suitable for users -console.log("Valid Off Curve Address: ", isAddress(offCurveAddress)); - -// // Not a valid public key -const errorPubkey = "testPubkey"; -console.log("Invalid Address: ", isAddress(errorPubkey)); -``` - - - - -```typescript -import { PublicKey } from "@solana/web3.js"; - -// Note that Keypair.generate() will always give a public key that is valid for users - -// Valid public key -const key = new PublicKey("5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY"); -// Lies on the ed25519 curve and is suitable for users -console.log(PublicKey.isOnCurve(key.toBytes())); - -// Valid public key -const offCurveAddress = new PublicKey( - "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e", -); - -// Not on the ed25519 curve, therefore not suitable for users -console.log(PublicKey.isOnCurve(offCurveAddress.toBytes())); - -// Not a valid public key -const errorPubkey = new PublicKey("testPubkey"); -console.log(PublicKey.isOnCurve(errorPubkey.toBytes())); -``` - - + + ```typescript file=/code/content/web3jsv2/cookbook/wallets/check-publickey.ts#L1-L49 + import { + isAddress, + isProgramDerivedAddress, + Address, + createAddressWithSeed, + } from "@solana/web3.js"; + + export type AddressValidationResult = { + onCurveAddress: { + address: string; + isValid: boolean; + }; + offCurveAddress: { + address: string; + isPDA: boolean; + seed: string; + }; + invalidAddress: { + address: string; + isValid: boolean; + }; + }; + + export async function validateAddresses(): Promise { + // Valid public key that lies on the ed25519 curve (suitable for users) + const key = "5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY" as Address; + + // Valid public key that's off curve (suitable for programs) + const seed = "21"; + const offCurveAddress = await createAddressWithSeed({ + baseAddress: key, + programAddress: "11111111111111111111111111111111" as Address, + seed, + }); + + // Invalid public key for testing + const errorPubkey = "testPubkey"; + + return { + onCurveAddress: { + address: key, + isValid: isAddress(key), + }, + offCurveAddress: { + address: offCurveAddress, + isPDA: isProgramDerivedAddress([offCurveAddress, 21]), + seed, + }, + invalidAddress: { + ``` + + + + + ```typescript file=/code/content/web3jsv1/cookbook/wallets/check-publickey.ts#L1-L24 + import { PublicKey, Keypair } from "@solana/web3.js"; + + // Note that Keypair.generate() will always give a public key that is valid for users + + // Valid public key + const key = new PublicKey("5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY"); + // Lies on the ed25519 curve and is suitable for users + console.log(PublicKey.isOnCurve(key.toBytes())); + + // Valid public key + const offCurveAddress = new PublicKey( + "4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e", + ); + + // Not on the ed25519 curve, therefore not suitable for users + console.log(PublicKey.isOnCurve(offCurveAddress.toBytes())); + + let errorPubkey; + try { + // Not a valid public key + errorPubkey = new PublicKey("testPubkey"); + } catch (err) { + // Error will be caught here + } + ``` + + diff --git a/content/cookbook/wallets/create-keypair.md b/content/cookbook/wallets/create-keypair.md index d8d6b360e..b0c23db7f 100644 --- a/content/cookbook/wallets/create-keypair.md +++ b/content/cookbook/wallets/create-keypair.md @@ -12,27 +12,44 @@ you do not need to worry about the keypair. Otherwise a keypair must be generated for signing transactions. + + ```typescript file=/code/content/web3jsv2/cookbook/wallets/create-keypair.ts#L1-L28 + import { generateKeyPair, generateKeyPairSigner } from "@solana/web3.js"; - + // Secret key is never exported or exposed. -```javascript -import { generateKeyPairSigner } from "@solana/web3.js"; + export const createKeypair = async (): Promise<{ address: string }> => { + // KeyPairs are low-level and use the native Crypto API directly, + // This means you can conveniently pass them to transaction pipelines and they will be used to sign your transactions. + const keypair = await generateKeyPair(); -const signer = await generateKeyPairSigner(); -console.log("address: ", signer.address); -``` + return { address: keypair.publicKey.toString() }; + }; - + export const createKeypairSigner = async (): Promise<{ address: string }> => { + // The Signer instance just wraps the KeyPair instance and uses it for signing using the native Crypto API when required. + // whereas Signers is a higher-level abstraction over the concept of signing transactions and messages + // (this could be using a keypair, using a wallet in the browser, using a ledger API directly, whatever you want). + // Therefore KeyPairSigners are Signers that wrap the KeyPair API. - + const signer = await generateKeyPairSigner(); -```javascript -import { Keypair } from "@solana/web3.js"; + return { address: signer.address }; + }; -const keypair = Keypair.generate(); -console.log("address:", keypair.publicKey.toBase58()); -``` + ``` - + + + ```typescript file=/code/content/web3jsv1/cookbook/wallets/create-keypair.ts + import { Keypair } from "@solana/web3.js"; + + const keypair = Keypair.generate(); + + export { keypair }; + + ``` + + diff --git a/content/cookbook/wallets/generate-mnemonic.md b/content/cookbook/wallets/generate-mnemonic.md index baa48397a..70232065a 100644 --- a/content/cookbook/wallets/generate-mnemonic.md +++ b/content/cookbook/wallets/generate-mnemonic.md @@ -11,7 +11,7 @@ generally used to make the user experience within wallets better than a Keypair file by using a list of readable words (instead of a shorter string of random numbers and letters). -```typescript filename="generate-mnemonic.ts" +```typescript filename="generate-mnemonic.ts" file=/code/content/web3jsv1/cookbook/wallets/generate-mnemonic.ts#L1-L3 import * as bip39 from "bip39"; const mnemonic = bip39.generateMnemonic(); diff --git a/content/cookbook/wallets/generate-vanity-address.md b/content/cookbook/wallets/generate-vanity-address.md index c8663ec22..1b71c8f0e 100644 --- a/content/cookbook/wallets/generate-vanity-address.md +++ b/content/cookbook/wallets/generate-vanity-address.md @@ -18,6 +18,6 @@ key more easily identifiable. You can generate a vanity address using the [Solana CLI](/docs/intro/installation.md): -```bash +```bash file=/code/content/web3jsv1/cookbook/wallets/generate-vanity-address.sh solana-keygen grind --starts-with e1v1s:1 ``` diff --git a/content/cookbook/wallets/restore-from-mnemonic.md b/content/cookbook/wallets/restore-from-mnemonic.md index 6477c5bb9..86625f278 100644 --- a/content/cookbook/wallets/restore-from-mnemonic.md +++ b/content/cookbook/wallets/restore-from-mnemonic.md @@ -9,7 +9,7 @@ convert the mnemonic to Keypairs for local testing. ## Restoring BIP39 format mnemonics -```typescript filename="restore-bip39-mnemonic.ts" +```typescript filename="restore-bip39-mnemonic.ts" file=/code/content/web3jsv1/cookbook/wallets/restore-bip39-mnemonic.ts#L1-L11 import { Keypair } from "@solana/web3.js"; import * as bip39 from "bip39"; @@ -21,27 +21,29 @@ const seed = bip39.mnemonicToSeedSync(mnemonic, ""); const keypair = Keypair.fromSeed(seed.slice(0, 32)); console.log(`${keypair.publicKey.toBase58()}`); - -// output: 5ZWj7a1f8tWkjBESHKgrLmXshuXxqeY9SYcfbshpAqPG ``` ## Restoring BIP44 formant mnemonics -```typescript filename="restore-bip44-mnemonic.ts" +```typescript filename="restore-bip44-mnemonic.ts" file=/code/content/web3jsv1/cookbook/wallets/restore-bip44-mnemonic.ts#L1-L3,L11-L27 import { Keypair } from "@solana/web3.js"; -import { HDKey } from "micro-ed25519-hdkey"; +import { HDKey } from "micro-key-producer/slip10.js"; import * as bip39 from "bip39"; - const mnemonic = "neither lonely flavor argue grass remind eye tag avocado spot unusual intact"; -// arguments: (mnemonic, password) const seed = bip39.mnemonicToSeedSync(mnemonic, ""); const hd = HDKey.fromMasterSeed(seed.toString("hex")); +const wallets: Wallet[] = []; + for (let i = 0; i < 10; i++) { const path = `m/44'/501'/${i}'/0'`; const keypair = Keypair.fromSeed(hd.derive(path).privateKey); - console.log(`${path} => ${keypair.publicKey.toBase58()}`); + wallets.push({ + path, + keypair, + publicKey: keypair.publicKey.toBase58(), + }); } ``` diff --git a/content/cookbook/wallets/restore-keypair.md b/content/cookbook/wallets/restore-keypair.md index 61aaba7e8..59f0f3330 100644 --- a/content/cookbook/wallets/restore-keypair.md +++ b/content/cookbook/wallets/restore-keypair.md @@ -10,68 +10,79 @@ secret to test out your dApp. ## From Bytes - - - -```typescript -import { createKeyPairFromBytes } from "@solana/web3.js"; - -const keypairBytes = new Uint8Array([ - 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, - 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, 15, - 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, 121, - 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, -]); - -const keypair = await createKeyPairFromBytes(keypairBytes); -``` - - - - -```typescript -import { Keypair } from "@solana/web3.js"; - -const keypairBytes = Uint8Array.from([ - 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, - 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, 15, - 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, 121, - 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, -]); - -const keypair = Keypair.fromSecretKey(keypairBytes); -``` - - + + ```typescript filename="restore-keypair-from-bytes.ts" file=/code/content/web3jsv2/cookbook/wallets/restore-keypair.ts#L1-L13 + import { + createKeyPairFromBytes, + createKeyPairSignerFromBytes, + getBase58Codec, + } from "@solana/web3.js"; + + export async function restoreKeypairBytes() { + const keypairBytes = new Uint8Array([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]); + ``` + + + + + ```typescript filename="restore-keypair-from-bytes.ts" file=/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bytes.ts#L1-L12 + import { Keypair } from "@solana/web3.js"; + + const keypair = Keypair.fromSecretKey( + Uint8Array.from([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]), + ); + + export { keypair as bytesKeypair }; + ``` + + ## From Base58 String - - - -```typescript -import { createKeyPairFromBytes, getBase58Codec } from "@solana/web3.js"; - -const keypairBase58 = - "5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG"; -const keypairBytes = getBase58Codec().decode(keypairBase58); -const keypair = await createKeyPairFromBytes(keypairBytes); -``` - - - - -```typescript -import { Keypair } from "@solana/web3.js"; -import * as bs58 from "bs58"; - -const keypairBase58 = - "5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG"; -const keypairBytes = bs58.decode(keypairBase58); -const keypair = Keypair.fromSecretKey(keypairBytes); -``` - - + + ```typescript filename="restore-keypair-from-base58.ts" file=/code/content/web3jsv2/cookbook/wallets/restore-keypair.ts#L1-L6,L19-L25 + import { + createKeyPairFromBytes, + createKeyPairSignerFromBytes, + getBase58Codec, + } from "@solana/web3.js"; + + export async function restoreKeypairBase58() { + const keypairBase58 = + "5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG"; + const keypairBytes = getBase58Codec().encode(keypairBase58); + + return await createKeyPairFromBytes(keypairBytes); + } + ``` + + + + + ```typescript filename="restore-keypair-from-base58.ts" file=/code/content/web3jsv1/cookbook/wallets/restore-keypair-from-bs58.ts#L1-L10 + import { Keypair } from "@solana/web3.js"; + import bs58 from "bs58"; + + const keypair = Keypair.fromSecretKey( + bs58.decode( + "4UzFMkVbk1q6ApxvDS8inUxg4cMBxCQRVXRx5msqQyktbi1QkJkt574Jda6BjZThSJi54CHfVoLFdVFX8XFn233L", + ), + ); + + export { keypair as bs58Keypair }; + ``` + + diff --git a/content/cookbook/wallets/sign-message.md b/content/cookbook/wallets/sign-message.md index 06ae8819f..45505a8fe 100644 --- a/content/cookbook/wallets/sign-message.md +++ b/content/cookbook/wallets/sign-message.md @@ -9,62 +9,71 @@ verification of the signature. Verification of a signature allows the recipient to be sure that the data was signed by the owner of a specific private key. - - - -```typescript -import { - generateKeyPair, - signBytes, - verifySignature, - getUtf8Encoder, - getBase58Decoder, -} from "@solana/web3.js"; - -const keys = await generateKeyPair(); -const message = getUtf8Encoder().encode("Hello, World!"); -const signedBytes = await signBytes(keys.privateKey, message); - -const decoded = getBase58Decoder().decode(signedBytes); -console.log("Signature:", decoded); - -const verified = await verifySignature(keys.publicKey, signedBytes, message); -console.log("Verified:", verified); -``` - - - - - -In Solana Web3.js v1, we can use the -[TweetNaCl](https://www.npmjs.com/package/tweetnacl) crypto library: - -```typescript -import { Keypair } from "@solana/web3.js"; -import nacl from "tweetnacl"; -import { decodeUTF8 } from "tweetnacl-util"; - -const keypair = Keypair.fromSecretKey( - Uint8Array.from([ - 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, - 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, - 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, - 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, - ]), -); - -const message = "The quick brown fox jumps over the lazy dog"; -const messageBytes = decodeUTF8(message); - -const signature = nacl.sign.detached(messageBytes, keypair.secretKey); -const result = nacl.sign.detached.verify( - messageBytes, - signature, - keypair.publicKey.toBytes(), -); - -console.log(result); -``` - - + + ```typescript filename="sign-message.ts" file=/code/content/web3jsv2/cookbook/wallets/sign-message.ts#L1-L28 + import { + signBytes, + verifySignature, + getUtf8Encoder, + getBase58Decoder, + Address, + } from "@solana/web3.js"; + + export async function signMessage( + keys: CryptoKeyPair, + message: string = "Hello, World!", + ) { + const encodedMessage = getUtf8Encoder().encode(message); + const signedBytes = await signBytes(keys.privateKey, encodedMessage); + + const decoded = getBase58Decoder().decode(signedBytes); + const verified = await verifySignature( + keys.publicKey, + signedBytes, + encodedMessage, + ); + + return { + signature: signedBytes, + decodedSignature: decoded, + verified, + }; + } + ``` + + + + + In Solana Web3.js v1, we can use the + [TweetNaCl](https://www.npmjs.com/package/tweetnacl) crypto library: + + ```typescript filename="sign-message.ts" file=/code/content/web3jsv1/cookbook/wallets/sign-message.ts#L1-L25 + import { Keypair } from "@solana/web3.js"; + import * as ed from "@noble/ed25519"; + import { sha512 } from "@noble/hashes/sha512"; + import { utf8ToBytes } from "@noble/hashes/utils"; + + // Enable synchronous methods for noble-ed25519 + ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m)); + + const keypair = Keypair.fromSecretKey( + Uint8Array.from([ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, + 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, + 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, + ]), + ); + + const message = "The quick brown fox jumps over the lazy dog"; + const messageBytes = utf8ToBytes(message); + + // Sign using noble-ed25519 + const signature = ed.sign(messageBytes, keypair.secretKey.slice(0, 32)); + + // Verify using noble-ed25519 + const result = ed.verify(signature, messageBytes, keypair.publicKey.toBytes()); + ``` + + diff --git a/content/cookbook/wallets/verify-keypair.md b/content/cookbook/wallets/verify-keypair.md index 6a812be52..b8b4a1ae9 100644 --- a/content/cookbook/wallets/verify-keypair.md +++ b/content/cookbook/wallets/verify-keypair.md @@ -7,7 +7,7 @@ description: "Learn how to verify keypairs on Solana." If you are given a keypair, you can verify whether or not the secret matches the given public key -```typescript filename="verify-keypair.ts" +```typescript typescript filename="verify-keypair.ts" file=/code/content/web3jsv1/cookbook/wallets/verify-keypair.ts#L1-L15 import { Keypair, PublicKey } from "@solana/web3.js"; const publicKey = new PublicKey("24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p"); @@ -18,7 +18,7 @@ const keypair = Keypair.fromSecretKey( 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 251, 237, 246, 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 121, 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, - ]), + ]) ); console.log(keypair.publicKey.toBase58() === publicKey.toBase58()); diff --git a/content/courses/connecting-to-offchain-data/oracles.md b/content/courses/connecting-to-offchain-data/oracles.md index 80d79798f..13fbd34fc 100644 --- a/content/courses/connecting-to-offchain-data/oracles.md +++ b/content/courses/connecting-to-offchain-data/oracles.md @@ -1191,7 +1191,7 @@ import { assert } from "chai"; import { confirmTransaction } from "@solana-developers/helpers"; const SOL_USD_SWITCHBOARD_FEED = new PublicKey( - "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR", + "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR" ); const ESCROW_SEED = "MICHAEL BURRY"; @@ -1215,18 +1215,18 @@ describe("burry-escrow", () => { before(async () => { switchboardProgram = await SwitchboardProgram.load( new Connection(DEVNET_RPC_URL), - payer, + payer ); aggregatorAccount = new AggregatorAccount( switchboardProgram, - SOL_USD_SWITCHBOARD_FEED, + SOL_USD_SWITCHBOARD_FEED ); }); const createAndVerifyEscrow = async (unlockPrice: number) => { const [escrow] = PublicKey.findProgramAddressSync( [Buffer.from(ESCROW_SEED), payer.publicKey.toBuffer()], - program.programId, + program.programId ); try { @@ -1243,13 +1243,13 @@ describe("burry-escrow", () => { await confirmTransaction( provider.connection, transaction, - CONFIRMATION_COMMITMENT, + CONFIRMATION_COMMITMENT ); const escrowAccount = await program.account.escrow.fetch(escrow); const escrowBalance = await provider.connection.getBalance( escrow, - CONFIRMATION_COMMITMENT, + CONFIRMATION_COMMITMENT ); console.log("Onchain unlock price:", escrowAccount.unlockPrice); @@ -1277,11 +1277,11 @@ describe("burry-escrow", () => { it("withdraws from escrow", async () => { const [escrow] = PublicKey.findProgramAddressSync( [Buffer.from(ESCROW_SEED), payer.publicKey.toBuffer()], - program.programId, + program.programId ); const userBalanceBefore = await provider.connection.getBalance( - payer.publicKey, + payer.publicKey ); try { @@ -1299,7 +1299,7 @@ describe("burry-escrow", () => { await confirmTransaction( provider.connection, transaction, - CONFIRMATION_COMMITMENT, + CONFIRMATION_COMMITMENT ); // Verify escrow account is closed @@ -1310,17 +1310,17 @@ describe("burry-escrow", () => { console.log(error.message); assert( error.message.includes("Account does not exist"), - "Unexpected error: " + error.message, + "Unexpected error: " + error.message ); } // Verify user balance increased const userBalanceAfter = await provider.connection.getBalance( - payer.publicKey, + payer.publicKey ); assert( userBalanceAfter > userBalanceBefore, - "User balance should have increased", + "User balance should have increased" ); } catch (error) { throw new Error(`Failed to withdraw from escrow: ${error.message}`); @@ -1340,7 +1340,7 @@ describe("burry-escrow", () => { it("fails to withdraw while price is below UnlockPrice", async () => { const [escrow] = PublicKey.findProgramAddressSync( [Buffer.from(ESCROW_SEED), payer.publicKey.toBuffer()], - program.programId, + program.programId ); try { @@ -1409,4 +1409,5 @@ A potential solution to this challenge can be found Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=1a5d266c-f4c1-4c45-b986-2afd4be59991)! + diff --git a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md index 70fc691af..72341da7f 100644 --- a/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md +++ b/content/courses/connecting-to-offchain-data/verifiable-randomness-functions.md @@ -151,7 +151,7 @@ now, we'll simply set them at `VRFAUTH`. [vrfAuthorityKey, vrfAuthoritySecret] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("VRFAUTH")], - program.programId, + program.programId ); ``` @@ -1242,11 +1242,11 @@ import { NodeOracle } from "@switchboard-xyz/oracle"; import { assert } from "chai"; export const solUsedSwitchboardFeed = new anchor.web3.PublicKey( - "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR", + "GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR" ); function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } describe("burry-escrow-vrf", () => { @@ -1261,17 +1261,17 @@ describe("burry-escrow-vrf", () => { const switchboardProgram = await SwitchboardProgram.load( "devnet", new anchor.web3.Connection("https://api.devnet.solana.com"), - payer, + payer ); const aggregatorAccount = new AggregatorAccount( switchboardProgram, - solUsedSwitchboardFeed, + solUsedSwitchboardFeed ); // derive escrow state account const [escrowState] = await anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("MICHAEL BURRY"), payer.publicKey.toBuffer()], - program.programId, + program.programId ); console.log("Escrow Account: ", escrowState.toBase58()); @@ -1303,7 +1303,7 @@ describe("burry-escrow-vrf", () => { const escrowBalance = await provider.connection.getBalance( escrowState, - "confirmed", + "confirmed" ); console.log("Onchain unlock price:", newAccount.unlockPrice); console.log("Amount in escrow:", escrowBalance); @@ -1323,7 +1323,7 @@ describe("burry-escrow-vrf", () => { // derive escrow address const [escrowState] = await anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("MICHAEL BURRY"), payer.publicKey.toBuffer()], - program.programId, + program.programId ); // send tx @@ -1346,9 +1346,9 @@ describe("burry-escrow-vrf", () => { assert( error.message.includes( - "Current SOL price is not above Escrow unlock price.", + "Current SOL price is not above Escrow unlock price." ), - "Unexpected error message: " + error.message, + "Unexpected error message: " + error.message ); } @@ -1448,7 +1448,7 @@ it("Roll till you can withdraw", async () => { // derive escrow address const [escrowState] = await anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("MICHAEL BURRY"), payer.publicKey.toBuffer()], - program.programId, + program.programId ); const vrfSecret = anchor.web3.Keypair.generate(); @@ -1459,7 +1459,7 @@ it("Roll till you can withdraw", async () => { escrowState.toBytes(), vrfSecret.publicKey.toBytes(), ], - program.programId, + program.programId ); console.log(`VRF Client: ${vrfClientKey}`); @@ -1496,13 +1496,13 @@ it("Roll till you can withdraw", async () => { switchboard.program, queue.authority, switchboard.queue.publicKey, - vrfAccount.publicKey, + vrfAccount.publicKey ); const [payerTokenWallet] = await switchboard.program.mint.getOrCreateWrappedUser( switchboard.program.walletPubkey, - { fundUpTo: 1.0 }, + { fundUpTo: 1.0 } ); // initialize vrf client @@ -1573,7 +1573,7 @@ it("Roll till you can withdraw", async () => { "Roll results - Die 1:", vrfState.dieResult1, "Die 2:", - vrfState.dieResult2, + vrfState.dieResult2 ); if (vrfState.dieResult1 == vrfState.dieResult2) { rolledDoubles = true; @@ -1611,7 +1611,7 @@ nice function to create and fund a test wallet. const [payerTokenWallet] = await switchboard.program.mint.getOrCreateWrappedUser( switchboard.program.walletPubkey, - { fundUpTo: 1.0 }, + { fundUpTo: 1.0 } ); ``` @@ -1636,6 +1636,6 @@ If you get stuck, we have the solution in the [`vrf-challenge-solution` branch](https://github.com/solana-developers/burry-escrow/tree/vrf-challenge-solution). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5af49eda-f3e7-407d-8cd7-78d0653ee17c)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5af49eda-f3e7-407d-8cd7-78d0653ee17c)! diff --git a/content/courses/intro-to-solana/interact-with-wallets.md b/content/courses/intro-to-solana/interact-with-wallets.md index 76cfc9db1..144699f13 100644 --- a/content/courses/intro-to-solana/interact-with-wallets.md +++ b/content/courses/intro-to-solana/interact-with-wallets.md @@ -90,10 +90,10 @@ npm install @solana/wallet-adapter-base \ ``` -We're learning doing this manually to learn about Wallet -Adapter, but you can also use -[create-solana-dapp](https://github.com/solana-developers/create-solana-dapp) to -create a brand new React or NextJS app that supports Solana wallets. + We're learning doing this manually to learn about Wallet Adapter, but you can + also use + [create-solana-dapp](https://github.com/solana-developers/create-solana-dapp) + to create a brand new React or NextJS app that supports Solana wallets. ### Connect To Wallets @@ -121,7 +121,7 @@ import { import { clusterApiUrl } from "@solana/web3.js"; import "@solana/wallet-adapter-react-ui/styles.css"; -export const Home: NextPage = props => { +export const Home: NextPage = (props) => { const endpoint = clusterApiUrl("devnet"); const wallets = useMemo(() => [], []); @@ -176,7 +176,7 @@ import { } from "@solana/web3.js"; import "@solana/wallet-adapter-react-ui/styles.css"; -const Home: NextPage = props => { +const Home: NextPage = (props) => { const endpoint = clusterApiUrl("devnet"); const wallets = useMemo(() => [], []); @@ -244,10 +244,10 @@ export const BalanceDisplay: FC = () => { try { connection.onAccountChange( publicKey, - updatedAccountInfo => { + (updatedAccountInfo) => { setBalance(updatedAccountInfo.lamports / LAMPORTS_PER_SOL); }, - "confirmed", + "confirmed" ); const accountInfo = await connection.getAccountInfo(publicKey); @@ -285,7 +285,7 @@ to submit transactions for approval. const { publicKey, sendTransaction } = useWallet(); const { connection } = useConnection(); -const sendSol = async event => { +const sendSol = async (event) => { event.preventDefault(); if (!publicKey) { @@ -473,7 +473,7 @@ import { AppBar } from "../components/AppBar"; import Head from "next/head"; import { PingButton } from "../components/PingButton"; -const Home: NextPage = props => { +const Home: NextPage = (props) => { return (
@@ -672,6 +672,6 @@ If you get really stumped, feel free to [check out the solution code](https://github.com/Unboxed-Software/solana-send-sol-frontend/tree/main). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=69c5aac6-8a9f-4e23-a7f5-28ae2845dfe1)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=69c5aac6-8a9f-4e23-a7f5-28ae2845dfe1)! diff --git a/content/courses/intro-to-solana/intro-to-cryptography.md b/content/courses/intro-to-solana/intro-to-cryptography.md index f88af61a3..02128ac6a 100644 --- a/content/courses/intro-to-solana/intro-to-cryptography.md +++ b/content/courses/intro-to-solana/intro-to-cryptography.md @@ -230,7 +230,7 @@ import { getKeypairFromEnvironment } from "@solana-developers/helpers"; const keypair = getKeypairFromEnvironment("SECRET_KEY"); console.log( - `✅ Finished! We've loaded our secret key securely, using an env file!`, + `✅ Finished! We've loaded our secret key securely, using an env file!` ); ``` @@ -248,4 +248,5 @@ Solana. In the next chapter, we'll use them! Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=ee06a213-5d74-4954-846e-cba883bc6db1)! + diff --git a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md index d0c2adc8f..db5c991c9 100644 --- a/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md +++ b/content/courses/intro-to-solana/intro-to-custom-onchain-programs.md @@ -67,7 +67,7 @@ const transaction = new web3.Transaction().add(instruction); const signature = await web3.sendAndConfirmTransaction( connection, transaction, - [payer], + [payer] ); console.log(`✅ Success! Transaction signature is: ${signature}`); @@ -123,7 +123,7 @@ const newBalance = await airdropIfRequired( connection, payer.publicKey, 1 * web3.LAMPORTS_PER_SOL, - 0.5 * web3.LAMPORTS_PER_SOL, + 0.5 * web3.LAMPORTS_PER_SOL ); ``` @@ -192,7 +192,7 @@ transaction.add(instruction); const signature = await web3.sendAndConfirmTransaction( connection, transaction, - [payer], + [payer] ); console.log(`✅ Transaction completed! Signature is ${signature}`); @@ -238,7 +238,7 @@ future, simply change your `console.log` to the following: ```typescript console.log( - `You can view your transaction on Solana Explorer at:\nhttps://explorer.solana.com/tx/${signature}?cluster=devnet`, + `You can view your transaction on Solana Explorer at:\nhttps://explorer.solana.com/tx/${signature}?cluster=devnet` ); ``` @@ -261,6 +261,6 @@ If you get stuck feel free to glance at the [solution code](https://github.com/Unboxed-Software/solana-ping-client). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=e969d07e-ae85-48c3-976f-261a22f02e52)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=e969d07e-ae85-48c3-976f-261a22f02e52)! diff --git a/content/courses/intro-to-solana/intro-to-reading-data.md b/content/courses/intro-to-solana/intro-to-reading-data.md index 4a8dcb97a..067ebeaad 100644 --- a/content/courses/intro-to-solana/intro-to-reading-data.md +++ b/content/courses/intro-to-solana/intro-to-reading-data.md @@ -4,8 +4,7 @@ objectives: - Understand accounts and their addresses - Understand SOL and lamports - Use web3.js to connect to Solana and read an account balance -description: - "Connect to Solana DevNet from TypeScript and read data from the blockchain!" +description: "Connect to Solana DevNet from TypeScript and read data from the blockchain!" --- ## Summary @@ -151,7 +150,7 @@ const balanceInLamports = await connection.getBalance(publicKey); const balanceInSOL = balanceInLamports / LAMPORTS_PER_SOL; console.log( - `💰 Finished! The balance for the wallet at address ${publicKey} is ${balanceInSOL}!`, + `💰 Finished! The balance for the wallet at address ${publicKey} is ${balanceInSOL}!` ); ``` @@ -201,7 +200,7 @@ const balanceInLamports = await connection.getBalance(publicKey); const balanceInSOL = balanceInLamports / LAMPORTS_PER_SOL; console.log( - `✅ Finished! The balance for the wallet at address ${publicKey} is ${balanceInSOL}!`, + `✅ Finished! The balance for the wallet at address ${publicKey} is ${balanceInSOL}!` ); ``` @@ -225,6 +224,6 @@ Modify the script as follows: We'll transfer SOL in the next lesson! -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=8bbbfd93-1cdc-4ce3-9c83-637e7aa57454)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=8bbbfd93-1cdc-4ce3-9c83-637e7aa57454)! diff --git a/content/courses/intro-to-solana/intro-to-writing-data.md b/content/courses/intro-to-solana/intro-to-writing-data.md index 4fde2db16..e0a7e766f 100644 --- a/content/courses/intro-to-solana/intro-to-writing-data.md +++ b/content/courses/intro-to-solana/intro-to-writing-data.md @@ -6,8 +6,7 @@ objectives: - Use `@solana/web3.js` to send SOL - Use `@solana/web3.js` to sign transactions - Use Solana Explorer to view transactions -description: - "Make your first transactions on DevNet, using the System and memo programs!" +description: "Make your first transactions on DevNet, using the System and memo programs!" --- ## Summary @@ -129,7 +128,7 @@ await airdropIfRequired( connection, keypair.publicKey, 1 * LAMPORTS_PER_SOL, - 0.5 * LAMPORTS_PER_SOL, + 0.5 * LAMPORTS_PER_SOL ); ``` @@ -194,7 +193,7 @@ const toPubkey = new PublicKey(suppliedToPubkey); const connection = new Connection("https://api.devnet.solana.com", "confirmed"); console.log( - `✅ Loaded our own keypair, the destination public key, and connected to Solana`, + `✅ Loaded our own keypair, the destination public key, and connected to Solana` ); ``` @@ -210,7 +209,7 @@ Add the following to complete the transaction and send it: ```typescript console.log( - `✅ Loaded our own keypair, the destination public key, and connected to Solana`, + `✅ Loaded our own keypair, the destination public key, and connected to Solana` ); const transaction = new Transaction(); @@ -230,7 +229,7 @@ const signature = await sendAndConfirmTransaction(connection, transaction, [ ]); console.log( - `💸 Finished! Sent ${LAMPORTS_TO_SEND} to the address ${toPubkey}. `, + `💸 Finished! Sent ${LAMPORTS_TO_SEND} to the address ${toPubkey}. ` ); console.log(`Transaction signature is ${signature}!`); ``` @@ -257,6 +256,6 @@ Answer the following questions: - What do you think "confirmed" means? -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=dda6b8de-9ed8-4ed2-b1a5-29d7a8a8b415)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=dda6b8de-9ed8-4ed2-b1a5-29d7a8a8b415)! diff --git a/content/courses/mobile/intro-to-solana-mobile.md b/content/courses/mobile/intro-to-solana-mobile.md index 0bd866010..7a258a14e 100644 --- a/content/courses/mobile/intro-to-solana-mobile.md +++ b/content/courses/mobile/intro-to-solana-mobile.md @@ -5,8 +5,7 @@ objectives: - Explain the high-level Mobile Wallet Adapter (MWA) flow - Explain the high-level differences between React and React Native - Create a simple Android Solana App using React Native -description: - "Learn how to build native mobile apps using blockchain functionality" +description: "Learn how to build native mobile apps using blockchain functionality" --- ## Summary @@ -137,7 +136,11 @@ points to note: and an emulator or physical device for testing. -**NOTE:** There is a learning curve, but if you know React you're not nearly as far from being able to develop mobile apps as you think! It may feel jarring to start, but after a few hours of React Native development, you will start to feel much more comfortable. We have included a [Lab](#lab) section below to help you. + **NOTE:** There is a learning curve, but if you know React you're not nearly + as far from being able to develop mobile apps as you think! It may feel + jarring to start, but after a few hours of React Native development, you will + start to feel much more comfortable. We have included a [Lab](#lab) section + below to help you. ## Creating a React Native App on Solana @@ -524,7 +527,7 @@ export interface ConnectionContextState { } const ConnectionContext = createContext( - {} as ConnectionContextState, + {} as ConnectionContextState ); export function ConnectionProvider({ @@ -534,7 +537,7 @@ export function ConnectionProvider({ }: ConnectionProviderProps) { const connection = useMemo( () => new Connection(endpoint, config), - [config, endpoint], + [config, endpoint] ); return ( @@ -604,19 +607,19 @@ import React from "react"; const AuthUtils = { getAuthorizationFromAuthResult: ( authResult: AuthorizationResult, - previousAccount?: Account, + previousAccount?: Account ): Authorization => { const selectedAccount = previousAccount === undefined || !authResult.accounts.some( - ({ address }) => address === previousAccount.address, + ({ address }) => address === previousAccount.address ) ? AuthUtils.getAccountFromAuthorizedAccount(authResult.accounts[0]) : previousAccount; return { accounts: authResult.accounts.map( - AuthUtils.getAccountFromAuthorizedAccount, + AuthUtils.getAccountFromAuthorizedAccount ), authToken: authResult.auth_token, selectedAccount, @@ -624,7 +627,7 @@ const AuthUtils = { }, getAccountFromAuthorizedAccount: ( - authAccount: AuthorizedAccount, + authAccount: AuthorizedAccount ): Account => ({ ...authAccount, publicKey: new PublicKey(toUint8Array(authAccount.address)), @@ -676,19 +679,19 @@ type AuthProviderProps = { function AuthorizationProvider({ children, cluster }: AuthProviderProps) { const [authorization, setAuthorization] = useState( - null, + null ); const handleAuthorizationResult = useCallback( async (authResult: AuthorizationResult): Promise => { const nextAuthorization = AuthUtils.getAuthorizationFromAuthResult( authResult, - authorization?.selectedAccount, + authorization?.selectedAccount ); setAuthorization(nextAuthorization); return nextAuthorization; }, - [authorization], + [authorization] ); const authorizeSession = useCallback( @@ -702,7 +705,7 @@ function AuthorizationProvider({ children, cluster }: AuthProviderProps) { return (await handleAuthorizationResult(authorizationResult)) .selectedAccount; }, - [authorization, cluster, handleAuthorizationResult], + [authorization, cluster, handleAuthorizationResult] ); const deauthorizeSession = useCallback( @@ -712,14 +715,14 @@ function AuthorizationProvider({ children, cluster }: AuthProviderProps) { setAuthorization(null); } }, - [authorization], + [authorization] ); const onChangeAccount = useCallback((nextAccount: Account) => { - setAuthorization(currentAuthorization => { + setAuthorization((currentAuthorization) => { if ( currentAuthorization?.accounts.some( - ({ address }) => address === nextAccount.address, + ({ address }) => address === nextAccount.address ) ) { return { ...currentAuthorization, selectedAccount: nextAccount }; @@ -736,7 +739,7 @@ function AuthorizationProvider({ children, cluster }: AuthProviderProps) { onChangeAccount, selectedAccount: authorization?.selectedAccount ?? null, }), - [authorization, authorizeSession, deauthorizeSession, onChangeAccount], + [authorization, authorizeSession, deauthorizeSession, onChangeAccount] ); return ( @@ -813,7 +816,7 @@ export function ProgramProvider({ children }: ProgramProviderProps) { const setup = useCallback(async () => { const programId = new PublicKey( - "ALeaCzuJpZpoCgTxMjJbNjREVqSwuvYFRZUfc151AKHU", + "ALeaCzuJpZpoCgTxMjJbNjREVqSwuvYFRZUfc151AKHU" ); // MockWallet is a placeholder wallet used for initializing the AnchorProvider. @@ -833,12 +836,12 @@ export function ProgramProvider({ children }: ProgramProviderProps) { const programInstance = new Program( IDL, programId, - provider, + provider ); const [counterProgramAddress] = PublicKey.findProgramAddressSync( [Buffer.from("counter")], - programId, + programId ); setProgram(programInstance); @@ -854,7 +857,7 @@ export function ProgramProvider({ children }: ProgramProviderProps) { program, counterAddress, }), - [program, counterAddress], + [program, counterAddress] ); return ( @@ -1011,13 +1014,13 @@ export function CounterView() { try { const data = program.coder.accounts.decode( "counter", - accountInfo.data, + accountInfo.data ); setCounter(data); } catch (e) { console.log("account decoding error: " + e); } - }, + } ); return () => { @@ -1049,10 +1052,11 @@ button will do the following in a new function `incrementCounter`: - Call `signAndSendTransactions` to have the wallet sign and send the transaction -The fake Solana wallet we use generates a new keypair every -time you restart the fake wallet app, requiring that we want to check for funds -and airdrop every time. This is for demo purposes only, you can't do this in -production. + + The fake Solana wallet we use generates a new keypair every time you restart + the fake wallet app, requiring that we want to check for funds and airdrop + every time. This is for demo purposes only, you can't do this in production. + Create the file `CounterButton.tsx` and paste in the following: @@ -1126,7 +1130,7 @@ export function CounterButton() { const balance = await connection.getBalance(authResult.publicKey); console.log( - `Wallet ${authResult.publicKey} has a balance of ${balance}`, + `Wallet ${authResult.publicKey} has a balance of ${balance}` ); // When on Devnet you may want to transfer SOL manually per session, due to Devnet's airdrop rate limit @@ -1134,7 +1138,7 @@ export function CounterButton() { if (balance < minBalance) { console.log( - `requesting airdrop for ${authResult.publicKey} on ${connection.rpcEndpoint}`, + `requesting airdrop for ${authResult.publicKey} on ${connection.rpcEndpoint}` ); await connection.requestAirdrop(authResult.publicKey, minBalance * 2); } @@ -1149,7 +1153,7 @@ export function CounterButton() { showToastOrAlert(`Transaction successful! ${signature}`); }) - .catch(e => { + .catch((e) => { console.log(e); showToastOrAlert(`Error: ${JSON.stringify(e)}`); }) @@ -1219,6 +1223,7 @@ code available on the [solution branch](https://github.com/solana-developers/react-native-counter). -If you’ve successfully completed the lab, push your code to GitHub and share -your feedback on this lesson through this [form](https://form.typeform.com/to/IPH0UGz7#answers-lesson=c15928ce-8302-4437-9b1b-9aa1d65af864) + If you’ve successfully completed the lab, push your code to GitHub and share + your feedback on this lesson through this + [form](https://form.typeform.com/to/IPH0UGz7#answers-lesson=c15928ce-8302-4437-9b1b-9aa1d65af864) diff --git a/content/courses/mobile/mwa-deep-dive.md b/content/courses/mobile/mwa-deep-dive.md index dc3363acf..0361d436b 100644 --- a/content/courses/mobile/mwa-deep-dive.md +++ b/content/courses/mobile/mwa-deep-dive.md @@ -6,8 +6,7 @@ objectives: - Connect to and sign transactions from a mobile wallet - Create a simple mobile wallet - Explain at a high level the interaction between `walletlib` and wallet apps -description: - "Initiate transactions on mobile wallets in your native mobile apps." +description: "Initiate transactions on mobile wallets in your native mobile apps." --- ## Summary @@ -346,14 +345,14 @@ function MWAApp() { const handleSessionEvent = useCallback( (sessionEvent: MWASessionEvent) => {}, - [], + [] ); useMobileWalletAdapterSession( "React Native Fake Wallet", config, handleRequest, - handleSessionEvent, + handleSessionEvent ); return I'm a wallet!; @@ -385,7 +384,7 @@ resolve function: ```ts export function resolve( request: SignAndSendTransactionsRequest, - response: SignAndSendTransactionsResponse, + response: SignAndSendTransactionsResponse ): void; ``` @@ -669,7 +668,7 @@ export function WalletProvider(props: WalletProviderProps) { keyPair = Keypair.generate(); await AsyncStorage.setItem( ASYNC_STORAGE_KEY, - JSON.stringify(encodeKeypair(keyPair)), + JSON.stringify(encodeKeypair(keyPair)) ); } setKeyPair(keyPair); @@ -684,7 +683,7 @@ export function WalletProvider(props: WalletProviderProps) { const connection = useMemo( () => new Connection(rpcUrl ?? "https://api.devnet.solana.com"), - [rpcUrl], + [rpcUrl] ); const value = { @@ -751,7 +750,7 @@ function MainScreen() { try { const signature = await connection.requestAirdrop( wallet.publicKey, - LAMPORTS_PER_SOL, + LAMPORTS_PER_SOL ); await connection.confirmTransaction(signature, "max"); await updateBalance(); @@ -929,14 +928,14 @@ const handleRequest = useCallback((request: MWARequest) => {}, []); const handleSessionEvent = useCallback( (sessionEvent: MWASessionEvent) => {}, - [], + [] ); useMobileWalletAdapterSession( "React Native Fake Wallet", config, handleRequest, - handleSessionEvent, + handleSessionEvent ); ``` @@ -980,14 +979,14 @@ function MWAApp() { const handleSessionEvent = useCallback( (sessionEvent: MWASessionEvent) => {}, - [], + [] ); useMobileWalletAdapterSession( "React Native Fake Wallet", config, handleRequest, - handleSessionEvent, + handleSessionEvent ); return ( @@ -1100,7 +1099,7 @@ const styles = StyleSheet.create({ function MWAApp() { const [currentRequest, setCurrentRequest] = useState(null); const [currentSession, setCurrentSession] = useState( - null, + null ); // ------------------- FUNCTIONS -------------------- @@ -1139,7 +1138,7 @@ function MWAApp() { } } return true; // Prevents default back button behavior - }, + } ); return () => backHandler.remove(); }, [currentRequest]); @@ -1178,7 +1177,7 @@ function MWAApp() { "React Native Fake Wallet", config, handleRequest, - handleSessionEvent, + handleSessionEvent ); // ------------------- RENDER -------------------- @@ -1352,7 +1351,7 @@ will fulfill the `SignAndSendTransactionsRequest` and ```ts export function resolve( request: SignAndSendTransactionsRequest, - response: SignAndSendTransactionsResponse, + response: SignAndSendTransactionsResponse ): void; ``` @@ -1425,9 +1424,9 @@ import { decode } from "bs58"; export async function sendSignedTransactions( signedTransactions: Array, minContextSlot: number | undefined, - connection: Connection, + connection: Connection ): Promise<[boolean[], Uint8Array[]]> { - const valid = signedTransactions.map(_ => true); + const valid = signedTransactions.map((_) => true); const signatures: (Uint8Array | null)[] = await Promise.all( signedTransactions.map(async (byteArray, index) => { try { @@ -1443,7 +1442,7 @@ export async function sendSignedTransactions( const response = await connection.confirmTransaction( signature, - "confirmed", + "confirmed" ); return decode(signature); @@ -1452,7 +1451,7 @@ export async function sendSignedTransactions( valid[index] = false; return null; } - }), + }) ); return [valid, signatures as Uint8Array[]]; @@ -1460,9 +1459,9 @@ export async function sendSignedTransactions( export function signTransactionPayloads( wallet: Keypair, - payloads: Uint8Array[], + payloads: Uint8Array[] ): [boolean[], Uint8Array[]] { - const valid = payloads.map(_ => true); + const valid = payloads.map((_) => true); const signedPayloads = payloads.map((payload, index) => { try { @@ -1491,7 +1490,7 @@ export interface SignAndSendTransactionScreenProps { } function SignAndSendTransactionScreen( - props: SignAndSendTransactionScreenProps, + props: SignAndSendTransactionScreenProps ) { const { request } = props; const { wallet, connection } = useWallet(); @@ -1504,11 +1503,11 @@ function SignAndSendTransactionScreen( const signAndSendTransaction = async ( wallet: Keypair, connection: Connection, - request: SignAndSendTransactionsRequest, + request: SignAndSendTransactionsRequest ) => { const [validSignatures, signedTransactions] = signTransactionPayloads( wallet, - request.payloads, + request.payloads ); if (validSignatures.includes(false)) { @@ -1523,7 +1522,7 @@ function SignAndSendTransactionScreen( await sendSignedTransactions( signedTransactions, request.minContextSlot ? request.minContextSlot : undefined, - connection, + connection ); if (validTransactions.includes(false)) { @@ -1628,6 +1627,6 @@ out the [solution code on the repo](https://github.com/solana-developers/react-native-fake-solana-wallet). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5a3d0f62-c5fc-4e03-b8a3-323c2c7b8f4f)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5a3d0f62-c5fc-4e03-b8a3-323c2c7b8f4f)! diff --git a/content/courses/mobile/solana-mobile-dapps-with-expo.md b/content/courses/mobile/solana-mobile-dapps-with-expo.md index 27006dd07..9b4697352 100644 --- a/content/courses/mobile/solana-mobile-dapps-with-expo.md +++ b/content/courses/mobile/solana-mobile-dapps-with-expo.md @@ -287,12 +287,16 @@ or physical device. There are also more details about this setup in the [Introduction to Solana Mobile lesson](/content/courses/mobile/intro-to-solana-mobile.md#0-prerequisites) -Even though we are using Expo, you'll need to follow the -React Native CLI guide for initial setup. + + Even though we are using Expo, you'll need to follow the React Native CLI + guide for initial setup. + -If you are running an emulator, it is highly recommend to -use a newer phone version to emulate along with providing several GB of RAM for -it to run. We use 5GB of ram on our side. + + If you are running an emulator, it is highly recommend to use a newer phone + version to emulate along with providing several GB of RAM for it to run. We + use 5GB of ram on our side. + #### 1. Sign up for Expo EAS CLI @@ -669,7 +673,7 @@ export const UmiProvider = ({ .use(mplCandyMachine()); if (selectedAccount === null) { const noopSigner = createNoopSigner( - publicKey("11111111111111111111111111111111"), + publicKey("11111111111111111111111111111111") ); umi.use(signerIdentity(noopSigner)); } else { @@ -684,7 +688,7 @@ export function useUmi(): Umi { if (!umi) { throw new Error( "Umi context was not initialized. " + - "Did you forget to wrap your app with ?", + "Did you forget to wrap your app with ?" ); } return umi; @@ -971,7 +975,7 @@ through the code for each of them and then show you the entire file at the end: if (isLoading) return; setIsLoading(true); - transact(async wallet => { + transact(async (wallet) => { const auth = await authorizeSession(wallet); setAccount(auth); }).finally(() => { @@ -1033,7 +1037,7 @@ async function uploadImageFromURI(fileUri: string) { const response = await fetch( "https://api.pinata.cloud/pinning/pinFileToIPFS", - options, + options ); const responseJson = await response.json(); return responseJson; @@ -1047,7 +1051,7 @@ async function uploadImageFromURI(fileUri: string) { async function uploadMetadataJson( name: string, description: string, - imageCID: string, + imageCID: string ) { const randomFileName = `metadata_${Date.now()}_${Math.floor(Math.random() * 10000)}.json`; const data = JSON.stringify({ @@ -1070,7 +1074,7 @@ async function uploadMetadataJson( Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_PINATA_JWT}`, }, body: data, - }, + } ); const responseBody = await response.json(); @@ -1086,16 +1090,16 @@ const uploadMetadata = useCallback( async ( name: string, description: string, - imageCID: string, + imageCID: string ): Promise => { const uploadResponse = await uploadMetadataJson( name, description, - imageCID, + imageCID ); return uploadResponse.IpfsHash; }, - [], + [] ); ``` @@ -1128,7 +1132,7 @@ const createNFT = useCallback( setIsLoading(false); } }, - [umi, account, isLoading, uploadImage, uploadMetadata], + [umi, account, isLoading, uploadImage, uploadMetadata] ); ``` @@ -1198,7 +1202,7 @@ export function NFTProvider(props: NFTProviderProps) { if (isLoading) return; setIsLoading(true); - transact(async wallet => { + transact(async (wallet) => { const auth = await authorizeSession(wallet); setAccount(auth); }).finally(() => { @@ -1230,7 +1234,7 @@ export function NFTProvider(props: NFTProviderProps) { const response = await fetch( "https://api.pinata.cloud/pinning/pinFileToIPFS", - options, + options ); const responseJson = await response.json(); console.log(responseJson.IpfsHash); @@ -1246,7 +1250,7 @@ export function NFTProvider(props: NFTProviderProps) { async function uploadMetadataJson( name = "Solanify", description = "A truly sweet NFT of your day.", - imageCID = "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4", + imageCID = "bafkreih5aznjvttude6c3wbvqeebb6rlx5wkbzyppv7garjiubll2ceym4" ) { const randomFileName = `metadata_${Date.now()}_${Math.floor(Math.random() * 10000)}.json`; const data = JSON.stringify({ @@ -1269,7 +1273,7 @@ export function NFTProvider(props: NFTProviderProps) { Authorization: `Bearer ${process.env.EXPO_PUBLIC_NFT_PINATA_JWT}`, }, body: data, - }, + } ); const responseBody = await response.json(); @@ -1299,16 +1303,16 @@ export function NFTProvider(props: NFTProviderProps) { async ( name: string, description: string, - imageCID: string, + imageCID: string ): Promise => { const uploadResponse = await uploadMetadataJson( name, description, - imageCID, + imageCID ); return uploadResponse.IpfsHash; }, - [], + [] ); const createNFT = useCallback( @@ -1335,7 +1339,7 @@ export function NFTProvider(props: NFTProviderProps) { setIsLoading(false); } }, - [umi, account, isLoading, uploadImage, uploadMetadata], + [umi, account, isLoading, uploadImage, uploadMetadata] ); const publicKey = useMemo( @@ -1343,7 +1347,7 @@ export function NFTProvider(props: NFTProviderProps) { account?.publicKey ? fromWeb3JsPublicKey(account.publicKey as solanaPublicKey) : null, - [account], + [account] ); const state: NFTContextState = { @@ -1406,7 +1410,7 @@ const mintNFT = async () => { createNFT( formatDate(todaysDate), `${todaysDate.getTime()}`, - result.assets[0].uri, + result.assets[0].uri ); } }; @@ -1518,7 +1522,7 @@ export function MainScreen() { const loadSnapshots = async () => { const loadedSnapshots = await Promise.all( - loadedNFTs.map(async loadedNft => { + loadedNFTs.map(async (loadedNft) => { if (!loadedNft.metadata.name) return null; if (!loadedNft.metadata.uri) return null; @@ -1535,12 +1539,12 @@ export function MainScreen() { uri: ipfsPrefix + imageCID, date: new Date(unixTime), } as NFTSnapshot; - }), + }) ); // Filter out null values const cleanedSnapshots = loadedSnapshots.filter( - (snapshot): snapshot is NFTSnapshot => snapshot !== null, + (snapshot): snapshot is NFTSnapshot => snapshot !== null ); // Sort by date @@ -1597,7 +1601,7 @@ export function MainScreen() { createNFT( formatDate(todaysDate), `${todaysDate.getTime()}`, - result.assets[0].uri, + result.assets[0].uri ); } }; @@ -1682,6 +1686,6 @@ welcome to choose your own, or you can select from the following ideas: from `expo-sensors` -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=19cf8d3a-89a0-465e-95da-908cf8f45409)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=19cf8d3a-89a0-465e-95da-908cf8f45409)! diff --git a/content/courses/native-onchain-development/cross-program-invocations.md b/content/courses/native-onchain-development/cross-program-invocations.md index ec5190f71..24f2df337 100644 --- a/content/courses/native-onchain-development/cross-program-invocations.md +++ b/content/courses/native-onchain-development/cross-program-invocations.md @@ -325,6 +325,7 @@ mutable. To see this in action, view this [transaction in the explorer](https://explorer.solana.com/tx/ExB9YQJiSzTZDBqx4itPaa4TpT8VK4Adk7GU5pSoGEzNz9fa7PPZsUxssHGrBbJRnCvhoKgLCWnAycFB7VYDbBg?cluster=devnet). + ### Why CPIs matter? @@ -847,6 +848,7 @@ After receiving the airdrop, attempt the deployment again. Ensure your Solana CLI is configured for the correct network (`Localnet`, `devnet`, `testnet`, or `mainnet-beta`) before deploying or requesting airdrops. + If you encounter the following error during program deployment, it indicates @@ -927,7 +929,7 @@ import { } from "@solana-developers/helpers"; const PROGRAM_ID = new PublicKey( - "AzKatnACpNwQxWRs2YyPovsGhgsYVBiTmC3TL4t72eJW", + "AzKatnACpNwQxWRs2YyPovsGhgsYVBiTmC3TL4t72eJW" ); const LOCALHOST_RPC_URL = "http://localhost:8899"; @@ -941,17 +943,17 @@ await airdropIfRequired( connection, userKeypair.publicKey, AIRDROP_AMOUNT, - MINIMUM_BALANCE_FOR_RENT_EXEMPTION, + MINIMUM_BALANCE_FOR_RENT_EXEMPTION ); const [tokenMintPDA] = PublicKey.findProgramAddressSync( [Buffer.from("token_mint")], - PROGRAM_ID, + PROGRAM_ID ); const [tokenAuthPDA] = PublicKey.findProgramAddressSync( [Buffer.from("token_auth")], - PROGRAM_ID, + PROGRAM_ID ); const INITIALIZE_MINT_INSTRUCTION = 3; @@ -975,7 +977,7 @@ try { const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [userKeypair], + [userKeypair] ); const explorerLink = getExplorerLink("transaction", transactionSignature); @@ -983,7 +985,7 @@ try { } catch (error) { if (error instanceof Error) { throw new Error( - `Failed to initialize program token mint: ${error.message}`, + `Failed to initialize program token mint: ${error.message}` ); } else { throw new Error("An unknown error occurred"); @@ -1033,4 +1035,5 @@ something like it on your own! Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=ade5d386-809f-42c2-80eb-a6c04c471f53)! + diff --git a/content/courses/native-onchain-development/deserialize-custom-data-frontend.md b/content/courses/native-onchain-development/deserialize-custom-data-frontend.md index 2b18a47dc..6e9dd9d46 100644 --- a/content/courses/native-onchain-development/deserialize-custom-data-frontend.md +++ b/content/courses/native-onchain-development/deserialize-custom-data-frontend.md @@ -5,8 +5,7 @@ objectives: - Derive PDAs given specific seeds - Fetch a program's accounts - Use Borsh to deserialize custom data -description: - Deserialize instructions in JS/TS clients to send to your native program. +description: Deserialize instructions in JS/TS clients to send to your native program. --- ## Summary @@ -67,7 +66,7 @@ the program ID and this same seed. ```typescript const [pda, bump] = await findProgramAddress( Buffer.from("GLOBAL_STATE"), - programId, + programId ); ``` @@ -84,7 +83,7 @@ finding the address using the program ID and the user's public key. import { PublicKey } from "@solana/web3.js"; const [pda, bump] = await PublicKey.findProgramAddressSync( [publicKey.toBuffer()], - programId, + programId ); ``` @@ -100,7 +99,7 @@ note's title. ```typescript const [pda, bump] = await PublicKey.findProgramAddressSync( [publicKey.toBuffer(), Buffer.from("Shopping list")], - programId, + programId ); ``` @@ -394,6 +393,6 @@ As always, get creative with these challenges and take them beyond the instructions if you want! -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=9cb89e09-2c97-4185-93b0-c89f7aca7677)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=9cb89e09-2c97-4185-93b0-c89f7aca7677)! diff --git a/content/courses/native-onchain-development/deserialize-instruction-data.md b/content/courses/native-onchain-development/deserialize-instruction-data.md index ddd5aa918..7a03701d2 100644 --- a/content/courses/native-onchain-development/deserialize-instruction-data.md +++ b/content/courses/native-onchain-development/deserialize-instruction-data.md @@ -8,8 +8,7 @@ objectives: - Deserialize instruction data into native Rust data types - Execute different program logic for different types of instructions - Explain the structure of a smart contract on Solana -description: - "Learn how native programs distinguish instructions for different functions." +description: "Learn how native programs distinguish instructions for different functions." --- ## Summary @@ -646,4 +645,5 @@ reference the [solution code](https://beta.solpg.io/62b0ce53f6273245aca4f5b0). Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=74a157dc-01a7-4b08-9a5f-27aa51a4346c)! + diff --git a/content/courses/native-onchain-development/hello-world-program.md b/content/courses/native-onchain-development/hello-world-program.md index d386491c6..6df6b5f8d 100644 --- a/content/courses/native-onchain-development/hello-world-program.md +++ b/content/courses/native-onchain-development/hello-world-program.md @@ -7,8 +7,7 @@ objectives: - Explain the entry point to a Solana program - Build and deploy a basic Solana program - Submit a transaction to invoke our “Hello, world!” program -description: - "Create an onchain program for Solana using native Rust, without Anchor." +description: "Create an onchain program for Solana using native Rust, without Anchor." --- ## Summary @@ -24,6 +23,7 @@ description: The following guide assumes you are familiar with Solana program basics. If not, check out [Introduction to Onchain Programming](/content/courses/onchain-development/intro-to-onchain.md). + This lesson will introduce you to writing and deploying a Solana program using @@ -387,4 +387,5 @@ basic instructions if you want — most importantly, have fun! Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5b56c69c-1490-46e4-850f-a7e37bbd79c2)! + diff --git a/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md b/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md index 2d9a4137d..73a333914 100644 --- a/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md +++ b/content/courses/native-onchain-development/paging-ordering-filtering-data-frontend.md @@ -57,7 +57,7 @@ const accountsWithoutData = await connection.getProgramAccounts(programId, { dataSlice: { offset: 0, length: 0 }, }); -const accountKeys = accountsWithoutData.map(account => account.pubkey); +const accountKeys = accountsWithoutData.map((account) => account.pubkey); ``` With this list of keys, you can then fetch account data in “pages” using the @@ -66,7 +66,7 @@ With this list of keys, you can then fetch account data in “pages” using the ```tsx const paginatedKeys = accountKeys.slice(0, 10); const accountInfos = await connection.getMultipleAccountsInfo(paginatedKeys); -const deserializedObjects = accountInfos.map(accountInfo => { +const deserializedObjects = accountInfos.map((accountInfo) => { // put logic to deserialize accountInfo.data here }); ``` @@ -144,11 +144,11 @@ accounts.sort((a, b) => { const dataA = a.account.data.subarray( STRING_LENGTH_SPACE, - STRING_LENGTH_SPACE + lengthA, + STRING_LENGTH_SPACE + lengthA ); const dataB = b.account.data.subarray( STRING_LENGTH_SPACE, - STRING_LENGTH_SPACE + lengthB, + STRING_LENGTH_SPACE + lengthB ); return dataA.compare(dataB); @@ -158,7 +158,7 @@ accounts.sort((a, b) => { } }); -const accountKeys = accounts.map(account => account.pubkey); +const accountKeys = accounts.map((account) => account.pubkey); ``` Note that in the snippet above we don't compare the data as given. This is @@ -200,7 +200,7 @@ const DATA_LENGTH = 18; // Retrieve 18 bytes of data, including the part of the async function fetchMatchingContactAccounts( connection: Connection, - search: string, + search: string ): Promise | null>> { let filters: Array<{ memcmp: { offset: number; bytes: string } }> = []; @@ -221,13 +221,13 @@ async function fetchMatchingContactAccounts( { dataSlice: { offset: DATA_OFFSET, length: DATA_LENGTH }, // Only retrieve the portion of data relevant to the search. filters, - }, + } ); // Make a mutable copy of the readonly array const accounts: Array = Array.from(readonlyAccounts); - return accounts.map(account => account.account); // Return the account data. + return accounts.map((account) => account.account); // Return the account data. } ``` @@ -301,7 +301,7 @@ export class MovieCoordinator { static async fetchPage( connection: Connection, page: number, - perPage: number, + perPage: number ): Promise> {} } ``` @@ -645,7 +645,7 @@ useEffect(() => { page, 5, search, - search !== "", + search !== "" ); setMovies(movies); } catch (error) { @@ -665,7 +665,7 @@ return ( setSearch(e.target.value)} + onChange={(e) => setSearch(e.target.value)} placeholder="Search" /> ... @@ -702,6 +702,6 @@ As always, get creative with these challenges and take them beyond the instructions if you want! -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=9342ad0a-1741-41a5-9f68-662642c8ec93)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=9342ad0a-1741-41a5-9f68-662642c8ec93)! diff --git a/content/courses/native-onchain-development/program-derived-addresses.md b/content/courses/native-onchain-development/program-derived-addresses.md index 128b53fed..66a1b32aa 100644 --- a/content/courses/native-onchain-development/program-derived-addresses.md +++ b/content/courses/native-onchain-development/program-derived-addresses.md @@ -982,5 +982,6 @@ Note that the solution code is on the `solution-add-replies` branch and that your code may look slightly different. -Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=89d367b4-5102-4237-a7f4-4f96050fe57e)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=89d367b4-5102-4237-a7f4-4f96050fe57e)! diff --git a/content/courses/native-onchain-development/program-security.md b/content/courses/native-onchain-development/program-security.md index ba3c931d7..eb4873cf7 100644 --- a/content/courses/native-onchain-development/program-security.md +++ b/content/courses/native-onchain-development/program-security.md @@ -950,4 +950,5 @@ on the checks you implement and the errors you write. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=3dfb98cc-7ba9-463d-8065-7bdb1c841d43)! + diff --git a/content/courses/native-onchain-development/program-state-management.md b/content/courses/native-onchain-development/program-state-management.md index f8dd25ff8..5800e26a1 100644 --- a/content/courses/native-onchain-development/program-state-management.md +++ b/content/courses/native-onchain-development/program-state-management.md @@ -9,8 +9,7 @@ objectives: - Use a Cross Program Invocation (CPI) to initialize an account with a PDA as the address of the new account - Explain how to update the data stored on a new account -description: - "Learn how programs store data using Solana's built-in key-value store." +description: "Learn how programs store data using Solana's built-in key-value store." --- ## Summary @@ -593,4 +592,5 @@ reference the [solution code](https://beta.solpg.io/62b11ce4f6273245aca4f5b2). Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=8320fc87-2b6d-4b3a-8b1a-54b55afed781)! + diff --git a/content/courses/native-onchain-development/serialize-instruction-data-frontend.md b/content/courses/native-onchain-development/serialize-instruction-data-frontend.md index 1387e4334..0f4c3ad4f 100644 --- a/content/courses/native-onchain-development/serialize-instruction-data-frontend.md +++ b/content/courses/native-onchain-development/serialize-instruction-data-frontend.md @@ -33,12 +33,13 @@ description: How to deserialize data fetched from Solana accounts. ### Transactions -This course requires completing -[Introduction to Solana](/content/courses/intro-to-solana) or equivalent -knowledge. It's also aimed at advanced developers that prefer more control over -the ease of use and safe defaults Anchor provides. If you're new to developing -onchain programs you may prefer -[Anchor](/content/courses/onchain-development) + + This course requires completing [Introduction to + Solana](/content/courses/intro-to-solana) or equivalent knowledge. It's also + aimed at advanced developers that prefer more control over the ease of use and + safe defaults Anchor provides. If you're new to developing onchain programs + you may prefer [Anchor](/content/courses/onchain-development) + In [Introduction to Solana](/content/courses/intro-to-solana) we learned how to create transactions with instructions for common Solana programs. @@ -192,7 +193,7 @@ const equipPlayerSchema = borsh.struct([ const buffer = Buffer.alloc(1000); equipPlayerSchema.encode( { variant: 2, playerId: 1435, itemId: 737498 }, - buffer, + buffer ); const instructionBuffer = buffer.subarray(0, equipPlayerSchema.getSpan(buffer)); @@ -229,7 +230,7 @@ const equipPlayerSchema = borsh.struct([ const buffer = Buffer.alloc(1000); equipPlayerSchema.encode( { variant: 2, playerId: 1435, itemId: 737498 }, - buffer, + buffer ); const instructionBuffer = buffer.subarray(0, equipPlayerSchema.getSpan(buffer)); @@ -266,7 +267,7 @@ try { const transactionId = await sendAndConfirmTransaction( connection, transaction, - [player], + [player] ); const explorerLink = getExplorerLink("transaction", transactionId, "devnet"); console.log(`Transaction submitted: ${explorerLink}`); @@ -393,7 +394,7 @@ export class Movie { } if (description.length > DESCRIPTION_SIZE) { throw new Error( - `Description cannot exceed ${DESCRIPTION_SIZE} characters.`, + `Description cannot exceed ${DESCRIPTION_SIZE} characters.` ); } @@ -535,7 +536,7 @@ stored: ```typescript const [pda] = await PublicKey.findProgramAddressSync( [publicKey.toBuffer(), Buffer.from(movie.title)], - new PublicKey(MOVIE_REVIEW_PROGRAM_ID), + new PublicKey(MOVIE_REVIEW_PROGRAM_ID) ); ``` @@ -557,7 +558,7 @@ const handleTransactionSubmit = async (movie: Movie) => { const [pda] = await PublicKey.findProgramAddressSync( [publicKey.toBuffer(), new TextEncoder().encode(movie.title)], - new PublicKey(MOVIE_REVIEW_PROGRAM_ID), + new PublicKey(MOVIE_REVIEW_PROGRAM_ID) ); const instruction = new TransactionInstruction({ @@ -589,7 +590,7 @@ const handleTransactionSubmit = async (movie: Movie) => { const explorerLink = getExplorerLink( "transaction", transactionId, - "devnet", + "devnet" ); console.log(`Transaction submitted: ${explorerLink}`); } catch (error) { @@ -640,6 +641,6 @@ Feel free to get creative with these challenges and take them even further. The instructions aren't here to hold you back! -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=6cb40094-3def-4b66-8a72-dd5f00298f61)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=6cb40094-3def-4b66-8a72-dd5f00298f61)! diff --git a/content/courses/offline-transactions/durable-nonces.md b/content/courses/offline-transactions/durable-nonces.md index 7074a1546..16976b71b 100644 --- a/content/courses/offline-transactions/durable-nonces.md +++ b/content/courses/offline-transactions/durable-nonces.md @@ -251,7 +251,7 @@ const tx = new Transaction().add( SystemProgram.nonceInitialize({ noncePubkey: nonceKeypair.publicKey, authorizedPubkey: nonceAuthority.publicKey, - }), + }) ); // Send the transaction @@ -376,7 +376,7 @@ durableTx.add( SystemProgram.nonceAdvance({ authorizedPubkey: nonceAuthority.publicKey, noncePubkey: nonceKeypair.publicKey, - }), + }) ); // Add any instructions you want to the transaction in this case we are just doing a transfer @@ -385,7 +385,7 @@ durableTx.add( fromPubkey: payer.publicKey, toPubkey: recipient.publicKey, lamports: 0.1 * LAMPORTS_PER_SOL, - }), + }) ); // sign the tx with the nonce authority's keypair @@ -394,7 +394,7 @@ durableTx.sign(payer, nonceAuthority); // once you have the signed tx, you can serialize it and store it in a database, or send it to another device. // You can submit it at a later point. const serializedTx = base58.encode( - durableTx.serialize({ requireAllSignatures: false }), + durableTx.serialize({ requireAllSignatures: false }) ); ``` @@ -502,7 +502,7 @@ async function createNonceAccount( connection: Connection, payer: Keypair, nonceKeypair: Keypair, - authority: PublicKey, + authority: PublicKey ) { const rentExemptBalance = await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH); @@ -520,7 +520,7 @@ async function createNonceAccount( SystemProgram.nonceInitialize({ noncePubkey: nonceKeypair.publicKey, authorizedPubkey: authority, - }), + }) ); const sig = await sendAndConfirmTransaction(connection, tx, [ @@ -574,7 +574,7 @@ it("Creates a durable transaction and submits it", async () => { connection, payer, nonceKeypair, - payer.publicKey, + payer.publicKey ); // Step 1.3: Create a new transaction @@ -589,7 +589,7 @@ it("Creates a durable transaction and submits it", async () => { SystemProgram.nonceAdvance({ authorizedPubkey: payer.publicKey, noncePubkey: nonceKeypair.publicKey, - }), + }) ); // Step 1.6: Add the transfer instruction @@ -598,7 +598,7 @@ it("Creates a durable transaction and submits it", async () => { fromPubkey: payer.publicKey, toPubkey: recipient.publicKey, lamports: TRANSFER_AMOUNT, - }), + }) ); // Step 1.7: Sign the transaction with the payer's keypair @@ -657,7 +657,7 @@ it("Fails if the nonce has advanced", async () => { connection, payer, nonceKeypair, - nonceAuthority.publicKey, + nonceAuthority.publicKey ); const durableTransaction = new Transaction(); @@ -669,7 +669,7 @@ it("Fails if the nonce has advanced", async () => { SystemProgram.nonceAdvance({ authorizedPubkey: nonceAuthority.publicKey, noncePubkey: nonceKeypair.publicKey, - }), + }) ); // Add a transfer instruction @@ -678,7 +678,7 @@ it("Fails if the nonce has advanced", async () => { fromPubkey: payer.publicKey, toPubkey: recipient.publicKey, lamports: TRANSFER_AMOUNT, - }), + }) ); // Sign the transaction with both the payer and nonce authority's keypairs @@ -696,26 +696,26 @@ it("Fails if the nonce has advanced", async () => { SystemProgram.nonceAdvance({ noncePubkey: nonceKeypair.publicKey, authorizedPubkey: nonceAuthority.publicKey, - }), + }) ), - [payer, nonceAuthority], + [payer, nonceAuthority] ); // Using getExplorerLink from solana-helpers console.log( "Nonce Advance Signature:", - getExplorerLink("tx", nonceAdvanceSignature, "localnet"), + getExplorerLink("tx", nonceAdvanceSignature, "localnet") ); // Deserialize the transaction const deserializedTransaction = Buffer.from( serializedTransaction, - "base64", + "base64" ); // Step 3: Try to submit the transaction, expecting it to fail due to nonce advancement await assert.rejects( - sendAndConfirmRawTransaction(connection, deserializedTransaction), + sendAndConfirmRawTransaction(connection, deserializedTransaction) ); } catch (error) { console.error("Test failed:", error); @@ -753,7 +753,7 @@ it("Advances the nonce account even if the transaction fails", async () => { connection, payer, nonceKeypair, - nonceAuthority.publicKey, + nonceAuthority.publicKey ); const nonceBeforeAdvancing = nonceAccount.nonce; @@ -765,7 +765,7 @@ it("Advances the nonce account even if the transaction fails", async () => { // Ensure the balance is less than the transfer amount (50 SOL) assert( balance < TRANSFER_AMOUNT * LAMPORTS_PER_SOL, - `Balance too high! Adjust 'TRANSFER_AMOUNT' to be higher than the current balance of ${balance / LAMPORTS_PER_SOL} SOL.`, + `Balance too high! Adjust 'TRANSFER_AMOUNT' to be higher than the current balance of ${balance / LAMPORTS_PER_SOL} SOL.` ); // Step 3: Create a durable transaction that will fail @@ -780,7 +780,7 @@ it("Advances the nonce account even if the transaction fails", async () => { SystemProgram.nonceAdvance({ authorizedPubkey: nonceAuthority.publicKey, noncePubkey: nonceKeypair.publicKey, - }), + }) ); // Step 5: Add a transfer instruction that will fail (since the payer has insufficient funds) @@ -789,7 +789,7 @@ it("Advances the nonce account even if the transaction fails", async () => { fromPubkey: payer.publicKey, toPubkey: recipient.publicKey, lamports: TRANSFER_AMOUNT * LAMPORTS_PER_SOL, - }), + }) ); // Step 6: Sign the transaction with both the payer and nonce authority @@ -797,7 +797,7 @@ it("Advances the nonce account even if the transaction fails", async () => { // Serialize the transaction and store or send it (if needed) const serializedTx = base58.encode( - durableTx.serialize({ requireAllSignatures: false }), + durableTx.serialize({ requireAllSignatures: false }) ); const tx = base58.decode(serializedTx); @@ -806,15 +806,15 @@ it("Advances the nonce account even if the transaction fails", async () => { await assert.rejects( sendAndConfirmRawTransaction(connection, tx as Buffer, { skipPreflight: true, // Ensure the transaction reaches the network despite the expected failure - }), + }) ); // Step 8: Fetch the nonce account again after the failed transaction const nonceAccountAfterAdvancing = await connection.getAccountInfo( - nonceKeypair.publicKey, + nonceKeypair.publicKey ); const nonceAfterAdvancing = NonceAccount.fromAccountData( - nonceAccountAfterAdvancing!.data, + nonceAccountAfterAdvancing!.data ).nonce; // Step 9: Verify that the nonce has advanced even though the transaction failed @@ -858,7 +858,7 @@ it("The nonce account will not advance if the transaction fails because the nonc connection, payer, nonceKeypair, - nonceAuthority.publicKey, + nonceAuthority.publicKey ); const nonceBeforeAdvancing = nonceAccount.nonce; @@ -876,7 +876,7 @@ it("The nonce account will not advance if the transaction fails because the nonc SystemProgram.nonceAdvance({ authorizedPubkey: nonceAuthority.publicKey, noncePubkey: nonceKeypair.publicKey, - }), + }) ); // Add transfer instruction @@ -885,7 +885,7 @@ it("The nonce account will not advance if the transaction fails because the nonc fromPubkey: payer.publicKey, toPubkey: recipient.publicKey, lamports: 0.1 * LAMPORTS_PER_SOL, - }), + }) ); // Sign the transaction only with the payer, omitting nonce authority signature (this will cause the failure) @@ -893,7 +893,7 @@ it("The nonce account will not advance if the transaction fails because the nonc // Step 5: Serialize the transaction const serializedTx = base58.encode( - durableTx.serialize({ requireAllSignatures: false }), + durableTx.serialize({ requireAllSignatures: false }) ); // Decode the serialized transaction @@ -903,15 +903,15 @@ it("The nonce account will not advance if the transaction fails because the nonc await assert.rejects( sendAndConfirmRawTransaction(connection, tx as Buffer, { skipPreflight: true, // Ensure the transaction reaches the network despite the expected failure - }), + }) ); // Step 7: Fetch the nonce account again after the failed transaction const nonceAccountAfterAdvancing = await connection.getAccountInfo( - nonceKeypair.publicKey, + nonceKeypair.publicKey ); const nonceAfterAdvancing = NonceAccount.fromAccountData( - nonceAccountAfterAdvancing!.data, + nonceAccountAfterAdvancing!.data ).nonce; // Step 8: Verify that the nonce has not advanced, as the failure was due to the nonce advance instruction @@ -942,7 +942,7 @@ it("Submits after changing the nonce authority to an already signed address", as connection, payer, nonceKeypair, - nonceAuthority.publicKey, + nonceAuthority.publicKey ); const nonceBeforeAdvancing = nonceAccount.nonce; @@ -960,7 +960,7 @@ it("Submits after changing the nonce authority to an already signed address", as SystemProgram.nonceAdvance({ authorizedPubkey: payer.publicKey, // should be nonce authority, will fail noncePubkey: nonceKeypair.publicKey, - }), + }) ); // Add a transfer instruction @@ -969,7 +969,7 @@ it("Submits after changing the nonce authority to an already signed address", as fromPubkey: payer.publicKey, toPubkey: recipient.publicKey, lamports: TRANSACTION_LAMPORTS, - }), + }) ); // Sign the transaction with the payer @@ -977,7 +977,7 @@ it("Submits after changing the nonce authority to an already signed address", as // Step 5: Serialize and store the transaction const serializedTransaction = base58.encode( - durableTransaction.serialize({ requireAllSignatures: false }), + durableTransaction.serialize({ requireAllSignatures: false }) ); const deserializedTx = base58.decode(serializedTransaction); @@ -986,15 +986,15 @@ it("Submits after changing the nonce authority to an already signed address", as await assert.rejects( sendAndConfirmRawTransaction(connection, deserializedTx as Buffer, { skipPreflight: true, // Ensures the transaction hits the network despite failure - }), + }) ); // Step 7: Verify that the nonce did not advance after the failed transaction const nonceAccountAfterAdvancing = await connection.getAccountInfo( - nonceKeypair.publicKey, + nonceKeypair.publicKey ); const nonceAfterAdvancing = NonceAccount.fromAccountData( - nonceAccountAfterAdvancing!.data, + nonceAccountAfterAdvancing!.data ).nonce; assert.equal(nonceBeforeAdvancing, nonceAfterAdvancing); @@ -1006,14 +1006,14 @@ it("Submits after changing the nonce authority to an already signed address", as noncePubkey: nonceKeypair.publicKey, authorizedPubkey: nonceAuthority.publicKey, newAuthorizedPubkey: payer.publicKey, // changing authority to payer - }), + }) ), - [payer, nonceAuthority], + [payer, nonceAuthority] ); console.log( "Nonce Auth Signature:", - getExplorerLink("tx", nonceAuthSignature, "localnet"), + getExplorerLink("tx", nonceAuthSignature, "localnet") ); // Step 9: Submit the transaction again, which should now succeed @@ -1022,12 +1022,12 @@ it("Submits after changing the nonce authority to an already signed address", as deserializedTx as Buffer, { skipPreflight: true, // Ensures submission without preflight checks - }, + } ); console.log( "Transaction Signature:", - getExplorerLink("tx", transactionSignature, "localnet"), + getExplorerLink("tx", transactionSignature, "localnet") ); } catch (error) { console.error("Test failed:", error); diff --git a/content/courses/onchain-development/anchor-cpi.md b/content/courses/onchain-development/anchor-cpi.md index 2b81c545b..4ec36dadd 100644 --- a/content/courses/onchain-development/anchor-cpi.md +++ b/content/courses/onchain-development/anchor-cpi.md @@ -686,7 +686,7 @@ additions are: it("Movie review is added`", async () => { const tokenAccount = await getAssociatedTokenAddress( mint, - provider.wallet.publicKey, + provider.wallet.publicKey ); const tx = await program.methods @@ -701,7 +701,7 @@ it("Movie review is added`", async () => { expect(account.rating).to.equal(movie.rating); expect(account.description).to.equal(movie.description); expect(account.reviewer.toBase58()).to.equal( - provider.wallet.publicKey.toBase58(), + provider.wallet.publicKey.toBase58() ); const userAta = await getAccount(provider.connection, tokenAccount); @@ -745,6 +745,6 @@ Note that your code may look slightly different than the solution code depending on your implementation. -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=21375c76-b6f1-4fb6-8cc1-9ef151bc5b0a)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=21375c76-b6f1-4fb6-8cc1-9ef151bc5b0a)! diff --git a/content/courses/onchain-development/anchor-pdas.md b/content/courses/onchain-development/anchor-pdas.md index 6df47a41a..1268fad43 100644 --- a/content/courses/onchain-development/anchor-pdas.md +++ b/content/courses/onchain-development/anchor-pdas.md @@ -5,8 +5,7 @@ objectives: - Enable and use the `init_if_needed` constraint - Use the `realloc` constraint to reallocate space on an existing account - Use the `close` constraint to close an existing account -description: - "Store arbitrary data on Solana, using PDAs, an inbuilt key-value store." +description: "Store arbitrary data on Solana, using PDAs, an inbuilt key-value store." --- ## Summary @@ -791,7 +790,7 @@ describe("anchor-movie-review-program", () => { const [moviePda] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from(movie.title), provider.wallet.publicKey.toBuffer()], - program.programId, + program.programId ); it("Movie review is added`", async () => {}); @@ -808,8 +807,10 @@ that we don't explicitly add `.accounts`. This is because the `Wallet` from accounts like `SystemProgram`, and Anchor can also infer the `movieReview` PDA from the `title` instruction argument and the signer's public key. -Don't forget to turn on seed inference with `seeds = true` -in the `Anchor.toml` file. + + Don't forget to turn on seed inference with `seeds = true` in the + `Anchor.toml` file. + Once the instruction runs, we then fetch the `movieReview` account and check that the data stored on the account match the expected values. @@ -900,6 +901,6 @@ reference the [solution code](https://github.com/Unboxed-Software/anchor-student-intro-program). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=f58108e9-94a0-45b2-b0d5-44ada1909105)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=f58108e9-94a0-45b2-b0d5-44ada1909105)! diff --git a/content/courses/onchain-development/intro-to-anchor-frontend.md b/content/courses/onchain-development/intro-to-anchor-frontend.md index 0afe4ba99..b9b521850 100644 --- a/content/courses/onchain-development/intro-to-anchor-frontend.md +++ b/content/courses/onchain-development/intro-to-anchor-frontend.md @@ -7,8 +7,7 @@ objectives: - Use the Anchor `MethodsBuilder` to build instructions and transactions - Use Anchor to fetch accounts - Set up a frontend to invoke instructions using Anchor and an IDL -description: - "Use Anchor's automatic JS/TS clients to send instructions to your program." +description: "Use Anchor's automatic JS/TS clients to send instructions to your program." --- ## Summary @@ -337,7 +336,7 @@ export interface WalletContextState { sendTransaction( transaction: Transaction, connection: Connection, - options?: SendTransactionOptions, + options?: SendTransactionOptions ): Promise; signTransaction: SignerWalletAdapterProps["signTransaction"] | undefined; signAllTransactions: @@ -621,7 +620,7 @@ const initialize = useMutation({ .signers([keypair]) .rpc(), - onSuccess: signature => { + onSuccess: (signature) => { transactionToast(signature); return accounts.refetch(); }, @@ -684,7 +683,7 @@ const incrementMutation = useMutation({ mutationFn: () => program.methods.increment().accounts({ counter: account }).rpc(), - onSuccess: tx => { + onSuccess: (tx) => { transactionToast(tx); return accountQuery.refetch(); }, @@ -750,6 +749,6 @@ reference the [solution code](https://github.com/solana-developers/anchor-ping-frontend/tree/solution-decrement). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=774a4023-646d-4394-af6d-19724a6db3db)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=774a4023-646d-4394-af6d-19724a6db3db)! diff --git a/content/courses/onchain-development/intro-to-anchor.md b/content/courses/onchain-development/intro-to-anchor.md index 76aff362d..91848ad19 100644 --- a/content/courses/onchain-development/intro-to-anchor.md +++ b/content/courses/onchain-development/intro-to-anchor.md @@ -146,7 +146,9 @@ struct holding the accounts, the context for the `add_movie_review()` function would be `Context`. - Yes, the Accounts struct is typically named the same thing as the instruction handler, just in TitleCase. Eg, the struct with the accounts for add_movie_review() is called AddMovieReview! + Yes, the Accounts struct is typically named the same thing as the instruction + handler, just in TitleCase. Eg, the struct with the accounts for + add_movie_review() is called AddMovieReview! Through this context argument the instruction can then access: @@ -819,6 +821,6 @@ reference the [solution code](https://github.com/Unboxed-Software/anchor-counter-program/tree/solution-decrement). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=334874b7-b152-4473-b5a5-5474c3f8f3f1)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=334874b7-b152-4473-b5a5-5474c3f8f3f1)! diff --git a/content/courses/onchain-development/local-setup.md b/content/courses/onchain-development/local-setup.md index 2d5fb49e7..41a405c20 100644 --- a/content/courses/onchain-development/local-setup.md +++ b/content/courses/onchain-development/local-setup.md @@ -4,8 +4,7 @@ objectives: - Set up a local environment for Solana program development, with Solana CLI tools, Rust, and Anchor. - Ensure Anchor works out of the box with no errors or warnings. -description: - "Setup a local development environment for building onchain programs." +description: "Setup a local development environment for building onchain programs." --- ## Summary @@ -208,4 +207,5 @@ before continuing. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=aa0b56d6-02a9-4b36-95c0-a817e2c5b19d)! + diff --git a/content/courses/program-optimization/lookup-tables.md b/content/courses/program-optimization/lookup-tables.md index cf02dad80..02935bbeb 100644 --- a/content/courses/program-optimization/lookup-tables.md +++ b/content/courses/program-optimization/lookup-tables.md @@ -156,15 +156,15 @@ generated using the `authority` and `recentSlot` as seeds. ```typescript const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync( [params.authority.toBuffer(), toBufferLE(BigInt(params.recentSlot), 8)], - this.programId, + this.programId ); ``` -Using the most recent slot sometimes results in errors when submitting the -transaction. To avoid this, it’s recommended to use a slot that is one slot -before the most recent one (`recentSlot: currentSlot - 1`). If you still -encounter errors when sending the transaction, try resubmitting it. + Using the most recent slot sometimes results in errors when submitting the + transaction. To avoid this, it’s recommended to use a slot that is one slot + before the most recent one (`recentSlot: currentSlot - 1`). If you still + encounter errors when sending the transaction, try resubmitting it. ``` @@ -406,19 +406,19 @@ async function main() { const payer = await initializeKeypair(connection); // Generate 22 recipient keypairs using makeKeypairs - const recipients = makeKeypairs(22).map(keypair => keypair.publicKey); + const recipients = makeKeypairs(22).map((keypair) => keypair.publicKey); // Create a legacy transaction const transaction = new Transaction(); // Add 22 transfer instructions to the transaction - recipients.forEach(recipient => { + recipients.forEach((recipient) => { transaction.add( SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: recipient, lamports: LAMPORTS_PER_SOL * 0.01, // Transfer 0.01 SOL to each recipient - }), + }) ); }); @@ -428,7 +428,7 @@ async function main() { payer, ]); console.log( - `Transaction successful with signature: ${getExplorerLink("tx", signature, "devnet")}`, + `Transaction successful with signature: ${getExplorerLink("tx", signature, "devnet")}` ); } catch (error) { console.error("Transaction failed:", error); @@ -496,7 +496,7 @@ async function main() { const payer = await initializeKeypair(connection); // Generate 22 recipient keypairs using makeKeypairs - const recipients = makeKeypairs(22).map(keypair => keypair.publicKey); + const recipients = makeKeypairs(22).map((keypair) => keypair.publicKey); } ``` @@ -531,7 +531,7 @@ async function sendV0Transaction( connection: Connection, user: Keypair, instructions: TransactionInstruction[], - lookupTableAccounts?: AddressLookupTableAccount[], + lookupTableAccounts?: AddressLookupTableAccount[] ) { // Get the latest blockhash and last valid block height const { blockhash, lastValidBlockHeight } = @@ -554,13 +554,13 @@ async function sendV0Transaction( [user], { commitment: "finalized", // Ensures the transaction is confirmed at the highest level - }, + } ); // Log the transaction URL on the Solana Explorer using the helper const explorerLink = getExplorerLink("tx", txid, "devnet"); console.log( - `Transaction successful! View it on Solana Explorer: ${explorerLink}`, + `Transaction successful! View it on Solana Explorer: ${explorerLink}` ); } ``` @@ -587,7 +587,7 @@ This function will: ```typescript filename="use-lookup-tables.ts" async function waitForNewBlock( connection: Connection, - targetHeight: number, + targetHeight: number ): Promise { console.log(`Waiting for ${targetHeight} new blocks...`); @@ -595,7 +595,7 @@ async function waitForNewBlock( const { lastValidBlockHeight: initialBlockHeight } = await connection.getLatestBlockhash(); - return new Promise(resolve => { + return new Promise((resolve) => { const SECOND = 1000; const checkInterval = 1 * SECOND; // Interval to check for new blocks (1000ms) @@ -654,7 +654,7 @@ by staying within Solana’s transaction size limits. async function initializeLookupTable( user: Keypair, connection: Connection, - addresses: PublicKey[], + addresses: PublicKey[] ): Promise { // Get the current slot using a helper function from @solana/web3.js const slot = await getSlot(connection); @@ -714,12 +714,12 @@ async function main() { const payer = await initializeKeypair(connection); // Generate 22 recipient keypairs using makeKeypairs - const recipients = makeKeypairs(22).map(keypair => keypair.publicKey); + const recipients = makeKeypairs(22).map((keypair) => keypair.publicKey); // Initialize the lookup table with the generated recipients const lookupTableAddress = await initializeLookupTable( user, connection, - recipients, + recipients ); // Wait for a new block before using the lookup table @@ -736,12 +736,12 @@ async function main() { } // Create transfer instructions for each recipient - const transferInstructions = recipients.map(recipient => + const transferInstructions = recipients.map((recipient) => SystemProgram.transfer({ fromPubkey: user.publicKey, // The payer toPubkey: recipient, // The recipient lamports: LAMPORTS_PER_SOL * 0.01, // Amount to transfer - }), + }) ); // Send the versioned transaction including the lookup table @@ -749,7 +749,7 @@ async function main() { connection, user, transferInstructions, - [lookupTableAccount], + [lookupTableAccount] ); // Log the transaction link for easy access @@ -805,7 +805,7 @@ All we need to do is go into `initializeLookupTable` and do two things: async function initializeLookupTable( user: Keypair, connection: Connection, - addresses: PublicKey[], + addresses: PublicKey[] ): Promise { // Get the current slot const slot = await connection.getSlot(); @@ -874,6 +874,6 @@ and waiting for the lookup table to activate/deactivate. Feel free to reference this [solution code](https://github.com/Unboxed-Software/versioned-transaction/tree/challenge). -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=b58fdd00-2b23-4e0d-be55-e62677d351ef)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=b58fdd00-2b23-4e0d-be55-e62677d351ef)! diff --git a/content/courses/program-optimization/program-architecture.md b/content/courses/program-optimization/program-architecture.md index 6800b51d5..481478af6 100644 --- a/content/courses/program-optimization/program-architecture.md +++ b/content/courses/program-optimization/program-architecture.md @@ -79,6 +79,7 @@ refunded the rent if you close the account. Previously, rent was paid in intervals, similar to traditional rent, but now there's an enforced minimum balance for rent exemption. You can read more about it in [the Solana documentation](https://solana.com/docs/core/fees#rent-exempt). + Putting data on the blockchain can be expensive, which is why NFT attributes and @@ -258,7 +259,7 @@ const ix = anchor.web3.SystemProgram.createAccount({ newAccountPubkey: someReallyBigData.publicKey, lamports: await program.provider.connection.getMinimumBalanceForRentExemption( - accountSize, + accountSize ), space: accountSize, programId: program.programId, @@ -662,7 +663,7 @@ The best example of this is good ‘ol Associated Token Accounts (ATAs)! ```typescript function findAssociatedTokenAddress( walletAddress: PublicKey, - tokenMintAddress: PublicKey, + tokenMintAddress: PublicKey ): PublicKey { return PublicKey.findProgramAddressSync( [ @@ -670,7 +671,7 @@ function findAssociatedTokenAddress( TOKEN_PROGRAM_ID.toBuffer(), tokenMintAddress.toBuffer(), ], - SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, + SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID )[0]; } ``` @@ -1841,7 +1842,7 @@ const findProgramAddress = (seeds: Buffer[]): [PublicKey, number] => const confirmTransaction = async ( signature: TransactionSignature, - provider: anchor.Provider, + provider: anchor.Provider ) => { const latestBlockhash = await provider.connection.getLatestBlockhash(); const confirmationStrategy: TransactionConfirmationStrategy = { @@ -1855,7 +1856,7 @@ const confirmTransaction = async ( await provider.connection.confirmTransaction(confirmationStrategy); if (confirmation.value.err) { throw new Error( - `Transaction failed: ${confirmation.value.err.toString()}`, + `Transaction failed: ${confirmation.value.err.toString()}` ); } } catch (error) { @@ -1875,7 +1876,7 @@ const createPlayerAddress = (gameAddress: PublicKey) => const createMonsterAddress = ( gameAddress: PublicKey, - monsterIndex: anchor.BN, + monsterIndex: anchor.BN ) => findProgramAddress([ Buffer.from(MONSTER_SEED), @@ -1969,7 +1970,7 @@ it("spawns a monster", async () => { const playerAccount = await program.account.player.fetch(playerAddress); const [monsterAddress] = createMonsterAddress( gameAddress, - playerAccount.nextMonsterIndex, + playerAccount.nextMonsterIndex ); const spawnMonsterSignature = await program.methods @@ -1997,7 +1998,7 @@ it("attacks a monster", async () => { const playerAccount = await program.account.player.fetch(playerAddress); const [monsterAddress] = createMonsterAddress( gameAddress, - playerAccount.nextMonsterIndex.subn(1), + playerAccount.nextMonsterIndex.subn(1) ); const attackMonsterSignature = await program.methods @@ -2015,7 +2016,7 @@ it("attacks a monster", async () => { const monsterAccount = await program.account.monster.fetch(monsterAddress); assert( monsterAccount.hitpoints.eqn(INITIAL_MONSTER_HITPOINTS - 1), - "Monster hitpoints should decrease by 1 after attack", + "Monster hitpoints should decrease by 1 after attack" ); } catch (error) { throw new Error(`Failed to attack monster: ${error.message}`); @@ -2052,7 +2053,7 @@ it("deposits action points", async () => { const clockworkProvider = new anchor.AnchorProvider( program.provider.connection, new NodeWallet(clockworkWallet), - anchor.AnchorProvider.defaultOptions(), + anchor.AnchorProvider.defaultOptions() ); // Have to give the accounts some lamports else the tx will fail @@ -2061,14 +2062,14 @@ it("deposits action points", async () => { const clockworkAirdropTx = await clockworkProvider.connection.requestAirdrop( clockworkWallet.publicKey, - amountToInitialize, + amountToInitialize ); await confirmTransaction(clockworkAirdropTx, clockworkProvider); const treasuryAirdropTx = await clockworkProvider.connection.requestAirdrop( treasury.publicKey, - amountToInitialize, + amountToInitialize ); await confirmTransaction(treasuryAirdropTx, clockworkProvider); @@ -2090,27 +2091,27 @@ it("deposits action points", async () => { SPAWN_MONSTER_ACTION_POINTS + ATTACK_MONSTER_ACTION_POINTS; const treasuryBalance = await provider.connection.getBalance( - treasury.publicKey, + treasury.publicKey ); assert( treasuryBalance === AIRDROP_AMOUNT + expectedActionPoints, - "Treasury balance should match expected action points", + "Treasury balance should match expected action points" ); const gameAccount = await program.account.game.fetch(gameAddress); assert( gameAccount.actionPointsCollected.eqn(expectedActionPoints), - "Game action points collected should match expected", + "Game action points collected should match expected" ); const playerAccount = await program.account.player.fetch(playerAddress); assert( playerAccount.actionPointsSpent.eqn(expectedActionPoints), - "Player action points spent should match expected", + "Player action points spent should match expected" ); assert( playerAccount.actionPointsToBeCollected.eqn(0), - "Player should have no action points to be collected", + "Player should have no action points to be collected" ); } catch (error) { throw new Error(`Failed to deposit action points: ${error.message}`); @@ -2154,4 +2155,5 @@ can make to improve memory management, storage size, and/or concurrency. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=4a628916-91f5-46a9-8eb0-6ba453aa6ca6)! + diff --git a/content/courses/program-optimization/program-configuration.md b/content/courses/program-optimization/program-configuration.md index 58760c4b0..019ebd015 100644 --- a/content/courses/program-optimization/program-configuration.md +++ b/content/courses/program-optimization/program-configuration.md @@ -8,8 +8,7 @@ objectives: which features are or are not enabled - Create an admin-only instruction to set up a program account that can be used to store program configuration values -description: - "Create distinct environments, feature flags and admin-only instructions." +description: "Create distinct environments, feature flags and admin-only instructions." --- ## Summary @@ -990,10 +989,10 @@ it("initializes program config account", async () => { const configAccount = await program.account.programConfig.fetch(programConfig); expect(configAccount.feeBasisPoints.toNumber()).to.equal( - INITIAL_FEE_BASIS_POINTS, + INITIAL_FEE_BASIS_POINTS ); expect(configAccount.admin.toString()).to.equal( - walletAuthority.publicKey.toString(), + walletAuthority.publicKey.toString() ); } catch (error) { console.error("Program config initialization failed:", error); @@ -1031,10 +1030,10 @@ it("completes payment successfully", async () => { expect(Number(senderBalance.amount)).to.equal(0); expect(Number(feeDestinationBalance.amount)).to.equal( - (PAYMENT_AMOUNT * INITIAL_FEE_BASIS_POINTS) / 10000, + (PAYMENT_AMOUNT * INITIAL_FEE_BASIS_POINTS) / 10000 ); expect(Number(receiverBalance.amount)).to.equal( - (PAYMENT_AMOUNT * (10000 - INITIAL_FEE_BASIS_POINTS)) / 10000, + (PAYMENT_AMOUNT * (10000 - INITIAL_FEE_BASIS_POINTS)) / 10000 ); } catch (error) { console.error("Payment failed:", error); @@ -1062,7 +1061,7 @@ it("updates program config account", async () => { const configAccount = await program.account.programConfig.fetch(programConfig); expect(configAccount.feeBasisPoints.toNumber()).to.equal( - UPDATED_FEE_BASIS_POINTS, + UPDATED_FEE_BASIS_POINTS ); } catch (error) { console.error("Program config update failed:", error); @@ -1151,4 +1150,5 @@ to see one possible solution. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=02a7dab7-d9c1-495b-928c-a4412006ec20)! + diff --git a/content/courses/program-optimization/rust-macros.md b/content/courses/program-optimization/rust-macros.md index 825c2844f..38d9c70f9 100644 --- a/content/courses/program-optimization/rust-macros.md +++ b/content/courses/program-optimization/rust-macros.md @@ -1121,6 +1121,6 @@ out! Since this is still practice, it's okay if it doesn't work out how you want or expect. Just jump in and experiment! -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=eb892157-3014-4635-beac-f562af600bf8)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=eb892157-3014-4635-beac-f562af600bf8)! diff --git a/content/courses/program-security/account-data-matching.md b/content/courses/program-security/account-data-matching.md index 1683cf3e4..73f63fb4b 100644 --- a/content/courses/program-security/account-data-matching.md +++ b/content/courses/program-security/account-data-matching.md @@ -4,8 +4,7 @@ objectives: - Explain the security risks associated with missing data validation checks - Implement data validation checks using long-form Rust - Implement data validation checks using Anchor constraints -description: - "How to check your program's data accounts in both Anchor and Native Rust." +description: "How to check your program's data accounts in both Anchor and Native Rust." --- ## Summary @@ -566,4 +565,5 @@ them! If you find one in your own program, be sure to patch it right away. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=a107787e-ad33-42bb-96b3-0592efc1b92f)! + diff --git a/content/courses/program-security/arbitrary-cpi.md b/content/courses/program-security/arbitrary-cpi.md index 975d241d3..6f055606b 100644 --- a/content/courses/program-security/arbitrary-cpi.md +++ b/content/courses/program-security/arbitrary-cpi.md @@ -188,6 +188,7 @@ impl<'info> Cpi<'info> { Like the example above, Anchor has created a few [wrappers for popular native programs](https://github.com/coral-xyz/anchor/tree/master/spl/src) that allow you to issue CPIs into them as if they were Anchor programs. + Additionally and depending on the program you're making the CPI to, you may be @@ -286,13 +287,13 @@ it("Insecure instructions allow attacker to win every time successfully", async const [playerOneMetadataKey] = getMetadataKey( playerOne.publicKey, gameplayProgram.programId, - metadataProgram.programId, + metadataProgram.programId ); const [attackerMetadataKey] = getMetadataKey( attacker.publicKey, gameplayProgram.programId, - fakeMetadataProgram.programId, + fakeMetadataProgram.programId ); const playerOneMetadata = @@ -471,4 +472,5 @@ them! If you find one in your own program, be sure to patch it right away. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5bcaf062-c356-4b58-80a0-12cca99c29b0)! + diff --git a/content/courses/program-security/bump-seed-canonicalization.md b/content/courses/program-security/bump-seed-canonicalization.md index b850b9ae3..9d499dc9a 100644 --- a/content/courses/program-security/bump-seed-canonicalization.md +++ b/content/courses/program-security/bump-seed-canonicalization.md @@ -366,7 +366,7 @@ it("allows attacker to claim more than reward limit with insecure instruction ha connection, attacker.publicKey, 1 * LAMPORTS_PER_SOL, - 0.5 * LAMPORTS_PER_SOL, + 0.5 * LAMPORTS_PER_SOL ); const ataKey = await getAssociatedTokenAddress(mint, attacker.publicKey); @@ -376,7 +376,7 @@ it("allows attacker to claim more than reward limit with insecure instruction ha try { const pda = anchor.web3.PublicKey.createProgramAddressSync( [attacker.publicKey.toBuffer(), Buffer.from([i])], - program.programId, + program.programId ); await program.methods .createUserInsecure(i) @@ -407,7 +407,7 @@ it("allows attacker to claim more than reward limit with insecure instruction ha if ( error instanceof Error && !error.message.includes( - "Invalid seeds, address must fall off the curve", + "Invalid seeds, address must fall off the curve" ) ) { console.error(error); @@ -419,8 +419,8 @@ it("allows attacker to claim more than reward limit with insecure instruction ha console.log( `Attacker claimed ${successfulClaimCount} times and got ${Number( - ata.amount, - )} tokens`, + ata.amount + )} tokens` ); expect(successfulClaimCount).to.be.greaterThan(1); @@ -580,12 +580,12 @@ it("allows attacker to claim only once with secure instruction handlers", async connection, attacker.publicKey, 1 * LAMPORTS_PER_SOL, - 0.5 * LAMPORTS_PER_SOL, + 0.5 * LAMPORTS_PER_SOL ); const ataKey = await getAssociatedTokenAddress(mint, attacker.publicKey); const [userPDA] = anchor.web3.PublicKey.findProgramAddressSync( [attacker.publicKey.toBuffer()], - program.programId, + program.programId ); await program.methods @@ -620,7 +620,7 @@ it("allows attacker to claim only once with secure instruction handlers", async try { const pda = anchor.web3.PublicKey.createProgramAddressSync( [attacker.publicKey.toBuffer(), Buffer.from([i])], - program.programId, + program.programId ); await program.methods .createUserSecure() @@ -654,7 +654,7 @@ it("allows attacker to claim only once with secure instruction handlers", async error instanceof Error && !error.message.includes("Error Number: 2006") && !error.message.includes( - "Invalid seeds, address must fall off the curve", + "Invalid seeds, address must fall off the curve" ) ) { // Comment console error logs to see the test outputs properly @@ -667,8 +667,8 @@ it("allows attacker to claim only once with secure instruction handlers", async console.log( `Attacker claimed ${successfulClaimCount} times and got ${Number( - ata.amount, - )} tokens`, + ata.amount + )} tokens` ); expect(Number(ata.amount)).to.equal(10); @@ -709,4 +709,5 @@ them! If you find one in your own program, be sure to patch it right away. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=d3f6ca7a-11c8-421f-b7a3-d6c08ef1aa8b)! + diff --git a/content/courses/program-security/closing-accounts.md b/content/courses/program-security/closing-accounts.md index 5b55b9c9a..7f3ce63a5 100644 --- a/content/courses/program-security/closing-accounts.md +++ b/content/courses/program-security/closing-accounts.md @@ -6,8 +6,7 @@ objectives: - Close program accounts safely and securely using native Rust - Close program accounts safely and securely using the Anchor `close` constraint -description: - "How to close program accounts safely and securely in Anchor and native Rust." +description: "How to close program accounts safely and securely in Anchor and native Rust." --- ## Summary @@ -332,25 +331,25 @@ it("attacker can close + refund lottery acct + claim multiple rewards", async ( mintAuth: mintAuth, tokenProgram: TOKEN_PROGRAM_ID, }) - .instruction(), + .instruction() ); // user adds instruction to refund dataAccount lamports const rentExemptLamports = await provider.connection.getMinimumBalanceForRentExemption( 82, - "confirmed", + "confirmed" ); tx.add( SystemProgram.transfer({ fromPubkey: attacker.publicKey, toPubkey: attackerLotteryEntry, lamports: rentExemptLamports, - }), + }) ); // send tx await sendAndConfirmTransaction(provider.connection, tx, [attacker]); - await new Promise(x => setTimeout(x, 5000)); + await new Promise((x) => setTimeout(x, 5000)); } const ata = await getAccount(provider.connection, attackerAta); @@ -358,7 +357,7 @@ it("attacker can close + refund lottery acct + claim multiple rewards", async ( await program.account.lotteryAccount.fetch(attackerLotteryEntry); expect(Number(ata.amount)).to.equal( - lotteryEntry.timestamp.toNumber() * 10 * 2, + lotteryEntry.timestamp.toNumber() * 10 * 2 ); }); ``` @@ -489,21 +488,21 @@ it("attacker cannot claim multiple rewards with secure claim", async () => { mintAuth: mintAuth, tokenProgram: TOKEN_PROGRAM_ID, }) - .instruction(), + .instruction() ); // user adds instruction to refund dataAccount lamports const rentExemptLamports = await provider.connection.getMinimumBalanceForRentExemption( 82, - "confirmed", + "confirmed" ); tx.add( SystemProgram.transfer({ fromPubkey: attacker.publicKey, toPubkey: attackerLotteryEntry, lamports: rentExemptLamports, - }), + }) ); // send tx await sendAndConfirmTransaction(provider.connection, tx, [attacker]); @@ -564,6 +563,6 @@ Remember, if you find a bug or exploit in somebody else's program, please alert them! If you find one in your own program, be sure to patch it right away. -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=e6b99d4b-35ed-4fb2-b9cd-73eefc875a0f)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=e6b99d4b-35ed-4fb2-b9cd-73eefc875a0f)! diff --git a/content/courses/program-security/duplicate-mutable-accounts.md b/content/courses/program-security/duplicate-mutable-accounts.md index b52f1e29e..1aeee95b8 100644 --- a/content/courses/program-security/duplicate-mutable-accounts.md +++ b/content/courses/program-security/duplicate-mutable-accounts.md @@ -432,6 +432,6 @@ Remember, if you find a bug or exploit in somebody else's program, please alert them! If you find one in your own program, be sure to patch it right away. -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=9b759e39-7a06-4694-ab6d-e3e7ac266ea7)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=9b759e39-7a06-4694-ab6d-e3e7ac266ea7)! diff --git a/content/courses/program-security/owner-checks.md b/content/courses/program-security/owner-checks.md index 99466ac52..cb899fd5b 100644 --- a/content/courses/program-security/owner-checks.md +++ b/content/courses/program-security/owner-checks.md @@ -665,4 +665,5 @@ find one in your own program, patch it immediately. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=e3069010-3038-4984-b9d3-2dc6585147b1)! + diff --git a/content/courses/program-security/pda-sharing.md b/content/courses/program-security/pda-sharing.md index 7b70de4a7..46d59642b 100644 --- a/content/courses/program-security/pda-sharing.md +++ b/content/courses/program-security/pda-sharing.md @@ -295,7 +295,7 @@ it("allows insecure initialization with incorrect vault", async () => { tokenMint, insecureVault.address, wallet.payer, - INITIAL_MINT_AMOUNT, + INITIAL_MINT_AMOUNT ); const vaultAccount = await getAccount(connection, insecureVault.address); @@ -441,7 +441,7 @@ it("performs secure pool initialization and withdrawal correctly", async () => { try { const initialWithdrawBalance = await getAccount( connection, - withdrawDestination, + withdrawDestination ); await program.methods @@ -454,7 +454,7 @@ it("performs secure pool initialization and withdrawal correctly", async () => { .signers([recommendedVault]) .rpc(); - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise((resolve) => setTimeout(resolve, 1000)); await mintTo( connection, @@ -462,7 +462,7 @@ it("performs secure pool initialization and withdrawal correctly", async () => { tokenMint, recommendedVault.publicKey, wallet.payer, - INITIAL_MINT_AMOUNT, + INITIAL_MINT_AMOUNT ); await program.methods @@ -475,12 +475,12 @@ it("performs secure pool initialization and withdrawal correctly", async () => { const finalWithdrawBalance = await getAccount( connection, - withdrawDestination, + withdrawDestination ); expect( Number(finalWithdrawBalance.amount) - - Number(initialWithdrawBalance.amount), + Number(initialWithdrawBalance.amount) ).to.equal(INITIAL_MINT_AMOUNT); } catch (error) { throw new Error(`Test failed: ${error.message}`); @@ -581,4 +581,5 @@ them! If you find one in your own program, be sure to patch it right away. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=5744079f-9473-4485-9a14-9be4d31b40d1)! + diff --git a/content/courses/program-security/reinitialization-attacks.md b/content/courses/program-security/reinitialization-attacks.md index 73e698c23..6c7e5949e 100644 --- a/content/courses/program-security/reinitialization-attacks.md +++ b/content/courses/program-security/reinitialization-attacks.md @@ -298,7 +298,7 @@ describe("Initialization", () => { try { const rentExemptionAmount = await provider.connection.getMinimumBalanceForRentExemption( - ACCOUNT_SPACE, + ACCOUNT_SPACE ); const createAccountInstruction = SystemProgram.createAccount({ @@ -314,14 +314,14 @@ describe("Initialization", () => { await anchor.web3.sendAndConfirmTransaction( provider.connection, transaction, - [walletAuthority.payer, insecureUserAccount], + [walletAuthority.payer, insecureUserAccount] ); await airdropIfRequired( provider.connection, secondWallet.publicKey, AIRDROP_AMOUNT, - MINIMUM_BALANCE_FOR_RENT_EXEMPTION, + MINIMUM_BALANCE_FOR_RENT_EXEMPTION ); } catch (error) { console.error("Setup failed:", error); @@ -359,7 +359,7 @@ describe("Initialization", () => { await anchor.web3.sendAndConfirmTransaction( provider.connection, transaction, - [secondWallet], + [secondWallet] ); } catch (error) { console.error("Re-invocation of insecure initialization failed:", error); @@ -521,4 +521,5 @@ find one in your own program, patch it immediately. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=652c68aa-18d9-464c-9522-e531fd8738d5)! + diff --git a/content/courses/program-security/security-intro.md b/content/courses/program-security/security-intro.md index f3a17c022..fadab6f6d 100644 --- a/content/courses/program-security/security-intro.md +++ b/content/courses/program-security/security-intro.md @@ -36,8 +36,9 @@ you progress, you'll encounter new types of attacks. We encourage you to explore all of them. -Unlike the lessons in other courses, which are in order, you are welcome to -explore these lessons in whatever order suits you best. + Unlike the lessons in other courses, which are in order, you are welcome to + explore these lessons in whatever order suits you best.{" "} + Even though each security vulnerability may seem "simple," there's a lot to discuss. These lessons contain less prose and more code, ensuring you gain a diff --git a/content/courses/program-security/signer-auth.md b/content/courses/program-security/signer-auth.md index 3257e3bdc..da8d739ea 100644 --- a/content/courses/program-security/signer-auth.md +++ b/content/courses/program-security/signer-auth.md @@ -191,8 +191,8 @@ pub struct Vault { ``` -When you use the `Signer` type, no other ownership or type checks are -performed. + When you use the `Signer` type, no other ownership or type checks are + performed. ### Using Anchor's `#[account(signer)]` Constraint @@ -605,4 +605,5 @@ specific vulnerability discussed in each lesson. After completing the challenge, push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=26b3f41e-8241-416b-9cfa-05c5ab519d80)! + diff --git a/content/courses/program-security/type-cosplay.md b/content/courses/program-security/type-cosplay.md index f52c7d25d..945f73e42 100644 --- a/content/courses/program-security/type-cosplay.md +++ b/content/courses/program-security/type-cosplay.md @@ -576,4 +576,5 @@ them. If you find one in your own program, patch it immediately. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=37ebccab-b19a-43c6-a96a-29fa7e80fdec)! + diff --git a/content/courses/solana-pay/solana-pay.md b/content/courses/solana-pay/solana-pay.md index 5d08a75e4..627c684ce 100644 --- a/content/courses/solana-pay/solana-pay.md +++ b/content/courses/solana-pay/solana-pay.md @@ -6,8 +6,7 @@ objectives: - Use the `@solana/pay` library to create Solana Pay transaction requests - Partially sign transactions and implement transaction gating based on specific conditions -description: - "How to create Solana Pay payment requests using links and QR codes." +description: "How to create Solana Pay payment requests using links and QR codes." --- ## Summary @@ -141,7 +140,7 @@ import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( request: NextApiRequest, - response: NextApiResponse, + response: NextApiResponse ) { // Handle the request } @@ -163,7 +162,7 @@ import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( request: NextApiRequest, - response: NextApiResponse, + response: NextApiResponse ) { if (request.method === "GET") { return get(response); @@ -204,7 +203,7 @@ import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( request: NextApiRequest, - response: NextApiResponse, + response: NextApiResponse ) { if (request.method === "GET") { return get(response); @@ -494,7 +493,7 @@ import { NextApiRequest, NextApiResponse } from "next"; export default async function handler( request: NextApiRequest, - response: NextApiResponse, + response: NextApiResponse ) { if (request.method === "GET") { return get(response); @@ -639,7 +638,7 @@ Using the empty helper functions and the new imports, we can fill in the async function buildTransaction( account: PublicKey, reference: PublicKey, - id: string, + id: string ): Promise { const userState = await fetchUserState(account); @@ -667,7 +666,7 @@ async function buildTransaction( } transaction.add( - await createCheckInInstruction(account, reference, currentLocation), + await createCheckInInstruction(account, reference, currentLocation) ); transaction.partialSign(eventOrganizer); @@ -693,13 +692,13 @@ async function fetchUserState(account: PublicKey): Promise { function verifyCorrectLocation( userState: UserState | null, - currentLocation: Location, + currentLocation: Location ): boolean { return false; } async function createInitUserInstruction( - account: PublicKey, + account: PublicKey ): Promise { throw ""; } @@ -707,7 +706,7 @@ async function createInitUserInstruction( async function createCheckInInstruction( account: PublicKey, reference: PublicKey, - location: Location, + location: Location ): Promise { throw ""; } @@ -724,7 +723,7 @@ fetches that account, returning null if it doesn't exist. async function fetchUserState(account: PublicKey): Promise { const userStatePDA = PublicKey.findProgramAddressSync( [gameId.toBuffer(), account.toBuffer()], - program.programId, + program.programId )[0]; try { @@ -751,14 +750,14 @@ it'll return false. ```typescript function verifyCorrectLocation( userState: UserState | null, - currentLocation: Location, + currentLocation: Location ): boolean { if (!userState) { return currentLocation.index === 1; } const lastLocation = locations.find( - location => location.key.toString() === userState.lastLocation.toString(), + (location) => location.key.toString() === userState.lastLocation.toString() ); if (!lastLocation || currentLocation.index !== lastLocation.index + 1) { @@ -777,7 +776,7 @@ needs to add `reference` to the instructions list of keys. ```typescript async function createInitUserInstruction( - account: PublicKey, + account: PublicKey ): Promise { const initializeInstruction = await program.methods .initialize(gameId) @@ -790,7 +789,7 @@ async function createInitUserInstruction( async function createCheckInInstruction( account: PublicKey, reference: PublicKey, - location: Location, + location: Location ): Promise { const checkInInstruction = await program.methods .checkIn(gameId, location.key) @@ -846,6 +845,6 @@ Get creative with this! The Solana pay spec opens up a lot of doors for unique use cases. -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=3c7e5796-c433-4575-93e1-1429f718aa10)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=3c7e5796-c433-4575-93e1-1429f718aa10)! diff --git a/content/courses/state-compression/compressed-nfts.md b/content/courses/state-compression/compressed-nfts.md index 89513bc5f..159e6bcb8 100644 --- a/content/courses/state-compression/compressed-nfts.md +++ b/content/courses/state-compression/compressed-nfts.md @@ -491,7 +491,7 @@ You might need to update your RPC connection endpoint in the Umi instantiation ```typescript const umi = createUmi( - "https://devnet.helius-rpc.com/?api-key=YOUR-HELIUS-API-KEY", + "https://devnet.helius-rpc.com/?api-key=YOUR-HELIUS-API-KEY" ); ``` @@ -793,7 +793,8 @@ a new collection for this lesson, check out the code [on this repo](https://github.com/solana-developers/professional-education/blob/main/labs/metaplex-umi/create-collection.ts) -Find the code to create a Metaplex Collection NFT in our [NFTs with Metaplex lesson](https://solana.com/developers/courses/tokens-and-nfts/nfts-with-metaplex#add-the-nft-to-a-collection). + Find the code to create a Metaplex Collection NFT in our [NFTs with Metaplex + lesson](https://solana.com/developers/courses/tokens-and-nfts/nfts-with-metaplex#add-the-nft-to-a-collection). To mint a compressed NFT to a collection we will need @@ -836,10 +837,10 @@ Putting it all into code, we will have const merkleTree = UMIPublicKey("ZwzNxXw83PUmWSypXmqRH669gD3hF9rEjHWPpVghr5h"); const collectionMint = UMIPublicKey( - "D2zi1QQmtZR5fk7wpA1Fmf6hTY2xy8xVMyNgfq6LsKy1", + "D2zi1QQmtZR5fk7wpA1Fmf6hTY2xy8xVMyNgfq6LsKy1" ); -const uintSig = await( +const uintSig = await ( await mintToCollectionV1(umi, { leafOwner: umi.identity.publicKey, merkleTree, @@ -857,7 +858,7 @@ const uintSig = await( }, ], }, - }).sendAndConfirm(umi), + }).sendAndConfirm(umi) ).signature; const b64Sig = base58.deserialize(uintSig); @@ -873,7 +874,7 @@ derive the asset ID. ```typescript filename="mint-compressed-nft-to-collection.ts" const leaf: LeafSchema = await parseLeafFromMintToCollectionV1Transaction( umi, - uintSig, + uintSig ); const assetId = findLeafAssetIdPda(umi, { merkleTree, @@ -931,7 +932,7 @@ import { createUmi } from "@metaplex-foundation/umi-bundle-defaults"; import { getKeypairFromFile } from "@solana-developers/helpers"; const umi = createUmi( - "https://devnet.helius-rpc.com/?api-key=YOUR-HELIUS-API-KEY", + "https://devnet.helius-rpc.com/?api-key=YOUR-HELIUS-API-KEY" ); // load keypair from local file system @@ -1088,12 +1089,12 @@ const assetId = UMIPublicKey("D4A8TYkKE5NzkqBQ4mPybgFbAUDN53fwJ64b8HwEEuUS"); //@ts-ignore const assetWithProof = await getAssetWithProof(umi, assetId); -let uintSig = await( +let uintSig = await ( await transfer(umi, { ...assetWithProof, leafOwner: umi.identity.publicKey, newLeafOwner: UMIPublicKey("J63YroB8AwjDVjKuxjcYFKypVM3aBeQrfrVmNBxfmThB"), - }).sendAndConfirm(umi), + }).sendAndConfirm(umi) ).signature; const b64sig = base58.deserialize(uintSig); @@ -1137,6 +1138,6 @@ be overly prescriptive at this point, but here are some ideas: program, i.e. write a program that can mint cNFTs -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=db156789-2400-4972-904f-40375582384a)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=db156789-2400-4972-904f-40375582384a)! diff --git a/content/courses/state-compression/generalized-state-compression.md b/content/courses/state-compression/generalized-state-compression.md index 6a74e7c70..f6c8133ad 100644 --- a/content/courses/state-compression/generalized-state-compression.md +++ b/content/courses/state-compression/generalized-state-compression.md @@ -691,7 +691,7 @@ program to illustrate the process: ```typescript export async function getMessageLog( connection: Connection, - txSignature: string, + txSignature: string ) { // Confirm the transaction, otherwise the getTransaction sometimes returns null const latestBlockHash = await connection.getLatestBlockhash(); @@ -712,10 +712,10 @@ export async function getMessageLog( // Get the inner instructions that match the SPL_NOOP_PROGRAM_ID const noopInnerIx = innerIx.filter( - instruction => + (instruction) => txInfo?.transaction.message.staticAccountKeys[ instruction.programIdIndex - ].toBase58() === SPL_NOOP_PROGRAM_ID.toBase58(), + ].toBase58() === SPL_NOOP_PROGRAM_ID.toBase58() ); let messageLog: MessageLog; @@ -723,7 +723,7 @@ export async function getMessageLog( try { // Try to decode and deserialize the instruction data const applicationDataEvent = deserializeApplicationDataEvent( - Buffer.from(bs58.decode(noopInnerIx[i]?.data!)), + Buffer.from(bs58.decode(noopInnerIx[i]?.data!)) ); // Get the application data @@ -733,7 +733,7 @@ export async function getMessageLog( messageLog = deserialize( MessageLogBorshSchema, MessageLog, - Buffer.from(applicationData), + Buffer.from(applicationData) ); if (messageLog !== undefined) { @@ -755,7 +755,9 @@ streamlined. If you discover solutions that enhance your development experience, please don’t hesitate to share them with the community! -Remember to write comprehensive tests for your state compression implementation. This ensures your program behaves correctly and helps catch potential issues early in the development process. + Remember to write comprehensive tests for your state compression + implementation. This ensures your program behaves correctly and helps catch + potential issues early in the development process. ## Lab: Building a Note-Taking App with Generalized State Compression @@ -1335,7 +1337,7 @@ export function getHash(note: string, owner: PublicKey) { const concatenatedUint8Array = new Uint8Array( concatenatedBuffer.buffer, concatenatedBuffer.byteOffset, - concatenatedBuffer.byteLength, + concatenatedBuffer.byteLength ); return keccak256(concatenatedUint8Array); } @@ -1360,10 +1362,10 @@ export async function getNoteLog(connection: Connection, txSignature: string) { // Get the inner instructions that match the SPL_NOOP_PROGRAM_ID const noopInnerIx = innerIx.filter( - instruction => + (instruction) => txInfo?.transaction.message.staticAccountKeys[ instruction.programIdIndex - ].toBase58() === SPL_NOOP_PROGRAM_ID.toBase58(), + ].toBase58() === SPL_NOOP_PROGRAM_ID.toBase58() ); let noteLog: NoteLog; @@ -1371,7 +1373,7 @@ export async function getNoteLog(connection: Connection, txSignature: string) { try { // Try to decode and deserialize the instruction data const applicationDataEvent = deserializeApplicationDataEvent( - Buffer.from(bs58.decode(noopInnerIx[i]?.data!)), + Buffer.from(bs58.decode(noopInnerIx[i]?.data!)) ); // Get the application data @@ -1381,7 +1383,7 @@ export async function getNoteLog(connection: Connection, txSignature: string) { noteLog = deserialize( NoteLogBorshSchema, NoteLog, - Buffer.from(applicationData), + Buffer.from(applicationData) ); if (noteLog !== undefined) { @@ -1466,7 +1468,7 @@ describe("compressed-notes", () => { anchor.setProvider(provider); const connection = new Connection( provider.connection.rpcEndpoint, - "confirmed", + "confirmed" ); const wallet = provider.wallet as anchor.Wallet; @@ -1478,7 +1480,7 @@ describe("compressed-notes", () => { // Derive the PDA to use as the tree authority for the Merkle tree account const [treeAuthority] = PublicKey.findProgramAddressSync( [merkleTree.publicKey.toBuffer()], - program.programId, + program.programId ); const firstNote = "hello world"; @@ -1515,7 +1517,7 @@ it("creates a new note tree", async () => { merkleTree.publicKey, wallet.publicKey, maxDepthSizePair, - canopyDepth, + canopyDepth ); // Instruction to initialize the tree through the Note program @@ -1537,7 +1539,7 @@ it("creates a new note tree", async () => { const merkleTreeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( connection, - merkleTree.publicKey, + merkleTree.publicKey ); assert(merkleTreeAccount, "Merkle tree should be initialized"); }); @@ -1559,7 +1561,7 @@ it("adds a note to the Merkle tree", async () => { assert( hash === Buffer.from(noteLog.leafNode).toString("hex"), - "Leaf node hash should match", + "Leaf node hash should match" ); assert(firstNote === noteLog.note, "Note should match the appended note"); }); @@ -1581,11 +1583,11 @@ it("adds max size note to the Merkle tree", async () => { assert( hash === Buffer.from(noteLog.leafNode).toString("hex"), - "Leaf node hash should match", + "Leaf node hash should match" ); assert( secondNote === noteLog.note, - "Note should match the appended max size note", + "Note should match the appended max size note" ); }); @@ -1593,7 +1595,7 @@ it("updates the first note in the Merkle tree", async () => { const merkleTreeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress( connection, - merkleTree.publicKey, + merkleTree.publicKey ); const root = merkleTreeAccount.getCurrentRoot(); @@ -1613,11 +1615,11 @@ it("updates the first note in the Merkle tree", async () => { assert( hash === Buffer.from(noteLog.leafNode).toString("hex"), - "Leaf node hash should match after update", + "Leaf node hash should match after update" ); assert( updatedNote === noteLog.note, - "Updated note should match the logged note", + "Updated note should match the logged note" ); }); ``` @@ -1644,6 +1646,7 @@ the [`main` branch on GitHub](https://github.com/Unboxed-Software/anchor-compressed-notes/tree/main). -Push your code to GitHub and [let us know what you think of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=60f6b072-eaeb-469c-b32e-5fea4b72d1d1)! + Push your code to GitHub and [let us know what you think of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=60f6b072-eaeb-469c-b32e-5fea4b72d1d1)! ``` diff --git a/content/courses/token-extensions/close-mint.md b/content/courses/token-extensions/close-mint.md index 08ecd4ca6..544b76226 100644 --- a/content/courses/token-extensions/close-mint.md +++ b/content/courses/token-extensions/close-mint.md @@ -84,7 +84,7 @@ const initializeMintCloseAuthorityInstruction = createInitializeMintCloseAuthorityInstruction( mint, authority, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -96,7 +96,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, null, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -107,14 +107,14 @@ network. const mintTransaction = new Transaction().add( createAccountInstruction, initializeMintCloseAuthorityInstruction, - initializeMintInstruction, + initializeMintInstruction ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); ``` @@ -130,8 +130,9 @@ Remember, that to close the mint account, the total supply has to be 0. So if any tokens exist, they have to be burned first. You can do this with the `burn` function. -The `closeAccount` function works for mints and token -accounts alike. + + The `closeAccount` function works for mints and token accounts alike.{" "} + ```ts // burn tokens to 0 @@ -144,7 +145,7 @@ const burnSignature = await burn( sourceAccountInfo.amount, // amount - Amount to burn [], // multiSigners - Signing accounts if `owner` is a multisig { commitment: "finalized" }, // confirmOptions - Options for confirming the transaction - TOKEN_2022_PROGRAM_ID, // programId - SPL Token program account + TOKEN_2022_PROGRAM_ID // programId - SPL Token program account ); // account can be closed as total supply is now 0 @@ -156,7 +157,7 @@ await closeAccount( payer, // authority - Authority which is allowed to close the account [], // multiSigners - Signing accounts if `authority` is a multisig { commitment: "finalized" }, // confirmOptions - Options for confirming the transaction - TOKEN_2022_PROGRAM_ID, // programIdSPL Token program account + TOKEN_2022_PROGRAM_ID // programIdSPL Token program account ); ``` @@ -192,7 +193,7 @@ await setAuthority( newAuthority, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -363,7 +364,7 @@ export async function createClosableMint( connection: Connection, payer: Keypair, mintKeypair: Keypair, - decimals: number, + decimals: number ): Promise { const extensions = [ExtensionType.MintCloseAuthority]; const mintLength = getMintLen(extensions); @@ -383,15 +384,15 @@ export async function createClosableMint( createInitializeMintCloseAuthorityInstruction( mintKeypair.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( mintKeypair.publicKey, decimals, payer.publicKey, null, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); console.log("Sending transaction..."); @@ -399,7 +400,7 @@ export async function createClosableMint( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); return signature; @@ -458,7 +459,7 @@ const sourceAccount = await createAccount( sourceKeypair.publicKey, undefined, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Minting 1 token...\n\n"); @@ -472,7 +473,7 @@ await mintTo( amount, [payer], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -488,7 +489,7 @@ let mintInfo = await getMint( connection, mintKeypair.publicKey, "finalized", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Initial supply: ", mintInfo.supply); ``` @@ -528,13 +529,13 @@ try { payer, [], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); } catch (e) { console.log( "Close account fails here because the supply is not zero. Check the program logs:", (e as any).logs, - "\n\n", + "\n\n" ); } ``` @@ -563,7 +564,7 @@ const sourceAccountInfo = await getAccount( connection, sourceAccount, "finalized", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Burning the supply..."); @@ -576,7 +577,7 @@ const burnSignature = await burn( sourceAccountInfo.amount, [], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -609,14 +610,14 @@ mintInfo = await getMint( connection, mintKeypair.publicKey, "finalized", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("After burn supply: ", mintInfo.supply); const accountInfoBeforeClose = await connection.getAccountInfo( mintKeypair.publicKey, - "finalized", + "finalized" ); console.log("Account closed? ", accountInfoBeforeClose === null); @@ -630,12 +631,12 @@ const closeSignature = await closeAccount( payer, [], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const accountInfoAfterClose = await connection.getAccountInfo( mintKeypair.publicKey, - "finalized", + "finalized" ); console.log("Account closed? ", accountInfoAfterClose === null); diff --git a/content/courses/token-extensions/cpi-guard.md b/content/courses/token-extensions/cpi-guard.md index 0d3d1fd45..89f536054 100644 --- a/content/courses/token-extensions/cpi-guard.md +++ b/content/courses/token-extensions/cpi-guard.md @@ -154,21 +154,21 @@ const enableCpiGuardInstruction = createEnableCpiGuardInstruction( tokenAccount, owner.publicKey, [], - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initializeAccountInstruction = createInitializeAccountInstruction( tokenAccount, mint, owner.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // construct transaction with these instructions const transaction = new Transaction().add( createTokenAccountInstruction, initializeAccountInstruction, - enableCpiGuardInstruction, + enableCpiGuardInstruction ); transaction.feePayer = payer.publicKey; @@ -194,7 +194,7 @@ await enableCpiGuard( payer, // payer userTokenAccount.publicKey, // account payer, // owner - [], // multiSigners + [] // multiSigners ); // disable CPI Guard @@ -203,7 +203,7 @@ await disableCpiGuard( payer, // payer userTokenAccount.publicKey, // account payer, // owner - [], // multiSigners + [] // multiSigners ); ``` @@ -531,7 +531,7 @@ export async function createTokenAccountWithCPIGuard( payer: Keypair, owner: Keypair, tokenAccountKeypair: Keypair, - mint: PublicKey, + mint: PublicKey ): Promise { const tokenAccount = tokenAccountKeypair.publicKey; @@ -553,20 +553,20 @@ export async function createTokenAccountWithCPIGuard( tokenAccount, mint, owner.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const enableCpiGuardInstruction = createEnableCpiGuardInstruction( tokenAccount, owner.publicKey, [], - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transaction = new Transaction().add( createTokenAccountInstruction, initializeAccountInstruction, - enableCpiGuardInstruction, + enableCpiGuardInstruction ); transaction.feePayer = payer.publicKey; @@ -664,14 +664,14 @@ it("stops 'Approve Delegate' when CPI guard is enabled", async () => { 6, testTokenMint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await createTokenAccountWithCPIGuard( provider.connection, payer, payer, userTokenAccount, - testTokenMint.publicKey, + testTokenMint.publicKey ); }); ``` @@ -697,10 +697,10 @@ try { } catch (e) { assert( e.message == - "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2d", + "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2d" ); console.log( - "CPI Guard is enabled, and a program attempted to approve a delegate", + "CPI Guard is enabled, and a program attempted to approve a delegate" ); } ``` @@ -723,7 +723,7 @@ it("allows 'Approve Delegate' when CPI guard is disabled", async () => { payer, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await program.methods @@ -815,14 +815,14 @@ it("stops 'Close Account' when CPI guard in enabled", async () => { 6, testTokenMint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await createTokenAccountWithCPIGuard( provider.connection, payer, payer, userTokenAccount, - testTokenMint.publicKey, + testTokenMint.publicKey ); }); ``` @@ -849,10 +849,10 @@ try { } catch (e) { assert( e.message == - "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2c", + "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2c" ); console.log( - "CPI Guard is enabled, and a program attempted to close an account without returning lamports to owner", + "CPI Guard is enabled, and a program attempted to close an account without returning lamports to owner" ); } ``` @@ -870,7 +870,7 @@ it("Close Account without CPI Guard", async () => { payer, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await program.methods @@ -952,14 +952,14 @@ it("sets authority when CPI guard in enabled", async () => { 6, testTokenMint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await createTokenAccountWithCPIGuard( provider.connection, payer, payer, userTokenAccount, - testTokenMint.publicKey, + testTokenMint.publicKey ); try { @@ -978,10 +978,10 @@ it("sets authority when CPI guard in enabled", async () => { } catch (e) { assert( e.message == - "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2e", + "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2e" ); console.log( - "CPI Guard is enabled, and a program attempted to add or change an authority", + "CPI Guard is enabled, and a program attempted to add or change an authority" ); } }); @@ -999,7 +999,7 @@ it("Set Authority Example", async () => { payer, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await program.methods @@ -1084,7 +1084,7 @@ it("stops 'Burn' without a delegate signature", async () => { 6, testTokenMint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await createTokenAccountWithCPIGuard( @@ -1092,7 +1092,7 @@ it("stops 'Burn' without a delegate signature", async () => { payer, payer, userTokenAccount, - testTokenMint.publicKey, + testTokenMint.publicKey ); }); ``` @@ -1110,7 +1110,7 @@ const mintToTx = await mintTo( 1000, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -1130,7 +1130,7 @@ const approveTx = await approve( 500, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -1159,10 +1159,10 @@ try { } catch (e) { assert( e.message == - "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2b", + "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2b" ); console.log( - "CPI Guard is enabled, and a program attempted to burn user funds without using a delegate.", + "CPI Guard is enabled, and a program attempted to burn user funds without using a delegate." ); } ``` @@ -1179,7 +1179,7 @@ it("Burn without Delegate Signature Example", async () => { payer, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const tx = await program.methods @@ -1266,7 +1266,7 @@ it("stops 'Set Authority' without CPI on a CPI-guarded account", async () => { 6, testTokenMint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await createTokenAccountWithCPIGuard( @@ -1274,7 +1274,7 @@ it("stops 'Set Authority' without CPI on a CPI-guarded account", async () => { payer, payer, userTokenAccount, - testTokenMint.publicKey, + testTokenMint.publicKey ); }); ``` @@ -1295,15 +1295,15 @@ try { newOwner.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); } catch (e) { assert( e.message == - "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2f", + "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2f" ); console.log( - "Account ownership cannot be changed while CPI Guard is enabled.", + "Account ownership cannot be changed while CPI Guard is enabled." ); } ``` @@ -1323,7 +1323,7 @@ it("Set Authority without CPI on Non-CPI Guarded Account", async () => { payer.publicKey, firstNonCPIGuardAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await setAuthority( @@ -1335,7 +1335,7 @@ it("Set Authority without CPI on Non-CPI Guarded Account", async () => { newOwner.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); }); ``` @@ -1361,10 +1361,10 @@ it("[CPI Guard] Set Authority via CPI on CPI Guarded Account", async () => { } catch (e) { assert( e.message == - "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2e", + "failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x2e" ); console.log( - "CPI Guard is enabled, and a program attempted to add or change an authority.", + "CPI Guard is enabled, and a program attempted to add or change an authority." ); } }); @@ -1382,7 +1382,7 @@ it("Set Authority via CPI on Non-CPI Guarded Account", async () => { payer.publicKey, secondNonCPIGuardAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await program.methods diff --git a/content/courses/token-extensions/default-account-state.md b/content/courses/token-extensions/default-account-state.md index dcbea482c..81e6d5493 100644 --- a/content/courses/token-extensions/default-account-state.md +++ b/content/courses/token-extensions/default-account-state.md @@ -4,8 +4,7 @@ objectives: - Create mint account with default account state of frozen - Explain the use cases of default account state - Experiment with the rules of the extension -description: - "Create token that requires interaction with a specific service to use." +description: "Create token that requires interaction with a specific service to use." --- ## Summary @@ -106,7 +105,7 @@ const initializeDefaultAccountStateInstruction = createInitializeDefaultAccountStateInstruction( mintKeypair.publicKey, // Mint defaultState, // Default State - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -118,7 +117,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -129,7 +128,7 @@ blockchain. const transaction = new Transaction().add( createAccountInstruction, initializeDefaultAccountStateInstruction, - initializeMintInstruction, + initializeMintInstruction ); return await sendAndConfirmTransaction(connection, transaction, [ @@ -166,7 +165,7 @@ export async function updateDefaultAccountState( freezeAuthority: Signer | PublicKey, multiSigners: Signer[] = [], confirmOptions?: ConfirmOptions, - programId = TOKEN_2022_PROGRAM_ID, + programId = TOKEN_2022_PROGRAM_ID ): Promise; ``` @@ -203,7 +202,7 @@ await setAuthority( newAuthority, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -376,7 +375,7 @@ const initializeDefaultAccountStateInstruction = createInitializeDefaultAccountStateInstruction( mintKeypair.publicKey, defaultState, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -391,7 +390,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, // Designated Mint Authority payer.publicKey, // Designated Freeze Authority - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -402,7 +401,7 @@ blockchain: const transaction = new Transaction().add( createAccountInstruction, initializeDefaultAccountStateInstruction, - initializeMintInstruction, + initializeMintInstruction ); return await sendAndConfirmTransaction(connection, transaction, [ @@ -445,7 +444,7 @@ export async function createTokenExtensionMintWithDefaultState( payer: Keypair, mintKeypair: Keypair, decimals: number = 2, - defaultState: AccountState, + defaultState: AccountState ): Promise { const mintLen = getMintLen([ExtensionType.DefaultAccountState]); // Minimum lamports required for Mint Account @@ -463,7 +462,7 @@ export async function createTokenExtensionMintWithDefaultState( createInitializeDefaultAccountStateInstruction( mintKeypair.publicKey, defaultState, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initializeMintInstruction = createInitializeMintInstruction( @@ -471,13 +470,13 @@ export async function createTokenExtensionMintWithDefaultState( decimals, payer.publicKey, // Designated Mint Authority payer.publicKey, // Designated Freeze Authority - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transaction = new Transaction().add( createAccountInstruction, initializeDefaultAccountStateInstruction, - initializeMintInstruction, + initializeMintInstruction ); return await sendAndConfirmTransaction(connection, transaction, [ @@ -505,7 +504,7 @@ await createTokenExtensionMintWithDefaultState( payer, mintKeypair, decimals, - defaultState, + defaultState ); ``` @@ -526,7 +525,7 @@ await createAccount( payer.publicKey, ourTokenAccountKeypair, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Transferring to account await createAccount( @@ -536,7 +535,7 @@ await createAccount( payer.publicKey, otherTokenAccountKeypair, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -574,13 +573,13 @@ try { amountToMint, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.error("Should not have minted..."); } catch (error) { console.log( - "✅ - We expected this to fail because the account is still frozen.", + "✅ - We expected this to fail because the account is still frozen." ); } ``` @@ -613,7 +612,7 @@ await thawAccount( payer.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Mint tokens to tokenAccount await mintTo( @@ -625,18 +624,18 @@ await mintTo( amountToMint, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const ourTokenAccountWithTokens = await getAccount( connection, ourTokenAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - `✅ - The new account balance is ${Number(ourTokenAccountWithTokens.amount)} after thawing and minting.`, + `✅ - The new account balance is ${Number(ourTokenAccountWithTokens.amount)} after thawing and minting.` ); ``` @@ -670,13 +669,13 @@ try { amountToTransfer, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.error("Should not have minted..."); } catch (error) { console.log( - "✅ - We expected this to fail because the account is still frozen.", + "✅ - We expected this to fail because the account is still frozen." ); } ``` @@ -706,7 +705,7 @@ await thawAccount( payer.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await transfer( @@ -718,20 +717,20 @@ await transfer( amountToTransfer, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const otherTokenAccountWithTokens = await getAccount( connection, otherTokenAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( `✅ - The new account balance is ${Number( - otherTokenAccountWithTokens.amount, - )} after thawing and transferring.`, + otherTokenAccountWithTokens.amount + )} after thawing and transferring.` ); ``` @@ -767,7 +766,7 @@ await freezeAccount( payer.publicKey, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await burn( @@ -779,6 +778,6 @@ await burn( 1, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` diff --git a/content/courses/token-extensions/group-member.md b/content/courses/token-extensions/group-member.md index 96eb49ed9..2466bbe9e 100644 --- a/content/courses/token-extensions/group-member.md +++ b/content/courses/token-extensions/group-member.md @@ -119,7 +119,7 @@ const initializeGroupPointerInstruction = mintKeypair.publicKey, payer.publicKey, mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -131,7 +131,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -157,14 +157,14 @@ const mintTransaction = new Transaction().add( createAccountInstruction, initializeGroupPointerInstruction, initializeMintInstruction, - initializeGroupInstruction, + initializeGroupInstruction ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); ``` @@ -184,7 +184,7 @@ const signature = await tokenGroupUpdateGroupAuthority( newAuthority, // account - Public key of the new update authority undefined, // multiSigners - Signing accounts if `authority` is a multisig { commitment: "finalized" }, // confirmOptions - Options for confirming thr transaction - TOKEN_2022_PROGRAM_ID, // programId - SPL Token program account + TOKEN_2022_PROGRAM_ID // programId - SPL Token program account ); ``` @@ -204,7 +204,7 @@ const signature = tokenGroupUpdateGroupMaxSize( 4, // maxSize - new max size of the group undefined, // multiSigners — Signing accounts if `authority` is a multisig { commitment: "finalized" }, // confirmOptions - Options for confirming thr transaction - TOKEN_2022_PROGRAM_ID, // programId - SPL Token program account + TOKEN_2022_PROGRAM_ID // programId - SPL Token program account ); ``` @@ -279,7 +279,7 @@ const initializeGroupMemberPointerInstruction = mintKeypair.publicKey, payer.publicKey, mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -291,7 +291,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -319,14 +319,14 @@ const mintTransaction = new Transaction().add( createAccountInstruction, initializeGroupMemberPointerInstruction, initializeMintInstruction, - initializeMemberInstruction, + initializeMemberInstruction ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); ``` @@ -355,7 +355,7 @@ const groupMint = await getMint( connection, mint, "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const groupPointerData: GroupPointer = getGroupPointerState(groupMint); @@ -387,7 +387,7 @@ const groupMint = await getMint( connection, mint, "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const groupData: TokenGroup = getTokenGroupState(groupMint); @@ -416,7 +416,7 @@ const memberMint = await getMint( connection, mint, "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const memberPointerData = getGroupMemberPointerState(memberMint); @@ -446,7 +446,7 @@ const memberMint = await getMint( connection, mint, "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const memberData = getTokenGroupMemberState(memberMint); ``` @@ -548,7 +548,7 @@ const collectionTokenMetadata: TokenMetadata = { uri: groupMetadata.tokenUri, updateAuthority: payer.publicKey, additionalMetadata: Object.entries( - groupMetadata.tokenAdditionalMetadata || [], + groupMetadata.tokenAdditionalMetadata || [] ).map(([trait_type, value]) => [trait_type, value]), }; ``` @@ -582,7 +582,7 @@ export async function createTokenGroup( mintKeypair: Keypair, decimals: number, maxMembers: number, - metadata: TokenMetadata, + metadata: TokenMetadata ): Promise; ``` @@ -639,7 +639,7 @@ export async function createTokenGroup( mintKeypair: Keypair, decimals: number, maxMembers: number, - metadata: TokenMetadata, + metadata: TokenMetadata ): Promise { const extensions: ExtensionType[] = [ ExtensionType.GroupPointer, @@ -665,20 +665,20 @@ export async function createTokenGroup( mintKeypair.publicKey, payer.publicKey, mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMetadataPointerInstruction( mintKeypair.publicKey, payer.publicKey, mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( mintKeypair.publicKey, decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeGroupInstruction({ group: mintKeypair.publicKey, @@ -697,13 +697,13 @@ export async function createTokenGroup( symbol: metadata.symbol, updateAuthority: payer.publicKey, uri: metadata.uri, - }), + }) ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, - [payer, mintKeypair], + [payer, mintKeypair] ); return signature; @@ -720,11 +720,11 @@ const signature = await createTokenGroup( groupMintKeypair, decimals, maxMembers, - collectionTokenMetadata, + collectionTokenMetadata ); console.log( - `Created collection mint with metadata:\n${getExplorerLink("tx", signature, "localnet")}\n`, + `Created collection mint with metadata:\n${getExplorerLink("tx", signature, "localnet")}\n` ); ``` @@ -737,11 +737,11 @@ const groupMint = await getMint( connection, groupMintKeypair.publicKey, "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const fetchedGroupMetadata = await getTokenMetadata( connection, - groupMintKeypair.publicKey, + groupMintKeypair.publicKey ); const metadataPointerState = getMetadataPointerState(groupMint); const groupData = getGroupPointerState(groupMint); @@ -750,7 +750,7 @@ console.log("\n---------- GROUP DATA -------------\n"); console.log("Group Mint: ", groupMint.address.toBase58()); console.log( "Metadata Pointer Account: ", - metadataPointerState?.metadataAddress?.toBase58(), + metadataPointerState?.metadataAddress?.toBase58() ); console.log("Group Pointer Account: ", groupData?.groupAddress?.toBase58()); console.log("\n--- METADATA ---\n"); @@ -838,7 +838,7 @@ member NFTs. ```ts // Format token metadata const memberTokenMetadata: { mintKeypair: Keypair; metadata: TokenMetadata }[] = - membersMetadata.map(member => ({ + membersMetadata.map((member) => ({ mintKeypair: member.mint, metadata: { name: member.tokenName, @@ -847,7 +847,7 @@ const memberTokenMetadata: { mintKeypair: Keypair; metadata: TokenMetadata }[] = uri: member.tokenUri, updateAuthority: payer.publicKey, additionalMetadata: Object.entries( - member.tokenAdditionalMetadata || [], + member.tokenAdditionalMetadata || [] ).map(([trait_type, value]) => [trait_type, value]), } as TokenMetadata, })); @@ -877,7 +877,7 @@ export async function createTokenMember( mintKeypair: Keypair, decimals: number, metadata: TokenMetadata, - groupAddress: PublicKey, + groupAddress: PublicKey ): Promise; ``` @@ -932,7 +932,7 @@ export async function createTokenMember( mintKeypair: Keypair, decimals: number, metadata: TokenMetadata, - groupAddress: PublicKey, + groupAddress: PublicKey ): Promise { const extensions: ExtensionType[] = [ ExtensionType.GroupMemberPointer, @@ -958,20 +958,20 @@ export async function createTokenMember( mintKeypair.publicKey, payer.publicKey, mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMetadataPointerInstruction( mintKeypair.publicKey, payer.publicKey, mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( mintKeypair.publicKey, decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMemberInstruction({ group: groupAddress, @@ -990,13 +990,13 @@ export async function createTokenMember( symbol: metadata.symbol, updateAuthority: payer.publicKey, uri: metadata.uri, - }), + }) ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, - [payer, mintKeypair], + [payer, mintKeypair] ); return signature; @@ -1014,11 +1014,11 @@ for (const memberMetadata of memberTokenMetadata) { memberMetadata.mintKeypair, decimals, memberMetadata.metadata, - groupMintKeypair.publicKey, + groupMintKeypair.publicKey ); console.log( - `Created ${memberMetadata.metadata.name} NFT:\n${getExplorerLink("tx", signature, "localnet")}\n`, + `Created ${memberMetadata.metadata.name} NFT:\n${getExplorerLink("tx", signature, "localnet")}\n` ); } ``` @@ -1031,11 +1031,11 @@ for (const member of membersMetadata) { connection, member.mint.publicKey, "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const memberMetadata = await getTokenMetadata( connection, - member.mint.publicKey, + member.mint.publicKey ); const metadataPointerState = getMetadataPointerState(memberMint); const memberPointerData = getGroupMemberPointerState(memberMint); @@ -1045,12 +1045,12 @@ for (const member of membersMetadata) { console.log("Member Mint: ", memberMint.address.toBase58()); console.log( "Metadata Pointer Account: ", - metadataPointerState?.metadataAddress?.toBase58(), + metadataPointerState?.metadataAddress?.toBase58() ); console.log("Group Account: ", memberData?.group?.toBase58()); console.log( "Member Pointer Account: ", - memberPointerData?.memberAddress?.toBase58(), + memberPointerData?.memberAddress?.toBase58() ); console.log("Member Number: ", memberData?.memberNumber); console.log("\n--- METADATA ---\n"); diff --git a/content/courses/token-extensions/immutable-owner.md b/content/courses/token-extensions/immutable-owner.md index bfd83f4f3..9b02e7789 100644 --- a/content/courses/token-extensions/immutable-owner.md +++ b/content/courses/token-extensions/immutable-owner.md @@ -4,8 +4,7 @@ objectives: - Create token accounts with an immutable owner - Explain the use cases of the immutable owner extension - Experiment with the rules of the extension -description: - "Make a token that ensures the account storing the tokens cannot change owner." +description: "Make a token that ensures the account storing the tokens cannot change owner." --- ## Summary @@ -85,7 +84,7 @@ the immutable owner extension. const initializeImmutableOwnerInstruction = createInitializeImmutableOwnerInstruction( tokenAccount, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -97,7 +96,7 @@ const initializeAccountInstruction = createInitializeAccountInstruction( tokenAccount, mint, owner.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -108,7 +107,7 @@ blockchain. const transaction = new Transaction().add( createTokenAccountInstruction, initializeImmutableOwnerInstruction, - initializeAccountInstruction, + initializeAccountInstruction ); transaction.feePayer = payer.publicKey; @@ -224,7 +223,7 @@ const mint = await createMint( 2, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -268,7 +267,7 @@ export async function createTokenAccountWithImmutableOwner( mint: PublicKey, payer: Keypair, owner: Keypair, - tokenAccountKeypair: Keypair, + tokenAccountKeypair: Keypair ): Promise { // Create account instruction @@ -319,7 +318,7 @@ function is used to generate this instruction. const initializeImmutableOwnerInstruction = createInitializeImmutableOwnerInstruction( tokenAccount, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -334,7 +333,7 @@ const initializeAccountInstruction = createInitializeAccountInstruction( tokenAccount, mint, owner.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -346,7 +345,7 @@ with an immutable owner. const transaction = new Transaction().add( createTokenAccountInstruction, initializeImmutableOwnerInstruction, - initializeAccountInstruction, + initializeAccountInstruction ); transaction.feePayer = payer.publicKey; @@ -424,13 +423,13 @@ try { otherOwner.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.error("You should not be able to change the owner of the account."); } catch (error) { console.log( - `✅ - We expected this to fail because the account is immutable, and cannot change owner.`, + `✅ - We expected this to fail because the account is immutable, and cannot change owner.` ); } ``` @@ -460,13 +459,13 @@ try { otherOwner.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.error("You should not be able to change the owner of the account."); } catch (error) { console.log( - `✅ - We expected this to fail because the associated token account is immutable, and cannot change owner.`, + `✅ - We expected this to fail because the associated token account is immutable, and cannot change owner.` ); } ``` diff --git a/content/courses/token-extensions/interest-bearing-token.md b/content/courses/token-extensions/interest-bearing-token.md index 316d440b6..41508a4de 100644 --- a/content/courses/token-extensions/interest-bearing-token.md +++ b/content/courses/token-extensions/interest-bearing-token.md @@ -88,7 +88,7 @@ createInitializeMintInstruction( decimals, mintAuthority.publicKey, null, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -112,7 +112,7 @@ const tokenInfo = await getAccount( connection, payerTokenAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); /** @@ -131,7 +131,7 @@ const uiAmount = await amountToUiAmount( payer, mint, tokenInfo.amount, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("UI Amount: ", uiAmount); @@ -182,7 +182,7 @@ await setAuthority( otherAccount.publicKey, // new rate authority, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await updateRateInterestBearingMint( @@ -193,7 +193,7 @@ await updateRateInterestBearingMint( 10, // updated rate undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -324,7 +324,7 @@ export async function createTokenWithInterestRateExtension( mintLen: number, rateAuthority: Keypair, rate: number, - mintKeypair: Keypair, + mintKeypair: Keypair ) { const mintAuthority = payer; const decimals = 9; @@ -366,22 +366,22 @@ const mintTransaction = new Transaction().add( mint, rateAuthority.publicKey, rate, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( mint, decimals, mintAuthority.publicKey, null, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - undefined, + undefined ); ``` @@ -408,7 +408,7 @@ await createTokenWithInterestRateExtension( mint, rateAuthority, rate, - mintKeypair, + mintKeypair ); // Create associated token account @@ -418,7 +418,7 @@ const payerTokenAccount = await createAssociatedTokenAccount( mint, payer.publicKey, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -456,7 +456,7 @@ async function getInterestBearingMint(inputs: GetInterestBearingMint) { connection, mint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // retrieves the interest state of mint @@ -491,12 +491,12 @@ try { 0, // updated rate undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const newRate = await getInterestBearingMint({ connection, mint }); console.log( - `✅ - We expected this to pass because the rate has been updated. Old rate: ${initialRate}. New rate: ${newRate}`, + `✅ - We expected this to pass because the rate has been updated. Old rate: ${initialRate}. New rate: ${newRate}` ); } catch (error) { console.error("You should be able to update the interest."); @@ -527,12 +527,12 @@ try { 0, // updated rate undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("You should be able to update the interest."); } catch (error) { console.error( - `✅ - We expected this to fail because the owner is incorrect.`, + `✅ - We expected this to fail because the owner is incorrect.` ); } ``` @@ -565,14 +565,14 @@ the new accrued interest: 100, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const tokenInfo = await getAccount( connection, payerTokenAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Convert amount to UI amount with accrued interest @@ -581,11 +581,11 @@ the new accrued interest: payer, mint, tokenInfo.amount, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - `Amount with accrued interest at ${rate}: ${tokenInfo.amount} tokens = ${uiAmount}`, + `Amount with accrued interest at ${rate}: ${tokenInfo.amount} tokens = ${uiAmount}` ); } } @@ -615,7 +615,7 @@ const mintAccount = await getMint( connection, mint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Get Interest Config for Mint Account @@ -624,7 +624,7 @@ const interestBearingMintConfig = console.log( "\nMint Config:", - JSON.stringify(interestBearingMintConfig, null, 2), + JSON.stringify(interestBearingMintConfig, null, 2) ); ``` @@ -662,7 +662,7 @@ try { otherAccount.publicKey, // new rate authority, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await updateRateInterestBearingMint( @@ -673,17 +673,17 @@ try { 10, // updated rate undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const newRate = await getInterestBearingMint({ connection, mint }); console.log( - `✅ - We expected this to pass because the rate can be updated with the new authority. New rate: ${newRate}`, + `✅ - We expected this to pass because the rate can be updated with the new authority. New rate: ${newRate}` ); } catch (error) { console.error( - `You should be able to update the interest with new rate authority.`, + `You should be able to update the interest with new rate authority.` ); } ``` diff --git a/content/courses/token-extensions/intro-to-token-extensions-program.md b/content/courses/token-extensions/intro-to-token-extensions-program.md index 607df31dd..f09fa9cab 100644 --- a/content/courses/token-extensions/intro-to-token-extensions-program.md +++ b/content/courses/token-extensions/intro-to-token-extensions-program.md @@ -90,8 +90,10 @@ accounts, and 12 on the Mint accounts: Accounts (ATAs). To avoid these issues, we can use this extension which makes it impossible to reassign account ownership. -All Token Extension Program ATAs have the immutable -ownership extension baked in. + + All Token Extension Program ATAs have the immutable ownership extension baked + in.{" "} + - **Default account state** Mint creators can use this extension which forces all new token accounts to be frozen. This way, users must eventually interact @@ -113,8 +115,10 @@ ownership extension baked in. However, the introduction of the close authority extension now allows for the closure of mint accounts as well. -To close a mint account, the supply has to be 0. So all -tokens minted must be burned. + + To close a mint account, the supply has to be 0. So all tokens minted must be + burned.{" "} + - **Interest-bearing tokens** Tokens which have constantly fluctuating values, showing the updated values in clients requires proxies that require regular @@ -173,8 +177,10 @@ tokens minted must be burned. - **Confidential transfers** This extension enhances privacy of the transactions without revealing key details of the transaction such as the amount. -These extensions can be mixes and matched to make a -plethora of highly functional tokens. + + These extensions can be mixes and matched to make a plethora of highly + functional tokens.{" "} + We'll dig deeper into each extension in separate lessons. @@ -201,9 +207,11 @@ installation, verify it by running the following command: spl-token --version ``` -Make sure you follow each step in the -[guide above](https://spl.solana.com/token#setup) as it also describes how to -initialize a local wallet and airdrop SOL. + + Make sure you follow each step in the [guide + above](https://spl.solana.com/token#setup) as it also describes how to + initialize a local wallet and airdrop SOL.{" "} + #### 2. Creating a mint with close authority @@ -253,8 +261,10 @@ Now that we have a mint, we can close it with the following where spl-token close-mint ``` -By closing the account, we reclaim the rent lamports on the -mint account. Remember, the supply on the mint must be zero. + + By closing the account, we reclaim the rent lamports on the mint account. + Remember, the supply on the mint must be zero. + As a challenge, repeat this process, but before closing the mint account, mint some tokens and then try to close it - see what happens. (Spoiler, it'll fail) diff --git a/content/courses/token-extensions/non-transferable-token.md b/content/courses/token-extensions/non-transferable-token.md index 34bc4dfe7..e1f6792b1 100644 --- a/content/courses/token-extensions/non-transferable-token.md +++ b/content/courses/token-extensions/non-transferable-token.md @@ -75,7 +75,7 @@ initializes the non-transferable extension. const initializeNonTransferableMintInstruction = createInitializeNonTransferableMintInstruction( mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -87,7 +87,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, null, // Confirmation Config - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -97,14 +97,14 @@ Lastly, add all of the instructions to a transaction and send to Solana. const mintTransaction = new Transaction().add( createAccountInstruction, initializeNonTransferableMintInstruction, - initializeMintInstruction, + initializeMintInstruction ); await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); ``` @@ -245,7 +245,7 @@ export async function createNonTransferableMint( connection: Connection, payer: Keypair, mintKeypair: Keypair, - decimals: number, + decimals: number ): Promise { const extensions = [ExtensionType.NonTransferable]; const mintLength = getMintLen(extensions); @@ -264,22 +264,22 @@ export async function createNonTransferableMint( }), createInitializeNonTransferableMintInstruction( mintKeypair.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( mintKeypair.publicKey, decimals, payer.publicKey, null, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); return signature; @@ -333,7 +333,7 @@ const ata = ( undefined, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ) ).address; @@ -349,12 +349,12 @@ await mintTo( amount, [payer], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const tokenBalance = await connection.getTokenAccountBalance(ata, "finalized"); console.log( - `Account ${ata.toBase58()} now has ${tokenBalance.value.uiAmount} token.`, + `Account ${ata.toBase58()} now has ${tokenBalance.value.uiAmount} token.` ); ``` @@ -393,7 +393,7 @@ const destinationAccount = await createAccount( destinationKeypair.publicKey, undefined, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -414,13 +414,13 @@ try { decimals, [destinationKeypair], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); } catch (e) { console.log( "This transfer is failing because the mint is non-transferable. Check out the program logs: ", (e as any).logs, - "\n\n", + "\n\n" ); } ``` diff --git a/content/courses/token-extensions/permanent-delegate.md b/content/courses/token-extensions/permanent-delegate.md index 2474c1cfe..b50c95cf5 100644 --- a/content/courses/token-extensions/permanent-delegate.md +++ b/content/courses/token-extensions/permanent-delegate.md @@ -91,7 +91,7 @@ createInitializeMintInstruction( decimals, mintAuthority.publicKey, null, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -134,7 +134,7 @@ await transferChecked( decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -172,7 +172,7 @@ await burnChecked( decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -215,7 +215,7 @@ await approveChecked( decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Newly assigned delegate can now transfer from an account @@ -230,7 +230,7 @@ await transferChecked( decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -406,7 +406,7 @@ const initializePermanentDelegateInstruction = createInitializePermanentDelegateInstruction( mint, permanentDelegate.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -421,7 +421,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, mintAuthority.publicKey, // Designated Mint Authority null, // No Freeze Authority - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -432,7 +432,7 @@ blockchain: const transaction = new Transaction().add( createAccountInstruction, initializePermanentDelegateInstruction, - initializeMintInstruction, + initializeMintInstruction ); return await sendAndConfirmTransaction(connection, transaction, [ @@ -474,7 +474,7 @@ export async function createTokenExtensionMintWithPermanentDelegate( payer: Keypair, mintKeypair: Keypair, decimals: number = 2, - permanentDelegate: Keypair, + permanentDelegate: Keypair ): Promise { const mintAuthority = payer; const mint = mintKeypair.publicKey; @@ -495,7 +495,7 @@ export async function createTokenExtensionMintWithPermanentDelegate( createInitializePermanentDelegateInstruction( mint, permanentDelegate.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initializeMintInstruction = createInitializeMintInstruction( @@ -503,13 +503,13 @@ export async function createTokenExtensionMintWithPermanentDelegate( decimals, mintAuthority.publicKey, // Designated Mint Authority null, // No Freeze Authority - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transaction = new Transaction().add( createAccountInstruction, initializePermanentDelegateInstruction, - initializeMintInstruction, + initializeMintInstruction ); return await sendAndConfirmTransaction(connection, transaction, [ @@ -532,7 +532,7 @@ function: async function printBalances( connection: Connection, tokenAccounts: PublicKey[], - names: string[], + names: string[] ) { if (tokenAccounts.length !== names.length) throw new Error("Names needs to be one to one with accounts"); @@ -542,7 +542,7 @@ async function printBalances( connection, tokenAccounts[i], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log(`${names[i]}: ${tokenInfo.amount}`); @@ -568,7 +568,7 @@ await createTokenExtensionMintWithPermanentDelegate( payer, // Also known as alice mintKeypair, decimals, - defaultState, + defaultState ); ``` @@ -589,7 +589,7 @@ const aliceAccount = await createAccount( alice.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const bobAccount = await createAccount( @@ -599,7 +599,7 @@ const bobAccount = await createAccount( bob.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const carolAccount = await createAccount( @@ -609,7 +609,7 @@ const carolAccount = await createAccount( carol.publicKey, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -638,7 +638,7 @@ for (const holder of tokenAccounts) { amountToMint, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); } @@ -730,10 +730,10 @@ out the balances of our accounts: decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - "✅ Since Alice is the permanent delegate, she has control over all token accounts of this mint", + "✅ Since Alice is the permanent delegate, she has control over all token accounts of this mint" ); await printBalances(connection, tokenAccounts, names); } catch (error) { @@ -776,12 +776,12 @@ Similar to the previous test we can create this test by calling decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Bob should not be able to transfer tokens"); } catch (error) { console.log( - "✅ We expect this to fail because Bob does not have authority over Alice's funds", + "✅ We expect this to fail because Bob does not have authority over Alice's funds" ); await printBalances(connection, tokenAccounts, names); } @@ -821,10 +821,10 @@ the balances: decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - "✅ Since Alice is the permanent delegate, she has control and can transfer Bob's tokens to Carol", + "✅ Since Alice is the permanent delegate, she has control and can transfer Bob's tokens to Carol" ); await printBalances(connection, tokenAccounts, names); } catch (error) { @@ -863,10 +863,10 @@ We'll do this by calling `burnChecked` and then printing out the balances: decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - "✅ Since Alice is the permanent delegate, she has control and can burn Bob's tokens", + "✅ Since Alice is the permanent delegate, she has control and can burn Bob's tokens" ); await printBalances(connection, tokenAccounts, names); } catch (error) { @@ -903,13 +903,13 @@ expected to fail as `bob` doesn't have any control over the token accounts. decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await printBalances(connection, tokenAccounts, names); console.error("Bob should not be able to burn the tokens"); } catch (error) { console.log( - "✅ We expect this to fail since Bob is not the permanent delegate and has no control over the tokens", + "✅ We expect this to fail since Bob is not the permanent delegate and has no control over the tokens" ); } } @@ -949,7 +949,7 @@ Add the following test: decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); await transferChecked( @@ -963,11 +963,11 @@ Add the following test: decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - "✅ Since Alice is the permanent delegate, she can allow Carol to transfer Bob's tokens to Carol", + "✅ Since Alice is the permanent delegate, she can allow Carol to transfer Bob's tokens to Carol" ); await printBalances(connection, tokenAccounts, names); } @@ -1000,11 +1000,11 @@ tokens to herself. This is expected to fail. decimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); } catch (e) { console.log( - `✅ We expect this to fail since Carol already transferred ${amountToTransfer} tokens and has no more allotted`, + `✅ We expect this to fail since Carol already transferred ${amountToTransfer} tokens and has no more allotted` ); } } diff --git a/content/courses/token-extensions/required-memo.md b/content/courses/token-extensions/required-memo.md index 54baeaeb6..09c4592ca 100644 --- a/content/courses/token-extensions/required-memo.md +++ b/content/courses/token-extensions/required-memo.md @@ -68,7 +68,7 @@ const initializeAccountInstruction = createInitializeAccountInstruction( tokenAccountKeypair.publicKey, mint, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -81,7 +81,7 @@ const enableRequiredMemoTransfersInstruction = tokenAccountKeypair.publicKey, payer.publicKey, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -92,13 +92,13 @@ is created with the required memo extension. const transaction = new Transaction().add( createAccountInstruction, initializeAccountInstruction, - enableRequiredMemoTransfersInstruction, + enableRequiredMemoTransfersInstruction ); const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [payer, tokenAccountKeypair], // Signers + [payer, tokenAccountKeypair] // Signers ); ``` @@ -124,8 +124,8 @@ const transaction = new Transaction().add( payer.publicKey, amountToTransfer, undefined, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); await sendAndConfirmTransaction(connection, transaction, [payer]); ``` @@ -157,7 +157,7 @@ await disableRequiredMemoTransfers( payer, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -278,7 +278,7 @@ const mint = await createMint( mintDecimals, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -316,7 +316,7 @@ export async function createTokenWithMemoExtension( connection: Connection, payer: Keypair, tokenAccountKeypair: Keypair, - mint: PublicKey, + mint: PublicKey ): Promise { // CREATE ACCOUNT INSTRUCTION @@ -360,7 +360,7 @@ const initializeAccountInstruction = createInitializeAccountInstruction( tokenAccountKeypair.publicKey, mint, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -376,7 +376,7 @@ const enableRequiredMemoTransfersInstruction = tokenAccountKeypair.publicKey, payer.publicKey, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -388,13 +388,13 @@ blockchain and return the signature const transaction = new Transaction().add( createAccountInstruction, initializeAccountInstruction, - enableRequiredMemoTransfersInstruction, + enableRequiredMemoTransfersInstruction ); const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [payer, tokenAccountKeypair], // Signers + [payer, tokenAccountKeypair] // Signers ); return transactionSignature; @@ -410,14 +410,14 @@ await createTokenWithMemoExtension( connection, payer, ourTokenAccountKeypair, - mint, + mint ); await createTokenWithMemoExtension( connection, payer, otherTokenAccountKeypair, - mint, + mint ); ``` @@ -435,7 +435,7 @@ await mintTo( amountToMint, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -469,8 +469,8 @@ try { payer.publicKey, amountToTransfer, undefined, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); await sendAndConfirmTransaction(connection, transaction, [payer]); @@ -478,7 +478,7 @@ try { console.error("You should not be able to transfer without a memo."); } catch (error) { console.log( - `✅ - We expected this to fail because you need to send a memo with the transfer.`, + `✅ - We expected this to fail because you need to send a memo with the transfer.` ); } ``` @@ -514,8 +514,8 @@ const transaction = new Transaction().add( payer.publicKey, amountToTransfer, undefined, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); await sendAndConfirmTransaction(connection, transaction, [payer]); @@ -523,11 +523,11 @@ const accountAfterMemoTransfer = await getAccount( connection, otherTokenAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - `✅ - We have transferred ${accountAfterMemoTransfer.amount} tokens to ${otherTokenAccount} with the memo: ${message}`, + `✅ - We have transferred ${accountAfterMemoTransfer.amount} tokens to ${otherTokenAccount} with the memo: ${message}` ); ``` @@ -552,7 +552,7 @@ await disableRequiredMemoTransfers( payer, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Transfer tokens to otherTokenAccount @@ -563,8 +563,8 @@ const transfer = new Transaction().add( payer.publicKey, amountToTransfer, undefined, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); await sendAndConfirmTransaction(connection, transfer, [payer]); @@ -573,7 +573,7 @@ const accountAfterDisable = await getAccount( connection, otherTokenAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // Re-enable memo transfers to show it exists @@ -584,11 +584,11 @@ await enableRequiredMemoTransfers( payer, undefined, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - `✅ - We have transferred ${accountAfterDisable.amount} tokens to ${otherTokenAccount} without a memo.`, + `✅ - We have transferred ${accountAfterDisable.amount} tokens to ${otherTokenAccount} without a memo.` ); ``` diff --git a/content/courses/token-extensions/token-extensions-in-the-client.md b/content/courses/token-extensions/token-extensions-in-the-client.md index 511ccdebe..c66fa7a18 100644 --- a/content/courses/token-extensions/token-extensions-in-the-client.md +++ b/content/courses/token-extensions/token-extensions-in-the-client.md @@ -5,8 +5,7 @@ objectives: client applications - Become proficient in utilizing the SPL TypeScript library for comprehensive token operations -description: - "Use mints with Token Extensions program from TS in web and mobile apps." +description: "Use mints with Token Extensions program from TS in web and mobile apps." --- ## Summary @@ -62,10 +61,12 @@ both the `TOKEN_PROGRAM_ID` and `TOKEN_2022_PROGRAM_ID` constants, along with all of its helper functions for creating and minting tokens that take a program ID as input. -`spl-token` defaults to using the `TOKEN_PROGRAM_ID` unless -otherwise specified. Make sure to explicitly pass the `TOKEN_2022_PROGRAM_ID` -for all function calls related to the Token Extensions Program. Otherwise, you -will get the following error: `TokenInvalidAccountOwnerError`. + + `spl-token` defaults to using the `TOKEN_PROGRAM_ID` unless otherwise + specified. Make sure to explicitly pass the `TOKEN_2022_PROGRAM_ID` for all + function calls related to the Token Extensions Program. Otherwise, you will + get the following error: `TokenInvalidAccountOwnerError`.{" "} + ### Considerations when working with both Token and Extension Tokens @@ -101,7 +102,7 @@ const tokenAccount = await getOrCreateAssociatedTokenAccount( true, "finalized", { commitment: "finalized" }, - tokenProgramId, // TOKEN_PROGRAM_ID for Token Program tokens and TOKEN_2022_PROGRAM_ID for Token Extensions Program tokens + tokenProgramId // TOKEN_PROGRAM_ID for Token Program tokens and TOKEN_2022_PROGRAM_ID for Token Extensions Program tokens ); ``` @@ -111,7 +112,7 @@ To re-create the ATA's address from scratch, we can use the ```ts function findAssociatedTokenAddress( walletAddress: PublicKey, - tokenMintAddress: PublicKey, + tokenMintAddress: PublicKey ): PublicKey { return PublicKey.findProgramAddressSync( [ @@ -119,7 +120,7 @@ function findAssociatedTokenAddress( TOKEN_PROGRAM_ID.toBuffer(), // replace TOKEN_PROGRAM_ID with TOKEN_2022_PROGRAM_ID for Token22 tokens tokenMintAddress.toBuffer(), ], - ASSOCIATED_TOKEN_PROGRAM_ID, + ASSOCIATED_TOKEN_PROGRAM_ID )[0]; } ``` @@ -134,7 +135,7 @@ tokens. All we have to do is provide the correct token program: ```ts const tokenAccounts = await connection.getTokenAccountsByOwner( walletPublicKey, - { programId: TOKEN_PROGRAM_ID }, // or TOKEN_2022_PROGRAM_ID + { programId: TOKEN_PROGRAM_ID } // or TOKEN_2022_PROGRAM_ID ); ``` @@ -146,18 +147,20 @@ function like `getTokenAccountsByOwner`, and then call it twice, once with const allOwnedTokens = []; const tokenAccounts = await connection.getTokenAccountsByOwner( wallet.publicKey, - { programId: TOKEN_PROGRAM_ID }, + { programId: TOKEN_PROGRAM_ID } ); const tokenExtensionAccounts = await connection.getTokenAccountsByOwner( wallet.publicKey, - { programId: TOKEN_2022_PROGRAM_ID }, + { programId: TOKEN_2022_PROGRAM_ID } ); allOwnedTokens.push(...tokenAccounts, ...tokenExtensionAccounts); ``` -It's likely you'll want to store and associate the token -program with the token upon fetching. + + It's likely you'll want to store and associate the token program with the + token upon fetching. + #### Check owning program @@ -176,13 +179,14 @@ const programId = accountInfo.value.owner; // will return TOKEN_PROGRAM_ID for T //we now use the programId to fetch the tokens const tokenAccounts = await connection.getTokenAccountsByOwner( wallet.publicKey, - { programId }, + { programId } ); ``` -After you fetch the owning program, it may be a good idea -to save that owner and associate it with the mints/tokens you are -handling. + + After you fetch the owning program, it may be a good idea to save that owner + and associate it with the mints/tokens you are handling. + ## Lab - Add Extension Token support to a script @@ -305,7 +309,7 @@ export async function createAndMintToken( tokenProgramId: PublicKey, payer: Keypair, decimals: number, - mintAmount: number, + mintAmount: number ): Promise { console.log("\\nCreating a new mint..."); const mint = await createMint( @@ -318,7 +322,7 @@ export async function createAndMintToken( { commitment: "finalized", // confirmOptions argument }, - tokenProgramId, + tokenProgramId ); console.log("\\nFetching mint info..."); @@ -336,7 +340,7 @@ export async function createAndMintToken( true, "finalized", { commitment: "finalized" }, - tokenProgramId, + tokenProgramId ); console.log(`Associated token account: ${tokenAccount.address.toBase58()}`); @@ -351,7 +355,7 @@ export async function createAndMintToken( mintAmount, [payer], { commitment: "finalized" }, - tokenProgramId, + tokenProgramId ); return mint; @@ -385,14 +389,14 @@ const tokenProgramMint = await createAndMintToken( TOKEN_PROGRAM_ID, payer, 0, - 1000, + 1000 ); const tokenExtensionProgramMint = await createAndMintToken( connection, TOKEN_2022_PROGRAM_ID, payer, 0, - 1000, + 1000 ); ``` @@ -454,7 +458,7 @@ export async function fetchTokenInfo( connection: Connection, owner: PublicKey, programId: PublicKey, - type: TokenTypeForDisplay, + type: TokenTypeForDisplay ): Promise { const tokenAccounts = await connection.getTokenAccountsByOwner(owner, { programId, @@ -469,7 +473,7 @@ export async function fetchTokenInfo( connection, accountData.mint, "finalized", - programId, + programId ); ownedTokens.push({ @@ -500,14 +504,14 @@ myTokens.push( connection, payer.publicKey, TOKEN_PROGRAM_ID, - "Token Program", + "Token Program" )), ...(await fetchTokenInfo( connection, payer.publicKey, TOKEN_2022_PROGRAM_ID, - "Token Extensions Program", - )), + "Token Extensions Program" + )) ); printTableData(myTokens); @@ -539,7 +543,7 @@ The final function will look like this: export async function fetchTokenProgramFromAccount( connection: Connection, - mint: PublicKey, + mint: PublicKey ) { // Find the program ID from the mint const accountInfo = await connection.getParsedAccountInfo(mint); @@ -564,11 +568,11 @@ import { // previous code const tokenProgramTokenProgram = await fetchTokenProgramFromAccount( connection, - tokenProgramMint, + tokenProgramMint ); const tokenExtensionProgramTokenProgram = await fetchTokenProgramFromAccount( connection, - tokenExtensionProgramMint, + tokenExtensionProgramMint ); if (!tokenProgramTokenProgram.equals(TOKEN_PROGRAM_ID)) diff --git a/content/courses/token-extensions/token-extensions-metadata.md b/content/courses/token-extensions/token-extensions-metadata.md index 2e06244be..724487831 100644 --- a/content/courses/token-extensions/token-extensions-metadata.md +++ b/content/courses/token-extensions/token-extensions-metadata.md @@ -46,8 +46,9 @@ Metadata extensions fix this by introducing two extensions: [Token-Metadata Interface](https://github.com/solana-labs/solana-program-library/tree/master/token-metadata/) which allows us to store the metadata in the mint itself. -The `metadata` extension must be used in conjunction with -the `metadata-pointer` extension which points back to the mint itself. + + The `metadata` extension must be used in conjunction with the + `metadata-pointer` extension which points back to the mint itself. ### Metadata-Pointer extension: @@ -88,7 +89,7 @@ function createInitializeMetadataPointerInstruction( mint: PublicKey, authority: PublicKey | null, metadataAddress: PublicKey | null, - programId: PublicKey, + programId: PublicKey ); ``` @@ -111,7 +112,7 @@ function createUpdateMetadataPointerInstruction( authority: PublicKey, metadataAddress: PublicKey | null, multiSigners: (Signer | PublicKey)[] = [], - programId: PublicKey = TOKEN_2022_PROGRAM_ID, + programId: PublicKey = TOKEN_2022_PROGRAM_ID ); ``` @@ -175,7 +176,7 @@ const initMetadataPointerInstructions = mint.publicKey, payer.publicKey, metadataAccount, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initMintInstructions = createInitializeMintInstruction( @@ -183,7 +184,7 @@ const initMintInstructions = createInitializeMintInstruction( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -194,7 +195,7 @@ Solana network: const transaction = new Transaction().add( createMintAccountInstructions, initMetadataPointerInstructions, - initMintInstructions, + initMintInstructions ); const sig = await sendAndConfirmTransaction(connection, transaction, [ payer, @@ -209,10 +210,11 @@ Program. This extension allows us to store the metadata directly _in_ the mint itself! This eliminates the need for a separate account, greatly simplifying the handling of metadata. -The `metadata` extension works directly with the -`metadata-pointer` extension. During the mint creation, you should also add the -`metadata-pointer` extension, pointed at the mint itself. Check out the -[Solana Token Extensions Program docs](https://spl.solana.com/token-2022/extensions#metadata) + + The `metadata` extension works directly with the `metadata-pointer` extension. + During the mint creation, you should also add the `metadata-pointer` + extension, pointed at the mint itself. Check out the [Solana Token Extensions + Program docs](https://spl.solana.com/token-2022/extensions#metadata) The added fields and functions in the metadata extension follow the @@ -291,7 +293,7 @@ export interface InitializeInstructionArgs { } export function createInitializeInstruction( - args: InitializeInstructionArgs, + args: InitializeInstructionArgs ): TransactionInstruction; ``` @@ -324,17 +326,18 @@ export interface UpdateFieldInstruction { } export function createUpdateFieldInstruction( - args: UpdateFieldInstruction, + args: UpdateFieldInstruction ): TransactionInstruction; ``` -If the metadata you are updating requires more space than -the initial allocated space, you'll have to pair it with a `system.transfer` to -have enough rent for the `createUpdateFieldInstruction` to realloc with. You can -get the extra space needed with `getAdditionalRentForUpdatedMetadata`. Or if -you're calling this update as a standalone, you can use the -`tokenMetadataUpdateFieldWithRentTransfer` helper to do all of this at -once. + + If the metadata you are updating requires more space than the initial + allocated space, you'll have to pair it with a `system.transfer` to have + enough rent for the `createUpdateFieldInstruction` to realloc with. You can + get the extra space needed with `getAdditionalRentForUpdatedMetadata`. Or if + you're calling this update as a standalone, you can use the + `tokenMetadataUpdateFieldWithRentTransfer` helper to do all of this at once. + The function `createRemoveKeyInstruction` returns the instruction that removes the `additional_metadata` field from a token-metadata account. @@ -358,7 +361,7 @@ export interface RemoveKeyInstructionArgs { } export function createRemoveKeyInstruction( - args: RemoveKeyInstructionArgs, + args: RemoveKeyInstructionArgs ): TransactionInstruction; ``` @@ -382,7 +385,7 @@ export interface UpdateAuthorityInstructionArgs { } export function createUpdateAuthorityInstruction( - args: UpdateAuthorityInstructionArgs, + args: UpdateAuthorityInstructionArgs ): TransactionInstruction; ``` @@ -410,7 +413,7 @@ export interface EmitInstructionArgs { } export function createEmitInstruction( - args: EmitInstructionArgs, + args: EmitInstructionArgs ): TransactionInstruction; ``` @@ -455,7 +458,7 @@ export async function getTokenMetadata( connection: Connection, address: PublicKey, commitment?: Commitment, - programId = TOKEN_2022_PROGRAM_ID, + programId = TOKEN_2022_PROGRAM_ID ): Promise; ``` @@ -542,7 +545,7 @@ const initMetadataPointerInstructions = mint.publicKey, payer.publicKey, mint.publicKey, // we will point to the mint it self as the metadata account - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initMintInstructions = createInitializeMintInstruction( @@ -550,7 +553,7 @@ const initMintInstructions = createInitializeMintInstruction( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initMetadataInstruction = createInitializeInstruction({ @@ -581,7 +584,7 @@ const transaction = new Transaction().add( initMetadataPointerInstructions, initMintInstructions, initMetadataInstruction, - updateMetadataFieldInstructions, // if you want to add any custom field + updateMetadataFieldInstructions // if you want to add any custom field ); const signature = await sendAndConfirmTransaction(connection, transaction, [ payer, @@ -637,8 +640,9 @@ dependencies, two other files have been provided in the `src/` directory. **`cat.png`** is the image we'll use for the NFT. Feel free to replace it with your own image. -we are using Irys on devnet to upload files, this is capped -at 100 KiB. + + we are using Irys on devnet to upload files, this is capped at 100 KiB.{" "} + **`helpers.ts`** file provides us with a useful helper function `uploadOffChainMetadata`. @@ -754,7 +758,7 @@ const tokenUri = await uploadOffChainMetadata( tokenExternalUrl, tokenAdditionalMetadata, }, - payer, + payer ); // You can log the URI here and run the code to test it @@ -828,7 +832,7 @@ import { } from "@solana/spl-token"; export default async function createNFTWithEmbeddedMetadata( - inputs: CreateNFTInputs, + inputs: CreateNFTInputs ) { const { payer, @@ -878,7 +882,7 @@ const metadata: TokenMetadata = { uri: tokenUri, // additionalMetadata: [['customField', 'customValue']], additionalMetadata: Object.entries(tokenAdditionalMetadata || []).map( - ([key, value]) => [key, value], + ([key, value]) => [key, value] ), }; ``` @@ -902,7 +906,7 @@ get our first instruction: const mintLen = getMintLen([ExtensionType.MetadataPointer]); const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; const lamports = await connection.getMinimumBalanceForRentExemption( - mintLen + metadataLen, + mintLen + metadataLen ); const createMintAccountInstruction = SystemProgram.createAccount({ @@ -914,8 +918,9 @@ const createMintAccountInstruction = SystemProgram.createAccount({ }); ``` -The more information in the metadata, the more it -costs. + + The more information in the metadata, the more it costs. + Step 3 has us initializing the `metadata pointer` extension. Let's do that by calling the `createInitializeMetadataPointerInstruction` function with the @@ -928,7 +933,7 @@ const initMetadataPointerInstruction = mint.publicKey, payer.publicKey, mint.publicKey, // Metadata account - points to itself - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -942,7 +947,7 @@ const initMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -981,7 +986,7 @@ for (const attributes of Object.entries(tokenAdditionalMetadata || [])) { field: attributes[0], value: attributes[1], programId: TOKEN_2022_PROGRAM_ID, - }), + }) ); } ``` @@ -1000,14 +1005,14 @@ const ata = await getAssociatedTokenAddress( mint.publicKey, payer.publicKey, false, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const createATAInstruction = createAssociatedTokenAccountInstruction( payer.publicKey, ata, payer.publicKey, mint.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const mintInstruction = createMintToCheckedInstruction( @@ -1017,7 +1022,7 @@ const mintInstruction = createMintToCheckedInstruction( supply, // NFTs should have a supply of one decimals, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // NFTs should have no mint authority so no one can mint any more of the same NFT @@ -1027,7 +1032,7 @@ const setMintTokenAuthorityInstruction = createSetAuthorityInstruction( AuthorityType.MintTokens, null, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -1044,12 +1049,12 @@ const transaction = new Transaction().add( ...setExtraMetadataInstructions, createATAInstruction, mintInstruction, - setMintTokenAuthorityInstruction, + setMintTokenAuthorityInstruction ); const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [payer, mint], + [payer, mint] ); ``` @@ -1063,7 +1068,7 @@ const accountDetails = await getAccount( connection, ata, "finalized", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Associate Token Account =====>", accountDetails); @@ -1072,7 +1077,7 @@ const mintDetails = await getMint( connection, mint.publicKey, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Mint =====>", mintDetails); @@ -1129,7 +1134,7 @@ import { } from "@solana/spl-token"; export default async function createNFTWithEmbeddedMetadata( - inputs: CreateNFTInputs, + inputs: CreateNFTInputs ) { const { payer, @@ -1153,7 +1158,7 @@ export default async function createNFTWithEmbeddedMetadata( uri: tokenUri, // additionalMetadata: [['customField', 'customValue']], additionalMetadata: Object.entries(tokenAdditionalMetadata || []).map( - ([key, value]) => [key, value], + ([key, value]) => [key, value] ), }; @@ -1161,7 +1166,7 @@ export default async function createNFTWithEmbeddedMetadata( const mintLen = getMintLen([ExtensionType.MetadataPointer]); const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; const lamports = await connection.getMinimumBalanceForRentExemption( - mintLen + metadataLen, + mintLen + metadataLen ); const createMintAccountInstruction = SystemProgram.createAccount({ @@ -1178,7 +1183,7 @@ export default async function createNFTWithEmbeddedMetadata( mint.publicKey, payer.publicKey, mint.publicKey, // Metadata account - points to itself - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // 4. Initialize the mint @@ -1187,7 +1192,7 @@ export default async function createNFTWithEmbeddedMetadata( decimals, payer.publicKey, payer.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // 5. Initialize the metadata inside the mint @@ -1212,7 +1217,7 @@ export default async function createNFTWithEmbeddedMetadata( field: attributes[0], value: attributes[1], programId: TOKEN_2022_PROGRAM_ID, - }), + }) ); } @@ -1221,14 +1226,14 @@ export default async function createNFTWithEmbeddedMetadata( mint.publicKey, payer.publicKey, false, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const createATAInstruction = createAssociatedTokenAccountInstruction( payer.publicKey, ata, payer.publicKey, mint.publicKey, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const mintInstruction = createMintToCheckedInstruction( @@ -1238,7 +1243,7 @@ export default async function createNFTWithEmbeddedMetadata( supply, // NFTs should have a supply of one decimals, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // NFTs should have no mint authority so no one can mint any more of the same NFT @@ -1248,7 +1253,7 @@ export default async function createNFTWithEmbeddedMetadata( AuthorityType.MintTokens, null, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // 8. Put all of that in one transaction and send it to the network. @@ -1260,12 +1265,12 @@ export default async function createNFTWithEmbeddedMetadata( ...setExtraMetadataInstructions, // Destructuring extra metadata fields createATAInstruction, mintInstruction, - setMintTokenAuthorityInstruction, + setMintTokenAuthorityInstruction ); const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [payer, mint], + [payer, mint] ); // 9. fetch and print the token account, the mint account, an the metadata to make sure that it is working correctly. @@ -1274,7 +1279,7 @@ export default async function createNFTWithEmbeddedMetadata( connection, ata, "finalized", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Associate Token Account =====>", accountDetails); @@ -1283,7 +1288,7 @@ export default async function createNFTWithEmbeddedMetadata( connection, mint.publicKey, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Mint =====>", mintDetails); diff --git a/content/courses/token-extensions/token-extensions-onchain.md b/content/courses/token-extensions/token-extensions-onchain.md index 768d72d29..43b244bb5 100644 --- a/content/courses/token-extensions/token-extensions-onchain.md +++ b/content/courses/token-extensions/token-extensions-onchain.md @@ -936,9 +936,11 @@ this associated token account. pub user_stake_token_account: InterfaceAccount<'info, token_interface::TokenAccount>, ``` -We are using the `InterfaceAccount` and -`token_interface::TokenAccount` types here. The `token_interface::TokenAccount` -type can only be used in conjunction with `InterfaceAccount`. + + We are using the `InterfaceAccount` and `token_interface::TokenAccount` types + here. The `token_interface::TokenAccount` type can only be used in conjunction + with `InterfaceAccount`. + Next, we add the `staking_token_mint` account. Notice we are using our first custom error here. This constraint verifies that the pubkey on the @@ -1145,9 +1147,11 @@ transferred, so their signature is a requirement for the transfer to take place. pub user: Signer<'info>, ``` -We also verify that the given user is the same pubkey -stored in the given `user_stake_entry` account. If it is not, our program will -throw the `InvalidUser` custom error. + + We also verify that the given user is the same pubkey stored in the given + `user_stake_entry` account. If it is not, our program will throw the + `InvalidUser` custom error.{" "} + The `user_token_account` is the token account where the tokens being transferred to be staked should be currently held. The mint of this token account must match diff --git a/content/courses/token-extensions/transfer-fee.md b/content/courses/token-extensions/transfer-fee.md index 6b150d899..bad7af2ed 100644 --- a/content/courses/token-extensions/transfer-fee.md +++ b/content/courses/token-extensions/transfer-fee.md @@ -4,8 +4,7 @@ objectives: - Create transfer fee configured mint - Transfer tokens of that mint - Collect fees for the transfer -description: - "Create a token that allows a fee to be charged each time the token is traded." +description: "Create a token that allows a fee to be charged each time the token is traded." --- ## Summary @@ -119,7 +118,7 @@ const initializeTransferFeeConfigInstruction = payer.publicKey, feeBasisPoints, maxFee, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -131,7 +130,7 @@ const initializeMintInstruction = createInitializeMintInstruction( decimals, payer.publicKey, null, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -142,14 +141,14 @@ off the the blockchain. const mintTransaction = new Transaction().add( createAccountInstruction, initializeTransferFeeConfigInstruction, - initializeMintInstruction, + initializeMintInstruction ); const signature = await sendAndConfirmTransaction( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); ``` @@ -221,7 +220,7 @@ const secondTransferSignature = await transferChecked( decimals, // Can also be gotten by getting the mint account details with `getMint(...)` [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -289,7 +288,7 @@ for (const accountInfo of accounts) { const unpackedAccount = unpackAccount( accountInfo.pubkey, accountInfo.account, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); // If there is withheld tokens add it to our list @@ -326,7 +325,7 @@ await withdrawWithheldTokensFromAccounts( [], accountsToWithdrawFrom, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -364,7 +363,7 @@ await harvestWithheldTokensToMint( mint, accountsToHarvestFrom, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); /** @@ -389,7 +388,7 @@ await withdrawWithheldTokensFromMint( authority, [], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -439,7 +438,7 @@ await setAuthority( newAuthority, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -592,7 +591,7 @@ export async function createMintWithTransferFee( mintKeypair: Keypair, decimals: number, feeBasisPoints: number, - maxFee: bigint, + maxFee: bigint ): Promise { const extensions = [ExtensionType.TransferFeeConfig]; const mintLength = getMintLen(extensions); @@ -615,15 +614,15 @@ export async function createMintWithTransferFee( payer.publicKey, feeBasisPoints, maxFee, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( mintKeypair.publicKey, decimals, payer.publicKey, null, - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); console.log("Sending transaction..."); @@ -631,7 +630,7 @@ export async function createMintWithTransferFee( connection, mintTransaction, [payer, mintKeypair], - { commitment: "finalized" }, + { commitment: "finalized" } ); console.log("Transaction sent"); @@ -655,7 +654,7 @@ await createMintWithTransferFee( mintKeypair, decimals, feeBasisPoints, - maxFee, + maxFee ); ``` @@ -683,7 +682,7 @@ const feeVaultAccount = await createAssociatedTokenAccount( mintKeypair.publicKey, payer.publicKey, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const initialBalance = ( @@ -720,7 +719,7 @@ const sourceAccount = await createAccount( sourceKeypair.publicKey, undefined, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Creating destination account..."); @@ -733,7 +732,7 @@ const destinationAccount = await createAccount( destinationKeypair.publicKey, undefined, { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log("Minting 10 tokens to source...\n\n"); @@ -749,7 +748,7 @@ await mintTo( amountToMint, [payer], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -815,33 +814,33 @@ const transferSignature = await transferCheckedWithFee( fee, [sourceKeypair], { commitment: "finalized" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const sourceAccountAfterTransfer = await getAccount( connection, sourceAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const destinationAccountAfterTransfer = await getAccount( connection, destinationAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const withheldAmountAfterTransfer = getTransferFeeAmount( - destinationAccountAfterTransfer, + destinationAccountAfterTransfer ); console.log(`Source Token Balance: ${sourceAccountAfterTransfer.amount}`); console.log( - `Destination Token Balance: ${destinationAccountAfterTransfer.amount}`, + `Destination Token Balance: ${destinationAccountAfterTransfer.amount}` ); console.log( - `Withheld Transfer Fees: ${withheldAmountAfterTransfer?.withheldAmount}\n`, + `Withheld Transfer Fees: ${withheldAmountAfterTransfer?.withheldAmount}\n` ); ``` @@ -877,12 +876,12 @@ const mintAccount = await getMint( connection, mint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transferFeeAmount = getTransferFeeConfig(mintAccount); const fee = calculateFee( transferFeeAmount?.newerTransferFee!, - secondTransferAmount, + secondTransferAmount ); ``` @@ -932,32 +931,32 @@ await withdrawWithheldTokensFromAccounts( [], [destinationAccount], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const withheldAccountAfterWithdraw = await getAccount( connection, destinationAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const withheldAmountAfterWithdraw = getTransferFeeAmount( - withheldAccountAfterWithdraw, + withheldAccountAfterWithdraw ); const feeVaultAfterWithdraw = await getAccount( connection, feeVaultAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - `Withheld amount after withdraw: ${withheldAmountAfterWithdraw?.withheldAmount}`, + `Withheld amount after withdraw: ${withheldAmountAfterWithdraw?.withheldAmount}` ); console.log( - `Fee vault balance after withdraw: ${feeVaultAfterWithdraw.amount}\n`, + `Fee vault balance after withdraw: ${feeVaultAfterWithdraw.amount}\n` ); ``` @@ -996,7 +995,7 @@ for (const accountInfo of accounts) { const unpackedAccount = unpackAccount( accountInfo.pubkey, accountInfo.account, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transferFeeAmount = getTransferFeeAmount(unpackedAccount); @@ -1017,7 +1016,7 @@ await withdrawWithheldTokensFromAccounts( [], accountsToWithdrawFrom, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -1058,33 +1057,33 @@ const secondTransferSignature = await transferChecked( decimals, // Can also be gotten by getting the mint account details with `getMint(...)` [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const sourceAccountAfterSecondTransfer = await getAccount( connection, sourceAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const destinationAccountAfterSecondTransfer = await getAccount( connection, destinationAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const withheldAmountAfterSecondTransfer = getTransferFeeAmount( - destinationAccountAfterTransfer, + destinationAccountAfterTransfer ); console.log(`Source Token Balance: ${sourceAccountAfterSecondTransfer.amount}`); console.log( - `Destination Token Balance: ${destinationAccountAfterSecondTransfer.amount}`, + `Destination Token Balance: ${destinationAccountAfterSecondTransfer.amount}` ); console.log( - `Withheld Transfer Fees: ${withheldAmountAfterSecondTransfer?.withheldAmount}\n`, + `Withheld Transfer Fees: ${withheldAmountAfterSecondTransfer?.withheldAmount}\n` ); ``` @@ -1112,36 +1111,36 @@ await harvestWithheldTokensToMint( mint, [destinationAccount], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const withheldAccountAfterHarvest = await getAccount( connection, destinationAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const withheldAmountAfterHarvest = getTransferFeeAmount( - withheldAccountAfterHarvest, + withheldAccountAfterHarvest ); const mintAccountAfterHarvest = await getMint( connection, mint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const mintTransferFeeConfigAfterHarvest = getTransferFeeConfig( - mintAccountAfterHarvest, + mintAccountAfterHarvest ); console.log( - `Withheld amount after harvest: ${withheldAmountAfterHarvest?.withheldAmount}`, + `Withheld amount after harvest: ${withheldAmountAfterHarvest?.withheldAmount}` ); console.log( - `Mint withheld amount after harvest: ${mintTransferFeeConfigAfterHarvest?.withheldAmount}\n`, + `Mint withheld amount after harvest: ${mintTransferFeeConfigAfterHarvest?.withheldAmount}\n` ); ``` @@ -1170,32 +1169,32 @@ await withdrawWithheldTokensFromMint( payer, [], undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const mintAccountAfterSecondWithdraw = await getMint( connection, mint, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const mintTransferFeeConfigAfterSecondWithdraw = getTransferFeeConfig( - mintAccountAfterSecondWithdraw, + mintAccountAfterSecondWithdraw ); const feeVaultAfterSecondWithdraw = await getAccount( connection, feeVaultAccount, undefined, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); console.log( - `Mint withheld balance after second withdraw: ${mintTransferFeeConfigAfterSecondWithdraw?.withheldAmount}`, + `Mint withheld balance after second withdraw: ${mintTransferFeeConfigAfterSecondWithdraw?.withheldAmount}` ); console.log( - `Fee Vault balance after second withdraw: ${feeVaultAfterSecondWithdraw.amount}`, + `Fee Vault balance after second withdraw: ${feeVaultAfterSecondWithdraw.amount}` ); ``` diff --git a/content/courses/token-extensions/transfer-hook.md b/content/courses/token-extensions/transfer-hook.md index a5a4fa122..83ee75873 100644 --- a/content/courses/token-extensions/transfer-hook.md +++ b/content/courses/token-extensions/transfer-hook.md @@ -88,7 +88,7 @@ instruction on a Transfer Hook program. However, the Program Derived Address ```typescript const [pda] = PublicKey.findProgramAddressSync( [Buffer.from("extra-account-metas"), mint.publicKey.toBuffer()], - program.programId, // transfer hook program ID + program.programId // transfer hook program ID ); ``` @@ -651,7 +651,7 @@ const initializeExtraAccountMetaListInstruction = await program.methods .instruction(); const transaction = new Transaction().add( - initializeExtraAccountMetaListInstruction, + initializeExtraAccountMetaListInstruction ); ``` @@ -677,7 +677,7 @@ const transferInstruction = 0, // Decimals [], "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); ``` @@ -713,7 +713,7 @@ export async function createTransferCheckedWithTransferHookInstruction( decimals: number, multiSigners: (Signer | PublicKey)[] = [], commitment?: Commitment, - programId = TOKEN_PROGRAM_ID, + programId = TOKEN_PROGRAM_ID ) { const instruction = createTransferCheckedInstruction( source, @@ -723,7 +723,7 @@ export async function createTransferCheckedWithTransferHookInstruction( amount, decimals, multiSigners, - programId, + programId ); const mintInfo = await getMint(connection, mint, commitment, programId); @@ -739,7 +739,7 @@ export async function createTransferCheckedWithTransferHookInstruction( destination, owner, amount, - commitment, + commitment ); } @@ -1428,7 +1428,7 @@ it("Creates a Cookie NFT with Transfer Hook and Metadata", async () => { const mintLen = getMintLen(extensions); const metadataLen = TYPE_SIZE + LENGTH_SIZE + pack(cookieMetadata).length; const lamports = await connection.getMinimumBalanceForRentExemption( - mintLen + metadataLen, + mintLen + metadataLen ); const transaction = new Transaction().add( @@ -1443,20 +1443,20 @@ it("Creates a Cookie NFT with Transfer Hook and Metadata", async () => { cookieMint.publicKey, //mint payerWallet.publicKey, //authority cookieMint.publicKey, //metadata address - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeTransferHookInstruction( cookieMint.publicKey, // mint payerWallet.publicKey, // authority program.programId, // Transfer Hook Program ID - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeMintInstruction( cookieMint.publicKey, // mint decimals, // decimals payerWallet.publicKey, // mint authority null, // freeze authority - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createInitializeInstruction({ programId: TOKEN_2022_PROGRAM_ID, @@ -1473,7 +1473,7 @@ it("Creates a Cookie NFT with Transfer Hook and Metadata", async () => { sourceCookieAccount, // associated token account payerWallet.publicKey, // owner cookieMint.publicKey, // mint - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createMintToInstruction( cookieMint.publicKey, // mint @@ -1481,7 +1481,7 @@ it("Creates a Cookie NFT with Transfer Hook and Metadata", async () => { payerWallet.publicKey, // authority 1, // amount - NFTs there will only be one [], // multi signers - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ), createSetAuthorityInstruction( // revoke mint authority @@ -1490,8 +1490,8 @@ it("Creates a Cookie NFT with Transfer Hook and Metadata", async () => { AuthorityType.MintTokens, // authority type null, // new authority [], // multi signers - TOKEN_2022_PROGRAM_ID, - ), + TOKEN_2022_PROGRAM_ID + ) ); const txSig = await sendAndConfirmTransaction(connection, transaction, [ @@ -1550,7 +1550,7 @@ it("Create Crumb Mint", async () => { const lamports = await connection.getMinimumBalanceForRentExemption(size); const TOKEN_METADATA_PROGRAM_ID = new PublicKey( - "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" ); const metadataData: DataV2 = { @@ -1567,7 +1567,7 @@ it("Create Crumb Mint", async () => { TOKEN_METADATA_PROGRAM_ID.toBuffer(), crumbMint.publicKey.toBuffer(), ], - TOKEN_METADATA_PROGRAM_ID, + TOKEN_METADATA_PROGRAM_ID ); const metadataPDA = metadataPDAAndBump[0]; @@ -1585,7 +1585,7 @@ it("Create Crumb Mint", async () => { decimals, // decimals payerWallet.publicKey, // mint authority null, // freeze authority - TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID ), createCreateMetadataAccountV3Instruction( { @@ -1601,7 +1601,7 @@ it("Create Crumb Mint", async () => { data: metadataData, isMutable: true, }, - }, + } ), createSetAuthorityInstruction( // set authority to transfer hook PDA @@ -1610,15 +1610,15 @@ it("Create Crumb Mint", async () => { AuthorityType.MintTokens, // authority type crumbMintAuthority, // new authority [], // multi signers - TOKEN_PROGRAM_ID, - ), + TOKEN_PROGRAM_ID + ) ); const txSig = await sendAndConfirmTransaction( provider.connection, transaction, [payerWallet.payer, crumbMint], - { skipPreflight: true }, + { skipPreflight: true } ); console.log(getExplorerLink("transaction", txSig, "localnet")); @@ -1655,7 +1655,7 @@ it("Initializes ExtraAccountMetaList Account", async () => { .instruction(); const transaction = new Transaction().add( - initializeExtraAccountMetaListInstruction, + initializeExtraAccountMetaListInstruction ); const txSig = await sendAndConfirmTransaction( @@ -1665,7 +1665,7 @@ it("Initializes ExtraAccountMetaList Account", async () => { { skipPreflight: true, commitment: "confirmed", - }, + } ); console.log(getExplorerLink("transaction", txSig, "localnet")); @@ -1737,7 +1737,7 @@ it("Transfer and Transfer Back", async () => { false, undefined, { commitment: "confirmed" }, - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ) ).address; @@ -1750,7 +1750,7 @@ it("Transfer and Transfer Back", async () => { false, undefined, { commitment: "confirmed" }, - TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID ) ).address; @@ -1763,7 +1763,7 @@ it("Transfer and Transfer Back", async () => { false, undefined, { commitment: "confirmed" }, - TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID ) ).address; @@ -1779,7 +1779,7 @@ it("Transfer and Transfer Back", async () => { decimals, // Decimals [], "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transferBackInstruction = @@ -1793,12 +1793,12 @@ it("Transfer and Transfer Back", async () => { decimals, // Decimals [], "confirmed", - TOKEN_2022_PROGRAM_ID, + TOKEN_2022_PROGRAM_ID ); const transaction = new Transaction().add( transferInstruction, - transferBackInstruction, + transferBackInstruction ); const txSig = await sendAndConfirmTransaction( connection, @@ -1806,7 +1806,7 @@ it("Transfer and Transfer Back", async () => { [payerWallet.payer, recipient], { skipPreflight: true, - }, + } ); console.log(getExplorerLink("transaction", txSig, "localnet")); @@ -1815,21 +1815,21 @@ it("Transfer and Transfer Back", async () => { connection, crumbMint.publicKey, "processed", - TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID ); const sourceCrumbAccountInfo = await getAccount( connection, sourceCrumbAccount, "processed", - TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID ); const destinationCrumbAccountInfo = await getAccount( connection, destinationCrumbAccount, "processed", - TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID ); expect(Number(mintInfo.supply)).to.equal(2); @@ -1840,7 +1840,7 @@ it("Transfer and Transfer Back", async () => { console.log("Source Crumb Amount:", Number(sourceCrumbAccountInfo.amount)); console.log( "Destination Crumb Amount\n", - Number(destinationCrumbAccountInfo.amount), + Number(destinationCrumbAccountInfo.amount) ); }); ``` diff --git a/content/courses/tokens-and-nfts/nfts-with-metaplex.md b/content/courses/tokens-and-nfts/nfts-with-metaplex.md index 060ac864d..47ff4dfc9 100644 --- a/content/courses/tokens-and-nfts/nfts-with-metaplex.md +++ b/content/courses/tokens-and-nfts/nfts-with-metaplex.md @@ -76,9 +76,12 @@ information on `metaplex-foundation/token-metadata` see the [developer docs for Token Metadata](https://developers.metaplex.com/token-metadata). -[Metaplex Core](https://developers.metaplex.com/core), is an NFT standard from Metaplex where asset details such as the owner, name, uri e.t.c are stored on a single account. However, the most common style of NFT is still by making a Solana -SPL token with some Metadata attached via the Metaplex Metadata program, so -that's what we'll be using in this tutorial. + [Metaplex Core](https://developers.metaplex.com/core), is an NFT standard from + Metaplex where asset details such as the owner, name, uri e.t.c are stored on + a single account. However, the most common style of NFT is still by making a + Solana SPL token with some Metadata attached via the Metaplex Metadata + program, so that's what we'll be using in this tutorial.{" "} + #### UMI instance @@ -388,7 +391,7 @@ await airdropIfRequired( connection, user.publicKey, 1 * LAMPORTS_PER_SOL, - 0.1 * LAMPORTS_PER_SOL, + 0.1 * LAMPORTS_PER_SOL ); console.log("Loaded user:", user.publicKey.toBase58()); @@ -472,7 +475,7 @@ await createNft(umi, { let explorerLink = getExplorerLink( "address", collectionMint.publicKey, - "devnet", + "devnet" ); console.log(`Collection NFT: ${explorerLink}`); console.log(`Collection NFT address is:`, collectionMint.publicKey); @@ -555,7 +558,7 @@ await airdropIfRequired( connection, user.publicKey, 1 * LAMPORTS_PER_SOL, - 0.1 * LAMPORTS_PER_SOL, + 0.1 * LAMPORTS_PER_SOL ); const umi = createUmi(connection); @@ -694,7 +697,7 @@ await airdropIfRequired( connection, user.publicKey, 1 * LAMPORTS_PER_SOL, - 0.1 * LAMPORTS_PER_SOL, + 0.1 * LAMPORTS_PER_SOL ); const umi = createUmi(connection); @@ -791,7 +794,7 @@ await airdropIfRequired( connection, user.publicKey, 1 * LAMPORTS_PER_SOL, - 0.1 * LAMPORTS_PER_SOL, + 0.1 * LAMPORTS_PER_SOL ); const umi = createUmi(connection); @@ -907,6 +910,6 @@ understanding of the tools but also boost your confidence in your ability to use them effectively in the future. -Push your code to GitHub and -[tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=296745ac-503c-4b14-b3a6-b51c5004c165)! + Push your code to GitHub and [tell us what you thought of this + lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=296745ac-503c-4b14-b3a6-b51c5004c165)! diff --git a/content/courses/tokens-and-nfts/token-program-advanced.md b/content/courses/tokens-and-nfts/token-program-advanced.md index b979dd803..915d659e2 100644 --- a/content/courses/tokens-and-nfts/token-program-advanced.md +++ b/content/courses/tokens-and-nfts/token-program-advanced.md @@ -4,8 +4,7 @@ objectives: - Understand why and how to burn tokens - Allow a token holder to allocate a limited amount of tokens to another account to spend or burn using token delegation. -description: - "How to burn tokens, and approve/revoke token delegations on Solana." +description: "How to burn tokens, and approve/revoke token delegations on Solana." --- ### Summary @@ -48,7 +47,7 @@ const transactionSignature = await burn( account, mint, owner, - amount, + amount ); ``` @@ -74,10 +73,10 @@ async function buildBurnTransaction( account: PublicKey, mint: PublicKey, owner: PublicKey, - amount: number, + amount: number ): Promise { const transaction = new Transaction().add( - createBurnInstruction(account, mint, owner, amount), + createBurnInstruction(account, mint, owner, amount) ); return transaction; @@ -103,7 +102,7 @@ const transactionSignature = await approve( account, delegate, owner, - amount, + amount ); ``` @@ -130,10 +129,10 @@ async function buildApproveTransaction( account: PublicKey, delegate: PublicKey, owner: PublicKey, - amount: number, + amount: number ): Promise { const transaction = new Transaction().add( - createApproveInstruction(account, delegate, owner, amount), + createApproveInstruction(account, delegate, owner, amount) ); return transaction; @@ -176,10 +175,10 @@ import { createRevokeInstruction } from "@solana/spl-token"; async function buildRevokeTransaction( account: PublicKey, - owner: PublicKey, + owner: PublicKey ): Promise { const transaction = new Transaction().add( - createRevokeInstruction(account, owner), + createRevokeInstruction(account, owner) ); return transaction; @@ -244,7 +243,7 @@ try { connection, user, tokenMintAddress, - user.publicKey, + user.publicKey ); // Approve the delegate @@ -254,19 +253,19 @@ try { userTokenAccount.address, delegatePublicKey, user.publicKey, - DELEGATE_AMOUNT * MINOR_UNITS_PER_MAJOR_UNITS, + DELEGATE_AMOUNT * MINOR_UNITS_PER_MAJOR_UNITS ); const explorerLink = getExplorerLink( "transaction", approveTransactionSignature, - "devnet", + "devnet" ); console.log(`✅ Delegate approved. Transaction: ${explorerLink}`); } catch (error) { console.error( - `Error: ${error instanceof Error ? error.message : String(error)}`, + `Error: ${error instanceof Error ? error.message : String(error)}` ); } ``` @@ -320,26 +319,26 @@ try { connection, user, tokenMintAddress, - user.publicKey, + user.publicKey ); const revokeTransactionSignature = await revoke( connection, user, userTokenAccount.address, - user.publicKey, + user.publicKey ); const explorerLink = getExplorerLink( "transaction", revokeTransactionSignature, - "devnet", + "devnet" ); console.log(`✅ Revoke Delegate Transaction: ${explorerLink}`); } catch (error) { console.error( - `Error: ${error instanceof Error ? error.message : String(error)}`, + `Error: ${error instanceof Error ? error.message : String(error)}` ); } ``` @@ -395,7 +394,7 @@ try { connection, user, tokenMintAccount, - user.publicKey, + user.publicKey ); const burnAmount = BURN_AMOUNT * 10 ** TOKEN_DECIMALS; @@ -406,19 +405,19 @@ try { userTokenAccount.address, tokenMintAccount, user, - burnAmount, + burnAmount ); const explorerLink = getExplorerLink( "transaction", transactionSignature, - "devnet", + "devnet" ); console.log(`✅ Burn Transaction: ${explorerLink}`); } catch (error) { console.error( - `Error: ${error instanceof Error ? error.message : String(error)}`, + `Error: ${error instanceof Error ? error.message : String(error)}` ); } ``` @@ -444,4 +443,5 @@ Well done! You've now completed the lab. Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=72cab3b8-984b-4b09-a341-86800167cfc7)! + diff --git a/content/courses/tokens-and-nfts/token-program.md b/content/courses/tokens-and-nfts/token-program.md index 6c05a868f..240280299 100644 --- a/content/courses/tokens-and-nfts/token-program.md +++ b/content/courses/tokens-and-nfts/token-program.md @@ -73,7 +73,7 @@ const tokenMint = await createMint( payer, mintAuthority, freezeAuthority, - decimal, + decimal ); ``` @@ -111,7 +111,7 @@ import * as token from "@solana/spl-token"; async function buildCreateMintTransaction( connection: web3.Connection, payer: web3.PublicKey, - decimals: number, + decimals: number ): Promise { const lamports = await token.getMinimumBalanceForRentExemptMint(connection); const accountKeypair = web3.Keypair.generate(); @@ -130,8 +130,8 @@ async function buildCreateMintTransaction( decimals, payer, payer, - programId, - ), + programId + ) ); return transaction; @@ -185,7 +185,7 @@ const tokenAccount = await createAccount( payer, mint, owner, - keypair, + keypair ); ``` @@ -230,7 +230,7 @@ import * as token from "@solana/spl-token"; async function buildCreateTokenAccountTransaction( connection: web3.Connection, payer: web3.PublicKey, - mint: web3.PublicKey, + mint: web3.PublicKey ): Promise { const mintState = await token.getMint(connection, mint); const accountKeypair = await web3.Keypair.generate(); @@ -250,8 +250,8 @@ async function buildCreateTokenAccountTransaction( accountKeypair.publicKey, mint, payer, - programId, - ), + programId + ) ); return transaction; @@ -287,7 +287,7 @@ const associatedTokenAccount = await createAssociatedTokenAccount( connection, payer, mint, - owner, + owner ); ``` @@ -318,12 +318,12 @@ import * as token from "@solana/spl-token"; async function buildCreateAssociatedTokenAccountTransaction( payer: web3.PublicKey, - mint: web3.PublicKey, + mint: web3.PublicKey ): Promise { const associatedTokenAddress = await token.getAssociatedTokenAddress( mint, payer, - false, + false ); const transaction = new web3.Transaction().add( @@ -331,8 +331,8 @@ async function buildCreateAssociatedTokenAccountTransaction( payer, associatedTokenAddress, payer, - mint, - ), + mint + ) ); return transaction; @@ -355,7 +355,7 @@ const transactionSignature = await mintTo( mint, destination, authority, - amount, + amount ); ``` @@ -388,10 +388,10 @@ async function buildMintToTransaction( authority: web3.PublicKey, mint: web3.PublicKey, amount: number, - destination: web3.PublicKey, + destination: web3.PublicKey ): Promise { const transaction = new web3.Transaction().add( - token.createMintToInstruction(mint, destination, authority, amount), + token.createMintToInstruction(mint, destination, authority, amount) ); return transaction; @@ -420,7 +420,7 @@ const transactionSignature = await transfer( source, destination, owner, - amount, + amount ); ``` @@ -445,10 +445,10 @@ async function buildTransferTransaction( source: web3.PublicKey, destination: web3.PublicKey, owner: web3.PublicKey, - amount: number, + amount: number ): Promise { const transaction = new web3.Transaction().add( - token.createTransferInstruction(source, destination, owner, amount), + token.createTransferInstruction(source, destination, owner, amount) ); return transaction; @@ -490,7 +490,7 @@ const connection = new Connection(clusterApiUrl("devnet")); const user = getKeypairFromEnvironment("SECRET_KEY"); console.log( - `🔑 Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, + `🔑 Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}` ); // This is a shortcut that runs: @@ -551,11 +551,11 @@ const user = getKeypairFromEnvironment("SECRET_KEY"); const connection = new Connection(clusterApiUrl("devnet")); console.log( - `🔑 We've loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, + `🔑 We've loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}` ); const TOKEN_METADATA_PROGRAM_ID = new PublicKey( - "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s", + "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" ); // Substitute in your token mint account @@ -578,7 +578,7 @@ const metadataPDAAndBump = PublicKey.findProgramAddressSync( TOKEN_METADATA_PROGRAM_ID.toBuffer(), tokenMintAccount.toBuffer(), ], - TOKEN_METADATA_PROGRAM_ID, + TOKEN_METADATA_PROGRAM_ID ); const metadataPDA = metadataPDAAndBump[0]; @@ -600,7 +600,7 @@ const createMetadataAccountInstruction = data: metadataData, isMutable: true, }, - }, + } ); transaction.add(createMetadataAccountInstruction); @@ -608,13 +608,13 @@ transaction.add(createMetadataAccountInstruction); const transactionSignature = await sendAndConfirmTransaction( connection, transaction, - [user], + [user] ); const transactionLink = getExplorerLink( "transaction", transactionSignature, - "devnet", + "devnet" ); console.log(`✅ Transaction confirmed, explorer link is: ${transactionLink}`); @@ -622,7 +622,7 @@ console.log(`✅ Transaction confirmed, explorer link is: ${transactionLink}`); const tokenMintLink = getExplorerLink( "address", tokenMintAccount.toString(), - "devnet", + "devnet" ); console.log(`✅ Look at the token mint again: ${tokenMintLink}`); @@ -671,7 +671,7 @@ const connection = new Connection(clusterApiUrl("devnet")); const user = getKeypairFromEnvironment("SECRET_KEY"); console.log( - `🔑 Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}`, + `🔑 Loaded our keypair securely, using an env file! Our public key is: ${user.publicKey.toBase58()}` ); // Substitute in your token mint account from create-token-mint.ts @@ -686,7 +686,7 @@ const tokenAccount = await getOrCreateAssociatedTokenAccount( connection, user, tokenMintAccount, - recipient, + recipient ); console.log(`Token Account: ${tokenAccount.address.toBase58()}`); @@ -694,7 +694,7 @@ console.log(`Token Account: ${tokenAccount.address.toBase58()}`); const link = getExplorerLink( "address", tokenAccount.address.toBase58(), - "devnet", + "devnet" ); console.log(`✅ Created token Account: ${link}`); @@ -742,7 +742,7 @@ const tokenMintAccount = new PublicKey("YOUR_TOKEN_MINT_ACCOUNT"); // Substitute in your own, or a friend's token account address, based on the previous step. const recipientAssociatedTokenAccount = new PublicKey( - "RECIPIENT_TOKEN_ACCOUNT", + "RECIPIENT_TOKEN_ACCOUNT" ); const transactionSignature = await mintTo( @@ -751,7 +751,7 @@ const transactionSignature = await mintTo( tokenMintAccount, recipientAssociatedTokenAccount, user, - 10 * MINOR_UNITS_PER_MAJOR_UNITS, + 10 * MINOR_UNITS_PER_MAJOR_UNITS ); const link = getExplorerLink("transaction", transactionSignature, "devnet"); @@ -799,7 +799,7 @@ const connection = new Connection(clusterApiUrl("devnet")); const sender = getKeypairFromEnvironment("SECRET_KEY"); console.log( - `🔑 Loaded our keypair securely, using an env file! Our public key is: ${sender.publicKey.toBase58()}`, + `🔑 Loaded our keypair securely, using an env file! Our public key is: ${sender.publicKey.toBase58()}` ); // Add the recipient public key here. @@ -818,7 +818,7 @@ const sourceTokenAccount = await getOrCreateAssociatedTokenAccount( connection, sender, tokenMintAccount, - sender.publicKey, + sender.publicKey ); // Get or create the destination token account to store this token @@ -826,7 +826,7 @@ const destinationTokenAccount = await getOrCreateAssociatedTokenAccount( connection, sender, tokenMintAccount, - recipient, + recipient ); // Transfer the tokens @@ -836,7 +836,7 @@ const signature = await transfer( sourceTokenAccount.address, destinationTokenAccount.address, sender, - 1 * MINOR_UNITS_PER_MAJOR_UNITS, + 1 * MINOR_UNITS_PER_MAJOR_UNITS ); const explorerLink = getExplorerLink("transaction", signature, "devnet"); @@ -895,4 +895,5 @@ And remember, get creative with these challenges and make them your own! Push your code to GitHub and [tell us what you thought of this lesson](https://form.typeform.com/to/IPH0UGz7#answers-lesson=72cab3b8-984b-4b09-a341-86800167cfc7)! + diff --git a/content/guides/advanced/how-to-request-optimal-compute.md b/content/guides/advanced/how-to-request-optimal-compute.md index 61c0aa43f..e159c62f0 100644 --- a/content/guides/advanced/how-to-request-optimal-compute.md +++ b/content/guides/advanced/how-to-request-optimal-compute.md @@ -70,7 +70,7 @@ For example: const units = await getSimulationComputeUnits( connection, transactions, - payer.publicKey, + payer.publicKey ); ``` @@ -84,7 +84,7 @@ async function buildOptimalTransaction( connection: Connection, instructions: Array, signer: Signer, - lookupTables: Array, + lookupTables: Array ) { const [microLamports, units, recentBlockhash] = await Promise.all([ 100 /* Get optimal priority fees - https://solana.com/developers/guides/advanced/how-to-use-priority-fees*/, @@ -92,13 +92,13 @@ async function buildOptimalTransaction( connection, instructions, signer.publicKey, - lookupTables, + lookupTables ), connection.getLatestBlockhash(), ]); instructions.unshift( - ComputeBudgetProgram.setComputeUnitPrice({ microLamports }), + ComputeBudgetProgram.setComputeUnitPrice({ microLamports }) ); if (units) { // probably should add some margin of error to units @@ -110,7 +110,7 @@ async function buildOptimalTransaction( instructions, recentBlockhash: recentBlockhash.blockhash, payerKey: signer.publicKey, - }).compileToV0Message(lookupTables), + }).compileToV0Message(lookupTables) ), recentBlockhash, }; diff --git a/content/guides/advanced/how-to-use-priority-fees.md b/content/guides/advanced/how-to-use-priority-fees.md index 685f3ad0f..b6594039f 100644 --- a/content/guides/advanced/how-to-use-priority-fees.md +++ b/content/guides/advanced/how-to-use-priority-fees.md @@ -78,7 +78,7 @@ const transaction = new Transaction() fromPubkey: payer.publicKey, toPubkey: toAccount, lamports: 10000000, - }), + }) ); ``` @@ -155,6 +155,6 @@ const transaction = new Transaction() fromPubkey: payer.publicKey, toPubkey: toAccount, lamports: 10000000, - }), + }) ); ``` diff --git a/content/guides/advanced/introduction-to-durable-nonces.md b/content/guides/advanced/introduction-to-durable-nonces.md index d41cfae98..7458c2860 100644 --- a/content/guides/advanced/introduction-to-durable-nonces.md +++ b/content/guides/advanced/introduction-to-durable-nonces.md @@ -521,7 +521,7 @@ tx.add( SystemProgram.nonceInitialize({ noncePubkey: nonceKeypair.publicKey, authorizedPubkey: nonceAuthKP.publicKey, - }), + }) ); // sign the transaction with both the nonce keypair and the authority keypair @@ -530,7 +530,7 @@ tx.sign(nonceKeypair, nonceAuthKP); // send the transaction const sig = await sendAndConfirmRawTransaction( connection, - tx.serialize({ requireAllSignatures: false }), + tx.serialize({ requireAllSignatures: false }) ); console.log("Nonce initiated: ", sig); ``` @@ -578,7 +578,7 @@ const signedTx = await signTransaction(tx); // in a database, or send it to another device. You can submit it // at a later point, without the tx having a mortality const serialisedTx = bs58.encode( - signedTx.serialize({ requireAllSignatures: false }), + signedTx.serialize({ requireAllSignatures: false }) ); console.log("Signed Durable Transaction: ", serialisedTx); ``` diff --git a/content/guides/advanced/testing-with-jest-and-bankrun.md b/content/guides/advanced/testing-with-jest-and-bankrun.md index 512920b3a..78c381cbf 100644 --- a/content/guides/advanced/testing-with-jest-and-bankrun.md +++ b/content/guides/advanced/testing-with-jest-and-bankrun.md @@ -300,10 +300,10 @@ retrieved in the rest of your Solana codebase, you can continue using connection.getAccount. Choose the method that best fits your specific use case. ```js -await client.getAccount(playerPDA).then(info => { +await client.getAccount(playerPDA).then((info) => { const decoded = program.coder.accounts.decode( "playerData", - Buffer.from(info.data), + Buffer.from(info.data) ); console.log("Player account info", JSON.stringify(decoded)); expect(decoded).toBeDefined(); @@ -351,7 +351,7 @@ bank. You can then use the `client` to interact with the bank. ```js const context = await start( [{ name: "counter_solana_native", programId: PROGRAM_ID }], - [], + [] ); const client = context.banksClient; ``` diff --git a/content/guides/dapps/cash-app.md b/content/guides/dapps/cash-app.md index 4a495f82d..efa3350ed 100644 --- a/content/guides/dapps/cash-app.md +++ b/content/guides/dapps/cash-app.md @@ -156,13 +156,13 @@ client with expo, the emulator will automatically update every time you save your code. -You must have -[fake wallet](https://github.com/solana-mobile/mobile-wallet-adapter/tree/main/android/fakewallet) -running on the same Android emulator to be able to test out transactions, as -explained in the -[Solana mobile development set up docs](https://docs.solanamobile.com/getting-started/development-setup) -or you must have a real wallet app, like Phantom or Solflare, installed and set -up on your emulator. + You must have [fake + wallet](https://github.com/solana-mobile/mobile-wallet-adapter/tree/main/android/fakewallet) + running on the same Android emulator to be able to test out transactions, as + explained in the [Solana mobile development set up + docs](https://docs.solanamobile.com/getting-started/development-setup) or you + must have a real wallet app, like Phantom or Solflare, installed and set up on + your emulator. ## Writing a Solana Program with Cash App Functionalities @@ -906,23 +906,23 @@ it("A to B user flow", async () => { const [myAccount] = await anchor.web3.PublicKey.findProgramAddress( [Buffer.from("cash-account"), myWallet.publicKey.toBuffer()], - program.programId, + program.programId ); const [yourAccount] = await anchor.web3.PublicKey.findProgramAddress( [Buffer.from("cash-account"), yourWallet.publicKey.toBuffer()], - program.programId, + program.programId ); console.log("requesting airdrop"); const airdropTx = await provider.connection.requestAirdrop( yourWallet.publicKey, - 5 * anchor.web3.LAMPORTS_PER_SOL, + 5 * anchor.web3.LAMPORTS_PER_SOL ); await provider.connection.confirmTransaction(airdropTx); let yourBalance = await program.provider.connection.getBalance( - yourWallet.publicKey, + yourWallet.publicKey ); console.log("Your wallet balance:", yourBalance); }); @@ -1024,7 +1024,7 @@ export function UseCashAppProgramAccount(user: PublicKey) { const cashAppProgramId = new PublicKey("11111111111111111111111111111111"); const [connection] = useState( - () => new Connection("https://api.devnet.solana.com"), + () => new Connection("https://api.devnet.solana.com") ); const [cashAppPDA] = useMemo(() => { @@ -1036,7 +1036,7 @@ export function UseCashAppProgramAccount(user: PublicKey) { return new Program( idl as CashAppProgram, cashAppProgramId, - { connection }, + { connection } ); }, [cashAppProgramId]); @@ -1046,7 +1046,7 @@ export function UseCashAppProgramAccount(user: PublicKey) { cashAppProgramId: cashAppProgramId, cashAppPDA: cashAppPDA, }), - [cashAppProgram, cashAppProgramId, cashAppPDA], + [cashAppProgram, cashAppProgramId, cashAppPDA] ); return value; @@ -1088,10 +1088,10 @@ parameter. export function UsePendingRequestAccount( user: PublicKey, count: number, - cashAppProgramId: PublicKey, + cashAppProgramId: PublicKey ) { const [connection] = useState( - () => new Connection("https://api.devnet.solana.com"), + () => new Connection("https://api.devnet.solana.com") ); bigNumber = new BN(count); @@ -1103,7 +1103,7 @@ export function UsePendingRequestAccount( ]; return PublicKey.findProgramAddressSync( pendingRequestSeed, - cashAppProgramId, + cashAppProgramId ); }, [cashAppProgramId]); @@ -1111,7 +1111,7 @@ export function UsePendingRequestAccount( () => ({ pendingRequestPDA: pendingRequestPDA, }), - [pendingRequestPDA], + [pendingRequestPDA] ); return value; @@ -1404,7 +1404,7 @@ accounts. ```tsx const [connection] = useState( - () => new Connection("https://api.devnet.solana.com"), + () => new Connection("https://api.devnet.solana.com") ); const depositFunds = useCallback( @@ -1434,19 +1434,19 @@ const depositFunds = useCallback( }); return signedTransactions[0]; - }, + } ); let txSignature = await connection.sendRawTransaction( signedTransactions.serialize(), { skipPreflight: true, - }, + } ); const confirmationResult = await connection.confirmTransaction( txSignature, - "confirmed", + "confirmed" ); if (confirmationResult.value.err) { @@ -1455,7 +1455,7 @@ const depositFunds = useCallback( console.log("Transaction successfully submitted!"); } }, - [authorizeSession, connection, cashAppPDA], + [authorizeSession, connection, cashAppPDA] ); ``` @@ -1529,7 +1529,7 @@ export function AccountButtonGroup({ address }: { address: PublicKey }) { const { cashAppProgram } = UseCashAppProgram(address); const [connection] = useState( - () => new Connection("https://api.devnet.solana.com"), + () => new Connection("https://api.devnet.solana.com") ); const DepositModal = () => ( @@ -1569,7 +1569,7 @@ export function AccountButtonGroup({ address }: { address: PublicKey }) { try { if (!cashAppProgram || !selectedAccount) { console.warn( - "Program/wallet is not initialized yet. Try connecting a wallet first.", + "Program/wallet is not initialized yet. Try connecting a wallet first." ); return; } @@ -1577,7 +1577,7 @@ export function AccountButtonGroup({ address }: { address: PublicKey }) { alertAndLog( "Funds deposited into cash account ", - "See console for logged transaction.", + "See console for logged transaction." ); console.log(deposit); } finally { @@ -1635,7 +1635,7 @@ export function AccountButtonGroup({ address }: { address: PublicKey }) { try { if (!cashAppProgram || !selectedAccount) { console.warn( - "Program/wallet is not initialized yet. Try connecting a wallet first.", + "Program/wallet is not initialized yet. Try connecting a wallet first." ); return; } @@ -1643,7 +1643,7 @@ export function AccountButtonGroup({ address }: { address: PublicKey }) { alertAndLog( "Funds withdrawn from cash account ", - "See console for logged transaction.", + "See console for logged transaction." ); console.log(deposit); } finally { @@ -1722,7 +1722,7 @@ const [pendingRequestPDA] = useMemo(() => { ]; return PublicKey.findProgramAddressSync( [pendingRequestSeed], - cashAppProgramId, + cashAppProgramId ); }, [cashAppProgramId]); @@ -1874,17 +1874,17 @@ const App: React.FC = ({ navigation }) => { - {[1, 2, 3].map(number => ( + {[1, 2, 3].map((number) => ( ))} - {[4, 5, 6].map(number => ( + {[4, 5, 6].map((number) => ( ))} - {[7, 8, 9].map(number => ( + {[7, 8, 9].map((number) => ( ))} @@ -1935,7 +1935,7 @@ const PayScreen: React.FC = ({ route, navigation }) => { const newAmount = new anchor.BN(inputValue); const [connection] = useState( - () => new Connection("https://api.devnet.solana.com"), + () => new Connection("https://api.devnet.solana.com") ); const { authorizeSession, selectedAccount } = useAuthorization(); const user = selectedAccount.publicKey; @@ -1972,19 +1972,19 @@ const PayScreen: React.FC = ({ route, navigation }) => { }); return signedTransactions[0]; - }, + } ); let txSignature = await connection.sendRawTransaction( signedTransactions.serialize(), { skipPreflight: true, - }, + } ); const confirmationResult = await connection.confirmTransaction( txSignature, - "confirmed", + "confirmed" ); if (confirmationResult.value.err) { @@ -1993,7 +1993,7 @@ const PayScreen: React.FC = ({ route, navigation }) => { console.log("Transaction successfully submitted!"); } }, - [authorizeSession, connection, cashAppPDA], + [authorizeSession, connection, cashAppPDA] ); return ( @@ -2013,7 +2013,7 @@ const PayScreen: React.FC = ({ route, navigation }) => { try { if (!cashAppProgram || !selectedAccount) { console.warn( - "Program/wallet is not initialized yet. Try connecting a wallet first.", + "Program/wallet is not initialized yet. Try connecting a wallet first." ); return; } @@ -2021,7 +2021,7 @@ const PayScreen: React.FC = ({ route, navigation }) => { alertAndLog( "Funds deposited into cash account ", - "See console for logged transaction.", + "See console for logged transaction." ); console.log(deposit); } finally { @@ -2087,7 +2087,7 @@ export function AddFriend({ address }: { address: PublicKey }) { const [pubkey, setPubkey] = useState(""); const [signingInProgress, setSigningInProgress] = useState(false); const [connection] = useState( - () => new Connection("https://api.devnet.solana.com"), + () => new Connection("https://api.devnet.solana.com") ); const { authorizeSession, selectedAccount } = useAuthorization(); const { cashAppProgram, cashAppPDA, friends } = UseCashAppProgram(address); @@ -2120,19 +2120,19 @@ export function AddFriend({ address }: { address: PublicKey }) { }); return signedTransactions[0]; - }, + } ); let txSignature = await connection.sendRawTransaction( signedTransactions.serialize(), { skipPreflight: true, - }, + } ); const confirmationResult = await connection.confirmTransaction( txSignature, - "confirmed", + "confirmed" ); if (confirmationResult.value.err) { @@ -2141,7 +2141,7 @@ export function AddFriend({ address }: { address: PublicKey }) { console.log("Transaction successfully submitted!"); } }, - [authorizeSession, connection, cashAppPDA], + [authorizeSession, connection, cashAppPDA] ); return ( @@ -2196,13 +2196,13 @@ export function AddFriend({ address }: { address: PublicKey }) { const signedTransaction = await addFriend(cashAppProgram); alertAndLog( "Transaction signed", - "View recent transactions for more information.", + "View recent transactions for more information." ); console.log(signedTransaction); } catch (err: any) { alertAndLog( "Error during signing", - err instanceof Error ? err.message : err, + err instanceof Error ? err.message : err ); } finally { setSigningInProgress(false); @@ -2456,7 +2456,7 @@ const transferFunds = useCallback( const recipientSeed = pubkey.toBuffer(); return PublicKey.findProgramAddressSync( [recipientSeed], - cashAppProgramId, + cashAppProgramId ); }, [cashAppProgramId]); @@ -2479,19 +2479,19 @@ const transferFunds = useCallback( }); return signedTransactions[0]; - }, + } ); let txSignature = await connection.sendRawTransaction( signedTransactions.serialize(), { skipPreflight: true, - }, + } ); const confirmationResult = await connection.confirmTransaction( txSignature, - "confirmed", + "confirmed" ); if (confirmationResult.value.err) { @@ -2500,7 +2500,7 @@ const transferFunds = useCallback( console.log("Transaction successfully submitted!"); } }, - [authorizeSession, connection, cashAppPDA], + [authorizeSession, connection, cashAppPDA] ); ``` diff --git a/content/guides/dapps/journal.md b/content/guides/dapps/journal.md index 4ddde0ede..dce9c2ee6 100644 --- a/content/guides/dapps/journal.md +++ b/content/guides/dapps/journal.md @@ -379,7 +379,7 @@ const createEntry = useMutation({ mutationFn: async ({ title, message, owner }) => { const [journalEntryAddress] = await PublicKey.findProgramAddress( [Buffer.from(title), owner.toBuffer()], - programId, + programId ); return program.methods @@ -389,11 +389,11 @@ const createEntry = useMutation({ }) .rpc(); }, - onSuccess: signature => { + onSuccess: (signature) => { transactionToast(signature); accounts.refetch(); }, - onError: error => { + onError: (error) => { toast.error(`Failed to create journal entry: ${error.message}`); }, }); @@ -408,7 +408,7 @@ const updateEntry = useMutation({ mutationFn: async ({ title, message, owner }) => { const [journalEntryAddress] = await PublicKey.findProgramAddress( [Buffer.from(title), owner.toBuffer()], - programId, + programId ); return program.methods @@ -418,11 +418,11 @@ const updateEntry = useMutation({ }) .rpc(); }, - onSuccess: signature => { + onSuccess: (signature) => { transactionToast(signature); accounts.refetch(); }, - onError: error => { + onError: (error) => { toast.error(`Failed to update journal entry: ${error.message}`); }, }); @@ -434,7 +434,7 @@ const deleteEntry = useMutation({ .deleteJournalEntry(title) .accounts({ journalEntry: account }) .rpc(), - onSuccess: tx => { + onSuccess: (tx) => { transactionToast(tx); return accounts.refetch(); }, @@ -469,13 +469,13 @@ export function JournalCreate() { type="text" placeholder="Title" value={title} - onChange={e => setTitle(e.target.value)} + onChange={(e) => setTitle(e.target.value)} className="input input-bordered w-full max-w-xs" />