Fuck this whole web3.js thing (#30062)
This commit is contained in:
parent
090b990e15
commit
a2cf25153f
|
@ -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:
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
explorer:
|
||||
- explorer/**/*
|
||||
|
||||
web3.js:
|
||||
- web3.js/**/*
|
||||
- web3.js-experimental/**/*
|
||||
|
|
|
@ -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 }}
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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...
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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...
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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...
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
15
codecov.yml
15
codecov.yml
|
@ -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/"
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -32,7 +32,6 @@ ignores=(
|
|||
.cache
|
||||
.cargo
|
||||
target
|
||||
web3.js/test
|
||||
node_modules
|
||||
)
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ ignores=(
|
|||
.cache
|
||||
.cargo
|
||||
target
|
||||
web3.js/test
|
||||
node_modules
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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'],
|
||||
},
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,2 +0,0 @@
|
|||
test/dist
|
||||
declarations
|
|
@ -1,6 +0,0 @@
|
|||
arrowParens: "avoid"
|
||||
bracketSpacing: false
|
||||
semi: true
|
||||
singleQuote: true
|
||||
tabWidth: 2
|
||||
trailingComma: "all"
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"repositoryUrl": "git@github.com:solana-labs/solana-web3.js.git",
|
||||
"preset": "conventionalcommits",
|
||||
"presetConfig": {
|
||||
"issueUrlFormat": "{{host}}/{{owner}}/solana/issues/{{id}}"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"scope": true,
|
||||
"body": true,
|
||||
"emoji": false,
|
||||
"lowercaseTypes": true,
|
||||
"rules": {
|
||||
"maxChar": 72,
|
||||
"minChar": 10,
|
||||
"endWithDot": false
|
||||
}
|
||||
}
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
};
|
|
@ -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 -- .)
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
];
|
|
@ -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'],
|
||||
};
|
|
@ -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
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
export const Headers = globalThis.Headers;
|
||||
export const Request = globalThis.Request;
|
||||
export const Response = globalThis.Response;
|
||||
export default globalThis.fetch;
|
|
@ -1 +0,0 @@
|
|||
export {default} from 'rpc-websockets/dist/lib/client/websocket.browser';
|
|
@ -1,4 +0,0 @@
|
|||
export const Headers = globalThis.Headers;
|
||||
export const Request = globalThis.Request;
|
||||
export const Response = globalThis.Response;
|
||||
export default globalThis.fetch;
|
|
@ -1 +0,0 @@
|
|||
export {default} from 'rpc-websockets/dist/lib/client/websocket.browser';
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* Blockhash as Base58 string.
|
||||
*/
|
||||
export type Blockhash = string;
|
|
@ -1,5 +0,0 @@
|
|||
import {PublicKey} from './publickey';
|
||||
|
||||
export const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey(
|
||||
'BPFLoader1111111111111111111111111111111111',
|
||||
);
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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`,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
),
|
||||
]),
|
||||
};
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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']],
|
||||
});
|
|
@ -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;
|
|
@ -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 +
|
||||
')',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
);
|
|
@ -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;
|
|
@ -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;
|
|
@ -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',
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
export * from './constants';
|
||||
export * from './expiry-custom-errors';
|
||||
export * from './legacy';
|
||||
export * from './message';
|
||||
export * from './versioned';
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
export default function (
|
||||
condition: unknown,
|
||||
message?: string,
|
||||
): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message || 'Assertion failed');
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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';
|
|
@ -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}`;
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// zzz
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue