├── .github ├── actions │ └── setup │ │ └── action.yml ├── dependabot.yml └── workflows │ ├── dependabot-auto-merge.yml │ ├── main.yml │ ├── publish-js-client.yml │ └── publish-rust-client.yml ├── .gitignore ├── .prettierrc ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── clients ├── js-legacy │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc │ ├── .gitignore │ ├── .nojekyll │ ├── LICENSE │ ├── README.md │ ├── jest.config.ts │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ └── index.ts │ ├── test │ │ ├── e2e │ │ │ └── transaction.test.ts │ │ └── unit │ │ │ └── index.test.ts │ ├── tsconfig.all.json │ ├── tsconfig.base.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.root.json │ └── typedoc.json ├── js │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierrc.json │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── generated │ │ │ ├── index.ts │ │ │ ├── instructions │ │ │ │ ├── addMemo.ts │ │ │ │ └── index.ts │ │ │ ├── programs │ │ │ │ ├── index.ts │ │ │ │ └── memo.ts │ │ │ └── shared │ │ │ │ └── index.ts │ │ └── index.ts │ ├── test │ │ ├── _setup.ts │ │ └── addMemo.test.ts │ ├── tsconfig.declarations.json │ ├── tsconfig.json │ ├── tsup.config.ts │ └── typedoc.json └── rust │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── generated │ ├── errors │ │ └── mod.rs │ ├── instructions │ │ ├── add_memo.rs │ │ └── mod.rs │ ├── mod.rs │ └── programs.rs │ └── lib.rs ├── package.json ├── pnpm-lock.yaml ├── program ├── Cargo.toml ├── idl.json ├── src │ ├── entrypoint.rs │ ├── lib.rs │ └── processor.rs └── tests │ └── functional.rs ├── rust-toolchain.toml ├── rustfmt.toml └── scripts ├── audit-rust.mjs ├── check-solana-version.mjs ├── ci └── set-env.mjs ├── client ├── format-js.mjs ├── format-rust.mjs ├── lint-js.mjs ├── lint-rust.mjs ├── publish-js.mjs ├── publish-rust.mjs ├── test-js.mjs └── test-rust.mjs ├── generate-clients.mjs ├── link-solana-version.mjs ├── program ├── build.mjs ├── format.mjs ├── lint.mjs └── test.mjs ├── solana.dic ├── spellcheck.toml ├── start-validator.mjs ├── stop-validator.mjs ├── upgrade-template.mjs └── utils.mjs /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup environment 2 | 3 | inputs: 4 | cargo-cache-key: 5 | description: The key to cache cargo dependencies. Skips cargo caching if not provided. 6 | required: false 7 | cargo-cache-fallback-key: 8 | description: The fallback key to use when caching cargo dependencies. Default to not using a fallback key. 9 | required: false 10 | cargo-cache-local-key: 11 | description: The key to cache local cargo dependencies. Skips local cargo caching if not provided. 12 | required: false 13 | clippy: 14 | description: Install Clippy if `true`. Defaults to `false`. 15 | required: false 16 | rustfmt: 17 | description: Install Rustfmt if `true`. Defaults to `false`. 18 | required: false 19 | solana: 20 | description: Install Solana if `true`. Defaults to `false`. 21 | required: false 22 | 23 | runs: 24 | using: 'composite' 25 | steps: 26 | - name: Setup pnpm 27 | uses: pnpm/action-setup@v3 28 | 29 | - name: Setup Node.js 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version: 20 33 | cache: 'pnpm' 34 | 35 | - name: Install Dependencies 36 | run: pnpm install --frozen-lockfile 37 | shell: bash 38 | 39 | - name: Set Environment Variables 40 | shell: bash 41 | run: pnpm zx ./scripts/ci/set-env.mjs 42 | 43 | - name: Install Rustfmt 44 | if: ${{ inputs.rustfmt == 'true' }} 45 | uses: dtolnay/rust-toolchain@master 46 | with: 47 | toolchain: ${{ env.TOOLCHAIN_FORMAT }} 48 | components: rustfmt 49 | 50 | - name: Install Clippy 51 | if: ${{ inputs.clippy == 'true' }} 52 | uses: dtolnay/rust-toolchain@master 53 | with: 54 | toolchain: ${{ env.TOOLCHAIN_LINT }} 55 | components: clippy 56 | 57 | - name: Install Solana 58 | if: ${{ inputs.solana == 'true' }} 59 | uses: solana-program/actions/install-solana@v1 60 | with: 61 | version: ${{ env.SOLANA_VERSION }} 62 | cache: true 63 | 64 | - name: Cache Cargo Dependencies 65 | if: ${{ inputs.cargo-cache-key && !inputs.cargo-cache-fallback-key }} 66 | uses: actions/cache@v4 67 | with: 68 | path: | 69 | ~/.cargo/bin/ 70 | ~/.cargo/registry/index/ 71 | ~/.cargo/registry/cache/ 72 | ~/.cargo/git/db/ 73 | target/ 74 | key: ${{ runner.os }}-${{ inputs.cargo-cache-key }}-${{ hashFiles('**/Cargo.lock') }} 75 | restore-keys: ${{ runner.os }}-${{ inputs.cargo-cache-key }} 76 | 77 | - name: Cache Cargo Dependencies With Fallback 78 | if: ${{ inputs.cargo-cache-key && inputs.cargo-cache-fallback-key }} 79 | uses: actions/cache@v4 80 | with: 81 | path: | 82 | ~/.cargo/bin/ 83 | ~/.cargo/registry/index/ 84 | ~/.cargo/registry/cache/ 85 | ~/.cargo/git/db/ 86 | target/ 87 | key: ${{ runner.os }}-${{ inputs.cargo-cache-key }}-${{ hashFiles('**/Cargo.lock') }} 88 | restore-keys: | 89 | ${{ runner.os }}-${{ inputs.cargo-cache-key }} 90 | ${{ runner.os }}-${{ inputs.cargo-cache-fallback-key }}-${{ hashFiles('**/Cargo.lock') }} 91 | ${{ runner.os }}-${{ inputs.cargo-cache-fallback-key }} 92 | 93 | - name: Cache Local Cargo Dependencies 94 | if: ${{ inputs.cargo-cache-local-key }} 95 | uses: actions/cache@v4 96 | with: 97 | path: | 98 | .cargo/bin/ 99 | .cargo/registry/index/ 100 | .cargo/registry/cache/ 101 | .cargo/git/db/ 102 | key: ${{ runner.os }}-${{ inputs.cargo-cache-local-key }}-${{ hashFiles('**/Cargo.lock') }} 103 | restore-keys: ${{ runner.os }}-${{ inputs.cargo-cache-local-key }} 104 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: cargo 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | time: "08:00" 13 | timezone: UTC 14 | open-pull-requests-limit: 6 15 | - package-ecosystem: npm 16 | directory: "/" 17 | schedule: 18 | interval: daily 19 | time: "09:00" 20 | timezone: UTC 21 | open-pull-requests-limit: 6 22 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository_owner == 'solana-program' 12 | steps: 13 | - name: Enable auto-merge 14 | run: gh pr merge --auto --squash "$PR_URL" 15 | env: 16 | PR_URL: ${{ github.event.pull_request.html_url }} 17 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | - name: Approve 19 | run: gh pr review --approve "$PR_URL" 20 | env: 21 | PR_URL: ${{ github.event.pull_request.html_url }} 22 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | format_and_lint_programs: 11 | name: Format & Lint Programs 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Git Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Environment 18 | uses: ./.github/actions/setup 19 | with: 20 | clippy: true 21 | rustfmt: true 22 | 23 | - name: Format Programs 24 | run: pnpm programs:format 25 | 26 | - name: Lint Programs 27 | run: pnpm programs:lint 28 | 29 | format_and_lint_client_js: 30 | name: Format & Lint Client JS 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Git Checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: Setup Environment 37 | uses: ./.github/actions/setup 38 | 39 | - name: Format Client JS 40 | run: pnpm clients:js:format 41 | 42 | - name: Lint Client JS 43 | run: pnpm clients:js:lint 44 | 45 | format_and_lint_client_rust: 46 | name: Format & Lint Client Rust 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Git Checkout 50 | uses: actions/checkout@v4 51 | 52 | - name: Setup Environment 53 | uses: ./.github/actions/setup 54 | with: 55 | clippy: true 56 | rustfmt: true 57 | 58 | - name: Format Client Rust 59 | run: pnpm clients:rust:format 60 | 61 | - name: Lint Client Rust 62 | run: pnpm clients:rust:lint 63 | 64 | audit_rust: 65 | name: Audit Rust 66 | runs-on: ubuntu-latest 67 | steps: 68 | - name: Git Checkout 69 | uses: actions/checkout@v4 70 | 71 | - name: Setup Environment 72 | uses: ./.github/actions/setup 73 | with: 74 | cargo-cache-key: cargo-audit 75 | 76 | - name: Install cargo-audit 77 | uses: taiki-e/install-action@v2 78 | with: 79 | tool: cargo-audit 80 | 81 | - name: Run cargo-audit 82 | run: pnpm rust:audit 83 | 84 | semver_rust: 85 | name: Check semver Rust 86 | runs-on: ubuntu-latest 87 | steps: 88 | - name: Git Checkout 89 | uses: actions/checkout@v4 90 | 91 | - name: Setup Environment 92 | uses: ./.github/actions/setup 93 | with: 94 | cargo-cache-key: cargo-semver 95 | 96 | - name: Install cargo-audit 97 | uses: taiki-e/install-action@v2 98 | with: 99 | tool: cargo-semver-checks 100 | 101 | - name: Run semver checks 102 | run: pnpm rust:semver 103 | 104 | spellcheck_rust: 105 | name: Spellcheck Rust 106 | runs-on: ubuntu-latest 107 | steps: 108 | - name: Git Checkout 109 | uses: actions/checkout@v4 110 | 111 | - name: Setup Environment 112 | uses: ./.github/actions/setup 113 | with: 114 | cargo-cache-key: cargo-spellcheck 115 | 116 | - name: Install cargo-spellcheck 117 | uses: taiki-e/install-action@v2 118 | with: 119 | tool: cargo-spellcheck 120 | 121 | - name: Run cargo-spellcheck 122 | run: pnpm rust:spellcheck 123 | 124 | build_programs: 125 | name: Build programs 126 | runs-on: ubuntu-latest 127 | needs: format_and_lint_programs 128 | steps: 129 | - name: Git Checkout 130 | uses: actions/checkout@v4 131 | 132 | - name: Setup Environment 133 | uses: ./.github/actions/setup 134 | with: 135 | cargo-cache-key: cargo-programs 136 | solana: true 137 | 138 | - name: Build Programs 139 | run: pnpm programs:build 140 | 141 | - name: Upload Program Builds 142 | uses: actions/upload-artifact@v4 143 | with: 144 | name: program-builds 145 | path: ./target/deploy/*.so 146 | if-no-files-found: error 147 | 148 | - name: Save Program Builds For Client Jobs 149 | uses: actions/cache/save@v4 150 | with: 151 | path: ./**/*.so 152 | key: ${{ runner.os }}-builds-${{ github.sha }} 153 | 154 | test_programs: 155 | name: Test Programs 156 | runs-on: ubuntu-latest 157 | needs: format_and_lint_programs 158 | steps: 159 | - name: Git Checkout 160 | uses: actions/checkout@v4 161 | 162 | - name: Setup Environment 163 | uses: ./.github/actions/setup 164 | with: 165 | cargo-cache-key: cargo-program-tests 166 | cargo-cache-fallback-key: cargo-programs 167 | solana: true 168 | 169 | - name: Test Programs 170 | run: pnpm programs:test 171 | 172 | generate_clients: 173 | name: Check Client Generation 174 | runs-on: ubuntu-latest 175 | needs: format_and_lint_programs 176 | steps: 177 | - name: Git Checkout 178 | uses: actions/checkout@v4 179 | 180 | - name: Setup Environment 181 | uses: ./.github/actions/setup 182 | with: 183 | rustfmt: true 184 | 185 | - name: Generate Clients 186 | run: pnpm generate:clients 187 | 188 | - name: Check Working Directory 189 | run: | 190 | git status --porcelain 191 | test -z "$(git status --porcelain)" 192 | 193 | test_client_js: 194 | name: Test Client JS 195 | runs-on: ubuntu-latest 196 | needs: build_programs 197 | steps: 198 | - name: Git Checkout 199 | uses: actions/checkout@v4 200 | 201 | - name: Setup Environment 202 | uses: ./.github/actions/setup 203 | with: 204 | solana: true 205 | 206 | - name: Restore Program Builds 207 | uses: actions/cache/restore@v4 208 | with: 209 | path: ./**/*.so 210 | key: ${{ runner.os }}-builds-${{ github.sha }} 211 | 212 | - name: Test Client JS 213 | run: pnpm clients:js:test 214 | 215 | test_client_js_legacy: 216 | name: Test Client JS (legacy) 217 | runs-on: ubuntu-latest 218 | needs: build_programs 219 | steps: 220 | - name: Git Checkout 221 | uses: actions/checkout@v4 222 | 223 | - name: Setup Environment 224 | uses: ./.github/actions/setup 225 | with: 226 | solana: true 227 | 228 | - name: Restore Program Builds 229 | uses: actions/cache/restore@v4 230 | with: 231 | path: ./**/*.so 232 | key: ${{ runner.os }}-builds-${{ github.sha }} 233 | 234 | - name: Test Client JS (legacy) 235 | run: | 236 | cd clients/js-legacy 237 | pnpm install 238 | pnpm lint 239 | pnpm build 240 | pnpm test 241 | 242 | test_client_rust: 243 | name: Test Client Rust 244 | runs-on: ubuntu-latest 245 | needs: build_programs 246 | steps: 247 | - name: Git Checkout 248 | uses: actions/checkout@v4 249 | 250 | - name: Setup Environment 251 | uses: ./.github/actions/setup 252 | with: 253 | cargo-cache-key: cargo-rust-client 254 | solana: true 255 | 256 | - name: Restore Program Builds 257 | uses: actions/cache/restore@v4 258 | with: 259 | path: ./**/*.so 260 | key: ${{ runner.os }}-builds-${{ github.sha }} 261 | 262 | - name: Test Client Rust 263 | run: pnpm clients:rust:test 264 | -------------------------------------------------------------------------------- /.github/workflows/publish-js-client.yml: -------------------------------------------------------------------------------- 1 | name: Publish JS Client 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | level: 7 | description: Version level 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | - prerelease 16 | - prepatch 17 | - preminor 18 | - premajor 19 | tag: 20 | description: NPM Tag (and preid for pre-releases) 21 | required: true 22 | type: string 23 | default: latest 24 | create_release: 25 | description: Create a GitHub release 26 | required: true 27 | type: boolean 28 | default: true 29 | 30 | jobs: 31 | test_js: 32 | name: Test JS client 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Git Checkout 36 | uses: actions/checkout@v4 37 | 38 | - name: Setup Environment 39 | uses: ./.github/actions/setup 40 | with: 41 | cargo-cache-key: cargo-programs 42 | solana: true 43 | 44 | - name: Format JS Client 45 | run: pnpm clients:js:format 46 | 47 | - name: Lint JS Client 48 | run: pnpm clients:js:lint 49 | 50 | - name: Build Programs 51 | run: pnpm programs:build 52 | 53 | - name: Test JS Client 54 | run: pnpm clients:js:test 55 | 56 | publish_js: 57 | name: Publish JS client 58 | runs-on: ubuntu-latest 59 | needs: test_js 60 | permissions: 61 | contents: write 62 | steps: 63 | - name: Git Checkout 64 | uses: actions/checkout@v4 65 | with: 66 | token: ${{ secrets.ANZA_TEAM_PAT }} 67 | 68 | - name: Setup Environment 69 | uses: ./.github/actions/setup 70 | 71 | - name: Ensure NPM_TOKEN variable is set 72 | env: 73 | token: ${{ secrets.NPM_TOKEN }} 74 | if: ${{ env.token == '' }} 75 | run: | 76 | echo "The NPM_TOKEN secret variable is not set" 77 | echo "Go to \"Settings\" -> \"Secrets and variables\" -> \"Actions\" -> \"New repository secret\"." 78 | exit 1 79 | 80 | - name: NPM Authentication 81 | run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" 82 | env: 83 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 84 | 85 | - name: Set Git Author 86 | run: | 87 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 88 | git config --global user.name "github-actions[bot]" 89 | 90 | - name: Publish JS Client 91 | id: publish 92 | run: pnpm clients:js:publish ${{ inputs.level }} ${{ inputs.tag }} 93 | 94 | - name: Push Commit and Tag 95 | run: git push origin --follow-tags 96 | 97 | - name: Create GitHub release 98 | if: github.event.inputs.create_release == 'true' 99 | uses: ncipollo/release-action@v1 100 | with: 101 | tag: js@v${{ steps.publish.outputs.new_version }} 102 | -------------------------------------------------------------------------------- /.github/workflows/publish-rust-client.yml: -------------------------------------------------------------------------------- 1 | name: Publish Rust Client 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | level: 7 | description: Level 8 | required: true 9 | default: patch 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | - rc 16 | - beta 17 | - alpha 18 | - release 19 | - version 20 | version: 21 | description: Version 22 | required: false 23 | type: string 24 | dry_run: 25 | description: Dry run 26 | required: true 27 | default: true 28 | type: boolean 29 | create_release: 30 | description: Create a GitHub release 31 | required: true 32 | type: boolean 33 | default: true 34 | 35 | jobs: 36 | test_rust: 37 | name: Test Rust client 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Git Checkout 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup Environment 44 | uses: ./.github/actions/setup 45 | with: 46 | cargo-cache-key: cargo-rust-client 47 | clippy: true 48 | rustfmt: true 49 | solana: true 50 | 51 | - name: Format Rust Client 52 | run: pnpm clients:rust:format 53 | 54 | - name: Lint Rust Client 55 | run: pnpm clients:rust:lint 56 | 57 | - name: Test Rust Client 58 | run: pnpm clients:rust:test 59 | 60 | publish_rust: 61 | name: Publish Rust Client 62 | runs-on: ubuntu-latest 63 | needs: test_rust 64 | permissions: 65 | contents: write 66 | steps: 67 | - name: Git Checkout 68 | uses: actions/checkout@v4 69 | 70 | - name: Setup Environment 71 | uses: ./.github/actions/setup 72 | with: 73 | cargo-cache-key: cargo-publish-rust-client 74 | cargo-cache-fallback-key: cargo-rust-client 75 | clippy: true 76 | rustfmt: true 77 | 78 | - name: Install Cargo Release 79 | run: which cargo-release || cargo install cargo-release 80 | 81 | - name: Ensure CARGO_REGISTRY_TOKEN variable is set 82 | env: 83 | token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 84 | if: ${{ env.token == '' }} 85 | run: | 86 | echo "The CARGO_REGISTRY_TOKEN secret variable is not set" 87 | echo "Go to \"Settings\" -> \"Secrets and variables\" -> \"Actions\" -> \"New repository secret\"." 88 | exit 1 89 | 90 | - name: Set Git Author 91 | run: | 92 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 93 | git config --global user.name "github-actions[bot]" 94 | 95 | - name: Publish Rust Client 96 | id: publish 97 | env: 98 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 99 | run: | 100 | if [ "${{ inputs.level }}" == "version" ]; then 101 | LEVEL=${{ inputs.version }} 102 | else 103 | LEVEL=${{ inputs.level }} 104 | fi 105 | 106 | if [ "${{ inputs.dry_run }}" == "true" ]; then 107 | OPTIONS="--dry-run" 108 | else 109 | OPTIONS="" 110 | fi 111 | 112 | pnpm clients:rust:publish $LEVEL $OPTIONS 113 | 114 | - name: Push Commit and Tag 115 | if: github.event.inputs.dry_run != 'true' 116 | run: git push origin --follow-tags 117 | 118 | - name: Create GitHub release 119 | if: github.event.inputs.create_release == 'true' && github.event.inputs.dry_run != 'true' 120 | uses: ncipollo/release-action@v1 121 | with: 122 | tag: rust@v${{ steps.publish.outputs.new_version }} 123 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "always", 8 | "printWidth": 80 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["clients/rust", "program"] 4 | 5 | [workspace.metadata.cli] 6 | solana = "2.2.0" 7 | 8 | # Specify Rust toolchains for rustfmt, clippy, and build. 9 | # Any unprovided toolchains default to stable. 10 | [workspace.metadata.toolchains] 11 | format = "nightly-2024-11-22" 12 | lint = "nightly-2024-11-22" 13 | 14 | [workspace.metadata.spellcheck] 15 | config = "scripts/spellcheck.toml" 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPL Memo Program 2 | 3 | 4 | 5 | [![Docs.rs](https://docs.rs/spl-memo/badge.svg?logo=rust)] 6 | 7 | 8 | 9 | 10 | A simple program that validates a string of UTF-8 encoded characters and logs it 11 | in the transaction log. The program also verifies that any accounts provided are 12 | signers of the transaction, and if so, logs their addresses. It can be used to 13 | record a string on-chain, stored in the instruction data of a successful 14 | transaction, and optionally verify the originator. 15 | 16 | This code was moved from the 17 | [solana-program-library](https://github.com/solana-labs/solana-program-library/tree/master/memo) 18 | and is now maintained in this repo. 19 | 20 | Full documentation is available at https://spl.solana.com/memo 21 | 22 | ## Audit 23 | 24 | The [security-audits repo](https://github.com/solana-labs/security-audits) contains 25 | all past and present program audits. 26 | 27 | ## Project setup 28 | 29 | The first thing you'll want to do is install NPM dependencies which will allow you to access all the scripts and tools provided by this template. 30 | 31 | ```sh 32 | pnpm install 33 | ``` 34 | 35 | ## Program 36 | 37 | The `program` folder in the root of this repository contains the program 38 | implementation. 39 | 40 | The following scripts build, test, format and lint the program respectively. 41 | 42 | ```sh 43 | pnpm programs:build 44 | pnpm programs:test 45 | pnpm programs:format 46 | pnpm programs:lint 47 | ``` 48 | 49 | ## Generating IDLs 50 | 51 | You may use the following command to generate the IDLs. 52 | 53 | ```sh 54 | pnpm generate:idls 55 | ``` 56 | 57 | Note that, to ensure IDLs are generated using the correct framework version, the 58 | specific version used by the program will be downloaded and used locally. 59 | 60 | ## Generating clients 61 | 62 | Once the program's IDL has been generated, you can generate clients for them using the following command. 63 | 64 | ```sh 65 | pnpm generate:clients 66 | ``` 67 | 68 | Alternatively, you can use the `generate` script to generate both the IDL and the clients at once. 69 | 70 | ```sh 71 | pnpm generate 72 | ``` 73 | 74 | ## Managing clients 75 | 76 | The following clients are available for your programs. You may use the following links to learn more about each client. 77 | 78 | - [JS client](./clients/js) 79 | - [Rust client](./clients/rust) 80 | 81 | ## Starting and stopping the local validator 82 | 83 | The following script is available to start your local validator. 84 | 85 | ```sh 86 | pnpm validator:start 87 | ``` 88 | 89 | By default, if a local validator is already running, the script will be skipped. You may use the `validator:restart` script instead to force the validator to restart. 90 | 91 | ```sh 92 | pnpm validator:restart 93 | ``` 94 | 95 | Finally, you may stop the local validator using the following command. 96 | 97 | ```sh 98 | pnpm validator:stop 99 | ``` 100 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting security problems 4 | 5 | **DO NOT CREATE A GITHUB ISSUE** to report a security problem. 6 | 7 | Instead please use this [Report a Vulnerability](https://github.com/solana-program/memo/security/advisories/new) link. 8 | Provide a helpful title and detailed description of the problem. 9 | 10 | If you haven't done so already, please **enable two-factor auth** in your GitHub account. 11 | 12 | Expect a response as fast as possible in the advisory, typically within 72 hours. 13 | 14 | -- 15 | 16 | If you do not receive a response in the advisory, send an email to 17 | with the full URL of the advisory you have created. DO NOT 18 | include attachments or provide detail sufficient for exploitation regarding the 19 | security issue in this email. **Only provide such details in the advisory**. 20 | 21 | If you do not receive a response from please followup with 22 | the team directly. You can do this in one of the `#Dev Tooling` channels of the 23 | [Solana Tech discord server](https://solana.com/discord), by pinging the admins 24 | in the channel and referencing the fact that you submitted a security problem. 25 | 26 | ## Security Bug Bounties 27 | 28 | The Solana Foundation offer bounties for critical security issues. Please 29 | see the [Agave Security Bug 30 | Bounties](https://github.com/anza-xyz/agave/security/policy#security-bug-bounties) 31 | for details on classes of bugs and payment amounts. 32 | 33 | ## Scope 34 | 35 | Only the `spl-memo` program is included in the bounty scope, at 36 | [program](https://github.com/solana-program/memo/tree/master/program). 37 | 38 | If you discover a critical security issue in an out-of-scope component, your finding 39 | may still be valuable. 40 | -------------------------------------------------------------------------------- /clients/js-legacy/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /clients/js-legacy/.eslintignore: -------------------------------------------------------------------------------- 1 | docs 2 | lib 3 | test-ledger 4 | 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /clients/js-legacy/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:require-extensions/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "plugins": [ 10 | "@typescript-eslint", 11 | "require-extensions" 12 | ], 13 | "rules": { 14 | "@typescript-eslint/ban-ts-comment": "off", 15 | "@typescript-eslint/no-explicit-any": "off", 16 | "@typescript-eslint/no-unused-vars": "off", 17 | "@typescript-eslint/no-empty-interface": "off", 18 | "@typescript-eslint/consistent-type-imports": "error" 19 | }, 20 | "overrides": [ 21 | { 22 | "files": [ 23 | "test/**/*" 24 | ], 25 | "rules": { 26 | "require-extensions/require-extensions": "off" 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /clients/js-legacy/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | docs 3 | lib 4 | test-ledger 5 | -------------------------------------------------------------------------------- /clients/js-legacy/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solana-program/memo/6d613c1952ff89e297a9d80b2e3d910dc8730d46/clients/js-legacy/.nojekyll -------------------------------------------------------------------------------- /clients/js-legacy/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /clients/js-legacy/README.md: -------------------------------------------------------------------------------- 1 | # `@solana/spl-memo` 2 | 3 | A TypeScript library for interacting with the SPL Memo program. 4 | 5 | ## Links 6 | 7 | - [Install](#install) 8 | - [Build from Source](#build-from-source) 9 | 10 | ## Install 11 | 12 | ```shell 13 | npm install --save @solana/spl-memo @solana/web3.js@1 14 | ``` 15 | _OR_ 16 | ```shell 17 | yarn add @solana/spl-memo @solana/web3.js@1 18 | ``` 19 | 20 | ## Build from Source 21 | 22 | 0. Prerequisites 23 | 24 | * Node 16+ 25 | * PNPM 9+ 26 | 27 | 1. Clone the project: 28 | ```shell 29 | git clone https://github.com/solana-program/memo.git 30 | ``` 31 | 32 | 2. Navigate to the library: 33 | ```shell 34 | cd clients/js-legacy 35 | ``` 36 | 37 | 3. Install the dependencies: 38 | ```shell 39 | pnpm install 40 | ``` 41 | 42 | 4. Build the library: 43 | ```shell 44 | pnpm build 45 | ``` 46 | 47 | 5. Run the tests: 48 | ```shell 49 | pnpm test 50 | ``` 51 | -------------------------------------------------------------------------------- /clients/js-legacy/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from 'ts-jest'; 2 | 3 | const jestConfig: JestConfigWithTsJest = { 4 | preset: 'ts-jest/presets/default-esm', 5 | }; 6 | 7 | export default jestConfig; 8 | -------------------------------------------------------------------------------- /clients/js-legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@solana/spl-memo", 3 | "description": "SPL Memo Program JS API", 4 | "version": "0.2.4", 5 | "author": "Solana Labs Maintainers ", 6 | "repository": "https://github.com/solana-labs/solana-program-library", 7 | "license": "Apache-2.0", 8 | "type": "module", 9 | "sideEffects": false, 10 | "engines": { 11 | "node": ">=16" 12 | }, 13 | "files": [ 14 | "lib", 15 | "src", 16 | "LICENSE", 17 | "README.md" 18 | ], 19 | "publishConfig": { 20 | "access": "public" 21 | }, 22 | "main": "./lib/cjs/index.js", 23 | "module": "./lib/esm/index.js", 24 | "types": "./lib/types/index.d.ts", 25 | "exports": { 26 | "types": "./lib/types/index.d.ts", 27 | "require": "./lib/cjs/index.js", 28 | "import": "./lib/esm/index.js" 29 | }, 30 | "scripts": { 31 | "nuke": "shx rm -rf node_modules package-lock.json || true", 32 | "reinstall": "npm run nuke && npm install", 33 | "clean": "shx rm -rf lib **/*.tsbuildinfo || true", 34 | "build": "tsc --build --verbose tsconfig.all.json", 35 | "postbuild": "shx echo '{ \"type\": \"commonjs\" }' > lib/cjs/package.json", 36 | "watch": "tsc --build --verbose --watch tsconfig.all.json", 37 | "release": "npm run clean && npm run build", 38 | "lint": "eslint --max-warnings 0 .", 39 | "lint:fix": "eslint --fix .", 40 | "test": "npm run test:unit && npm run test:e2e", 41 | "test:unit": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest test/unit", 42 | "test:e2e": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" start-server-and-test 'solana-test-validator -r -q' http://127.0.0.1:8899/health 'jest test/e2e'", 43 | "deploy": "npm run deploy:docs", 44 | "docs": "shx rm -rf docs && typedoc && shx cp .nojekyll docs/", 45 | "deploy:docs": "npm run docs && gh-pages --dest memo/js --dist docs --dotfiles" 46 | }, 47 | "peerDependencies": { 48 | "@solana/web3.js": "^1.95.5" 49 | }, 50 | "dependencies": { 51 | "buffer": "^6.0.3" 52 | }, 53 | "devDependencies": { 54 | "@solana/web3.js": "^1.95.5", 55 | "@types/chai": "^5.0.1", 56 | "@types/jest": "^29.5.14", 57 | "@types/node": "^22.9.1", 58 | "@types/node-fetch": "^2.6.12", 59 | "@typescript-eslint/eslint-plugin": "^8.4.0", 60 | "@typescript-eslint/parser": "^8.4.0", 61 | "chai": "^5.1.2", 62 | "eslint": "^8.57.0", 63 | "eslint-plugin-require-extensions": "^0.1.1", 64 | "gh-pages": "^6.2.0", 65 | "jest": "^29.0.0", 66 | "process": "^0.11.10", 67 | "shx": "^0.3.4", 68 | "start-server-and-test": "^2.0.8", 69 | "ts-jest": "^29.2.5", 70 | "ts-node": "^10.9.2", 71 | "typedoc": "^0.26.11", 72 | "typescript": "^5.6.3" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /clients/js-legacy/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'buffer'; 2 | import { PublicKey, TransactionInstruction } from '@solana/web3.js'; 3 | 4 | export const MEMO_PROGRAM_ID: PublicKey = new PublicKey('MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'); 5 | 6 | /** 7 | * Creates and returns an instruction which validates a string of UTF-8 8 | * encoded characters and verifies that any accounts provided are signers of 9 | * the transaction. The program also logs the memo, as well as any verified 10 | * signer addresses, to the transaction log, so that anyone can easily observe 11 | * memos and know they were approved by zero or more addresses by inspecting 12 | * the transaction log from a trusted provider. 13 | * 14 | * Public keys passed in via the signerPubkeys will identify Signers which 15 | * must subsequently sign the Transaction including the returned 16 | * TransactionInstruction in order for the transaction to be valid. 17 | * 18 | * @param memo The UTF-8 encoded memo string to validate 19 | * @param signerPubkeys An array of public keys which must sign the 20 | * Transaction including the returned TransactionInstruction in order 21 | * for the transaction to be valid and the memo verification to 22 | * succeed. null is allowed if there are no signers for the memo 23 | * verification. 24 | **/ 25 | export function createMemoInstruction(memo: string, signerPubkeys?: Array): TransactionInstruction { 26 | const keys = 27 | signerPubkeys == null 28 | ? [] 29 | : signerPubkeys.map(function (key) { 30 | return { pubkey: key, isSigner: true, isWritable: false }; 31 | }); 32 | 33 | return new TransactionInstruction({ 34 | keys: keys, 35 | programId: MEMO_PROGRAM_ID, 36 | data: Buffer.from(memo, 'utf8'), 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /clients/js-legacy/test/e2e/transaction.test.ts: -------------------------------------------------------------------------------- 1 | import { createMemoInstruction } from '../../src/index'; 2 | import { Connection, Keypair, Transaction, LAMPORTS_PER_SOL, sendAndConfirmTransaction } from '@solana/web3.js'; 3 | 4 | test('transaction: live', async () => { 5 | const url = 'http://127.0.0.1:8899'; 6 | const connection = new Connection(url, 'confirmed'); 7 | await connection.getVersion(); 8 | const signer = new Keypair(); // also fee-payer 9 | 10 | const airdropSignature = await connection.requestAirdrop(signer.publicKey, LAMPORTS_PER_SOL / 10); 11 | await connection.confirmTransaction(airdropSignature, 'confirmed'); 12 | 13 | const memoTx = new Transaction().add(createMemoInstruction('this is a test memo', [signer.publicKey])); 14 | await sendAndConfirmTransaction(connection, memoTx, [signer], { 15 | preflightCommitment: 'confirmed', 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /clients/js-legacy/test/unit/index.test.ts: -------------------------------------------------------------------------------- 1 | import { createMemoInstruction, MEMO_PROGRAM_ID } from '../../src/index'; 2 | import { expect } from 'chai'; 3 | import { Keypair } from '@solana/web3.js'; 4 | 5 | test('instruction: no signers', () => { 6 | const ix = createMemoInstruction('this is a test memo', []); 7 | expect(ix.programId).to.eql(MEMO_PROGRAM_ID); 8 | expect(ix.keys).to.have.length(0); 9 | expect(ix.data).to.have.length(19); 10 | 11 | const ix2 = createMemoInstruction('this is a test'); 12 | expect(ix2.programId).to.eql(MEMO_PROGRAM_ID); 13 | expect(ix2.keys).to.have.length(0); 14 | expect(ix2.data).to.have.length(14); 15 | }); 16 | 17 | test('instruction: one signer', () => { 18 | const signer = new Keypair(); 19 | const ix = createMemoInstruction('this is a test memo', [signer.publicKey]); 20 | expect(ix.programId).to.eql(MEMO_PROGRAM_ID); 21 | expect(ix.keys).to.have.length(1); 22 | expect(ix.data).to.have.length(19); 23 | }); 24 | 25 | test('instruction: many signers', () => { 26 | const signer0 = new Keypair(); 27 | const signer1 = new Keypair(); 28 | const signer2 = new Keypair(); 29 | const signer3 = new Keypair(); 30 | const signer4 = new Keypair(); 31 | const ix = createMemoInstruction('this is a test memo', [ 32 | signer0.publicKey, 33 | signer1.publicKey, 34 | signer2.publicKey, 35 | signer3.publicKey, 36 | signer4.publicKey, 37 | ]); 38 | expect(ix.programId).to.eql(MEMO_PROGRAM_ID); 39 | expect(ix.keys).to.have.length(5); 40 | expect(ix.data).to.have.length(19); 41 | }); 42 | -------------------------------------------------------------------------------- /clients/js-legacy/tsconfig.all.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.root.json", 3 | "references": [ 4 | { 5 | "path": "./tsconfig.cjs.json" 6 | }, 7 | { 8 | "path": "./tsconfig.esm.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /clients/js-legacy/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [], 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "esModuleInterop": true, 8 | "isolatedModules": true, 9 | "noEmitOnError": true, 10 | "resolveJsonModule": true, 11 | "strict": true, 12 | "stripInternal": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /clients/js-legacy/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "outDir": "lib/cjs", 6 | "target": "ES2016", 7 | "module": "CommonJS", 8 | "sourceMap": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /clients/js-legacy/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "outDir": "lib/esm", 6 | "declarationDir": "lib/types", 7 | "target": "ES2020", 8 | "module": "ES2020", 9 | "sourceMap": true, 10 | "declaration": true, 11 | "declarationMap": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /clients/js-legacy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.all.json", 3 | "include": ["src", "test"], 4 | "compilerOptions": { 5 | "noEmit": true, 6 | "skipLibCheck": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /clients/js-legacy/tsconfig.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "composite": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /clients/js-legacy/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "out": "docs", 4 | "readme": "README.md" 5 | } 6 | -------------------------------------------------------------------------------- /clients/js/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@solana/eslint-config-solana'], 3 | ignorePatterns: ['.eslintrc.cjs', 'tsup.config.ts', 'env-shim.ts'], 4 | parserOptions: { 5 | project: 'tsconfig.json', 6 | tsconfigRootDir: __dirname, 7 | sourceType: 'module', 8 | }, 9 | rules: { 10 | '@typescript-eslint/ban-types': 'off', 11 | '@typescript-eslint/sort-type-constituents': 'off', 12 | 'prefer-destructuring': 'off', 13 | 'simple-import-sort/imports': 'off', 14 | 'sort-keys-fix/sort-keys-fix': 'off', 15 | 'typescript-sort-keys/interface': 'off', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /clients/js/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | docs 3 | -------------------------------------------------------------------------------- /clients/js/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "always", 8 | "printWidth": 80 9 | } 10 | -------------------------------------------------------------------------------- /clients/js/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript client 2 | 3 | A generated JavaScript library for the Memo program. 4 | 5 | ## Getting started 6 | 7 | To build and test your JavaScript client from the root of the repository, you may use the following command. 8 | 9 | ```sh 10 | pnpm clients:js:test 11 | ``` 12 | 13 | This will start a new local validator, if one is not already running, and run the tests for your JavaScript client. 14 | 15 | ## Available client scripts. 16 | 17 | Alternatively, you can go into the client directory and run the tests directly. 18 | 19 | ```sh 20 | # Build your programs and start the validator. 21 | pnpm programs:build 22 | pnpm validator:restart 23 | 24 | # Go into the client directory and run the tests. 25 | cd clients/js 26 | pnpm install 27 | pnpm build 28 | pnpm test 29 | ``` 30 | 31 | You may also use the following scripts to lint and/or format your JavaScript client. 32 | 33 | ```sh 34 | pnpm lint 35 | pnpm lint:fix 36 | pnpm format 37 | pnpm format:fix 38 | ``` 39 | -------------------------------------------------------------------------------- /clients/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@solana-program/memo", 3 | "version": "0.7.0", 4 | "description": "JavaScript client for the Memo program", 5 | "sideEffects": false, 6 | "module": "./dist/src/index.mjs", 7 | "main": "./dist/src/index.js", 8 | "types": "./dist/types/index.d.ts", 9 | "type": "commonjs", 10 | "exports": { 11 | ".": { 12 | "types": "./dist/types/index.d.ts", 13 | "import": "./dist/src/index.mjs", 14 | "require": "./dist/src/index.js" 15 | } 16 | }, 17 | "files": [ 18 | "./dist/src", 19 | "./dist/types" 20 | ], 21 | "scripts": { 22 | "build": "rimraf dist && tsup && tsc -p ./tsconfig.declarations.json", 23 | "build:docs": "typedoc", 24 | "test": "ava", 25 | "lint": "eslint --ext js,ts,tsx src", 26 | "lint:fix": "eslint --fix --ext js,ts,tsx src", 27 | "format": "prettier --check src test", 28 | "format:fix": "prettier --write src test", 29 | "prepublishOnly": "pnpm build" 30 | }, 31 | "publishConfig": { 32 | "access": "public", 33 | "registry": "https://registry.npmjs.org" 34 | }, 35 | "license": "Apache-2.0", 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/solana-program/memo.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/solana-program/memo/issues" 42 | }, 43 | "homepage": "https://github.com/solana-program/memo#readme", 44 | "peerDependencies": { 45 | "@solana/kit": "^2.1.0" 46 | }, 47 | "devDependencies": { 48 | "@ava/typescript": "^4.1.0", 49 | "@solana/eslint-config-solana": "^3.0.3", 50 | "@solana/kit": "^2.1.0", 51 | "@types/node": "^20", 52 | "@typescript-eslint/eslint-plugin": "^7.16.1", 53 | "@typescript-eslint/parser": "^7.16.1", 54 | "ava": "^6.1.3", 55 | "eslint": "^8.57.0", 56 | "prettier": "^3.3.3", 57 | "rimraf": "^5.0.5", 58 | "tsup": "^8.1.2", 59 | "typedoc": "^0.25.12", 60 | "typescript": "^5.5.3" 61 | }, 62 | "ava": { 63 | "nodeArguments": [ 64 | "--no-warnings" 65 | ], 66 | "typescript": { 67 | "compile": false, 68 | "rewritePaths": { 69 | "test/": "dist/test/" 70 | } 71 | } 72 | }, 73 | "packageManager": "pnpm@9.1.0" 74 | } 75 | -------------------------------------------------------------------------------- /clients/js/src/generated/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the codama library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun codama to update it. 5 | * 6 | * @see https://github.com/codama-idl/codama 7 | */ 8 | 9 | export * from './instructions'; 10 | export * from './programs'; 11 | -------------------------------------------------------------------------------- /clients/js/src/generated/instructions/addMemo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the codama library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun codama to update it. 5 | * 6 | * @see https://github.com/codama-idl/codama 7 | */ 8 | 9 | import { 10 | AccountRole, 11 | combineCodec, 12 | getStructDecoder, 13 | getStructEncoder, 14 | getUtf8Decoder, 15 | getUtf8Encoder, 16 | type Address, 17 | type Codec, 18 | type Decoder, 19 | type Encoder, 20 | type IAccountMeta, 21 | type IInstruction, 22 | type IInstructionWithAccounts, 23 | type IInstructionWithData, 24 | type TransactionSigner, 25 | } from '@solana/kit'; 26 | import { MEMO_PROGRAM_ADDRESS } from '../programs'; 27 | 28 | export type AddMemoInstruction< 29 | TProgram extends string = typeof MEMO_PROGRAM_ADDRESS, 30 | TRemainingAccounts extends readonly IAccountMeta[] = [], 31 | > = IInstruction & 32 | IInstructionWithData & 33 | IInstructionWithAccounts; 34 | 35 | export type AddMemoInstructionData = { memo: string }; 36 | 37 | export type AddMemoInstructionDataArgs = AddMemoInstructionData; 38 | 39 | export function getAddMemoInstructionDataEncoder(): Encoder { 40 | return getStructEncoder([['memo', getUtf8Encoder()]]); 41 | } 42 | 43 | export function getAddMemoInstructionDataDecoder(): Decoder { 44 | return getStructDecoder([['memo', getUtf8Decoder()]]); 45 | } 46 | 47 | export function getAddMemoInstructionDataCodec(): Codec< 48 | AddMemoInstructionDataArgs, 49 | AddMemoInstructionData 50 | > { 51 | return combineCodec( 52 | getAddMemoInstructionDataEncoder(), 53 | getAddMemoInstructionDataDecoder() 54 | ); 55 | } 56 | 57 | export type AddMemoInput = { 58 | memo: AddMemoInstructionDataArgs['memo']; 59 | signers?: Array; 60 | }; 61 | 62 | export function getAddMemoInstruction< 63 | TProgramAddress extends Address = typeof MEMO_PROGRAM_ADDRESS, 64 | >( 65 | input: AddMemoInput, 66 | config?: { programAddress?: TProgramAddress } 67 | ): AddMemoInstruction { 68 | // Program address. 69 | const programAddress = config?.programAddress ?? MEMO_PROGRAM_ADDRESS; 70 | 71 | // Original args. 72 | const args = { ...input }; 73 | 74 | // Remaining accounts. 75 | const remainingAccounts: IAccountMeta[] = (args.signers ?? []).map( 76 | (signer) => ({ 77 | address: signer.address, 78 | role: AccountRole.READONLY_SIGNER, 79 | signer, 80 | }) 81 | ); 82 | 83 | const instruction = { 84 | accounts: remainingAccounts, 85 | programAddress, 86 | data: getAddMemoInstructionDataEncoder().encode( 87 | args as AddMemoInstructionDataArgs 88 | ), 89 | } as AddMemoInstruction; 90 | 91 | return instruction; 92 | } 93 | 94 | export type ParsedAddMemoInstruction< 95 | TProgram extends string = typeof MEMO_PROGRAM_ADDRESS, 96 | > = { 97 | programAddress: Address; 98 | data: AddMemoInstructionData; 99 | }; 100 | 101 | export function parseAddMemoInstruction( 102 | instruction: IInstruction & IInstructionWithData 103 | ): ParsedAddMemoInstruction { 104 | return { 105 | programAddress: instruction.programAddress, 106 | data: getAddMemoInstructionDataDecoder().decode(instruction.data), 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /clients/js/src/generated/instructions/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the codama library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun codama to update it. 5 | * 6 | * @see https://github.com/codama-idl/codama 7 | */ 8 | 9 | export * from './addMemo'; 10 | -------------------------------------------------------------------------------- /clients/js/src/generated/programs/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the codama library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun codama to update it. 5 | * 6 | * @see https://github.com/codama-idl/codama 7 | */ 8 | 9 | export * from './memo'; 10 | -------------------------------------------------------------------------------- /clients/js/src/generated/programs/memo.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the codama library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun codama to update it. 5 | * 6 | * @see https://github.com/codama-idl/codama 7 | */ 8 | 9 | import { type Address } from '@solana/kit'; 10 | import { type ParsedAddMemoInstruction } from '../instructions'; 11 | 12 | export const MEMO_PROGRAM_ADDRESS = 13 | 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr' as Address<'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr'>; 14 | 15 | export enum MemoInstruction { 16 | AddMemo, 17 | } 18 | 19 | export type ParsedMemoInstruction< 20 | TProgram extends string = 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', 21 | > = { 22 | instructionType: MemoInstruction.AddMemo; 23 | } & ParsedAddMemoInstruction; 24 | -------------------------------------------------------------------------------- /clients/js/src/generated/shared/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was AUTOGENERATED using the codama library. 3 | * Please DO NOT EDIT THIS FILE, instead use visitors 4 | * to add features, then rerun codama to update it. 5 | * 6 | * @see https://github.com/codama-idl/codama 7 | */ 8 | 9 | import { 10 | AccountRole, 11 | isProgramDerivedAddress, 12 | isTransactionSigner as kitIsTransactionSigner, 13 | type Address, 14 | type IAccountMeta, 15 | type IAccountSignerMeta, 16 | type ProgramDerivedAddress, 17 | type TransactionSigner, 18 | upgradeRoleToSigner, 19 | } from '@solana/kit'; 20 | 21 | /** 22 | * Asserts that the given value is not null or undefined. 23 | * @internal 24 | */ 25 | export function expectSome(value: T | null | undefined): T { 26 | if (value == null) { 27 | throw new Error('Expected a value but received null or undefined.'); 28 | } 29 | return value; 30 | } 31 | 32 | /** 33 | * Asserts that the given value is a PublicKey. 34 | * @internal 35 | */ 36 | export function expectAddress( 37 | value: 38 | | Address 39 | | ProgramDerivedAddress 40 | | TransactionSigner 41 | | null 42 | | undefined 43 | ): Address { 44 | if (!value) { 45 | throw new Error('Expected a Address.'); 46 | } 47 | if (typeof value === 'object' && 'address' in value) { 48 | return value.address; 49 | } 50 | if (Array.isArray(value)) { 51 | return value[0]; 52 | } 53 | return value as Address; 54 | } 55 | 56 | /** 57 | * Asserts that the given value is a PDA. 58 | * @internal 59 | */ 60 | export function expectProgramDerivedAddress( 61 | value: 62 | | Address 63 | | ProgramDerivedAddress 64 | | TransactionSigner 65 | | null 66 | | undefined 67 | ): ProgramDerivedAddress { 68 | if (!value || !Array.isArray(value) || !isProgramDerivedAddress(value)) { 69 | throw new Error('Expected a ProgramDerivedAddress.'); 70 | } 71 | return value; 72 | } 73 | 74 | /** 75 | * Asserts that the given value is a TransactionSigner. 76 | * @internal 77 | */ 78 | export function expectTransactionSigner( 79 | value: 80 | | Address 81 | | ProgramDerivedAddress 82 | | TransactionSigner 83 | | null 84 | | undefined 85 | ): TransactionSigner { 86 | if (!value || !isTransactionSigner(value)) { 87 | throw new Error('Expected a TransactionSigner.'); 88 | } 89 | return value; 90 | } 91 | 92 | /** 93 | * Defines an instruction account to resolve. 94 | * @internal 95 | */ 96 | export type ResolvedAccount< 97 | T extends string = string, 98 | U extends 99 | | Address 100 | | ProgramDerivedAddress 101 | | TransactionSigner 102 | | null = 103 | | Address 104 | | ProgramDerivedAddress 105 | | TransactionSigner 106 | | null, 107 | > = { 108 | isWritable: boolean; 109 | value: U; 110 | }; 111 | 112 | /** 113 | * Defines an instruction that stores additional bytes on-chain. 114 | * @internal 115 | */ 116 | export type IInstructionWithByteDelta = { 117 | byteDelta: number; 118 | }; 119 | 120 | /** 121 | * Get account metas and signers from resolved accounts. 122 | * @internal 123 | */ 124 | export function getAccountMetaFactory( 125 | programAddress: Address, 126 | optionalAccountStrategy: 'omitted' | 'programId' 127 | ) { 128 | return ( 129 | account: ResolvedAccount 130 | ): IAccountMeta | IAccountSignerMeta | undefined => { 131 | if (!account.value) { 132 | if (optionalAccountStrategy === 'omitted') return; 133 | return Object.freeze({ 134 | address: programAddress, 135 | role: AccountRole.READONLY, 136 | }); 137 | } 138 | 139 | const writableRole = account.isWritable 140 | ? AccountRole.WRITABLE 141 | : AccountRole.READONLY; 142 | return Object.freeze({ 143 | address: expectAddress(account.value), 144 | role: isTransactionSigner(account.value) 145 | ? upgradeRoleToSigner(writableRole) 146 | : writableRole, 147 | ...(isTransactionSigner(account.value) ? { signer: account.value } : {}), 148 | }); 149 | }; 150 | } 151 | 152 | export function isTransactionSigner( 153 | value: 154 | | Address 155 | | ProgramDerivedAddress 156 | | TransactionSigner 157 | ): value is TransactionSigner { 158 | return ( 159 | !!value && 160 | typeof value === 'object' && 161 | 'address' in value && 162 | kitIsTransactionSigner(value) 163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /clients/js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generated'; 2 | -------------------------------------------------------------------------------- /clients/js/test/_setup.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Commitment, 3 | CompilableTransactionMessage, 4 | Rpc, 5 | RpcSubscriptions, 6 | SolanaRpcApi, 7 | SolanaRpcSubscriptionsApi, 8 | TransactionMessageWithBlockhashLifetime, 9 | TransactionSigner, 10 | airdropFactory, 11 | createSolanaRpc, 12 | createSolanaRpcSubscriptions, 13 | createTransactionMessage, 14 | generateKeyPairSigner, 15 | getSignatureFromTransaction, 16 | lamports, 17 | pipe, 18 | sendAndConfirmTransactionFactory, 19 | setTransactionMessageFeePayerSigner, 20 | setTransactionMessageLifetimeUsingBlockhash, 21 | signTransactionMessageWithSigners, 22 | } from '@solana/kit'; 23 | 24 | type Client = { 25 | rpc: Rpc; 26 | rpcSubscriptions: RpcSubscriptions; 27 | }; 28 | 29 | export const createDefaultSolanaClient = (): Client => { 30 | const rpc = createSolanaRpc('http://127.0.0.1:8899'); 31 | const rpcSubscriptions = createSolanaRpcSubscriptions('ws://127.0.0.1:8900'); 32 | return { rpc, rpcSubscriptions }; 33 | }; 34 | 35 | export const generateKeyPairSignerWithSol = async ( 36 | client: Client, 37 | putativeLamports: bigint = 1_000_000_000n 38 | ) => { 39 | const signer = await generateKeyPairSigner(); 40 | await airdropFactory(client)({ 41 | recipientAddress: signer.address, 42 | lamports: lamports(putativeLamports), 43 | commitment: 'confirmed', 44 | }); 45 | return signer; 46 | }; 47 | 48 | export const createDefaultTransaction = async ( 49 | client: Client, 50 | feePayer: TransactionSigner 51 | ) => { 52 | const { value: latestBlockhash } = await client.rpc 53 | .getLatestBlockhash() 54 | .send(); 55 | return pipe( 56 | createTransactionMessage({ version: 0 }), 57 | (tx) => setTransactionMessageFeePayerSigner(feePayer, tx), 58 | (tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx) 59 | ); 60 | }; 61 | 62 | export const signAndSendTransaction = async ( 63 | client: Client, 64 | transactionMessage: CompilableTransactionMessage & 65 | TransactionMessageWithBlockhashLifetime, 66 | commitment: Commitment = 'confirmed' 67 | ) => { 68 | const signedTransaction = 69 | await signTransactionMessageWithSigners(transactionMessage); 70 | const signature = getSignatureFromTransaction(signedTransaction); 71 | await sendAndConfirmTransactionFactory(client)(signedTransaction, { 72 | commitment, 73 | }); 74 | return signature; 75 | }; 76 | -------------------------------------------------------------------------------- /clients/js/test/addMemo.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | appendTransactionMessageInstruction, 3 | getBase58Encoder, 4 | getUtf8Decoder, 5 | pipe, 6 | } from '@solana/kit'; 7 | import test from 'ava'; 8 | import { getAddMemoInstruction } from '../src'; 9 | import { 10 | createDefaultSolanaClient, 11 | createDefaultTransaction, 12 | generateKeyPairSignerWithSol, 13 | signAndSendTransaction, 14 | } from './_setup'; 15 | 16 | test('it adds custom text to the transaction logs', async (t) => { 17 | // Given a payer wallet. 18 | const client = createDefaultSolanaClient(); 19 | const payer = await generateKeyPairSignerWithSol(client); 20 | 21 | // When we create a transaction with a custom memo. 22 | const addMemo = getAddMemoInstruction({ memo: 'Hello world!' }); 23 | const signature = await pipe( 24 | await createDefaultTransaction(client, payer), 25 | (tx) => appendTransactionMessageInstruction(addMemo, tx), 26 | async (tx) => signAndSendTransaction(client, tx) 27 | ); 28 | 29 | // Then the instruction data contains our memo. 30 | const result = await client.rpc 31 | .getTransaction(signature, { maxSupportedTransactionVersion: 0 }) 32 | .send(); 33 | const instructionDataBase58 = 34 | result!.transaction.message.instructions[0].data; 35 | const instructionDataBytes = getBase58Encoder().encode(instructionDataBase58); 36 | const instructionMemo = getUtf8Decoder().decode(instructionDataBytes); 37 | t.is(instructionMemo, 'Hello world!'); 38 | }); 39 | -------------------------------------------------------------------------------- /clients/js/tsconfig.declarations.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "emitDeclarationOnly": true, 6 | "outDir": "./dist/types", 7 | }, 8 | "extends": "./tsconfig.json", 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /clients/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "composite": false, 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "inlineSources": false, 10 | "isolatedModules": true, 11 | "module": "ESNext", 12 | "moduleResolution": "node", 13 | "noFallthroughCasesInSwitch": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "outDir": "./dist", 17 | "preserveWatchOutput": true, 18 | "skipLibCheck": true, 19 | "strict": true, 20 | "target": "ESNext" 21 | }, 22 | "exclude": ["node_modules"], 23 | "include": ["src", "test"] 24 | } 25 | -------------------------------------------------------------------------------- /clients/js/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'node:process'; 2 | import { defineConfig, Options } from 'tsup'; 3 | 4 | const SHARED_OPTIONS: Options = { 5 | define: { __VERSION__: `"${env.npm_package_version}"` }, 6 | entry: ['./src/index.ts'], 7 | outDir: './dist/src', 8 | outExtension: ({ format }) => ({ js: format === 'cjs' ? '.js' : '.mjs' }), 9 | sourcemap: true, 10 | treeshake: true, 11 | }; 12 | 13 | export default defineConfig(() => [ 14 | // Source. 15 | { ...SHARED_OPTIONS, format: 'cjs' }, 16 | { ...SHARED_OPTIONS, format: 'esm' }, 17 | 18 | // Tests. 19 | { 20 | ...SHARED_OPTIONS, 21 | bundle: false, 22 | entry: ['./test/**/*.ts'], 23 | format: 'cjs', 24 | outDir: './dist/test', 25 | }, 26 | ]); 27 | -------------------------------------------------------------------------------- /clients/js/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "includeVersion": true, 4 | "readme": "none", 5 | "out": "docs" 6 | } 7 | -------------------------------------------------------------------------------- /clients/rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spl-memo-client" 3 | version = "0.1.0" 4 | description = "A generated Rust library for the Memo program" 5 | repository = "https://github.com/solana-program/memo" 6 | edition = "2021" 7 | readme = "README.md" 8 | license-file = "../../LICENSE" 9 | 10 | [features] 11 | test-sbf = [] 12 | serde = ["dep:serde", "dep:serde_with"] 13 | 14 | [dependencies] 15 | borsh = "^0.10" 16 | kaigan = "0.2.6" 17 | num-derive = "^0.4" 18 | num-traits = "^0.2" 19 | serde = { version = "^1.0", features = ["derive"], optional = true } 20 | serde_with = { version = "^3.12", optional = true } 21 | solana-program = "2.2.1" 22 | thiserror = "^2.0" 23 | 24 | [dev-dependencies] 25 | assert_matches = "1.5.0" 26 | solana-program-test = "2.2.7" 27 | solana-sdk = "2.2.1" 28 | -------------------------------------------------------------------------------- /clients/rust/README.md: -------------------------------------------------------------------------------- 1 | # Rust client 2 | 3 | A generated Rust library for the Memo program. 4 | 5 | ## Getting started 6 | 7 | To build and test your Rust client from the root of the repository, you may use the following command. 8 | 9 | ```sh 10 | pnpm clients:js:test 11 | ``` 12 | 13 | This will start a new local validator, if one is not already running, and run the tests for your Rust client. 14 | -------------------------------------------------------------------------------- /clients/rust/src/generated/errors/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the codama library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun codama to update it. 4 | //! 5 | //! 6 | //! 7 | -------------------------------------------------------------------------------- /clients/rust/src/generated/instructions/add_memo.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the codama library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun codama to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use borsh::BorshDeserialize; 9 | use borsh::BorshSerialize; 10 | use kaigan::types::RemainderStr; 11 | 12 | /// Accounts. 13 | #[derive(Debug)] 14 | pub struct AddMemo {} 15 | 16 | impl AddMemo { 17 | pub fn instruction( 18 | &self, 19 | args: AddMemoInstructionArgs, 20 | ) -> solana_program::instruction::Instruction { 21 | self.instruction_with_remaining_accounts(args, &[]) 22 | } 23 | #[allow(clippy::arithmetic_side_effects)] 24 | #[allow(clippy::vec_init_then_push)] 25 | pub fn instruction_with_remaining_accounts( 26 | &self, 27 | args: AddMemoInstructionArgs, 28 | remaining_accounts: &[solana_program::instruction::AccountMeta], 29 | ) -> solana_program::instruction::Instruction { 30 | let mut accounts = Vec::with_capacity(remaining_accounts.len()); 31 | accounts.extend_from_slice(remaining_accounts); 32 | let mut data = borsh::to_vec(&AddMemoInstructionData::new()).unwrap(); 33 | let mut args = borsh::to_vec(&args).unwrap(); 34 | data.append(&mut args); 35 | 36 | solana_program::instruction::Instruction { 37 | program_id: crate::MEMO_ID, 38 | accounts, 39 | data, 40 | } 41 | } 42 | } 43 | 44 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 45 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 46 | pub struct AddMemoInstructionData {} 47 | 48 | impl AddMemoInstructionData { 49 | pub fn new() -> Self { 50 | Self {} 51 | } 52 | } 53 | 54 | impl Default for AddMemoInstructionData { 55 | fn default() -> Self { 56 | Self::new() 57 | } 58 | } 59 | 60 | #[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] 61 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 62 | pub struct AddMemoInstructionArgs { 63 | pub memo: RemainderStr, 64 | } 65 | 66 | /// Instruction builder for `AddMemo`. 67 | /// 68 | /// ### Accounts: 69 | /// 70 | #[derive(Clone, Debug, Default)] 71 | pub struct AddMemoBuilder { 72 | memo: Option, 73 | __remaining_accounts: Vec, 74 | } 75 | 76 | impl AddMemoBuilder { 77 | pub fn new() -> Self { 78 | Self::default() 79 | } 80 | #[inline(always)] 81 | pub fn memo(&mut self, memo: RemainderStr) -> &mut Self { 82 | self.memo = Some(memo); 83 | self 84 | } 85 | /// Add an additional account to the instruction. 86 | #[inline(always)] 87 | pub fn add_remaining_account( 88 | &mut self, 89 | account: solana_program::instruction::AccountMeta, 90 | ) -> &mut Self { 91 | self.__remaining_accounts.push(account); 92 | self 93 | } 94 | /// Add additional accounts to the instruction. 95 | #[inline(always)] 96 | pub fn add_remaining_accounts( 97 | &mut self, 98 | accounts: &[solana_program::instruction::AccountMeta], 99 | ) -> &mut Self { 100 | self.__remaining_accounts.extend_from_slice(accounts); 101 | self 102 | } 103 | #[allow(clippy::clone_on_copy)] 104 | pub fn instruction(&self) -> solana_program::instruction::Instruction { 105 | let accounts = AddMemo {}; 106 | let args = AddMemoInstructionArgs { 107 | memo: self.memo.clone().expect("memo is not set"), 108 | }; 109 | 110 | accounts.instruction_with_remaining_accounts(args, &self.__remaining_accounts) 111 | } 112 | } 113 | 114 | /// `add_memo` CPI instruction. 115 | pub struct AddMemoCpi<'a, 'b> { 116 | /// The program to invoke. 117 | pub __program: &'b solana_program::account_info::AccountInfo<'a>, 118 | /// The arguments for the instruction. 119 | pub __args: AddMemoInstructionArgs, 120 | } 121 | 122 | impl<'a, 'b> AddMemoCpi<'a, 'b> { 123 | pub fn new( 124 | program: &'b solana_program::account_info::AccountInfo<'a>, 125 | args: AddMemoInstructionArgs, 126 | ) -> Self { 127 | Self { 128 | __program: program, 129 | __args: args, 130 | } 131 | } 132 | #[inline(always)] 133 | pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { 134 | self.invoke_signed_with_remaining_accounts(&[], &[]) 135 | } 136 | #[inline(always)] 137 | pub fn invoke_with_remaining_accounts( 138 | &self, 139 | remaining_accounts: &[( 140 | &'b solana_program::account_info::AccountInfo<'a>, 141 | bool, 142 | bool, 143 | )], 144 | ) -> solana_program::entrypoint::ProgramResult { 145 | self.invoke_signed_with_remaining_accounts(&[], remaining_accounts) 146 | } 147 | #[inline(always)] 148 | pub fn invoke_signed( 149 | &self, 150 | signers_seeds: &[&[&[u8]]], 151 | ) -> solana_program::entrypoint::ProgramResult { 152 | self.invoke_signed_with_remaining_accounts(signers_seeds, &[]) 153 | } 154 | #[allow(clippy::arithmetic_side_effects)] 155 | #[allow(clippy::clone_on_copy)] 156 | #[allow(clippy::vec_init_then_push)] 157 | pub fn invoke_signed_with_remaining_accounts( 158 | &self, 159 | signers_seeds: &[&[&[u8]]], 160 | remaining_accounts: &[( 161 | &'b solana_program::account_info::AccountInfo<'a>, 162 | bool, 163 | bool, 164 | )], 165 | ) -> solana_program::entrypoint::ProgramResult { 166 | let mut accounts = Vec::with_capacity(remaining_accounts.len()); 167 | remaining_accounts.iter().for_each(|remaining_account| { 168 | accounts.push(solana_program::instruction::AccountMeta { 169 | pubkey: *remaining_account.0.key, 170 | is_signer: remaining_account.1, 171 | is_writable: remaining_account.2, 172 | }) 173 | }); 174 | let mut data = borsh::to_vec(&AddMemoInstructionData::new()).unwrap(); 175 | let mut args = borsh::to_vec(&self.__args).unwrap(); 176 | data.append(&mut args); 177 | 178 | let instruction = solana_program::instruction::Instruction { 179 | program_id: crate::MEMO_ID, 180 | accounts, 181 | data, 182 | }; 183 | let mut account_infos = Vec::with_capacity(1 + remaining_accounts.len()); 184 | account_infos.push(self.__program.clone()); 185 | remaining_accounts 186 | .iter() 187 | .for_each(|remaining_account| account_infos.push(remaining_account.0.clone())); 188 | 189 | if signers_seeds.is_empty() { 190 | solana_program::program::invoke(&instruction, &account_infos) 191 | } else { 192 | solana_program::program::invoke_signed(&instruction, &account_infos, signers_seeds) 193 | } 194 | } 195 | } 196 | 197 | /// Instruction builder for `AddMemo` via CPI. 198 | /// 199 | /// ### Accounts: 200 | /// 201 | #[derive(Clone, Debug)] 202 | pub struct AddMemoCpiBuilder<'a, 'b> { 203 | instruction: Box>, 204 | } 205 | 206 | impl<'a, 'b> AddMemoCpiBuilder<'a, 'b> { 207 | pub fn new(program: &'b solana_program::account_info::AccountInfo<'a>) -> Self { 208 | let instruction = Box::new(AddMemoCpiBuilderInstruction { 209 | __program: program, 210 | memo: None, 211 | __remaining_accounts: Vec::new(), 212 | }); 213 | Self { instruction } 214 | } 215 | #[inline(always)] 216 | pub fn memo(&mut self, memo: RemainderStr) -> &mut Self { 217 | self.instruction.memo = Some(memo); 218 | self 219 | } 220 | /// Add an additional account to the instruction. 221 | #[inline(always)] 222 | pub fn add_remaining_account( 223 | &mut self, 224 | account: &'b solana_program::account_info::AccountInfo<'a>, 225 | is_writable: bool, 226 | is_signer: bool, 227 | ) -> &mut Self { 228 | self.instruction 229 | .__remaining_accounts 230 | .push((account, is_writable, is_signer)); 231 | self 232 | } 233 | /// Add additional accounts to the instruction. 234 | /// 235 | /// Each account is represented by a tuple of the `AccountInfo`, a `bool` indicating whether the account is writable or not, 236 | /// and a `bool` indicating whether the account is a signer or not. 237 | #[inline(always)] 238 | pub fn add_remaining_accounts( 239 | &mut self, 240 | accounts: &[( 241 | &'b solana_program::account_info::AccountInfo<'a>, 242 | bool, 243 | bool, 244 | )], 245 | ) -> &mut Self { 246 | self.instruction 247 | .__remaining_accounts 248 | .extend_from_slice(accounts); 249 | self 250 | } 251 | #[inline(always)] 252 | pub fn invoke(&self) -> solana_program::entrypoint::ProgramResult { 253 | self.invoke_signed(&[]) 254 | } 255 | #[allow(clippy::clone_on_copy)] 256 | #[allow(clippy::vec_init_then_push)] 257 | pub fn invoke_signed( 258 | &self, 259 | signers_seeds: &[&[&[u8]]], 260 | ) -> solana_program::entrypoint::ProgramResult { 261 | let args = AddMemoInstructionArgs { 262 | memo: self.instruction.memo.clone().expect("memo is not set"), 263 | }; 264 | let instruction = AddMemoCpi { 265 | __program: self.instruction.__program, 266 | __args: args, 267 | }; 268 | instruction.invoke_signed_with_remaining_accounts( 269 | signers_seeds, 270 | &self.instruction.__remaining_accounts, 271 | ) 272 | } 273 | } 274 | 275 | #[derive(Clone, Debug)] 276 | struct AddMemoCpiBuilderInstruction<'a, 'b> { 277 | __program: &'b solana_program::account_info::AccountInfo<'a>, 278 | memo: Option, 279 | /// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`. 280 | __remaining_accounts: Vec<( 281 | &'b solana_program::account_info::AccountInfo<'a>, 282 | bool, 283 | bool, 284 | )>, 285 | } 286 | -------------------------------------------------------------------------------- /clients/rust/src/generated/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the codama library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun codama to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | pub(crate) mod r#add_memo; 9 | 10 | pub use self::r#add_memo::*; 11 | -------------------------------------------------------------------------------- /clients/rust/src/generated/mod.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the codama library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun codama to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | pub mod errors; 9 | pub mod instructions; 10 | pub mod programs; 11 | 12 | pub(crate) use programs::*; 13 | -------------------------------------------------------------------------------- /clients/rust/src/generated/programs.rs: -------------------------------------------------------------------------------- 1 | //! This code was AUTOGENERATED using the codama library. 2 | //! Please DO NOT EDIT THIS FILE, instead use visitors 3 | //! to add features, then rerun codama to update it. 4 | //! 5 | //! 6 | //! 7 | 8 | use solana_program::{pubkey, pubkey::Pubkey}; 9 | 10 | /// `memo` program ID. 11 | pub const MEMO_ID: Pubkey = pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); 12 | -------------------------------------------------------------------------------- /clients/rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod generated; 2 | 3 | pub use generated::programs::MEMO_ID as ID; 4 | pub use generated::*; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "programs:build": "zx ./scripts/program/build.mjs", 5 | "programs:test": "zx ./scripts/program/test.mjs", 6 | "programs:format": "zx ./scripts/program/format.mjs", 7 | "programs:lint": "zx ./scripts/program/lint.mjs", 8 | "solana:check": "zx ./scripts/check-solana-version.mjs", 9 | "solana:link": "zx ./scripts/link-solana-version.mjs", 10 | "generate": "pnpm generate:clients", 11 | "generate:clients": "zx ./scripts/generate-clients.mjs", 12 | "validator:start": "zx ./scripts/start-validator.mjs", 13 | "validator:restart": "pnpm validator:start --restart", 14 | "validator:stop": "zx ./scripts/stop-validator.mjs", 15 | "clients:js:format": "zx ./scripts/client/format-js.mjs", 16 | "clients:js:lint": "zx ./scripts/client/lint-js.mjs", 17 | "clients:js:publish": "zx ./scripts/client/publish-js.mjs", 18 | "clients:js:test": "zx ./scripts/client/test-js.mjs", 19 | "clients:rust:format": "zx ./scripts/client/format-rust.mjs", 20 | "clients:rust:lint": "zx ./scripts/client/lint-rust.mjs", 21 | "clients:rust:publish": "zx ./scripts/client/publish-rust.mjs", 22 | "clients:rust:test": "zx ./scripts/client/test-rust.mjs", 23 | "template:upgrade": "zx ./scripts/upgrade-template.mjs", 24 | "rust:spellcheck": "cargo spellcheck --code 1", 25 | "rust:audit": "zx ./scripts/audit-rust.mjs", 26 | "rust:semver": "cargo semver-checks" 27 | }, 28 | "devDependencies": { 29 | "@codama/renderers-js": "^1.2.14", 30 | "@codama/renderers-rust": "^1.0.22", 31 | "@iarna/toml": "^2.2.5", 32 | "codama": "^1.3.0", 33 | "typescript": "^5.8.3", 34 | "zx": "^8.5.5" 35 | }, 36 | "engines": { 37 | "node": ">=v20.0.0" 38 | }, 39 | "packageManager": "pnpm@9.1.0" 40 | } 41 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@codama/renderers-js': 12 | specifier: ^1.2.14 13 | version: 1.2.14(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3) 14 | '@codama/renderers-rust': 15 | specifier: ^1.0.22 16 | version: 1.0.22(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3) 17 | '@iarna/toml': 18 | specifier: ^2.2.5 19 | version: 2.2.5 20 | codama: 21 | specifier: ^1.3.0 22 | version: 1.3.0 23 | typescript: 24 | specifier: ^5.8.3 25 | version: 5.8.3 26 | zx: 27 | specifier: ^8.5.5 28 | version: 8.5.5 29 | 30 | packages: 31 | 32 | '@codama/errors@1.3.0': 33 | resolution: {integrity: sha512-pYp/gOi1c/9btrybWvH2iK5jj7SOpMmyLm9EqbxrivV+lqzfEogU8IG+BLch9sqnuhPgg/PzCaB4sYd438UC/Q==} 34 | hasBin: true 35 | 36 | '@codama/node-types@1.3.0': 37 | resolution: {integrity: sha512-3dPbMXR/QgKUqXMMxc6PUi2f8Dq6eZ9P/v6+ChTQFLGhSQmqC1PsCbL77PuM7mf4IK9np1RXFqbC/EVpgLvbgA==} 38 | 39 | '@codama/nodes@1.3.0': 40 | resolution: {integrity: sha512-Spf+Whm4jBLFxGPtJuDGmteGe+avoIDnh6rsByU1iJlYmcJJLjZayexFkvW8+1IeDclUMPQYBSj6SjuQEItLqQ==} 41 | 42 | '@codama/renderers-core@1.0.16': 43 | resolution: {integrity: sha512-IJshH6JsX7GUaYmC6KlOd5pLLyW1Yqd0I8B0pVWvvv9BfPNosC4t4RfusHSkYQePkIvs7CJ7YlwUywwW36Vt8A==} 44 | 45 | '@codama/renderers-js@1.2.14': 46 | resolution: {integrity: sha512-KukxwlX5iJpMBMRsiVY6vrmMipw8MbgHdNO3WFkNV1hO2F/ykhZfdHtZVEVL0Z/dNCBNfExLpign00teneCIoA==} 47 | 48 | '@codama/renderers-rust@1.0.22': 49 | resolution: {integrity: sha512-ovK1UyFYt/oWW8W2B6ghe/nc1kvITakVQqVIlK21l2VHGo6yEFcdWvBRYn5VXMuwW0nMwPuC7Hv0eZmGLRR7xg==} 50 | 51 | '@codama/validators@1.3.0': 52 | resolution: {integrity: sha512-s6rG3fJSvS7zc+fQ5yJN1NHlGQiut+P3Bpi5sQcEikjdWDDezu4OOgkxTh8ksTsCu8Rsif2hVzFYDLMcKjNrag==} 53 | 54 | '@codama/visitors-core@1.3.0': 55 | resolution: {integrity: sha512-Lldy0aOc882QYDa1IhjXhwpDsQE7oirBaebRddggXYFQs4+cvFROibHXBqG2npHPvQM4Mot6dJHQqffB/QL4iQ==} 56 | 57 | '@codama/visitors@1.3.0': 58 | resolution: {integrity: sha512-xcFae6NwC6Omr9tDm6P8rF3IDm5jWe2VMPomixaAc7+mpRhAWLbsg+FYCi0E09NvADo4C1tGo8U/SsvZLATUsQ==} 59 | 60 | '@iarna/toml@2.2.5': 61 | resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} 62 | 63 | '@solana/codecs-core@2.0.0-rc.4': 64 | resolution: {integrity: sha512-JIrTSps032mSE3wBxW3bXOqWfoy4CMy1CX/XeVCijyh5kLVxZTSDIdRTYdePdL1yzaOZF1Xysvt1DhOUgBdM+A==} 65 | engines: {node: '>=20.18.0'} 66 | peerDependencies: 67 | typescript: '>=5' 68 | 69 | '@solana/codecs-core@2.1.1': 70 | resolution: {integrity: sha512-iPQW3UZ2Vi7QFBo2r9tw0NubtH8EdrhhmZulx6lC8V5a+qjaxovtM/q/UW2BTNpqqHLfO0tIcLyBLrNH4HTWPg==} 71 | engines: {node: '>=20.18.0'} 72 | peerDependencies: 73 | typescript: '>=5.3.3' 74 | 75 | '@solana/codecs-numbers@2.0.0-rc.4': 76 | resolution: {integrity: sha512-ZJR7TaUO65+3Hzo3YOOUCS0wlzh17IW+j0MZC2LCk1R0woaypRpHKj4iSMYeQOZkMxsd9QT3WNvjFrPC2qA6Sw==} 77 | engines: {node: '>=20.18.0'} 78 | peerDependencies: 79 | typescript: '>=5' 80 | 81 | '@solana/codecs-numbers@2.1.1': 82 | resolution: {integrity: sha512-m20IUPJhPUmPkHSlZ2iMAjJ7PaYUvlMtFhCQYzm9BEBSI6OCvXTG3GAPpAnSGRBfg5y+QNqqmKn4QHU3B6zzCQ==} 83 | engines: {node: '>=20.18.0'} 84 | peerDependencies: 85 | typescript: '>=5.3.3' 86 | 87 | '@solana/codecs-strings@2.0.0-rc.4': 88 | resolution: {integrity: sha512-LGfK2RL0BKjYYUfzu2FG/gTgCsYOMz9FKVs2ntji6WneZygPxJTV5W98K3J8Rl0JewpCSCFQH3xjLSHBJUS0fA==} 89 | engines: {node: '>=20.18.0'} 90 | peerDependencies: 91 | fastestsmallesttextencoderdecoder: ^1.0.22 92 | typescript: '>=5' 93 | 94 | '@solana/codecs-strings@2.1.1': 95 | resolution: {integrity: sha512-uhj+A7eT6IJn4nuoX8jDdvZa7pjyZyN+k64EZ8+aUtJGt5Ft4NjRM8Jl5LljwYBWKQCgouVuigZHtTO2yAWExA==} 96 | engines: {node: '>=20.18.0'} 97 | peerDependencies: 98 | fastestsmallesttextencoderdecoder: ^1.0.22 99 | typescript: '>=5.3.3' 100 | 101 | '@solana/errors@2.0.0-rc.4': 102 | resolution: {integrity: sha512-0PPaMyB81keEHG/1pnyEuiBVKctbXO641M2w3CIOrYT/wzjunfF0FTxsqq9wYJeYo0AyiefCKGgSPs6wiY2PpQ==} 103 | engines: {node: '>=20.18.0'} 104 | hasBin: true 105 | peerDependencies: 106 | typescript: '>=5' 107 | 108 | '@solana/errors@2.1.1': 109 | resolution: {integrity: sha512-sj6DaWNbSJFvLzT8UZoabMefQUfSW/8tXK7NTiagsDmh+Q87eyQDDC9L3z+mNmx9b6dEf6z660MOIplDD2nfEw==} 110 | engines: {node: '>=20.18.0'} 111 | hasBin: true 112 | peerDependencies: 113 | typescript: '>=5.3.3' 114 | 115 | a-sync-waterfall@1.0.1: 116 | resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} 117 | 118 | asap@2.0.6: 119 | resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} 120 | 121 | call-bind-apply-helpers@1.0.2: 122 | resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} 123 | engines: {node: '>= 0.4'} 124 | 125 | call-bind@1.0.8: 126 | resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} 127 | engines: {node: '>= 0.4'} 128 | 129 | call-bound@1.0.4: 130 | resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 131 | engines: {node: '>= 0.4'} 132 | 133 | chalk@5.4.1: 134 | resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} 135 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 136 | 137 | codama@1.3.0: 138 | resolution: {integrity: sha512-rqnD0DRq7H3yJce3nDU1Kdy3Y/TsC/GirS4VazG7AEJ5UbR0fGIoThWWOpl2d5J1JmsaheY7APCY4PpnEZBoJA==} 139 | 140 | commander@12.1.0: 141 | resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} 142 | engines: {node: '>=18'} 143 | 144 | commander@13.1.0: 145 | resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} 146 | engines: {node: '>=18'} 147 | 148 | commander@5.1.0: 149 | resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} 150 | engines: {node: '>= 6'} 151 | 152 | define-data-property@1.1.4: 153 | resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 154 | engines: {node: '>= 0.4'} 155 | 156 | dunder-proto@1.0.1: 157 | resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} 158 | engines: {node: '>= 0.4'} 159 | 160 | es-define-property@1.0.1: 161 | resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} 162 | engines: {node: '>= 0.4'} 163 | 164 | es-errors@1.3.0: 165 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 166 | engines: {node: '>= 0.4'} 167 | 168 | es-object-atoms@1.1.1: 169 | resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} 170 | engines: {node: '>= 0.4'} 171 | 172 | fastestsmallesttextencoderdecoder@1.0.22: 173 | resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} 174 | 175 | function-bind@1.1.2: 176 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 177 | 178 | get-intrinsic@1.3.0: 179 | resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} 180 | engines: {node: '>= 0.4'} 181 | 182 | get-proto@1.0.1: 183 | resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} 184 | engines: {node: '>= 0.4'} 185 | 186 | gopd@1.2.0: 187 | resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} 188 | engines: {node: '>= 0.4'} 189 | 190 | has-property-descriptors@1.0.2: 191 | resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 192 | 193 | has-symbols@1.1.0: 194 | resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} 195 | engines: {node: '>= 0.4'} 196 | 197 | hasown@2.0.2: 198 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 199 | engines: {node: '>= 0.4'} 200 | 201 | isarray@2.0.5: 202 | resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} 203 | 204 | json-stable-stringify@1.3.0: 205 | resolution: {integrity: sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==} 206 | engines: {node: '>= 0.4'} 207 | 208 | jsonify@0.0.1: 209 | resolution: {integrity: sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==} 210 | 211 | math-intrinsics@1.1.0: 212 | resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} 213 | engines: {node: '>= 0.4'} 214 | 215 | nunjucks@3.2.4: 216 | resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} 217 | engines: {node: '>= 6.9.0'} 218 | hasBin: true 219 | peerDependencies: 220 | chokidar: ^3.3.0 221 | peerDependenciesMeta: 222 | chokidar: 223 | optional: true 224 | 225 | object-keys@1.1.1: 226 | resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 227 | engines: {node: '>= 0.4'} 228 | 229 | prettier@3.5.3: 230 | resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} 231 | engines: {node: '>=14'} 232 | hasBin: true 233 | 234 | set-function-length@1.2.2: 235 | resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 236 | engines: {node: '>= 0.4'} 237 | 238 | typescript@5.8.3: 239 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 240 | engines: {node: '>=14.17'} 241 | hasBin: true 242 | 243 | zx@8.5.5: 244 | resolution: {integrity: sha512-kzkjV3uqyEthw1IBDbA7Co2djji77vCP1DRvt58aYSMwiX4nyvAkFE8OBSEsOUbDJAst0Yo4asNvMTGG5HGPXA==} 245 | engines: {node: '>= 12.17.0'} 246 | hasBin: true 247 | 248 | snapshots: 249 | 250 | '@codama/errors@1.3.0': 251 | dependencies: 252 | '@codama/node-types': 1.3.0 253 | chalk: 5.4.1 254 | commander: 13.1.0 255 | 256 | '@codama/node-types@1.3.0': {} 257 | 258 | '@codama/nodes@1.3.0': 259 | dependencies: 260 | '@codama/errors': 1.3.0 261 | '@codama/node-types': 1.3.0 262 | 263 | '@codama/renderers-core@1.0.16': 264 | dependencies: 265 | '@codama/errors': 1.3.0 266 | '@codama/nodes': 1.3.0 267 | '@codama/visitors-core': 1.3.0 268 | 269 | '@codama/renderers-js@1.2.14(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)': 270 | dependencies: 271 | '@codama/errors': 1.3.0 272 | '@codama/nodes': 1.3.0 273 | '@codama/renderers-core': 1.0.16 274 | '@codama/visitors-core': 1.3.0 275 | '@solana/codecs-strings': 2.1.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3) 276 | nunjucks: 3.2.4 277 | prettier: 3.5.3 278 | transitivePeerDependencies: 279 | - chokidar 280 | - fastestsmallesttextencoderdecoder 281 | - typescript 282 | 283 | '@codama/renderers-rust@1.0.22(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)': 284 | dependencies: 285 | '@codama/errors': 1.3.0 286 | '@codama/nodes': 1.3.0 287 | '@codama/renderers-core': 1.0.16 288 | '@codama/visitors-core': 1.3.0 289 | '@solana/codecs-strings': 2.0.0-rc.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3) 290 | nunjucks: 3.2.4 291 | transitivePeerDependencies: 292 | - chokidar 293 | - fastestsmallesttextencoderdecoder 294 | - typescript 295 | 296 | '@codama/validators@1.3.0': 297 | dependencies: 298 | '@codama/errors': 1.3.0 299 | '@codama/nodes': 1.3.0 300 | '@codama/visitors-core': 1.3.0 301 | 302 | '@codama/visitors-core@1.3.0': 303 | dependencies: 304 | '@codama/errors': 1.3.0 305 | '@codama/nodes': 1.3.0 306 | json-stable-stringify: 1.3.0 307 | 308 | '@codama/visitors@1.3.0': 309 | dependencies: 310 | '@codama/errors': 1.3.0 311 | '@codama/nodes': 1.3.0 312 | '@codama/visitors-core': 1.3.0 313 | 314 | '@iarna/toml@2.2.5': {} 315 | 316 | '@solana/codecs-core@2.0.0-rc.4(typescript@5.8.3)': 317 | dependencies: 318 | '@solana/errors': 2.0.0-rc.4(typescript@5.8.3) 319 | typescript: 5.8.3 320 | 321 | '@solana/codecs-core@2.1.1(typescript@5.8.3)': 322 | dependencies: 323 | '@solana/errors': 2.1.1(typescript@5.8.3) 324 | typescript: 5.8.3 325 | 326 | '@solana/codecs-numbers@2.0.0-rc.4(typescript@5.8.3)': 327 | dependencies: 328 | '@solana/codecs-core': 2.0.0-rc.4(typescript@5.8.3) 329 | '@solana/errors': 2.0.0-rc.4(typescript@5.8.3) 330 | typescript: 5.8.3 331 | 332 | '@solana/codecs-numbers@2.1.1(typescript@5.8.3)': 333 | dependencies: 334 | '@solana/codecs-core': 2.1.1(typescript@5.8.3) 335 | '@solana/errors': 2.1.1(typescript@5.8.3) 336 | typescript: 5.8.3 337 | 338 | '@solana/codecs-strings@2.0.0-rc.4(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)': 339 | dependencies: 340 | '@solana/codecs-core': 2.0.0-rc.4(typescript@5.8.3) 341 | '@solana/codecs-numbers': 2.0.0-rc.4(typescript@5.8.3) 342 | '@solana/errors': 2.0.0-rc.4(typescript@5.8.3) 343 | fastestsmallesttextencoderdecoder: 1.0.22 344 | typescript: 5.8.3 345 | 346 | '@solana/codecs-strings@2.1.1(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.8.3)': 347 | dependencies: 348 | '@solana/codecs-core': 2.1.1(typescript@5.8.3) 349 | '@solana/codecs-numbers': 2.1.1(typescript@5.8.3) 350 | '@solana/errors': 2.1.1(typescript@5.8.3) 351 | fastestsmallesttextencoderdecoder: 1.0.22 352 | typescript: 5.8.3 353 | 354 | '@solana/errors@2.0.0-rc.4(typescript@5.8.3)': 355 | dependencies: 356 | chalk: 5.4.1 357 | commander: 12.1.0 358 | typescript: 5.8.3 359 | 360 | '@solana/errors@2.1.1(typescript@5.8.3)': 361 | dependencies: 362 | chalk: 5.4.1 363 | commander: 13.1.0 364 | typescript: 5.8.3 365 | 366 | a-sync-waterfall@1.0.1: {} 367 | 368 | asap@2.0.6: {} 369 | 370 | call-bind-apply-helpers@1.0.2: 371 | dependencies: 372 | es-errors: 1.3.0 373 | function-bind: 1.1.2 374 | 375 | call-bind@1.0.8: 376 | dependencies: 377 | call-bind-apply-helpers: 1.0.2 378 | es-define-property: 1.0.1 379 | get-intrinsic: 1.3.0 380 | set-function-length: 1.2.2 381 | 382 | call-bound@1.0.4: 383 | dependencies: 384 | call-bind-apply-helpers: 1.0.2 385 | get-intrinsic: 1.3.0 386 | 387 | chalk@5.4.1: {} 388 | 389 | codama@1.3.0: 390 | dependencies: 391 | '@codama/errors': 1.3.0 392 | '@codama/nodes': 1.3.0 393 | '@codama/validators': 1.3.0 394 | '@codama/visitors': 1.3.0 395 | 396 | commander@12.1.0: {} 397 | 398 | commander@13.1.0: {} 399 | 400 | commander@5.1.0: {} 401 | 402 | define-data-property@1.1.4: 403 | dependencies: 404 | es-define-property: 1.0.1 405 | es-errors: 1.3.0 406 | gopd: 1.2.0 407 | 408 | dunder-proto@1.0.1: 409 | dependencies: 410 | call-bind-apply-helpers: 1.0.2 411 | es-errors: 1.3.0 412 | gopd: 1.2.0 413 | 414 | es-define-property@1.0.1: {} 415 | 416 | es-errors@1.3.0: {} 417 | 418 | es-object-atoms@1.1.1: 419 | dependencies: 420 | es-errors: 1.3.0 421 | 422 | fastestsmallesttextencoderdecoder@1.0.22: {} 423 | 424 | function-bind@1.1.2: {} 425 | 426 | get-intrinsic@1.3.0: 427 | dependencies: 428 | call-bind-apply-helpers: 1.0.2 429 | es-define-property: 1.0.1 430 | es-errors: 1.3.0 431 | es-object-atoms: 1.1.1 432 | function-bind: 1.1.2 433 | get-proto: 1.0.1 434 | gopd: 1.2.0 435 | has-symbols: 1.1.0 436 | hasown: 2.0.2 437 | math-intrinsics: 1.1.0 438 | 439 | get-proto@1.0.1: 440 | dependencies: 441 | dunder-proto: 1.0.1 442 | es-object-atoms: 1.1.1 443 | 444 | gopd@1.2.0: {} 445 | 446 | has-property-descriptors@1.0.2: 447 | dependencies: 448 | es-define-property: 1.0.1 449 | 450 | has-symbols@1.1.0: {} 451 | 452 | hasown@2.0.2: 453 | dependencies: 454 | function-bind: 1.1.2 455 | 456 | isarray@2.0.5: {} 457 | 458 | json-stable-stringify@1.3.0: 459 | dependencies: 460 | call-bind: 1.0.8 461 | call-bound: 1.0.4 462 | isarray: 2.0.5 463 | jsonify: 0.0.1 464 | object-keys: 1.1.1 465 | 466 | jsonify@0.0.1: {} 467 | 468 | math-intrinsics@1.1.0: {} 469 | 470 | nunjucks@3.2.4: 471 | dependencies: 472 | a-sync-waterfall: 1.0.1 473 | asap: 2.0.6 474 | commander: 5.1.0 475 | 476 | object-keys@1.1.1: {} 477 | 478 | prettier@3.5.3: {} 479 | 480 | set-function-length@1.2.2: 481 | dependencies: 482 | define-data-property: 1.1.4 483 | es-errors: 1.3.0 484 | function-bind: 1.1.2 485 | get-intrinsic: 1.3.0 486 | gopd: 1.2.0 487 | has-property-descriptors: 1.0.2 488 | 489 | typescript@5.8.3: {} 490 | 491 | zx@8.5.5: {} 492 | -------------------------------------------------------------------------------- /program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spl-memo" 3 | version = "6.0.0" 4 | description = "Solana Program Library Memo" 5 | authors = ["Solana Labs Maintainers "] 6 | repository = "https://github.com/solana-labs/solana-program-library" 7 | license = "Apache-2.0" 8 | edition = "2021" 9 | 10 | [features] 11 | no-entrypoint = [] 12 | test-sbf = [] 13 | 14 | [dependencies] 15 | solana-account-info = "2.2.1" 16 | solana-instruction = "2.2.1" 17 | solana-msg = "2.2.1" 18 | solana-program-entrypoint = "2.2.1" 19 | solana-program-error = "2.2.2" 20 | solana-pubkey = "2.2.1" 21 | 22 | [dev-dependencies] 23 | solana-program-test = "2.2.7" 24 | solana-sdk = "2.2.1" 25 | 26 | [lib] 27 | crate-type = ["cdylib", "lib"] 28 | 29 | [package.metadata.docs.rs] 30 | targets = ["x86_64-unknown-linux-gnu"] 31 | 32 | [package.metadata.solana] 33 | program-id = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" 34 | -------------------------------------------------------------------------------- /program/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "rootNode", 3 | "program": { 4 | "kind": "programNode", 5 | "pdas": [], 6 | "accounts": [], 7 | "instructions": [ 8 | { 9 | "kind": "instructionNode", 10 | "accounts": [], 11 | "arguments": [ 12 | { 13 | "kind": "instructionArgumentNode", 14 | "name": "memo", 15 | "type": { "kind": "stringTypeNode", "encoding": "utf8" }, 16 | "docs": [] 17 | } 18 | ], 19 | "remainingAccounts": [ 20 | { 21 | "kind": "instructionRemainingAccountsNode", 22 | "value": { "kind": "argumentValueNode", "name": "signers" }, 23 | "isOptional": true, 24 | "isSigner": true 25 | } 26 | ], 27 | "name": "addMemo", 28 | "idlName": "addMemo", 29 | "docs": [], 30 | "optionalAccountStrategy": "programId" 31 | } 32 | ], 33 | "definedTypes": [], 34 | "errors": [], 35 | "name": "memo", 36 | "prefix": "", 37 | "publicKey": "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", 38 | "version": "3.0.1", 39 | "origin": "shank" 40 | }, 41 | "additionalPrograms": [], 42 | "standard": "codama", 43 | "version": "1.0.0" 44 | } 45 | -------------------------------------------------------------------------------- /program/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | //! Program entrypoint 2 | 3 | #![cfg(not(feature = "no-entrypoint"))] 4 | 5 | use { 6 | solana_account_info::AccountInfo, solana_program_entrypoint::ProgramResult, 7 | solana_pubkey::Pubkey, 8 | }; 9 | 10 | solana_program_entrypoint::entrypoint!(process_instruction); 11 | fn process_instruction( 12 | program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | crate::processor::process_instruction(program_id, accounts, instruction_data) 17 | } 18 | -------------------------------------------------------------------------------- /program/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! A program that accepts a string of encoded characters and verifies that it 4 | //! parses, while verifying and logging signers. Currently handles UTF-8 5 | //! characters. 6 | 7 | mod entrypoint; 8 | pub mod processor; 9 | 10 | // Export current sdk types for downstream users building with a different sdk 11 | // version 12 | pub use { 13 | solana_account_info, solana_instruction, solana_msg, solana_program_entrypoint, 14 | solana_program_error, solana_pubkey, 15 | }; 16 | use { 17 | solana_instruction::{AccountMeta, Instruction}, 18 | solana_pubkey::Pubkey, 19 | }; 20 | 21 | /// Legacy symbols from Memo version 1 22 | pub mod v1 { 23 | solana_pubkey::declare_id!("Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"); 24 | } 25 | 26 | solana_pubkey::declare_id!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"); 27 | 28 | /// Build a memo instruction, possibly signed 29 | /// 30 | /// Accounts expected by this instruction: 31 | /// 32 | /// 0. `..0+N` `[signer]` Expected signers; if zero provided, instruction will 33 | /// be processed as a normal, unsigned spl-memo 34 | pub fn build_memo(memo: &[u8], signer_pubkeys: &[&Pubkey]) -> Instruction { 35 | Instruction { 36 | program_id: id(), 37 | accounts: signer_pubkeys 38 | .iter() 39 | .map(|&pubkey| AccountMeta::new_readonly(*pubkey, true)) 40 | .collect(), 41 | data: memo.to_vec(), 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn test_build_memo() { 51 | let signer_pubkey = Pubkey::new_unique(); 52 | let memo = "🐆".as_bytes(); 53 | let instruction = build_memo(memo, &[&signer_pubkey]); 54 | assert_eq!(memo, instruction.data); 55 | assert_eq!(instruction.accounts.len(), 1); 56 | assert_eq!(instruction.accounts[0].pubkey, signer_pubkey); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /program/src/processor.rs: -------------------------------------------------------------------------------- 1 | //! Program state processor 2 | 3 | use { 4 | solana_account_info::AccountInfo, solana_msg::msg, solana_program_entrypoint::ProgramResult, 5 | solana_program_error::ProgramError, solana_pubkey::Pubkey, std::str::from_utf8, 6 | }; 7 | 8 | /// Instruction processor 9 | pub fn process_instruction( 10 | _program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | input: &[u8], 13 | ) -> ProgramResult { 14 | let account_info_iter = &mut accounts.iter(); 15 | let mut missing_required_signature = false; 16 | for account_info in account_info_iter { 17 | if let Some(address) = account_info.signer_key() { 18 | msg!("Signed by {:?}", address); 19 | } else { 20 | missing_required_signature = true; 21 | } 22 | } 23 | if missing_required_signature { 24 | return Err(ProgramError::MissingRequiredSignature); 25 | } 26 | 27 | let memo = from_utf8(input).map_err(|err| { 28 | msg!("Invalid UTF-8, from byte {}", err.valid_up_to()); 29 | ProgramError::InvalidInstructionData 30 | })?; 31 | msg!("Memo (len {}): {:?}", memo.len(), memo); 32 | 33 | Ok(()) 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use { 39 | super::*, solana_account_info::IntoAccountInfo, solana_program_error::ProgramError, 40 | solana_pubkey::Pubkey, solana_sdk::account::Account, 41 | }; 42 | 43 | #[test] 44 | fn test_utf8_memo() { 45 | let program_id = Pubkey::new_from_array([0; 32]); 46 | 47 | let string = b"letters and such"; 48 | assert_eq!(Ok(()), process_instruction(&program_id, &[], string)); 49 | 50 | let emoji = "🐆".as_bytes(); 51 | let bytes = [0xF0, 0x9F, 0x90, 0x86]; 52 | assert_eq!(emoji, bytes); 53 | assert_eq!(Ok(()), process_instruction(&program_id, &[], emoji)); 54 | 55 | let mut bad_utf8 = bytes; 56 | bad_utf8[3] = 0xFF; // Invalid UTF-8 byte 57 | assert_eq!( 58 | Err(ProgramError::InvalidInstructionData), 59 | process_instruction(&program_id, &[], &bad_utf8) 60 | ); 61 | } 62 | 63 | #[test] 64 | fn test_signers() { 65 | let program_id = Pubkey::new_from_array([0; 32]); 66 | let memo = "🐆".as_bytes(); 67 | 68 | let pubkey0 = Pubkey::new_unique(); 69 | let pubkey1 = Pubkey::new_unique(); 70 | let pubkey2 = Pubkey::new_unique(); 71 | let mut account0 = Account::default(); 72 | let mut account1 = Account::default(); 73 | let mut account2 = Account::default(); 74 | 75 | let signed_account_infos = vec![ 76 | (&pubkey0, true, &mut account0).into_account_info(), 77 | (&pubkey1, true, &mut account1).into_account_info(), 78 | (&pubkey2, true, &mut account2).into_account_info(), 79 | ]; 80 | assert_eq!( 81 | Ok(()), 82 | process_instruction(&program_id, &signed_account_infos, memo) 83 | ); 84 | 85 | assert_eq!(Ok(()), process_instruction(&program_id, &[], memo)); 86 | 87 | let unsigned_account_infos = vec![ 88 | (&pubkey0, false, &mut account0).into_account_info(), 89 | (&pubkey1, false, &mut account1).into_account_info(), 90 | (&pubkey2, false, &mut account2).into_account_info(), 91 | ]; 92 | assert_eq!( 93 | Err(ProgramError::MissingRequiredSignature), 94 | process_instruction(&program_id, &unsigned_account_infos, memo) 95 | ); 96 | 97 | let partially_signed_account_infos = vec![ 98 | (&pubkey0, true, &mut account0).into_account_info(), 99 | (&pubkey1, false, &mut account1).into_account_info(), 100 | (&pubkey2, true, &mut account2).into_account_info(), 101 | ]; 102 | assert_eq!( 103 | Err(ProgramError::MissingRequiredSignature), 104 | process_instruction(&program_id, &partially_signed_account_infos, memo) 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /program/tests/functional.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "test-sbf")] 2 | 3 | use { 4 | solana_instruction::{error::InstructionError, AccountMeta, Instruction}, 5 | solana_program_test::*, 6 | solana_pubkey::Pubkey, 7 | solana_sdk::{ 8 | signature::{Keypair, Signer}, 9 | transaction::{Transaction, TransactionError}, 10 | }, 11 | spl_memo::*, 12 | }; 13 | 14 | fn program_test() -> ProgramTest { 15 | ProgramTest::new("spl_memo", id(), processor!(processor::process_instruction)) 16 | } 17 | 18 | #[tokio::test] 19 | async fn test_memo_signing() { 20 | let memo = "🐆".as_bytes(); 21 | let (banks_client, payer, recent_blockhash) = program_test().start().await; 22 | 23 | let keypairs = vec![Keypair::new(), Keypair::new(), Keypair::new()]; 24 | let pubkeys: Vec = keypairs.iter().map(|keypair| keypair.pubkey()).collect(); 25 | 26 | // Test complete signing 27 | let signer_key_refs: Vec<&Pubkey> = pubkeys.iter().collect(); 28 | let mut transaction = 29 | Transaction::new_with_payer(&[build_memo(memo, &signer_key_refs)], Some(&payer.pubkey())); 30 | let mut signers = vec![&payer]; 31 | for keypair in keypairs.iter() { 32 | signers.push(keypair); 33 | } 34 | transaction.sign(&signers, recent_blockhash); 35 | banks_client.process_transaction(transaction).await.unwrap(); 36 | 37 | // Test unsigned memo 38 | let mut transaction = 39 | Transaction::new_with_payer(&[build_memo(memo, &[])], Some(&payer.pubkey())); 40 | transaction.sign(&[&payer], recent_blockhash); 41 | banks_client.process_transaction(transaction).await.unwrap(); 42 | 43 | // Demonstrate success on signature provided, regardless of specific memo 44 | // AccountMeta 45 | let mut transaction = Transaction::new_with_payer( 46 | &[Instruction { 47 | program_id: id(), 48 | accounts: vec![ 49 | AccountMeta::new_readonly(keypairs[0].pubkey(), true), 50 | AccountMeta::new_readonly(keypairs[1].pubkey(), true), 51 | AccountMeta::new_readonly(payer.pubkey(), false), 52 | ], 53 | data: memo.to_vec(), 54 | }], 55 | Some(&payer.pubkey()), 56 | ); 57 | transaction.sign(&[&payer, &keypairs[0], &keypairs[1]], recent_blockhash); 58 | banks_client.process_transaction(transaction).await.unwrap(); 59 | 60 | // Test missing signer(s) 61 | let mut transaction = Transaction::new_with_payer( 62 | &[Instruction { 63 | program_id: id(), 64 | accounts: vec![ 65 | AccountMeta::new_readonly(keypairs[0].pubkey(), true), 66 | AccountMeta::new_readonly(keypairs[1].pubkey(), false), 67 | AccountMeta::new_readonly(keypairs[2].pubkey(), true), 68 | ], 69 | data: memo.to_vec(), 70 | }], 71 | Some(&payer.pubkey()), 72 | ); 73 | transaction.sign(&[&payer, &keypairs[0], &keypairs[2]], recent_blockhash); 74 | assert_eq!( 75 | banks_client 76 | .process_transaction(transaction) 77 | .await 78 | .unwrap_err() 79 | .unwrap(), 80 | TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) 81 | ); 82 | 83 | let mut transaction = Transaction::new_with_payer( 84 | &[Instruction { 85 | program_id: id(), 86 | accounts: vec![ 87 | AccountMeta::new_readonly(keypairs[0].pubkey(), false), 88 | AccountMeta::new_readonly(keypairs[1].pubkey(), false), 89 | AccountMeta::new_readonly(keypairs[2].pubkey(), false), 90 | ], 91 | data: memo.to_vec(), 92 | }], 93 | Some(&payer.pubkey()), 94 | ); 95 | transaction.sign(&[&payer], recent_blockhash); 96 | assert_eq!( 97 | banks_client 98 | .process_transaction(transaction) 99 | .await 100 | .unwrap_err() 101 | .unwrap(), 102 | TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature) 103 | ); 104 | 105 | // Test invalid utf-8; demonstrate log 106 | let invalid_utf8 = [0xF0, 0x9F, 0x90, 0x86, 0xF0, 0x9F, 0xFF, 0x86]; 107 | let mut transaction = 108 | Transaction::new_with_payer(&[build_memo(&invalid_utf8, &[])], Some(&payer.pubkey())); 109 | transaction.sign(&[&payer], recent_blockhash); 110 | assert_eq!( 111 | banks_client 112 | .process_transaction(transaction) 113 | .await 114 | .unwrap_err() 115 | .unwrap(), 116 | TransactionError::InstructionError(0, InstructionError::InvalidInstructionData) 117 | ); 118 | } 119 | 120 | #[tokio::test] 121 | #[ignore] 122 | async fn test_memo_compute_limits() { 123 | let (banks_client, payer, recent_blockhash) = program_test().start().await; 124 | 125 | // Test memo length 126 | let mut memo = vec![]; 127 | for _ in 0..1000 { 128 | let mut vec = vec![0x53, 0x4F, 0x4C]; 129 | memo.append(&mut vec); 130 | } 131 | 132 | let mut transaction = 133 | Transaction::new_with_payer(&[build_memo(&memo[..450], &[])], Some(&payer.pubkey())); 134 | transaction.sign(&[&payer], recent_blockhash); 135 | banks_client.process_transaction(transaction).await.unwrap(); 136 | 137 | let mut transaction = 138 | Transaction::new_with_payer(&[build_memo(&memo[..600], &[])], Some(&payer.pubkey())); 139 | transaction.sign(&[&payer], recent_blockhash); 140 | let err = banks_client 141 | .process_transaction(transaction) 142 | .await 143 | .unwrap_err() 144 | .unwrap(); 145 | let failed_to_complete = 146 | TransactionError::InstructionError(0, InstructionError::ProgramFailedToComplete); 147 | let computational_budget_exceeded = 148 | TransactionError::InstructionError(0, InstructionError::ComputationalBudgetExceeded); 149 | assert!(err == failed_to_complete || err == computational_budget_exceeded); 150 | 151 | let mut memo = vec![]; 152 | for _ in 0..100 { 153 | let mut vec = vec![0xE2, 0x97, 0x8E]; 154 | memo.append(&mut vec); 155 | } 156 | 157 | let mut transaction = 158 | Transaction::new_with_payer(&[build_memo(&memo[..60], &[])], Some(&payer.pubkey())); 159 | transaction.sign(&[&payer], recent_blockhash); 160 | banks_client.process_transaction(transaction).await.unwrap(); 161 | 162 | let mut transaction = 163 | Transaction::new_with_payer(&[build_memo(&memo[..63], &[])], Some(&payer.pubkey())); 164 | transaction.sign(&[&payer], recent_blockhash); 165 | let err = banks_client 166 | .process_transaction(transaction) 167 | .await 168 | .unwrap_err() 169 | .unwrap(); 170 | assert!(err == failed_to_complete || err == computational_budget_exceeded); 171 | 172 | // Test num signers with 32-byte memo 173 | let memo = Pubkey::new_unique().to_bytes(); 174 | let mut keypairs = vec![]; 175 | for _ in 0..20 { 176 | keypairs.push(Keypair::new()); 177 | } 178 | let pubkeys: Vec = keypairs.iter().map(|keypair| keypair.pubkey()).collect(); 179 | let signer_key_refs: Vec<&Pubkey> = pubkeys.iter().collect(); 180 | 181 | let mut signers = vec![&payer]; 182 | for keypair in keypairs[..12].iter() { 183 | signers.push(keypair); 184 | } 185 | let mut transaction = Transaction::new_with_payer( 186 | &[build_memo(&memo, &signer_key_refs[..12])], 187 | Some(&payer.pubkey()), 188 | ); 189 | transaction.sign(&signers, recent_blockhash); 190 | banks_client.process_transaction(transaction).await.unwrap(); 191 | 192 | let mut signers = vec![&payer]; 193 | for keypair in keypairs[..15].iter() { 194 | signers.push(keypair); 195 | } 196 | let mut transaction = Transaction::new_with_payer( 197 | &[build_memo(&memo, &signer_key_refs[..15])], 198 | Some(&payer.pubkey()), 199 | ); 200 | transaction.sign(&signers, recent_blockhash); 201 | let err = banks_client 202 | .process_transaction(transaction) 203 | .await 204 | .unwrap_err() 205 | .unwrap(); 206 | assert!(err == failed_to_complete || err == computational_budget_exceeded); 207 | } 208 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.84.1" 3 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | reorder_imports = true 3 | reorder_modules = true 4 | 5 | # == Nightly only. == 6 | # imports_indent = "Block" 7 | # imports_layout = "Mixed" 8 | # imports_granularity = "Crate" 9 | # group_imports = "Preserve" 10 | # reorder_impl_items = false 11 | -------------------------------------------------------------------------------- /scripts/audit-rust.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | 4 | const advisories = [ 5 | // ed25519-dalek: Double Public Key Signing Function Oracle Attack 6 | // 7 | // Remove once repo upgrades to ed25519-dalek v2 8 | 'RUSTSEC-2022-0093', 9 | 10 | // curve25519-dalek 11 | // 12 | // Remove once repo upgrades to curve25519-dalek v4 13 | 'RUSTSEC-2024-0344', 14 | 15 | // Crate: tonic 16 | // Version: 0.9.2 17 | // Title: Remotely exploitable Denial of Service in Tonic 18 | // Date: 2024-10-01 19 | // ID: RUSTSEC-2024-0376 20 | // URL: https://rustsec.org/advisories/RUSTSEC-2024-0376 21 | // Solution: Upgrade to >=0.12.3 22 | 'RUSTSEC-2024-0376', 23 | ]; 24 | const ignores = [] 25 | advisories.forEach(x => { 26 | ignores.push('--ignore'); 27 | ignores.push(x); 28 | }); 29 | 30 | // Check Solana version. 31 | await $`cargo audit ${ignores}`; 32 | -------------------------------------------------------------------------------- /scripts/check-solana-version.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { getInstalledSolanaVersion, getSolanaVersion } from './utils.mjs'; 4 | 5 | const expectedVersion = getSolanaVersion(); 6 | const installedVersion = await getInstalledSolanaVersion(); 7 | 8 | if (!installedVersion) { 9 | echo( 10 | chalk.red('[ ERROR ]'), 11 | `No Solana installation found. Please install Solana ${expectedVersion} before proceeding.` 12 | ); 13 | process.exit(1); 14 | } else if (installedVersion !== expectedVersion) { 15 | echo( 16 | chalk.yellow('[ WARNING ]'), 17 | `The installed Solana version ${installedVersion} does not match the expected version ${expectedVersion}.` 18 | ); 19 | } else { 20 | echo( 21 | chalk.green('[ SUCCESS ]'), 22 | `The expected Solana version ${expectedVersion} is installed.` 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /scripts/ci/set-env.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import { getSolanaVersion, getToolchain } from '../utils.mjs'; 3 | 4 | await $`echo "SOLANA_VERSION=${getSolanaVersion()}" >> $GITHUB_ENV`; 5 | await $`echo "TOOLCHAIN_FORMAT=${getToolchain('format')}" >> $GITHUB_ENV`; 6 | await $`echo "TOOLCHAIN_LINT=${getToolchain('lint')}" >> $GITHUB_ENV`; 7 | -------------------------------------------------------------------------------- /scripts/client/format-js.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, workingDirectory } from '../utils.mjs'; 4 | 5 | // Format the client using Prettier. 6 | cd(path.join(workingDirectory, 'clients', 'js')); 7 | await $`pnpm install`; 8 | await $`pnpm format ${cliArguments()}`; 9 | -------------------------------------------------------------------------------- /scripts/client/format-rust.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | partitionArguments, 7 | popArgument, 8 | workingDirectory, 9 | } from '../utils.mjs'; 10 | 11 | // Configure additional arguments here, e.g.: 12 | // ['--arg1', '--arg2', ...cliArguments()] 13 | const formatArgs = cliArguments(); 14 | 15 | const fix = popArgument(formatArgs, '--fix'); 16 | const [cargoArgs, fmtArgs] = partitionArguments(formatArgs, '--'); 17 | const toolchain = getToolchainArgument('format'); 18 | const manifestPath = path.join( 19 | workingDirectory, 20 | 'clients', 21 | 'rust', 22 | 'Cargo.toml' 23 | ); 24 | 25 | // Format the client. 26 | if (fix) { 27 | await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- ${fmtArgs}`; 28 | } else { 29 | await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- --check ${fmtArgs}`; 30 | } 31 | -------------------------------------------------------------------------------- /scripts/client/lint-js.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, workingDirectory } from '../utils.mjs'; 4 | 5 | // Check the client using ESLint. 6 | cd(path.join(workingDirectory, 'clients', 'js')); 7 | await $`pnpm install`; 8 | await $`pnpm lint ${cliArguments()}`; 9 | -------------------------------------------------------------------------------- /scripts/client/lint-rust.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getToolchainArgument, 6 | popArgument, 7 | workingDirectory, 8 | } from '../utils.mjs'; 9 | 10 | // Configure additional arguments here, e.g.: 11 | // ['--arg1', '--arg2', ...cliArguments()] 12 | const lintArgs = cliArguments(); 13 | 14 | const fix = popArgument(lintArgs, '--fix'); 15 | const toolchain = getToolchainArgument('lint'); 16 | const manifestPath = path.join( 17 | workingDirectory, 18 | 'clients', 19 | 'rust', 20 | 'Cargo.toml' 21 | ); 22 | 23 | // Check the client using Clippy. 24 | if (fix) { 25 | await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} --fix ${lintArgs}`; 26 | } else { 27 | await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} ${lintArgs}`; 28 | } 29 | -------------------------------------------------------------------------------- /scripts/client/publish-js.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, workingDirectory } from '../utils.mjs'; 4 | 5 | const [level, tag = 'latest'] = cliArguments(); 6 | if (!level) { 7 | throw new Error('A version level — e.g. "path" — must be provided.'); 8 | } 9 | 10 | // Go to the client directory and install the dependencies. 11 | cd(path.join(workingDirectory, 'clients', 'js')); 12 | await $`pnpm install`; 13 | 14 | // Update the version. 15 | const versionArgs = [ 16 | '--no-git-tag-version', 17 | ...(level.startsWith('pre') ? [`--preid ${tag}`] : []), 18 | ]; 19 | let { stdout } = await $`pnpm version ${level} ${versionArgs}`; 20 | const newVersion = stdout.slice(1).trim(); 21 | 22 | // Expose the new version to CI if needed. 23 | if (process.env.CI) { 24 | await $`echo "new_version=${newVersion}" >> $GITHUB_OUTPUT`; 25 | } 26 | 27 | // Publish the package. 28 | // This will also build the package before publishing (see prepublishOnly script). 29 | await $`pnpm publish --no-git-checks --tag ${tag}`; 30 | 31 | // Commit the new version. 32 | await $`git commit -am "Publish JS client v${newVersion}"`; 33 | 34 | // Tag the new version. 35 | await $`git tag -a js@v${newVersion} -m "JS client v${newVersion}"`; 36 | -------------------------------------------------------------------------------- /scripts/client/publish-rust.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, getCargo, workingDirectory } from '../utils.mjs'; 4 | 5 | const dryRun = argv['dry-run'] ?? false; 6 | const [level] = cliArguments(); 7 | if (!level) { 8 | throw new Error('A version level — e.g. "path" — must be provided.'); 9 | } 10 | 11 | // Go to the client directory and install the dependencies. 12 | cd(path.join(workingDirectory, 'clients', 'rust')); 13 | 14 | // Publish the new version. 15 | const releaseArgs = dryRun 16 | ? [] 17 | : ['--no-push', '--no-tag', '--no-confirm', '--execute']; 18 | await $`cargo release ${level} ${releaseArgs}`; 19 | 20 | // Stop here if this is a dry run. 21 | if (dryRun) { 22 | process.exit(0); 23 | } 24 | 25 | // Get the new version. 26 | const newVersion = getCargo(path.join('clients', 'rust')).package.version; 27 | 28 | // Expose the new version to CI if needed. 29 | if (process.env.CI) { 30 | await $`echo "new_version=${newVersion}" >> $GITHUB_OUTPUT`; 31 | } 32 | 33 | // Soft reset the last commit so we can create our own commit and tag. 34 | await $`git reset --soft HEAD~1`; 35 | 36 | // Commit the new version. 37 | await $`git commit -am "Publish Rust client v${newVersion}"`; 38 | 39 | // Tag the new version. 40 | await $`git tag -a rust@v${newVersion} -m "Rust client v${newVersion}"`; 41 | -------------------------------------------------------------------------------- /scripts/client/test-js.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, workingDirectory } from '../utils.mjs'; 4 | 5 | // Start the local validator, or restart it if it is already running. 6 | await $`pnpm validator:restart`; 7 | 8 | // Build the client and run the tests. 9 | cd(path.join(workingDirectory, 'clients', 'js')); 10 | await $`pnpm install`; 11 | await $`pnpm build`; 12 | await $`pnpm test ${cliArguments()}`; 13 | -------------------------------------------------------------------------------- /scripts/client/test-rust.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { cliArguments, workingDirectory } from '../utils.mjs'; 4 | 5 | // Configure additional arguments here, e.g.: 6 | // ['--arg1', '--arg2', ...cliArguments()] 7 | const testArgs = cliArguments(); 8 | 9 | const hasSolfmt = await which('solfmt', { nothrow: true }); 10 | const sbfOutDir = path.join(workingDirectory, 'target', 'deploy'); 11 | 12 | // Run the tests. 13 | cd(path.join(workingDirectory, 'clients', 'rust')); 14 | if (hasSolfmt) { 15 | await $`SBF_OUT_DIR=${sbfOutDir} cargo test --features "test-sbf" ${testArgs} 2>&1 | solfmt`; 16 | } else { 17 | await $`SBF_OUT_DIR=${sbfOutDir} cargo test --features "test-sbf" ${testArgs}`; 18 | } 19 | -------------------------------------------------------------------------------- /scripts/generate-clients.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { createFromRoot } from 'codama'; 4 | import { renderVisitor as renderJavaScriptVisitor } from '@codama/renderers-js'; 5 | import { renderVisitor as renderRustVisitor } from '@codama/renderers-rust'; 6 | import { getToolchainArgument, workingDirectory } from './utils.mjs'; 7 | 8 | // Instanciate Codama. 9 | const codama = createFromRoot( 10 | require(path.join(workingDirectory, 'program', 'idl.json')) 11 | ); 12 | 13 | // Render JavaScript. 14 | const jsClient = path.join(__dirname, '..', 'clients', 'js'); 15 | codama.accept( 16 | renderJavaScriptVisitor(path.join(jsClient, 'src', 'generated'), { 17 | prettier: require(path.join(jsClient, '.prettierrc.json')), 18 | }) 19 | ); 20 | 21 | // Render Rust. 22 | const rustClient = path.join(__dirname, '..', 'clients', 'rust'); 23 | codama.accept( 24 | renderRustVisitor(path.join(rustClient, 'src', 'generated'), { 25 | formatCode: true, 26 | crateFolder: rustClient, 27 | toolchain: getToolchainArgument('format'), 28 | }) 29 | ); 30 | -------------------------------------------------------------------------------- /scripts/link-solana-version.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { getInstalledSolanaVersion, getSolanaVersion } from './utils.mjs'; 4 | 5 | const expectedVersion = getSolanaVersion(); 6 | const installedVersion = await getInstalledSolanaVersion(); 7 | 8 | const installPath = path.join( 9 | os.homedir(), 10 | '.local', 11 | 'share', 12 | 'solana', 13 | 'install' 14 | ); 15 | const releasePath = path.join( 16 | installPath, 17 | 'releases', 18 | expectedVersion, 19 | 'solana-release' 20 | ); 21 | const activeReleasePath = path.join(installPath, 'active_release'); 22 | const hasRelease = await fs.exists(releasePath); 23 | 24 | if (!installedVersion) { 25 | echo( 26 | chalk.red('[ ERROR ]'), 27 | `No Solana installation found. Solana ${expectedVersion} is required for this project.` 28 | ); 29 | await askToInstallSolana(expectedVersion); 30 | } else if (installedVersion === expectedVersion) { 31 | echo( 32 | chalk.green('[ SUCCESS ]'), 33 | `The expected Solana version ${expectedVersion} is installed.` 34 | ); 35 | } else if (hasRelease) { 36 | await $`rm -f "${activeReleasePath}"`; 37 | await $`ln -s "${releasePath}" "${activeReleasePath}"`; 38 | echo( 39 | chalk.green('[ SUCCESS ]'), 40 | `Successfully switched from Solana version ${installedVersion} to ${expectedVersion} to match the project's requirements.` 41 | ); 42 | } else { 43 | echo( 44 | chalk.yellow('[ WARNING ]'), 45 | `Cannot switch from Solana version ${installedVersion} to ${expectedVersion} because it is not installed.` 46 | ); 47 | await askToInstallSolana(expectedVersion); 48 | } 49 | 50 | async function askToInstallSolana(version) { 51 | const installRelease = await question('Should we install it now? [y/N] '); 52 | if (installRelease === 'y') { 53 | await installSolana(version); 54 | echo( 55 | chalk.green('[ SUCCESS ]'), 56 | `Successfully installed Solana version ${version}.` 57 | ); 58 | } else { 59 | process.exit(1); 60 | } 61 | } 62 | 63 | async function installSolana(version) { 64 | echo(`Installing Solana ${version}...`); 65 | const cutoff = '1.18.19'; 66 | const isBeforeCutoff = 67 | (await $`[[ "$(printf '%s\n' "${cutoff}" "${version}" | sort -V | head -n1)" = "${version}" ]] && [[ "${cutoff}" != "${version}" ]]`.quiet() 68 | .exitCode) == 0; 69 | if (isBeforeCutoff) { 70 | await $`sh -c "$(curl -sSfL https://release.solana.com/v${version}/install)"`; 71 | } else { 72 | await $`sh -c "$(curl -sSfL https://release.anza.xyz/v${version}/install)"`; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /scripts/program/build.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getProgramFolders, 6 | workingDirectory, 7 | } from '../utils.mjs'; 8 | 9 | // Build the programs. 10 | for (const folder of getProgramFolders()) { 11 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 12 | await $`cargo-build-sbf --manifest-path ${manifestPath} ${cliArguments()}`; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/program/format.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getProgramFolders, 6 | getToolchainArgument, 7 | partitionArguments, 8 | popArgument, 9 | workingDirectory, 10 | } from '../utils.mjs'; 11 | 12 | // Configure additional arguments here, e.g.: 13 | // ['--arg1', '--arg2', ...cliArguments()] 14 | const formatArgs = cliArguments(); 15 | 16 | const fix = popArgument(formatArgs, '--fix'); 17 | const [cargoArgs, fmtArgs] = partitionArguments(formatArgs, '--'); 18 | const toolchain = getToolchainArgument('format'); 19 | 20 | // Format the programs. 21 | for (const folder of getProgramFolders()) { 22 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 23 | 24 | if (fix) { 25 | await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- ${fmtArgs}`; 26 | } else { 27 | await $`cargo ${toolchain} fmt --manifest-path ${manifestPath} ${cargoArgs} -- --check ${fmtArgs}`; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/program/lint.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getProgramFolders, 6 | getToolchainArgument, 7 | popArgument, 8 | workingDirectory, 9 | } from '../utils.mjs'; 10 | 11 | // Configure arguments here. 12 | const lintArgs = [ 13 | '-Zunstable-options', 14 | '--all-targets', 15 | '--all-features', 16 | '--', 17 | '--deny=warnings', 18 | '--deny=clippy::arithmetic_side_effects', 19 | ...cliArguments(), 20 | ]; 21 | 22 | const fix = popArgument(lintArgs, '--fix'); 23 | const toolchain = getToolchainArgument('lint'); 24 | 25 | // Lint the programs using clippy. 26 | for (const folder of getProgramFolders()) { 27 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 28 | 29 | if (fix) { 30 | await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} --fix ${lintArgs}`; 31 | } else { 32 | await $`cargo ${toolchain} clippy --manifest-path ${manifestPath} ${lintArgs}`; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scripts/program/test.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { 4 | cliArguments, 5 | getProgramFolders, 6 | workingDirectory, 7 | } from '../utils.mjs'; 8 | 9 | const hasSolfmt = await which('solfmt', { nothrow: true }); 10 | 11 | // Test the programs. 12 | for (const folder of getProgramFolders()) { 13 | const manifestPath = path.join(workingDirectory, folder, 'Cargo.toml'); 14 | 15 | if (hasSolfmt) { 16 | await $`RUST_LOG=error cargo test-sbf --manifest-path ${manifestPath} ${cliArguments()} 2>&1 | solfmt`; 17 | } else { 18 | await $`RUST_LOG=error cargo test-sbf --manifest-path ${manifestPath} ${cliArguments()}`; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/solana.dic: -------------------------------------------------------------------------------- 1 | 1000 2 | config 3 | metadata 4 | json 5 | uri 6 | ui 7 | cli 8 | readme/S 9 | arg/S 10 | vec/S 11 | enum/S 12 | noop/S 13 | realloc/S 14 | overallocate/SGD 15 | namespace 16 | serde 17 | deserialize/SRGD 18 | deserialization 19 | struct/S 20 | param/S 21 | tuple/S 22 | metas 23 | infos 24 | async 25 | subcommand 26 | repo 27 | init 28 | solana 29 | sol/S 30 | blockchain/S 31 | permissionless 32 | composability 33 | runtime 34 | onchain 35 | offchain 36 | keypair/S 37 | decrypt/SGD 38 | lamport/S 39 | validator/S 40 | pubkey/S 41 | sysvar/S 42 | timestamp/S 43 | entrypoint/S 44 | spl 45 | pda/S 46 | multisignature/S 47 | multisig/S 48 | staker/S 49 | APY 50 | codama 51 | autogenerated 52 | -------------------------------------------------------------------------------- /scripts/spellcheck.toml: -------------------------------------------------------------------------------- 1 | [Hunspell] 2 | use_builtin = true 3 | skip_os_lookups = false 4 | search_dirs = ["."] 5 | extra_dictionaries = ["solana.dic"] 6 | 7 | -------------------------------------------------------------------------------- /scripts/start-validator.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import { spawn } from 'node:child_process'; 3 | import fs from 'node:fs'; 4 | import 'zx/globals'; 5 | import { 6 | getCargo, 7 | getExternalAccountAddresses, 8 | getExternalProgramAddresses, 9 | getExternalProgramOutputDir, 10 | getProgramFolders, 11 | } from './utils.mjs'; 12 | 13 | // Check Solana version. 14 | await $`pnpm solana:check`; 15 | 16 | // Options and arguments. 17 | const restart = argv['restart']; 18 | 19 | // Keep the validator running when not using the restart flag. 20 | const isValidatorRunning = (await $`lsof -t -i:8899`.quiet().exitCode) === 0; 21 | if (!restart && isValidatorRunning) { 22 | echo(chalk.yellow('Local validator is already running.')); 23 | process.exit(); 24 | } 25 | 26 | // Initial message. 27 | const verb = isValidatorRunning ? 'Restarting' : 'Starting'; 28 | 29 | // Get programs and accounts. 30 | const programs = [...getPrograms(), ...getExternalPrograms()]; 31 | const programPluralized = programs.length === 1 ? 'program' : 'programs'; 32 | const accounts = [...getExternalAccounts()]; 33 | const accountsPluralized = accounts.length === 1 ? 'account' : 'accounts'; 34 | 35 | echo( 36 | `${verb} local validator with ${programs.length} custom ${programPluralized}` + 37 | (accounts.length > 0 38 | ? ` and ${accounts.length} external ${accountsPluralized}...` 39 | : `...`) 40 | ); 41 | 42 | // Kill the validator if it's already running. 43 | if (isValidatorRunning) { 44 | await $`pkill -f solana-test-validator`.quiet(); 45 | await sleep(1000); 46 | } 47 | 48 | // Global validator arguments. 49 | const args = [/* Reset ledger */ '-r']; 50 | 51 | // Load programs. 52 | programs.forEach(({ programId, deployPath }) => { 53 | args.push(/* Load BPF program */ '--bpf-program', programId, deployPath); 54 | }); 55 | 56 | // Load accounts. 57 | accounts.forEach(({ account, deployPath }) => { 58 | args.push(/* Load account */ '--account', account, deployPath); 59 | }); 60 | 61 | // Start the validator in detached mode. 62 | const cliLogs = path.join(os.tmpdir(), 'validator-cli.log'); 63 | fs.writeFileSync(cliLogs, '', () => {}); 64 | const out = fs.openSync(cliLogs, 'a'); 65 | const err = fs.openSync(cliLogs, 'a'); 66 | const validator = spawn('solana-test-validator', args, { 67 | detached: true, 68 | stdio: ['ignore', out, err], 69 | }); 70 | validator.unref(); 71 | 72 | // Wait for the validator to stabilize. 73 | const waitForValidator = spinner( 74 | 'Waiting for local validator to stabilize...', 75 | () => 76 | new Promise((resolve, reject) => { 77 | setInterval(() => { 78 | const logs = fs.readFileSync(cliLogs, 'utf8'); 79 | if (validator.exitCode !== null) { 80 | reject(logs); 81 | } else if (logs.includes('Confirmed Slot: 1')) { 82 | resolve(); 83 | } 84 | }, 1000); 85 | }) 86 | ); 87 | 88 | try { 89 | await waitForValidator; 90 | echo(chalk.green('Local validator is up and running!')); 91 | } catch (error) { 92 | echo(error); 93 | echo(chalk.red('Could not start local validator.')); 94 | } finally { 95 | fs.rmSync(cliLogs); 96 | process.exit(); 97 | } 98 | 99 | function getPrograms() { 100 | const binaryDir = path.join(__dirname, '..', 'target', 'deploy'); 101 | return getProgramFolders().map((folder) => { 102 | const cargo = getCargo(folder); 103 | const name = cargo.package.name.replace(/-/g, '_'); 104 | return { 105 | programId: cargo.package.metadata.solana['program-id'], 106 | deployPath: path.join(binaryDir, `${name}.so`), 107 | }; 108 | }); 109 | } 110 | 111 | function getExternalPrograms() { 112 | const binaryDir = getExternalProgramOutputDir(); 113 | return getExternalProgramAddresses().map((address) => ({ 114 | programId: address, 115 | deployPath: path.join(binaryDir, `${address}.so`), 116 | })); 117 | } 118 | 119 | function getExternalAccounts() { 120 | const binaryDir = getExternalProgramOutputDir(); 121 | return getExternalAccountAddresses().map((address) => ({ 122 | account: address, 123 | deployPath: path.join(binaryDir, `${address}.json`), 124 | })); 125 | } 126 | -------------------------------------------------------------------------------- /scripts/stop-validator.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | 4 | const isValidatorRunning = (await $`lsof -t -i:8899`.quiet().exitCode) === 0; 5 | 6 | if (isValidatorRunning) { 7 | // Kill the validator if it's already running. 8 | await $`pkill -f solana-test-validator`.quiet(); 9 | await sleep(1000); 10 | echo(chalk.green('Local validator terminated!')); 11 | } else { 12 | echo(chalk.yellow('Local validator is not running.')); 13 | } 14 | -------------------------------------------------------------------------------- /scripts/upgrade-template.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zx 2 | import 'zx/globals'; 3 | import { getCargo } from './utils.mjs'; 4 | 5 | // Arguments to pass to the `create-solana-program` command. 6 | const rustClientCargo = getCargo(path.join('clients', 'rust')); 7 | const jsClientPkg = require( 8 | path.join(__dirname, '..', 'clients', 'js', 'package.json') 9 | ); 10 | const templateArgs = [ 11 | 'memo', 12 | '--address', 13 | 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr', 14 | '--org', 15 | 'solana-program', 16 | '--rust-client-crate-name', 17 | rustClientCargo.package.name, 18 | '--js-client-package-name', 19 | jsClientPkg.name, 20 | '--default', 21 | '--force', 22 | ]; 23 | 24 | // File and folder patterns that should not be overwritten by the template upgrade. 25 | const unchangedGlobs = [ 26 | 'clients/**/src/**', 27 | 'clients/**/src/*', 28 | 'clients/js/test/*', 29 | 'clients/rust/tests/*', 30 | 'program/**/*', 31 | 'program/*', 32 | 'scripts/generate-clients.mjs', 33 | 'scripts/generate-idls.mjs', 34 | 'scripts/upgrade-template.mjs', 35 | 'scripts/program/*', 36 | 'Cargo.lock', 37 | '**/pnpm-lock.yaml', 38 | 'pnpm-lock.yaml', 39 | ]; 40 | 41 | // Prevent CLI arguments from being escaped. 42 | $.quote = (command) => command; 43 | 44 | // Re-generate the repo from the parent directory. 45 | cd('..'); 46 | await $`pnpm create solana-program@latest ${templateArgs}`; 47 | 48 | // Go back inside the updated repo. 49 | cd('memo'); 50 | 51 | // Restore files and folders that should not be overwritten. 52 | await $`git add --all`; 53 | for (const glob of unchangedGlobs) { 54 | await $`git restore --worktree --staged "${glob}"`; 55 | } 56 | 57 | // Re-install dependencies. 58 | await $`pnpm install`; 59 | -------------------------------------------------------------------------------- /scripts/utils.mjs: -------------------------------------------------------------------------------- 1 | import 'zx/globals'; 2 | import { parse as parseToml } from '@iarna/toml'; 3 | 4 | $.verbose = true; 5 | process.env.FORCE_COLOR = 3; 6 | process.env.CARGO_TERM_COLOR = 'always'; 7 | 8 | export const workingDirectory = (await $`pwd`.quiet()).toString().trim(); 9 | 10 | export function getAllProgramIdls() { 11 | return getAllProgramFolders().map((folder) => 12 | path.join(workingDirectory, folder, 'idl.json') 13 | ); 14 | } 15 | 16 | export function getExternalProgramOutputDir() { 17 | const config = getCargoMetadata()?.solana?.['external-programs-output']; 18 | return path.join(workingDirectory, config ?? 'target/deploy'); 19 | } 20 | 21 | export function getExternalProgramAddresses() { 22 | const addresses = getProgramFolders().flatMap( 23 | (folder) => getCargoMetadata(folder)?.solana?.['program-dependencies'] ?? [] 24 | ); 25 | return Array.from(new Set(addresses)); 26 | } 27 | 28 | export function getExternalAccountAddresses() { 29 | const addresses = getProgramFolders().flatMap( 30 | (folder) => getCargoMetadata(folder)?.solana?.['account-dependencies'] ?? [] 31 | ); 32 | return Array.from(new Set(addresses)); 33 | } 34 | 35 | let didWarnAboutMissingPrograms = false; 36 | export function getProgramFolders() { 37 | let programs; 38 | 39 | if (process.env.PROGRAMS) { 40 | try { 41 | programs = JSON.parse(process.env.PROGRAMS); 42 | } catch (error) { 43 | programs = process.env.PROGRAMS.split(/\s+/); 44 | } 45 | } else { 46 | programs = getAllProgramFolders(); 47 | } 48 | 49 | const filteredPrograms = programs.filter((program) => 50 | fs.existsSync(path.join(workingDirectory, program)) 51 | ); 52 | 53 | if ( 54 | filteredPrograms.length !== programs.length && 55 | !didWarnAboutMissingPrograms 56 | ) { 57 | didWarnAboutMissingPrograms = true; 58 | programs 59 | .filter((program) => !filteredPrograms.includes(program)) 60 | .forEach((program) => { 61 | echo(chalk.yellow(`Program not found: ${workingDirectory}/${program}`)); 62 | }); 63 | } 64 | 65 | return filteredPrograms; 66 | } 67 | 68 | export function getAllProgramFolders() { 69 | return getCargo().workspace.members.filter((member) => 70 | (getCargo(member).lib?.['crate-type'] ?? []).includes('cdylib') 71 | ); 72 | } 73 | 74 | export function getCargo(folder) { 75 | return parseToml( 76 | fs.readFileSync( 77 | path.join(workingDirectory, folder ? folder : '.', 'Cargo.toml'), 78 | 'utf8' 79 | ) 80 | ); 81 | } 82 | 83 | export function getCargoMetadata(folder) { 84 | const cargo = getCargo(folder); 85 | return folder ? cargo?.package?.metadata : cargo?.workspace?.metadata; 86 | } 87 | 88 | export function getSolanaVersion() { 89 | return getCargoMetadata()?.cli?.solana; 90 | } 91 | 92 | export function getToolchain(operation) { 93 | return getCargoMetadata()?.toolchains?.[operation]; 94 | } 95 | 96 | export function getToolchainArgument(operation) { 97 | const channel = getToolchain(operation); 98 | return channel ? `+${channel}` : ''; 99 | } 100 | 101 | export function cliArguments() { 102 | return process.argv.slice(3); 103 | } 104 | 105 | export function popArgument(args, arg) { 106 | const index = args.indexOf(arg); 107 | if (index >= 0) { 108 | args.splice(index, 1); 109 | } 110 | return index >= 0; 111 | } 112 | 113 | export function partitionArguments(args, delimiter) { 114 | const index = args.indexOf(delimiter); 115 | return index >= 0 116 | ? [args.slice(0, index), args.slice(index + 1)] 117 | : [args, []]; 118 | } 119 | 120 | export async function getInstalledSolanaVersion() { 121 | try { 122 | const { stdout } = await $`solana --version`.quiet(); 123 | return stdout.match(/(\d+\.\d+\.\d+)/)?.[1]; 124 | } catch (error) { 125 | return ''; 126 | } 127 | } 128 | --------------------------------------------------------------------------------