Fuck this whole web3.js thing (#30062)

This commit is contained in:
Steven Luscher 2023-02-01 14:34:23 -08:00 committed by GitHub
parent 090b990e15
commit a2cf25153f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 7 additions and 65099 deletions

View File

@ -15,18 +15,6 @@ updates:
# - "automerge"
open-pull-requests-limit: 3
- package-ecosystem: npm
directory: "/web3.js"
schedule:
interval: daily
time: "01:00"
timezone: America/Los_Angeles
labels:
- "automerge"
commit-message:
prefix: "chore:"
open-pull-requests-limit: 3
- package-ecosystem: npm
directory: "/explorer"
schedule:

4
.github/labeler.yml vendored
View File

@ -1,6 +1,2 @@
explorer:
- explorer/**/*
web3.js:
- web3.js/**/*
- web3.js-experimental/**/*

View File

@ -1,35 +0,0 @@
name: export-github-repo
on:
push:
branches:
- master
paths:
- "web3.js/**"
env:
GITHUB_TOKEN: ${{secrets.PAT}}
jobs:
web3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: cmd
run: |
chmod +x ./ci/export-github-repo.sh
./ci/export-github-repo.sh web3.js/ solana-web3.js
shell: bash
error_reporting:
needs:
- web3
if: failure() && github.event_name == 'push'
uses: ./.github/workflows/error-reporting.yml
secrets:
WEBHOOK: ${{ secrets.SLACK_ERROR_REPORTING_WEBHOOK }}

View File

@ -7,30 +7,6 @@ on:
branches: [master]
jobs:
Export_Github_Repositories:
runs-on: ubuntu-latest
env:
VERCEL_TOKEN: ${{secrets.VERCEL_TOKEN}}
GITHUB_TOKEN: ${{secrets.PAT_ANM}}
COMMIT_RANGE: ${{ github.event.before}}...${{ github.event.after}}
steps:
- name: Checkout repo
uses: actions/checkout@v2
with:
fetch-depth: 2
- run: echo "COMMIT_DIFF_RANGE=$(echo $COMMIT_RANGE)" >> $GITHUB_ENV
# - run: echo "$COMMIT_DIFF_RANGE"
- name: Set up Python
uses: actions/setup-python@v2
with:
GITHUB_TOKEN: ${{secrets.PAT_ANM}}
if: ${{ github.event_name == 'push' && 'cron'&& github.ref == 'refs/heads/master'}}
- name: cmd
run : |
.travis/export-github-repo.sh web3.js/ solana-web3.js
macos-artifacts:
needs: [Export_Github_Repositories]
strategy:

View File

@ -1,73 +0,0 @@
name: Web3
on:
push:
branches: [ master ]
paths:
- "web3.js/**"
- ".github/workflows/web3.yml"
pull_request:
branches: [ master ]
paths:
- "web3.js/**"
- ".github/workflows/web3.yml"
jobs:
# needed for grouping check-web3 strategies into one check for mergify
all-web3-checks:
runs-on: ubuntu-latest
needs:
- check-web3
steps:
- run: echo "Done"
web3-commit-lint:
runs-on: ubuntu-latest
# Set to true in order to avoid cancelling other workflow jobs.
# Mergify will still require web3-commit-lint for automerge
continue-on-error: true
defaults:
run:
working-directory: web3.js
steps:
- uses: actions/checkout@v3
with:
# maybe needed for base sha below
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: web3.js/package-lock.json
- run: npm ci
- name: commit-lint
if: ${{ github.event_name == 'pull_request' }}
run: bash commitlint.sh
env:
COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}
check-web3:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web3.js
strategy:
matrix:
node:
- '14'
- '16'
name: Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: 'npm'
cache-dependency-path: web3.js/package-lock.json
- run: |
scripts/test.sh

View File

@ -46,12 +46,6 @@ pull_request_rules:
# only require explorer checks if explorer files changed
- status-success=check-explorer
- -files~=^explorer/
- or:
- and:
- status-success=all-web3-checks
- status-success=web3-commit-lint
# only require web3 checks if web3.js files changed
- -files~=^web3.js/
actions:
merge:
method: squash

View File

@ -16,14 +16,6 @@ language: minimal
jobs:
include:
- name: "Export Github Repositories"
if: type IN (push, cron) AND branch = master
language: python
git:
depth: false
script:
- .travis/export-github-repo.sh web3.js/ solana-web3.js
- &release-artifacts
if: type IN (api, cron) OR tag IS present
name: "macOS release artifacts"

View File

@ -304,11 +304,11 @@ pull_or_push_steps() {
# Run the full test suite by default, skipping only if modifications are local
# to some particular areas of the tree
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^explorer/ ^.gitbook; then
all_test_steps
fi
# web3.js, explorer and docs changes run on Travis or Github actions...
# explorer and docs changes run on Travis or Github actions...
}

View File

@ -342,11 +342,11 @@ pull_or_push_steps() {
# Run the full test suite by default, skipping only if modifications are local
# to some particular areas of the tree
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^explorer/ ^.gitbook; then
all_test_steps
fi
# web3.js, explorer and docs changes run on Travis or Github actions...
# explorer and docs changes run on Travis or Github actions...
}

View File

@ -306,11 +306,11 @@ pull_or_push_steps() {
# Run the full test suite by default, skipping only if modifications are local
# to some particular areas of the tree
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^web3.js/ ^explorer/ ^.gitbook; then
if affects_other_than ^.buildkite ^.mergify .md$ ^docs/ ^explorer/ ^.gitbook; then
all_test_steps
fi
# web3.js, explorer and docs changes run on Travis or Github actions...
# explorer and docs changes run on Travis or Github actions...
}

View File

@ -1,34 +0,0 @@
#!/usr/bin/env bash
#
# Exports a subdirectory into another github repository
#
set -e
if [[ -z $GITHUB_TOKEN ]]; then
echo GITHUB_TOKEN not defined
exit 1
fi
cd "$(dirname "$0")/.."
pip3 install git-filter-repo
declare subdir=$1
declare repo_name=$2
[[ -n "$subdir" ]] || {
echo "Error: subdir not specified"
exit 1
}
[[ -n "$repo_name" ]] || {
echo "Error: repo_name not specified"
exit 1
}
echo "Exporting $subdir"
set -x
rm -rf .github_export/"$repo_name"
git clone https://"$GITHUB_TOKEN"@github.com/solana-labs/"$repo_name" .github_export/"$repo_name"
git filter-repo --subdirectory-filter "$subdir" --target .github_export/"$repo_name"
git -C .github_export/"$repo_name" push https://"$GITHUB_TOKEN"@github.com/solana-labs/"$repo_name"

View File

@ -1,15 +0,0 @@
coverage:
range: 50..100
round: down
precision: 1
status:
project: off
patch: off
comment:
layout: "diff"
behavior: default
require_changes: no
fixes:
- "src/::web3.js/src/"

View File

@ -7,7 +7,7 @@ use {
thiserror::Error,
};
// Keep in sync with web3.js/src/errors.ts
// Keep in sync with https://github.com/solana-labs/solana-web3.js/blob/master/src/errors.ts
pub const JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: i64 = -32001;
pub const JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: i64 = -32002;
pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: i64 = -32003;

View File

@ -16,7 +16,6 @@ fmt_dirs=(
sdk/cargo-build-sbf/tests/crates/fail
sdk/cargo-build-sbf/tests/crates/noop
storage-bigtable/build-proto
web3.js/test/fixtures/noop-program
)
for fmt_dir in "${fmt_dirs[@]}"; do

View File

@ -32,7 +32,6 @@ ignores=(
.cache
.cargo
target
web3.js/test
node_modules
)

View File

@ -22,7 +22,6 @@ ignores=(
.cache
.cargo
target
web3.js/test
node_modules
)

View File

@ -1,11 +0,0 @@
/commitlint.config.js
/coverage
/declarations
/deploy
/doc
/lib
/.eslintrc.js
/test/.eslintrc.js
/test/dist
/test/rollup.config.js
/rollup.config.js

View File

@ -1,47 +0,0 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true,
mocha: true,
},
extends: [
'eslint:recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 8,
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-unused-vars': ['error'],
'import/first': ['error'],
'import/no-commonjs': ['error'],
'import/order': [
'error',
{
groups: [
['internal', 'external', 'builtin'],
['index', 'sibling', 'parent'],
],
'newlines-between': 'always',
},
],
'linebreak-style': ['error', 'unix'],
'no-console': [0],
'no-trailing-spaces': ['error'],
'no-undef': 'off',
'no-unused-vars': 'off',
quotes: [
'error',
'single',
{avoidEscape: true, allowTemplateLiterals: true},
],
'require-await': ['error'],
semi: ['error', 'always'],
},
};

View File

@ -1,3 +0,0 @@
# This repo is a mirror of https://github.com/solana-labs/solana/tree/master/web3.js
Please make changes directly to the main Solana repo: https://github.com/solana-labs/solana

View File

@ -1,36 +0,0 @@
name: CI/CD
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: "npm"
- name: Build
run: |
scripts/test.sh
- name: Publish NPM
run: |
npx semantic-release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Deploy Github Page
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc

29
web3.js/.gitignore vendored
View File

@ -1,29 +0,0 @@
# IDE & OS specific
.DS_Store
.idea
# Logs
logs
*.log
# Dependencies
node_modules
# Coverage
coverage
.nyc_output
# Release
lib
# Generated docs
doc
# VIM swap files
*.sw*
# `solana-test-validator` ledger location
test-ledger/
# TypeScript
declarations

View File

@ -1,23 +0,0 @@
pull_request_rules:
- name: automatic merge on CI success
conditions:
- status-success=continuous-integration/travis-ci/pr
- label=automerge
actions:
merge:
method: rebase
strict: true
- name: remove automerge label on CI failure
conditions:
- status-failure=continuous-integration/travis-ci/pr
- label=automerge
actions:
label:
remove:
- automerge
- name: remove outdated reviews
conditions:
- base=master
actions:
dismiss_reviews:
changes_requested: true

View File

@ -1,2 +0,0 @@
test/dist
declarations

View File

@ -1,6 +0,0 @@
arrowParens: "avoid"
bracketSpacing: false
semi: true
singleQuote: true
tabWidth: 2
trailingComma: "all"

View File

@ -1,7 +0,0 @@
{
"repositoryUrl": "git@github.com:solana-labs/solana-web3.js.git",
"preset": "conventionalcommits",
"presetConfig": {
"issueUrlFormat": "{{host}}/{{owner}}/solana/issues/{{id}}"
}
}

View File

@ -1,11 +0,0 @@
{
"scope": true,
"body": true,
"emoji": false,
"lowercaseTypes": true,
"rules": {
"maxChar": 72,
"minChar": 10,
"endWithDot": false
}
}

View File

@ -1,29 +0,0 @@
✨ Thanks for contributing to **solana-web3.js**! ✨
As a contributor, here are the guidelines we would like you to follow:
* Ensure `npm run ok` passes before submitting a Pull Request
* Features and bug fixes should be covered by new test cases
* Commits follow the [Angular commit convention](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines)
## Creating releases
We use [semantic-release](https://github.com/semantic-release/semantic-release)
to release new versions automatically from the `master` branch:
* Commits of type `fix` will trigger bugfix releases, think `0.0.1`
* Commits of type `feat` will trigger feature releases, think `0.1.0`
* Commits with `BREAKING CHANGE` in body or footer will trigger breaking releases, think `1.0.0`
All other commit types will trigger no new release.
## Reference
### Static Analysis
eslint and TypeScript are used.
### Testing Framework
https://mochajs.org/
### API Documentation
TypeDoc is used to document the public API. See
https://typedoc.org/ for details.

View File

@ -1,20 +0,0 @@
Copyright (c) 2018 Solana Labs, Inc
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,157 +0,0 @@
[![codecov][codecov-image]][codecov-url]
<br>
[![npm][npm-image]][npm-url]
[![npm-downloads][npm-downloads-image]][npm-url]
<br>
[![semantic-release][semantic-release-image]][semantic-release-url]
[![code-style-prettier][code-style-prettier-image]][code-style-prettier-url]
[codecov-image]: https://codecov.io/gh/solana-labs/solana-web3.js/branch/master/graph/badge.svg
[codecov-url]: https://codecov.io/gh/solana-labs/solana-web3.js
[npm-image]: https://img.shields.io/npm/v/@solana/web3.js.svg?style=flat
[npm-downloads-image]: https://img.shields.io/npm/dm/@solana/web3.js.svg?style=flat
[npm-url]: https://www.npmjs.com/package/@solana/web3.js
[semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[semantic-release-url]: https://github.com/semantic-release/semantic-release
[code-style-prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square
[code-style-prettier-url]: https://github.com/prettier/prettier
# Solana JavaScript API
This is the Solana Javascript API built on the Solana [JSON RPC API](https://docs.solana.com/apps/jsonrpc-api)
## Documentation and examples
- [The Solana Cookbook](https://solanacookbook.com/) has extensive task-based documentation using this library.
- For more detail on individual functions, see the [latest API Documentation](https://solana-labs.github.io/solana-web3.js/)
## Installation
### Yarn
```
$ yarn add @solana/web3.js
```
### npm
```
$ npm install --save @solana/web3.js
```
### Browser bundle
```html
<!-- Development (un-minified) -->
<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.js"></script>
<!-- Production (minified) -->
<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.min.js"></script>
```
## Development Environment Setup
Install the latest Solana release from https://docs.solana.com/cli/install-solana-cli-tools
### Run test validator
**Use `solana-test-validator` from the latest Solana release**
### SBF program development
**Use `cargo build-bpf` from the latest Solana release**
## Usage
### Javascript
```js
const solanaWeb3 = require('@solana/web3.js');
console.log(solanaWeb3);
```
### ES6
```js
import * as solanaWeb3 from '@solana/web3.js';
console.log(solanaWeb3);
```
### Browser bundle
```js
// `solanaWeb3` is provided in the global namespace by the `solanaWeb3.min.js` script bundle.
console.log(solanaWeb3);
```
## Compatibility
This library requires a JavaScript runtime that supports [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) and the [exponentiation operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation). Both are supported in the following runtimes:
- Browsers, by [release date](https://caniuse.com/bigint):
- Chrome: May 2018
- Firefox: July 2019
- Safari: September 2020
- Mobile Safari: September 2020
- Edge: January 2020
- Opera: June 2018
- Samsung Internet: April 2019
- Runtimes, [by version](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt):
- Deno: >=1.0
- Node: >=10.4.0
- React Native:
- \>=0.7.0 using the [Hermes](https://reactnative.dev/blog/2022/07/08/hermes-as-the-default) engine ([integration guide](https://solanacookbook.com/integrations/react-native.html#how-to-use-solana-web3-js-in-a-react-native-app)):
## Releases
Releases are available on [Github](https://github.com/solana-labs/solana-web3.js/releases)
and [npmjs.com](https://www.npmjs.com/package/@solana/web3.js)
Each Github release features a tarball containing API documentation and a
minified version of the module suitable for direct use in a browser environment
(`<script>` tag)
## Contributing
If you have an issue to report or would like to contribute a pull request, please do so against the monorepo at https://github.com/solana-labs/solana. We are not able to merge pull requests into the mirror repo https://github.com/solana-labs/solana-web3.js and issues filed there may go unnoticed.
## Disclaimer
All claims, content, designs, algorithms, estimates, roadmaps,
specifications, and performance measurements described in this project
are done with the Solana Foundation's ("SF") best efforts. It is up to
the reader to check and validate their accuracy and truthfulness.
Furthermore nothing in this project constitutes a solicitation for
investment.
Any content produced by SF or developer resources that SF provides, are
for educational and inspiration purposes only. SF does not encourage,
induce or sanction the deployment, integration or use of any such
applications (including the code comprising the Solana blockchain
protocol) in violation of applicable laws or regulations and hereby
prohibits any such deployment, integration or use. This includes use of
any such applications by the reader (a) in violation of export control
or sanctions laws of the United States or any other applicable
jurisdiction, (b) if the reader is located in or ordinarily resident in
a country or territory subject to comprehensive sanctions administered
by the U.S. Office of Foreign Assets Control (OFAC), or (c) if the
reader is or is working on behalf of a Specially Designated National
(SDN) or a person subject to similar blocking or denied party
prohibitions.
The reader should be aware that U.S. export control and sanctions laws
prohibit U.S. persons (and other persons that are subject to such laws)
from transacting with persons in certain countries and territories or
that are on the SDN list. As a project based primarily on open-source
software, it is possible that such sanctioned persons may nevertheless
bypass prohibitions, obtain the code comprising the Solana blockchain
protocol (or other project code or applications) and deploy, integrate,
or otherwise use it. Accordingly, there is a risk to individuals that
other persons using the Solana blockchain protocol may be sanctioned
persons and that transactions with such persons would be a violation of
U.S. export controls and sanctions law. This risk applies to
individuals, organizations, and other ecosystem participants that
deploy, integrate, or use the Solana blockchain protocol code directly
(e.g., as a node operator), and individuals that transact on the Solana
blockchain through light clients, third party interfaces, and/or wallet
software.

View File

@ -1,31 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"bugfixes": true
}
],
["@babel/preset-typescript"]
],
"plugins": [
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
],
[
"@babel/plugin-proposal-private-methods",
{
"loose": true
}
],
[
"@babel/plugin-proposal-private-property-in-object",
{
"loose": true
}
]
]
}

View File

@ -1,3 +0,0 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
};

View File

@ -1,32 +0,0 @@
#!/usr/bin/env bash
#
# Runs commitlint in the provided subdirectory
#
set -e
basedir=$1
if [[ -z "$basedir" ]]; then
basedir=.
fi
if [[ ! -d "$basedir" ]]; then
echo "Error: not a directory: $basedir"
exit 1
fi
if [[ ! -f "$basedir"/commitlint.config.js ]]; then
echo "Error: No commitlint configuration found"
exit 1
fi
if [[ -z $COMMIT_RANGE ]]; then
echo "Error: COMMIT_RANGE not defined"
exit 1
fi
cd "$basedir"
echo "Checking commits in COMMIT_RANGE: $COMMIT_RANGE"
while IFS= read -r line; do
echo "$line" | npx commitlint
done < <(git log "$COMMIT_RANGE" --format=%s -- .)

27191
web3.js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,140 +0,0 @@
{
"name": "@solana/web3.js",
"version": "0.0.0-development",
"description": "Solana Javascript API",
"keywords": [
"api",
"blockchain"
],
"license": "MIT",
"author": "Solana Labs Maintainers <maintainers@solanalabs.com>",
"homepage": "https://solana.com/",
"repository": {
"type": "git",
"url": "https://github.com/solana-labs/solana-web3.js.git"
},
"bugs": {
"url": "http://github.com/solana-labs/solana-web3.js.git/issues"
},
"publishConfig": {
"access": "public"
},
"browser": {
"./lib/index.cjs.js": "./lib/index.browser.cjs.js",
"./lib/index.esm.js": "./lib/index.browser.esm.js"
},
"react-native": "lib/index.native.js",
"main": "lib/index.cjs.js",
"module": "lib/index.esm.js",
"types": "lib/index.d.ts",
"browserslist": [
"defaults",
"not IE 11",
"maintained node versions"
],
"files": [
"/lib",
"/src"
],
"scripts": {
"build": "npm run clean; cross-env NODE_ENV=production rollup -c; npm run type:gen",
"build:fixtures": "set -ex; ./test/fixtures/noop-program/build.sh",
"clean": "rimraf ./coverage ./lib",
"codecov": "set -ex; npm run test:cover; cat ./coverage/lcov.info | codecov",
"dev": "cross-env NODE_ENV=development rollup -c",
"doc": "set -ex; typedoc --tsconfig ./tsconfig.library.json --treatWarningsAsErrors",
"type:gen": "./scripts/typegen.sh",
"lint": "set -ex; npm run pretty; eslint . --ext .js,.ts",
"lint:fix": "npm run pretty:fix && eslint . --fix --ext .js,.ts",
"type:check": "tsc -p tsconfig.library.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
"ok": "run-s lint test doc type:check",
"pretty": "prettier --check '{,{src,test}/**/}*.{j,t}s'",
"pretty:fix": "prettier --write '{,{src,test}/**/}*.{j,t}s'",
"re": "semantic-release --repository-url git@github.com:solana-labs/solana-web3.js.git",
"test": "cross-env NODE_ENV=test TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\", \"target\": \"es2019\" }' ts-mocha -p ./tsconfig.tests.json --require esm './test/**/*.test.ts'",
"test:cover": "nyc --reporter=lcov npm run test",
"test:live": "TEST_LIVE=1 npm run test",
"test:live-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:live"
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@noble/ed25519": "^1.7.0",
"@noble/hashes": "^1.1.2",
"@noble/secp256k1": "^1.6.3",
"@solana/buffer-layout": "^4.0.0",
"agentkeepalive": "^4.2.1",
"bigint-buffer": "^1.1.5",
"bn.js": "^5.0.0",
"borsh": "^0.7.0",
"bs58": "^4.0.1",
"buffer": "6.0.1",
"fast-stable-stringify": "^1.0.0",
"jayson": "^3.4.4",
"node-fetch": "2",
"rpc-websockets": "^7.5.0",
"superstruct": "^0.14.2"
},
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.16",
"@commitlint/config-conventional": "^17.0.2",
"@rollup/plugin-alias": "^4.0.3",
"@rollup/plugin-babel": "^5.2.3",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-multi-entry": "^4.0.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-replace": "^4.0.0",
"@solana/spl-token": "^0.2.0",
"@types/bn.js": "^5.1.0",
"@types/bs58": "^4.0.1",
"@types/chai": "^4.2.15",
"@types/chai-as-promised": "^7.1.3",
"@types/express-serve-static-core": "^4.17.21",
"@types/mocha": "^10.0.0",
"@types/mz": "^2.7.3",
"@types/node": "^18.11.10",
"@types/node-fetch": "2",
"@types/sinon": "^10.0.0",
"@types/sinon-chai": "^3.2.8",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1",
"chai": "^4.3.0",
"chai-as-promised": "^7.1.1",
"codecov": "^3.0.4",
"cross-env": "7.0.3",
"eslint": "^8.25.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-prettier": "^4.2.1",
"esm": "^3.2.25",
"mocha": "^10.1.0",
"mockttp": "^3.6.2",
"mz": "^2.7.0",
"node-abort-controller": "^3.0.1",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"prettier": "^2.3.0",
"rimraf": "3.0.2",
"rollup": "2.79.1",
"rollup-plugin-dts": "^4.0.0",
"rollup-plugin-node-polyfills": "^0.2.1",
"rollup-plugin-terser": "^7.0.2",
"semantic-release": "^19.0.3",
"sinon": "^13.0.2",
"sinon-chai": "^3.7.0",
"start-server-and-test": "^1.12.0",
"ts-mocha": "^10.0.0",
"ts-node": "^10.0.0",
"tslib": "^2.1.0",
"typedoc": "^0.23",
"typescript": "^4.9"
},
"engines": {
"node": ">=12.20.0"
}
}

View File

@ -1,223 +0,0 @@
import alias from '@rollup/plugin-alias';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import * as fs from 'fs';
import path from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import {terser} from 'rollup-plugin-terser';
const env = process.env.NODE_ENV;
const extensions = ['.js', '.ts'];
function generateConfig(configType, format) {
const browser = configType === 'browser' || configType === 'react-native';
const bundle = format === 'iife';
const config = {
input: 'src/index.ts',
plugins: [
alias({
entries: [
{
find: /^\./, // Relative paths.
replacement: '.',
async customResolver(source, importer, options) {
const resolved = await this.resolve(source, importer, {
skipSelf: true,
...options,
});
if (resolved == null) {
return;
}
const {id: resolvedId} = resolved;
const directory = path.dirname(resolvedId);
const moduleFilename = path.basename(resolvedId);
const forkPath = path.join(
directory,
'__forks__',
configType,
moduleFilename,
);
const hasForkCacheKey = `has_fork:${forkPath}`;
let hasFork = this.cache.get(hasForkCacheKey);
if (hasFork === undefined) {
hasFork = fs.existsSync(forkPath);
this.cache.set(hasForkCacheKey, hasFork);
}
if (hasFork) {
return forkPath;
}
},
},
],
}),
commonjs(),
nodeResolve({
browser,
dedupe: ['bn.js', 'buffer'],
extensions,
preferBuiltins: !browser,
}),
babel({
exclude: '**/node_modules/**',
extensions,
babelHelpers: bundle ? 'bundled' : 'runtime',
plugins: bundle ? [] : ['@babel/plugin-transform-runtime'],
}),
replace({
preventAssignment: true,
values: {
'process.env.NODE_ENV': JSON.stringify(env),
'process.env.BROWSER': JSON.stringify(browser),
'process.env.npm_package_version': JSON.stringify(
process.env.npm_package_version,
),
},
}),
],
onwarn: function (warning, rollupWarn) {
rollupWarn(warning);
if (warning.code === 'CIRCULAR_DEPENDENCY') {
throw new Error(
'Please eliminate the circular dependencies listed ' +
'above and retry the build',
);
}
},
treeshake: {
moduleSideEffects: false,
},
};
if (!browser) {
// Prevent dependencies from being bundled
config.external = [
/@babel\/runtime/,
'@noble/hashes/hmac',
'@noble/hashes/sha256',
'@noble/hashes/sha3',
'@noble/hashes/sha512',
'@noble/ed25519',
'@noble/secp256k1',
'@solana/buffer-layout',
'bigint-buffer',
'bn.js',
'borsh',
'bs58',
'buffer',
'crypto-hash',
'jayson/lib/client/browser',
'node-fetch',
'rpc-websockets',
'rpc-websockets/dist/lib/client',
'rpc-websockets/dist/lib/client/client.types',
'rpc-websockets/dist/lib/client/websocket',
'rpc-websockets/dist/lib/client/websocket.browser',
'superstruct',
];
}
switch (configType) {
case 'browser':
case 'react-native':
switch (format) {
case 'iife': {
config.external = ['http', 'https', 'node-fetch'];
config.output = [
{
file: 'lib/index.iife.js',
format: 'iife',
name: 'solanaWeb3',
sourcemap: true,
},
{
file: 'lib/index.iife.min.js',
format: 'iife',
name: 'solanaWeb3',
sourcemap: true,
plugins: [terser({mangle: false, compress: false})],
},
];
break;
}
default: {
config.output = [
{
file: `lib/index.${
configType === 'react-native' ? 'native' : 'browser.cjs'
}.js`,
format: 'cjs',
sourcemap: true,
},
configType === 'browser'
? {
file: 'lib/index.browser.esm.js',
format: 'es',
sourcemap: true,
}
: null,
].filter(Boolean);
// Prevent dependencies from being bundled
config.external = [
/@babel\/runtime/,
'@solana/buffer-layout',
'@noble/hashes/hmac',
'@noble/hashes/sha256',
'@noble/hashes/sha3',
'@noble/hashes/sha512',
'@noble/ed25519',
'@noble/secp256k1',
'bigint-buffer',
'bn.js',
'borsh',
'bs58',
'buffer',
'crypto-hash',
'http',
'https',
'jayson/lib/client/browser',
'node-fetch',
'react-native-url-polyfill',
'rpc-websockets',
'rpc-websockets/dist/lib/client',
'rpc-websockets/dist/lib/client/client.types',
'rpc-websockets/dist/lib/client/websocket',
'rpc-websockets/dist/lib/client/websocket.browser',
'superstruct',
];
break;
}
}
break;
case 'node':
config.output = [
{
file: 'lib/index.cjs.js',
format: 'cjs',
sourcemap: true,
},
{
file: 'lib/index.esm.js',
format: 'es',
sourcemap: true,
},
];
break;
default:
throw new Error(`Unknown configType: ${configType}`);
}
return config;
}
export default [
generateConfig('node'),
generateConfig('browser'),
generateConfig('browser', 'iife'),
generateConfig('react-native'),
];

View File

@ -1,8 +0,0 @@
import dts from 'rollup-plugin-dts';
export default {
input: './declarations/index.d.ts',
output: [{file: 'lib/index.d.ts', format: 'es'}],
plugins: [dts()],
external: ['http', 'https'],
};

View File

@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -ex
# setup environment
sh -c "$(curl -sSfL https://release.solana.com/edge/install)"
PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
solana --version
# build and test
npm install
npm run build
ls -l lib
test -r lib/index.iife.js
test -r lib/index.cjs.js
test -r lib/index.esm.js
npm run ok
npm run codecov
npm run test:live-with-test-validator

View File

@ -1,25 +0,0 @@
set -e
# Generate typescript declarations
npx tsc -p tsconfig.d.json -d
# Flatten typescript declarations
npx rollup -c rollup.config.types.js
# Replace export with closing brace for module declaration
sed -i.bak '$s/export {.*};/}/' lib/index.d.ts
# Replace declare's with export's
sed -i.bak 's/declare/export/g' lib/index.d.ts
# Prepend declare module line
sed -i.bak '2s;^;declare module "@solana/web3.js" {\n;' lib/index.d.ts
# Remove backup file from `sed` above
rm lib/index.d.ts.bak
# Run prettier
npx prettier --write lib/index.d.ts
# Check result
npx tsc lib/index.d.ts

View File

@ -1,4 +0,0 @@
export const Headers = globalThis.Headers;
export const Request = globalThis.Request;
export const Response = globalThis.Response;
export default globalThis.fetch;

View File

@ -1 +0,0 @@
export {default} from 'rpc-websockets/dist/lib/client/websocket.browser';

View File

@ -1,4 +0,0 @@
export const Headers = globalThis.Headers;
export const Request = globalThis.Request;
export const Response = globalThis.Response;
export default globalThis.fetch;

View File

@ -1 +0,0 @@
export {default} from 'rpc-websockets/dist/lib/client/websocket.browser';

View File

@ -1,39 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
export interface IAccountStateData {
readonly typeIndex: number;
}
/**
* @internal
*/
export type AccountType<TInputData extends IAccountStateData> = {
/** The account type index (from solana upstream program) */
index: number;
/** The BufferLayout to use to build data */
layout: BufferLayout.Layout<TInputData>;
};
/**
* Decode account data buffer using an AccountType
* @internal
*/
export function decodeData<TAccountStateData extends IAccountStateData>(
type: AccountType<TAccountStateData>,
data: Uint8Array,
): TAccountStateData {
let decoded: TAccountStateData;
try {
decoded = type.layout.decode(data);
} catch (err) {
throw new Error('invalid instruction; ' + err);
}
if (decoded.typeIndex !== type.index) {
throw new Error(
`invalid account data; account type mismatch ${decoded.typeIndex} != ${type.index}`,
);
}
return decoded;
}

View File

@ -1,55 +0,0 @@
import {Buffer} from 'buffer';
import {generatePrivateKey, getPublicKey} from './utils/ed25519';
import {toBuffer} from './utils/to-buffer';
import {PublicKey} from './publickey';
/**
* An account key pair (public and secret keys).
*
* @deprecated since v1.10.0, please use {@link Keypair} instead.
*/
export class Account {
/** @internal */
private _publicKey: Buffer;
/** @internal */
private _secretKey: Buffer;
/**
* Create a new Account object
*
* If the secretKey parameter is not provided a new key pair is randomly
* created for the account
*
* @param secretKey Secret key for the account
*/
constructor(secretKey?: Uint8Array | Array<number>) {
if (secretKey) {
const secretKeyBuffer = toBuffer(secretKey);
if (secretKey.length !== 64) {
throw new Error('bad secret key size');
}
this._publicKey = secretKeyBuffer.slice(32, 64);
this._secretKey = secretKeyBuffer.slice(0, 32);
} else {
this._secretKey = toBuffer(generatePrivateKey());
this._publicKey = toBuffer(getPublicKey(this._secretKey));
}
}
/**
* The public key for this account
*/
get publicKey(): PublicKey {
return new PublicKey(this._publicKey);
}
/**
* The **unencrypted** secret key for this account. The first 32 bytes
* is the private scalar and the last 32 bytes is the public key.
* Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
get secretKey(): Buffer {
return Buffer.concat([this._secretKey, this._publicKey], 64);
}
}

View File

@ -1,4 +0,0 @@
/**
* Blockhash as Base58 string.
*/
export type Blockhash = string;

View File

@ -1,5 +0,0 @@
import {PublicKey} from './publickey';
export const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey(
'BPFLoader1111111111111111111111111111111111',
);

View File

@ -1,45 +0,0 @@
import type {Buffer} from 'buffer';
import {PublicKey} from './publickey';
import {Loader} from './loader';
import type {Connection} from './connection';
import type {Signer} from './keypair';
export const BPF_LOADER_PROGRAM_ID = new PublicKey(
'BPFLoader2111111111111111111111111111111111',
);
/**
* Factory class for transactions to interact with a program loader
*/
export class BpfLoader {
/**
* Minimum number of signatures required to load a program not including
* retries
*
* Can be used to calculate transaction fees
*/
static getMinNumSignatures(dataLength: number): number {
return Loader.getMinNumSignatures(dataLength);
}
/**
* Load a SBF program
*
* @param connection The connection to use
* @param payer Account that will pay program loading fees
* @param program Account to load the program into
* @param elf The entire ELF containing the SBF program
* @param loaderProgramId The program id of the BPF loader to use
* @return true if program was loaded successfully, false if program was already loaded
*/
static load(
connection: Connection,
payer: Signer,
program: Signer,
elf: Buffer | Uint8Array | Array<number>,
loaderProgramId: PublicKey,
): Promise<boolean> {
return Loader.load(connection, payer, program, loaderProgramId, elf);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,102 +0,0 @@
const MINIMUM_SLOT_PER_EPOCH = 32;
// Returns the number of trailing zeros in the binary representation of self.
function trailingZeros(n: number) {
let trailingZeros = 0;
while (n > 1) {
n /= 2;
trailingZeros++;
}
return trailingZeros;
}
// Returns the smallest power of two greater than or equal to n
function nextPowerOfTwo(n: number) {
if (n === 0) return 1;
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n |= n >> 32;
return n + 1;
}
/**
* Epoch schedule
* (see https://docs.solana.com/terminology#epoch)
* Can be retrieved with the {@link Connection.getEpochSchedule} method
*/
export class EpochSchedule {
/** The maximum number of slots in each epoch */
public slotsPerEpoch: number;
/** The number of slots before beginning of an epoch to calculate a leader schedule for that epoch */
public leaderScheduleSlotOffset: number;
/** Indicates whether epochs start short and grow */
public warmup: boolean;
/** The first epoch with `slotsPerEpoch` slots */
public firstNormalEpoch: number;
/** The first slot of `firstNormalEpoch` */
public firstNormalSlot: number;
constructor(
slotsPerEpoch: number,
leaderScheduleSlotOffset: number,
warmup: boolean,
firstNormalEpoch: number,
firstNormalSlot: number,
) {
this.slotsPerEpoch = slotsPerEpoch;
this.leaderScheduleSlotOffset = leaderScheduleSlotOffset;
this.warmup = warmup;
this.firstNormalEpoch = firstNormalEpoch;
this.firstNormalSlot = firstNormalSlot;
}
getEpoch(slot: number): number {
return this.getEpochAndSlotIndex(slot)[0];
}
getEpochAndSlotIndex(slot: number): [number, number] {
if (slot < this.firstNormalSlot) {
const epoch =
trailingZeros(nextPowerOfTwo(slot + MINIMUM_SLOT_PER_EPOCH + 1)) -
trailingZeros(MINIMUM_SLOT_PER_EPOCH) -
1;
const epochLen = this.getSlotsInEpoch(epoch);
const slotIndex = slot - (epochLen - MINIMUM_SLOT_PER_EPOCH);
return [epoch, slotIndex];
} else {
const normalSlotIndex = slot - this.firstNormalSlot;
const normalEpochIndex = Math.floor(normalSlotIndex / this.slotsPerEpoch);
const epoch = this.firstNormalEpoch + normalEpochIndex;
const slotIndex = normalSlotIndex % this.slotsPerEpoch;
return [epoch, slotIndex];
}
}
getFirstSlotInEpoch(epoch: number): number {
if (epoch <= this.firstNormalEpoch) {
return (Math.pow(2, epoch) - 1) * MINIMUM_SLOT_PER_EPOCH;
} else {
return (
(epoch - this.firstNormalEpoch) * this.slotsPerEpoch +
this.firstNormalSlot
);
}
}
getLastSlotInEpoch(epoch: number): number {
return this.getFirstSlotInEpoch(epoch) + this.getSlotsInEpoch(epoch) - 1;
}
getSlotsInEpoch(epoch: number) {
if (epoch < this.firstNormalEpoch) {
return Math.pow(2, epoch + trailingZeros(MINIMUM_SLOT_PER_EPOCH));
} else {
return this.slotsPerEpoch;
}
}
}

View File

@ -1,50 +0,0 @@
export class SendTransactionError extends Error {
logs: string[] | undefined;
constructor(message: string, logs?: string[]) {
super(message);
this.logs = logs;
}
}
// Keep in sync with client/src/rpc_custom_errors.rs
// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/
export const SolanaJSONRPCErrorCode = {
JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: -32001,
JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: -32002,
JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: -32003,
JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE: -32004,
JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY: -32005,
JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: -32006,
JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: -32007,
JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: -32008,
JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: -32009,
JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: -32010,
JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: -32011,
JSON_RPC_SCAN_ERROR: -32012,
JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: -32013,
JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: -32014,
JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: -32015,
JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: -32016,
} as const;
export type SolanaJSONRPCErrorCodeEnum =
typeof SolanaJSONRPCErrorCode[keyof typeof SolanaJSONRPCErrorCode];
export class SolanaJSONRPCError extends Error {
code: SolanaJSONRPCErrorCodeEnum | unknown;
data?: any;
constructor(
{
code,
message,
data,
}: Readonly<{code: unknown; message: string; data?: any}>,
customMessage?: string,
) {
super(customMessage != null ? `${customMessage}: ${message}` : message);
this.code = code;
this.data = data;
this.name = 'SolanaJSONRPCError';
}
}

View File

@ -1,18 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
/**
* https://github.com/solana-labs/solana/blob/90bedd7e067b5b8f3ddbb45da00a4e9cabb22c62/sdk/src/fee_calculator.rs#L7-L11
*
* @internal
*/
export const FeeCalculatorLayout = BufferLayout.nu64('lamportsPerSignature');
/**
* Calculator for transaction fees.
*
* @deprecated Deprecated since Solana v1.8.0.
*/
export interface FeeCalculator {
/** Cost in lamports to validate a signature. */
lamportsPerSignature: number;
}

View File

@ -1,13 +0,0 @@
import * as nodeFetch from 'node-fetch';
export * from 'node-fetch';
export default async function (
input: nodeFetch.RequestInfo,
init?: nodeFetch.RequestInit,
): Promise<nodeFetch.Response> {
const processedInput =
typeof input === 'string' && input.slice(0, 2) === '//'
? 'https:' + input
: input;
return await nodeFetch.default(processedInput, init);
}

View File

@ -1,24 +0,0 @@
export * from './account';
export * from './blockhash';
export * from './bpf-loader-deprecated';
export * from './bpf-loader';
export * from './connection';
export * from './epoch-schedule';
export * from './errors';
export * from './fee-calculator';
export * from './keypair';
export * from './loader';
export * from './message';
export * from './nonce-account';
export * from './programs';
export * from './publickey';
export * from './transaction';
export * from './validator-info';
export * from './vote-account';
export * from './sysvar';
export * from './utils';
/**
* There are 1-billion lamports in one SOL
*/
export const LAMPORTS_PER_SOL = 1000000000;

View File

@ -1,58 +0,0 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import * as Layout from './layout';
export interface IInstructionInputData {
readonly instruction: number;
}
/**
* @internal
*/
export type InstructionType<TInputData extends IInstructionInputData> = {
/** The Instruction index (from solana upstream program) */
index: number;
/** The BufferLayout to use to build data */
layout: BufferLayout.Layout<TInputData>;
};
/**
* Populate a buffer of instruction data using an InstructionType
* @internal
*/
export function encodeData<TInputData extends IInstructionInputData>(
type: InstructionType<TInputData>,
fields?: any,
): Buffer {
const allocLength =
type.layout.span >= 0 ? type.layout.span : Layout.getAlloc(type, fields);
const data = Buffer.alloc(allocLength);
const layoutFields = Object.assign({instruction: type.index}, fields);
type.layout.encode(layoutFields, data);
return data;
}
/**
* Decode instruction data buffer using an InstructionType
* @internal
*/
export function decodeData<TInputData extends IInstructionInputData>(
type: InstructionType<TInputData>,
buffer: Buffer,
): TInputData {
let data: TInputData;
try {
data = type.layout.decode(buffer);
} catch (err) {
throw new Error('invalid instruction; ' + err);
}
if (data.instruction !== type.index) {
throw new Error(
`invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`,
);
}
return data;
}

View File

@ -1,93 +0,0 @@
import {generateKeypair, getPublicKey, Ed25519Keypair} from './utils/ed25519';
import {PublicKey} from './publickey';
/**
* Keypair signer interface
*/
export interface Signer {
publicKey: PublicKey;
secretKey: Uint8Array;
}
/**
* An account keypair used for signing transactions.
*/
export class Keypair {
private _keypair: Ed25519Keypair;
/**
* Create a new keypair instance.
* Generate random keypair if no {@link Ed25519Keypair} is provided.
*
* @param keypair ed25519 keypair
*/
constructor(keypair?: Ed25519Keypair) {
this._keypair = keypair ?? generateKeypair();
}
/**
* Generate a new random keypair
*/
static generate(): Keypair {
return new Keypair(generateKeypair());
}
/**
* Create a keypair from a raw secret key byte array.
*
* This method should only be used to recreate a keypair from a previously
* generated secret key. Generating keypairs from a random seed should be done
* with the {@link Keypair.fromSeed} method.
*
* @throws error if the provided secret key is invalid and validation is not skipped.
*
* @param secretKey secret key byte array
* @param options: skip secret key validation
*/
static fromSecretKey(
secretKey: Uint8Array,
options?: {skipValidation?: boolean},
): Keypair {
if (secretKey.byteLength !== 64) {
throw new Error('bad secret key size');
}
const publicKey = secretKey.slice(32, 64);
if (!options || !options.skipValidation) {
const privateScalar = secretKey.slice(0, 32);
const computedPublicKey = getPublicKey(privateScalar);
for (let ii = 0; ii < 32; ii++) {
if (publicKey[ii] !== computedPublicKey[ii]) {
throw new Error('provided secretKey is invalid');
}
}
}
return new Keypair({publicKey, secretKey});
}
/**
* Generate a keypair from a 32 byte seed.
*
* @param seed seed byte array
*/
static fromSeed(seed: Uint8Array): Keypair {
const publicKey = getPublicKey(seed);
const secretKey = new Uint8Array(64);
secretKey.set(seed);
secretKey.set(publicKey, 32);
return new Keypair({publicKey, secretKey});
}
/**
* The public key for this keypair
*/
get publicKey(): PublicKey {
return new PublicKey(this._keypair.publicKey);
}
/**
* The raw secret key for this keypair
*/
get secretKey(): Uint8Array {
return new Uint8Array(this._keypair.secretKey);
}
}

View File

@ -1,188 +0,0 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import {VoteAuthorizeWithSeedArgs} from './programs/vote';
/**
* Layout for a public key
*/
export const publicKey = (property: string = 'publicKey') => {
return BufferLayout.blob(32, property);
};
/**
* Layout for a signature
*/
export const signature = (property: string = 'signature') => {
return BufferLayout.blob(64, property);
};
/**
* Layout for a 64bit unsigned value
*/
export const uint64 = (property: string = 'uint64') => {
return BufferLayout.blob(8, property);
};
interface IRustStringShim
extends Omit<
BufferLayout.Structure<
Readonly<{
length: number;
lengthPadding: number;
chars: Uint8Array;
}>
>,
'decode' | 'encode' | 'replicate'
> {
alloc: (str: string) => number;
decode: (b: Uint8Array, offset?: number) => string;
encode: (str: string, b: Uint8Array, offset?: number) => number;
replicate: (property: string) => this;
}
/**
* Layout for a Rust String type
*/
export const rustString = (
property: string = 'string',
): BufferLayout.Layout<string> => {
const rsl = BufferLayout.struct<
Readonly<{
length?: number;
lengthPadding?: number;
chars: Uint8Array;
}>
>(
[
BufferLayout.u32('length'),
BufferLayout.u32('lengthPadding'),
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
],
property,
);
const _decode = rsl.decode.bind(rsl);
const _encode = rsl.encode.bind(rsl);
const rslShim = rsl as unknown as IRustStringShim;
rslShim.decode = (b: Uint8Array, offset?: number) => {
const data = _decode(b, offset);
return data['chars'].toString();
};
rslShim.encode = (str: string, b: Uint8Array, offset?: number) => {
const data = {
chars: Buffer.from(str, 'utf8'),
};
return _encode(data, b, offset);
};
rslShim.alloc = (str: string) => {
return (
BufferLayout.u32().span +
BufferLayout.u32().span +
Buffer.from(str, 'utf8').length
);
};
return rslShim;
};
/**
* Layout for an Authorized object
*/
export const authorized = (property: string = 'authorized') => {
return BufferLayout.struct<
Readonly<{
staker: Uint8Array;
withdrawer: Uint8Array;
}>
>([publicKey('staker'), publicKey('withdrawer')], property);
};
/**
* Layout for a Lockup object
*/
export const lockup = (property: string = 'lockup') => {
return BufferLayout.struct<
Readonly<{
custodian: Uint8Array;
epoch: number;
unixTimestamp: number;
}>
>(
[
BufferLayout.ns64('unixTimestamp'),
BufferLayout.ns64('epoch'),
publicKey('custodian'),
],
property,
);
};
/**
* Layout for a VoteInit object
*/
export const voteInit = (property: string = 'voteInit') => {
return BufferLayout.struct<
Readonly<{
authorizedVoter: Uint8Array;
authorizedWithdrawer: Uint8Array;
commission: number;
nodePubkey: Uint8Array;
}>
>(
[
publicKey('nodePubkey'),
publicKey('authorizedVoter'),
publicKey('authorizedWithdrawer'),
BufferLayout.u8('commission'),
],
property,
);
};
/**
* Layout for a VoteAuthorizeWithSeedArgs object
*/
export const voteAuthorizeWithSeedArgs = (
property: string = 'voteAuthorizeWithSeedArgs',
) => {
return BufferLayout.struct<VoteAuthorizeWithSeedArgs>(
[
BufferLayout.u32('voteAuthorizationType'),
publicKey('currentAuthorityDerivedKeyOwnerPubkey'),
rustString('currentAuthorityDerivedKeySeed'),
publicKey('newAuthorized'),
],
property,
);
};
export function getAlloc(type: any, fields: any): number {
const getItemAlloc = (item: any): number => {
if (item.span >= 0) {
return item.span;
} else if (typeof item.alloc === 'function') {
return item.alloc(fields[item.property]);
} else if ('count' in item && 'elementLayout' in item) {
const field = fields[item.property];
if (Array.isArray(field)) {
return field.length * getItemAlloc(item.elementLayout);
}
} else if ('fields' in item) {
// This is a `Structure` whose size needs to be recursively measured.
return getAlloc({layout: item}, fields[item.property]);
}
// Couldn't determine allocated size of layout
return 0;
};
let alloc = 0;
type.layout.fields.forEach((item: any) => {
alloc += getItemAlloc(item);
});
return alloc;
}

View File

@ -1,236 +0,0 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import {PublicKey} from './publickey';
import {Transaction, PACKET_DATA_SIZE} from './transaction';
import {SYSVAR_RENT_PUBKEY} from './sysvar';
import {sendAndConfirmTransaction} from './utils/send-and-confirm-transaction';
import {sleep} from './utils/sleep';
import type {Connection} from './connection';
import type {Signer} from './keypair';
import {SystemProgram} from './programs/system';
import {IInstructionInputData} from './instruction';
// Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the
// rest of the Transaction fields
//
// TODO: replace 300 with a proper constant for the size of the other
// Transaction fields
const CHUNK_SIZE = PACKET_DATA_SIZE - 300;
/**
* Program loader interface
*/
export class Loader {
/**
* @internal
*/
constructor() {}
/**
* Amount of program data placed in each load Transaction
*/
static chunkSize: number = CHUNK_SIZE;
/**
* Minimum number of signatures required to load a program not including
* retries
*
* Can be used to calculate transaction fees
*/
static getMinNumSignatures(dataLength: number): number {
return (
2 * // Every transaction requires two signatures (payer + program)
(Math.ceil(dataLength / Loader.chunkSize) +
1 + // Add one for Create transaction
1) // Add one for Finalize transaction
);
}
/**
* Loads a generic program
*
* @param connection The connection to use
* @param payer System account that pays to load the program
* @param program Account to load the program into
* @param programId Public key that identifies the loader
* @param data Program octets
* @return true if program was loaded successfully, false if program was already loaded
*/
static async load(
connection: Connection,
payer: Signer,
program: Signer,
programId: PublicKey,
data: Buffer | Uint8Array | Array<number>,
): Promise<boolean> {
{
const balanceNeeded = await connection.getMinimumBalanceForRentExemption(
data.length,
);
// Fetch program account info to check if it has already been created
const programInfo = await connection.getAccountInfo(
program.publicKey,
'confirmed',
);
let transaction: Transaction | null = null;
if (programInfo !== null) {
if (programInfo.executable) {
console.error('Program load failed, account is already executable');
return false;
}
if (programInfo.data.length !== data.length) {
transaction = transaction || new Transaction();
transaction.add(
SystemProgram.allocate({
accountPubkey: program.publicKey,
space: data.length,
}),
);
}
if (!programInfo.owner.equals(programId)) {
transaction = transaction || new Transaction();
transaction.add(
SystemProgram.assign({
accountPubkey: program.publicKey,
programId,
}),
);
}
if (programInfo.lamports < balanceNeeded) {
transaction = transaction || new Transaction();
transaction.add(
SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: program.publicKey,
lamports: balanceNeeded - programInfo.lamports,
}),
);
}
} else {
transaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: program.publicKey,
lamports: balanceNeeded > 0 ? balanceNeeded : 1,
space: data.length,
programId,
}),
);
}
// If the account is already created correctly, skip this step
// and proceed directly to loading instructions
if (transaction !== null) {
await sendAndConfirmTransaction(
connection,
transaction,
[payer, program],
{
commitment: 'confirmed',
},
);
}
}
const dataLayout = BufferLayout.struct<
Readonly<{
bytes: number[];
bytesLength: number;
bytesLengthPadding: number;
instruction: number;
offset: number;
}>
>([
BufferLayout.u32('instruction'),
BufferLayout.u32('offset'),
BufferLayout.u32('bytesLength'),
BufferLayout.u32('bytesLengthPadding'),
BufferLayout.seq(
BufferLayout.u8('byte'),
BufferLayout.offset(BufferLayout.u32(), -8),
'bytes',
),
]);
const chunkSize = Loader.chunkSize;
let offset = 0;
let array = data;
let transactions = [];
while (array.length > 0) {
const bytes = array.slice(0, chunkSize);
const data = Buffer.alloc(chunkSize + 16);
dataLayout.encode(
{
instruction: 0, // Load instruction
offset,
bytes: bytes as number[],
bytesLength: 0,
bytesLengthPadding: 0,
},
data,
);
const transaction = new Transaction().add({
keys: [{pubkey: program.publicKey, isSigner: true, isWritable: true}],
programId,
data,
});
transactions.push(
sendAndConfirmTransaction(connection, transaction, [payer, program], {
commitment: 'confirmed',
}),
);
// Delay between sends in an attempt to reduce rate limit errors
if (connection._rpcEndpoint.includes('solana.com')) {
const REQUESTS_PER_SECOND = 4;
await sleep(1000 / REQUESTS_PER_SECOND);
}
offset += chunkSize;
array = array.slice(chunkSize);
}
await Promise.all(transactions);
// Finalize the account loaded with program data for execution
{
const dataLayout = BufferLayout.struct<IInstructionInputData>([
BufferLayout.u32('instruction'),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 1, // Finalize instruction
},
data,
);
const transaction = new Transaction().add({
keys: [
{pubkey: program.publicKey, isSigner: true, isWritable: true},
{pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
],
programId,
data,
});
await sendAndConfirmTransaction(
connection,
transaction,
[payer, program],
{
commitment: 'confirmed',
},
);
}
// success
return true;
}
}

View File

@ -1,79 +0,0 @@
import {LoadedAddresses} from '../connection';
import {PublicKey} from '../publickey';
import {TransactionInstruction} from '../transaction';
import {MessageCompiledInstruction} from './index';
export type AccountKeysFromLookups = LoadedAddresses;
export class MessageAccountKeys {
staticAccountKeys: Array<PublicKey>;
accountKeysFromLookups?: AccountKeysFromLookups;
constructor(
staticAccountKeys: Array<PublicKey>,
accountKeysFromLookups?: AccountKeysFromLookups,
) {
this.staticAccountKeys = staticAccountKeys;
this.accountKeysFromLookups = accountKeysFromLookups;
}
keySegments(): Array<Array<PublicKey>> {
const keySegments = [this.staticAccountKeys];
if (this.accountKeysFromLookups) {
keySegments.push(this.accountKeysFromLookups.writable);
keySegments.push(this.accountKeysFromLookups.readonly);
}
return keySegments;
}
get(index: number): PublicKey | undefined {
for (const keySegment of this.keySegments()) {
if (index < keySegment.length) {
return keySegment[index];
} else {
index -= keySegment.length;
}
}
return;
}
get length(): number {
return this.keySegments().flat().length;
}
compileInstructions(
instructions: Array<TransactionInstruction>,
): Array<MessageCompiledInstruction> {
// Bail early if any account indexes would overflow a u8
const U8_MAX = 255;
if (this.length > U8_MAX + 1) {
throw new Error('Account index overflow encountered during compilation');
}
const keyIndexMap = new Map();
this.keySegments()
.flat()
.forEach((key, index) => {
keyIndexMap.set(key.toBase58(), index);
});
const findKeyIndex = (key: PublicKey) => {
const keyIndex = keyIndexMap.get(key.toBase58());
if (keyIndex === undefined)
throw new Error(
'Encountered an unknown instruction account key during compilation',
);
return keyIndex;
};
return instructions.map((instruction): MessageCompiledInstruction => {
return {
programIdIndex: findKeyIndex(instruction.programId),
accountKeyIndexes: instruction.keys.map(meta =>
findKeyIndex(meta.pubkey),
),
data: instruction.data,
};
});
}
}

View File

@ -1,165 +0,0 @@
import {MessageHeader, MessageAddressTableLookup} from './index';
import {AccountKeysFromLookups} from './account-keys';
import {AddressLookupTableAccount} from '../programs';
import {TransactionInstruction} from '../transaction';
import assert from '../utils/assert';
import {PublicKey} from '../publickey';
export type CompiledKeyMeta = {
isSigner: boolean;
isWritable: boolean;
isInvoked: boolean;
};
type KeyMetaMap = Map<string, CompiledKeyMeta>;
export class CompiledKeys {
payer: PublicKey;
keyMetaMap: KeyMetaMap;
constructor(payer: PublicKey, keyMetaMap: KeyMetaMap) {
this.payer = payer;
this.keyMetaMap = keyMetaMap;
}
static compile(
instructions: Array<TransactionInstruction>,
payer: PublicKey,
): CompiledKeys {
const keyMetaMap: KeyMetaMap = new Map();
const getOrInsertDefault = (pubkey: PublicKey): CompiledKeyMeta => {
const address = pubkey.toBase58();
let keyMeta = keyMetaMap.get(address);
if (keyMeta === undefined) {
keyMeta = {
isSigner: false,
isWritable: false,
isInvoked: false,
};
keyMetaMap.set(address, keyMeta);
}
return keyMeta;
};
const payerKeyMeta = getOrInsertDefault(payer);
payerKeyMeta.isSigner = true;
payerKeyMeta.isWritable = true;
for (const ix of instructions) {
getOrInsertDefault(ix.programId).isInvoked = true;
for (const accountMeta of ix.keys) {
const keyMeta = getOrInsertDefault(accountMeta.pubkey);
keyMeta.isSigner ||= accountMeta.isSigner;
keyMeta.isWritable ||= accountMeta.isWritable;
}
}
return new CompiledKeys(payer, keyMetaMap);
}
getMessageComponents(): [MessageHeader, Array<PublicKey>] {
const mapEntries = [...this.keyMetaMap.entries()];
assert(mapEntries.length <= 256, 'Max static account keys length exceeded');
const writableSigners = mapEntries.filter(
([, meta]) => meta.isSigner && meta.isWritable,
);
const readonlySigners = mapEntries.filter(
([, meta]) => meta.isSigner && !meta.isWritable,
);
const writableNonSigners = mapEntries.filter(
([, meta]) => !meta.isSigner && meta.isWritable,
);
const readonlyNonSigners = mapEntries.filter(
([, meta]) => !meta.isSigner && !meta.isWritable,
);
const header: MessageHeader = {
numRequiredSignatures: writableSigners.length + readonlySigners.length,
numReadonlySignedAccounts: readonlySigners.length,
numReadonlyUnsignedAccounts: readonlyNonSigners.length,
};
// sanity checks
{
assert(
writableSigners.length > 0,
'Expected at least one writable signer key',
);
const [payerAddress] = writableSigners[0];
assert(
payerAddress === this.payer.toBase58(),
'Expected first writable signer key to be the fee payer',
);
}
const staticAccountKeys = [
...writableSigners.map(([address]) => new PublicKey(address)),
...readonlySigners.map(([address]) => new PublicKey(address)),
...writableNonSigners.map(([address]) => new PublicKey(address)),
...readonlyNonSigners.map(([address]) => new PublicKey(address)),
];
return [header, staticAccountKeys];
}
extractTableLookup(
lookupTable: AddressLookupTableAccount,
): [MessageAddressTableLookup, AccountKeysFromLookups] | undefined {
const [writableIndexes, drainedWritableKeys] =
this.drainKeysFoundInLookupTable(
lookupTable.state.addresses,
keyMeta =>
!keyMeta.isSigner && !keyMeta.isInvoked && keyMeta.isWritable,
);
const [readonlyIndexes, drainedReadonlyKeys] =
this.drainKeysFoundInLookupTable(
lookupTable.state.addresses,
keyMeta =>
!keyMeta.isSigner && !keyMeta.isInvoked && !keyMeta.isWritable,
);
// Don't extract lookup if no keys were found
if (writableIndexes.length === 0 && readonlyIndexes.length === 0) {
return;
}
return [
{
accountKey: lookupTable.key,
writableIndexes,
readonlyIndexes,
},
{
writable: drainedWritableKeys,
readonly: drainedReadonlyKeys,
},
];
}
/** @internal */
private drainKeysFoundInLookupTable(
lookupTableEntries: Array<PublicKey>,
keyMetaFilter: (keyMeta: CompiledKeyMeta) => boolean,
): [Array<number>, Array<PublicKey>] {
const lookupTableIndexes = new Array();
const drainedKeys = new Array();
for (const [address, keyMeta] of this.keyMetaMap.entries()) {
if (keyMetaFilter(keyMeta)) {
const key = new PublicKey(address);
const lookupTableIndex = lookupTableEntries.findIndex(entry =>
entry.equals(key),
);
if (lookupTableIndex >= 0) {
assert(lookupTableIndex < 256, 'Max lookup table index exceeded');
lookupTableIndexes.push(lookupTableIndex);
drainedKeys.push(key);
this.keyMetaMap.delete(address);
}
}
}
return [lookupTableIndexes, drainedKeys];
}
}

View File

@ -1,47 +0,0 @@
import {PublicKey} from '../publickey';
export * from './account-keys';
// note: compiled-keys is internal and doesn't need to be exported
export * from './legacy';
export * from './versioned';
export * from './v0';
/**
* The message header, identifying signed and read-only account
*/
export type MessageHeader = {
/**
* The number of signatures required for this message to be considered valid. The
* signatures must match the first `numRequiredSignatures` of `accountKeys`.
*/
numRequiredSignatures: number;
/** The last `numReadonlySignedAccounts` of the signed keys are read-only accounts */
numReadonlySignedAccounts: number;
/** The last `numReadonlySignedAccounts` of the unsigned keys are read-only accounts */
numReadonlyUnsignedAccounts: number;
};
/**
* An address table lookup used to load additional accounts
*/
export type MessageAddressTableLookup = {
accountKey: PublicKey;
writableIndexes: Array<number>;
readonlyIndexes: Array<number>;
};
/**
* An instruction to execute by a program
*
* @property {number} programIdIndex
* @property {number[]} accountKeyIndexes
* @property {Uint8Array} data
*/
export type MessageCompiledInstruction = {
/** Index into the transaction keys array indicating the program account that executes this instruction */
programIdIndex: number;
/** Ordered indices into the transaction keys array indicating which accounts to pass to the program */
accountKeyIndexes: number[];
/** The program input data */
data: Uint8Array;
};

View File

@ -1,326 +0,0 @@
import bs58 from 'bs58';
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import {PublicKey, PUBLIC_KEY_LENGTH} from '../publickey';
import type {Blockhash} from '../blockhash';
import * as Layout from '../layout';
import {PACKET_DATA_SIZE, VERSION_PREFIX_MASK} from '../transaction/constants';
import * as shortvec from '../utils/shortvec-encoding';
import {toBuffer} from '../utils/to-buffer';
import {
MessageHeader,
MessageAddressTableLookup,
MessageCompiledInstruction,
} from './index';
import {TransactionInstruction} from '../transaction';
import {CompiledKeys} from './compiled-keys';
import {MessageAccountKeys} from './account-keys';
/**
* An instruction to execute by a program
*
* @property {number} programIdIndex
* @property {number[]} accounts
* @property {string} data
*/
export type CompiledInstruction = {
/** Index into the transaction keys array indicating the program account that executes this instruction */
programIdIndex: number;
/** Ordered indices into the transaction keys array indicating which accounts to pass to the program */
accounts: number[];
/** The program input data encoded as base 58 */
data: string;
};
/**
* Message constructor arguments
*/
export type MessageArgs = {
/** The message header, identifying signed and read-only `accountKeys` */
header: MessageHeader;
/** All the account keys used by this transaction */
accountKeys: string[] | PublicKey[];
/** The hash of a recent ledger block */
recentBlockhash: Blockhash;
/** Instructions that will be executed in sequence and committed in one atomic transaction if all succeed. */
instructions: CompiledInstruction[];
};
export type CompileLegacyArgs = {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;
};
/**
* List of instructions to be processed atomically
*/
export class Message {
header: MessageHeader;
accountKeys: PublicKey[];
recentBlockhash: Blockhash;
instructions: CompiledInstruction[];
private indexToProgramIds: Map<number, PublicKey> = new Map<
number,
PublicKey
>();
constructor(args: MessageArgs) {
this.header = args.header;
this.accountKeys = args.accountKeys.map(account => new PublicKey(account));
this.recentBlockhash = args.recentBlockhash;
this.instructions = args.instructions;
this.instructions.forEach(ix =>
this.indexToProgramIds.set(
ix.programIdIndex,
this.accountKeys[ix.programIdIndex],
),
);
}
get version(): 'legacy' {
return 'legacy';
}
get staticAccountKeys(): Array<PublicKey> {
return this.accountKeys;
}
get compiledInstructions(): Array<MessageCompiledInstruction> {
return this.instructions.map(
(ix): MessageCompiledInstruction => ({
programIdIndex: ix.programIdIndex,
accountKeyIndexes: ix.accounts,
data: bs58.decode(ix.data),
}),
);
}
get addressTableLookups(): Array<MessageAddressTableLookup> {
return [];
}
getAccountKeys(): MessageAccountKeys {
return new MessageAccountKeys(this.staticAccountKeys);
}
static compile(args: CompileLegacyArgs): Message {
const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey);
const [header, staticAccountKeys] = compiledKeys.getMessageComponents();
const accountKeys = new MessageAccountKeys(staticAccountKeys);
const instructions = accountKeys.compileInstructions(args.instructions).map(
(ix: MessageCompiledInstruction): CompiledInstruction => ({
programIdIndex: ix.programIdIndex,
accounts: ix.accountKeyIndexes,
data: bs58.encode(ix.data),
}),
);
return new Message({
header,
accountKeys: staticAccountKeys,
recentBlockhash: args.recentBlockhash,
instructions,
});
}
isAccountSigner(index: number): boolean {
return index < this.header.numRequiredSignatures;
}
isAccountWritable(index: number): boolean {
const numSignedAccounts = this.header.numRequiredSignatures;
if (index >= this.header.numRequiredSignatures) {
const unsignedAccountIndex = index - numSignedAccounts;
const numUnsignedAccounts = this.accountKeys.length - numSignedAccounts;
const numWritableUnsignedAccounts =
numUnsignedAccounts - this.header.numReadonlyUnsignedAccounts;
return unsignedAccountIndex < numWritableUnsignedAccounts;
} else {
const numWritableSignedAccounts =
numSignedAccounts - this.header.numReadonlySignedAccounts;
return index < numWritableSignedAccounts;
}
}
isProgramId(index: number): boolean {
return this.indexToProgramIds.has(index);
}
programIds(): PublicKey[] {
return [...this.indexToProgramIds.values()];
}
nonProgramIds(): PublicKey[] {
return this.accountKeys.filter((_, index) => !this.isProgramId(index));
}
serialize(): Buffer {
const numKeys = this.accountKeys.length;
let keyCount: number[] = [];
shortvec.encodeLength(keyCount, numKeys);
const instructions = this.instructions.map(instruction => {
const {accounts, programIdIndex} = instruction;
const data = Array.from(bs58.decode(instruction.data));
let keyIndicesCount: number[] = [];
shortvec.encodeLength(keyIndicesCount, accounts.length);
let dataCount: number[] = [];
shortvec.encodeLength(dataCount, data.length);
return {
programIdIndex,
keyIndicesCount: Buffer.from(keyIndicesCount),
keyIndices: accounts,
dataLength: Buffer.from(dataCount),
data,
};
});
let instructionCount: number[] = [];
shortvec.encodeLength(instructionCount, instructions.length);
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE);
Buffer.from(instructionCount).copy(instructionBuffer);
let instructionBufferLength = instructionCount.length;
instructions.forEach(instruction => {
const instructionLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
keyIndices: number[];
keyIndicesCount: Uint8Array;
programIdIndex: number;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(
instruction.keyIndicesCount.length,
'keyIndicesCount',
),
BufferLayout.seq(
BufferLayout.u8('keyIndex'),
instruction.keyIndices.length,
'keyIndices',
),
BufferLayout.blob(instruction.dataLength.length, 'dataLength'),
BufferLayout.seq(
BufferLayout.u8('userdatum'),
instruction.data.length,
'data',
),
]);
const length = instructionLayout.encode(
instruction,
instructionBuffer,
instructionBufferLength,
);
instructionBufferLength += length;
});
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength);
const signDataLayout = BufferLayout.struct<
Readonly<{
keyCount: Uint8Array;
keys: Uint8Array[];
numReadonlySignedAccounts: Uint8Array;
numReadonlyUnsignedAccounts: Uint8Array;
numRequiredSignatures: Uint8Array;
recentBlockhash: Uint8Array;
}>
>([
BufferLayout.blob(1, 'numRequiredSignatures'),
BufferLayout.blob(1, 'numReadonlySignedAccounts'),
BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'),
BufferLayout.blob(keyCount.length, 'keyCount'),
BufferLayout.seq(Layout.publicKey('key'), numKeys, 'keys'),
Layout.publicKey('recentBlockhash'),
]);
const transaction = {
numRequiredSignatures: Buffer.from([this.header.numRequiredSignatures]),
numReadonlySignedAccounts: Buffer.from([
this.header.numReadonlySignedAccounts,
]),
numReadonlyUnsignedAccounts: Buffer.from([
this.header.numReadonlyUnsignedAccounts,
]),
keyCount: Buffer.from(keyCount),
keys: this.accountKeys.map(key => toBuffer(key.toBytes())),
recentBlockhash: bs58.decode(this.recentBlockhash),
};
let signData = Buffer.alloc(2048);
const length = signDataLayout.encode(transaction, signData);
instructionBuffer.copy(signData, length);
return signData.slice(0, length + instructionBuffer.length);
}
/**
* Decode a compiled message into a Message object.
*/
static from(buffer: Buffer | Uint8Array | Array<number>): Message {
// Slice up wire data
let byteArray = [...buffer];
const numRequiredSignatures = byteArray.shift()!;
if (
numRequiredSignatures !==
(numRequiredSignatures & VERSION_PREFIX_MASK)
) {
throw new Error(
'Versioned messages must be deserialized with VersionedMessage.deserialize()',
);
}
const numReadonlySignedAccounts = byteArray.shift()!;
const numReadonlyUnsignedAccounts = byteArray.shift()!;
const accountCount = shortvec.decodeLength(byteArray);
let accountKeys = [];
for (let i = 0; i < accountCount; i++) {
const account = byteArray.slice(0, PUBLIC_KEY_LENGTH);
byteArray = byteArray.slice(PUBLIC_KEY_LENGTH);
accountKeys.push(new PublicKey(Buffer.from(account)));
}
const recentBlockhash = byteArray.slice(0, PUBLIC_KEY_LENGTH);
byteArray = byteArray.slice(PUBLIC_KEY_LENGTH);
const instructionCount = shortvec.decodeLength(byteArray);
let instructions: CompiledInstruction[] = [];
for (let i = 0; i < instructionCount; i++) {
const programIdIndex = byteArray.shift()!;
const accountCount = shortvec.decodeLength(byteArray);
const accounts = byteArray.slice(0, accountCount);
byteArray = byteArray.slice(accountCount);
const dataLength = shortvec.decodeLength(byteArray);
const dataSlice = byteArray.slice(0, dataLength);
const data = bs58.encode(Buffer.from(dataSlice));
byteArray = byteArray.slice(dataLength);
instructions.push({
programIdIndex,
accounts,
data,
});
}
const messageArgs = {
header: {
numRequiredSignatures,
numReadonlySignedAccounts,
numReadonlyUnsignedAccounts,
},
recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)),
accountKeys,
instructions,
};
return new Message(messageArgs);
}
}

View File

@ -1,496 +0,0 @@
import bs58 from 'bs58';
import * as BufferLayout from '@solana/buffer-layout';
import * as Layout from '../layout';
import {Blockhash} from '../blockhash';
import {
MessageHeader,
MessageAddressTableLookup,
MessageCompiledInstruction,
} from './index';
import {PublicKey, PUBLIC_KEY_LENGTH} from '../publickey';
import * as shortvec from '../utils/shortvec-encoding';
import assert from '../utils/assert';
import {PACKET_DATA_SIZE, VERSION_PREFIX_MASK} from '../transaction/constants';
import {TransactionInstruction} from '../transaction';
import {AddressLookupTableAccount} from '../programs';
import {CompiledKeys} from './compiled-keys';
import {AccountKeysFromLookups, MessageAccountKeys} from './account-keys';
/**
* Message constructor arguments
*/
export type MessageV0Args = {
/** The message header, identifying signed and read-only `accountKeys` */
header: MessageHeader;
/** The static account keys used by this transaction */
staticAccountKeys: PublicKey[];
/** The hash of a recent ledger block */
recentBlockhash: Blockhash;
/** Instructions that will be executed in sequence and committed in one atomic transaction if all succeed. */
compiledInstructions: MessageCompiledInstruction[];
/** Instructions that will be executed in sequence and committed in one atomic transaction if all succeed. */
addressTableLookups: MessageAddressTableLookup[];
};
export type CompileV0Args = {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;
addressLookupTableAccounts?: Array<AddressLookupTableAccount>;
};
export type GetAccountKeysArgs =
| {
accountKeysFromLookups?: AccountKeysFromLookups | null;
}
| {
addressLookupTableAccounts?: AddressLookupTableAccount[] | null;
};
export class MessageV0 {
header: MessageHeader;
staticAccountKeys: Array<PublicKey>;
recentBlockhash: Blockhash;
compiledInstructions: Array<MessageCompiledInstruction>;
addressTableLookups: Array<MessageAddressTableLookup>;
constructor(args: MessageV0Args) {
this.header = args.header;
this.staticAccountKeys = args.staticAccountKeys;
this.recentBlockhash = args.recentBlockhash;
this.compiledInstructions = args.compiledInstructions;
this.addressTableLookups = args.addressTableLookups;
}
get version(): 0 {
return 0;
}
get numAccountKeysFromLookups(): number {
let count = 0;
for (const lookup of this.addressTableLookups) {
count += lookup.readonlyIndexes.length + lookup.writableIndexes.length;
}
return count;
}
getAccountKeys(args?: GetAccountKeysArgs): MessageAccountKeys {
let accountKeysFromLookups: AccountKeysFromLookups | undefined;
if (
args &&
'accountKeysFromLookups' in args &&
args.accountKeysFromLookups
) {
if (
this.numAccountKeysFromLookups !=
args.accountKeysFromLookups.writable.length +
args.accountKeysFromLookups.readonly.length
) {
throw new Error(
'Failed to get account keys because of a mismatch in the number of account keys from lookups',
);
}
accountKeysFromLookups = args.accountKeysFromLookups;
} else if (
args &&
'addressLookupTableAccounts' in args &&
args.addressLookupTableAccounts
) {
accountKeysFromLookups = this.resolveAddressTableLookups(
args.addressLookupTableAccounts,
);
} else if (this.addressTableLookups.length > 0) {
throw new Error(
'Failed to get account keys because address table lookups were not resolved',
);
}
return new MessageAccountKeys(
this.staticAccountKeys,
accountKeysFromLookups,
);
}
isAccountSigner(index: number): boolean {
return index < this.header.numRequiredSignatures;
}
isAccountWritable(index: number): boolean {
const numSignedAccounts = this.header.numRequiredSignatures;
const numStaticAccountKeys = this.staticAccountKeys.length;
if (index >= numStaticAccountKeys) {
const lookupAccountKeysIndex = index - numStaticAccountKeys;
const numWritableLookupAccountKeys = this.addressTableLookups.reduce(
(count, lookup) => count + lookup.writableIndexes.length,
0,
);
return lookupAccountKeysIndex < numWritableLookupAccountKeys;
} else if (index >= this.header.numRequiredSignatures) {
const unsignedAccountIndex = index - numSignedAccounts;
const numUnsignedAccounts = numStaticAccountKeys - numSignedAccounts;
const numWritableUnsignedAccounts =
numUnsignedAccounts - this.header.numReadonlyUnsignedAccounts;
return unsignedAccountIndex < numWritableUnsignedAccounts;
} else {
const numWritableSignedAccounts =
numSignedAccounts - this.header.numReadonlySignedAccounts;
return index < numWritableSignedAccounts;
}
}
resolveAddressTableLookups(
addressLookupTableAccounts: AddressLookupTableAccount[],
): AccountKeysFromLookups {
const accountKeysFromLookups: AccountKeysFromLookups = {
writable: [],
readonly: [],
};
for (const tableLookup of this.addressTableLookups) {
const tableAccount = addressLookupTableAccounts.find(account =>
account.key.equals(tableLookup.accountKey),
);
if (!tableAccount) {
throw new Error(
`Failed to find address lookup table account for table key ${tableLookup.accountKey.toBase58()}`,
);
}
for (const index of tableLookup.writableIndexes) {
if (index < tableAccount.state.addresses.length) {
accountKeysFromLookups.writable.push(
tableAccount.state.addresses[index],
);
} else {
throw new Error(
`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`,
);
}
}
for (const index of tableLookup.readonlyIndexes) {
if (index < tableAccount.state.addresses.length) {
accountKeysFromLookups.readonly.push(
tableAccount.state.addresses[index],
);
} else {
throw new Error(
`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`,
);
}
}
}
return accountKeysFromLookups;
}
static compile(args: CompileV0Args): MessageV0 {
const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey);
const addressTableLookups = new Array<MessageAddressTableLookup>();
const accountKeysFromLookups: AccountKeysFromLookups = {
writable: new Array(),
readonly: new Array(),
};
const lookupTableAccounts = args.addressLookupTableAccounts || [];
for (const lookupTable of lookupTableAccounts) {
const extractResult = compiledKeys.extractTableLookup(lookupTable);
if (extractResult !== undefined) {
const [addressTableLookup, {writable, readonly}] = extractResult;
addressTableLookups.push(addressTableLookup);
accountKeysFromLookups.writable.push(...writable);
accountKeysFromLookups.readonly.push(...readonly);
}
}
const [header, staticAccountKeys] = compiledKeys.getMessageComponents();
const accountKeys = new MessageAccountKeys(
staticAccountKeys,
accountKeysFromLookups,
);
const compiledInstructions = accountKeys.compileInstructions(
args.instructions,
);
return new MessageV0({
header,
staticAccountKeys,
recentBlockhash: args.recentBlockhash,
compiledInstructions,
addressTableLookups,
});
}
serialize(): Uint8Array {
const encodedStaticAccountKeysLength = Array<number>();
shortvec.encodeLength(
encodedStaticAccountKeysLength,
this.staticAccountKeys.length,
);
const serializedInstructions = this.serializeInstructions();
const encodedInstructionsLength = Array<number>();
shortvec.encodeLength(
encodedInstructionsLength,
this.compiledInstructions.length,
);
const serializedAddressTableLookups = this.serializeAddressTableLookups();
const encodedAddressTableLookupsLength = Array<number>();
shortvec.encodeLength(
encodedAddressTableLookupsLength,
this.addressTableLookups.length,
);
const messageLayout = BufferLayout.struct<{
prefix: number;
header: MessageHeader;
staticAccountKeysLength: Uint8Array;
staticAccountKeys: Array<Uint8Array>;
recentBlockhash: Uint8Array;
instructionsLength: Uint8Array;
serializedInstructions: Uint8Array;
addressTableLookupsLength: Uint8Array;
serializedAddressTableLookups: Uint8Array;
}>([
BufferLayout.u8('prefix'),
BufferLayout.struct<MessageHeader>(
[
BufferLayout.u8('numRequiredSignatures'),
BufferLayout.u8('numReadonlySignedAccounts'),
BufferLayout.u8('numReadonlyUnsignedAccounts'),
],
'header',
),
BufferLayout.blob(
encodedStaticAccountKeysLength.length,
'staticAccountKeysLength',
),
BufferLayout.seq(
Layout.publicKey(),
this.staticAccountKeys.length,
'staticAccountKeys',
),
Layout.publicKey('recentBlockhash'),
BufferLayout.blob(encodedInstructionsLength.length, 'instructionsLength'),
BufferLayout.blob(
serializedInstructions.length,
'serializedInstructions',
),
BufferLayout.blob(
encodedAddressTableLookupsLength.length,
'addressTableLookupsLength',
),
BufferLayout.blob(
serializedAddressTableLookups.length,
'serializedAddressTableLookups',
),
]);
const serializedMessage = new Uint8Array(PACKET_DATA_SIZE);
const MESSAGE_VERSION_0_PREFIX = 1 << 7;
const serializedMessageLength = messageLayout.encode(
{
prefix: MESSAGE_VERSION_0_PREFIX,
header: this.header,
staticAccountKeysLength: new Uint8Array(encodedStaticAccountKeysLength),
staticAccountKeys: this.staticAccountKeys.map(key => key.toBytes()),
recentBlockhash: bs58.decode(this.recentBlockhash),
instructionsLength: new Uint8Array(encodedInstructionsLength),
serializedInstructions,
addressTableLookupsLength: new Uint8Array(
encodedAddressTableLookupsLength,
),
serializedAddressTableLookups,
},
serializedMessage,
);
return serializedMessage.slice(0, serializedMessageLength);
}
private serializeInstructions(): Uint8Array {
let serializedLength = 0;
const serializedInstructions = new Uint8Array(PACKET_DATA_SIZE);
for (const instruction of this.compiledInstructions) {
const encodedAccountKeyIndexesLength = Array<number>();
shortvec.encodeLength(
encodedAccountKeyIndexesLength,
instruction.accountKeyIndexes.length,
);
const encodedDataLength = Array<number>();
shortvec.encodeLength(encodedDataLength, instruction.data.length);
const instructionLayout = BufferLayout.struct<{
programIdIndex: number;
encodedAccountKeyIndexesLength: Uint8Array;
accountKeyIndexes: number[];
encodedDataLength: Uint8Array;
data: Uint8Array;
}>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(
encodedAccountKeyIndexesLength.length,
'encodedAccountKeyIndexesLength',
),
BufferLayout.seq(
BufferLayout.u8(),
instruction.accountKeyIndexes.length,
'accountKeyIndexes',
),
BufferLayout.blob(encodedDataLength.length, 'encodedDataLength'),
BufferLayout.blob(instruction.data.length, 'data'),
]);
serializedLength += instructionLayout.encode(
{
programIdIndex: instruction.programIdIndex,
encodedAccountKeyIndexesLength: new Uint8Array(
encodedAccountKeyIndexesLength,
),
accountKeyIndexes: instruction.accountKeyIndexes,
encodedDataLength: new Uint8Array(encodedDataLength),
data: instruction.data,
},
serializedInstructions,
serializedLength,
);
}
return serializedInstructions.slice(0, serializedLength);
}
private serializeAddressTableLookups(): Uint8Array {
let serializedLength = 0;
const serializedAddressTableLookups = new Uint8Array(PACKET_DATA_SIZE);
for (const lookup of this.addressTableLookups) {
const encodedWritableIndexesLength = Array<number>();
shortvec.encodeLength(
encodedWritableIndexesLength,
lookup.writableIndexes.length,
);
const encodedReadonlyIndexesLength = Array<number>();
shortvec.encodeLength(
encodedReadonlyIndexesLength,
lookup.readonlyIndexes.length,
);
const addressTableLookupLayout = BufferLayout.struct<{
accountKey: Uint8Array;
encodedWritableIndexesLength: Uint8Array;
writableIndexes: number[];
encodedReadonlyIndexesLength: Uint8Array;
readonlyIndexes: number[];
}>([
Layout.publicKey('accountKey'),
BufferLayout.blob(
encodedWritableIndexesLength.length,
'encodedWritableIndexesLength',
),
BufferLayout.seq(
BufferLayout.u8(),
lookup.writableIndexes.length,
'writableIndexes',
),
BufferLayout.blob(
encodedReadonlyIndexesLength.length,
'encodedReadonlyIndexesLength',
),
BufferLayout.seq(
BufferLayout.u8(),
lookup.readonlyIndexes.length,
'readonlyIndexes',
),
]);
serializedLength += addressTableLookupLayout.encode(
{
accountKey: lookup.accountKey.toBytes(),
encodedWritableIndexesLength: new Uint8Array(
encodedWritableIndexesLength,
),
writableIndexes: lookup.writableIndexes,
encodedReadonlyIndexesLength: new Uint8Array(
encodedReadonlyIndexesLength,
),
readonlyIndexes: lookup.readonlyIndexes,
},
serializedAddressTableLookups,
serializedLength,
);
}
return serializedAddressTableLookups.slice(0, serializedLength);
}
static deserialize(serializedMessage: Uint8Array): MessageV0 {
let byteArray = [...serializedMessage];
const prefix = byteArray.shift() as number;
const maskedPrefix = prefix & VERSION_PREFIX_MASK;
assert(
prefix !== maskedPrefix,
`Expected versioned message but received legacy message`,
);
const version = maskedPrefix;
assert(
version === 0,
`Expected versioned message with version 0 but found version ${version}`,
);
const header: MessageHeader = {
numRequiredSignatures: byteArray.shift() as number,
numReadonlySignedAccounts: byteArray.shift() as number,
numReadonlyUnsignedAccounts: byteArray.shift() as number,
};
const staticAccountKeys = [];
const staticAccountKeysLength = shortvec.decodeLength(byteArray);
for (let i = 0; i < staticAccountKeysLength; i++) {
staticAccountKeys.push(
new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)),
);
}
const recentBlockhash = bs58.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH));
const instructionCount = shortvec.decodeLength(byteArray);
const compiledInstructions: MessageCompiledInstruction[] = [];
for (let i = 0; i < instructionCount; i++) {
const programIdIndex = byteArray.shift() as number;
const accountKeyIndexesLength = shortvec.decodeLength(byteArray);
const accountKeyIndexes = byteArray.splice(0, accountKeyIndexesLength);
const dataLength = shortvec.decodeLength(byteArray);
const data = new Uint8Array(byteArray.splice(0, dataLength));
compiledInstructions.push({
programIdIndex,
accountKeyIndexes,
data,
});
}
const addressTableLookupsCount = shortvec.decodeLength(byteArray);
const addressTableLookups: MessageAddressTableLookup[] = [];
for (let i = 0; i < addressTableLookupsCount; i++) {
const accountKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH));
const writableIndexesLength = shortvec.decodeLength(byteArray);
const writableIndexes = byteArray.splice(0, writableIndexesLength);
const readonlyIndexesLength = shortvec.decodeLength(byteArray);
const readonlyIndexes = byteArray.splice(0, readonlyIndexesLength);
addressTableLookups.push({
accountKey,
writableIndexes,
readonlyIndexes,
});
}
return new MessageV0({
header,
staticAccountKeys,
recentBlockhash,
compiledInstructions,
addressTableLookups,
});
}
}

View File

@ -1,36 +0,0 @@
import {VERSION_PREFIX_MASK} from '../transaction/constants';
import {Message} from './legacy';
import {MessageV0} from './v0';
export type VersionedMessage = Message | MessageV0;
// eslint-disable-next-line no-redeclare
export const VersionedMessage = {
deserializeMessageVersion(serializedMessage: Uint8Array): 'legacy' | number {
const prefix = serializedMessage[0];
const maskedPrefix = prefix & VERSION_PREFIX_MASK;
// if the highest bit of the prefix is not set, the message is not versioned
if (maskedPrefix === prefix) {
return 'legacy';
}
// the lower 7 bits of the prefix indicate the message version
return maskedPrefix;
},
deserialize: (serializedMessage: Uint8Array): VersionedMessage => {
const version =
VersionedMessage.deserializeMessageVersion(serializedMessage);
if (version === 'legacy') {
return Message.from(serializedMessage);
}
if (version === 0) {
return MessageV0.deserialize(serializedMessage);
} else {
throw new Error(
`Transaction message version ${version} deserialization is not supported`,
);
}
},
};

View File

@ -1,82 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import {Buffer} from 'buffer';
import * as Layout from './layout';
import {PublicKey} from './publickey';
import type {FeeCalculator} from './fee-calculator';
import {FeeCalculatorLayout} from './fee-calculator';
import {toBuffer} from './utils/to-buffer';
/**
* See https://github.com/solana-labs/solana/blob/0ea2843ec9cdc517572b8e62c959f41b55cf4453/sdk/src/nonce_state.rs#L29-L32
*
* @internal
*/
const NonceAccountLayout = BufferLayout.struct<
Readonly<{
authorizedPubkey: Uint8Array;
feeCalculator: Readonly<{
lamportsPerSignature: number;
}>;
nonce: Uint8Array;
state: number;
version: number;
}>
>([
BufferLayout.u32('version'),
BufferLayout.u32('state'),
Layout.publicKey('authorizedPubkey'),
Layout.publicKey('nonce'),
BufferLayout.struct<Readonly<{lamportsPerSignature: number}>>(
[FeeCalculatorLayout],
'feeCalculator',
),
]);
export const NONCE_ACCOUNT_LENGTH = NonceAccountLayout.span;
/**
* A durable nonce is a 32 byte value encoded as a base58 string.
*/
export type DurableNonce = string;
type NonceAccountArgs = {
authorizedPubkey: PublicKey;
nonce: DurableNonce;
feeCalculator: FeeCalculator;
};
/**
* NonceAccount class
*/
export class NonceAccount {
authorizedPubkey: PublicKey;
nonce: DurableNonce;
feeCalculator: FeeCalculator;
/**
* @internal
*/
constructor(args: NonceAccountArgs) {
this.authorizedPubkey = args.authorizedPubkey;
this.nonce = args.nonce;
this.feeCalculator = args.feeCalculator;
}
/**
* Deserialize NonceAccount from the account data.
*
* @param buffer account data
* @return NonceAccount
*/
static fromAccountData(
buffer: Buffer | Uint8Array | Array<number>,
): NonceAccount {
const nonceAccount = NonceAccountLayout.decode(toBuffer(buffer), 0);
return new NonceAccount({
authorizedPubkey: new PublicKey(nonceAccount.authorizedPubkey),
nonce: new PublicKey(nonceAccount.nonce).toString(),
feeCalculator: nonceAccount.feeCalculator,
});
}
}

View File

@ -1,435 +0,0 @@
import {toBufferLE} from 'bigint-buffer';
import * as BufferLayout from '@solana/buffer-layout';
import * as Layout from '../../layout';
import {PublicKey} from '../../publickey';
import * as bigintLayout from '../../utils/bigint';
import {SystemProgram} from '../system';
import {TransactionInstruction} from '../../transaction';
import {decodeData, encodeData, IInstructionInputData} from '../../instruction';
export * from './state';
export type CreateLookupTableParams = {
/** Account used to derive and control the new address lookup table. */
authority: PublicKey;
/** Account that will fund the new address lookup table. */
payer: PublicKey;
/** A recent slot must be used in the derivation path for each initialized table. */
recentSlot: bigint | number;
};
export type FreezeLookupTableParams = {
/** Address lookup table account to freeze. */
lookupTable: PublicKey;
/** Account which is the current authority. */
authority: PublicKey;
};
export type ExtendLookupTableParams = {
/** Address lookup table account to extend. */
lookupTable: PublicKey;
/** Account which is the current authority. */
authority: PublicKey;
/** Account that will fund the table reallocation.
* Not required if the reallocation has already been funded. */
payer?: PublicKey;
/** List of Public Keys to be added to the lookup table. */
addresses: Array<PublicKey>;
};
export type DeactivateLookupTableParams = {
/** Address lookup table account to deactivate. */
lookupTable: PublicKey;
/** Account which is the current authority. */
authority: PublicKey;
};
export type CloseLookupTableParams = {
/** Address lookup table account to close. */
lookupTable: PublicKey;
/** Account which is the current authority. */
authority: PublicKey;
/** Recipient of closed account lamports. */
recipient: PublicKey;
};
/**
* An enumeration of valid LookupTableInstructionType's
*/
export type LookupTableInstructionType =
| 'CreateLookupTable'
| 'ExtendLookupTable'
| 'CloseLookupTable'
| 'FreezeLookupTable'
| 'DeactivateLookupTable';
type LookupTableInstructionInputData = {
CreateLookupTable: IInstructionInputData &
Readonly<{
recentSlot: bigint;
bumpSeed: number;
}>;
FreezeLookupTable: IInstructionInputData;
ExtendLookupTable: IInstructionInputData &
Readonly<{
numberOfAddresses: bigint;
addresses: Array<Uint8Array>;
}>;
DeactivateLookupTable: IInstructionInputData;
CloseLookupTable: IInstructionInputData;
};
/**
* An enumeration of valid address lookup table InstructionType's
* @internal
*/
export const LOOKUP_TABLE_INSTRUCTION_LAYOUTS = Object.freeze({
CreateLookupTable: {
index: 0,
layout: BufferLayout.struct<
LookupTableInstructionInputData['CreateLookupTable']
>([
BufferLayout.u32('instruction'),
bigintLayout.u64('recentSlot'),
BufferLayout.u8('bumpSeed'),
]),
},
FreezeLookupTable: {
index: 1,
layout: BufferLayout.struct<
LookupTableInstructionInputData['FreezeLookupTable']
>([BufferLayout.u32('instruction')]),
},
ExtendLookupTable: {
index: 2,
layout: BufferLayout.struct<
LookupTableInstructionInputData['ExtendLookupTable']
>([
BufferLayout.u32('instruction'),
bigintLayout.u64(),
BufferLayout.seq(
Layout.publicKey(),
BufferLayout.offset(BufferLayout.u32(), -8),
'addresses',
),
]),
},
DeactivateLookupTable: {
index: 3,
layout: BufferLayout.struct<
LookupTableInstructionInputData['DeactivateLookupTable']
>([BufferLayout.u32('instruction')]),
},
CloseLookupTable: {
index: 4,
layout: BufferLayout.struct<
LookupTableInstructionInputData['CloseLookupTable']
>([BufferLayout.u32('instruction')]),
},
});
export class AddressLookupTableInstruction {
/**
* @internal
*/
constructor() {}
static decodeInstructionType(
instruction: TransactionInstruction,
): LookupTableInstructionType {
this.checkProgramId(instruction.programId);
const instructionTypeLayout = BufferLayout.u32('instruction');
const index = instructionTypeLayout.decode(instruction.data);
let type: LookupTableInstructionType | undefined;
for (const [layoutType, layout] of Object.entries(
LOOKUP_TABLE_INSTRUCTION_LAYOUTS,
)) {
if ((layout as any).index == index) {
type = layoutType as LookupTableInstructionType;
break;
}
}
if (!type) {
throw new Error(
'Invalid Instruction. Should be a LookupTable Instruction',
);
}
return type;
}
static decodeCreateLookupTable(
instruction: TransactionInstruction,
): CreateLookupTableParams {
this.checkProgramId(instruction.programId);
this.checkKeysLength(instruction.keys, 4);
const {recentSlot} = decodeData(
LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable,
instruction.data,
);
return {
authority: instruction.keys[1].pubkey,
payer: instruction.keys[2].pubkey,
recentSlot: Number(recentSlot),
};
}
static decodeExtendLookupTable(
instruction: TransactionInstruction,
): ExtendLookupTableParams {
this.checkProgramId(instruction.programId);
if (instruction.keys.length < 2) {
throw new Error(
`invalid instruction; found ${instruction.keys.length} keys, expected at least 2`,
);
}
const {addresses} = decodeData(
LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable,
instruction.data,
);
return {
lookupTable: instruction.keys[0].pubkey,
authority: instruction.keys[1].pubkey,
payer:
instruction.keys.length > 2 ? instruction.keys[2].pubkey : undefined,
addresses: addresses.map(buffer => new PublicKey(buffer)),
};
}
static decodeCloseLookupTable(
instruction: TransactionInstruction,
): CloseLookupTableParams {
this.checkProgramId(instruction.programId);
this.checkKeysLength(instruction.keys, 3);
return {
lookupTable: instruction.keys[0].pubkey,
authority: instruction.keys[1].pubkey,
recipient: instruction.keys[2].pubkey,
};
}
static decodeFreezeLookupTable(
instruction: TransactionInstruction,
): FreezeLookupTableParams {
this.checkProgramId(instruction.programId);
this.checkKeysLength(instruction.keys, 2);
return {
lookupTable: instruction.keys[0].pubkey,
authority: instruction.keys[1].pubkey,
};
}
static decodeDeactivateLookupTable(
instruction: TransactionInstruction,
): DeactivateLookupTableParams {
this.checkProgramId(instruction.programId);
this.checkKeysLength(instruction.keys, 2);
return {
lookupTable: instruction.keys[0].pubkey,
authority: instruction.keys[1].pubkey,
};
}
/**
* @internal
*/
static checkProgramId(programId: PublicKey) {
if (!programId.equals(AddressLookupTableProgram.programId)) {
throw new Error(
'invalid instruction; programId is not AddressLookupTable Program',
);
}
}
/**
* @internal
*/
static checkKeysLength(keys: Array<any>, expectedLength: number) {
if (keys.length < expectedLength) {
throw new Error(
`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`,
);
}
}
}
export class AddressLookupTableProgram {
/**
* @internal
*/
constructor() {}
static programId: PublicKey = new PublicKey(
'AddressLookupTab1e1111111111111111111111111',
);
static createLookupTable(params: CreateLookupTableParams) {
const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync(
[params.authority.toBuffer(), toBufferLE(BigInt(params.recentSlot), 8)],
this.programId,
);
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable;
const data = encodeData(type, {
recentSlot: BigInt(params.recentSlot),
bumpSeed: bumpSeed,
});
const keys = [
{
pubkey: lookupTableAddress,
isSigner: false,
isWritable: true,
},
{
pubkey: params.authority,
isSigner: true,
isWritable: false,
},
{
pubkey: params.payer,
isSigner: true,
isWritable: true,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
];
return [
new TransactionInstruction({
programId: this.programId,
keys: keys,
data: data,
}),
lookupTableAddress,
] as [TransactionInstruction, PublicKey];
}
static freezeLookupTable(params: FreezeLookupTableParams) {
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.FreezeLookupTable;
const data = encodeData(type);
const keys = [
{
pubkey: params.lookupTable,
isSigner: false,
isWritable: true,
},
{
pubkey: params.authority,
isSigner: true,
isWritable: false,
},
];
return new TransactionInstruction({
programId: this.programId,
keys: keys,
data: data,
});
}
static extendLookupTable(params: ExtendLookupTableParams) {
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable;
const data = encodeData(type, {
addresses: params.addresses.map(addr => addr.toBytes()),
});
const keys = [
{
pubkey: params.lookupTable,
isSigner: false,
isWritable: true,
},
{
pubkey: params.authority,
isSigner: true,
isWritable: false,
},
];
if (params.payer) {
keys.push(
{
pubkey: params.payer,
isSigner: true,
isWritable: true,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
);
}
return new TransactionInstruction({
programId: this.programId,
keys: keys,
data: data,
});
}
static deactivateLookupTable(params: DeactivateLookupTableParams) {
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.DeactivateLookupTable;
const data = encodeData(type);
const keys = [
{
pubkey: params.lookupTable,
isSigner: false,
isWritable: true,
},
{
pubkey: params.authority,
isSigner: true,
isWritable: false,
},
];
return new TransactionInstruction({
programId: this.programId,
keys: keys,
data: data,
});
}
static closeLookupTable(params: CloseLookupTableParams) {
const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CloseLookupTable;
const data = encodeData(type);
const keys = [
{
pubkey: params.lookupTable,
isSigner: false,
isWritable: true,
},
{
pubkey: params.authority,
isSigner: true,
isWritable: false,
},
{
pubkey: params.recipient,
isSigner: false,
isWritable: true,
},
];
return new TransactionInstruction({
programId: this.programId,
keys: keys,
data: data,
});
}
}

View File

@ -1,84 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import assert from '../../utils/assert';
import * as Layout from '../../layout';
import {PublicKey} from '../../publickey';
import {u64} from '../../utils/bigint';
import {decodeData} from '../../account-data';
export type AddressLookupTableState = {
deactivationSlot: bigint;
lastExtendedSlot: number;
lastExtendedSlotStartIndex: number;
authority?: PublicKey;
addresses: Array<PublicKey>;
};
export type AddressLookupTableAccountArgs = {
key: PublicKey;
state: AddressLookupTableState;
};
/// The serialized size of lookup table metadata
const LOOKUP_TABLE_META_SIZE = 56;
export class AddressLookupTableAccount {
key: PublicKey;
state: AddressLookupTableState;
constructor(args: AddressLookupTableAccountArgs) {
this.key = args.key;
this.state = args.state;
}
isActive(): boolean {
const U64_MAX = BigInt('0xffffffffffffffff');
return this.state.deactivationSlot === U64_MAX;
}
static deserialize(accountData: Uint8Array): AddressLookupTableState {
const meta = decodeData(LookupTableMetaLayout, accountData);
const serializedAddressesLen = accountData.length - LOOKUP_TABLE_META_SIZE;
assert(serializedAddressesLen >= 0, 'lookup table is invalid');
assert(serializedAddressesLen % 32 === 0, 'lookup table is invalid');
const numSerializedAddresses = serializedAddressesLen / 32;
const {addresses} = BufferLayout.struct<{addresses: Array<Uint8Array>}>([
BufferLayout.seq(Layout.publicKey(), numSerializedAddresses, 'addresses'),
]).decode(accountData.slice(LOOKUP_TABLE_META_SIZE));
return {
deactivationSlot: meta.deactivationSlot,
lastExtendedSlot: meta.lastExtendedSlot,
lastExtendedSlotStartIndex: meta.lastExtendedStartIndex,
authority:
meta.authority.length !== 0
? new PublicKey(meta.authority[0])
: undefined,
addresses: addresses.map(address => new PublicKey(address)),
};
}
}
const LookupTableMetaLayout = {
index: 1,
layout: BufferLayout.struct<{
typeIndex: number;
deactivationSlot: bigint;
lastExtendedSlot: number;
lastExtendedStartIndex: number;
authority: Array<Uint8Array>;
}>([
BufferLayout.u32('typeIndex'),
u64('deactivationSlot'),
BufferLayout.nu64('lastExtendedSlot'),
BufferLayout.u8('lastExtendedStartIndex'),
BufferLayout.u8(), // option
BufferLayout.seq(
Layout.publicKey(),
BufferLayout.offset(BufferLayout.u8(), -1),
'authority',
),
]),
};

View File

@ -1,281 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import {
encodeData,
decodeData,
InstructionType,
IInstructionInputData,
} from '../instruction';
import {PublicKey} from '../publickey';
import {TransactionInstruction} from '../transaction';
import {u64} from '../utils/bigint';
/**
* Compute Budget Instruction class
*/
export class ComputeBudgetInstruction {
/**
* @internal
*/
constructor() {}
/**
* Decode a compute budget instruction and retrieve the instruction type.
*/
static decodeInstructionType(
instruction: TransactionInstruction,
): ComputeBudgetInstructionType {
this.checkProgramId(instruction.programId);
const instructionTypeLayout = BufferLayout.u8('instruction');
const typeIndex = instructionTypeLayout.decode(instruction.data);
let type: ComputeBudgetInstructionType | undefined;
for (const [ixType, layout] of Object.entries(
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS,
)) {
if (layout.index == typeIndex) {
type = ixType as ComputeBudgetInstructionType;
break;
}
}
if (!type) {
throw new Error(
'Instruction type incorrect; not a ComputeBudgetInstruction',
);
}
return type;
}
/**
* Decode request units compute budget instruction and retrieve the instruction params.
*/
static decodeRequestUnits(
instruction: TransactionInstruction,
): RequestUnitsParams {
this.checkProgramId(instruction.programId);
const {units, additionalFee} = decodeData(
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits,
instruction.data,
);
return {units, additionalFee};
}
/**
* Decode request heap frame compute budget instruction and retrieve the instruction params.
*/
static decodeRequestHeapFrame(
instruction: TransactionInstruction,
): RequestHeapFrameParams {
this.checkProgramId(instruction.programId);
const {bytes} = decodeData(
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame,
instruction.data,
);
return {bytes};
}
/**
* Decode set compute unit limit compute budget instruction and retrieve the instruction params.
*/
static decodeSetComputeUnitLimit(
instruction: TransactionInstruction,
): SetComputeUnitLimitParams {
this.checkProgramId(instruction.programId);
const {units} = decodeData(
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitLimit,
instruction.data,
);
return {units};
}
/**
* Decode set compute unit price compute budget instruction and retrieve the instruction params.
*/
static decodeSetComputeUnitPrice(
instruction: TransactionInstruction,
): SetComputeUnitPriceParams {
this.checkProgramId(instruction.programId);
const {microLamports} = decodeData(
COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice,
instruction.data,
);
return {microLamports};
}
/**
* @internal
*/
static checkProgramId(programId: PublicKey) {
if (!programId.equals(ComputeBudgetProgram.programId)) {
throw new Error(
'invalid instruction; programId is not ComputeBudgetProgram',
);
}
}
}
/**
* An enumeration of valid ComputeBudgetInstructionType's
*/
export type ComputeBudgetInstructionType =
// FIXME
// It would be preferable for this type to be `keyof ComputeBudgetInstructionInputData`
// but Typedoc does not transpile `keyof` expressions.
// See https://github.com/TypeStrong/typedoc/issues/1894
| 'RequestUnits'
| 'RequestHeapFrame'
| 'SetComputeUnitLimit'
| 'SetComputeUnitPrice';
type ComputeBudgetInstructionInputData = {
RequestUnits: IInstructionInputData & Readonly<RequestUnitsParams>;
RequestHeapFrame: IInstructionInputData & Readonly<RequestHeapFrameParams>;
SetComputeUnitLimit: IInstructionInputData &
Readonly<SetComputeUnitLimitParams>;
SetComputeUnitPrice: IInstructionInputData &
Readonly<SetComputeUnitPriceParams>;
};
/**
* Request units instruction params
*/
export interface RequestUnitsParams {
/** Units to request for transaction-wide compute */
units: number;
/** Prioritization fee lamports */
additionalFee: number;
}
/**
* Request heap frame instruction params
*/
export type RequestHeapFrameParams = {
/** Requested transaction-wide program heap size in bytes. Must be multiple of 1024. Applies to each program, including CPIs. */
bytes: number;
};
/**
* Set compute unit limit instruction params
*/
export interface SetComputeUnitLimitParams {
/** Transaction-wide compute unit limit */
units: number;
}
/**
* Set compute unit price instruction params
*/
export interface SetComputeUnitPriceParams {
/** Transaction compute unit price used for prioritization fees */
microLamports: number | bigint;
}
/**
* An enumeration of valid ComputeBudget InstructionType's
* @internal
*/
export const COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = Object.freeze<{
[Instruction in ComputeBudgetInstructionType]: InstructionType<
ComputeBudgetInstructionInputData[Instruction]
>;
}>({
RequestUnits: {
index: 0,
layout: BufferLayout.struct<
ComputeBudgetInstructionInputData['RequestUnits']
>([
BufferLayout.u8('instruction'),
BufferLayout.u32('units'),
BufferLayout.u32('additionalFee'),
]),
},
RequestHeapFrame: {
index: 1,
layout: BufferLayout.struct<
ComputeBudgetInstructionInputData['RequestHeapFrame']
>([BufferLayout.u8('instruction'), BufferLayout.u32('bytes')]),
},
SetComputeUnitLimit: {
index: 2,
layout: BufferLayout.struct<
ComputeBudgetInstructionInputData['SetComputeUnitLimit']
>([BufferLayout.u8('instruction'), BufferLayout.u32('units')]),
},
SetComputeUnitPrice: {
index: 3,
layout: BufferLayout.struct<
ComputeBudgetInstructionInputData['SetComputeUnitPrice']
>([BufferLayout.u8('instruction'), u64('microLamports')]),
},
});
/**
* Factory class for transaction instructions to interact with the Compute Budget program
*/
export class ComputeBudgetProgram {
/**
* @internal
*/
constructor() {}
/**
* Public key that identifies the Compute Budget program
*/
static programId: PublicKey = new PublicKey(
'ComputeBudget111111111111111111111111111111',
);
/**
* @deprecated Instead, call {@link setComputeUnitLimit} and/or {@link setComputeUnitPrice}
*/
static requestUnits(params: RequestUnitsParams): TransactionInstruction {
const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits;
const data = encodeData(type, params);
return new TransactionInstruction({
keys: [],
programId: this.programId,
data,
});
}
static requestHeapFrame(
params: RequestHeapFrameParams,
): TransactionInstruction {
const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame;
const data = encodeData(type, params);
return new TransactionInstruction({
keys: [],
programId: this.programId,
data,
});
}
static setComputeUnitLimit(
params: SetComputeUnitLimitParams,
): TransactionInstruction {
const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitLimit;
const data = encodeData(type, params);
return new TransactionInstruction({
keys: [],
programId: this.programId,
data,
});
}
static setComputeUnitPrice(
params: SetComputeUnitPriceParams,
): TransactionInstruction {
const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice;
const data = encodeData(type, {
microLamports: BigInt(params.microLamports),
});
return new TransactionInstruction({
keys: [],
programId: this.programId,
data,
});
}
}

View File

@ -1,157 +0,0 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import {Keypair} from '../keypair';
import {PublicKey} from '../publickey';
import {TransactionInstruction} from '../transaction';
import assert from '../utils/assert';
import {sign} from '../utils/ed25519';
const PRIVATE_KEY_BYTES = 64;
const PUBLIC_KEY_BYTES = 32;
const SIGNATURE_BYTES = 64;
/**
* Params for creating an ed25519 instruction using a public key
*/
export type CreateEd25519InstructionWithPublicKeyParams = {
publicKey: Uint8Array;
message: Uint8Array;
signature: Uint8Array;
instructionIndex?: number;
};
/**
* Params for creating an ed25519 instruction using a private key
*/
export type CreateEd25519InstructionWithPrivateKeyParams = {
privateKey: Uint8Array;
message: Uint8Array;
instructionIndex?: number;
};
const ED25519_INSTRUCTION_LAYOUT = BufferLayout.struct<
Readonly<{
messageDataOffset: number;
messageDataSize: number;
messageInstructionIndex: number;
numSignatures: number;
padding: number;
publicKeyInstructionIndex: number;
publicKeyOffset: number;
signatureInstructionIndex: number;
signatureOffset: number;
}>
>([
BufferLayout.u8('numSignatures'),
BufferLayout.u8('padding'),
BufferLayout.u16('signatureOffset'),
BufferLayout.u16('signatureInstructionIndex'),
BufferLayout.u16('publicKeyOffset'),
BufferLayout.u16('publicKeyInstructionIndex'),
BufferLayout.u16('messageDataOffset'),
BufferLayout.u16('messageDataSize'),
BufferLayout.u16('messageInstructionIndex'),
]);
export class Ed25519Program {
/**
* @internal
*/
constructor() {}
/**
* Public key that identifies the ed25519 program
*/
static programId: PublicKey = new PublicKey(
'Ed25519SigVerify111111111111111111111111111',
);
/**
* Create an ed25519 instruction with a public key and signature. The
* public key must be a buffer that is 32 bytes long, and the signature
* must be a buffer of 64 bytes.
*/
static createInstructionWithPublicKey(
params: CreateEd25519InstructionWithPublicKeyParams,
): TransactionInstruction {
const {publicKey, message, signature, instructionIndex} = params;
assert(
publicKey.length === PUBLIC_KEY_BYTES,
`Public Key must be ${PUBLIC_KEY_BYTES} bytes but received ${publicKey.length} bytes`,
);
assert(
signature.length === SIGNATURE_BYTES,
`Signature must be ${SIGNATURE_BYTES} bytes but received ${signature.length} bytes`,
);
const publicKeyOffset = ED25519_INSTRUCTION_LAYOUT.span;
const signatureOffset = publicKeyOffset + publicKey.length;
const messageDataOffset = signatureOffset + signature.length;
const numSignatures = 1;
const instructionData = Buffer.alloc(messageDataOffset + message.length);
const index =
instructionIndex == null
? 0xffff // An index of `u16::MAX` makes it default to the current instruction.
: instructionIndex;
ED25519_INSTRUCTION_LAYOUT.encode(
{
numSignatures,
padding: 0,
signatureOffset,
signatureInstructionIndex: index,
publicKeyOffset,
publicKeyInstructionIndex: index,
messageDataOffset,
messageDataSize: message.length,
messageInstructionIndex: index,
},
instructionData,
);
instructionData.fill(publicKey, publicKeyOffset);
instructionData.fill(signature, signatureOffset);
instructionData.fill(message, messageDataOffset);
return new TransactionInstruction({
keys: [],
programId: Ed25519Program.programId,
data: instructionData,
});
}
/**
* Create an ed25519 instruction with a private key. The private key
* must be a buffer that is 64 bytes long.
*/
static createInstructionWithPrivateKey(
params: CreateEd25519InstructionWithPrivateKeyParams,
): TransactionInstruction {
const {privateKey, message, instructionIndex} = params;
assert(
privateKey.length === PRIVATE_KEY_BYTES,
`Private key must be ${PRIVATE_KEY_BYTES} bytes but received ${privateKey.length} bytes`,
);
try {
const keypair = Keypair.fromSecretKey(privateKey);
const publicKey = keypair.publicKey.toBytes();
const signature = sign(message, keypair.secretKey);
return this.createInstructionWithPublicKey({
publicKey,
message,
signature,
instructionIndex,
});
} catch (error) {
throw new Error(`Error creating instruction; ${error}`);
}
}
}

View File

@ -1,7 +0,0 @@
export * from './address-lookup-table';
export * from './compute-budget';
export * from './ed25519';
export * from './secp256k1';
export * from './stake';
export * from './system';
export * from './vote';

View File

@ -1,228 +0,0 @@
import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';
import {keccak_256} from '@noble/hashes/sha3';
import {PublicKey} from '../publickey';
import {TransactionInstruction} from '../transaction';
import assert from '../utils/assert';
import {publicKeyCreate, ecdsaSign} from '../utils/secp256k1';
import {toBuffer} from '../utils/to-buffer';
const PRIVATE_KEY_BYTES = 32;
const ETHEREUM_ADDRESS_BYTES = 20;
const PUBLIC_KEY_BYTES = 64;
const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 11;
/**
* Params for creating an secp256k1 instruction using a public key
*/
export type CreateSecp256k1InstructionWithPublicKeyParams = {
publicKey: Buffer | Uint8Array | Array<number>;
message: Buffer | Uint8Array | Array<number>;
signature: Buffer | Uint8Array | Array<number>;
recoveryId: number;
instructionIndex?: number;
};
/**
* Params for creating an secp256k1 instruction using an Ethereum address
*/
export type CreateSecp256k1InstructionWithEthAddressParams = {
ethAddress: Buffer | Uint8Array | Array<number> | string;
message: Buffer | Uint8Array | Array<number>;
signature: Buffer | Uint8Array | Array<number>;
recoveryId: number;
instructionIndex?: number;
};
/**
* Params for creating an secp256k1 instruction using a private key
*/
export type CreateSecp256k1InstructionWithPrivateKeyParams = {
privateKey: Buffer | Uint8Array | Array<number>;
message: Buffer | Uint8Array | Array<number>;
instructionIndex?: number;
};
const SECP256K1_INSTRUCTION_LAYOUT = BufferLayout.struct<
Readonly<{
ethAddress: Uint8Array;
ethAddressInstructionIndex: number;
ethAddressOffset: number;
messageDataOffset: number;
messageDataSize: number;
messageInstructionIndex: number;
numSignatures: number;
recoveryId: number;
signature: Uint8Array;
signatureInstructionIndex: number;
signatureOffset: number;
}>
>([
BufferLayout.u8('numSignatures'),
BufferLayout.u16('signatureOffset'),
BufferLayout.u8('signatureInstructionIndex'),
BufferLayout.u16('ethAddressOffset'),
BufferLayout.u8('ethAddressInstructionIndex'),
BufferLayout.u16('messageDataOffset'),
BufferLayout.u16('messageDataSize'),
BufferLayout.u8('messageInstructionIndex'),
BufferLayout.blob(20, 'ethAddress'),
BufferLayout.blob(64, 'signature'),
BufferLayout.u8('recoveryId'),
]);
export class Secp256k1Program {
/**
* @internal
*/
constructor() {}
/**
* Public key that identifies the secp256k1 program
*/
static programId: PublicKey = new PublicKey(
'KeccakSecp256k11111111111111111111111111111',
);
/**
* Construct an Ethereum address from a secp256k1 public key buffer.
* @param {Buffer} publicKey a 64 byte secp256k1 public key buffer
*/
static publicKeyToEthAddress(
publicKey: Buffer | Uint8Array | Array<number>,
): Buffer {
assert(
publicKey.length === PUBLIC_KEY_BYTES,
`Public key must be ${PUBLIC_KEY_BYTES} bytes but received ${publicKey.length} bytes`,
);
try {
return Buffer.from(keccak_256(toBuffer(publicKey))).slice(
-ETHEREUM_ADDRESS_BYTES,
);
} catch (error) {
throw new Error(`Error constructing Ethereum address: ${error}`);
}
}
/**
* Create an secp256k1 instruction with a public key. The public key
* must be a buffer that is 64 bytes long.
*/
static createInstructionWithPublicKey(
params: CreateSecp256k1InstructionWithPublicKeyParams,
): TransactionInstruction {
const {publicKey, message, signature, recoveryId, instructionIndex} =
params;
return Secp256k1Program.createInstructionWithEthAddress({
ethAddress: Secp256k1Program.publicKeyToEthAddress(publicKey),
message,
signature,
recoveryId,
instructionIndex,
});
}
/**
* Create an secp256k1 instruction with an Ethereum address. The address
* must be a hex string or a buffer that is 20 bytes long.
*/
static createInstructionWithEthAddress(
params: CreateSecp256k1InstructionWithEthAddressParams,
): TransactionInstruction {
const {
ethAddress: rawAddress,
message,
signature,
recoveryId,
instructionIndex = 0,
} = params;
let ethAddress;
if (typeof rawAddress === 'string') {
if (rawAddress.startsWith('0x')) {
ethAddress = Buffer.from(rawAddress.substr(2), 'hex');
} else {
ethAddress = Buffer.from(rawAddress, 'hex');
}
} else {
ethAddress = rawAddress;
}
assert(
ethAddress.length === ETHEREUM_ADDRESS_BYTES,
`Address must be ${ETHEREUM_ADDRESS_BYTES} bytes but received ${ethAddress.length} bytes`,
);
const dataStart = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE;
const ethAddressOffset = dataStart;
const signatureOffset = dataStart + ethAddress.length;
const messageDataOffset = signatureOffset + signature.length + 1;
const numSignatures = 1;
const instructionData = Buffer.alloc(
SECP256K1_INSTRUCTION_LAYOUT.span + message.length,
);
SECP256K1_INSTRUCTION_LAYOUT.encode(
{
numSignatures,
signatureOffset,
signatureInstructionIndex: instructionIndex,
ethAddressOffset,
ethAddressInstructionIndex: instructionIndex,
messageDataOffset,
messageDataSize: message.length,
messageInstructionIndex: instructionIndex,
signature: toBuffer(signature),
ethAddress: toBuffer(ethAddress),
recoveryId,
},
instructionData,
);
instructionData.fill(toBuffer(message), SECP256K1_INSTRUCTION_LAYOUT.span);
return new TransactionInstruction({
keys: [],
programId: Secp256k1Program.programId,
data: instructionData,
});
}
/**
* Create an secp256k1 instruction with a private key. The private key
* must be a buffer that is 32 bytes long.
*/
static createInstructionWithPrivateKey(
params: CreateSecp256k1InstructionWithPrivateKeyParams,
): TransactionInstruction {
const {privateKey: pkey, message, instructionIndex} = params;
assert(
pkey.length === PRIVATE_KEY_BYTES,
`Private key must be ${PRIVATE_KEY_BYTES} bytes but received ${pkey.length} bytes`,
);
try {
const privateKey = toBuffer(pkey);
const publicKey = publicKeyCreate(
privateKey,
false /* isCompressed */,
).slice(1); // throw away leading byte
const messageHash = Buffer.from(keccak_256(toBuffer(message)));
const [signature, recoveryId] = ecdsaSign(messageHash, privateKey);
return this.createInstructionWithPublicKey({
publicKey,
message,
signature,
recoveryId,
instructionIndex,
});
} catch (error) {
throw new Error(`Error creating instruction; ${error}`);
}
}
}

View File

@ -1,923 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import {
encodeData,
decodeData,
InstructionType,
IInstructionInputData,
} from '../instruction';
import * as Layout from '../layout';
import {PublicKey} from '../publickey';
import {SystemProgram} from './system';
import {
SYSVAR_CLOCK_PUBKEY,
SYSVAR_RENT_PUBKEY,
SYSVAR_STAKE_HISTORY_PUBKEY,
} from '../sysvar';
import {Transaction, TransactionInstruction} from '../transaction';
import {toBuffer} from '../utils/to-buffer';
/**
* Address of the stake config account which configures the rate
* of stake warmup and cooldown as well as the slashing penalty.
*/
export const STAKE_CONFIG_ID = new PublicKey(
'StakeConfig11111111111111111111111111111111',
);
/**
* Stake account authority info
*/
export class Authorized {
/** stake authority */
staker: PublicKey;
/** withdraw authority */
withdrawer: PublicKey;
/**
* Create a new Authorized object
* @param staker the stake authority
* @param withdrawer the withdraw authority
*/
constructor(staker: PublicKey, withdrawer: PublicKey) {
this.staker = staker;
this.withdrawer = withdrawer;
}
}
type AuthorizedRaw = Readonly<{
staker: Uint8Array;
withdrawer: Uint8Array;
}>;
/**
* Stake account lockup info
*/
export class Lockup {
/** Unix timestamp of lockup expiration */
unixTimestamp: number;
/** Epoch of lockup expiration */
epoch: number;
/** Lockup custodian authority */
custodian: PublicKey;
/**
* Create a new Lockup object
*/
constructor(unixTimestamp: number, epoch: number, custodian: PublicKey) {
this.unixTimestamp = unixTimestamp;
this.epoch = epoch;
this.custodian = custodian;
}
/**
* Default, inactive Lockup value
*/
static default: Lockup = new Lockup(0, 0, PublicKey.default);
}
type LockupRaw = Readonly<{
custodian: Uint8Array;
epoch: number;
unixTimestamp: number;
}>;
/**
* Create stake account transaction params
*/
export type CreateStakeAccountParams = {
/** Address of the account which will fund creation */
fromPubkey: PublicKey;
/** Address of the new stake account */
stakePubkey: PublicKey;
/** Authorities of the new stake account */
authorized: Authorized;
/** Lockup of the new stake account */
lockup?: Lockup;
/** Funding amount */
lamports: number;
};
/**
* Create stake account with seed transaction params
*/
export type CreateStakeAccountWithSeedParams = {
fromPubkey: PublicKey;
stakePubkey: PublicKey;
basePubkey: PublicKey;
seed: string;
authorized: Authorized;
lockup?: Lockup;
lamports: number;
};
/**
* Initialize stake instruction params
*/
export type InitializeStakeParams = {
stakePubkey: PublicKey;
authorized: Authorized;
lockup?: Lockup;
};
/**
* Delegate stake instruction params
*/
export type DelegateStakeParams = {
stakePubkey: PublicKey;
authorizedPubkey: PublicKey;
votePubkey: PublicKey;
};
/**
* Authorize stake instruction params
*/
export type AuthorizeStakeParams = {
stakePubkey: PublicKey;
authorizedPubkey: PublicKey;
newAuthorizedPubkey: PublicKey;
stakeAuthorizationType: StakeAuthorizationType;
custodianPubkey?: PublicKey;
};
/**
* Authorize stake instruction params using a derived key
*/
export type AuthorizeWithSeedStakeParams = {
stakePubkey: PublicKey;
authorityBase: PublicKey;
authoritySeed: string;
authorityOwner: PublicKey;
newAuthorizedPubkey: PublicKey;
stakeAuthorizationType: StakeAuthorizationType;
custodianPubkey?: PublicKey;
};
/**
* Split stake instruction params
*/
export type SplitStakeParams = {
stakePubkey: PublicKey;
authorizedPubkey: PublicKey;
splitStakePubkey: PublicKey;
lamports: number;
};
/**
* Split with seed transaction params
*/
export type SplitStakeWithSeedParams = {
stakePubkey: PublicKey;
authorizedPubkey: PublicKey;
splitStakePubkey: PublicKey;
basePubkey: PublicKey;
seed: string;
lamports: number;
};
/**
* Withdraw stake instruction params
*/
export type WithdrawStakeParams = {
stakePubkey: PublicKey;
authorizedPubkey: PublicKey;
toPubkey: PublicKey;
lamports: number;
custodianPubkey?: PublicKey;
};
/**
* Deactivate stake instruction params
*/
export type DeactivateStakeParams = {
stakePubkey: PublicKey;
authorizedPubkey: PublicKey;
};
/**
* Merge stake instruction params
*/
export type MergeStakeParams = {
stakePubkey: PublicKey;
sourceStakePubKey: PublicKey;
authorizedPubkey: PublicKey;
};
/**
* Stake Instruction class
*/
export class StakeInstruction {
/**
* @internal
*/
constructor() {}
/**
* Decode a stake instruction and retrieve the instruction type.
*/
static decodeInstructionType(
instruction: TransactionInstruction,
): StakeInstructionType {
this.checkProgramId(instruction.programId);
const instructionTypeLayout = BufferLayout.u32('instruction');
const typeIndex = instructionTypeLayout.decode(instruction.data);
let type: StakeInstructionType | undefined;
for (const [ixType, layout] of Object.entries(STAKE_INSTRUCTION_LAYOUTS)) {
if (layout.index == typeIndex) {
type = ixType as StakeInstructionType;
break;
}
}
if (!type) {
throw new Error('Instruction type incorrect; not a StakeInstruction');
}
return type;
}
/**
* Decode a initialize stake instruction and retrieve the instruction params.
*/
static decodeInitialize(
instruction: TransactionInstruction,
): InitializeStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);
const {authorized, lockup} = decodeData(
STAKE_INSTRUCTION_LAYOUTS.Initialize,
instruction.data,
);
return {
stakePubkey: instruction.keys[0].pubkey,
authorized: new Authorized(
new PublicKey(authorized.staker),
new PublicKey(authorized.withdrawer),
),
lockup: new Lockup(
lockup.unixTimestamp,
lockup.epoch,
new PublicKey(lockup.custodian),
),
};
}
/**
* Decode a delegate stake instruction and retrieve the instruction params.
*/
static decodeDelegate(
instruction: TransactionInstruction,
): DelegateStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 6);
decodeData(STAKE_INSTRUCTION_LAYOUTS.Delegate, instruction.data);
return {
stakePubkey: instruction.keys[0].pubkey,
votePubkey: instruction.keys[1].pubkey,
authorizedPubkey: instruction.keys[5].pubkey,
};
}
/**
* Decode an authorize stake instruction and retrieve the instruction params.
*/
static decodeAuthorize(
instruction: TransactionInstruction,
): AuthorizeStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
const {newAuthorized, stakeAuthorizationType} = decodeData(
STAKE_INSTRUCTION_LAYOUTS.Authorize,
instruction.data,
);
const o: AuthorizeStakeParams = {
stakePubkey: instruction.keys[0].pubkey,
authorizedPubkey: instruction.keys[2].pubkey,
newAuthorizedPubkey: new PublicKey(newAuthorized),
stakeAuthorizationType: {
index: stakeAuthorizationType,
},
};
if (instruction.keys.length > 3) {
o.custodianPubkey = instruction.keys[3].pubkey;
}
return o;
}
/**
* Decode an authorize-with-seed stake instruction and retrieve the instruction params.
*/
static decodeAuthorizeWithSeed(
instruction: TransactionInstruction,
): AuthorizeWithSeedStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);
const {
newAuthorized,
stakeAuthorizationType,
authoritySeed,
authorityOwner,
} = decodeData(
STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed,
instruction.data,
);
const o: AuthorizeWithSeedStakeParams = {
stakePubkey: instruction.keys[0].pubkey,
authorityBase: instruction.keys[1].pubkey,
authoritySeed: authoritySeed,
authorityOwner: new PublicKey(authorityOwner),
newAuthorizedPubkey: new PublicKey(newAuthorized),
stakeAuthorizationType: {
index: stakeAuthorizationType,
},
};
if (instruction.keys.length > 3) {
o.custodianPubkey = instruction.keys[3].pubkey;
}
return o;
}
/**
* Decode a split stake instruction and retrieve the instruction params.
*/
static decodeSplit(instruction: TransactionInstruction): SplitStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
const {lamports} = decodeData(
STAKE_INSTRUCTION_LAYOUTS.Split,
instruction.data,
);
return {
stakePubkey: instruction.keys[0].pubkey,
splitStakePubkey: instruction.keys[1].pubkey,
authorizedPubkey: instruction.keys[2].pubkey,
lamports,
};
}
/**
* Decode a merge stake instruction and retrieve the instruction params.
*/
static decodeMerge(instruction: TransactionInstruction): MergeStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
decodeData(STAKE_INSTRUCTION_LAYOUTS.Merge, instruction.data);
return {
stakePubkey: instruction.keys[0].pubkey,
sourceStakePubKey: instruction.keys[1].pubkey,
authorizedPubkey: instruction.keys[4].pubkey,
};
}
/**
* Decode a withdraw stake instruction and retrieve the instruction params.
*/
static decodeWithdraw(
instruction: TransactionInstruction,
): WithdrawStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 5);
const {lamports} = decodeData(
STAKE_INSTRUCTION_LAYOUTS.Withdraw,
instruction.data,
);
const o: WithdrawStakeParams = {
stakePubkey: instruction.keys[0].pubkey,
toPubkey: instruction.keys[1].pubkey,
authorizedPubkey: instruction.keys[4].pubkey,
lamports,
};
if (instruction.keys.length > 5) {
o.custodianPubkey = instruction.keys[5].pubkey;
}
return o;
}
/**
* Decode a deactivate stake instruction and retrieve the instruction params.
*/
static decodeDeactivate(
instruction: TransactionInstruction,
): DeactivateStakeParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
decodeData(STAKE_INSTRUCTION_LAYOUTS.Deactivate, instruction.data);
return {
stakePubkey: instruction.keys[0].pubkey,
authorizedPubkey: instruction.keys[2].pubkey,
};
}
/**
* @internal
*/
static checkProgramId(programId: PublicKey) {
if (!programId.equals(StakeProgram.programId)) {
throw new Error('invalid instruction; programId is not StakeProgram');
}
}
/**
* @internal
*/
static checkKeyLength(keys: Array<any>, expectedLength: number) {
if (keys.length < expectedLength) {
throw new Error(
`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`,
);
}
}
}
/**
* An enumeration of valid StakeInstructionType's
*/
export type StakeInstructionType =
// FIXME
// It would be preferable for this type to be `keyof StakeInstructionInputData`
// but Typedoc does not transpile `keyof` expressions.
// See https://github.com/TypeStrong/typedoc/issues/1894
| 'Authorize'
| 'AuthorizeWithSeed'
| 'Deactivate'
| 'Delegate'
| 'Initialize'
| 'Merge'
| 'Split'
| 'Withdraw';
type StakeInstructionInputData = {
Authorize: IInstructionInputData &
Readonly<{
newAuthorized: Uint8Array;
stakeAuthorizationType: number;
}>;
AuthorizeWithSeed: IInstructionInputData &
Readonly<{
authorityOwner: Uint8Array;
authoritySeed: string;
instruction: number;
newAuthorized: Uint8Array;
stakeAuthorizationType: number;
}>;
Deactivate: IInstructionInputData;
Delegate: IInstructionInputData;
Initialize: IInstructionInputData &
Readonly<{
authorized: AuthorizedRaw;
lockup: LockupRaw;
}>;
Merge: IInstructionInputData;
Split: IInstructionInputData &
Readonly<{
lamports: number;
}>;
Withdraw: IInstructionInputData &
Readonly<{
lamports: number;
}>;
};
/**
* An enumeration of valid stake InstructionType's
* @internal
*/
export const STAKE_INSTRUCTION_LAYOUTS = Object.freeze<{
[Instruction in StakeInstructionType]: InstructionType<
StakeInstructionInputData[Instruction]
>;
}>({
Initialize: {
index: 0,
layout: BufferLayout.struct<StakeInstructionInputData['Initialize']>([
BufferLayout.u32('instruction'),
Layout.authorized(),
Layout.lockup(),
]),
},
Authorize: {
index: 1,
layout: BufferLayout.struct<StakeInstructionInputData['Authorize']>([
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('stakeAuthorizationType'),
]),
},
Delegate: {
index: 2,
layout: BufferLayout.struct<StakeInstructionInputData['Delegate']>([
BufferLayout.u32('instruction'),
]),
},
Split: {
index: 3,
layout: BufferLayout.struct<StakeInstructionInputData['Split']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
Withdraw: {
index: 4,
layout: BufferLayout.struct<StakeInstructionInputData['Withdraw']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
Deactivate: {
index: 5,
layout: BufferLayout.struct<StakeInstructionInputData['Deactivate']>([
BufferLayout.u32('instruction'),
]),
},
Merge: {
index: 7,
layout: BufferLayout.struct<StakeInstructionInputData['Merge']>([
BufferLayout.u32('instruction'),
]),
},
AuthorizeWithSeed: {
index: 8,
layout: BufferLayout.struct<StakeInstructionInputData['AuthorizeWithSeed']>(
[
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('stakeAuthorizationType'),
Layout.rustString('authoritySeed'),
Layout.publicKey('authorityOwner'),
],
),
},
});
/**
* Stake authorization type
*/
export type StakeAuthorizationType = {
/** The Stake Authorization index (from solana-stake-program) */
index: number;
};
/**
* An enumeration of valid StakeAuthorizationLayout's
*/
export const StakeAuthorizationLayout = Object.freeze({
Staker: {
index: 0,
},
Withdrawer: {
index: 1,
},
});
/**
* Factory class for transactions to interact with the Stake program
*/
export class StakeProgram {
/**
* @internal
*/
constructor() {}
/**
* Public key that identifies the Stake program
*/
static programId: PublicKey = new PublicKey(
'Stake11111111111111111111111111111111111111',
);
/**
* Max space of a Stake account
*
* This is generated from the solana-stake-program StakeState struct as
* `StakeState::size_of()`:
* https://docs.rs/solana-stake-program/latest/solana_stake_program/stake_state/enum.StakeState.html
*/
static space: number = 200;
/**
* Generate an Initialize instruction to add to a Stake Create transaction
*/
static initialize(params: InitializeStakeParams): TransactionInstruction {
const {stakePubkey, authorized, lockup: maybeLockup} = params;
const lockup: Lockup = maybeLockup || Lockup.default;
const type = STAKE_INSTRUCTION_LAYOUTS.Initialize;
const data = encodeData(type, {
authorized: {
staker: toBuffer(authorized.staker.toBuffer()),
withdrawer: toBuffer(authorized.withdrawer.toBuffer()),
},
lockup: {
unixTimestamp: lockup.unixTimestamp,
epoch: lockup.epoch,
custodian: toBuffer(lockup.custodian.toBuffer()),
},
});
const instructionData = {
keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
],
programId: this.programId,
data,
};
return new TransactionInstruction(instructionData);
}
/**
* Generate a Transaction that creates a new Stake account at
* an address generated with `from`, a seed, and the Stake programId
*/
static createAccountWithSeed(
params: CreateStakeAccountWithSeedParams,
): Transaction {
const transaction = new Transaction();
transaction.add(
SystemProgram.createAccountWithSeed({
fromPubkey: params.fromPubkey,
newAccountPubkey: params.stakePubkey,
basePubkey: params.basePubkey,
seed: params.seed,
lamports: params.lamports,
space: this.space,
programId: this.programId,
}),
);
const {stakePubkey, authorized, lockup} = params;
return transaction.add(this.initialize({stakePubkey, authorized, lockup}));
}
/**
* Generate a Transaction that creates a new Stake account
*/
static createAccount(params: CreateStakeAccountParams): Transaction {
const transaction = new Transaction();
transaction.add(
SystemProgram.createAccount({
fromPubkey: params.fromPubkey,
newAccountPubkey: params.stakePubkey,
lamports: params.lamports,
space: this.space,
programId: this.programId,
}),
);
const {stakePubkey, authorized, lockup} = params;
return transaction.add(this.initialize({stakePubkey, authorized, lockup}));
}
/**
* Generate a Transaction that delegates Stake tokens to a validator
* Vote PublicKey. This transaction can also be used to redelegate Stake
* to a new validator Vote PublicKey.
*/
static delegate(params: DelegateStakeParams): Transaction {
const {stakePubkey, authorizedPubkey, votePubkey} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.Delegate;
const data = encodeData(type);
return new Transaction().add({
keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: votePubkey, isSigner: false, isWritable: false},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that authorizes a new PublicKey as Staker
* or Withdrawer on the Stake account.
*/
static authorize(params: AuthorizeStakeParams): Transaction {
const {
stakePubkey,
authorizedPubkey,
newAuthorizedPubkey,
stakeAuthorizationType,
custodianPubkey,
} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.Authorize;
const data = encodeData(type, {
newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()),
stakeAuthorizationType: stakeAuthorizationType.index,
});
const keys = [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: true},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
];
if (custodianPubkey) {
keys.push({pubkey: custodianPubkey, isSigner: false, isWritable: false});
}
return new Transaction().add({
keys,
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that authorizes a new PublicKey as Staker
* or Withdrawer on the Stake account.
*/
static authorizeWithSeed(params: AuthorizeWithSeedStakeParams): Transaction {
const {
stakePubkey,
authorityBase,
authoritySeed,
authorityOwner,
newAuthorizedPubkey,
stakeAuthorizationType,
custodianPubkey,
} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed;
const data = encodeData(type, {
newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()),
stakeAuthorizationType: stakeAuthorizationType.index,
authoritySeed: authoritySeed,
authorityOwner: toBuffer(authorityOwner.toBuffer()),
});
const keys = [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: authorityBase, isSigner: true, isWritable: false},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
];
if (custodianPubkey) {
keys.push({pubkey: custodianPubkey, isSigner: false, isWritable: false});
}
return new Transaction().add({
keys,
programId: this.programId,
data,
});
}
/**
* @internal
*/
static splitInstruction(params: SplitStakeParams): TransactionInstruction {
const {stakePubkey, authorizedPubkey, splitStakePubkey, lamports} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.Split;
const data = encodeData(type, {lamports});
return new TransactionInstruction({
keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: splitStakePubkey, isSigner: false, isWritable: true},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that splits Stake tokens into another stake account
*/
static split(params: SplitStakeParams): Transaction {
const transaction = new Transaction();
transaction.add(
SystemProgram.createAccount({
fromPubkey: params.authorizedPubkey,
newAccountPubkey: params.splitStakePubkey,
lamports: 0,
space: this.space,
programId: this.programId,
}),
);
return transaction.add(this.splitInstruction(params));
}
/**
* Generate a Transaction that splits Stake tokens into another account
* derived from a base public key and seed
*/
static splitWithSeed(params: SplitStakeWithSeedParams): Transaction {
const {
stakePubkey,
authorizedPubkey,
splitStakePubkey,
basePubkey,
seed,
lamports,
} = params;
const transaction = new Transaction();
transaction.add(
SystemProgram.allocate({
accountPubkey: splitStakePubkey,
basePubkey,
seed,
space: this.space,
programId: this.programId,
}),
);
return transaction.add(
this.splitInstruction({
stakePubkey,
authorizedPubkey,
splitStakePubkey,
lamports,
}),
);
}
/**
* Generate a Transaction that merges Stake accounts.
*/
static merge(params: MergeStakeParams): Transaction {
const {stakePubkey, sourceStakePubKey, authorizedPubkey} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.Merge;
const data = encodeData(type);
return new Transaction().add({
keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: sourceStakePubKey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that withdraws deactivated Stake tokens.
*/
static withdraw(params: WithdrawStakeParams): Transaction {
const {stakePubkey, authorizedPubkey, toPubkey, lamports, custodianPubkey} =
params;
const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw;
const data = encodeData(type, {lamports});
const keys = [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: toPubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{
pubkey: SYSVAR_STAKE_HISTORY_PUBKEY,
isSigner: false,
isWritable: false,
},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
];
if (custodianPubkey) {
keys.push({pubkey: custodianPubkey, isSigner: false, isWritable: false});
}
return new Transaction().add({
keys,
programId: this.programId,
data,
});
}
/**
* Generate a Transaction that deactivates Stake tokens.
*/
static deactivate(params: DeactivateStakeParams): Transaction {
const {stakePubkey, authorizedPubkey} = params;
const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate;
const data = encodeData(type);
return new Transaction().add({
keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
});
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,541 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import {
encodeData,
decodeData,
InstructionType,
IInstructionInputData,
} from '../instruction';
import * as Layout from '../layout';
import {PublicKey} from '../publickey';
import {SystemProgram} from './system';
import {SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY} from '../sysvar';
import {Transaction, TransactionInstruction} from '../transaction';
import {toBuffer} from '../utils/to-buffer';
/**
* Vote account info
*/
export class VoteInit {
nodePubkey: PublicKey;
authorizedVoter: PublicKey;
authorizedWithdrawer: PublicKey;
commission: number; /** [0, 100] */
constructor(
nodePubkey: PublicKey,
authorizedVoter: PublicKey,
authorizedWithdrawer: PublicKey,
commission: number,
) {
this.nodePubkey = nodePubkey;
this.authorizedVoter = authorizedVoter;
this.authorizedWithdrawer = authorizedWithdrawer;
this.commission = commission;
}
}
/**
* Create vote account transaction params
*/
export type CreateVoteAccountParams = {
fromPubkey: PublicKey;
votePubkey: PublicKey;
voteInit: VoteInit;
lamports: number;
};
/**
* InitializeAccount instruction params
*/
export type InitializeAccountParams = {
votePubkey: PublicKey;
nodePubkey: PublicKey;
voteInit: VoteInit;
};
/**
* Authorize instruction params
*/
export type AuthorizeVoteParams = {
votePubkey: PublicKey;
/** Current vote or withdraw authority, depending on `voteAuthorizationType` */
authorizedPubkey: PublicKey;
newAuthorizedPubkey: PublicKey;
voteAuthorizationType: VoteAuthorizationType;
};
/**
* AuthorizeWithSeed instruction params
*/
export type AuthorizeVoteWithSeedParams = {
currentAuthorityDerivedKeyBasePubkey: PublicKey;
currentAuthorityDerivedKeyOwnerPubkey: PublicKey;
currentAuthorityDerivedKeySeed: string;
newAuthorizedPubkey: PublicKey;
voteAuthorizationType: VoteAuthorizationType;
votePubkey: PublicKey;
};
/**
* Withdraw from vote account transaction params
*/
export type WithdrawFromVoteAccountParams = {
votePubkey: PublicKey;
authorizedWithdrawerPubkey: PublicKey;
lamports: number;
toPubkey: PublicKey;
};
/**
* Vote Instruction class
*/
export class VoteInstruction {
/**
* @internal
*/
constructor() {}
/**
* Decode a vote instruction and retrieve the instruction type.
*/
static decodeInstructionType(
instruction: TransactionInstruction,
): VoteInstructionType {
this.checkProgramId(instruction.programId);
const instructionTypeLayout = BufferLayout.u32('instruction');
const typeIndex = instructionTypeLayout.decode(instruction.data);
let type: VoteInstructionType | undefined;
for (const [ixType, layout] of Object.entries(VOTE_INSTRUCTION_LAYOUTS)) {
if (layout.index == typeIndex) {
type = ixType as VoteInstructionType;
break;
}
}
if (!type) {
throw new Error('Instruction type incorrect; not a VoteInstruction');
}
return type;
}
/**
* Decode an initialize vote instruction and retrieve the instruction params.
*/
static decodeInitializeAccount(
instruction: TransactionInstruction,
): InitializeAccountParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 4);
const {voteInit} = decodeData(
VOTE_INSTRUCTION_LAYOUTS.InitializeAccount,
instruction.data,
);
return {
votePubkey: instruction.keys[0].pubkey,
nodePubkey: instruction.keys[3].pubkey,
voteInit: new VoteInit(
new PublicKey(voteInit.nodePubkey),
new PublicKey(voteInit.authorizedVoter),
new PublicKey(voteInit.authorizedWithdrawer),
voteInit.commission,
),
};
}
/**
* Decode an authorize instruction and retrieve the instruction params.
*/
static decodeAuthorize(
instruction: TransactionInstruction,
): AuthorizeVoteParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
const {newAuthorized, voteAuthorizationType} = decodeData(
VOTE_INSTRUCTION_LAYOUTS.Authorize,
instruction.data,
);
return {
votePubkey: instruction.keys[0].pubkey,
authorizedPubkey: instruction.keys[2].pubkey,
newAuthorizedPubkey: new PublicKey(newAuthorized),
voteAuthorizationType: {
index: voteAuthorizationType,
},
};
}
/**
* Decode an authorize instruction and retrieve the instruction params.
*/
static decodeAuthorizeWithSeed(
instruction: TransactionInstruction,
): AuthorizeVoteWithSeedParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
const {
voteAuthorizeWithSeedArgs: {
currentAuthorityDerivedKeyOwnerPubkey,
currentAuthorityDerivedKeySeed,
newAuthorized,
voteAuthorizationType,
},
} = decodeData(
VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed,
instruction.data,
);
return {
currentAuthorityDerivedKeyBasePubkey: instruction.keys[2].pubkey,
currentAuthorityDerivedKeyOwnerPubkey: new PublicKey(
currentAuthorityDerivedKeyOwnerPubkey,
),
currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed,
newAuthorizedPubkey: new PublicKey(newAuthorized),
voteAuthorizationType: {
index: voteAuthorizationType,
},
votePubkey: instruction.keys[0].pubkey,
};
}
/**
* Decode a withdraw instruction and retrieve the instruction params.
*/
static decodeWithdraw(
instruction: TransactionInstruction,
): WithdrawFromVoteAccountParams {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);
const {lamports} = decodeData(
VOTE_INSTRUCTION_LAYOUTS.Withdraw,
instruction.data,
);
return {
votePubkey: instruction.keys[0].pubkey,
authorizedWithdrawerPubkey: instruction.keys[2].pubkey,
lamports,
toPubkey: instruction.keys[1].pubkey,
};
}
/**
* @internal
*/
static checkProgramId(programId: PublicKey) {
if (!programId.equals(VoteProgram.programId)) {
throw new Error('invalid instruction; programId is not VoteProgram');
}
}
/**
* @internal
*/
static checkKeyLength(keys: Array<any>, expectedLength: number) {
if (keys.length < expectedLength) {
throw new Error(
`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`,
);
}
}
}
/**
* An enumeration of valid VoteInstructionType's
*/
export type VoteInstructionType =
// FIXME
// It would be preferable for this type to be `keyof VoteInstructionInputData`
// but Typedoc does not transpile `keyof` expressions.
// See https://github.com/TypeStrong/typedoc/issues/1894
'Authorize' | 'AuthorizeWithSeed' | 'InitializeAccount' | 'Withdraw';
/** @internal */
export type VoteAuthorizeWithSeedArgs = Readonly<{
currentAuthorityDerivedKeyOwnerPubkey: Uint8Array;
currentAuthorityDerivedKeySeed: string;
newAuthorized: Uint8Array;
voteAuthorizationType: number;
}>;
type VoteInstructionInputData = {
Authorize: IInstructionInputData & {
newAuthorized: Uint8Array;
voteAuthorizationType: number;
};
AuthorizeWithSeed: IInstructionInputData & {
voteAuthorizeWithSeedArgs: VoteAuthorizeWithSeedArgs;
};
InitializeAccount: IInstructionInputData & {
voteInit: Readonly<{
authorizedVoter: Uint8Array;
authorizedWithdrawer: Uint8Array;
commission: number;
nodePubkey: Uint8Array;
}>;
};
Withdraw: IInstructionInputData & {
lamports: number;
};
};
const VOTE_INSTRUCTION_LAYOUTS = Object.freeze<{
[Instruction in VoteInstructionType]: InstructionType<
VoteInstructionInputData[Instruction]
>;
}>({
InitializeAccount: {
index: 0,
layout: BufferLayout.struct<VoteInstructionInputData['InitializeAccount']>([
BufferLayout.u32('instruction'),
Layout.voteInit(),
]),
},
Authorize: {
index: 1,
layout: BufferLayout.struct<VoteInstructionInputData['Authorize']>([
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('voteAuthorizationType'),
]),
},
Withdraw: {
index: 3,
layout: BufferLayout.struct<VoteInstructionInputData['Withdraw']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
AuthorizeWithSeed: {
index: 10,
layout: BufferLayout.struct<VoteInstructionInputData['AuthorizeWithSeed']>([
BufferLayout.u32('instruction'),
Layout.voteAuthorizeWithSeedArgs(),
]),
},
});
/**
* VoteAuthorize type
*/
export type VoteAuthorizationType = {
/** The VoteAuthorize index (from solana-vote-program) */
index: number;
};
/**
* An enumeration of valid VoteAuthorization layouts.
*/
export const VoteAuthorizationLayout = Object.freeze({
Voter: {
index: 0,
},
Withdrawer: {
index: 1,
},
});
/**
* Factory class for transactions to interact with the Vote program
*/
export class VoteProgram {
/**
* @internal
*/
constructor() {}
/**
* Public key that identifies the Vote program
*/
static programId: PublicKey = new PublicKey(
'Vote111111111111111111111111111111111111111',
);
/**
* Max space of a Vote account
*
* This is generated from the solana-vote-program VoteState struct as
* `VoteState::size_of()`:
* https://docs.rs/solana-vote-program/1.9.5/solana_vote_program/vote_state/struct.VoteState.html#method.size_of
*/
static space: number = 3731;
/**
* Generate an Initialize instruction.
*/
static initializeAccount(
params: InitializeAccountParams,
): TransactionInstruction {
const {votePubkey, nodePubkey, voteInit} = params;
const type = VOTE_INSTRUCTION_LAYOUTS.InitializeAccount;
const data = encodeData(type, {
voteInit: {
nodePubkey: toBuffer(voteInit.nodePubkey.toBuffer()),
authorizedVoter: toBuffer(voteInit.authorizedVoter.toBuffer()),
authorizedWithdrawer: toBuffer(
voteInit.authorizedWithdrawer.toBuffer(),
),
commission: voteInit.commission,
},
});
const instructionData = {
keys: [
{pubkey: votePubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: nodePubkey, isSigner: true, isWritable: false},
],
programId: this.programId,
data,
};
return new TransactionInstruction(instructionData);
}
/**
* Generate a transaction that creates a new Vote account.
*/
static createAccount(params: CreateVoteAccountParams): Transaction {
const transaction = new Transaction();
transaction.add(
SystemProgram.createAccount({
fromPubkey: params.fromPubkey,
newAccountPubkey: params.votePubkey,
lamports: params.lamports,
space: this.space,
programId: this.programId,
}),
);
return transaction.add(
this.initializeAccount({
votePubkey: params.votePubkey,
nodePubkey: params.voteInit.nodePubkey,
voteInit: params.voteInit,
}),
);
}
/**
* Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account.
*/
static authorize(params: AuthorizeVoteParams): Transaction {
const {
votePubkey,
authorizedPubkey,
newAuthorizedPubkey,
voteAuthorizationType,
} = params;
const type = VOTE_INSTRUCTION_LAYOUTS.Authorize;
const data = encodeData(type, {
newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()),
voteAuthorizationType: voteAuthorizationType.index,
});
const keys = [
{pubkey: votePubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
];
return new Transaction().add({
keys,
programId: this.programId,
data,
});
}
/**
* Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account
* where the current Voter or Withdrawer authority is a derived key.
*/
static authorizeWithSeed(params: AuthorizeVoteWithSeedParams): Transaction {
const {
currentAuthorityDerivedKeyBasePubkey,
currentAuthorityDerivedKeyOwnerPubkey,
currentAuthorityDerivedKeySeed,
newAuthorizedPubkey,
voteAuthorizationType,
votePubkey,
} = params;
const type = VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed;
const data = encodeData(type, {
voteAuthorizeWithSeedArgs: {
currentAuthorityDerivedKeyOwnerPubkey: toBuffer(
currentAuthorityDerivedKeyOwnerPubkey.toBuffer(),
),
currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed,
newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()),
voteAuthorizationType: voteAuthorizationType.index,
},
});
const keys = [
{pubkey: votePubkey, isSigner: false, isWritable: true},
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
{
pubkey: currentAuthorityDerivedKeyBasePubkey,
isSigner: true,
isWritable: false,
},
];
return new Transaction().add({
keys,
programId: this.programId,
data,
});
}
/**
* Generate a transaction to withdraw from a Vote account.
*/
static withdraw(params: WithdrawFromVoteAccountParams): Transaction {
const {votePubkey, authorizedWithdrawerPubkey, lamports, toPubkey} = params;
const type = VOTE_INSTRUCTION_LAYOUTS.Withdraw;
const data = encodeData(type, {lamports});
const keys = [
{pubkey: votePubkey, isSigner: false, isWritable: true},
{pubkey: toPubkey, isSigner: false, isWritable: true},
{pubkey: authorizedWithdrawerPubkey, isSigner: true, isWritable: false},
];
return new Transaction().add({
keys,
programId: this.programId,
data,
});
}
/**
* Generate a transaction to withdraw safely from a Vote account.
*
* This function was created as a safeguard for vote accounts running validators, `safeWithdraw`
* checks that the withdraw amount will not exceed the specified balance while leaving enough left
* to cover rent. If you wish to close the vote account by withdrawing the full amount, call the
* `withdraw` method directly.
*/
static safeWithdraw(
params: WithdrawFromVoteAccountParams,
currentVoteAccountBalance: number,
rentExemptMinimum: number,
): Transaction {
if (params.lamports > currentVoteAccountBalance - rentExemptMinimum) {
throw new Error(
'Withdraw will leave vote account with insuffcient funds.',
);
}
return VoteProgram.withdraw(params);
}
}

View File

@ -1,259 +0,0 @@
import BN from 'bn.js';
import bs58 from 'bs58';
import {Buffer} from 'buffer';
import {sha256} from '@noble/hashes/sha256';
import {isOnCurve} from './utils/ed25519';
import {Struct, SOLANA_SCHEMA} from './utils/borsh-schema';
import {toBuffer} from './utils/to-buffer';
/**
* Maximum length of derived pubkey seed
*/
export const MAX_SEED_LENGTH = 32;
/**
* Size of public key in bytes
*/
export const PUBLIC_KEY_LENGTH = 32;
/**
* Value to be converted into public key
*/
export type PublicKeyInitData =
| number
| string
| Uint8Array
| Array<number>
| PublicKeyData;
/**
* JSON object representation of PublicKey class
*/
export type PublicKeyData = {
/** @internal */
_bn: BN;
};
function isPublicKeyData(value: PublicKeyInitData): value is PublicKeyData {
return (value as PublicKeyData)._bn !== undefined;
}
// local counter used by PublicKey.unique()
let uniquePublicKeyCounter = 1;
/**
* A public key
*/
export class PublicKey extends Struct {
/** @internal */
_bn: BN;
/**
* Create a new PublicKey object
* @param value ed25519 public key as buffer or base-58 encoded string
*/
constructor(value: PublicKeyInitData) {
super({});
if (isPublicKeyData(value)) {
this._bn = value._bn;
} else {
if (typeof value === 'string') {
// assume base 58 encoding by default
const decoded = bs58.decode(value);
if (decoded.length != PUBLIC_KEY_LENGTH) {
throw new Error(`Invalid public key input`);
}
this._bn = new BN(decoded);
} else {
this._bn = new BN(value);
}
if (this._bn.byteLength() > PUBLIC_KEY_LENGTH) {
throw new Error(`Invalid public key input`);
}
}
}
/**
* Returns a unique PublicKey for tests and benchmarks using a counter
*/
static unique(): PublicKey {
const key = new PublicKey(uniquePublicKeyCounter);
uniquePublicKeyCounter += 1;
return new PublicKey(key.toBuffer());
}
/**
* Default public key value. The base58-encoded string representation is all ones (as seen below)
* The underlying BN number is 32 bytes that are all zeros
*/
static default: PublicKey = new PublicKey('11111111111111111111111111111111');
/**
* Checks if two publicKeys are equal
*/
equals(publicKey: PublicKey): boolean {
return this._bn.eq(publicKey._bn);
}
/**
* Return the base-58 representation of the public key
*/
toBase58(): string {
return bs58.encode(this.toBytes());
}
toJSON(): string {
return this.toBase58();
}
/**
* Return the byte array representation of the public key in big endian
*/
toBytes(): Uint8Array {
const buf = this.toBuffer();
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
/**
* Return the Buffer representation of the public key in big endian
*/
toBuffer(): Buffer {
const b = this._bn.toArrayLike(Buffer);
if (b.length === PUBLIC_KEY_LENGTH) {
return b;
}
const zeroPad = Buffer.alloc(32);
b.copy(zeroPad, 32 - b.length);
return zeroPad;
}
get [Symbol.toStringTag](): string {
return `PublicKey(${this.toString()})`;
}
/**
* Return the base-58 representation of the public key
*/
toString(): string {
return this.toBase58();
}
/**
* Derive a public key from another key, a seed, and a program ID.
* The program ID will also serve as the owner of the public key, giving
* it permission to write data to the account.
*/
/* eslint-disable require-await */
static async createWithSeed(
fromPublicKey: PublicKey,
seed: string,
programId: PublicKey,
): Promise<PublicKey> {
const buffer = Buffer.concat([
fromPublicKey.toBuffer(),
Buffer.from(seed),
programId.toBuffer(),
]);
const publicKeyBytes = sha256(buffer);
return new PublicKey(publicKeyBytes);
}
/**
* Derive a program address from seeds and a program ID.
*/
/* eslint-disable require-await */
static createProgramAddressSync(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): PublicKey {
let buffer = Buffer.alloc(0);
seeds.forEach(function (seed) {
if (seed.length > MAX_SEED_LENGTH) {
throw new TypeError(`Max seed length exceeded`);
}
buffer = Buffer.concat([buffer, toBuffer(seed)]);
});
buffer = Buffer.concat([
buffer,
programId.toBuffer(),
Buffer.from('ProgramDerivedAddress'),
]);
const publicKeyBytes = sha256(buffer);
if (isOnCurve(publicKeyBytes)) {
throw new Error(`Invalid seeds, address must fall off the curve`);
}
return new PublicKey(publicKeyBytes);
}
/**
* Async version of createProgramAddressSync
* For backwards compatibility
*
* @deprecated Use {@link createProgramAddressSync} instead
*/
/* eslint-disable require-await */
static async createProgramAddress(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): Promise<PublicKey> {
return this.createProgramAddressSync(seeds, programId);
}
/**
* Find a valid program address
*
* Valid program addresses must fall off the ed25519 curve. This function
* iterates a nonce until it finds one that when combined with the seeds
* results in a valid program address.
*/
static findProgramAddressSync(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): [PublicKey, number] {
let nonce = 255;
let address;
while (nonce != 0) {
try {
const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
address = this.createProgramAddressSync(seedsWithNonce, programId);
} catch (err) {
if (err instanceof TypeError) {
throw err;
}
nonce--;
continue;
}
return [address, nonce];
}
throw new Error(`Unable to find a viable program address nonce`);
}
/**
* Async version of findProgramAddressSync
* For backwards compatibility
*
* @deprecated Use {@link findProgramAddressSync} instead
*/
static async findProgramAddress(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): Promise<[PublicKey, number]> {
return this.findProgramAddressSync(seeds, programId);
}
/**
* Check that a pubkey is on the ed25519 curve.
*/
static isOnCurve(pubkeyData: PublicKeyInitData): boolean {
const pubkey = new PublicKey(pubkeyData);
return isOnCurve(pubkey.toBytes());
}
}
SOLANA_SCHEMA.set(PublicKey, {
kind: 'struct',
fields: [['_bn', 'u256']],
});

View File

@ -1,4 +0,0 @@
import {ICommonWebSocketFactory} from 'rpc-websockets/dist/lib/client/client.types';
import WebsocketFactory from 'rpc-websockets/dist/lib/client/websocket';
export default WebsocketFactory as ICommonWebSocketFactory;

View File

@ -1,79 +0,0 @@
import RpcWebSocketCommonClient from 'rpc-websockets/dist/lib/client';
import RpcWebSocketBrowserFactory from 'rpc-websockets/dist/lib/client/websocket.browser';
import {
ICommonWebSocket,
IWSClientAdditionalOptions,
NodeWebSocketType,
NodeWebSocketTypeOptions,
} from 'rpc-websockets/dist/lib/client/client.types';
import createRpc from './rpc-websocket-factory';
interface IHasReadyState {
readyState: WebSocket['readyState'];
}
export default class RpcWebSocketClient extends RpcWebSocketCommonClient {
private underlyingSocket: IHasReadyState | undefined;
constructor(
address?: string,
options?: IWSClientAdditionalOptions & NodeWebSocketTypeOptions,
generate_request_id?: (
method: string,
params: object | Array<any>,
) => number,
) {
const webSocketFactory = (url: string) => {
const rpc = createRpc(url, {
autoconnect: true,
max_reconnects: 5,
reconnect: true,
reconnect_interval: 1000,
...options,
});
if ('socket' in rpc) {
this.underlyingSocket = (
rpc as ReturnType<typeof RpcWebSocketBrowserFactory>
).socket;
} else {
this.underlyingSocket = rpc as NodeWebSocketType;
}
return rpc as ICommonWebSocket;
};
super(webSocketFactory, address, options, generate_request_id);
}
call(
...args: Parameters<RpcWebSocketCommonClient['call']>
): ReturnType<RpcWebSocketCommonClient['call']> {
const readyState = this.underlyingSocket?.readyState;
if (readyState === 1 /* WebSocket.OPEN */) {
return super.call(...args);
}
return Promise.reject(
new Error(
'Tried to call a JSON-RPC method `' +
args[0] +
'` but the socket was not `CONNECTING` or `OPEN` (`readyState` was ' +
readyState +
')',
),
);
}
notify(
...args: Parameters<RpcWebSocketCommonClient['notify']>
): ReturnType<RpcWebSocketCommonClient['notify']> {
const readyState = this.underlyingSocket?.readyState;
if (readyState === 1 /* WebSocket.OPEN */) {
return super.notify(...args);
}
return Promise.reject(
new Error(
'Tried to send a JSON-RPC notification `' +
args[0] +
'` but the socket was not `CONNECTING` or `OPEN` (`readyState` was ' +
readyState +
')',
),
);
}
}

View File

@ -1,37 +0,0 @@
import {PublicKey} from './publickey';
export const SYSVAR_CLOCK_PUBKEY = new PublicKey(
'SysvarC1ock11111111111111111111111111111111',
);
export const SYSVAR_EPOCH_SCHEDULE_PUBKEY = new PublicKey(
'SysvarEpochSchedu1e111111111111111111111111',
);
export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
'Sysvar1nstructions1111111111111111111111111',
);
export const SYSVAR_RECENT_BLOCKHASHES_PUBKEY = new PublicKey(
'SysvarRecentB1ockHashes11111111111111111111',
);
export const SYSVAR_RENT_PUBKEY = new PublicKey(
'SysvarRent111111111111111111111111111111111',
);
export const SYSVAR_REWARDS_PUBKEY = new PublicKey(
'SysvarRewards111111111111111111111111111111',
);
export const SYSVAR_SLOT_HASHES_PUBKEY = new PublicKey(
'SysvarS1otHashes111111111111111111111111111',
);
export const SYSVAR_SLOT_HISTORY_PUBKEY = new PublicKey(
'SysvarS1otHistory11111111111111111111111111',
);
export const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey(
'SysvarStakeHistory1111111111111111111111111',
);

View File

@ -1,23 +0,0 @@
// TODO: These constants should be removed in favor of reading them out of a
// Syscall account
/**
* @internal
*/
export const NUM_TICKS_PER_SECOND = 160;
/**
* @internal
*/
export const DEFAULT_TICKS_PER_SLOT = 64;
/**
* @internal
*/
export const NUM_SLOTS_PER_SECOND =
NUM_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT;
/**
* @internal
*/
export const MS_PER_SLOT = 1000 / NUM_SLOTS_PER_SECOND;

View File

@ -1,12 +0,0 @@
/**
* Maximum over-the-wire size of a Transaction
*
* 1280 is IPv6 minimum MTU
* 40 bytes is the size of the IPv6 header
* 8 bytes is the size of the fragment header
*/
export const PACKET_DATA_SIZE = 1280 - 40 - 8;
export const VERSION_PREFIX_MASK = 0x7f;
export const SIGNATURE_LENGTH_IN_BYTES = 64;

View File

@ -1,48 +0,0 @@
export class TransactionExpiredBlockheightExceededError extends Error {
signature: string;
constructor(signature: string) {
super(`Signature ${signature} has expired: block height exceeded.`);
this.signature = signature;
}
}
Object.defineProperty(
TransactionExpiredBlockheightExceededError.prototype,
'name',
{
value: 'TransactionExpiredBlockheightExceededError',
},
);
export class TransactionExpiredTimeoutError extends Error {
signature: string;
constructor(signature: string, timeoutSeconds: number) {
super(
`Transaction was not confirmed in ${timeoutSeconds.toFixed(
2,
)} seconds. It is ` +
'unknown if it succeeded or failed. Check signature ' +
`${signature} using the Solana Explorer or CLI tools.`,
);
this.signature = signature;
}
}
Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', {
value: 'TransactionExpiredTimeoutError',
});
export class TransactionExpiredNonceInvalidError extends Error {
signature: string;
constructor(signature: string) {
super(`Signature ${signature} has expired: the nonce is no longer valid.`);
this.signature = signature;
}
}
Object.defineProperty(TransactionExpiredNonceInvalidError.prototype, 'name', {
value: 'TransactionExpiredNonceInvalidError',
});

View File

@ -1,5 +0,0 @@
export * from './constants';
export * from './expiry-custom-errors';
export * from './legacy';
export * from './message';
export * from './versioned';

View File

@ -1,908 +0,0 @@
import bs58 from 'bs58';
import {Buffer} from 'buffer';
import {PACKET_DATA_SIZE, SIGNATURE_LENGTH_IN_BYTES} from './constants';
import {Connection} from '../connection';
import {Message} from '../message';
import {PublicKey} from '../publickey';
import * as shortvec from '../utils/shortvec-encoding';
import {toBuffer} from '../utils/to-buffer';
import invariant from '../utils/assert';
import type {Signer} from '../keypair';
import type {Blockhash} from '../blockhash';
import type {CompiledInstruction} from '../message';
import {sign, verify} from '../utils/ed25519';
/**
* Transaction signature as base-58 encoded string
*/
export type TransactionSignature = string;
export const enum TransactionStatus {
BLOCKHEIGHT_EXCEEDED,
PROCESSED,
TIMED_OUT,
NONCE_INVALID,
}
/**
* Default (empty) signature
*/
const DEFAULT_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0);
/**
* Account metadata used to define instructions
*/
export type AccountMeta = {
/** An account's public key */
pubkey: PublicKey;
/** True if an instruction requires a transaction signature matching `pubkey` */
isSigner: boolean;
/** True if the `pubkey` can be loaded as a read-write account. */
isWritable: boolean;
};
/**
* List of TransactionInstruction object fields that may be initialized at construction
*/
export type TransactionInstructionCtorFields = {
keys: Array<AccountMeta>;
programId: PublicKey;
data?: Buffer;
};
/**
* Configuration object for Transaction.serialize()
*/
export type SerializeConfig = {
/** Require all transaction signatures be present (default: true) */
requireAllSignatures?: boolean;
/** Verify provided signatures (default: true) */
verifySignatures?: boolean;
};
/**
* @internal
*/
export interface TransactionInstructionJSON {
keys: {
pubkey: string;
isSigner: boolean;
isWritable: boolean;
}[];
programId: string;
data: number[];
}
/**
* Transaction Instruction class
*/
export class TransactionInstruction {
/**
* Public keys to include in this transaction
* Boolean represents whether this pubkey needs to sign the transaction
*/
keys: Array<AccountMeta>;
/**
* Program Id to execute
*/
programId: PublicKey;
/**
* Program input
*/
data: Buffer = Buffer.alloc(0);
constructor(opts: TransactionInstructionCtorFields) {
this.programId = opts.programId;
this.keys = opts.keys;
if (opts.data) {
this.data = opts.data;
}
}
/**
* @internal
*/
toJSON(): TransactionInstructionJSON {
return {
keys: this.keys.map(({pubkey, isSigner, isWritable}) => ({
pubkey: pubkey.toJSON(),
isSigner,
isWritable,
})),
programId: this.programId.toJSON(),
data: [...this.data],
};
}
}
/**
* Pair of signature and corresponding public key
*/
export type SignaturePubkeyPair = {
signature: Buffer | null;
publicKey: PublicKey;
};
/**
* List of Transaction object fields that may be initialized at construction
*/
export type TransactionCtorFields_DEPRECATED = {
/** Optional nonce information used for offline nonce'd transactions */
nonceInfo?: NonceInformation | null;
/** The transaction fee payer */
feePayer?: PublicKey | null;
/** One or more signatures */
signatures?: Array<SignaturePubkeyPair>;
/** A recent blockhash */
recentBlockhash?: Blockhash;
};
// For backward compatibility; an unfortunate consequence of being
// forced to over-export types by the documentation generator.
// See https://github.com/solana-labs/solana/pull/25820
export type TransactionCtorFields = TransactionCtorFields_DEPRECATED;
/**
* Blockhash-based transactions have a lifetime that are defined by
* the blockhash they include. Any transaction whose blockhash is
* too old will be rejected.
*/
export type TransactionBlockhashCtor = {
/** The transaction fee payer */
feePayer?: PublicKey | null;
/** One or more signatures */
signatures?: Array<SignaturePubkeyPair>;
/** A recent blockhash */
blockhash: Blockhash;
/** the last block chain can advance to before tx is declared expired */
lastValidBlockHeight: number;
};
/**
* Use these options to construct a durable nonce transaction.
*/
export type TransactionNonceCtor = {
/** The transaction fee payer */
feePayer?: PublicKey | null;
minContextSlot: number;
nonceInfo: NonceInformation;
/** One or more signatures */
signatures?: Array<SignaturePubkeyPair>;
};
/**
* Nonce information to be used to build an offline Transaction.
*/
export type NonceInformation = {
/** The current blockhash stored in the nonce */
nonce: Blockhash;
/** AdvanceNonceAccount Instruction */
nonceInstruction: TransactionInstruction;
};
/**
* @internal
*/
export interface TransactionJSON {
recentBlockhash: string | null;
feePayer: string | null;
nonceInfo: {
nonce: string;
nonceInstruction: TransactionInstructionJSON;
} | null;
instructions: TransactionInstructionJSON[];
signers: string[];
}
/**
* Transaction class
*/
export class Transaction {
/**
* Signatures for the transaction. Typically created by invoking the
* `sign()` method
*/
signatures: Array<SignaturePubkeyPair> = [];
/**
* The first (payer) Transaction signature
*/
get signature(): Buffer | null {
if (this.signatures.length > 0) {
return this.signatures[0].signature;
}
return null;
}
/**
* The transaction fee payer
*/
feePayer?: PublicKey;
/**
* The instructions to atomically execute
*/
instructions: Array<TransactionInstruction> = [];
/**
* A recent transaction id. Must be populated by the caller
*/
recentBlockhash?: Blockhash;
/**
* the last block chain can advance to before tx is declared expired
* */
lastValidBlockHeight?: number;
/**
* Optional Nonce information. If populated, transaction will use a durable
* Nonce hash instead of a recentBlockhash. Must be populated by the caller
*/
nonceInfo?: NonceInformation;
/**
* If this is a nonce transaction this represents the minimum slot from which
* to evaluate if the nonce has advanced when attempting to confirm the
* transaction. This protects against a case where the transaction confirmation
* logic loads the nonce account from an old slot and assumes the mismatch in
* nonce value implies that the nonce has been advanced.
*/
minNonceContextSlot?: number;
/**
* @internal
*/
_message?: Message;
/**
* @internal
*/
_json?: TransactionJSON;
// Construct a transaction with a blockhash and lastValidBlockHeight
constructor(opts?: TransactionBlockhashCtor);
// Construct a transaction using a durable nonce
constructor(opts?: TransactionNonceCtor);
/**
* @deprecated `TransactionCtorFields` has been deprecated and will be removed in a future version.
* Please supply a `TransactionBlockhashCtor` instead.
*/
constructor(opts?: TransactionCtorFields_DEPRECATED);
/**
* Construct an empty Transaction
*/
constructor(
opts?:
| TransactionBlockhashCtor
| TransactionNonceCtor
| TransactionCtorFields_DEPRECATED,
) {
if (!opts) {
return;
}
if (opts.feePayer) {
this.feePayer = opts.feePayer;
}
if (opts.signatures) {
this.signatures = opts.signatures;
}
if (Object.prototype.hasOwnProperty.call(opts, 'nonceInfo')) {
const {minContextSlot, nonceInfo} = opts as TransactionNonceCtor;
this.minNonceContextSlot = minContextSlot;
this.nonceInfo = nonceInfo;
} else if (
Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')
) {
const {blockhash, lastValidBlockHeight} =
opts as TransactionBlockhashCtor;
this.recentBlockhash = blockhash;
this.lastValidBlockHeight = lastValidBlockHeight;
} else {
const {recentBlockhash, nonceInfo} =
opts as TransactionCtorFields_DEPRECATED;
if (nonceInfo) {
this.nonceInfo = nonceInfo;
}
this.recentBlockhash = recentBlockhash;
}
}
/**
* @internal
*/
toJSON(): TransactionJSON {
return {
recentBlockhash: this.recentBlockhash || null,
feePayer: this.feePayer ? this.feePayer.toJSON() : null,
nonceInfo: this.nonceInfo
? {
nonce: this.nonceInfo.nonce,
nonceInstruction: this.nonceInfo.nonceInstruction.toJSON(),
}
: null,
instructions: this.instructions.map(instruction => instruction.toJSON()),
signers: this.signatures.map(({publicKey}) => {
return publicKey.toJSON();
}),
};
}
/**
* Add one or more instructions to this Transaction
*/
add(
...items: Array<
Transaction | TransactionInstruction | TransactionInstructionCtorFields
>
): Transaction {
if (items.length === 0) {
throw new Error('No instructions');
}
items.forEach((item: any) => {
if ('instructions' in item) {
this.instructions = this.instructions.concat(item.instructions);
} else if ('data' in item && 'programId' in item && 'keys' in item) {
this.instructions.push(item);
} else {
this.instructions.push(new TransactionInstruction(item));
}
});
return this;
}
/**
* Compile transaction data
*/
compileMessage(): Message {
if (
this._message &&
JSON.stringify(this.toJSON()) === JSON.stringify(this._json)
) {
return this._message;
}
let recentBlockhash;
let instructions: TransactionInstruction[];
if (this.nonceInfo) {
recentBlockhash = this.nonceInfo.nonce;
if (this.instructions[0] != this.nonceInfo.nonceInstruction) {
instructions = [this.nonceInfo.nonceInstruction, ...this.instructions];
} else {
instructions = this.instructions;
}
} else {
recentBlockhash = this.recentBlockhash;
instructions = this.instructions;
}
if (!recentBlockhash) {
throw new Error('Transaction recentBlockhash required');
}
if (instructions.length < 1) {
console.warn('No instructions provided');
}
let feePayer: PublicKey;
if (this.feePayer) {
feePayer = this.feePayer;
} else if (this.signatures.length > 0 && this.signatures[0].publicKey) {
// Use implicit fee payer
feePayer = this.signatures[0].publicKey;
} else {
throw new Error('Transaction fee payer required');
}
for (let i = 0; i < instructions.length; i++) {
if (instructions[i].programId === undefined) {
throw new Error(
`Transaction instruction index ${i} has undefined program id`,
);
}
}
const programIds: string[] = [];
const accountMetas: AccountMeta[] = [];
instructions.forEach(instruction => {
instruction.keys.forEach(accountMeta => {
accountMetas.push({...accountMeta});
});
const programId = instruction.programId.toString();
if (!programIds.includes(programId)) {
programIds.push(programId);
}
});
// Append programID account metas
programIds.forEach(programId => {
accountMetas.push({
pubkey: new PublicKey(programId),
isSigner: false,
isWritable: false,
});
});
// Cull duplicate account metas
const uniqueMetas: AccountMeta[] = [];
accountMetas.forEach(accountMeta => {
const pubkeyString = accountMeta.pubkey.toString();
const uniqueIndex = uniqueMetas.findIndex(x => {
return x.pubkey.toString() === pubkeyString;
});
if (uniqueIndex > -1) {
uniqueMetas[uniqueIndex].isWritable =
uniqueMetas[uniqueIndex].isWritable || accountMeta.isWritable;
uniqueMetas[uniqueIndex].isSigner =
uniqueMetas[uniqueIndex].isSigner || accountMeta.isSigner;
} else {
uniqueMetas.push(accountMeta);
}
});
// Sort. Prioritizing first by signer, then by writable
uniqueMetas.sort(function (x, y) {
if (x.isSigner !== y.isSigner) {
// Signers always come before non-signers
return x.isSigner ? -1 : 1;
}
if (x.isWritable !== y.isWritable) {
// Writable accounts always come before read-only accounts
return x.isWritable ? -1 : 1;
}
// Otherwise, sort by pubkey, stringwise.
return x.pubkey.toBase58().localeCompare(y.pubkey.toBase58());
});
// Move fee payer to the front
const feePayerIndex = uniqueMetas.findIndex(x => {
return x.pubkey.equals(feePayer);
});
if (feePayerIndex > -1) {
const [payerMeta] = uniqueMetas.splice(feePayerIndex, 1);
payerMeta.isSigner = true;
payerMeta.isWritable = true;
uniqueMetas.unshift(payerMeta);
} else {
uniqueMetas.unshift({
pubkey: feePayer,
isSigner: true,
isWritable: true,
});
}
// Disallow unknown signers
for (const signature of this.signatures) {
const uniqueIndex = uniqueMetas.findIndex(x => {
return x.pubkey.equals(signature.publicKey);
});
if (uniqueIndex > -1) {
if (!uniqueMetas[uniqueIndex].isSigner) {
uniqueMetas[uniqueIndex].isSigner = true;
console.warn(
'Transaction references a signature that is unnecessary, ' +
'only the fee payer and instruction signer accounts should sign a transaction. ' +
'This behavior is deprecated and will throw an error in the next major version release.',
);
}
} else {
throw new Error(`unknown signer: ${signature.publicKey.toString()}`);
}
}
let numRequiredSignatures = 0;
let numReadonlySignedAccounts = 0;
let numReadonlyUnsignedAccounts = 0;
// Split out signing from non-signing keys and count header values
const signedKeys: string[] = [];
const unsignedKeys: string[] = [];
uniqueMetas.forEach(({pubkey, isSigner, isWritable}) => {
if (isSigner) {
signedKeys.push(pubkey.toString());
numRequiredSignatures += 1;
if (!isWritable) {
numReadonlySignedAccounts += 1;
}
} else {
unsignedKeys.push(pubkey.toString());
if (!isWritable) {
numReadonlyUnsignedAccounts += 1;
}
}
});
const accountKeys = signedKeys.concat(unsignedKeys);
const compiledInstructions: CompiledInstruction[] = instructions.map(
instruction => {
const {data, programId} = instruction;
return {
programIdIndex: accountKeys.indexOf(programId.toString()),
accounts: instruction.keys.map(meta =>
accountKeys.indexOf(meta.pubkey.toString()),
),
data: bs58.encode(data),
};
},
);
compiledInstructions.forEach(instruction => {
invariant(instruction.programIdIndex >= 0);
instruction.accounts.forEach(keyIndex => invariant(keyIndex >= 0));
});
return new Message({
header: {
numRequiredSignatures,
numReadonlySignedAccounts,
numReadonlyUnsignedAccounts,
},
accountKeys,
recentBlockhash,
instructions: compiledInstructions,
});
}
/**
* @internal
*/
_compile(): Message {
const message = this.compileMessage();
const signedKeys = message.accountKeys.slice(
0,
message.header.numRequiredSignatures,
);
if (this.signatures.length === signedKeys.length) {
const valid = this.signatures.every((pair, index) => {
return signedKeys[index].equals(pair.publicKey);
});
if (valid) return message;
}
this.signatures = signedKeys.map(publicKey => ({
signature: null,
publicKey,
}));
return message;
}
/**
* Get a buffer of the Transaction data that need to be covered by signatures
*/
serializeMessage(): Buffer {
return this._compile().serialize();
}
/**
* Get the estimated fee associated with a transaction
*/
async getEstimatedFee(connection: Connection): Promise<number | null> {
return (await connection.getFeeForMessage(this.compileMessage())).value;
}
/**
* Specify the public keys which will be used to sign the Transaction.
* The first signer will be used as the transaction fee payer account.
*
* Signatures can be added with either `partialSign` or `addSignature`
*
* @deprecated Deprecated since v0.84.0. Only the fee payer needs to be
* specified and it can be set in the Transaction constructor or with the
* `feePayer` property.
*/
setSigners(...signers: Array<PublicKey>) {
if (signers.length === 0) {
throw new Error('No signers');
}
const seen = new Set();
this.signatures = signers
.filter(publicKey => {
const key = publicKey.toString();
if (seen.has(key)) {
return false;
} else {
seen.add(key);
return true;
}
})
.map(publicKey => ({signature: null, publicKey}));
}
/**
* Sign the Transaction with the specified signers. Multiple signatures may
* be applied to a Transaction. The first signature is considered "primary"
* and is used identify and confirm transactions.
*
* If the Transaction `feePayer` is not set, the first signer will be used
* as the transaction fee payer account.
*
* Transaction fields should not be modified after the first call to `sign`,
* as doing so may invalidate the signature and cause the Transaction to be
* rejected.
*
* The Transaction must be assigned a valid `recentBlockhash` before invoking this method
*/
sign(...signers: Array<Signer>) {
if (signers.length === 0) {
throw new Error('No signers');
}
// Dedupe signers
const seen = new Set();
const uniqueSigners = [];
for (const signer of signers) {
const key = signer.publicKey.toString();
if (seen.has(key)) {
continue;
} else {
seen.add(key);
uniqueSigners.push(signer);
}
}
this.signatures = uniqueSigners.map(signer => ({
signature: null,
publicKey: signer.publicKey,
}));
const message = this._compile();
this._partialSign(message, ...uniqueSigners);
}
/**
* Partially sign a transaction with the specified accounts. All accounts must
* correspond to either the fee payer or a signer account in the transaction
* instructions.
*
* All the caveats from the `sign` method apply to `partialSign`
*/
partialSign(...signers: Array<Signer>) {
if (signers.length === 0) {
throw new Error('No signers');
}
// Dedupe signers
const seen = new Set();
const uniqueSigners = [];
for (const signer of signers) {
const key = signer.publicKey.toString();
if (seen.has(key)) {
continue;
} else {
seen.add(key);
uniqueSigners.push(signer);
}
}
const message = this._compile();
this._partialSign(message, ...uniqueSigners);
}
/**
* @internal
*/
_partialSign(message: Message, ...signers: Array<Signer>) {
const signData = message.serialize();
signers.forEach(signer => {
const signature = sign(signData, signer.secretKey);
this._addSignature(signer.publicKey, toBuffer(signature));
});
}
/**
* Add an externally created signature to a transaction. The public key
* must correspond to either the fee payer or a signer account in the transaction
* instructions.
*/
addSignature(pubkey: PublicKey, signature: Buffer) {
this._compile(); // Ensure signatures array is populated
this._addSignature(pubkey, signature);
}
/**
* @internal
*/
_addSignature(pubkey: PublicKey, signature: Buffer) {
invariant(signature.length === 64);
const index = this.signatures.findIndex(sigpair =>
pubkey.equals(sigpair.publicKey),
);
if (index < 0) {
throw new Error(`unknown signer: ${pubkey.toString()}`);
}
this.signatures[index].signature = Buffer.from(signature);
}
/**
* Verify signatures of a Transaction
* Optional parameter specifies if we're expecting a fully signed Transaction or a partially signed one.
* If no boolean is provided, we expect a fully signed Transaction by default.
*/
verifySignatures(requireAllSignatures?: boolean): boolean {
return this._verifySignatures(
this.serializeMessage(),
requireAllSignatures === undefined ? true : requireAllSignatures,
);
}
/**
* @internal
*/
_verifySignatures(
signData: Uint8Array,
requireAllSignatures: boolean,
): boolean {
for (const {signature, publicKey} of this.signatures) {
if (signature === null) {
if (requireAllSignatures) {
return false;
}
} else {
if (!verify(signature, signData, publicKey.toBytes())) {
return false;
}
}
}
return true;
}
/**
* Serialize the Transaction in the wire format.
*/
serialize(config?: SerializeConfig): Buffer {
const {requireAllSignatures, verifySignatures} = Object.assign(
{requireAllSignatures: true, verifySignatures: true},
config,
);
const signData = this.serializeMessage();
if (
verifySignatures &&
!this._verifySignatures(signData, requireAllSignatures)
) {
throw new Error('Signature verification failed');
}
return this._serialize(signData);
}
/**
* @internal
*/
_serialize(signData: Buffer): Buffer {
const {signatures} = this;
const signatureCount: number[] = [];
shortvec.encodeLength(signatureCount, signatures.length);
const transactionLength =
signatureCount.length + signatures.length * 64 + signData.length;
const wireTransaction = Buffer.alloc(transactionLength);
invariant(signatures.length < 256);
Buffer.from(signatureCount).copy(wireTransaction, 0);
signatures.forEach(({signature}, index) => {
if (signature !== null) {
invariant(signature.length === 64, `signature has invalid length`);
Buffer.from(signature).copy(
wireTransaction,
signatureCount.length + index * 64,
);
}
});
signData.copy(
wireTransaction,
signatureCount.length + signatures.length * 64,
);
invariant(
wireTransaction.length <= PACKET_DATA_SIZE,
`Transaction too large: ${wireTransaction.length} > ${PACKET_DATA_SIZE}`,
);
return wireTransaction;
}
/**
* Deprecated method
* @internal
*/
get keys(): Array<PublicKey> {
invariant(this.instructions.length === 1);
return this.instructions[0].keys.map(keyObj => keyObj.pubkey);
}
/**
* Deprecated method
* @internal
*/
get programId(): PublicKey {
invariant(this.instructions.length === 1);
return this.instructions[0].programId;
}
/**
* Deprecated method
* @internal
*/
get data(): Buffer {
invariant(this.instructions.length === 1);
return this.instructions[0].data;
}
/**
* Parse a wire transaction into a Transaction object.
*/
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction {
// Slice up wire data
let byteArray = [...buffer];
const signatureCount = shortvec.decodeLength(byteArray);
let signatures = [];
for (let i = 0; i < signatureCount; i++) {
const signature = byteArray.slice(0, SIGNATURE_LENGTH_IN_BYTES);
byteArray = byteArray.slice(SIGNATURE_LENGTH_IN_BYTES);
signatures.push(bs58.encode(Buffer.from(signature)));
}
return Transaction.populate(Message.from(byteArray), signatures);
}
/**
* Populate Transaction object from message and signatures
*/
static populate(
message: Message,
signatures: Array<string> = [],
): Transaction {
const transaction = new Transaction();
transaction.recentBlockhash = message.recentBlockhash;
if (message.header.numRequiredSignatures > 0) {
transaction.feePayer = message.accountKeys[0];
}
signatures.forEach((signature, index) => {
const sigPubkeyPair = {
signature:
signature == bs58.encode(DEFAULT_SIGNATURE)
? null
: bs58.decode(signature),
publicKey: message.accountKeys[index],
};
transaction.signatures.push(sigPubkeyPair);
});
message.instructions.forEach(instruction => {
const keys = instruction.accounts.map(account => {
const pubkey = message.accountKeys[account];
return {
pubkey,
isSigner:
transaction.signatures.some(
keyObj => keyObj.publicKey.toString() === pubkey.toString(),
) || message.isAccountSigner(account),
isWritable: message.isAccountWritable(account),
};
});
transaction.instructions.push(
new TransactionInstruction({
keys,
programId: message.accountKeys[instruction.programIdIndex],
data: bs58.decode(instruction.data),
}),
);
});
transaction._message = message;
transaction._json = transaction.toJSON();
return transaction;
}
}

View File

@ -1,140 +0,0 @@
import {AccountKeysFromLookups} from '../message/account-keys';
import assert from '../utils/assert';
import {toBuffer} from '../utils/to-buffer';
import {Blockhash} from '../blockhash';
import {Message, MessageV0, VersionedMessage} from '../message';
import {PublicKey} from '../publickey';
import {AddressLookupTableAccount} from '../programs';
import {AccountMeta, TransactionInstruction} from './legacy';
export type TransactionMessageArgs = {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;
};
export type DecompileArgs =
| {
accountKeysFromLookups: AccountKeysFromLookups;
}
| {
addressLookupTableAccounts: AddressLookupTableAccount[];
};
export class TransactionMessage {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;
constructor(args: TransactionMessageArgs) {
this.payerKey = args.payerKey;
this.instructions = args.instructions;
this.recentBlockhash = args.recentBlockhash;
}
static decompile(
message: VersionedMessage,
args?: DecompileArgs,
): TransactionMessage {
const {header, compiledInstructions, recentBlockhash} = message;
const {
numRequiredSignatures,
numReadonlySignedAccounts,
numReadonlyUnsignedAccounts,
} = header;
const numWritableSignedAccounts =
numRequiredSignatures - numReadonlySignedAccounts;
assert(numWritableSignedAccounts > 0, 'Message header is invalid');
const numWritableUnsignedAccounts =
message.staticAccountKeys.length -
numRequiredSignatures -
numReadonlyUnsignedAccounts;
assert(numWritableUnsignedAccounts >= 0, 'Message header is invalid');
const accountKeys = message.getAccountKeys(args);
const payerKey = accountKeys.get(0);
if (payerKey === undefined) {
throw new Error(
'Failed to decompile message because no account keys were found',
);
}
const instructions: TransactionInstruction[] = [];
for (const compiledIx of compiledInstructions) {
const keys: AccountMeta[] = [];
for (const keyIndex of compiledIx.accountKeyIndexes) {
const pubkey = accountKeys.get(keyIndex);
if (pubkey === undefined) {
throw new Error(
`Failed to find key for account key index ${keyIndex}`,
);
}
const isSigner = keyIndex < numRequiredSignatures;
let isWritable;
if (isSigner) {
isWritable = keyIndex < numWritableSignedAccounts;
} else if (keyIndex < accountKeys.staticAccountKeys.length) {
isWritable =
keyIndex - numRequiredSignatures < numWritableUnsignedAccounts;
} else {
isWritable =
keyIndex - accountKeys.staticAccountKeys.length <
// accountKeysFromLookups cannot be undefined because we already found a pubkey for this index above
accountKeys.accountKeysFromLookups!.writable.length;
}
keys.push({
pubkey,
isSigner: keyIndex < header.numRequiredSignatures,
isWritable,
});
}
const programId = accountKeys.get(compiledIx.programIdIndex);
if (programId === undefined) {
throw new Error(
`Failed to find program id for program id index ${compiledIx.programIdIndex}`,
);
}
instructions.push(
new TransactionInstruction({
programId,
data: toBuffer(compiledIx.data),
keys,
}),
);
}
return new TransactionMessage({
payerKey,
instructions,
recentBlockhash,
});
}
compileToLegacyMessage(): Message {
return Message.compile({
payerKey: this.payerKey,
recentBlockhash: this.recentBlockhash,
instructions: this.instructions,
});
}
compileToV0Message(
addressLookupTableAccounts?: AddressLookupTableAccount[],
): MessageV0 {
return MessageV0.compile({
payerKey: this.payerKey,
recentBlockhash: this.recentBlockhash,
instructions: this.instructions,
addressLookupTableAccounts,
});
}
}

View File

@ -1,126 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import {Signer} from '../keypair';
import assert from '../utils/assert';
import {VersionedMessage} from '../message/versioned';
import {SIGNATURE_LENGTH_IN_BYTES} from './constants';
import * as shortvec from '../utils/shortvec-encoding';
import * as Layout from '../layout';
import {sign} from '../utils/ed25519';
import {PublicKey} from '../publickey';
export type TransactionVersion = 'legacy' | 0;
/**
* Versioned transaction class
*/
export class VersionedTransaction {
signatures: Array<Uint8Array>;
message: VersionedMessage;
get version(): TransactionVersion {
return this.message.version;
}
constructor(message: VersionedMessage, signatures?: Array<Uint8Array>) {
if (signatures !== undefined) {
assert(
signatures.length === message.header.numRequiredSignatures,
'Expected signatures length to be equal to the number of required signatures',
);
this.signatures = signatures;
} else {
const defaultSignatures = [];
for (let i = 0; i < message.header.numRequiredSignatures; i++) {
defaultSignatures.push(new Uint8Array(SIGNATURE_LENGTH_IN_BYTES));
}
this.signatures = defaultSignatures;
}
this.message = message;
}
serialize(): Uint8Array {
const serializedMessage = this.message.serialize();
const encodedSignaturesLength = Array<number>();
shortvec.encodeLength(encodedSignaturesLength, this.signatures.length);
const transactionLayout = BufferLayout.struct<{
encodedSignaturesLength: Uint8Array;
signatures: Array<Uint8Array>;
serializedMessage: Uint8Array;
}>([
BufferLayout.blob(
encodedSignaturesLength.length,
'encodedSignaturesLength',
),
BufferLayout.seq(
Layout.signature(),
this.signatures.length,
'signatures',
),
BufferLayout.blob(serializedMessage.length, 'serializedMessage'),
]);
const serializedTransaction = new Uint8Array(2048);
const serializedTransactionLength = transactionLayout.encode(
{
encodedSignaturesLength: new Uint8Array(encodedSignaturesLength),
signatures: this.signatures,
serializedMessage,
},
serializedTransaction,
);
return serializedTransaction.slice(0, serializedTransactionLength);
}
static deserialize(serializedTransaction: Uint8Array): VersionedTransaction {
let byteArray = [...serializedTransaction];
const signatures = [];
const signaturesLength = shortvec.decodeLength(byteArray);
for (let i = 0; i < signaturesLength; i++) {
signatures.push(
new Uint8Array(byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES)),
);
}
const message = VersionedMessage.deserialize(new Uint8Array(byteArray));
return new VersionedTransaction(message, signatures);
}
sign(signers: Array<Signer>) {
const messageData = this.message.serialize();
const signerPubkeys = this.message.staticAccountKeys.slice(
0,
this.message.header.numRequiredSignatures,
);
for (const signer of signers) {
const signerIndex = signerPubkeys.findIndex(pubkey =>
pubkey.equals(signer.publicKey),
);
assert(
signerIndex >= 0,
`Cannot sign with non signer key ${signer.publicKey.toBase58()}`,
);
this.signatures[signerIndex] = sign(messageData, signer.secretKey);
}
}
addSignature(publicKey: PublicKey, signature: Uint8Array) {
assert(signature.byteLength === 64, 'Signature must be 64 bytes long');
const signerPubkeys = this.message.staticAccountKeys.slice(
0,
this.message.header.numRequiredSignatures,
);
const signerIndex = signerPubkeys.findIndex(pubkey =>
pubkey.equals(publicKey),
);
assert(
signerIndex >= 0,
`Can not add signature; \`${publicKey.toBase58()}\` is not required to sign this transaction`,
);
this.signatures[signerIndex] = signature;
}
}

View File

@ -1,8 +0,0 @@
export default function (
condition: unknown,
message?: string,
): asserts condition {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}

View File

@ -1,43 +0,0 @@
import {Buffer} from 'buffer';
import {blob, Layout} from '@solana/buffer-layout';
import {toBigIntLE, toBufferLE} from 'bigint-buffer';
interface EncodeDecode<T> {
decode(buffer: Buffer, offset?: number): T;
encode(src: T, buffer: Buffer, offset?: number): number;
}
const encodeDecode = <T>(layout: Layout<T>): EncodeDecode<T> => {
const decode = layout.decode.bind(layout);
const encode = layout.encode.bind(layout);
return {decode, encode};
};
const bigInt =
(length: number) =>
(property?: string): Layout<bigint> => {
const layout = blob(length, property);
const {encode, decode} = encodeDecode(layout);
const bigIntLayout = layout as Layout<unknown> as Layout<bigint>;
bigIntLayout.decode = (buffer: Buffer, offset: number) => {
const src = decode(buffer, offset);
return toBigIntLE(Buffer.from(src));
};
bigIntLayout.encode = (bigInt: bigint, buffer: Buffer, offset: number) => {
const src = toBufferLE(bigInt, length);
return encode(src, buffer, offset);
};
return bigIntLayout;
};
export const u64 = bigInt(8);
export const u128 = bigInt(16);
export const u192 = bigInt(24);
export const u256 = bigInt(32);

View File

@ -1,38 +0,0 @@
import {Buffer} from 'buffer';
import {serialize, deserialize, deserializeUnchecked} from 'borsh';
// Class wrapping a plain object
export class Struct {
constructor(properties: any) {
Object.assign(this, properties);
}
encode(): Buffer {
return Buffer.from(serialize(SOLANA_SCHEMA, this));
}
static decode(data: Buffer): any {
return deserialize(SOLANA_SCHEMA, this, data);
}
static decodeUnchecked(data: Buffer): any {
return deserializeUnchecked(SOLANA_SCHEMA, this, data);
}
}
// Class representing a Rust-compatible enum, since enums are only strings or
// numbers in pure JS
export class Enum extends Struct {
enum: string = '';
constructor(properties: any) {
super(properties);
if (Object.keys(properties).length !== 1) {
throw new Error('Enum can only take single value');
}
Object.keys(properties).map(key => {
this.enum = key;
});
}
}
export const SOLANA_SCHEMA: Map<Function, any> = new Map();

View File

@ -1,31 +0,0 @@
const endpoint = {
http: {
devnet: 'http://api.devnet.solana.com',
testnet: 'http://api.testnet.solana.com',
'mainnet-beta': 'http://api.mainnet-beta.solana.com/',
},
https: {
devnet: 'https://api.devnet.solana.com',
testnet: 'https://api.testnet.solana.com',
'mainnet-beta': 'https://api.mainnet-beta.solana.com/',
},
};
export type Cluster = 'devnet' | 'testnet' | 'mainnet-beta';
/**
* Retrieves the RPC API URL for the specified cluster
*/
export function clusterApiUrl(cluster?: Cluster, tls?: boolean): string {
const key = tls === false ? 'http' : 'https';
if (!cluster) {
return endpoint[key]['devnet'];
}
const url = endpoint[key][cluster];
if (!url) {
throw new Error(`Unknown ${key} cluster: ${cluster}`);
}
return url;
}

View File

@ -1,46 +0,0 @@
import {sha512} from '@noble/hashes/sha512';
import * as ed25519 from '@noble/ed25519';
/**
* A 64 byte secret key, the first 32 bytes of which is the
* private scalar and the last 32 bytes is the public key.
* Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/
*/
type Ed25519SecretKey = Uint8Array;
/**
* Ed25519 Keypair
*/
export interface Ed25519Keypair {
publicKey: Uint8Array;
secretKey: Ed25519SecretKey;
}
ed25519.utils.sha512Sync = (...m) => sha512(ed25519.utils.concatBytes(...m));
export const generatePrivateKey = ed25519.utils.randomPrivateKey;
export const generateKeypair = (): Ed25519Keypair => {
const privateScalar = ed25519.utils.randomPrivateKey();
const publicKey = getPublicKey(privateScalar);
const secretKey = new Uint8Array(64);
secretKey.set(privateScalar);
secretKey.set(publicKey, 32);
return {
publicKey,
secretKey,
};
};
export const getPublicKey = ed25519.sync.getPublicKey;
export function isOnCurve(publicKey: Uint8Array): boolean {
try {
ed25519.Point.fromHex(publicKey, true /* strict */);
return true;
} catch {
return false;
}
}
export const sign = (
message: Parameters<typeof ed25519.sync.sign>[0],
secretKey: Ed25519SecretKey,
) => ed25519.sync.sign(message, secretKey.slice(0, 32));
export const verify = ed25519.sync.verify;

View File

@ -1,5 +0,0 @@
export * from './borsh-schema';
export * from './cluster';
export type {Ed25519Keypair} from './ed25519';
export * from './send-and-confirm-raw-transaction';
export * from './send-and-confirm-transaction';

View File

@ -1,26 +0,0 @@
const URL_RE = /^[^:]+:\/\/([^:[]+|\[[^\]]+\])(:\d+)?(.*)/i;
export function makeWebsocketUrl(endpoint: string) {
const matches = endpoint.match(URL_RE);
if (matches == null) {
throw TypeError(`Failed to validate endpoint URL \`${endpoint}\``);
}
const [
_, // eslint-disable-line @typescript-eslint/no-unused-vars
hostish,
portWithColon,
rest,
] = matches;
const protocol = endpoint.startsWith('https:') ? 'wss:' : 'ws:';
const startPort =
portWithColon == null ? null : parseInt(portWithColon.slice(1), 10);
const websocketPort =
// Only shift the port by +1 as a convention for ws(s) only if given endpoint
// is explictly specifying the endpoint port (HTTP-based RPC), assuming
// we're directly trying to connect to solana-validator's ws listening port.
// When the endpoint omits the port, we're connecting to the protocol
// default ports: http(80) or https(443) and it's assumed we're behind a reverse
// proxy which manages WebSocket upgrade and backend port redirection.
startPort == null ? '' : `:${startPort + 1}`;
return `${protocol}//${hostish}${websocketPort}${rest}`;
}

View File

@ -1,14 +0,0 @@
export function promiseTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
): Promise<T | null> {
let timeoutId: ReturnType<typeof setTimeout>;
const timeoutPromise: Promise<null> = new Promise(resolve => {
timeoutId = setTimeout(() => resolve(null), timeoutMs);
});
return Promise.race([promise, timeoutPromise]).then((result: T | null) => {
clearTimeout(timeoutId);
return result;
});
}

View File

@ -1,18 +0,0 @@
import {hmac} from '@noble/hashes/hmac';
import {sha256} from '@noble/hashes/sha256';
import * as secp256k1 from '@noble/secp256k1';
// Supply a synchronous hashing algorithm to make this
// library interoperable with the synchronous APIs in web3.js.
secp256k1.utils.hmacSha256Sync = (key: Uint8Array, ...msgs: Uint8Array[]) => {
const h = hmac.create(sha256, key);
msgs.forEach(msg => h.update(msg));
return h.digest();
};
export const ecdsaSign = (
msgHash: Parameters<typeof secp256k1.signSync>[0],
privKey: Parameters<typeof secp256k1.signSync>[1],
) => secp256k1.signSync(msgHash, privKey, {der: false, recovered: true});
export const isValidPrivateKey = secp256k1.utils.isValidPrivateKey;
export const publicKeyCreate = secp256k1.getPublicKey;

View File

@ -1,102 +0,0 @@
import type {Buffer} from 'buffer';
import {
BlockheightBasedTransactionConfirmationStrategy,
Connection,
DurableNonceTransactionConfirmationStrategy,
TransactionConfirmationStrategy,
} from '../connection';
import type {TransactionSignature} from '../transaction';
import type {ConfirmOptions} from '../connection';
/**
* Send and confirm a raw transaction
*
* If `commitment` option is not specified, defaults to 'max' commitment.
*
* @param {Connection} connection
* @param {Buffer} rawTransaction
* @param {TransactionConfirmationStrategy} confirmationStrategy
* @param {ConfirmOptions} [options]
* @returns {Promise<TransactionSignature>}
*/
export async function sendAndConfirmRawTransaction(
connection: Connection,
rawTransaction: Buffer,
confirmationStrategy: TransactionConfirmationStrategy,
options?: ConfirmOptions,
): Promise<TransactionSignature>;
/**
* @deprecated Calling `sendAndConfirmRawTransaction()` without a `confirmationStrategy`
* is no longer supported and will be removed in a future version.
*/
// eslint-disable-next-line no-redeclare
export async function sendAndConfirmRawTransaction(
connection: Connection,
rawTransaction: Buffer,
options?: ConfirmOptions,
): Promise<TransactionSignature>;
// eslint-disable-next-line no-redeclare
export async function sendAndConfirmRawTransaction(
connection: Connection,
rawTransaction: Buffer,
confirmationStrategyOrConfirmOptions:
| TransactionConfirmationStrategy
| ConfirmOptions
| undefined,
maybeConfirmOptions?: ConfirmOptions,
): Promise<TransactionSignature> {
let confirmationStrategy: TransactionConfirmationStrategy | undefined;
let options: ConfirmOptions | undefined;
if (
confirmationStrategyOrConfirmOptions &&
Object.prototype.hasOwnProperty.call(
confirmationStrategyOrConfirmOptions,
'lastValidBlockHeight',
)
) {
confirmationStrategy =
confirmationStrategyOrConfirmOptions as BlockheightBasedTransactionConfirmationStrategy;
options = maybeConfirmOptions;
} else if (
confirmationStrategyOrConfirmOptions &&
Object.prototype.hasOwnProperty.call(
confirmationStrategyOrConfirmOptions,
'nonceValue',
)
) {
confirmationStrategy =
confirmationStrategyOrConfirmOptions as DurableNonceTransactionConfirmationStrategy;
options = maybeConfirmOptions;
} else {
options = confirmationStrategyOrConfirmOptions as
| ConfirmOptions
| undefined;
}
const sendOptions = options && {
skipPreflight: options.skipPreflight,
preflightCommitment: options.preflightCommitment || options.commitment,
minContextSlot: options.minContextSlot,
};
const signature = await connection.sendRawTransaction(
rawTransaction,
sendOptions,
);
const commitment = options && options.commitment;
const confirmationPromise = confirmationStrategy
? connection.confirmTransaction(confirmationStrategy, commitment)
: connection.confirmTransaction(signature, commitment);
const status = (await confirmationPromise).value;
if (status.err) {
throw new Error(
`Raw transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
return signature;
}

View File

@ -1,98 +0,0 @@
import {Connection, SignatureResult} from '../connection';
import {Transaction} from '../transaction';
import type {ConfirmOptions} from '../connection';
import type {Signer} from '../keypair';
import type {TransactionSignature} from '../transaction';
/**
* Sign, send and confirm a transaction.
*
* If `commitment` option is not specified, defaults to 'max' commitment.
*
* @param {Connection} connection
* @param {Transaction} transaction
* @param {Array<Signer>} signers
* @param {ConfirmOptions} [options]
* @returns {Promise<TransactionSignature>}
*/
export async function sendAndConfirmTransaction(
connection: Connection,
transaction: Transaction,
signers: Array<Signer>,
options?: ConfirmOptions &
Readonly<{
// A signal that, when aborted, cancels any outstanding transaction confirmation operations
abortSignal?: AbortSignal;
}>,
): Promise<TransactionSignature> {
const sendOptions = options && {
skipPreflight: options.skipPreflight,
preflightCommitment: options.preflightCommitment || options.commitment,
maxRetries: options.maxRetries,
minContextSlot: options.minContextSlot,
};
const signature = await connection.sendTransaction(
transaction,
signers,
sendOptions,
);
let status: SignatureResult;
if (
transaction.recentBlockhash != null &&
transaction.lastValidBlockHeight != null
) {
status = (
await connection.confirmTransaction(
{
abortSignal: options?.abortSignal,
signature: signature,
blockhash: transaction.recentBlockhash,
lastValidBlockHeight: transaction.lastValidBlockHeight,
},
options && options.commitment,
)
).value;
} else if (
transaction.minNonceContextSlot != null &&
transaction.nonceInfo != null
) {
const {nonceInstruction} = transaction.nonceInfo;
const nonceAccountPubkey = nonceInstruction.keys[0].pubkey;
status = (
await connection.confirmTransaction(
{
abortSignal: options?.abortSignal,
minContextSlot: transaction.minNonceContextSlot,
nonceAccountPubkey,
nonceValue: transaction.nonceInfo.nonce,
signature,
},
options && options.commitment,
)
).value;
} else {
if (options?.abortSignal != null) {
console.warn(
'sendAndConfirmTransaction(): A transaction with a deprecated confirmation strategy was ' +
'supplied along with an `abortSignal`. Only transactions having `lastValidBlockHeight` ' +
'or a combination of `nonceInfo` and `minNonceContextSlot` are abortable.',
);
}
status = (
await connection.confirmTransaction(
signature,
options && options.commitment,
)
).value;
}
if (status.err) {
throw new Error(
`Transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
return signature;
}

View File

@ -1,28 +0,0 @@
export function decodeLength(bytes: Array<number>): number {
let len = 0;
let size = 0;
for (;;) {
let elem = bytes.shift() as number;
len |= (elem & 0x7f) << (size * 7);
size += 1;
if ((elem & 0x80) === 0) {
break;
}
}
return len;
}
export function encodeLength(bytes: Array<number>, len: number) {
let rem_len = len;
for (;;) {
let elem = rem_len & 0x7f;
rem_len >>= 7;
if (rem_len == 0) {
bytes.push(elem);
break;
} else {
elem |= 0x80;
bytes.push(elem);
}
}
}

View File

@ -1,4 +0,0 @@
// zzz
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

View File

@ -1,11 +0,0 @@
import {Buffer} from 'buffer';
export const toBuffer = (arr: Buffer | Uint8Array | Array<number>): Buffer => {
if (Buffer.isBuffer(arr)) {
return arr;
} else if (arr instanceof Uint8Array) {
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
} else {
return Buffer.from(arr);
}
};

View File

@ -1,104 +0,0 @@
import {Buffer} from 'buffer';
import {
assert as assertType,
optional,
string,
type as pick,
} from 'superstruct';
import * as Layout from './layout';
import * as shortvec from './utils/shortvec-encoding';
import {PublicKey, PUBLIC_KEY_LENGTH} from './publickey';
export const VALIDATOR_INFO_KEY = new PublicKey(
'Va1idator1nfo111111111111111111111111111111',
);
/**
* @internal
*/
type ConfigKey = {
publicKey: PublicKey;
isSigner: boolean;
};
/**
* Info used to identity validators.
*/
export type Info = {
/** validator name */
name: string;
/** optional, validator website */
website?: string;
/** optional, extra information the validator chose to share */
details?: string;
/** optional, used to identify validators on keybase.io */
keybaseUsername?: string;
};
const InfoString = pick({
name: string(),
website: optional(string()),
details: optional(string()),
keybaseUsername: optional(string()),
});
/**
* ValidatorInfo class
*/
export class ValidatorInfo {
/**
* validator public key
*/
key: PublicKey;
/**
* validator information
*/
info: Info;
/**
* Construct a valid ValidatorInfo
*
* @param key validator public key
* @param info validator information
*/
constructor(key: PublicKey, info: Info) {
this.key = key;
this.info = info;
}
/**
* Deserialize ValidatorInfo from the config account data. Exactly two config
* keys are required in the data.
*
* @param buffer config account data
* @return null if info was not found
*/
static fromConfigData(
buffer: Buffer | Uint8Array | Array<number>,
): ValidatorInfo | null {
let byteArray = [...buffer];
const configKeyCount = shortvec.decodeLength(byteArray);
if (configKeyCount !== 2) return null;
const configKeys: Array<ConfigKey> = [];
for (let i = 0; i < 2; i++) {
const publicKey = new PublicKey(byteArray.slice(0, PUBLIC_KEY_LENGTH));
byteArray = byteArray.slice(PUBLIC_KEY_LENGTH);
const isSigner = byteArray.slice(0, 1)[0] === 1;
byteArray = byteArray.slice(1);
configKeys.push({publicKey, isSigner});
}
if (configKeys[0].publicKey.equals(VALIDATOR_INFO_KEY)) {
if (configKeys[1].isSigner) {
const rawInfo: any = Layout.rustString().decode(Buffer.from(byteArray));
const info = JSON.parse(rawInfo as string);
assertType(info, InfoString);
return new ValidatorInfo(configKeys[1].publicKey, info);
}
}
return null;
}
}

View File

@ -1,236 +0,0 @@
import * as BufferLayout from '@solana/buffer-layout';
import type {Buffer} from 'buffer';
import * as Layout from './layout';
import {PublicKey} from './publickey';
import {toBuffer} from './utils/to-buffer';
export const VOTE_PROGRAM_ID = new PublicKey(
'Vote111111111111111111111111111111111111111',
);
export type Lockout = {
slot: number;
confirmationCount: number;
};
/**
* History of how many credits earned by the end of each epoch
*/
export type EpochCredits = Readonly<{
epoch: number;
credits: number;
prevCredits: number;
}>;
export type AuthorizedVoter = Readonly<{
epoch: number;
authorizedVoter: PublicKey;
}>;
type AuthorizedVoterRaw = Readonly<{
authorizedVoter: Uint8Array;
epoch: number;
}>;
type PriorVoters = Readonly<{
buf: PriorVoterRaw[];
idx: number;
isEmpty: number;
}>;
export type PriorVoter = Readonly<{
authorizedPubkey: PublicKey;
epochOfLastAuthorizedSwitch: number;
targetEpoch: number;
}>;
type PriorVoterRaw = Readonly<{
authorizedPubkey: Uint8Array;
epochOfLastAuthorizedSwitch: number;
targetEpoch: number;
}>;
export type BlockTimestamp = Readonly<{
slot: number;
timestamp: number;
}>;
type VoteAccountData = Readonly<{
authorizedVoters: AuthorizedVoterRaw[];
authorizedWithdrawer: Uint8Array;
commission: number;
epochCredits: EpochCredits[];
lastTimestamp: BlockTimestamp;
nodePubkey: Uint8Array;
priorVoters: PriorVoters;
rootSlot: number;
rootSlotValid: number;
votes: Lockout[];
}>;
/**
* See https://github.com/solana-labs/solana/blob/8a12ed029cfa38d4a45400916c2463fb82bbec8c/programs/vote_api/src/vote_state.rs#L68-L88
*
* @internal
*/
const VoteAccountLayout = BufferLayout.struct<VoteAccountData>([
Layout.publicKey('nodePubkey'),
Layout.publicKey('authorizedWithdrawer'),
BufferLayout.u8('commission'),
BufferLayout.nu64(), // votes.length
BufferLayout.seq<Lockout>(
BufferLayout.struct([
BufferLayout.nu64('slot'),
BufferLayout.u32('confirmationCount'),
]),
BufferLayout.offset(BufferLayout.u32(), -8),
'votes',
),
BufferLayout.u8('rootSlotValid'),
BufferLayout.nu64('rootSlot'),
BufferLayout.nu64(), // authorizedVoters.length
BufferLayout.seq<AuthorizedVoterRaw>(
BufferLayout.struct([
BufferLayout.nu64('epoch'),
Layout.publicKey('authorizedVoter'),
]),
BufferLayout.offset(BufferLayout.u32(), -8),
'authorizedVoters',
),
BufferLayout.struct<PriorVoters>(
[
BufferLayout.seq(
BufferLayout.struct([
Layout.publicKey('authorizedPubkey'),
BufferLayout.nu64('epochOfLastAuthorizedSwitch'),
BufferLayout.nu64('targetEpoch'),
]),
32,
'buf',
),
BufferLayout.nu64('idx'),
BufferLayout.u8('isEmpty'),
],
'priorVoters',
),
BufferLayout.nu64(), // epochCredits.length
BufferLayout.seq<EpochCredits>(
BufferLayout.struct([
BufferLayout.nu64('epoch'),
BufferLayout.nu64('credits'),
BufferLayout.nu64('prevCredits'),
]),
BufferLayout.offset(BufferLayout.u32(), -8),
'epochCredits',
),
BufferLayout.struct<BlockTimestamp>(
[BufferLayout.nu64('slot'), BufferLayout.nu64('timestamp')],
'lastTimestamp',
),
]);
type VoteAccountArgs = {
nodePubkey: PublicKey;
authorizedWithdrawer: PublicKey;
commission: number;
rootSlot: number | null;
votes: Lockout[];
authorizedVoters: AuthorizedVoter[];
priorVoters: PriorVoter[];
epochCredits: EpochCredits[];
lastTimestamp: BlockTimestamp;
};
/**
* VoteAccount class
*/
export class VoteAccount {
nodePubkey: PublicKey;
authorizedWithdrawer: PublicKey;
commission: number;
rootSlot: number | null;
votes: Lockout[];
authorizedVoters: AuthorizedVoter[];
priorVoters: PriorVoter[];
epochCredits: EpochCredits[];
lastTimestamp: BlockTimestamp;
/**
* @internal
*/
constructor(args: VoteAccountArgs) {
this.nodePubkey = args.nodePubkey;
this.authorizedWithdrawer = args.authorizedWithdrawer;
this.commission = args.commission;
this.rootSlot = args.rootSlot;
this.votes = args.votes;
this.authorizedVoters = args.authorizedVoters;
this.priorVoters = args.priorVoters;
this.epochCredits = args.epochCredits;
this.lastTimestamp = args.lastTimestamp;
}
/**
* Deserialize VoteAccount from the account data.
*
* @param buffer account data
* @return VoteAccount
*/
static fromAccountData(
buffer: Buffer | Uint8Array | Array<number>,
): VoteAccount {
const versionOffset = 4;
const va = VoteAccountLayout.decode(toBuffer(buffer), versionOffset);
let rootSlot: number | null = va.rootSlot;
if (!va.rootSlotValid) {
rootSlot = null;
}
return new VoteAccount({
nodePubkey: new PublicKey(va.nodePubkey),
authorizedWithdrawer: new PublicKey(va.authorizedWithdrawer),
commission: va.commission,
votes: va.votes,
rootSlot,
authorizedVoters: va.authorizedVoters.map(parseAuthorizedVoter),
priorVoters: getPriorVoters(va.priorVoters),
epochCredits: va.epochCredits,
lastTimestamp: va.lastTimestamp,
});
}
}
function parseAuthorizedVoter({
authorizedVoter,
epoch,
}: AuthorizedVoterRaw): AuthorizedVoter {
return {
epoch,
authorizedVoter: new PublicKey(authorizedVoter),
};
}
function parsePriorVoters({
authorizedPubkey,
epochOfLastAuthorizedSwitch,
targetEpoch,
}: PriorVoterRaw): PriorVoter {
return {
authorizedPubkey: new PublicKey(authorizedPubkey),
epochOfLastAuthorizedSwitch,
targetEpoch,
};
}
function getPriorVoters({buf, idx, isEmpty}: PriorVoters): PriorVoter[] {
if (isEmpty) {
return [];
}
return [
...buf.slice(idx + 1).map(parsePriorVoters),
...buf.slice(0, idx).map(parsePriorVoters),
];
}

Some files were not shown because too many files have changed in this diff Show More