├── counterAnchor ├── web │ ├── public │ │ ├── .gitkeep │ │ ├── favicon.ico │ │ └── solana-logo.png │ ├── app │ │ ├── page.module.css │ │ ├── api │ │ │ └── hello │ │ │ │ └── route.ts │ │ ├── clusters │ │ │ └── page.tsx │ │ ├── counter │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── account │ │ │ ├── page.tsx │ │ │ └── [address] │ │ │ │ └── page.tsx │ │ ├── global.css │ │ ├── react-query-provider.tsx │ │ └── layout.tsx │ ├── index.d.ts │ ├── next-env.d.ts │ ├── tailwind.config.js │ ├── postcss.config.js │ ├── components │ │ ├── account │ │ │ ├── account-list-feature.tsx │ │ │ ├── account-detail-feature.tsx │ │ │ ├── account-data-access.tsx │ │ │ └── account-ui.tsx │ │ ├── cluster │ │ │ ├── cluster-feature.tsx │ │ │ ├── cluster-data-access.tsx │ │ │ └── cluster-ui.tsx │ │ ├── dashboard │ │ │ └── dashboard-feature.tsx │ │ ├── counter │ │ │ ├── counter-feature.tsx │ │ │ ├── counter-data-access.tsx │ │ │ └── counter-ui.tsx │ │ ├── solana │ │ │ └── solana-provider.tsx │ │ └── ui │ │ │ └── ui-layout.tsx │ ├── next.config.js │ ├── .eslintrc.json │ ├── tsconfig.json │ └── project.json ├── .eslintignore ├── .prettierrc ├── anchor │ ├── programs │ │ └── counter │ │ │ ├── Xargo.toml │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── lib.rs │ ├── src │ │ ├── index.ts │ │ └── counter-exports.ts │ ├── Cargo.toml │ ├── tsconfig.lib.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── package.json │ ├── migrations │ │ └── deploy.ts │ ├── README.md │ ├── Anchor.toml │ ├── .swcrc │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── tests │ │ └── counter.spec.ts │ └── yarn.lock ├── vercel.json ├── jest.preset.js ├── jest.config.ts ├── .vscode │ └── extensions.json ├── .editorconfig ├── .prettierignore ├── tsconfig.base.json ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── nx.json ├── README.md └── package.json ├── Cargo.toml ├── counterC ├── Cargo.toml ├── test-c.sh ├── test-rust-mangle-c.sh ├── counter │ ├── Cargo.toml │ ├── src │ │ ├── c-assembly-code.s │ │ ├── rust-unsave-c-code-assembly.s │ │ ├── main.c │ │ └── lib.rs │ └── tests │ │ └── counter_test.rs ├── install-solana-c.sh ├── Makefile └── README.md ├── .gitignore ├── counterNative ├── .gitignore ├── readme.md ├── tsconfig.json ├── program │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── client │ └── client.ts ├── package.json └── tests │ └── native.test.ts ├── counterAsmSbpf ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json ├── src │ └── sbpfCounter │ │ └── sbpfCounter.s └── tests │ └── sbpfCounter.test.ts ├── counterAsm ├── test-asm.sh ├── Cargo.toml ├── src │ └── lib.rs ├── README.md ├── asm │ ├── Makefile │ └── main.s ├── install-solana-llvm.sh └── tests │ └── functional.rs ├── test-asm.sh ├── .vscode └── settings.json ├── install-solana-llvm.sh └── readme.md /counterAnchor/web/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /counterAnchor/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /counterAnchor/web/app/page.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | } 3 | -------------------------------------------------------------------------------- /counterAnchor/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "counterAsm" 4 | ] 5 | resolver = "2" 6 | 7 | -------------------------------------------------------------------------------- /counterC/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "counter" 4 | ] 5 | resolver = "2" 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .zig-cache/ 3 | zig-out/ 4 | solana-llvm/ 5 | solana-zig/ 6 | solana-c-sdk/ 7 | out/ 8 | -------------------------------------------------------------------------------- /counterAnchor/anchor/programs/counter/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /counterAnchor/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "npm run build", 3 | "outputDirectory": "dist/web/.next" 4 | } 5 | -------------------------------------------------------------------------------- /counterAnchor/jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /counterAnchor/web/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | return new Response('Hello, from API!'); 3 | } 4 | -------------------------------------------------------------------------------- /counterAnchor/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solana-developers/cu_optimizations/HEAD/counterAnchor/web/public/favicon.ico -------------------------------------------------------------------------------- /counterAnchor/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nx/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /counterAnchor/web/public/solana-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solana-developers/cu_optimizations/HEAD/counterAnchor/web/public/solana-logo.png -------------------------------------------------------------------------------- /counterNative/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .cargo 3 | .DS_Store 4 | **/.DS_Store 5 | **/target 6 | **/*.rs.bk 7 | node_modules 8 | test-ledger 9 | dist 10 | -------------------------------------------------------------------------------- /counterAnchor/anchor/src/index.ts: -------------------------------------------------------------------------------- 1 | // This file was generated by preset-anchor. Programs are exported from this file. 2 | 3 | export * from './counter-exports'; 4 | -------------------------------------------------------------------------------- /counterAsmSbpf/.gitignore: -------------------------------------------------------------------------------- 1 | build/**/* 2 | deploy/**/* 3 | node_modules 4 | .sbpf 5 | .DS_Store 6 | .vscode 7 | keypair.json 8 | package-lock.json 9 | test-ledger 10 | yarn.lock -------------------------------------------------------------------------------- /counterAnchor/web/app/clusters/page.tsx: -------------------------------------------------------------------------------- 1 | import ClusterFeature from '@/components/cluster/cluster-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /counterAnchor/web/app/counter/page.tsx: -------------------------------------------------------------------------------- 1 | import CounterFeature from '@/components/counter/counter-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /counterAnchor/web/app/page.tsx: -------------------------------------------------------------------------------- 1 | import DashboardFeature from '@/components/dashboard/dashboard-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /counterAnchor/web/app/account/page.tsx: -------------------------------------------------------------------------------- 1 | import AccountListFeature from '@/components/account/account-list-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /counterAnchor/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /counterAnchor/web/app/account/[address]/page.tsx: -------------------------------------------------------------------------------- 1 | import AccountDetailFeature from '@/components/account/account-detail-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /counterAnchor/web/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /counterC/test-c.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROGRAM_NAME="$1" 4 | ROOT_DIR="$(cd "$(dirname "$0")"; pwd)" 5 | set -e 6 | PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME 7 | make 8 | SBF_OUT_DIR="out" cargo test --manifest-path "Cargo.toml" 9 | -------------------------------------------------------------------------------- /counterNative/readme.md: -------------------------------------------------------------------------------- 1 | Install packages 2 | 3 | ```bash 4 | yarn install 5 | ``` 6 | 7 | Run the tests like this: 8 | 9 | ```bash 10 | cargo build-bpf && solana program deploy ./target/deploy/temp.so && yarn test 11 | ``` -------------------------------------------------------------------------------- /counterAsm/test-asm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROGRAM_NAME="$1" 4 | ROOT_DIR="$(cd "$(dirname "$0")"; pwd)" 5 | set -e 6 | PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME 7 | cd asm 8 | make 9 | SBF_OUT_DIR="./asm/out" cargo test 10 | 11 | -------------------------------------------------------------------------------- /counterAnchor/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /counterC/test-rust-mangle-c.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROGRAM_NAME="$1" 4 | ROOT_DIR="$(cd "$(dirname "$0")"; pwd)" 5 | set -e 6 | PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME 7 | cargo build-sbf 8 | SBF_OUT_DIR="../target/deploy" cargo test --manifest-path "Cargo.toml" 9 | -------------------------------------------------------------------------------- /counterNative/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test-asm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROGRAM_NAME="$1" 4 | ROOT_DIR="$(cd "$(dirname "$0")"; pwd)" 5 | set -e 6 | PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME 7 | cd $PROGRAM_DIR/asm 8 | make 9 | SBF_OUT_DIR="$PROGRAM_DIR/asm/out" cargo test --manifest-path "$PROGRAM_DIR/Cargo.toml" 10 | 11 | -------------------------------------------------------------------------------- /counterAsm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana-program-rosetta-helloworld" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | solana-program = "2" 8 | 9 | [dev-dependencies] 10 | solana-program-test = "2" 11 | solana-sdk = "2" 12 | 13 | [lib] 14 | crate-type = ["cdylib", "lib"] 15 | -------------------------------------------------------------------------------- /counterAnchor/anchor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | 12 | [profile.release.build-override] 13 | opt-level = 3 14 | incremental = false 15 | codegen-units = 1 16 | -------------------------------------------------------------------------------- /counterC/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana-program-rosetta-helloworld" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | solana-program = "2" 8 | 9 | [dev-dependencies] 10 | solana-program-test = "2" 11 | solana-sdk = "2" 12 | 13 | [lib] 14 | crate-type = ["cdylib", "lib"] 15 | -------------------------------------------------------------------------------- /counterAnchor/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /counterAnchor/anchor/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /counterAsm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::msg; 2 | 3 | #[no_mangle] 4 | pub extern "C" fn entrypoint(_: *mut u8) -> u64 { 5 | msg!("Hello world!"); 6 | 0 7 | } 8 | #[cfg(target_os = "solana")] 9 | #[no_mangle] 10 | fn custom_panic(info: &core::panic::PanicInfo<'_>) {} 11 | solana_program::custom_heap_default!(); 12 | -------------------------------------------------------------------------------- /counterNative/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "temp" 3 | version = "0.1.0" 4 | description = "Native Solana Program" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | 10 | [features] 11 | no-entrypoint = [] 12 | 13 | [dependencies] 14 | borsh = "0.10.3" 15 | solana-program = "1.16.24" 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "solana_sdk.h": "c", 4 | "blake3.h": "c", 5 | "compute_units.h": "c", 6 | "cpi.h": "c", 7 | "deserialize.h": "c", 8 | "deserialize_deprecated.h": "c", 9 | "keccak.h": "c", 10 | "entrypoint.h": "c", 11 | "types.h": "c" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /counterAsmSbpf/tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "target": "es6", 5 | "module": "commonjs", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /counterAnchor/anchor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | }, 6 | "files": [], 7 | "include": [], 8 | "references": [ 9 | { 10 | "path": "./tsconfig.lib.json" 11 | }, 12 | { 13 | "path": "./tsconfig.spec.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /counterAnchor/anchor/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /counterAnchor/anchor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@counter/anchor", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "@coral-xyz/anchor": "^0.30.1", 6 | "@solana/web3.js": "1.95.3" 7 | }, 8 | "type": "commonjs", 9 | "main": "./index.cjs", 10 | "module": "./index.js", 11 | "devDependencies": { 12 | "prettier": "3.2.5" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /counterAsm/README.md: -------------------------------------------------------------------------------- 1 | This counter example was created using the Assembly Tools written by Jon Cinque: https://github.com/joncinque/solana-program-rosetta?tab=readme-ov-file#assembly 2 | 3 | # How to run 4 | 5 | cd counterAsm 6 | 7 | ./install-solana-llvm.sh 8 | 9 | cd asm 10 | 11 | make 12 | 13 | cd .. 14 | 15 | Run tests: 16 | ./test-asm.sh counterAsm -------------------------------------------------------------------------------- /counterAnchor/.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache 5 | .anchor 6 | anchor/target/deploy 7 | anchor/target/debug 8 | anchor/target/release 9 | anchor/target/sbf-solana-solana 10 | anchor/target/.rustc_info.json 11 | !anchor/target/idl/*.json 12 | !anchor/target/types/*.ts 13 | node_modules 14 | dist 15 | tmp 16 | build 17 | test-ledger -------------------------------------------------------------------------------- /counterAnchor/web/app/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | height: 100%; 8 | } 9 | 10 | .wallet-adapter-button-trigger { 11 | background: rgb(100, 26, 230) !important; 12 | border-radius: 8px !important; 13 | padding-left: 16px !important; 14 | padding-right: 16px !important; 15 | } 16 | .wallet-adapter-dropdown-list, 17 | .wallet-adapter-button { 18 | font-family: inherit !important; 19 | } 20 | -------------------------------------------------------------------------------- /counterAnchor/anchor/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | import * as anchor from '@coral-xyz/anchor'; 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /counterC/counter/src/c-assembly-code.s: -------------------------------------------------------------------------------- 1 | .text 2 | .file "main.c" 3 | .globl entrypoint # -- Begin function entrypoint 4 | .p2align 3 5 | .type entrypoint,@function 6 | entrypoint: # @entrypoint 7 | # %bb.0: 8 | r2 = *(u8 *)(r1 + 96) 9 | r2 += 1 10 | *(u8 *)(r1 + 96) = r2 11 | r0 = 0 12 | exit 13 | .Lfunc_end0: 14 | .size entrypoint, .Lfunc_end0-entrypoint 15 | # -- End function 16 | .addrsig 17 | -------------------------------------------------------------------------------- /counterAnchor/anchor/README.md: -------------------------------------------------------------------------------- 1 | # anchor 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build anchor` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test anchor` to execute the unit tests via [Jest](https://jestjs.io). 12 | 13 | 14 | Links: 15 | 16 | https://www.rareskills.io/post/solana-compute-unit-price 17 | https://solana.com/developers/guides/advanced/how-to-use-priority-fees 18 | https://hackmd.io/@0xNallok/BkxteE0VVp 19 | -------------------------------------------------------------------------------- /counterAsmSbpf/README.md: -------------------------------------------------------------------------------- 1 | # sbpfCounter 2 | 3 | Created with [sbpf](https://github.com/deanmlittle/sbpf) 4 | 5 | This program is written in assembly and reads data from an account increases the counter u64 value by 1 and writes it back to the account. 6 | 7 | ## Usage 8 | 9 | Start a local validator 10 | 11 | ```bash 12 | solana-test-validator 13 | ``` 14 | 15 | Then you can build, deploy and test the program with the following commands: 16 | 17 | ```bash 18 | sbpf build && sbpf deploy && sbpf test 19 | ``` 20 | -------------------------------------------------------------------------------- /counterAnchor/web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); 2 | const { join } = require('path'); 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | module.exports = { 6 | content: [ 7 | join( 8 | __dirname, 9 | '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' 10 | ), 11 | ...createGlobPatternsForDependencies(__dirname), 12 | ], 13 | theme: { 14 | extend: {}, 15 | }, 16 | plugins: [require('daisyui')], 17 | }; 18 | -------------------------------------------------------------------------------- /counterAnchor/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build 4 | // option from your application's configuration (i.e. project.json). 5 | // 6 | // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries 7 | 8 | module.exports = { 9 | plugins: { 10 | tailwindcss: { 11 | config: join(__dirname, 'tailwind.config.js'), 12 | }, 13 | autoprefixer: {}, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /counterAnchor/anchor/programs/counter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "counter" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "counter" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | trace-compute = [] 18 | idl-build = ["anchor-lang/idl-build"] 19 | 20 | [dependencies] 21 | anchor-lang = "0.30.1" 22 | solana-program = "1.18.15" 23 | bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"]} -------------------------------------------------------------------------------- /counterAnchor/anchor/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | seeds = false 5 | skip-lint = false 6 | 7 | [programs.localnet] 8 | counter = "5N5E4imUxwwdbYZern6xzKXejuHy3jNprfrvVmzT3YdF" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "localnet" 15 | wallet = "~/.config/solana/id.json" 16 | 17 | [scripts] 18 | test = "../node_modules/.bin/nx run anchor:jest" 19 | 20 | [test] 21 | startup_wait = 5000 22 | shutdown_wait = 2000 23 | upgradeable = false 24 | 25 | [test.validator] 26 | bind_address = "127.0.0.1" 27 | ledger = ".anchor/test-ledger" 28 | rpc_port = 8899 29 | -------------------------------------------------------------------------------- /counterAnchor/web/app/react-query-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { ReactNode, useState } from 'react'; 4 | import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; 5 | import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; 6 | 7 | export function ReactQueryProvider({ children }: { children: ReactNode }) { 8 | const [client] = useState(new QueryClient()); 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /counterNative/client/client.ts: -------------------------------------------------------------------------------- 1 | import * as web3 from "@solana/web3.js"; 2 | // Manually initialize variables that are automatically defined in Playground 3 | const PROGRAM_ID = new web3.PublicKey("52XVTAWa3VfFheKnD1VVX7JzMTyKhCYiTGumwNiCnGz"); 4 | const connection = new web3.Connection("https://api.devnet.solana.com", "confirmed"); 5 | const wallet = { keypair: web3.Keypair.generate() }; 6 | 7 | // Client 8 | console.log("My address:", wallet.keypair.publicKey.toString()); 9 | const balance = await connection.getBalance(wallet.keypair.publicKey); 10 | console.log(`My balance: ${balance / web3.LAMPORTS_PER_SOL} SOL`); 11 | -------------------------------------------------------------------------------- /counterAnchor/web/components/account/account-list-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useWallet } from '@solana/wallet-adapter-react'; 4 | import { WalletButton } from '../solana/solana-provider'; 5 | 6 | import { redirect } from 'next/navigation'; 7 | 8 | export default function AccountListFeature() { 9 | const { publicKey } = useWallet(); 10 | 11 | if (publicKey) { 12 | return redirect(`/account/${publicKey.toString()}`); 13 | } 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /counterAsmSbpf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sbpfCounter", 3 | "description": "Created with sBPF", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "KEYPAIR=$(solana config get | grep Keypair | cut -b 15-) && cross-env SIGNER=$(cat $KEYPAIR) mocha --import=tsx tests/**/*.ts" 9 | }, 10 | "dependencies": { 11 | "@solana/web3.js": "^1.91.8" 12 | }, 13 | "devDependencies": { 14 | "cross-env": "^7.0.3", 15 | "@types/chai": "^4.3.16", 16 | "@types/mocha": "^10.0.6", 17 | "chai": "^5.1.1", 18 | "mocha": "^10.4.0", 19 | "tsx": "^4.11.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /counterAnchor/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2020", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./web/*"], 19 | "@counter/anchor": ["anchor/src/index.ts"] 20 | } 21 | }, 22 | "exclude": ["node_modules", "tmp"] 23 | } 24 | -------------------------------------------------------------------------------- /counterAnchor/anchor/.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsc": { 3 | "target": "es2017", 4 | "parser": { 5 | "syntax": "typescript", 6 | "decorators": true, 7 | "dynamicImport": true 8 | }, 9 | "transform": { 10 | "decoratorMetadata": true, 11 | "legacyDecorator": true 12 | }, 13 | "keepClassNames": true, 14 | "externalHelpers": true, 15 | "loose": true 16 | }, 17 | "module": { 18 | "type": "es6" 19 | }, 20 | "sourceMaps": true, 21 | "exclude": [ 22 | "jest.config.ts", 23 | ".*\\.spec.tsx?$", 24 | ".*\\.test.tsx?$", 25 | "./src/jest-setup.ts$", 26 | "./**/jest-setup.ts$", 27 | ".*.js$" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /counterAnchor/anchor/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | }, 17 | { 18 | "files": ["*.json"], 19 | "parser": "jsonc-eslint-parser", 20 | "rules": { 21 | "@nx/dependency-checks": [ 22 | "error", 23 | { 24 | "ignoredFiles": ["{projectRoot}/rollup.config.{js,ts,mjs,mts}"] 25 | } 26 | ] 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /counterNative/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check", 5 | "client": "yarn run ts-node client/*.ts", 6 | "test": "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 7 | }, 8 | "dependencies": { 9 | "@solana-developers/helpers": "^2.1.0", 10 | "@solana/web3.js": "1.78.4", 11 | "assert": "*", 12 | "borsh": "0.7.0" 13 | }, 14 | "devDependencies": { 15 | "@types/bn.js": "^5.1.1", 16 | "@types/chai": "^4.3.5", 17 | "@types/mocha": "^10.0.1", 18 | "chai": "^4.3.8", 19 | "mocha": "^10.2.0", 20 | "prettier": "^3.0.2", 21 | "ts-mocha": "^10.0.0", 22 | "typescript": "^5.2.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /counterAsm/asm/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | OUT_DIR ?= ./out 4 | 5 | all: $(OUT_DIR)/solana_program_rosetta_helloworld.so 6 | 7 | clean: 8 | rm -rf $(OUT_DIR) 9 | 10 | LLVM_DIR := ../solana-llvm 11 | STD_LIB_DIRS := $(LLVM_DIR)/lib 12 | 13 | LLVM_MC := $(LLVM_DIR)/bin/llvm-mc 14 | LLD := $(LLVM_DIR)/bin/ld.lld 15 | 16 | SBF_LLD_FLAGS := \ 17 | -z notext \ 18 | -shared \ 19 | --Bdynamic \ 20 | $(LLVM_DIR)/sbf.ld \ 21 | --entry entrypoint \ 22 | -L $(STD_LIB_DIRS) \ 23 | 24 | $(OUT_DIR)/main.o: ./main.s 25 | mkdir -p $(OUT_DIR) 26 | $(LLVM_MC) -triple sbf -filetype=obj -o $(OUT_DIR)/main.o main.s 27 | 28 | $(OUT_DIR)/solana_program_rosetta_helloworld.so: $(OUT_DIR)/main.o 29 | $(LLD) $(SBF_LLD_FLAGS) -o $(OUT_DIR)/solana_program_rosetta_helloworld.so $(OUT_DIR)/main.o 30 | -------------------------------------------------------------------------------- /counterAnchor/web/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { composePlugins, withNx } = require('@nx/next'); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | webpack: (config) => { 11 | config.externals = [ 12 | ...(config.externals || []), 13 | 'bigint', 14 | 'node-gyp-build', 15 | ]; 16 | return config; 17 | }, 18 | nx: { 19 | // Set this to true if you would like to use SVGR 20 | // See: https://github.com/gregberge/svgr 21 | svgr: false, 22 | }, 23 | }; 24 | 25 | const plugins = [ 26 | // Add more Next.js plugins to this list if needed. 27 | withNx, 28 | ]; 29 | 30 | module.exports = composePlugins(...plugins)(nextConfig); 31 | -------------------------------------------------------------------------------- /counterC/install-solana-c.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n $SOLANA_VERSION ]]; then 4 | solana_version="$SOLANA_VERSION" 5 | else 6 | solana_version="v2.0.8" 7 | fi 8 | 9 | output_dir="$1" 10 | if [[ -z $output_dir ]]; then 11 | output_dir="solana-c-sdk" 12 | fi 13 | output_dir="$(mkdir -p "$output_dir"; cd "$output_dir"; pwd)" 14 | cd $output_dir 15 | 16 | sdk_tar="sbf-sdk.tar.bz2" 17 | sdk_release_url="https://github.com/anza-xyz/agave/releases/download/$solana_version/$sdk_tar" 18 | echo "Downloading $sdk_release_url" 19 | curl --proto '=https' --tlsv1.2 -SfOL "$sdk_release_url" 20 | echo "Unpacking $sdk_tar" 21 | tar -xjf $sdk_tar 22 | rm $sdk_tar 23 | 24 | # Install platform-tools 25 | mv sbf-sdk/* . 26 | rmdir sbf-sdk 27 | ./scripts/install.sh 28 | echo "solana-c compiler available at $output_dir" 29 | -------------------------------------------------------------------------------- /counterC/counter/src/rust-unsave-c-code-assembly.s: -------------------------------------------------------------------------------- 1 | 2 | target/deploy/solana_program_rosetta_helloworld.so: file format elf64-bpf 3 | 4 | Sections: 5 | Idx Name Size VMA Type 6 | 0 00000000 0000000000000000 7 | 1 .text 00000028 0000000000000120 TEXT 8 | 2 .dynamic 00000070 0000000000000148 9 | 3 .dynsym 00000030 00000000000001b8 10 | 4 .dynstr 0000000c 00000000000001e8 11 | 5 .shstrtab 0000002a 0000000000000000 12 | 13 | Disassembly of section .text: 14 | 15 | 0000000000000120 : 16 | 36: 71 12 60 00 00 00 00 00 r2 = *(u8 *)(r1 + 0x60) 17 | 37: 07 02 00 00 01 00 00 00 r2 += 0x1 18 | 38: 73 21 60 00 00 00 00 00 *(u8 *)(r1 + 0x60) = r2 19 | 39: b7 00 00 00 00 00 00 00 r0 = 0x0 20 | 40: 95 00 00 00 00 00 00 00 exit 21 | -------------------------------------------------------------------------------- /counterAnchor/web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": ["error", "web/pages"] 14 | } 15 | }, 16 | { 17 | "files": ["*.ts", "*.tsx"], 18 | "rules": {} 19 | }, 20 | { 21 | "files": ["*.js", "*.jsx"], 22 | "rules": {} 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 26 | "rules": { 27 | "@nx/enforce-module-boundaries": [ 28 | "error", 29 | { 30 | "allow": ["@/"] 31 | } 32 | ] 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /counterAnchor/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /counterAnchor/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "plugins": [ 15 | { 16 | "name": "next" 17 | } 18 | ] 19 | }, 20 | "include": [ 21 | "**/*.ts", 22 | "**/*.tsx", 23 | "**/*.js", 24 | "**/*.jsx", 25 | "../web/.next/types/**/*.ts", 26 | "../dist/web/.next/types/**/*.ts", 27 | "next-env.d.ts", 28 | ".next/types/**/*.ts" 29 | ], 30 | "exclude": [ 31 | "node_modules", 32 | "jest.config.ts", 33 | "src/**/*.spec.ts", 34 | "src/**/*.test.ts" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /counterAnchor/web/components/cluster/cluster-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { AppHero } from '../ui/ui-layout'; 5 | import { ClusterUiModal } from './cluster-ui'; 6 | import { ClusterUiTable } from './cluster-ui'; 7 | 8 | export default function ClusterFeature() { 9 | const [showModal, setShowModal] = useState(false); 10 | 11 | return ( 12 |
13 | 17 | setShowModal(false)} 20 | /> 21 | 27 | 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /counterAnchor/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | # dependencies 8 | node_modules 9 | # IDEs and editors 10 | /.idea 11 | .project 12 | .classpath 13 | .c9/ 14 | *.launch 15 | .settings/ 16 | *.sublime-workspace 17 | # IDE - VSCode 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | # misc 24 | /.sass-cache 25 | /connect.lock 26 | /coverage 27 | /libpeerconnection.log 28 | npm-debug.log 29 | yarn-error.log 30 | testem.log 31 | /typings 32 | # System Files 33 | .DS_Store 34 | Thumbs.db 35 | .nx/cache 36 | # Next.js 37 | .next 38 | .anchor 39 | anchor/target/deploy 40 | anchor/target/debug 41 | anchor/target/release 42 | anchor/target/sbf-solana-solana 43 | anchor/target/.rustc_info.json 44 | !anchor/target/idl/*.json 45 | !anchor/target/types/*.ts 46 | test-ledger 47 | .yarn -------------------------------------------------------------------------------- /counterAsmSbpf/src/sbpfCounter/sbpfCounter.s: -------------------------------------------------------------------------------- 1 | .globl entrypoint 2 | 3 | entrypoint: 4 | ldxdw r2, [r1 + 0] # The first byte of the payload in r1 holds the amount of accounts 5 | jne r2, 2, error # we make sure here we have two. First one will be the counter (for easy access) and second one the signer 6 | 7 | ldxdw r3, [r1 + 8 + 8 + 32 + 32 + 8] # accountNumber, 8byte flags(signer, writable, executable), account key, owner, lamports, dataSize, data 8 | # You can see the exact account layout here: https://solana.com/docs/programs/faq#input-parameter-serialization 9 | jne r3, 8, error # Here we check if the account data is exactly 8 byte for one u64 10 | 11 | ldxdw r4, [r1 + 8 + 8 + 32 + 32 + 8 + 8] // Put the account data into R4 12 | add64 r4, 1 // increase counter by one 13 | stxdw [r1 + 8 + 8 + 32 + 32 + 8 + 8], r4 // write data back to account 14 | 15 | jne r4, 1, error // Check if counter is 1 16 | 17 | exit 18 | 19 | error: 20 | mov64 r0, 1 # Set an error in r0 21 | exit 22 | 23 | 24 | -------------------------------------------------------------------------------- /counterC/counter/src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief C-based Counter BPF program 3 | */ 4 | #include 5 | 6 | // Highly optimized version directly working on the incoming byte array (5 CU) 7 | // extern uint64_t entrypoint(const uint8_t *input) { 8 | // ((uint8_t *)input)[96]++; 9 | // return SUCCESS; 10 | // } 11 | 12 | // Version that uses the SolAccountInfo struct with the deserialization it takes 96 CU 13 | extern uint64_t entrypoint(const uint8_t *input) { 14 | SolAccountInfo accounts[2]; 15 | SolParameters params = (SolParameters){.ka = accounts}; 16 | 17 | if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(accounts))) { 18 | sol_log("Failed to deserialize input"); 19 | return ERROR_INVALID_ARGUMENT; 20 | } 21 | 22 | uint8_t* data_ptr = params.ka[0].data; 23 | *data_ptr += 1; 24 | 25 | // Logging is expensive. Only do it when you really need to 26 | // sol_log_pubkey(params.ka[0].key); 27 | // sol_log_64(params.ka[0].data[0], 0, 0, 0, 0); 28 | 29 | return SUCCESS; 30 | } 31 | -------------------------------------------------------------------------------- /counterAsm/asm/main.s: -------------------------------------------------------------------------------- 1 | .globl entrypoint 2 | 3 | entrypoint: 4 | ldxdw r2, [r1 + 0] # The first byte of the payload in r1 holds the amount of accounts 5 | jne r2, 2, error # we make sure here we have two. First one will be the counter (for easy access) and second one the signer 6 | 7 | ldxdw r3, [r1 + 8 + 8 + 32 + 32 + 8] # accountNumber, 8byte flags(signer, writable, executable), account key, owner, lamports, dataSize, data 8 | # You can see the exact account layout here: https://solana.com/docs/programs/faq#input-parameter-serialization 9 | jne r3, 8, error # Here we check if the instruction data is exactly 8 for our u64 counter 10 | 11 | mov64 r4, r1 # Move all to r4 12 | add64 r4, 8 + 8 + 32 + 32 + 8 + 8 # Access the data fiel which holds our u64 value 13 | ldxdw r5, [r4 + 0] # Move data to r5 14 | 15 | add64 r5, 1 # Add 1 16 | 17 | stxdw [r4 +0], r5 # write it back into the data field 18 | 19 | exit 20 | 21 | error: 22 | mov64 r0, 1 # Set an error in r0 23 | exit -------------------------------------------------------------------------------- /counterAnchor/anchor/src/counter-exports.ts: -------------------------------------------------------------------------------- 1 | // Here we export some useful types and functions for interacting with the Anchor program. 2 | import { Cluster, PublicKey } from '@solana/web3.js'; 3 | import type { Counter } from '../target/types/counter'; 4 | import { IDL as CounterIDL } from '../target/types/counter'; 5 | 6 | // Re-export the generated IDL and type 7 | export { Counter, CounterIDL }; 8 | 9 | // After updating your program ID (e.g. after running `anchor keys sync`) update the value below. 10 | export const COUNTER_PROGRAM_ID = new PublicKey( 11 | '5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ' 12 | ); 13 | 14 | // This is a helper function to get the program ID for the Counter program depending on the cluster. 15 | export function getCounterProgramId(cluster: Cluster) { 16 | switch (cluster) { 17 | case 'devnet': 18 | case 'testnet': 19 | // This is the program ID for the Counter program on devnet and testnet. 20 | return new PublicKey('CounNZdmsQmWh7uVngV9FXW2dZ6zAgbJyYsvBpqbykg'); 21 | case 'mainnet-beta': 22 | default: 23 | return COUNTER_PROGRAM_ID; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /counterAnchor/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 jonasmac2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /counterAnchor/web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import { UiLayout } from '@/components/ui/ui-layout'; 3 | import { ClusterProvider } from '@/components/cluster/cluster-data-access'; 4 | import { SolanaProvider } from '@/components/solana/solana-provider'; 5 | import { ReactQueryProvider } from './react-query-provider'; 6 | 7 | export const metadata = { 8 | title: 'counter', 9 | description: 'Generated by create-solana-dapp', 10 | }; 11 | 12 | const links: { label: string; path: string }[] = [ 13 | { label: 'Account', path: '/account' }, 14 | { label: 'Clusters', path: '/clusters' }, 15 | { label: 'Counter Program', path: '/counter' }, 16 | ]; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: { 21 | children: React.ReactNode; 22 | }) { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /counterAnchor/anchor/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { readFileSync } from 'fs'; 3 | 4 | // Reading the SWC compilation config and remove the "exclude" 5 | // for the test files to be compiled by SWC 6 | const { exclude: _, ...swcJestConfig } = JSON.parse( 7 | readFileSync(`${__dirname}/.swcrc`, 'utf-8') 8 | ); 9 | 10 | // disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves. 11 | // If we do not disable this, SWC Core will read .swcrc and won't transform our test files due to "exclude" 12 | if (swcJestConfig.swcrc === undefined) { 13 | swcJestConfig.swcrc = false; 14 | } 15 | 16 | // Uncomment if using global setup/teardown files being transformed via swc 17 | // https://nx.dev/packages/jest/documents/overview#global-setup/teardown-with-nx-libraries 18 | // jest needs EsModule Interop to find the default exported setup/teardown functions 19 | // swcJestConfig.module.noInterop = false; 20 | 21 | export default { 22 | displayName: 'anchor', 23 | preset: '../jest.preset.js', 24 | transform: { 25 | '^.+\\.[tj]s$': ['@swc/jest', swcJestConfig], 26 | }, 27 | moduleFileExtensions: ['ts', 'js', 'html'], 28 | testEnvironment: '', 29 | coverageDirectory: '../coverage/anchor', 30 | }; 31 | -------------------------------------------------------------------------------- /counterC/counter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::msg; 2 | 3 | // Entrypoint function using unsafe Rust code to increment a value. 4 | // Note: This is equivalent to a typical Rust approach but leverages unsafe code 5 | // to perform direct pointer manipulation, which bypasses Rust's usual safety checks. 6 | // So if you don't like the borrow checker you can use this ;) (not recommended) 7 | // 8 | // # Safety 9 | // The input pointer is expected to be valid and aligned for this function to work correctly. 10 | // The function increments the byte at offset 96 from the provided input pointer 11 | // which is the first byte of the data field in the first account which is our counter account in this case. 12 | // Improper handling or misuse of the pointer could lead to undefined behavior. 13 | // Takes 5 CU. Can be improved to 4 CU using https://crates.io/crates/sbpf-asm-macros and removing the return type 14 | #[no_mangle] 15 | pub extern "C" fn entrypoint(input: *mut u8) -> u64 { 16 | unsafe { 17 | *input.add(96) += 1; 18 | } 19 | 0 20 | } 21 | 22 | // You can define custom panic handlers and heap allocators here. 23 | // #[cfg(target_os = "solana")] 24 | // #[no_mangle] 25 | // fn custom_panic(_info: &core::panic::PanicInfo<'_>) {} 26 | 27 | // solana_program::custom_heap_default!(); 28 | -------------------------------------------------------------------------------- /counterC/Makefile: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := ./solana-c-sdk/c/ 2 | 3 | .PHONY: all clean 4 | 5 | SRC_DIR ?= ./counter/src 6 | OUT_DIR ?= ./counter/out 7 | 8 | all: $(OUT_DIR)/solana_program_rosetta_helloworld.so 9 | 10 | clean: 11 | rm -rf $(OUT_DIR) 12 | 13 | LLVM_DIR = $(LOCAL_PATH)../dependencies/platform-tools/llvm 14 | LLVM_SYSTEM_INC_DIRS := $(LLVM_DIR)/lib/clang/17/include 15 | STD_INC_DIRS := $(LLVM_DIR)/include 16 | STD_LIB_DIRS := $(LLVM_DIR)/lib 17 | 18 | CC := $(LLVM_DIR)/bin/clang 19 | LLD := $(LLVM_DIR)/bin/ld.lld 20 | 21 | SYSTEM_INC_DIRS := \ 22 | $(LOCAL_PATH)inc \ 23 | $(LLVM_SYSTEM_INC_DIRS) \ 24 | 25 | C_FLAGS := \ 26 | -Werror \ 27 | -O2 \ 28 | -fno-builtin \ 29 | -std=c17 \ 30 | $(addprefix -isystem,$(SYSTEM_INC_DIRS)) \ 31 | $(addprefix -I,$(STD_INC_DIRS)) \ 32 | -target sbf \ 33 | -fPIC 34 | 35 | SBF_LLD_FLAGS := \ 36 | -z notext \ 37 | -shared \ 38 | --Bdynamic \ 39 | $(LOCAL_PATH)sbf.ld \ 40 | --entry entrypoint \ 41 | -L $(STD_LIB_DIRS) \ 42 | -lc \ 43 | 44 | $(OUT_DIR)/main.o: $(SRC_DIR)/main.c 45 | mkdir -p $(OUT_DIR) 46 | $(CC) $(C_FLAGS) -o $(OUT_DIR)/main.o -c $(SRC_DIR)/main.c 47 | 48 | $(OUT_DIR)/solana_program_rosetta_helloworld.so: $(OUT_DIR)/main.o 49 | $(LLD) $(SBF_LLD_FLAGS) -o $(OUT_DIR)/solana_program_rosetta_helloworld.so $(OUT_DIR)/main.o 50 | -------------------------------------------------------------------------------- /counterAnchor/web/components/dashboard/dashboard-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { AppHero } from '../ui/ui-layout'; 4 | 5 | const links: { label: string; href: string }[] = [ 6 | { label: 'Solana Docs', href: 'https://docs.solana.com/' }, 7 | { label: 'Solana Faucet', href: 'https://faucet.solana.com/' }, 8 | { label: 'Solana Cookbook', href: 'https://solanacookbook.com/' }, 9 | { label: 'Solana Stack Overflow', href: 'https://solana.stackexchange.com/' }, 10 | { 11 | label: 'Solana Developers GitHub', 12 | href: 'https://github.com/solana-developers/', 13 | }, 14 | ]; 15 | 16 | export default function DashboardFeature() { 17 | return ( 18 |
19 | 20 |
21 |
22 |

Here are some helpful links to get you started.

23 | {links.map((link, index) => ( 24 | 34 | ))} 35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /counterAnchor/web/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "web", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/next:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/web" 13 | }, 14 | "configurations": { 15 | "development": { 16 | "outputPath": "web" 17 | }, 18 | "production": {} 19 | } 20 | }, 21 | "serve": { 22 | "executor": "@nx/next:server", 23 | "defaultConfiguration": "development", 24 | "options": { 25 | "buildTarget": "web:build", 26 | "dev": true, 27 | "port": 3000 28 | }, 29 | "configurations": { 30 | "development": { 31 | "buildTarget": "web:build:development", 32 | "dev": true 33 | }, 34 | "production": { 35 | "buildTarget": "web:build:production", 36 | "dev": false 37 | } 38 | } 39 | }, 40 | "export": { 41 | "executor": "@nx/next:export", 42 | "options": { 43 | "buildTarget": "web:build:production" 44 | } 45 | }, 46 | "lint": { 47 | "executor": "@nx/eslint:lint", 48 | "outputs": ["{options.outputFile}"] 49 | } 50 | }, 51 | "tags": [] 52 | } 53 | -------------------------------------------------------------------------------- /counterAnchor/web/components/counter/counter-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useWallet } from '@solana/wallet-adapter-react'; 4 | import { WalletButton } from '../solana/solana-provider'; 5 | import { AppHero, ellipsify } from '../ui/ui-layout'; 6 | import { ExplorerLink } from '../cluster/cluster-ui'; 7 | import { useCounterProgram } from './counter-data-access'; 8 | import { CounterCreate, CounterList } from './counter-ui'; 9 | 10 | export default function CounterFeature() { 11 | const { publicKey } = useWallet(); 12 | const { programId } = useCounterProgram(); 13 | 14 | return publicKey ? ( 15 |
16 | 22 |

23 | 27 |

28 | 29 |
30 | 31 |
32 | ) : ( 33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /counterAnchor/web/components/account/account-detail-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { PublicKey } from '@solana/web3.js'; 4 | import { useMemo } from 'react'; 5 | 6 | import { useParams } from 'next/navigation'; 7 | 8 | import { ExplorerLink } from '../cluster/cluster-ui'; 9 | import { AppHero, ellipsify } from '../ui/ui-layout'; 10 | import { 11 | AccountBalance, 12 | AccountButtons, 13 | AccountTokens, 14 | AccountTransactions, 15 | } from './account-ui'; 16 | 17 | export default function AccountDetailFeature() { 18 | const params = useParams(); 19 | const address = useMemo(() => { 20 | if (!params.address) { 21 | return; 22 | } 23 | try { 24 | return new PublicKey(params.address); 25 | } catch (e) { 26 | console.log(`Invalid public key`, e); 27 | } 28 | }, [params]); 29 | if (!address) { 30 | return
Error loading account
; 31 | } 32 | 33 | return ( 34 |
35 | } 37 | subtitle={ 38 |
39 | 43 |
44 | } 45 | > 46 |
47 | 48 |
49 |
50 |
51 | 52 | 53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /counterAnchor/anchor/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anchor", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "anchor/src", 5 | "projectType": "library", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/rollup:rollup", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/anchor", 12 | "main": "anchor/src/index.ts", 13 | "tsConfig": "anchor/tsconfig.lib.json", 14 | "assets": [], 15 | "project": "anchor/package.json", 16 | "compiler": "swc", 17 | "format": ["cjs", "esm"] 18 | } 19 | }, 20 | "lint": { 21 | "executor": "@nx/eslint:lint", 22 | "outputs": ["{options.outputFile}"] 23 | }, 24 | "test": { 25 | "executor": "nx:run-commands", 26 | "options": { 27 | "cwd": "anchor", 28 | "commands": ["anchor test"], 29 | "parallel": false 30 | } 31 | }, 32 | "anchor": { 33 | "executor": "nx:run-commands", 34 | "options": { 35 | "cwd": "anchor", 36 | "commands": ["anchor"], 37 | "parallel": false 38 | } 39 | }, 40 | "localnet": { 41 | "executor": "nx:run-commands", 42 | "options": { 43 | "cwd": "anchor", 44 | "commands": ["anchor localnet"], 45 | "parallel": false 46 | } 47 | }, 48 | "jest": { 49 | "executor": "@nx/jest:jest", 50 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 51 | "options": { 52 | "jestConfig": "anchor/jest.config.ts" 53 | } 54 | } 55 | }, 56 | "tags": [] 57 | } 58 | -------------------------------------------------------------------------------- /counterAnchor/nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "targetDefaults": { 4 | "build": { 5 | "cache": true, 6 | "dependsOn": ["^build"], 7 | "inputs": ["production", "^production"] 8 | }, 9 | "lint": { 10 | "cache": true, 11 | "inputs": [ 12 | "default", 13 | "{workspaceRoot}/.eslintrc.json", 14 | "{workspaceRoot}/.eslintignore", 15 | "{workspaceRoot}/eslint.config.js" 16 | ] 17 | }, 18 | "@nx/jest:jest": { 19 | "cache": true, 20 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], 21 | "options": { 22 | "passWithNoTests": true 23 | }, 24 | "configurations": { 25 | "ci": { 26 | "ci": true, 27 | "codeCoverage": true 28 | } 29 | } 30 | } 31 | }, 32 | "namedInputs": { 33 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 34 | "production": [ 35 | "default", 36 | "!{projectRoot}/.eslintrc.json", 37 | "!{projectRoot}/eslint.config.js", 38 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 39 | "!{projectRoot}/tsconfig.spec.json", 40 | "!{projectRoot}/jest.config.[jt]s", 41 | "!{projectRoot}/src/test-setup.[jt]s", 42 | "!{projectRoot}/test-setup.[jt]s" 43 | ], 44 | "sharedGlobals": [] 45 | }, 46 | "generators": { 47 | "@nx/react": { 48 | "application": { 49 | "babel": true 50 | } 51 | }, 52 | "@nx/next": { 53 | "application": { 54 | "style": "css", 55 | "linter": "eslint" 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /counterAnchor/web/components/solana/solana-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import dynamic from 'next/dynamic'; 4 | import { AnchorProvider } from '@coral-xyz/anchor'; 5 | import { WalletError } from '@solana/wallet-adapter-base'; 6 | import { 7 | AnchorWallet, 8 | useConnection, 9 | useWallet, 10 | ConnectionProvider, 11 | WalletProvider, 12 | } from '@solana/wallet-adapter-react'; 13 | import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; 14 | import { ReactNode, useCallback, useMemo } from 'react'; 15 | import { useCluster } from '../cluster/cluster-data-access'; 16 | 17 | require('@solana/wallet-adapter-react-ui/styles.css'); 18 | 19 | export const WalletButton = dynamic( 20 | async () => 21 | (await import('@solana/wallet-adapter-react-ui')).WalletMultiButton, 22 | { ssr: false } 23 | ); 24 | 25 | export function SolanaProvider({ children }: { children: ReactNode }) { 26 | const { cluster } = useCluster(); 27 | const endpoint = useMemo(() => cluster.endpoint, [cluster]); 28 | const onError = useCallback((error: WalletError) => { 29 | console.error(error); 30 | }, []); 31 | 32 | return ( 33 | 34 | 35 | {children} 36 | 37 | 38 | ); 39 | } 40 | 41 | export function useAnchorProvider() { 42 | const { connection } = useConnection(); 43 | const wallet = useWallet(); 44 | 45 | return new AnchorProvider(connection, wallet as AnchorWallet, { 46 | commitment: 'confirmed', 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /install-solana-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n $SOLANA_TOOLS_VERSION ]]; then 4 | solana_tools_version="$SOLANA_TOOLS_VERSION" 5 | else 6 | solana_tools_version="v1.43.1" 7 | fi 8 | release_url="https://github.com/joncinque/solana-zig-bootstrap/releases/download/solana-$solana_tools_version" 9 | 10 | output_dir="$1" 11 | if [[ -z $output_dir ]]; then 12 | output_dir="solana-llvm" 13 | fi 14 | output_dir="$(mkdir -p "$output_dir"; cd "$output_dir"; pwd)" 15 | cd $output_dir 16 | 17 | arch=$(uname -m) 18 | if [[ "$arch" == "arm64" ]]; then 19 | arch="aarch64" 20 | fi 21 | case $(uname -s | cut -c1-7) in 22 | "Linux") 23 | os="linux" 24 | abi="musl" 25 | ;; 26 | "Darwin") 27 | os="macos" 28 | abi="none" 29 | ;; 30 | "Windows" | "MINGW64") 31 | os="windows" 32 | abi="gnu" 33 | ;; 34 | *) 35 | echo "install-solana-llvm.sh: Unknown OS $(uname -s)" >&2 36 | exit 1 37 | ;; 38 | esac 39 | 40 | solana_llvm_tar=llvm-$arch-$os-$abi.tar.bz2 41 | url="$release_url/$solana_llvm_tar" 42 | echo "Downloading $url" 43 | curl --proto '=https' --tlsv1.2 -SfOL "$url" 44 | echo "Unpacking $solana_llvm_tar" 45 | tar -xjf $solana_llvm_tar 46 | rm $solana_llvm_tar 47 | 48 | solana_llvm_dir="llvm-$arch-$os-$abi-baseline" 49 | mv "$solana_llvm_dir"/* . 50 | rmdir $solana_llvm_dir 51 | 52 | echo "PHDRS 53 | { 54 | text PT_LOAD ; 55 | rodata PT_LOAD ; 56 | data PT_LOAD ; 57 | dynamic PT_DYNAMIC ; 58 | } 59 | 60 | SECTIONS 61 | { 62 | . = SIZEOF_HEADERS; 63 | .text : { *(.text*) } :text 64 | .rodata : { *(.rodata*) } :rodata 65 | .data.rel.ro : { *(.data.rel.ro*) } :rodata 66 | .dynamic : { *(.dynamic) } :dynamic 67 | .dynsym : { *(.dynsym) } :data 68 | .dynstr : { *(.dynstr) } :data 69 | .rel.dyn : { *(.rel.dyn) } :data 70 | /DISCARD/ : { 71 | *(.eh_frame*) 72 | *(.gnu.hash*) 73 | *(.hash*) 74 | } 75 | }" > sbf.ld 76 | echo "solana-llvm tools available at $output_dir" 77 | -------------------------------------------------------------------------------- /counterAsm/install-solana-llvm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n $SOLANA_TOOLS_VERSION ]]; then 4 | solana_tools_version="$SOLANA_TOOLS_VERSION" 5 | else 6 | solana_tools_version="v1.43.1" 7 | fi 8 | release_url="https://github.com/joncinque/solana-zig-bootstrap/releases/download/solana-$solana_tools_version" 9 | 10 | output_dir="$1" 11 | if [[ -z $output_dir ]]; then 12 | output_dir="solana-llvm" 13 | fi 14 | output_dir="$(mkdir -p "$output_dir"; cd "$output_dir"; pwd)" 15 | cd $output_dir 16 | 17 | arch=$(uname -m) 18 | if [[ "$arch" == "arm64" ]]; then 19 | arch="aarch64" 20 | fi 21 | case $(uname -s | cut -c1-7) in 22 | "Linux") 23 | os="linux" 24 | abi="musl" 25 | ;; 26 | "Darwin") 27 | os="macos" 28 | abi="none" 29 | ;; 30 | "Windows" | "MINGW64") 31 | os="windows" 32 | abi="gnu" 33 | ;; 34 | *) 35 | echo "install-solana-llvm.sh: Unknown OS $(uname -s)" >&2 36 | exit 1 37 | ;; 38 | esac 39 | 40 | solana_llvm_tar=llvm-$arch-$os-$abi.tar.bz2 41 | url="$release_url/$solana_llvm_tar" 42 | echo "Downloading $url" 43 | curl --proto '=https' --tlsv1.2 -SfOL "$url" 44 | echo "Unpacking $solana_llvm_tar" 45 | tar -xjf $solana_llvm_tar 46 | rm $solana_llvm_tar 47 | 48 | solana_llvm_dir="llvm-$arch-$os-$abi-baseline" 49 | mv "$solana_llvm_dir"/* . 50 | rmdir $solana_llvm_dir 51 | 52 | echo "PHDRS 53 | { 54 | text PT_LOAD ; 55 | rodata PT_LOAD ; 56 | data PT_LOAD ; 57 | dynamic PT_DYNAMIC ; 58 | } 59 | 60 | SECTIONS 61 | { 62 | . = SIZEOF_HEADERS; 63 | .text : { *(.text*) } :text 64 | .rodata : { *(.rodata*) } :rodata 65 | .data.rel.ro : { *(.data.rel.ro*) } :rodata 66 | .dynamic : { *(.dynamic) } :dynamic 67 | .dynsym : { *(.dynsym) } :data 68 | .dynstr : { *(.dynstr) } :data 69 | .rel.dyn : { *(.rel.dyn) } :data 70 | /DISCARD/ : { 71 | *(.eh_frame*) 72 | *(.gnu.hash*) 73 | *(.hash*) 74 | } 75 | }" > sbf.ld 76 | echo "solana-llvm tools available at $output_dir" 77 | -------------------------------------------------------------------------------- /counterAnchor/README.md: -------------------------------------------------------------------------------- 1 | # counter 2 | 3 | This project is generated with the [create-solana-dapp](https://github.com/solana-developers/create-solana-dapp) generator. 4 | 5 | ## Getting Started 6 | 7 | ### Prerequisites 8 | 9 | - Node v18.18.0 or higher 10 | 11 | - Rust v1.70.0 or higher 12 | - Anchor CLI 0.29.0 or higher 13 | - Solana CLI 1.17.0 or higher 14 | 15 | ### Installation 16 | 17 | #### Clone the repo 18 | 19 | ```shell 20 | git clone 21 | cd 22 | ``` 23 | 24 | #### Install Dependencies 25 | 26 | ```shell 27 | npm install 28 | ``` 29 | 30 | #### Start the web app 31 | 32 | ``` 33 | npm run dev 34 | ``` 35 | 36 | ## Apps 37 | 38 | ### anchor 39 | 40 | This is a Solana program written in Rust using the Anchor framework. 41 | 42 | #### Commands 43 | 44 | You can use any normal anchor commands. Either move to the `anchor` directory and run the `anchor` command or prefix the command with `npm run`, eg: `npm run anchor`. 45 | 46 | #### Sync the program id: 47 | 48 | Running this command will create a new keypair in the `anchor/target/deploy` directory and save the address to the Anchor config file and update the `declare_id!` macro in the `./src/lib.rs` file of the program. 49 | 50 | You will manually need to update the constant in `anchor/lib/counter-exports.ts` to match the new program id. 51 | 52 | ```shell 53 | npm run anchor keys sync 54 | ``` 55 | 56 | #### Build the program: 57 | 58 | ```shell 59 | npm run anchor-build 60 | ``` 61 | 62 | #### Start the test validator with the program deployed: 63 | 64 | ```shell 65 | npm run anchor-localnet 66 | ``` 67 | 68 | #### Run the tests 69 | 70 | ```shell 71 | npm run anchor-test 72 | ``` 73 | 74 | #### Deploy to Devnet 75 | 76 | ```shell 77 | npm run anchor deploy --provider.cluster devnet 78 | ``` 79 | 80 | ### web 81 | 82 | This is a React app that uses the Anchor generated client to interact with the Solana program. 83 | 84 | #### Commands 85 | 86 | Start the web app 87 | 88 | ```shell 89 | npm run dev 90 | ``` 91 | 92 | Build the web app 93 | 94 | ```shell 95 | npm run build 96 | ``` 97 | -------------------------------------------------------------------------------- /counterC/counter/tests/counter_test.rs: -------------------------------------------------------------------------------- 1 | use ::{ 2 | solana_program::{ instruction::Instruction, log }, 3 | solana_program_test::{ tokio, ProgramTest }, 4 | solana_sdk::{ 5 | account, 6 | instruction::AccountMeta, 7 | msg, 8 | nonce::state::Data, 9 | signature::{ Keypair, Signer }, 10 | system_instruction, 11 | system_program, 12 | transaction::Transaction, 13 | }, 14 | }; 15 | mod program { 16 | solana_program::declare_id!("1og1111111111111111111111111111111111111111"); 17 | } 18 | fn program_test() -> ProgramTest { 19 | ProgramTest::new("solana_program_rosetta_helloworld", program::id(), None) 20 | } 21 | #[tokio::test] 22 | async fn call() { 23 | let pt = program_test(); 24 | let mut context = pt.start_with_context().await; 25 | let blockhash = context.banks_client.get_latest_blockhash().await.unwrap(); 26 | 27 | let counter_keypair: Keypair = Keypair::new(); 28 | 29 | let rent_exempt = context.banks_client.get_rent().await.unwrap(); 30 | 31 | let create_counter_account_instruction: Instruction = system_instruction::create_account( 32 | &context.payer.pubkey(), 33 | &counter_keypair.pubkey(), 34 | rent_exempt.minimum_balance(8), 35 | 8, 36 | &program::id() 37 | ); 38 | 39 | let transaction = Transaction::new_signed_with_payer( 40 | &[ 41 | create_counter_account_instruction, 42 | Instruction { 43 | program_id: program::id(), 44 | accounts: vec![ 45 | AccountMeta::new(counter_keypair.pubkey(), true), 46 | AccountMeta::new(context.payer.pubkey(), true), 47 | ], 48 | data: vec![], 49 | }, 50 | ], 51 | Some(&context.payer.pubkey()), 52 | &[&context.payer, &counter_keypair], 53 | blockhash 54 | ); 55 | context.banks_client.process_transaction(transaction).await.unwrap(); 56 | 57 | let account = context.banks_client.get_account(counter_keypair.pubkey()).await.unwrap(); 58 | 59 | match account { 60 | Some(accout_data) => { 61 | panic!("dataq: {:?}", accout_data.data); 62 | } 63 | None => { 64 | println!("No data"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /counterNative/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint, 5 | entrypoint::ProgramResult, 6 | msg, 7 | program_error::ProgramError, 8 | pubkey::Pubkey, 9 | }; 10 | 11 | /// Define the type of state stored in accounts 12 | #[derive(BorshSerialize, BorshDeserialize, Debug)] 13 | pub struct Counter { 14 | pub counter: u64, 15 | } 16 | 17 | #[macro_export] 18 | macro_rules! compute_fn { 19 | ($msg:expr=> $($tt:tt)*) => { 20 | ::solana_program::msg!(concat!($msg, " {")); 21 | ::solana_program::log::sol_log_compute_units(); 22 | let res = { $($tt)* }; 23 | ::solana_program::log::sol_log_compute_units(); 24 | ::solana_program::msg!(concat!(" } // ", $msg)); 25 | res 26 | }; 27 | } 28 | 29 | // Declare and export the program's entrypoint 30 | entrypoint!(process_instruction); 31 | 32 | // Empty 240 CU 33 | // Without Logging 296 CU 34 | // With Logging count 841 CU 35 | // With tracking 2,886 CU 36 | pub fn process_instruction( 37 | program_id: &Pubkey, 38 | accounts: &[AccountInfo], // Only contains the counter account 39 | _instruction_data: &[u8], // Only one instruction so we can ignore the rest 40 | ) -> ProgramResult { 41 | let account; 42 | 43 | // 102 CU 44 | compute_fn!("Get Account Info" => { 45 | let accounts_iter = &mut accounts.iter(); 46 | account = next_account_info(accounts_iter)?; 47 | }); 48 | 49 | // 103 CU 50 | compute_fn!("Do a signer check" => { 51 | if !account.is_signer { 52 | panic!("Account {} must be a signer", account.is_signer); 53 | } 54 | }); 55 | 56 | // 116 CU 57 | compute_fn!("Owner Check " => { 58 | if account.owner != program_id { 59 | msg!("Greeted account does not have the correct program id"); 60 | return Err(ProgramError::IncorrectProgramId); 61 | } 62 | }); 63 | 64 | let mut greeting_account; 65 | 66 | // 128 CU 67 | compute_fn!("Load account data, increase and serialize " => { 68 | // Increment and store the number of times the account has been greeted 69 | greeting_account = Counter::try_from_slice(&account.data.borrow())?; 70 | greeting_account.counter += 1; 71 | greeting_account.serialize(&mut *account.data.borrow_mut())?; 72 | }); 73 | 74 | // 645 CU 75 | compute_fn!("Logging " => { 76 | msg!("Greeted {} time(s)!", greeting_account.counter); 77 | }); 78 | 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /counterAnchor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@counter/source", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "anchor": "nx run anchor:anchor", 7 | "anchor-build": "nx run anchor:anchor build", 8 | "anchor-localnet": "nx run anchor:anchor localnet", 9 | "anchor-test": "nx run anchor:anchor test", 10 | "feature": "nx generate @solana-developers/preset-react:feature", 11 | "build": "nx build web", 12 | "dev": "nx serve web" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@coral-xyz/anchor": "^0.29.0", 17 | "@solana-developers/preset-next": "2.2.0", 18 | "@solana/spl-token": "0.4.1", 19 | "@solana/wallet-adapter-base": "^0.9.23", 20 | "@solana/wallet-adapter-react": "^0.15.35", 21 | "@solana/wallet-adapter-react-ui": "^0.9.35", 22 | "@solana/web3.js": "1.90.0", 23 | "@swc/helpers": "~0.5.2", 24 | "@tabler/icons-react": "2.47.0", 25 | "@tailwindcss/typography": "0.5.10", 26 | "@tanstack/react-query": "5.24.1", 27 | "@tanstack/react-query-next-experimental": "5.24.1", 28 | "bs58": "5.0.0", 29 | "buffer": "6.0.3", 30 | "daisyui": "4.7.2", 31 | "encoding": "0.1.13", 32 | "jotai": "2.6.5", 33 | "next": "13.4.4", 34 | "react": "18.2.0", 35 | "react-dom": "18.2.0", 36 | "react-hot-toast": "2.4.1", 37 | "tslib": "^2.3.0" 38 | }, 39 | "devDependencies": { 40 | "@nx/eslint": "17.2.7", 41 | "@nx/eslint-plugin": "17.2.7", 42 | "@nx/jest": "17.2.7", 43 | "@nx/js": "17.2.7", 44 | "@nx/next": "17.2.7", 45 | "@nx/react": "17.2.7", 46 | "@nx/rollup": "17.2.7", 47 | "@nx/workspace": "17.2.7", 48 | "@swc-node/register": "~1.6.7", 49 | "@swc/cli": "~0.1.62", 50 | "@swc/core": "~1.3.85", 51 | "@swc/jest": "0.2.20", 52 | "@testing-library/react": "14.0.0", 53 | "@types/jest": "^29.4.0", 54 | "@types/node": "18.16.9", 55 | "@types/react": "18.2.33", 56 | "@types/react-dom": "18.2.14", 57 | "@typescript-eslint/eslint-plugin": "^6.9.1", 58 | "@typescript-eslint/parser": "^6.9.1", 59 | "autoprefixer": "10.4.13", 60 | "eslint": "~8.48.0", 61 | "eslint-config-next": "13.4.4", 62 | "eslint-config-prettier": "^9.0.0", 63 | "eslint-plugin-import": "2.27.5", 64 | "eslint-plugin-jsx-a11y": "6.7.1", 65 | "eslint-plugin-react": "7.32.2", 66 | "eslint-plugin-react-hooks": "4.6.0", 67 | "jest": "^29.4.1", 68 | "jest-environment-jsdom": "^29.4.1", 69 | "nx": "17.2.7", 70 | "postcss": "8.4.21", 71 | "prettier": "^2.6.2", 72 | "tailwindcss": "3.2.7", 73 | "ts-jest": "^29.1.0", 74 | "ts-node": "10.9.1", 75 | "typescript": "~5.2.2" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /counterAsmSbpf/tests/sbpfCounter.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | SystemProgram, 5 | Transaction, 6 | TransactionInstruction, 7 | } from "@solana/web3.js"; 8 | import programSeed from "../deploy/sbpfCounter-keypair.json"; 9 | 10 | const programKeypair = Keypair.fromSecretKey(new Uint8Array(programSeed)); 11 | const program = programKeypair.publicKey; 12 | const signerSeed = JSON.parse(process.env.SIGNER!); 13 | const signer = Keypair.fromSecretKey(new Uint8Array(signerSeed)); 14 | 15 | console.log("Signer" + signer.publicKey.toBase58()); 16 | 17 | const connection = new Connection("http://127.0.0.1:8899", { 18 | commitment: "confirmed", 19 | }); 20 | 21 | describe("Assembly tests", () => { 22 | it('Create counter and increase by one!"', async () => { 23 | const tx = new Transaction(); 24 | var counterKeyPair = new Keypair(); 25 | 26 | var rent = await connection.getMinimumBalanceForRentExemption(8); 27 | 28 | // Create the counter account before hand 29 | // (Using a PDA would work differently since the program needs to claim ownership of the account first) 30 | var params = { 31 | fromPubkey: signer.publicKey, 32 | newAccountPubkey: counterKeyPair.publicKey, 33 | lamports: rent, 34 | space: 8, 35 | programId: program, 36 | }; 37 | var createAccountIx = SystemProgram.createAccount(params); 38 | 39 | tx.instructions.push(createAccountIx); 40 | 41 | tx.instructions.push( 42 | new TransactionInstruction({ 43 | keys: [ 44 | { 45 | pubkey: counterKeyPair.publicKey, // Notice how the first account is the counter so we can easily access it 46 | isSigner: true, 47 | isWritable: true, 48 | }, 49 | { 50 | pubkey: signer.publicKey, 51 | isSigner: true, 52 | isWritable: true, 53 | }, 54 | ], 55 | programId: program, 56 | }) 57 | ); 58 | var result = await signAndSend(tx, counterKeyPair).then(confirm).then(log); 59 | 60 | var transaction = await connection.getTransaction(result); 61 | console.log(transaction?.meta?.logMessages); 62 | 63 | var counter = await connection.getAccountInfo(counterKeyPair.publicKey); 64 | console.log(counter?.data); 65 | }); 66 | }); 67 | 68 | const confirm = async (signature: string): Promise => { 69 | const block = await connection.getLatestBlockhash(); 70 | await connection.confirmTransaction({ 71 | signature, 72 | ...block, 73 | }); 74 | return signature; 75 | }; 76 | 77 | const log = async (signature: string): Promise => { 78 | var transaction = await connection.getTransaction(signature); 79 | console.log(JSON.stringify(transaction?.meta?.logMessages, null, 2)); 80 | console.log( 81 | `Transaction successful! https://explorer.solana.com/tx/${signature}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899` 82 | ); 83 | return signature; 84 | }; 85 | 86 | const signAndSend = async ( 87 | tx: Transaction, 88 | additionalSigner: Keypair 89 | ): Promise => { 90 | const block = await connection.getLatestBlockhash(); 91 | tx.recentBlockhash = block.blockhash; 92 | tx.lastValidBlockHeight = block.lastValidBlockHeight; 93 | const signature = await connection.sendTransaction(tx, [ 94 | signer, 95 | additionalSigner, 96 | ]); 97 | return signature; 98 | }; 99 | -------------------------------------------------------------------------------- /counterNative/tests/native.test.ts: -------------------------------------------------------------------------------- 1 | import * as borsh from "borsh"; 2 | import assert from "assert"; 3 | import * as web3 from "@solana/web3.js"; 4 | import { airdropIfRequired, getKeypairFromFile } from "@solana-developers/helpers"; 5 | 6 | 7 | // Manually initialize variables that are automatically defined in Playground 8 | const PROGRAM_ID = new web3.PublicKey("52XVTAWa3VfFheKnD1VVX7JzMTyKhCYiTGumwNiCnGz"); 9 | const connection = new web3.Connection("https://api.devnet.solana.com", "confirmed"); 10 | 11 | /** 12 | * The state of a greeting account managed by the hello world program 13 | */ 14 | class CounterAccount { 15 | counter = 0; 16 | constructor(fields: { counter: number } | undefined = undefined) { 17 | if (fields) { 18 | this.counter = fields.counter; 19 | } 20 | } 21 | } 22 | 23 | /** 24 | * Borsh schema definition for greeting accounts 25 | */ 26 | const GreetingSchema = new Map([ 27 | [CounterAccount, { kind: "struct", fields: [["counter", "u64"]] }], 28 | ]); 29 | 30 | /** 31 | * The expected size of each greeting account. 32 | */ 33 | const GREETING_SIZE = borsh.serialize( 34 | GreetingSchema, 35 | new CounterAccount() 36 | ).length; 37 | 38 | describe("Test", () => { 39 | 40 | it("greet", async () => { 41 | const keyPair = await getKeypairFromFile(); 42 | const wallet = { keypair: keyPair }; 43 | 44 | const newBalance = await airdropIfRequired( 45 | connection, 46 | keyPair.publicKey, 47 | 0.5 * web3.LAMPORTS_PER_SOL, 48 | 1 * web3.LAMPORTS_PER_SOL, 49 | ); 50 | 51 | // Create greetings account instruction 52 | const counterAccountKp = new web3.Keypair(); 53 | const lamports = await connection.getMinimumBalanceForRentExemption( 54 | GREETING_SIZE 55 | ); 56 | const createGreetingAccountIx = web3.SystemProgram.createAccount({ 57 | fromPubkey: wallet.keypair.publicKey, 58 | lamports, 59 | newAccountPubkey: counterAccountKp.publicKey, 60 | programId: PROGRAM_ID, 61 | space: GREETING_SIZE, 62 | }); 63 | 64 | // Create greet instruction 65 | const greetIx = new web3.TransactionInstruction({ 66 | keys: [ 67 | { 68 | pubkey: counterAccountKp.publicKey, 69 | isSigner: false, 70 | isWritable: true, 71 | }, 72 | ], 73 | programId: PROGRAM_ID, 74 | }); 75 | 76 | // Create transaction and add the instructions 77 | const tx = new web3.Transaction(); 78 | tx.add(createGreetingAccountIx, greetIx); 79 | 80 | // Send and confirm the transaction 81 | const txHash = await web3.sendAndConfirmTransaction(connection, tx, [ 82 | wallet.keypair, 83 | counterAccountKp, 84 | ], {skipPreflight: true}); 85 | console.log(`Use 'solana confirm -v ${txHash}' to see the logs`); 86 | 87 | // Fetch the greetings account 88 | const greetingAccount = await connection.getAccountInfo( 89 | counterAccountKp.publicKey 90 | ); 91 | 92 | // Deserialize the account data 93 | const deserializedAccountData = borsh.deserialize( 94 | GreetingSchema, 95 | CounterAccount, 96 | greetingAccount.data 97 | ); 98 | 99 | // Assertions 100 | assert.equal(greetingAccount.lamports, lamports); 101 | assert(greetingAccount.owner.equals(PROGRAM_ID)); 102 | assert.deepEqual(greetingAccount.data, Buffer.from([1, 0, 0, 0, 0, 0, 0, 0])); 103 | assert.equal(deserializedAccountData.counter, 1); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /counterAnchor/web/components/counter/counter-data-access.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { CounterIDL, getCounterProgramId } from '@counter/anchor'; 4 | import { Program } from '@coral-xyz/anchor'; 5 | import { useConnection } from '@solana/wallet-adapter-react'; 6 | import { Cluster, Keypair, PublicKey } from '@solana/web3.js'; 7 | import { useMutation, useQuery } from '@tanstack/react-query'; 8 | import { useMemo } from 'react'; 9 | import toast from 'react-hot-toast'; 10 | import { useCluster } from '../cluster/cluster-data-access'; 11 | import { useAnchorProvider } from '../solana/solana-provider'; 12 | import { useTransactionToast } from '../ui/ui-layout'; 13 | 14 | export function useCounterProgram() { 15 | const { connection } = useConnection(); 16 | const { cluster } = useCluster(); 17 | const transactionToast = useTransactionToast(); 18 | const provider = useAnchorProvider(); 19 | const programId = useMemo( 20 | () => getCounterProgramId(cluster.network as Cluster), 21 | [cluster] 22 | ); 23 | const program = new Program(CounterIDL, programId, provider); 24 | 25 | const accounts = useQuery({ 26 | queryKey: ['counter', 'all', { cluster }], 27 | queryFn: () => program.account.counter.all(), 28 | }); 29 | 30 | const getProgramAccount = useQuery({ 31 | queryKey: ['get-program-account', { cluster }], 32 | queryFn: () => connection.getParsedAccountInfo(programId), 33 | }); 34 | 35 | const initialize = useMutation({ 36 | mutationKey: ['counter', 'initialize', { cluster }], 37 | mutationFn: (keypair: Keypair) => 38 | program.methods 39 | .initialize() 40 | .accounts({ counter: keypair.publicKey }) 41 | .signers([keypair]) 42 | .rpc(), 43 | onSuccess: (signature) => { 44 | transactionToast(signature); 45 | return accounts.refetch(); 46 | }, 47 | onError: () => toast.error('Failed to initialize account'), 48 | }); 49 | 50 | return { 51 | program, 52 | programId, 53 | accounts, 54 | getProgramAccount, 55 | initialize, 56 | }; 57 | } 58 | 59 | export function useCounterProgramAccount({ account }: { account: PublicKey }) { 60 | const { cluster } = useCluster(); 61 | const transactionToast = useTransactionToast(); 62 | const { program, accounts } = useCounterProgram(); 63 | 64 | const accountQuery = useQuery({ 65 | queryKey: ['counter', 'fetch', { cluster, account }], 66 | queryFn: () => program.account.counter.fetch(account), 67 | }); 68 | 69 | const closeMutation = useMutation({ 70 | mutationKey: ['counter', 'close', { cluster, account }], 71 | mutationFn: () => 72 | program.methods.close().accounts({ counter: account }).rpc(), 73 | onSuccess: (tx) => { 74 | transactionToast(tx); 75 | return accounts.refetch(); 76 | }, 77 | }); 78 | 79 | const decrementMutation = useMutation({ 80 | mutationKey: ['counter', 'decrement', { cluster, account }], 81 | mutationFn: () => 82 | program.methods.decrement().accounts({ counter: account }).rpc(), 83 | onSuccess: (tx) => { 84 | transactionToast(tx); 85 | return accountQuery.refetch(); 86 | }, 87 | }); 88 | 89 | const incrementMutation = useMutation({ 90 | mutationKey: ['counter', 'increment', { cluster, account }], 91 | mutationFn: () => 92 | program.methods.increment().accounts({ counter: account }).rpc(), 93 | onSuccess: (tx) => { 94 | transactionToast(tx); 95 | return accountQuery.refetch(); 96 | }, 97 | }); 98 | 99 | const setMutation = useMutation({ 100 | mutationKey: ['counter', 'set', { cluster, account }], 101 | mutationFn: (value: number) => 102 | program.methods.set(value).accounts({ counter: account }).rpc(), 103 | onSuccess: (tx) => { 104 | transactionToast(tx); 105 | return accountQuery.refetch(); 106 | }, 107 | }); 108 | 109 | return { 110 | accountQuery, 111 | closeMutation, 112 | decrementMutation, 113 | incrementMutation, 114 | setMutation, 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /counterAnchor/web/components/cluster/cluster-data-access.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { clusterApiUrl, Connection } from '@solana/web3.js'; 4 | import { atom, useAtomValue, useSetAtom } from 'jotai'; 5 | import { atomWithStorage } from 'jotai/utils'; 6 | import { createContext, ReactNode, useContext } from 'react'; 7 | import toast from 'react-hot-toast'; 8 | 9 | export interface Cluster { 10 | name: string; 11 | endpoint: string; 12 | network?: ClusterNetwork; 13 | active?: boolean; 14 | } 15 | 16 | export enum ClusterNetwork { 17 | Mainnet = 'mainnet-beta', 18 | Testnet = 'testnet', 19 | Devnet = 'devnet', 20 | Custom = 'custom', 21 | } 22 | 23 | // By default, we don't configure the mainnet-beta cluster 24 | // The endpoint provided by clusterApiUrl('mainnet-beta') does not allow access from the browser due to CORS restrictions 25 | // To use the mainnet-beta cluster, provide a custom endpoint 26 | export const defaultClusters: Cluster[] = [ 27 | { 28 | name: 'devnet', 29 | endpoint: clusterApiUrl('devnet'), 30 | network: ClusterNetwork.Devnet, 31 | }, 32 | { name: 'local', endpoint: 'http://localhost:8899' }, 33 | { 34 | name: 'testnet', 35 | endpoint: clusterApiUrl('testnet'), 36 | network: ClusterNetwork.Testnet, 37 | }, 38 | ]; 39 | 40 | const clusterAtom = atomWithStorage( 41 | 'solana-cluster', 42 | defaultClusters[0] 43 | ); 44 | const clustersAtom = atomWithStorage( 45 | 'solana-clusters', 46 | defaultClusters 47 | ); 48 | 49 | const activeClustersAtom = atom((get) => { 50 | const clusters = get(clustersAtom); 51 | const cluster = get(clusterAtom); 52 | return clusters.map((item) => ({ 53 | ...item, 54 | active: item.name === cluster.name, 55 | })); 56 | }); 57 | 58 | const activeClusterAtom = atom((get) => { 59 | const clusters = get(activeClustersAtom); 60 | 61 | return clusters.find((item) => item.active) || clusters[0]; 62 | }); 63 | 64 | export interface ClusterProviderContext { 65 | cluster: Cluster; 66 | clusters: Cluster[]; 67 | addCluster: (cluster: Cluster) => void; 68 | deleteCluster: (cluster: Cluster) => void; 69 | setCluster: (cluster: Cluster) => void; 70 | getExplorerUrl(path: string): string; 71 | } 72 | 73 | const Context = createContext( 74 | {} as ClusterProviderContext 75 | ); 76 | 77 | export function ClusterProvider({ children }: { children: ReactNode }) { 78 | const cluster = useAtomValue(activeClusterAtom); 79 | const clusters = useAtomValue(activeClustersAtom); 80 | const setCluster = useSetAtom(clusterAtom); 81 | const setClusters = useSetAtom(clustersAtom); 82 | 83 | const value: ClusterProviderContext = { 84 | cluster, 85 | clusters: clusters.sort((a, b) => (a.name > b.name ? 1 : -1)), 86 | addCluster: (cluster: Cluster) => { 87 | try { 88 | new Connection(cluster.endpoint); 89 | setClusters([...clusters, cluster]); 90 | } catch (err) { 91 | toast.error(`${err}`); 92 | } 93 | }, 94 | deleteCluster: (cluster: Cluster) => { 95 | setClusters(clusters.filter((item) => item.name !== cluster.name)); 96 | }, 97 | setCluster: (cluster: Cluster) => setCluster(cluster), 98 | getExplorerUrl: (path: string) => 99 | `https://explorer.solana.com/${path}${getClusterUrlParam(cluster)}`, 100 | }; 101 | return {children}; 102 | } 103 | 104 | export function useCluster() { 105 | return useContext(Context); 106 | } 107 | 108 | function getClusterUrlParam(cluster: Cluster): string { 109 | let suffix = ''; 110 | switch (cluster.network) { 111 | case ClusterNetwork.Devnet: 112 | suffix = 'devnet'; 113 | break; 114 | case ClusterNetwork.Mainnet: 115 | suffix = ''; 116 | break; 117 | case ClusterNetwork.Testnet: 118 | suffix = 'testnet'; 119 | break; 120 | default: 121 | suffix = `custom&customUrl=${encodeURIComponent(cluster.endpoint)}`; 122 | break; 123 | } 124 | 125 | return suffix.length ? `?cluster=${suffix}` : ''; 126 | } 127 | -------------------------------------------------------------------------------- /counterAsm/tests/functional.rs: -------------------------------------------------------------------------------- 1 | use ::{ 2 | solana_program::instruction::Instruction, 3 | solana_program_test::{ tokio, ProgramTest }, 4 | solana_sdk::{ 5 | signature::Signer, 6 | transaction::Transaction, 7 | system_instruction, 8 | pubkey::Pubkey, 9 | instruction::AccountMeta, 10 | }, 11 | }; 12 | mod program { 13 | solana_program::declare_id!("1og1111111111111111111111111111111111111111"); 14 | } 15 | fn program_test() -> ProgramTest { 16 | ProgramTest::new("solana_program_rosetta_helloworld", program::id(), None) 17 | } 18 | #[tokio::test] 19 | async fn call() { 20 | let pt = program_test(); 21 | let mut context = pt.start_with_context().await; 22 | let blockhash = context.banks_client.get_latest_blockhash().await.unwrap(); 23 | 24 | let newCounterKeypair = solana_sdk::signature::Keypair::new(); 25 | let pda_account_data = context.banks_client 26 | .get_account(newCounterKeypair.pubkey()).await 27 | .unwrap(); 28 | if pda_account_data.is_none() { 29 | // Set lamports to cover rent and size (8 bytes for a u64 counter) 30 | let create_pda_ix = system_instruction::create_account( 31 | &context.payer.pubkey(), 32 | &newCounterKeypair.pubkey(), 33 | 1_000_000_000, // Adjust lamports based on rent-exempt requirement 34 | 8, // Size in bytes (64-bit counter = 8 bytes) 35 | &program::id() // The program owning the PDA 36 | ); 37 | 38 | let create_pda_tx = Transaction::new_signed_with_payer( 39 | &[create_pda_ix], 40 | Some(&context.payer.pubkey()), 41 | &[&context.payer, &newCounterKeypair], 42 | blockhash 43 | ); 44 | 45 | context.banks_client.process_transaction(create_pda_tx).await.unwrap(); 46 | println!("PDA account created!"); 47 | } else { 48 | println!("PDA account already exists!"); 49 | } 50 | 51 | let pda_account_data = context.banks_client 52 | .get_account(newCounterKeypair.pubkey()).await 53 | .unwrap(); 54 | if pda_account_data.is_none() { 55 | panic!("PDA account not found!"); 56 | } else { 57 | let pdaData = pda_account_data.unwrap(); 58 | println!("PDA account data: {:?}", pdaData); 59 | } 60 | 61 | let transaction = Transaction::new_signed_with_payer( 62 | &[ 63 | Instruction { 64 | program_id: program::id(), 65 | accounts: vec![ 66 | AccountMeta::new(newCounterKeypair.pubkey(), true), 67 | AccountMeta::new_readonly(context.payer.pubkey(), true) 68 | ], 69 | data: vec![], 70 | }, 71 | ], 72 | Some(&context.payer.pubkey()), 73 | &[&context.payer, &newCounterKeypair], 74 | blockhash 75 | ); 76 | context.banks_client.process_transaction(transaction).await.unwrap(); 77 | 78 | let pda_account_data = context.banks_client 79 | .get_account(newCounterKeypair.pubkey()).await 80 | .unwrap(); 81 | let pdaData = pda_account_data.unwrap(); 82 | println!("PDA account data: {:?}", pdaData); 83 | assert!(pdaData.data[0] == 1); 84 | 85 | let transaction2 = Transaction::new_signed_with_payer( 86 | &[ 87 | Instruction { 88 | program_id: program::id(), 89 | accounts: vec![ 90 | AccountMeta::new(newCounterKeypair.pubkey(), true), 91 | AccountMeta::new_readonly(context.payer.pubkey(), true) 92 | ], 93 | data: vec![2], 94 | }, 95 | ], 96 | Some(&context.payer.pubkey()), 97 | &[&context.payer, &newCounterKeypair], 98 | blockhash 99 | ); 100 | context.banks_client.process_transaction(transaction2).await.unwrap(); 101 | 102 | let pda_account_data = context.banks_client 103 | .get_account(newCounterKeypair.pubkey()).await 104 | .unwrap(); 105 | let pdaData = pda_account_data.unwrap(); 106 | assert!(pdaData.data[0] == 2); 107 | //panic!("PDA account data: {:?}", pdaData); 108 | } 109 | -------------------------------------------------------------------------------- /counterC/README.md: -------------------------------------------------------------------------------- 1 | # Install Solana C compiler 2 | 3 | ```console 4 | ./install-solana-c.sh 5 | ``` 6 | 7 | # Build the program 8 | 9 | - Go to a program directory 10 | 11 | ```console 12 | cd counterC 13 | ``` 14 | 15 | ```console 16 | make 17 | ``` 18 | 19 | # Test the C program 20 | 21 | ```console 22 | SBF_OUT_DIR="out" cargo test --manifest-path "Cargo.toml" 23 | ``` 24 | 25 | OR use the helper from the root of this repo to build and test 26 | 27 | ```console 28 | ./test-c.sh counter 29 | ``` 30 | 31 | # Test the unsave Rust C program 32 | 33 | A similar result can also be achieved using unsafe rust. The code is in the `counter/src/lib.rs` file. The code is compiled to a shared object file using the following command: 34 | 35 | ```bash 36 | cargo build-sbf 37 | ``` 38 | 39 | Then you can run the same tests against the rust compiled file: 40 | 41 | ```bash 42 | SBF_OUT_DIR="../target/deploy" cargo test --manifest-path "Cargo.toml" 43 | ``` 44 | 45 | OR use the helper from the root of this repo to build and test the rust version as well: 46 | 47 | ```console 48 | ./test-rust-mangle-c.sh counter 49 | ``` 50 | 51 | Both the C and Rust version result in 5 CU for increasing a counter. So its a matter of taste which one you want to use. 52 | 53 | If you want to see the generated assembly code, you can run the following command: 54 | 55 | ```bash 56 | ./solana-c-sdk/dependencies/platform-tools/llvm/bin/clang \ 57 | -target bpfel \ 58 | -fno-builtin \ 59 | -std=c17 \ 60 | -O2 \ 61 | -S \ 62 | -I ./solana-c-sdk/c/inc \ 63 | -I ./solana-c-sdk/dependencies/platform-tools/llvm/lib/clang/17/include \ 64 | -I ./solana-c-sdk/dependencies/platform-tools/llvm/include \ 65 | -o counter/src/c-assembly-code.s \ 66 | ./counter/src/main.c 67 | ``` 68 | 69 | This is using the `clang`compiler with the `-S`flag which generates assembly code. 70 | 71 | If you want to see the generated Assemlby code for the C mangled code you can use `llvm-objdump`: 72 | 73 | First install llvm: 74 | 75 | ```bash 76 | brew install llvm 77 | ``` 78 | 79 | and then decompile the generated `.so` file using `llvm-objdump` and write it into a file: 80 | 81 | ```bash 82 | llvm-objdump \ 83 | --demangle \ 84 | --print-imm-hex \ 85 | -g --full-leading-addr \ 86 | --debug-vars --section-headers \ 87 | --symbolize-operands \ 88 | --source --disassemble target/deploy/solana_program_rosetta_helloworld.so > counter/src/rust-unsave-c-code-assembly.s 89 | ``` 90 | 91 | To get more info in the dissasebmled file you can use different flags when compiling. For example: 92 | 93 | ```bash 94 | RUSTFLAGS="-C debuginfo=2 -C opt-level=0" cargo build-sbf 95 | ``` 96 | 97 | of if you cant see some functions even: 98 | 99 | ```bash 100 | RUSTFLAGS="-C debuginfo=2 -C opt-level=0 -C link-dead-code" cargo build-sbf 101 | ``` 102 | 103 | If you wonder where the strings that you are printing are actually saved. Its in the `.rodata`which stands for read only data. 104 | 105 | You can find all strings in an `.so` file using the following command: 106 | 107 | ```bash 108 | strings target/deploy/solana_program_rosetta_helloworld.so 109 | ``` 110 | 111 | or you can directly print the `.rodata` section using `llvm-objdump`: 112 | 113 | ```bash 114 | llvm-objdump -s -j .rodata target/deploy/solana_program_rosetta_helloworld.so 115 | ``` 116 | 117 | You can also read the elf file using `readelf`: 118 | 119 | ```bash 120 | readelf -a target/deploy/solana_program_rosetta_helloworld.so 121 | ``` 122 | 123 | ### Input Parameter Serialization 124 | 125 | SBF loaders serialize the program input parameters into a byte array that is 126 | then passed to the program's entrypoint, where the program is responsible for 127 | deserializing it on-chain. One of the changes between the deprecated loader and 128 | the current loader is that the input parameters are serialized in a way that 129 | results in various parameters falling on aligned offsets within the aligned byte 130 | array. This allows deserialization implementations to directly reference the 131 | byte array and provide aligned pointers to the program. 132 | 133 | The latest loader serializes the program input parameters as follows (all 134 | encoding is little endian): 135 | 136 | - 8 bytes unsigned number of accounts 137 | - For each account 138 | - 1 byte indicating if this is a duplicate account, if not a duplicate then 139 | the value is 0xff, otherwise the value is the index of the account it is a 140 | duplicate of. 141 | - If duplicate: 7 bytes of padding 142 | - If not duplicate: 143 | - 1 byte boolean, true if account is a signer 144 | - 1 byte boolean, true if account is writable 145 | - 1 byte boolean, true if account is executable 146 | - 4 bytes of padding 147 | - 32 bytes of the account public key 148 | - 32 bytes of the account's owner public key 149 | - 8 bytes unsigned number of lamports owned by the account 150 | - 8 bytes unsigned number of bytes of account data 151 | - x bytes of account data 152 | - 10k bytes of padding, used for realloc 153 | - enough padding to align the offset to 8 bytes. 154 | - 8 bytes rent epoch 155 | - 8 bytes of unsigned number of instruction data 156 | - x bytes of instruction data 157 | - 32 bytes of the program id 158 | -------------------------------------------------------------------------------- /counterAnchor/web/components/counter/counter-ui.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Keypair, PublicKey } from '@solana/web3.js'; 4 | import { useMemo } from 'react'; 5 | import { ellipsify } from '../ui/ui-layout'; 6 | import { ExplorerLink } from '../cluster/cluster-ui'; 7 | import { 8 | useCounterProgram, 9 | useCounterProgramAccount, 10 | } from './counter-data-access'; 11 | 12 | export function CounterCreate() { 13 | const { initialize } = useCounterProgram(); 14 | 15 | return ( 16 | 23 | ); 24 | } 25 | 26 | export function CounterList() { 27 | const { accounts, getProgramAccount } = useCounterProgram(); 28 | 29 | if (getProgramAccount.isLoading) { 30 | return ; 31 | } 32 | if (!getProgramAccount.data?.value) { 33 | return ( 34 |
35 | 36 | Program account not found. Make sure you have deployed the program and 37 | are on the correct cluster. 38 | 39 |
40 | ); 41 | } 42 | return ( 43 |
44 | {accounts.isLoading ? ( 45 | 46 | ) : accounts.data?.length ? ( 47 |
48 | {accounts.data?.map((account) => ( 49 | 53 | ))} 54 |
55 | ) : ( 56 |
57 |

No accounts

58 | No accounts found. Create one above to get started. 59 |
60 | )} 61 |
62 | ); 63 | } 64 | 65 | function CounterCard({ account }: { account: PublicKey }) { 66 | const { 67 | accountQuery, 68 | incrementMutation, 69 | setMutation, 70 | decrementMutation, 71 | closeMutation, 72 | } = useCounterProgramAccount({ account }); 73 | 74 | const count = useMemo( 75 | () => accountQuery.data?.count ?? 0, 76 | [accountQuery.data?.count] 77 | ); 78 | 79 | return accountQuery.isLoading ? ( 80 | 81 | ) : ( 82 |
83 |
84 |
85 |

accountQuery.refetch()} 88 | > 89 | {count} 90 |

91 |
92 | 99 | 119 | 126 |
127 |
128 |

129 | 133 |

134 | 150 |
151 |
152 |
153 |
154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /counterAnchor/web/components/ui/ui-layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { WalletButton } from '../solana/solana-provider'; 4 | import * as React from 'react'; 5 | import { ReactNode, Suspense, useEffect, useRef } from 'react'; 6 | 7 | import Link from 'next/link'; 8 | import { usePathname } from 'next/navigation'; 9 | 10 | import { AccountChecker } from '../account/account-ui'; 11 | import { 12 | ClusterChecker, 13 | ClusterUiSelect, 14 | ExplorerLink, 15 | } from '../cluster/cluster-ui'; 16 | import toast, { Toaster } from 'react-hot-toast'; 17 | 18 | export function UiLayout({ 19 | children, 20 | links, 21 | }: { 22 | children: ReactNode; 23 | links: { label: string; path: string }[]; 24 | }) { 25 | const pathname = usePathname(); 26 | 27 | return ( 28 |
29 |
30 |
31 | 32 | Solana Logo 37 | 38 |
    39 | {links.map(({ label, path }) => ( 40 |
  • 41 | 45 | {label} 46 | 47 |
  • 48 | ))} 49 |
50 |
51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 | 59 |
60 | 63 | 64 |
65 | } 66 | > 67 | {children} 68 | 69 | 70 |
71 | 86 | 87 | ); 88 | } 89 | 90 | export function AppModal({ 91 | children, 92 | title, 93 | hide, 94 | show, 95 | submit, 96 | submitDisabled, 97 | submitLabel, 98 | }: { 99 | children: ReactNode; 100 | title: string; 101 | hide: () => void; 102 | show: boolean; 103 | submit?: () => void; 104 | submitDisabled?: boolean; 105 | submitLabel?: string; 106 | }) { 107 | const dialogRef = useRef(null); 108 | 109 | useEffect(() => { 110 | if (!dialogRef.current) return; 111 | if (show) { 112 | dialogRef.current.showModal(); 113 | } else { 114 | dialogRef.current.close(); 115 | } 116 | }, [show, dialogRef]); 117 | 118 | return ( 119 | 120 |
121 |

{title}

122 | {children} 123 |
124 |
125 | {submit ? ( 126 | 133 | ) : null} 134 | 137 |
138 |
139 |
140 |
141 | ); 142 | } 143 | 144 | export function AppHero({ 145 | children, 146 | title, 147 | subtitle, 148 | }: { 149 | children?: ReactNode; 150 | title: ReactNode; 151 | subtitle: ReactNode; 152 | }) { 153 | return ( 154 |
155 |
156 |
157 | {typeof title === 'string' ? ( 158 |

{title}

159 | ) : ( 160 | title 161 | )} 162 | {typeof subtitle === 'string' ? ( 163 |

{subtitle}

164 | ) : ( 165 | subtitle 166 | )} 167 | {children} 168 |
169 |
170 |
171 | ); 172 | } 173 | 174 | export function ellipsify(str = '', len = 4) { 175 | if (str.length > 30) { 176 | return ( 177 | str.substring(0, len) + '..' + str.substring(str.length - len, str.length) 178 | ); 179 | } 180 | return str; 181 | } 182 | 183 | export function useTransactionToast() { 184 | return (signature: string) => { 185 | toast.success( 186 |
187 |
Transaction sent
188 | 193 |
194 | ); 195 | }; 196 | } 197 | -------------------------------------------------------------------------------- /counterAnchor/web/components/account/account-data-access.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'; 4 | import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 5 | import { 6 | Connection, 7 | LAMPORTS_PER_SOL, 8 | PublicKey, 9 | SystemProgram, 10 | TransactionMessage, 11 | TransactionSignature, 12 | VersionedTransaction, 13 | } from '@solana/web3.js'; 14 | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 15 | import toast from 'react-hot-toast'; 16 | import { useTransactionToast } from '../ui/ui-layout'; 17 | 18 | export function useGetBalance({ address }: { address: PublicKey }) { 19 | const { connection } = useConnection(); 20 | 21 | return useQuery({ 22 | queryKey: ['get-balance', { endpoint: connection.rpcEndpoint, address }], 23 | queryFn: () => connection.getBalance(address), 24 | }); 25 | } 26 | 27 | export function useGetSignatures({ address }: { address: PublicKey }) { 28 | const { connection } = useConnection(); 29 | 30 | return useQuery({ 31 | queryKey: ['get-signatures', { endpoint: connection.rpcEndpoint, address }], 32 | queryFn: () => connection.getConfirmedSignaturesForAddress2(address), 33 | }); 34 | } 35 | 36 | export function useGetTokenAccounts({ address }: { address: PublicKey }) { 37 | const { connection } = useConnection(); 38 | 39 | return useQuery({ 40 | queryKey: [ 41 | 'get-token-accounts', 42 | { endpoint: connection.rpcEndpoint, address }, 43 | ], 44 | queryFn: async () => { 45 | const [tokenAccounts, token2022Accounts] = await Promise.all([ 46 | connection.getParsedTokenAccountsByOwner(address, { 47 | programId: TOKEN_PROGRAM_ID, 48 | }), 49 | connection.getParsedTokenAccountsByOwner(address, { 50 | programId: TOKEN_2022_PROGRAM_ID, 51 | }), 52 | ]); 53 | return [...tokenAccounts.value, ...token2022Accounts.value]; 54 | }, 55 | }); 56 | } 57 | 58 | export function useTransferSol({ address }: { address: PublicKey }) { 59 | const { connection } = useConnection(); 60 | const transactionToast = useTransactionToast(); 61 | const wallet = useWallet(); 62 | const client = useQueryClient(); 63 | 64 | return useMutation({ 65 | mutationKey: [ 66 | 'transfer-sol', 67 | { endpoint: connection.rpcEndpoint, address }, 68 | ], 69 | mutationFn: async (input: { destination: PublicKey; amount: number }) => { 70 | let signature: TransactionSignature = ''; 71 | try { 72 | const { transaction, latestBlockhash } = await createTransaction({ 73 | publicKey: address, 74 | destination: input.destination, 75 | amount: input.amount, 76 | connection, 77 | }); 78 | 79 | // Send transaction and await for signature 80 | signature = await wallet.sendTransaction(transaction, connection); 81 | 82 | // Send transaction and await for signature 83 | await connection.confirmTransaction( 84 | { signature, ...latestBlockhash }, 85 | 'confirmed' 86 | ); 87 | 88 | console.log(signature); 89 | return signature; 90 | } catch (error: unknown) { 91 | console.log('error', `Transaction failed! ${error}`, signature); 92 | 93 | return; 94 | } 95 | }, 96 | onSuccess: (signature) => { 97 | if (signature) { 98 | transactionToast(signature); 99 | } 100 | return Promise.all([ 101 | client.invalidateQueries({ 102 | queryKey: [ 103 | 'get-balance', 104 | { endpoint: connection.rpcEndpoint, address }, 105 | ], 106 | }), 107 | client.invalidateQueries({ 108 | queryKey: [ 109 | 'get-signatures', 110 | { endpoint: connection.rpcEndpoint, address }, 111 | ], 112 | }), 113 | ]); 114 | }, 115 | onError: (error) => { 116 | toast.error(`Transaction failed! ${error}`); 117 | }, 118 | }); 119 | } 120 | 121 | export function useRequestAirdrop({ address }: { address: PublicKey }) { 122 | const { connection } = useConnection(); 123 | const transactionToast = useTransactionToast(); 124 | const client = useQueryClient(); 125 | 126 | return useMutation({ 127 | mutationKey: ['airdrop', { endpoint: connection.rpcEndpoint, address }], 128 | mutationFn: async (amount: number = 1) => { 129 | const [latestBlockhash, signature] = await Promise.all([ 130 | connection.getLatestBlockhash(), 131 | connection.requestAirdrop(address, amount * LAMPORTS_PER_SOL), 132 | ]); 133 | 134 | await connection.confirmTransaction( 135 | { signature, ...latestBlockhash }, 136 | 'confirmed' 137 | ); 138 | return signature; 139 | }, 140 | onSuccess: (signature) => { 141 | transactionToast(signature); 142 | return Promise.all([ 143 | client.invalidateQueries({ 144 | queryKey: [ 145 | 'get-balance', 146 | { endpoint: connection.rpcEndpoint, address }, 147 | ], 148 | }), 149 | client.invalidateQueries({ 150 | queryKey: [ 151 | 'get-signatures', 152 | { endpoint: connection.rpcEndpoint, address }, 153 | ], 154 | }), 155 | ]); 156 | }, 157 | }); 158 | } 159 | 160 | async function createTransaction({ 161 | publicKey, 162 | destination, 163 | amount, 164 | connection, 165 | }: { 166 | publicKey: PublicKey; 167 | destination: PublicKey; 168 | amount: number; 169 | connection: Connection; 170 | }): Promise<{ 171 | transaction: VersionedTransaction; 172 | latestBlockhash: { blockhash: string; lastValidBlockHeight: number }; 173 | }> { 174 | // Get the latest blockhash to use in our transaction 175 | const latestBlockhash = await connection.getLatestBlockhash(); 176 | 177 | // Create instructions to send, in this case a simple transfer 178 | const instructions = [ 179 | SystemProgram.transfer({ 180 | fromPubkey: publicKey, 181 | toPubkey: destination, 182 | lamports: amount * LAMPORTS_PER_SOL, 183 | }), 184 | ]; 185 | 186 | // Create a new TransactionMessage with version and compile it to legacy 187 | const messageLegacy = new TransactionMessage({ 188 | payerKey: publicKey, 189 | recentBlockhash: latestBlockhash.blockhash, 190 | instructions, 191 | }).compileToLegacyMessage(); 192 | 193 | // Create a new VersionedTransaction which supports legacy and v0 194 | const transaction = new VersionedTransaction(messageLegacy); 195 | 196 | return { 197 | transaction, 198 | latestBlockhash, 199 | }; 200 | } 201 | -------------------------------------------------------------------------------- /counterAnchor/web/components/cluster/cluster-ui.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useConnection } from '@solana/wallet-adapter-react'; 4 | import { IconTrash } from '@tabler/icons-react'; 5 | import { useQuery } from '@tanstack/react-query'; 6 | import { ReactNode, useState } from 'react'; 7 | import { AppModal } from '../ui/ui-layout'; 8 | import { ClusterNetwork, useCluster } from './cluster-data-access'; 9 | import { Connection } from '@solana/web3.js'; 10 | 11 | export function ExplorerLink({ 12 | path, 13 | label, 14 | className, 15 | }: { 16 | path: string; 17 | label: string; 18 | className?: string; 19 | }) { 20 | const { getExplorerUrl } = useCluster(); 21 | return ( 22 | 28 | {label} 29 | 30 | ); 31 | } 32 | 33 | export function ClusterChecker({ children }: { children: ReactNode }) { 34 | const { cluster } = useCluster(); 35 | const { connection } = useConnection(); 36 | 37 | const query = useQuery({ 38 | queryKey: ['version', { cluster, endpoint: connection.rpcEndpoint }], 39 | queryFn: () => connection.getVersion(), 40 | retry: 1, 41 | }); 42 | if (query.isLoading) { 43 | return null; 44 | } 45 | if (query.isError || !query.data) { 46 | return ( 47 |
48 | 49 | Error connecting to cluster {cluster.name} 50 | 51 | 57 |
58 | ); 59 | } 60 | return children; 61 | } 62 | 63 | export function ClusterUiSelect() { 64 | const { clusters, setCluster, cluster } = useCluster(); 65 | return ( 66 |
67 | 70 |
    74 | {clusters.map((item) => ( 75 |
  • 76 | 84 |
  • 85 | ))} 86 |
87 |
88 | ); 89 | } 90 | 91 | export function ClusterUiModal({ 92 | hideModal, 93 | show, 94 | }: { 95 | hideModal: () => void; 96 | show: boolean; 97 | }) { 98 | const { addCluster } = useCluster(); 99 | const [name, setName] = useState(''); 100 | const [network, setNetwork] = useState(); 101 | const [endpoint, setEndpoint] = useState(''); 102 | 103 | return ( 104 | { 109 | try { 110 | new Connection(endpoint); 111 | if (name) { 112 | addCluster({ name, network, endpoint }); 113 | hideModal(); 114 | } else { 115 | console.log('Invalid cluster name'); 116 | } 117 | } catch { 118 | console.log('Invalid cluster endpoint'); 119 | } 120 | }} 121 | submitLabel="Save" 122 | > 123 | setName(e.target.value)} 129 | /> 130 | setEndpoint(e.target.value)} 136 | /> 137 | 147 | 148 | ); 149 | } 150 | 151 | export function ClusterUiTable() { 152 | const { clusters, setCluster, deleteCluster } = useCluster(); 153 | return ( 154 |
155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | {clusters.map((item) => ( 164 | 165 | 188 | 200 | 201 | ))} 202 | 203 |
Name/ Network / EndpointActions
166 |
167 | 168 | {item?.active ? ( 169 | item.name 170 | ) : ( 171 | 178 | )} 179 | 180 |
181 | 182 | Network: {item.network ?? 'custom'} 183 | 184 |
185 | {item.endpoint} 186 |
187 |
189 | 199 |
204 |
205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /counterAnchor/anchor/tests/counter.spec.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from '@coral-xyz/anchor'; 2 | import { Program } from '@coral-xyz/anchor'; 3 | import { Keypair, PublicKey } from '@solana/web3.js'; 4 | import { Counter } from '../target/types/counter'; 5 | 6 | describe('counter', () => { 7 | // Configure the client to use the local cluster. 8 | const provider = anchor.AnchorProvider.env(); 9 | anchor.setProvider(provider); 10 | const payer = provider.wallet as anchor.Wallet; 11 | 12 | const program = anchor.workspace.Counter as Program; 13 | 14 | const counterKeypair = Keypair.generate(); 15 | const counterZeroCopyKeypair = Keypair.generate(); 16 | 17 | it('Initialize Counter', async () => { 18 | const sig = await program.methods 19 | .initialize() 20 | .accounts({ 21 | counter: counterKeypair.publicKey, 22 | payer: payer.publicKey, 23 | }) 24 | .signers([counterKeypair]) 25 | .rpc(); 26 | 27 | console.log('Init counter ' + sig); 28 | }); 29 | 30 | it('Initialize Counter Zero Copy', async () => { 31 | const sig = await program.methods 32 | .initializeZeroCopy() 33 | .accounts({ 34 | counterZeroCopy: counterZeroCopyKeypair.publicKey, 35 | payer: payer.publicKey, 36 | }) 37 | .signers([counterZeroCopyKeypair]) 38 | .rpc(); 39 | 40 | console.log('Init counter zero copy ' + sig); 41 | }); 42 | 43 | it('Increment Counter', async () => { 44 | const sim = await program.methods 45 | .increment() 46 | .accounts({ 47 | counter: counterKeypair.publicKey, 48 | }) 49 | .simulate(); 50 | 51 | console.log('Increment counter ' + JSON.stringify(sim)); 52 | 53 | const sig = await program.methods 54 | .increment() 55 | .accounts({ 56 | counter: counterKeypair.publicKey, 57 | }) 58 | .rpc(); 59 | 60 | console.log('Increment counter ' + sig); 61 | 62 | const currentCount = await program.account.counterData.fetch( 63 | counterKeypair.publicKey, 64 | ); 65 | console.log('Current count ' + currentCount.count.toNumber()); 66 | }); 67 | 68 | it('Increment Counter zero copy', async () => { 69 | const sim = await program.methods 70 | .incrementZeroCopy() 71 | .accounts({ counterZeroCopy: counterZeroCopyKeypair.publicKey }) 72 | .simulate(); 73 | console.log('Increment counter zero copy ' + JSON.stringify(sim)); 74 | 75 | const sig = await program.methods 76 | .incrementZeroCopy() 77 | .accounts({ counterZeroCopy: counterZeroCopyKeypair.publicKey }) 78 | .rpc({ skipPreflight: true }); 79 | console.log('Increment counter zero copy ' + sig); 80 | 81 | const currentCount = await program.account.counterZeroCopy.fetch( 82 | counterZeroCopyKeypair.publicKey, 83 | ); 84 | console.log('Current count ' + currentCount.count.toNumber()); 85 | }); 86 | 87 | it('Allocations', async () => { 88 | const sig = await program.methods 89 | .allocations() 90 | .accounts({ counter: counterKeypair.publicKey }) 91 | .simulate(); 92 | 93 | console.log('Allocations ' + JSON.stringify(sig)); 94 | }); 95 | 96 | it('Closure and Function calls', async () => { 97 | const sim = await program.methods 98 | .incrementWithFnCall() 99 | .accounts({ counter: counterKeypair.publicKey }) 100 | .simulate(); 101 | 102 | console.log('Closure and Function calls ' + JSON.stringify(sim)); 103 | }); 104 | 105 | it('Set counter value u64', async () => { 106 | const sim = await program.methods 107 | .setBigData(new anchor.BN(42)) 108 | .accounts({ counter: counterKeypair.publicKey }) 109 | .simulate(); 110 | 111 | console.log('Set value u64: ' + JSON.stringify(sim)); 112 | 113 | await program.methods 114 | .setBigData(new anchor.BN(42)) 115 | .accounts({ counter: counterKeypair.publicKey }) 116 | .rpc(); 117 | 118 | const currentCount = await program.account.counterData.fetch( 119 | counterKeypair.publicKey, 120 | ); 121 | 122 | expect(currentCount.count.toNumber()).toEqual(42); 123 | }); 124 | 125 | it('Set counter value u8', async () => { 126 | const sim = await program.methods 127 | .setSmallData(42) 128 | .accounts({ counter: counterKeypair.publicKey }) 129 | .simulate(); 130 | 131 | console.log('Set value u8: ' + JSON.stringify(sim)); 132 | 133 | await program.methods 134 | .setSmallData(42) 135 | .accounts({ counter: counterKeypair.publicKey }) 136 | .rpc(); 137 | 138 | const currentCount = await program.account.counterData.fetch( 139 | counterKeypair.publicKey, 140 | ); 141 | 142 | expect(currentCount.count.toNumber()).toEqual(42); 143 | }); 144 | 145 | it('CPI', async () => { 146 | const sim = await program.methods 147 | .doCpi(new anchor.BN(1)) 148 | .accounts({ counter: counterKeypair.publicKey }) 149 | .simulate(); 150 | 151 | console.log('Do Cpi: ' + JSON.stringify(sim)); 152 | 153 | const sig = await program.methods 154 | .doCpi(new anchor.BN(1)) 155 | .accounts({ counter: counterKeypair.publicKey }) 156 | .rpc({ skipPreflight: true }); 157 | console.log('Do CPI ' + sig); 158 | }); 159 | 160 | it('PDAS', async () => { 161 | const counter_checked = PublicKey.findProgramAddressSync( 162 | [Buffer.from('counter')], 163 | program.programId, 164 | ); 165 | 166 | const init_sig = await program.methods 167 | .initPdaWithSeed() 168 | .accounts({ 169 | }) 170 | .rpc({ skipPreflight: true }); 171 | console.log('Init Pda with seed ' + init_sig); 172 | 173 | const sim = await program.methods 174 | .pdas() 175 | .accounts({ 176 | counter: counterKeypair.publicKey, 177 | }) 178 | .simulate(); 179 | 180 | console.log('PDAS: ' + JSON.stringify(sim)); 181 | 182 | const sig = await program.methods 183 | .pdas() 184 | .accounts({ 185 | counter: counterKeypair.publicKey, 186 | }) 187 | .rpc({ skipPreflight: true }); 188 | console.log('PDAS ' + sig); 189 | }); 190 | 191 | it('Checked Math', async () => { 192 | const sim = await program.methods 193 | .checkedMathTest() 194 | .accounts({ 195 | counter: counterKeypair.publicKey, 196 | }) 197 | .simulate(); 198 | 199 | console.log('Calculations ' + JSON.stringify(sim)); 200 | 201 | const sig = await program.methods 202 | .checkedMathTest() 203 | .accounts({ 204 | counter: counterKeypair.publicKey, 205 | }) 206 | .rpc(); 207 | 208 | console.log('mulVsDiv ' + sig); 209 | }); 210 | 211 | it('Clone variables', async () => { 212 | const sim = await program.methods 213 | .cloneVariables() 214 | .accounts({ 215 | counter: counterKeypair.publicKey, 216 | }) 217 | .simulate(); 218 | 219 | console.log('cloneVariables ' + JSON.stringify(sim)); 220 | 221 | const sig = await program.methods 222 | .cloneVariables() 223 | .accounts({ 224 | counter: counterKeypair.publicKey, 225 | }) 226 | .rpc(); 227 | 228 | console.log('cloneVariables ' + sig); 229 | }); 230 | }); 231 | -------------------------------------------------------------------------------- /counterAnchor/web/components/account/account-ui.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useWallet } from '@solana/wallet-adapter-react'; 4 | import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; 5 | import { IconRefresh } from '@tabler/icons-react'; 6 | import { useQueryClient } from '@tanstack/react-query'; 7 | import { useMemo, useState } from 'react'; 8 | import { AppModal, ellipsify } from '../ui/ui-layout'; 9 | import { useCluster } from '../cluster/cluster-data-access'; 10 | import { ExplorerLink } from '../cluster/cluster-ui'; 11 | import { 12 | useGetBalance, 13 | useGetSignatures, 14 | useGetTokenAccounts, 15 | useRequestAirdrop, 16 | useTransferSol, 17 | } from './account-data-access'; 18 | 19 | export function AccountBalance({ address }: { address: PublicKey }) { 20 | const query = useGetBalance({ address }); 21 | 22 | return ( 23 |
24 |

query.refetch()} 27 | > 28 | {query.data ? : '...'} SOL 29 |

30 |
31 | ); 32 | } 33 | export function AccountChecker() { 34 | const { publicKey } = useWallet(); 35 | if (!publicKey) { 36 | return null; 37 | } 38 | return ; 39 | } 40 | export function AccountBalanceCheck({ address }: { address: PublicKey }) { 41 | const { cluster } = useCluster(); 42 | const mutation = useRequestAirdrop({ address }); 43 | const query = useGetBalance({ address }); 44 | 45 | if (query.isLoading) { 46 | return null; 47 | } 48 | if (query.isError || !query.data) { 49 | return ( 50 |
51 | 52 | You are connected to {cluster.name} but your account 53 | is not found on this cluster. 54 | 55 | 63 |
64 | ); 65 | } 66 | return null; 67 | } 68 | 69 | export function AccountButtons({ address }: { address: PublicKey }) { 70 | const wallet = useWallet(); 71 | const { cluster } = useCluster(); 72 | const [showAirdropModal, setShowAirdropModal] = useState(false); 73 | const [showReceiveModal, setShowReceiveModal] = useState(false); 74 | const [showSendModal, setShowSendModal] = useState(false); 75 | 76 | return ( 77 |
78 | setShowAirdropModal(false)} 80 | address={address} 81 | show={showAirdropModal} 82 | /> 83 | setShowReceiveModal(false)} 87 | /> 88 | setShowSendModal(false)} 92 | /> 93 |
94 | 101 | 108 | 114 |
115 |
116 | ); 117 | } 118 | 119 | export function AccountTokens({ address }: { address: PublicKey }) { 120 | const [showAll, setShowAll] = useState(false); 121 | const query = useGetTokenAccounts({ address }); 122 | const client = useQueryClient(); 123 | const items = useMemo(() => { 124 | if (showAll) return query.data; 125 | return query.data?.slice(0, 5); 126 | }, [query.data, showAll]); 127 | 128 | return ( 129 |
130 |
131 |
132 |

Token Accounts

133 |
134 | {query.isLoading ? ( 135 | 136 | ) : ( 137 | 148 | )} 149 |
150 |
151 |
152 | {query.isError && ( 153 |
154 |           Error: {query.error?.message.toString()}
155 |         
156 | )} 157 | {query.isSuccess && ( 158 |
159 | {query.data.length === 0 ? ( 160 |
No token accounts found.
161 | ) : ( 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | {items?.map(({ account, pubkey }) => ( 172 | 173 | 183 | 193 | 198 | 199 | ))} 200 | 201 | {(query.data?.length ?? 0) > 5 && ( 202 | 203 | 211 | 212 | )} 213 | 214 |
Public KeyMintBalance
174 |
175 | 176 | 180 | 181 |
182 |
184 |
185 | 186 | 190 | 191 |
192 |
194 | 195 | {account.data.parsed.info.tokenAmount.uiAmount} 196 | 197 |
204 | 210 |
215 | )} 216 |
217 | )} 218 |
219 | ); 220 | } 221 | 222 | export function AccountTransactions({ address }: { address: PublicKey }) { 223 | const query = useGetSignatures({ address }); 224 | const [showAll, setShowAll] = useState(false); 225 | 226 | const items = useMemo(() => { 227 | if (showAll) return query.data; 228 | return query.data?.slice(0, 5); 229 | }, [query.data, showAll]); 230 | 231 | return ( 232 |
233 |
234 |

Transaction History

235 |
236 | {query.isLoading ? ( 237 | 238 | ) : ( 239 | 245 | )} 246 |
247 |
248 | {query.isError && ( 249 |
250 |           Error: {query.error?.message.toString()}
251 |         
252 | )} 253 | {query.isSuccess && ( 254 |
255 | {query.data.length === 0 ? ( 256 |
No transactions found.
257 | ) : ( 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | {items?.map((item) => ( 269 | 270 | 276 | 282 | 285 | 297 | 298 | ))} 299 | {(query.data?.length ?? 0) > 5 && ( 300 | 301 | 309 | 310 | )} 311 | 312 |
SignatureSlotBlock TimeStatus
271 | 275 | 277 | 281 | 283 | {new Date((item.blockTime ?? 0) * 1000).toISOString()} 284 | 286 | {item.err ? ( 287 |
291 | Failed 292 |
293 | ) : ( 294 |
Success
295 | )} 296 |
302 | 308 |
313 | )} 314 |
315 | )} 316 |
317 | ); 318 | } 319 | 320 | function BalanceSol({ balance }: { balance: number }) { 321 | return ( 322 | {Math.round((balance / LAMPORTS_PER_SOL) * 100000) / 100000} 323 | ); 324 | } 325 | 326 | function ModalReceive({ 327 | hide, 328 | show, 329 | address, 330 | }: { 331 | hide: () => void; 332 | show: boolean; 333 | address: PublicKey; 334 | }) { 335 | return ( 336 | 337 |

Receive assets by sending them to your public key:

338 | {address.toString()} 339 |
340 | ); 341 | } 342 | 343 | function ModalAirdrop({ 344 | hide, 345 | show, 346 | address, 347 | }: { 348 | hide: () => void; 349 | show: boolean; 350 | address: PublicKey; 351 | }) { 352 | const mutation = useRequestAirdrop({ address }); 353 | const [amount, setAmount] = useState('2'); 354 | 355 | return ( 356 | mutation.mutateAsync(parseFloat(amount)).then(() => hide())} 363 | > 364 | setAmount(e.target.value)} 373 | /> 374 | 375 | ); 376 | } 377 | 378 | function ModalSend({ 379 | hide, 380 | show, 381 | address, 382 | }: { 383 | hide: () => void; 384 | show: boolean; 385 | address: PublicKey; 386 | }) { 387 | const wallet = useWallet(); 388 | const mutation = useTransferSol({ address }); 389 | const [destination, setDestination] = useState(''); 390 | const [amount, setAmount] = useState('1'); 391 | 392 | if (!address || !wallet.sendTransaction) { 393 | return
Wallet not connected
; 394 | } 395 | 396 | return ( 397 | { 404 | mutation 405 | .mutateAsync({ 406 | destination: new PublicKey(destination), 407 | amount: parseFloat(amount), 408 | }) 409 | .then(() => hide()); 410 | }} 411 | > 412 | setDestination(e.target.value)} 419 | /> 420 | setAmount(e.target.value)} 429 | /> 430 | 431 | ); 432 | } 433 | -------------------------------------------------------------------------------- /counterAnchor/anchor/programs/counter/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::result_large_err)] 2 | 3 | use anchor_lang::prelude::*; 4 | use bytemuck::{ Pod, Zeroable }; 5 | 6 | declare_id!("5N5E4imUxwwdbYZern6xzKXejuHy3jNprfrvVmzT3YdF"); 7 | 8 | #[macro_export] 9 | #[cfg(not(feature = "trace-compute"))] 10 | macro_rules! compute { 11 | ( 12 | $msg:expr => $($tt:tt)* 13 | ) => { $($tt)* }; 14 | } 15 | 16 | /// Total extra compute units used per compute_fn! call 409 CU 17 | /// https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/programs/bpf_loader/src/syscalls/logging.rs#L70 18 | /// https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/program-runtime/src/compute_budget.rs#L150 19 | #[macro_export] 20 | macro_rules! compute_fn { 21 | ( 22 | $msg:expr => $($tt:tt)* 23 | ) => { 24 | ::solana_program::msg!(concat!($msg, " {")); 25 | ::solana_program::log::sol_log_compute_units(); 26 | let res = { $($tt)* }; 27 | ::solana_program::log::sol_log_compute_units(); 28 | ::solana_program::msg!(concat!(" } // ", $msg)); 29 | res 30 | }; 31 | } 32 | 33 | #[program] 34 | pub mod counter { 35 | use anchor_lang::system_program; 36 | use std::str::FromStr; 37 | 38 | use super::*; 39 | 40 | // Total 19241 CU 41 | pub fn allocations(ctx: Context) -> Result<()> { 42 | // 109 CU 43 | compute_fn! { "increase u8" => 44 | ctx.accounts.counter.count += 1; 45 | } 46 | 47 | // 204 48 | compute_fn! { "Log a string " => 49 | msg!("Compute units"); 50 | } 51 | 52 | // 11962 CU !! 53 | // Base58 encoding is expensive, concatenation is expensive 54 | compute_fn! { "Log a pubkey to account info" => 55 | msg!("A string {0}", ctx.accounts.counter.to_account_info().key()); 56 | } 57 | 58 | // 262 cu 59 | compute_fn! { "Log a pubkey" => 60 | ctx.accounts.counter.to_account_info().key().log(); 61 | } 62 | 63 | let pubkey = ctx.accounts.counter.to_account_info().key(); 64 | 65 | // 206 CU 66 | compute_fn! { "Pubkey.log" => 67 | pubkey.log(); 68 | } 69 | 70 | // 357 CU - string concatenation is expensive 71 | compute_fn! { "Log a pubkey simple concat" => 72 | msg!("A string {0}", "5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ"); 73 | } 74 | 75 | // 357 76 | compute_fn! { "Push Vector u64 " => 77 | let mut a: Vec = Vec::new(); 78 | a.push(1); 79 | a.push(1); 80 | a.push(1); 81 | a.push(1); 82 | a.push(1); 83 | a.push(1); 84 | } 85 | 86 | // 125 CU 87 | compute_fn! { "Vector u64 init" => 88 | let _a: Vec = vec![1, 1, 1, 1, 1, 1]; 89 | } 90 | 91 | // 356 CU 92 | compute_fn! { "Vector i64 " => 93 | // this costs 356 CU (takes the same space as u64) 94 | let mut a: Vec = Vec::new(); 95 | a.push(1); 96 | a.push(1); 97 | a.push(1); 98 | a.push(1); 99 | a.push(1); 100 | a.push(1); 101 | } 102 | 103 | // 211 CU 104 | compute_fn! { "Vector u8 " => 105 | let mut a: Vec = Vec::new(); 106 | a.push(1); 107 | a.push(1); 108 | a.push(1); 109 | a.push(1); 110 | a.push(1); 111 | a.push(1); 112 | } 113 | 114 | Ok(()) 115 | } 116 | 117 | // Total 14742 CU 118 | pub fn increment(ctx: Context) -> Result<()> { 119 | // There seems to not be any difference in between different ways of using checked_add 120 | let counter = &mut ctx.accounts.counter; 121 | 122 | // 3405 CU 123 | compute_fn! { "counter checked_add.unwrap()" => 124 | for _ in 0..254 { 125 | counter.count = counter.count.checked_add(1).unwrap(); 126 | } 127 | } 128 | counter.count = 0; 129 | 130 | // 3404 CU 131 | compute_fn! { "counter checked_add()" => 132 | for _ in 0..254 { 133 | match counter.count.checked_add(1) { 134 | Some(v) => counter.count = v, 135 | None => panic!("overflow"), 136 | } 137 | } 138 | } 139 | counter.count = 0; 140 | 141 | // 3404 CU 142 | compute_fn! { "counter += 1" => 143 | for _ in 0..254 { 144 | counter.count += 1; 145 | } 146 | } 147 | 148 | Ok(()) 149 | } 150 | 151 | // Total 16058 CU - there seem to be no big speed differences here. Surprisingly the function version is cheapest 152 | pub fn increment_with_fn_call(ctx: Context) -> Result<()> { 153 | let closure = |x: u64| -> u64 { x + 1 }; 154 | 155 | // 3913 CU 156 | compute_fn! { "counter with closure call" => 157 | for _ in 0..254 { 158 | ctx.accounts.counter.count = closure(ctx.accounts.counter.count); 159 | } 160 | } 161 | msg!("Counter: {}", ctx.accounts.counter.count); 162 | 163 | // 3660 CU 164 | compute_fn! { "counter with function call" => 165 | for _ in 0..254 { 166 | ctx.accounts.counter.count = increase_counter_function(ctx.accounts.counter.count); 167 | } 168 | } 169 | msg!("Counter: {}", ctx.accounts.counter.count); 170 | 171 | // 3914 CU 172 | compute_fn! { "inline " => 173 | for _ in 0..254 { 174 | ctx.accounts.counter.count += 1; 175 | } 176 | } 177 | msg!("Counter: {}", ctx.accounts.counter.count); 178 | 179 | Ok(()) 180 | } 181 | 182 | // Total 13496 CU - There seems to be no speed improvements here 183 | pub fn increment_zero_copy(ctx: Context) -> Result<()> { 184 | let mut counter = ctx.accounts.counter_zero_copy.load_mut()?; 185 | 186 | // 3912 CU 187 | compute_fn! { "counter checked_add.unwrap()" => 188 | for _ in 0..254 { 189 | counter.count = counter.count.checked_add(1).unwrap(); 190 | } 191 | } 192 | counter.count = 0; 193 | 194 | // 3912 CU 195 | compute_fn! { "counter checked_add()" => 196 | for _ in 0..254 { 197 | match counter.count.checked_add(1) { 198 | Some(v) => counter.count = v, 199 | None => panic!("overflow"), 200 | } 201 | } 202 | } 203 | counter.count = 0; 204 | 205 | // 3912 CU 206 | compute_fn! { "counter += 1" => 207 | for _ in 0..254 { 208 | counter.count += 1; 209 | } 210 | } 211 | Ok(()) 212 | } 213 | 214 | // 6302 CU 215 | pub fn initialize(_ctx: Context) -> Result<()> { 216 | Ok(()) 217 | } 218 | 219 | // 5020 CU 220 | pub fn initialize_zero_copy(_ctx: Context) -> Result<()> { 221 | Ok(()) 222 | } 223 | 224 | // Total 946 CU 225 | pub fn set_big_data(ctx: Context, _data: u64) -> Result<()> { 226 | ctx.accounts.counter.count = _data; 227 | Ok(()) 228 | } 229 | 230 | // Total 945 CU 231 | pub fn set_small_data(ctx: Context, _data: u8) -> Result<()> { 232 | ctx.accounts.counter.count = _data as u64; 233 | Ok(()) 234 | } 235 | 236 | pub fn init_pda_with_seed(ctx: Context) -> Result<()> { 237 | ctx.accounts.counter_checked.bump = ctx.bumps.counter_checked; 238 | Ok(()) 239 | } 240 | 241 | // Total 24985 CU - with the anchor checks for account_checked it becomes 38135 CU so the seeds check is around 12000 CU 242 | // which is not bad, but could be better. 243 | // If you instead use the bump that is saved in the counter_checked account it becomes 27859 CU so the overhead of the check is only 3000 CU 244 | pub fn pdas(ctx: Context) -> Result<()> { 245 | let program_id = Pubkey::from_str("5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ").unwrap(); 246 | 247 | // 12,136 CUs 248 | compute_fn! { "Find PDA" => 249 | Pubkey::find_program_address(&[b"counter"], ctx.program_id); 250 | } 251 | 252 | // 1,651 CUs 253 | compute_fn! { "Find PDA" => 254 | Pubkey::create_program_address(&[b"counter", &[248_u8]], &program_id).unwrap(); 255 | } 256 | 257 | Ok(()) 258 | } 259 | 260 | // Total 5787 CU 261 | pub fn do_cpi(ctx: Context, _data: u64) -> Result<()> { 262 | // 2,215 CUs 263 | compute_fn! { "CPI system program" => 264 | let cpi_context = CpiContext::new( 265 | ctx.accounts.system_program.to_account_info(), 266 | system_program::Transfer { 267 | from: ctx.accounts.payer.to_account_info().clone(), 268 | to: ctx.accounts.counter.to_account_info().clone(), 269 | }, 270 | ); 271 | system_program::transfer(cpi_context, 1)?; 272 | } 273 | 274 | // 251 CUs. In an error case though the whole transactions is 1,199 CUs bigger than without. So error handling is expensive 275 | compute_fn! { "Transfer borrowing lamports" => 276 | let counter_account_info = ctx.accounts.counter.to_account_info(); 277 | let mut source_lamports = counter_account_info.try_borrow_mut_lamports()?; 278 | const AMOUNT: u64 = 1; 279 | if **source_lamports < AMOUNT { 280 | msg!("source account has less than {} lamports", AMOUNT); 281 | let err = Err(anchor_lang::error::Error::from(ProgramError::InsufficientFunds)); 282 | return err; 283 | } 284 | **source_lamports -= AMOUNT; 285 | let payer_account_info = ctx.accounts.payer.to_account_info(); 286 | **payer_account_info.try_borrow_mut_lamports()? += AMOUNT; 287 | } 288 | 289 | Ok(()) 290 | } 291 | 292 | // 186578 CU 293 | pub fn checked_math_test(_ctx: Context) -> Result<()> { 294 | let mut test_value_mul: u64 = 7; 295 | let mut test_value_shift: u64 = 7; 296 | 297 | // The compiler is very good at optimizing and inlining thats 298 | // why these calculations and in functions with parameters and the 299 | // No inlining flag. 300 | 301 | // Testing checked_mul 97314 CU 302 | compute_fn! { "checked mul" => 303 | test_value_mul = test_checked_mul(test_value_mul, 7, 200, 60); 304 | } 305 | 306 | msg!("Test value mul: {}", test_value_mul); 307 | 308 | // Testing bit_shift 85113 CU 309 | compute_fn! { "bit shift" => 310 | test_value_shift = check_bit_shift(test_value_shift, 7, 200, 60); 311 | } 312 | 313 | msg!("Test value shift: {}", test_value_shift); 314 | 315 | Ok(()) 316 | } 317 | 318 | // Total 101237 CU 319 | pub fn clone_variables(_ctx: Context) -> Result<()> { 320 | let balances = vec![10_u64; 100]; // a vector with 10,000 elements 321 | 322 | let mut sum_reference = 0; 323 | 324 | // Here we can see that passing by reference is cheaper than cloning 325 | // Interesting is also that due to the bump allocator that solana 326 | // uses we will run out of memory as soon as we go to 40 iterations 327 | // on the loop that clones the vector. This is because the bump allocator 328 | // does not free memory and we only have 32 KB of heap space. 329 | 330 | // 47683 CU 331 | compute_fn! { "pass by reference" => 332 | for _ in 0..39 { 333 | sum_reference += sum_vector_by_reference(&balances); 334 | } 335 | } 336 | 337 | msg!("Sum by reference: {}", sum_reference); 338 | 339 | let mut sum_clone = 0; 340 | 341 | // 49322 CU 342 | compute_fn! { "clone" => 343 | for _ in 0..39 { 344 | sum_clone += sum_vector_by_value(balances.clone()); 345 | } 346 | } 347 | 348 | msg!("Sum by clone: {}", sum_clone); 349 | 350 | Ok(()) 351 | } 352 | } 353 | 354 | // Function that takes a reference to a vector 355 | fn sum_vector_by_reference(balances: &Vec) -> u64 { 356 | balances.iter().sum() 357 | } 358 | 359 | // Function that takes a vector by value 360 | fn sum_vector_by_value(balances: Vec) -> u64 { 361 | balances.iter().sum() 362 | } 363 | 364 | pub fn increase_counter_function(mut count: u64) -> u64 { 365 | count += 1; 366 | count 367 | } 368 | 369 | #[inline(never)] 370 | pub fn check_bit_shift( 371 | mut count: u64, 372 | mut extraValue: u64, 373 | first_loop: u64, 374 | second_loop: u64 375 | ) -> u64 { 376 | for i in 0..first_loop { 377 | count = i; 378 | for _ in 0..second_loop { 379 | count = bit_shift(count); 380 | } 381 | } 382 | count 383 | } 384 | 385 | #[inline(never)] 386 | pub fn bit_shift(mut count: u64) -> u64 { 387 | count << 1 388 | } 389 | 390 | #[inline(never)] 391 | pub fn test_checked_mul( 392 | mut count: u64, 393 | mut extraValue: u64, 394 | first_loop: u64, 395 | second_loop: u64 396 | ) -> u64 { 397 | for i in 0..first_loop { 398 | count = i; 399 | for _ in 0..second_loop { 400 | checked_mul(count); 401 | } 402 | } 403 | count 404 | } 405 | 406 | #[inline(never)] 407 | pub fn checked_mul(mut count: u64) -> u64 { 408 | count.checked_mul(2).expect("overflow") 409 | } 410 | 411 | #[derive(Accounts)] 412 | pub struct InitializeCounter<'info> { 413 | #[account(mut)] 414 | pub payer: Signer<'info>, 415 | 416 | #[account(init, space = 8 + CounterData::INIT_SPACE, payer = payer)] 417 | pub counter: Box>, 418 | pub system_program: Program<'info, System>, 419 | } 420 | 421 | #[derive(Accounts)] 422 | pub struct InitializeCounterZeroCopy<'info> { 423 | #[account(mut)] 424 | pub payer: Signer<'info>, 425 | #[account(init, space = 8 + CounterZeroCopy::INIT_SPACE, payer = payer)] 426 | pub counter_zero_copy: AccountLoader<'info, CounterZeroCopy>, 427 | pub system_program: Program<'info, System>, 428 | } 429 | 430 | // 15166 CU with signer 14863 CU without signer check 431 | #[derive(Accounts)] 432 | pub struct Update<'info> { 433 | #[account(mut)] 434 | pub counter: Account<'info, CounterData>, 435 | } 436 | 437 | /* 438 | When boxing the account the CU increase to 3,657 which indicated that it takes more cu to 439 | Calculate the data on the heap 440 | #[derive(Accounts)] 441 | pub struct Update<'info> { 442 | #[account(mut)] 443 | pub counter: Box>, 444 | }*/ 445 | 446 | #[derive(Accounts)] 447 | pub struct InitPdaWithSeeds<'info> { 448 | #[account(init, seeds = [b"counter"], bump, payer = signer, space = 8 + CounterData::INIT_SPACE)] 449 | pub counter_checked: Account<'info, CounterData>, 450 | #[account(mut)] 451 | pub signer: Signer<'info>, 452 | pub system_program: Program<'info, System>, 453 | } 454 | 455 | #[derive(Accounts)] 456 | pub struct PdaAccounts<'info> { 457 | #[account(mut)] 458 | pub counter: Account<'info, CounterData>, 459 | // 12,136 CUs when not defining the bump, but only 1600 if using the bump that is saved in the counter_checked account 460 | #[account(seeds = [b"counter"], bump = counter_checked.bump)] 461 | pub counter_checked: Account<'info, CounterData>, 462 | } 463 | 464 | #[derive(Accounts)] 465 | pub struct DoCpi<'info> { 466 | #[account(mut)] 467 | pub payer: Signer<'info>, 468 | #[account(mut)] 469 | pub counter: Account<'info, CounterData>, 470 | pub system_program: Program<'info, System>, 471 | } 472 | 473 | #[derive(Accounts)] 474 | pub struct UpdateZeroCopy<'info> { 475 | #[account(mut)] 476 | pub counter_zero_copy: AccountLoader<'info, CounterZeroCopy>, 477 | } 478 | 479 | // 6389 CU 480 | #[account] 481 | #[derive(InitSpace)] 482 | pub struct CounterData { 483 | count: u64, 484 | test: Pubkey, 485 | test1: u64, 486 | test2: u64, 487 | //big_struct: BigStructNoZeroCopy, // When adding this big struct you will get a stack frame violation 488 | bump: u8, 489 | } 490 | 491 | // 5020 CU 492 | #[account(zero_copy)] 493 | #[repr(C)] 494 | #[derive(InitSpace)] 495 | pub struct CounterZeroCopy { 496 | count: u64, 497 | test: Pubkey, 498 | test1: u64, 499 | test2: u64, 500 | big_struct: BigStruct, 501 | } 502 | 503 | #[repr(C)] 504 | #[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, Eq, Zeroable, Pod, InitSpace)] 505 | pub struct BigStruct { 506 | test: Pubkey, 507 | test1: u64, 508 | test2: u64, 509 | test3: Pubkey, 510 | test4: u64, 511 | test5: u64, 512 | test6: Pubkey, 513 | pubkey_array: [Pubkey; 43], 514 | } 515 | 516 | #[account] 517 | #[derive(InitSpace)] 518 | pub struct BigStructNoZeroCopy { 519 | test: Pubkey, 520 | test1: u64, 521 | test2: u64, 522 | test3: Pubkey, 523 | test4: u64, 524 | test5: u64, 525 | test6: Pubkey, 526 | pubkey_array: [Pubkey; 120], 527 | } 528 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Optimizing CU in programs 2 | 3 | ## Introduction 4 | 5 | The get started there is a [video guide](https://www.youtube.com/watch?v=7CbAK7Oq_o4) on how to optimize CU in programs which covers the contents in this repository. 6 | And also a [Part1](https://www.youtube.com/watch?v=xoJ-3NkYXfY&ab_channel=Solandy%5Bsolandy.sol%5D) and [Part2](https://www.youtube.com/watch?v=Pwly1cOa2hg&ab_channel=Solandy%5Bsolandy.sol%5D) on how optimize programs by SolAndy. 7 | 8 | Every block on Solana has a blockspace limit of 48 million CUs and a 12 million CUs per account write lock. If you exhaust the CU limit your transaction will fail. Optimizing your program CUs has many advantages. 9 | 10 | Currently every transactions on Solana costs 5000 lamports per signature independant on the compute units used. 11 | Four reasons on why to optimize CU anyway: 12 | 13 | 1. A smaller transaction is more likely to be included in a block. 14 | 2. Currently every transaction costs 5000 lamports/sig no matter the CU. This may change in the future. Better be prepared. 15 | 3. It makes your program more composable, because when another program does a CPI in your program it also need to cover your CU. 16 | 4. More block space for every one. One block could theoretically hold around tens of thousands of simple transfer transactions for example. So the less intensive each transaction is the more we can get in a block. 17 | 18 | By default every transaction on solana requests 200.000 CUs. 19 | With the call setComputeLimit this can be increased to a maximum of 1.4 million. 20 | 21 | If you are only doing a simple transfer, you can set the CU limit to 300 for example. 22 | 23 | ```js 24 | const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({ 25 | units: 300, 26 | }); 27 | ``` 28 | 29 | There can also be priority fees which can be set like this: 30 | 31 | ```js 32 | const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({ 33 | microLamports: 1, 34 | }); 35 | ``` 36 | 37 | This means for every requested CU, 1 microLamport is paid. This would result in a fee of 0.2 lamports. 38 | These instructions can be put into a transaction at any position. 39 | 40 | ```js 41 | const transaction = new Transaction().add(computePriceIx, computeLimitIx, ...); 42 | ``` 43 | 44 | Find Compute Budget code here: 45 | https://github.com/solana-labs/solana/blob/090e11210aa7222d8295610a6ccac4acda711bb9/program-runtime/src/compute_budget.rs#L26-L87 46 | 47 | Blocks are packed using the real CU used and not the requested CU. 48 | 49 | There may be a CU cost to loaded account size as well soon with a maximum of 16.000 CU which would be charges heap space at rate of 8cu per 32K. (Max loaded accounts per transaction is 64Mb) 50 | https://github.com/solana-labs/solana/issues/29582 51 | 52 | ## How to measure CU 53 | 54 | The best way to measure CU is to use the solana program log. 55 | 56 | For that you can use this macro: 57 | 58 | ```rust 59 | /// Total extra compute units used per compute_fn! call 409 CU 60 | /// https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/programs/bpf_loader/src/syscalls/logging.rs#L70 61 | /// https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/program-runtime/src/compute_budget.rs#L150 62 | #[macro_export] 63 | macro_rules! compute_fn { 64 | ($msg:expr=> $($tt:tt)*) => { 65 | ::solana_program::msg!(concat!($msg, " {")); 66 | ::solana_program::log::sol_log_compute_units(); 67 | let res = { $($tt)* }; 68 | ::solana_program::log::sol_log_compute_units(); 69 | ::solana_program::msg!(concat!(" } // ", $msg)); 70 | res 71 | }; 72 | } 73 | ``` 74 | 75 | You put it in front and after the code block you want to measure like so: 76 | 77 | ```rust 78 | 79 | compute_fn!("My message" => { 80 | // Your code here 81 | }); 82 | 83 | ``` 84 | 85 | Then you can paste the logs into chatGPT and let i calculate the CU for you if you dont want to do it yourself. 86 | Here is the original from @thlorenz https://github.com/thlorenz/sol-contracts/blob/master/packages/sol-common/rust/src/lib.rs 87 | 88 | # Optimizations 89 | 90 | ## 1 Logging 91 | 92 | Logging is very expensive. Especially logging pubkeys and concatenating strings. Use pubkey.log instead if you need it and only log what is really necessary. 93 | 94 | ```rust 95 | // 11962 CU !! 96 | // Base58 encoding is expensive, concatenation is expensive 97 | compute_fn! { "Log a pubkey to account info" => 98 | msg!("A string {0}", ctx.accounts.counter.to_account_info().key()); 99 | } 100 | 101 | // 262 cu 102 | compute_fn! { "Log a pubkey" => 103 | ctx.accounts.counter.to_account_info().key().log(); 104 | } 105 | 106 | let pubkey = ctx.accounts.counter.to_account_info().key(); 107 | 108 | // 206 CU 109 | compute_fn! { "Pubkey.log" => 110 | pubkey.log(); 111 | } 112 | 113 | // 357 CU - string concatenation is expensive 114 | compute_fn! { "Log a pubkey simple concat" => 115 | msg!("A string {0}", "5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ"); 116 | } 117 | ``` 118 | 119 | ## 2 Data Types 120 | 121 | Bigger data types cost more CU. Use the smallest data type possible. 122 | 123 | ```rust 124 | // 357 125 | compute_fn! { "Push Vector u64 " => 126 | let mut a: Vec = Vec::new(); 127 | a.push(1); 128 | a.push(1); 129 | a.push(1); 130 | a.push(1); 131 | a.push(1); 132 | a.push(1); 133 | } 134 | 135 | // 211 CU 136 | compute_fn! { "Vector u8 " => 137 | let mut a: Vec = Vec::new(); 138 | a.push(1); 139 | a.push(1); 140 | a.push(1); 141 | a.push(1); 142 | a.push(1); 143 | a.push(1); 144 | } 145 | 146 | ``` 147 | 148 | ## 3 Serialization 149 | 150 | Borsh serialization can be expensive depending on the account structs, use zero copy when possible and directly interact with the memory. It also saves more stack space than boxing. Operations on the stack are slightly more efficient though. 151 | 152 | ```rust 153 | // 6302 CU 154 | pub fn initialize(_ctx: Context) -> Result<()> { 155 | Ok(()) 156 | } 157 | 158 | // 5020 CU 159 | pub fn initialize_zero_copy(_ctx: Context) -> Result<()> { 160 | Ok(()) 161 | } 162 | ``` 163 | 164 | ```rust 165 | // 108 CU - total CU including serialization 2600 166 | let counter = &mut ctx.accounts.counter; 167 | compute_fn! { "Borsh Serialize" => 168 | counter.count = counter.count.checked_add(1).unwrap(); 169 | } 170 | 171 | // 151 CU - total CU including serialization 1254 172 | let counter = &mut ctx.accounts.counter_zero_copy.load_mut()?; 173 | compute_fn! { "Zero Copy Serialize" => 174 | counter.count = counter.count.checked_add(1).unwrap(); 175 | } 176 | ``` 177 | 178 | ## 4 PDAs 179 | 180 | Depending on the seeds find_program_address can use multiple loops and become very expensive. You can save the bump in an account or pass it in from the client to remove this overhead: 181 | 182 | ```rust 183 | pub fn pdas(ctx: Context) -> Result<()> { 184 | let program_id = Pubkey::from_str("5w6z5PWvtkCd4PaAV7avxE6Fy5brhZsFdbRLMt8UefRQ").unwrap(); 185 | 186 | // 12,136 CUs 187 | compute_fn! { "Find PDA" => 188 | Pubkey::find_program_address(&[b"counter"], ctx.program_id); 189 | } 190 | 191 | // 1,651 CUs 192 | compute_fn! { "Find PDA" => 193 | Pubkey::create_program_address(&[b"counter", &[248_u8]], &program_id).unwrap(); 194 | } 195 | 196 | Ok(()) 197 | } 198 | 199 | #[derive(Accounts)] 200 | pub struct PdaAccounts<'info> { 201 | #[account(mut)] 202 | pub counter: Account<'info, CounterData>, 203 | // 12,136 CUs when not defining the bump 204 | #[account( 205 | seeds = [b"counter"], 206 | bump 207 | )] 208 | pub counter_checked: Account<'info, CounterData>, 209 | } 210 | 211 | #[derive(Accounts)] 212 | pub struct PdaAccounts<'info> { 213 | #[account(mut)] 214 | pub counter: Account<'info, CounterData>, 215 | // only 1600 if using the bump that is saved in the counter_checked account 216 | #[account( 217 | seeds = [b"counter"], 218 | bump = counter_checked.bump 219 | )] 220 | pub counter_checked: Account<'info, CounterData>, 221 | } 222 | 223 | ``` 224 | 225 | ## 5 Closures and function 226 | 227 | During the tests it looks like that closures, function calls and inlining have a similar cost and were well optimized by the compiler. 228 | 229 | ## 6 CPIs 230 | 231 | Every CPI comes with a cost and you also need to calculate in the costs of the called programs function. 232 | If possible avoid doing many CPIs. 233 | I did not find a difference in CPI cost between anchor and native and the optimization is more in the called function. 234 | A CPI for a transfer with the system program costs 2215 CU. 235 | Interesting is though that error handling also costs a lot of CU. So profile how you handle errors and optimize there. 236 | 237 | ```rust 238 | // 2,215 CUs 239 | compute_fn! { "CPI system program" => 240 | let cpi_context = CpiContext::new( 241 | ctx.accounts.system_program.to_account_info(), 242 | system_program::Transfer { 243 | from: ctx.accounts.payer.to_account_info().clone(), 244 | to: ctx.accounts.counter.to_account_info().clone(), 245 | }, 246 | ); 247 | system_program::transfer(cpi_context, 1)?; 248 | } 249 | 250 | // 251 CUs. In an error case though the whole transactions is 1,199 CUs bigger than without. So error handling is expensive 251 | compute_fn! { "Transfer borrowing lamports" => 252 | let counter_account_info = ctx.accounts.counter.to_account_info(); 253 | let mut source_lamports = counter_account_info.try_borrow_mut_lamports()?; 254 | const AMOUNT: u64 = 1; 255 | if **source_lamports < AMOUNT { 256 | msg!("source account has less than {} lamports", AMOUNT); 257 | let err = Err(anchor_lang::error::Error::from(ProgramError::InsufficientFunds)); 258 | return err; 259 | } 260 | **source_lamports -= AMOUNT; 261 | let payer_account_info = ctx.accounts.payer.to_account_info(); 262 | **payer_account_info.try_borrow_mut_lamports()? += AMOUNT; 263 | } 264 | ``` 265 | 266 | ## 7 Checked Math 267 | 268 | It turns out that checked math is more expensive than unchecked math. This is because on every operation the program needs to check if the operation is valid and does not under- or overflow for example. If you are sure that the operation is valid you can use unchecked math so save some CU. 269 | Also the compiler is very good at optimizing the code and inline checks and remove unnecessary calculations if a value is not used for example. 270 | This is why the functions have the `no_inline` attribute to prevent the compiler from optimizing the code away and the calculations are in public functions and the result values are logged after the calculation. 271 | 272 | ```rust 273 | // Testing checked_mul 97314 CU 274 | compute_fn! { "checked mul" => 275 | test_checked_mul(test_value_mul, 7, 200, 60); 276 | } 277 | 278 | msg!("Test value mul: {}", test_value_mul); 279 | 280 | // Testing bit_shift 85113 CU 281 | compute_fn! { "bit shift" => 282 | check_bit_shift(test_value_shift, 7, 200, 60); 283 | } 284 | ``` 285 | 286 | ## 8 Clone vs Reference 287 | 288 | Here we can see that passing by reference is cheaper than cloning. 289 | Interesting is also that due to the bump allocator that solana 290 | uses we will run out of memory as soon as we go to 40 iterations 291 | on the loop that clones the vector. This is because the bump allocator 292 | does not free memory and we only have 32 KB of heap space. By passing in the balances vector by reference we can avoid this problem. 293 | 294 | ```rust 295 | let balances = vec![10_u64; 100]; // a vector with 10,000 elements 296 | 297 | // 47683 CU 298 | compute_fn! { "Pass by reference" => 299 | for _ in 0..39 { 300 | sum_reference += sum_vector_by_reference(&balances); 301 | } 302 | } 303 | 304 | msg!("Sum by reference: {}", sum_reference); 305 | 306 | let mut sum_clone = 0; 307 | 308 | // 49322 CU 309 | compute_fn! { "Clone" => 310 | for _ in 0..39 { 311 | sum_clone += sum_vector_by_value(balances.clone()); 312 | } 313 | } 314 | 315 | msg!("Sum by clone: {}", sum_clone); 316 | ``` 317 | 318 | ## 9 Native vs Anchor vs Asm vs C vs unsave rust 319 | 320 | Anchor is a great tool for writing programs, but it comes with a cost. Every check that anchor does costs CU. While most checks are useful, there may be room for improvement. The anchor generated code is not necessarily optimized for CU. 321 | 322 | | Test Title | Anchor | Native | ASM \*1 | C | 323 | | ------------ | ---------------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------- | ----------------------------------- | 324 | | Deploy size | 265677 bytes (1.8500028 sol) | 48573 bytes (0.33895896 sol) | 1389 bytes (0.01055832 sol) | 1333 Bytes (0.01016856 sol) | 325 | | Counter Inc | [946 CU](counterAnchor/anchor/programs/counter/src/lib.rs) | [843 CU](counterNative/program/src/lib.rs) | [4 CU](counterAsmSbpf/src/sbpfCounter/sbpfCounter.s) | [5 CU](counterC/counter/src/main.c) | 326 | | Signer Check | 303 CU | 103 CU | 1 CU | - | 327 | 328 | | Test Title | Unsave Rust \*2 | 329 | | ------------ | ----------------------------------- | 330 | | Deploy size | 973 bytes (0.00766296 sol) | 331 | | Counter Inc | [5 CU](counterC/counter/src/lib.rs) | 332 | | Signer Check | - | 333 | 334 | *1 Note though that the assembly example is a very simple example and does not include any checks or error handling. 335 | *2 Can be decreased to 4 CU using [sbf-asm-macros](https://crates.io/crates/sbpf-asm-macros) to remove the return type since it is not needed in success case. 336 | 337 | Size will increase with every check that you implement and every instruction that you add. You will also need to increase your program size whenever it becomes bigger. 338 | 339 | Note please that all these tests are tests are super specific for a counter and not really realistic for a production program. Theses tests are only to show the differences between the different program types and ways to think about optimizing your program. 340 | 341 | ## 10 Writing programs in Assembly 342 | 343 | To get started you can watch this [video guide](https://www.youtube.com/watch?v=eacDC0VgyxI&t=1273s&ab_channel=SolPlay) on how to write programs in assembly. 344 | 345 | Writing programs in assembly can be very efficient. You can write very small programs that use very little CU. This can be great for example to write primitives that are used in other programs like checking token balances at the end of a swap or similar. 346 | 347 | The downside is that you need to write everything yourself and you need to be very careful with the stack and heap. You also need to be very careful with the memory layout and the program will be harder to read and maintain. Also Anchor and even native rust are way easier to write secure programs with! Only use this if you know what you are doing. 348 | 349 | There are two counter example in this repository. One was written using the [solana-program-rosetta](https://github.com/joncinque/solana-program-rosetta?tab=readme-ov-file#assembly) by Jon Cinque. It is a great tool to get started with writing programs in assembly. It comes complete with bankrun tests written in rust. It also contains examples written in Zig and C which can also bring great performance improvements. 350 | 351 | Instead of solana-program-rosetta you can also use [SBPF](https://github.com/deanmlittle/sbpf) by [Dean](https://github.com/deanmlittle) which gives your very convenient functions like `sbpf init`, `sbpf build` and `sbpf deploy`. This one comes with Js tests using `mocha` and `chai` and is also a great tool to get started with writing programs in assembly and makes setting up projects much easier. 352 | 353 | If you want to get started on Solana ASM program writing you should start by reading the [docs on the exact memory layout](https://solana.com/docs/programs/faq#input-parameter-serialization) of the [entry point](https://github.com/anza-xyz/agave/blob/1b3eb3df5e244cdbdedb7eff8978823ef0611669/sdk/program/src/entrypoint.rs#L336) and the registers for heap and stack frame. 354 | 355 | Great [repository links and tips for ASM programs](https://github.com/deanmlittle/awesome-sbpf). 356 | 357 | There is also a VSCode extension by Dean: https://github.com/deanmlittle/vscode-sbpf-asm that helps with autocomplete and syntax highlighting. 358 | 359 | It is probably not realistic to write huge programs in assembly, but for small programs or primitives it can be a useful tool. Also knowing how Rust and C code transforms to assembly code can be useful when optimizing your programs. 360 | 361 | Here are some ASM examples for reference: 362 | 363 | - [Fibonacci example](https://github.com/deanmlittle/solana-fibonacci-asm) 364 | - [Sol Transfer](https://github.com/joncinque/solana-program-rosetta/tree/main/transfer-lamports/asm) 365 | - Hello world: https://github.com/deanmlittle/ezbpf run `sbpf init` 366 | - The Counter examples can be found here in this repo 367 | 368 | If you want to use AI like chat GPT to write your programs make sure to train it on these examples before you start. 369 | 370 | ## 11 Other low level optimizations 371 | 372 | ### Compiler flags 373 | 374 | There is certain compiler flags that you set set to decrease CU usage of yor program. You can set the following flags in your `Cargo.toml`. For example you could disable overflow checks. 375 | Note thought that changing a flag like overflow checks of course comes with additional risks like overflow bugs. 376 | 377 | TODO: Add performance tests on different flags. 378 | https://doc.rust-lang.org/cargo/reference/profiles.html#overflow-checks 379 | 380 | ### Inline 381 | 382 | Inlining functions can save CU. The compiler is very good at optimizing the code and inline checks and remove unnecessary calculations if a value is not used for example. 383 | 384 | ```rust 385 | #[inline(always)] 386 | fn add(a: u64, b: u64) -> u64 { 387 | a + b 388 | } 389 | ``` 390 | 391 | Note though that you need to balance inline always vs inline never. Inlining saves CU but needs more stack space while inline never saves stack space but costs more CU. 392 | 393 | ### Non standart heap allocators 394 | 395 | The standard heap allocator is a bump heap allocator which does not free memory. This can lead to out of memory errors if you use a lot of memory. You can use a different heap allocator. 396 | 397 | Metaplex token meta data program uses smalloc heap for example: https://github.com/metaplex-foundation/mpl-core-candy-machine/pull/10 398 | 399 | ### Different entry points 400 | 401 | The standard entry points is not necessarily the most efficient one. You can use a different entry point to save CU. For example the no_std entry point: 402 | https://github.com/cavemanloverboy/solana-nostd-entrypoint 403 | It uses unsafe rust though. 404 | You can read on some comparison of different entry points here: https://github.com/hetdagli234/optimising-solana-programs/tree/main 405 | and here: https://github.com/febo/eisodos 406 | 407 | ## 12 Analyze and optimize yourself 408 | 409 | Most important here is actually to know that every check and every serialization costs compute and how to profile and optimize it yourself since every program is different. Profile and optimize your programs today! 410 | 411 | Feel free to play around with it and add more examples. Also here is a very nice article from @RareSkills_io https://www.rareskills.io/post/solana-compute-unit-price on that topic. I replicated some of the examples :) 412 | 413 | Also here is a nice Twitter thread on the topic: https://x.com/daglihet/status/1840396773833261085 with another CU optimization repository looking also at different entry points and how to optimize them. https://github.com/hetdagli234/optimising-solana-programs/tree/main 414 | 415 | Some nice optimizations can also he found in this [twitter post by dev4all](https://twitter.com/kAsky53/status/1777799557759254810). 416 | -------------------------------------------------------------------------------- /counterAnchor/anchor/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.24.7": 6 | version "7.24.7" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" 8 | integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== 9 | dependencies: 10 | regenerator-runtime "^0.14.0" 11 | 12 | "@babel/runtime@^7.25.0": 13 | version "7.25.7" 14 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" 15 | integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== 16 | dependencies: 17 | regenerator-runtime "^0.14.0" 18 | 19 | "@coral-xyz/anchor-errors@^0.30.1": 20 | version "0.30.1" 21 | resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" 22 | integrity sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ== 23 | 24 | "@coral-xyz/anchor@^0.30.1": 25 | version "0.30.1" 26 | resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" 27 | integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== 28 | dependencies: 29 | "@coral-xyz/anchor-errors" "^0.30.1" 30 | "@coral-xyz/borsh" "^0.30.1" 31 | "@noble/hashes" "^1.3.1" 32 | "@solana/web3.js" "^1.68.0" 33 | bn.js "^5.1.2" 34 | bs58 "^4.0.1" 35 | buffer-layout "^1.2.2" 36 | camelcase "^6.3.0" 37 | cross-fetch "^3.1.5" 38 | crypto-hash "^1.3.0" 39 | eventemitter3 "^4.0.7" 40 | pako "^2.0.3" 41 | snake-case "^3.0.4" 42 | superstruct "^0.15.4" 43 | toml "^3.0.0" 44 | 45 | "@coral-xyz/borsh@^0.30.1": 46 | version "0.30.1" 47 | resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3" 48 | integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ== 49 | dependencies: 50 | bn.js "^5.1.2" 51 | buffer-layout "^1.2.0" 52 | 53 | "@noble/curves@^1.4.0": 54 | version "1.4.0" 55 | resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" 56 | integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== 57 | dependencies: 58 | "@noble/hashes" "1.4.0" 59 | 60 | "@noble/curves@^1.4.2": 61 | version "1.6.0" 62 | resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" 63 | integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== 64 | dependencies: 65 | "@noble/hashes" "1.5.0" 66 | 67 | "@noble/hashes@1.4.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0": 68 | version "1.4.0" 69 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" 70 | integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== 71 | 72 | "@noble/hashes@1.5.0": 73 | version "1.5.0" 74 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" 75 | integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== 76 | 77 | "@solana/buffer-layout@^4.0.1": 78 | version "4.0.1" 79 | resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" 80 | integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== 81 | dependencies: 82 | buffer "~6.0.3" 83 | 84 | "@solana/web3.js@1.95.3": 85 | version "1.95.3" 86 | resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.3.tgz#70b5f4d76823f56b5af6403da51125fffeb65ff3" 87 | integrity sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og== 88 | dependencies: 89 | "@babel/runtime" "^7.25.0" 90 | "@noble/curves" "^1.4.2" 91 | "@noble/hashes" "^1.4.0" 92 | "@solana/buffer-layout" "^4.0.1" 93 | agentkeepalive "^4.5.0" 94 | bigint-buffer "^1.1.5" 95 | bn.js "^5.2.1" 96 | borsh "^0.7.0" 97 | bs58 "^4.0.1" 98 | buffer "6.0.3" 99 | fast-stable-stringify "^1.0.0" 100 | jayson "^4.1.1" 101 | node-fetch "^2.7.0" 102 | rpc-websockets "^9.0.2" 103 | superstruct "^2.0.2" 104 | 105 | "@solana/web3.js@^1.68.0": 106 | version "1.93.4" 107 | resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.93.4.tgz#f16c9f8584e3bd518186e95c4a6347e269dede4f" 108 | integrity sha512-6hHtSYmkUPwcm3eGeqBanMT3pDAhTprrm2IUoboSjNN4gKCBEkY9uIeS2TZKLpsGY+R6fDub5pRi+e4xFVv55w== 109 | dependencies: 110 | "@babel/runtime" "^7.24.7" 111 | "@noble/curves" "^1.4.0" 112 | "@noble/hashes" "^1.4.0" 113 | "@solana/buffer-layout" "^4.0.1" 114 | agentkeepalive "^4.5.0" 115 | bigint-buffer "^1.1.5" 116 | bn.js "^5.2.1" 117 | borsh "^0.7.0" 118 | bs58 "^4.0.1" 119 | buffer "6.0.3" 120 | fast-stable-stringify "^1.0.0" 121 | jayson "^4.1.0" 122 | node-fetch "^2.7.0" 123 | rpc-websockets "^9.0.2" 124 | superstruct "^1.0.4" 125 | 126 | "@swc/helpers@^0.5.11": 127 | version "0.5.11" 128 | resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.11.tgz#5bab8c660a6e23c13b2d23fcd1ee44a2db1b0cb7" 129 | integrity sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A== 130 | dependencies: 131 | tslib "^2.4.0" 132 | 133 | "@types/connect@^3.4.33": 134 | version "3.4.38" 135 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" 136 | integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== 137 | dependencies: 138 | "@types/node" "*" 139 | 140 | "@types/node@*": 141 | version "20.14.9" 142 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" 143 | integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== 144 | dependencies: 145 | undici-types "~5.26.4" 146 | 147 | "@types/node@^12.12.54": 148 | version "12.20.55" 149 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" 150 | integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== 151 | 152 | "@types/uuid@^8.3.4": 153 | version "8.3.4" 154 | resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" 155 | integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== 156 | 157 | "@types/ws@^7.4.4": 158 | version "7.4.7" 159 | resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" 160 | integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== 161 | dependencies: 162 | "@types/node" "*" 163 | 164 | "@types/ws@^8.2.2": 165 | version "8.5.10" 166 | resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" 167 | integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== 168 | dependencies: 169 | "@types/node" "*" 170 | 171 | JSONStream@^1.3.5: 172 | version "1.3.5" 173 | resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" 174 | integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== 175 | dependencies: 176 | jsonparse "^1.2.0" 177 | through ">=2.2.7 <3" 178 | 179 | agentkeepalive@^4.5.0: 180 | version "4.5.0" 181 | resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" 182 | integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== 183 | dependencies: 184 | humanize-ms "^1.2.1" 185 | 186 | base-x@^3.0.2: 187 | version "3.0.9" 188 | resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" 189 | integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== 190 | dependencies: 191 | safe-buffer "^5.0.1" 192 | 193 | base64-js@^1.3.1: 194 | version "1.5.1" 195 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 196 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 197 | 198 | bigint-buffer@^1.1.5: 199 | version "1.1.5" 200 | resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" 201 | integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== 202 | dependencies: 203 | bindings "^1.3.0" 204 | 205 | bindings@^1.3.0: 206 | version "1.5.0" 207 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 208 | integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 209 | dependencies: 210 | file-uri-to-path "1.0.0" 211 | 212 | bn.js@^5.1.2, bn.js@^5.2.0, bn.js@^5.2.1: 213 | version "5.2.1" 214 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" 215 | integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== 216 | 217 | borsh@^0.7.0: 218 | version "0.7.0" 219 | resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" 220 | integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== 221 | dependencies: 222 | bn.js "^5.2.0" 223 | bs58 "^4.0.0" 224 | text-encoding-utf-8 "^1.0.2" 225 | 226 | bs58@^4.0.0, bs58@^4.0.1: 227 | version "4.0.1" 228 | resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" 229 | integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== 230 | dependencies: 231 | base-x "^3.0.2" 232 | 233 | buffer-layout@^1.2.0, buffer-layout@^1.2.2: 234 | version "1.2.2" 235 | resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" 236 | integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== 237 | 238 | buffer@6.0.3, buffer@^6.0.3, buffer@~6.0.3: 239 | version "6.0.3" 240 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 241 | integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 242 | dependencies: 243 | base64-js "^1.3.1" 244 | ieee754 "^1.2.1" 245 | 246 | bufferutil@^4.0.1: 247 | version "4.0.8" 248 | resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" 249 | integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== 250 | dependencies: 251 | node-gyp-build "^4.3.0" 252 | 253 | camelcase@^6.3.0: 254 | version "6.3.0" 255 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" 256 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== 257 | 258 | commander@^2.20.3: 259 | version "2.20.3" 260 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 261 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 262 | 263 | cross-fetch@^3.1.5: 264 | version "3.1.8" 265 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" 266 | integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== 267 | dependencies: 268 | node-fetch "^2.6.12" 269 | 270 | crypto-hash@^1.3.0: 271 | version "1.3.0" 272 | resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" 273 | integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== 274 | 275 | delay@^5.0.0: 276 | version "5.0.0" 277 | resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" 278 | integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== 279 | 280 | dot-case@^3.0.4: 281 | version "3.0.4" 282 | resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" 283 | integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== 284 | dependencies: 285 | no-case "^3.0.4" 286 | tslib "^2.0.3" 287 | 288 | es6-promise@^4.0.3: 289 | version "4.2.8" 290 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" 291 | integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== 292 | 293 | es6-promisify@^5.0.0: 294 | version "5.0.0" 295 | resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" 296 | integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== 297 | dependencies: 298 | es6-promise "^4.0.3" 299 | 300 | eventemitter3@^4.0.7: 301 | version "4.0.7" 302 | resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" 303 | integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== 304 | 305 | eventemitter3@^5.0.1: 306 | version "5.0.1" 307 | resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" 308 | integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== 309 | 310 | eyes@^0.1.8: 311 | version "0.1.8" 312 | resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" 313 | integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== 314 | 315 | fast-stable-stringify@^1.0.0: 316 | version "1.0.0" 317 | resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" 318 | integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== 319 | 320 | file-uri-to-path@1.0.0: 321 | version "1.0.0" 322 | resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 323 | integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 324 | 325 | humanize-ms@^1.2.1: 326 | version "1.2.1" 327 | resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" 328 | integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== 329 | dependencies: 330 | ms "^2.0.0" 331 | 332 | ieee754@^1.2.1: 333 | version "1.2.1" 334 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 335 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 336 | 337 | isomorphic-ws@^4.0.1: 338 | version "4.0.1" 339 | resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" 340 | integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== 341 | 342 | jayson@^4.1.0: 343 | version "4.1.0" 344 | resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.0.tgz#60dc946a85197317f2b1439d672a8b0a99cea2f9" 345 | integrity sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A== 346 | dependencies: 347 | "@types/connect" "^3.4.33" 348 | "@types/node" "^12.12.54" 349 | "@types/ws" "^7.4.4" 350 | JSONStream "^1.3.5" 351 | commander "^2.20.3" 352 | delay "^5.0.0" 353 | es6-promisify "^5.0.0" 354 | eyes "^0.1.8" 355 | isomorphic-ws "^4.0.1" 356 | json-stringify-safe "^5.0.1" 357 | uuid "^8.3.2" 358 | ws "^7.4.5" 359 | 360 | jayson@^4.1.1: 361 | version "4.1.2" 362 | resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" 363 | integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA== 364 | dependencies: 365 | "@types/connect" "^3.4.33" 366 | "@types/node" "^12.12.54" 367 | "@types/ws" "^7.4.4" 368 | JSONStream "^1.3.5" 369 | commander "^2.20.3" 370 | delay "^5.0.0" 371 | es6-promisify "^5.0.0" 372 | eyes "^0.1.8" 373 | isomorphic-ws "^4.0.1" 374 | json-stringify-safe "^5.0.1" 375 | uuid "^8.3.2" 376 | ws "^7.5.10" 377 | 378 | json-stringify-safe@^5.0.1: 379 | version "5.0.1" 380 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 381 | integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== 382 | 383 | jsonparse@^1.2.0: 384 | version "1.3.1" 385 | resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" 386 | integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== 387 | 388 | lower-case@^2.0.2: 389 | version "2.0.2" 390 | resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" 391 | integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== 392 | dependencies: 393 | tslib "^2.0.3" 394 | 395 | ms@^2.0.0: 396 | version "2.1.3" 397 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 398 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 399 | 400 | no-case@^3.0.4: 401 | version "3.0.4" 402 | resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" 403 | integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== 404 | dependencies: 405 | lower-case "^2.0.2" 406 | tslib "^2.0.3" 407 | 408 | node-fetch@^2.6.12, node-fetch@^2.7.0: 409 | version "2.7.0" 410 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" 411 | integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== 412 | dependencies: 413 | whatwg-url "^5.0.0" 414 | 415 | node-gyp-build@^4.3.0: 416 | version "4.8.1" 417 | resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" 418 | integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw== 419 | 420 | pako@^2.0.3: 421 | version "2.1.0" 422 | resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" 423 | integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== 424 | 425 | prettier@3.2.5: 426 | version "3.2.5" 427 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" 428 | integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== 429 | 430 | regenerator-runtime@^0.14.0: 431 | version "0.14.1" 432 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" 433 | integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== 434 | 435 | rpc-websockets@^9.0.2: 436 | version "9.0.2" 437 | resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.2.tgz#4c1568d00b8100f997379a363478f41f8f4b242c" 438 | integrity sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw== 439 | dependencies: 440 | "@swc/helpers" "^0.5.11" 441 | "@types/uuid" "^8.3.4" 442 | "@types/ws" "^8.2.2" 443 | buffer "^6.0.3" 444 | eventemitter3 "^5.0.1" 445 | uuid "^8.3.2" 446 | ws "^8.5.0" 447 | optionalDependencies: 448 | bufferutil "^4.0.1" 449 | utf-8-validate "^5.0.2" 450 | 451 | safe-buffer@^5.0.1: 452 | version "5.2.1" 453 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 454 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 455 | 456 | snake-case@^3.0.4: 457 | version "3.0.4" 458 | resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" 459 | integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== 460 | dependencies: 461 | dot-case "^3.0.4" 462 | tslib "^2.0.3" 463 | 464 | superstruct@^0.15.4: 465 | version "0.15.5" 466 | resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" 467 | integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== 468 | 469 | superstruct@^1.0.4: 470 | version "1.0.4" 471 | resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.4.tgz#0adb99a7578bd2f1c526220da6571b2d485d91ca" 472 | integrity sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ== 473 | 474 | superstruct@^2.0.2: 475 | version "2.0.2" 476 | resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" 477 | integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== 478 | 479 | text-encoding-utf-8@^1.0.2: 480 | version "1.0.2" 481 | resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" 482 | integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== 483 | 484 | "through@>=2.2.7 <3": 485 | version "2.3.8" 486 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 487 | integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== 488 | 489 | toml@^3.0.0: 490 | version "3.0.0" 491 | resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" 492 | integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== 493 | 494 | tr46@~0.0.3: 495 | version "0.0.3" 496 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 497 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 498 | 499 | tslib@^2.0.3, tslib@^2.4.0: 500 | version "2.6.3" 501 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" 502 | integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== 503 | 504 | undici-types@~5.26.4: 505 | version "5.26.5" 506 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" 507 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== 508 | 509 | utf-8-validate@^5.0.2: 510 | version "5.0.10" 511 | resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" 512 | integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== 513 | dependencies: 514 | node-gyp-build "^4.3.0" 515 | 516 | uuid@^8.3.2: 517 | version "8.3.2" 518 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" 519 | integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== 520 | 521 | webidl-conversions@^3.0.0: 522 | version "3.0.1" 523 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 524 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 525 | 526 | whatwg-url@^5.0.0: 527 | version "5.0.0" 528 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 529 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 530 | dependencies: 531 | tr46 "~0.0.3" 532 | webidl-conversions "^3.0.0" 533 | 534 | ws@^7.4.5, ws@^7.5.10: 535 | version "7.5.10" 536 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" 537 | integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== 538 | 539 | ws@^8.5.0: 540 | version "8.17.1" 541 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" 542 | integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== 543 | --------------------------------------------------------------------------------