├── .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 | []
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 |
--------------------------------------------------------------------------------