Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: solana-labs/solana-web3.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 168d5e088edd48f9f0c1a877e888592ca4cfdf38
Choose a base ref
...
head repository: solana-labs/solana-web3.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 091faf5d0d0c23d475a88d5813d413fe4c28681f
Choose a head ref

Commits on Nov 25, 2021

  1. chore: bump typedoc from 0.22.8 to 0.22.10 in /web3.js (#21425)

    Bumps [typedoc](https://github.com/TypeStrong/TypeDoc) from 0.22.8 to 0.22.10.
    - [Release notes](https://github.com/TypeStrong/TypeDoc/releases)
    - [Changelog](https://github.com/TypeStrong/typedoc/blob/master/CHANGELOG.md)
    - [Commits](TypeStrong/typedoc@v0.22.8...v0.22.10)
    
    ---
    updated-dependencies:
    - dependency-name: typedoc
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    00a7252 View commit details
  2. chore: bump @babel/preset-env from 7.16.0 to 7.16.4 in /web3.js (#21428)

    Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.16.0 to 7.16.4.
    - [Release notes](https://github.com/babel/babel/releases)
    - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/babel/babel/commits/v7.16.4/packages/babel-preset-env)
    
    ---
    updated-dependencies:
    - dependency-name: "@babel/preset-env"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    8177dd7 View commit details
  3. chore: bump @types/node from 16.11.7 to 16.11.10 in /web3.js (#21430)

    Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.11.7 to 16.11.10.
    - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
    - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
    
    ---
    updated-dependencies:
    - dependency-name: "@types/node"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    b231d1a View commit details
  4. chore: bump @commitlint/travis-cli from 14.1.0 to 15.0.0 in /web3.js …

    …(#21431)
    
    Bumps [@commitlint/travis-cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/travis-cli) from 14.1.0 to 15.0.0.
    - [Release notes](https://github.com/conventional-changelog/commitlint/releases)
    - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/travis-cli/CHANGELOG.md)
    - [Commits](https://github.com/conventional-changelog/commitlint/commits/v15.0.0/@commitlint/travis-cli)
    
    ---
    updated-dependencies:
    - dependency-name: "@commitlint/travis-cli"
      dependency-type: direct:development
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    67d799a View commit details
  5. chore: bump @babel/plugin-transform-runtime in /web3.js (#21432)

    Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.16.0 to 7.16.4.
    - [Release notes](https://github.com/babel/babel/releases)
    - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
    - [Commits](https://github.com/babel/babel/commits/v7.16.4/packages/babel-plugin-transform-runtime)
    
    ---
    updated-dependencies:
    - dependency-name: "@babel/plugin-transform-runtime"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    760a3c1 View commit details
  6. chore: bump prettier from 2.4.1 to 2.5.0 in /web3.js (#21434)

    Bumps [prettier](https://github.com/prettier/prettier) from 2.4.1 to 2.5.0.
    - [Release notes](https://github.com/prettier/prettier/releases)
    - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
    - [Commits](prettier/prettier@2.4.1...2.5.0)
    
    ---
    updated-dependencies:
    - dependency-name: prettier
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    04e5733 View commit details
  7. Copy the full SHA
    a871789 View commit details
  8. chore: bump flowgen from 1.15.0 to 1.16.0 in /web3.js (#21435)

    Bumps [flowgen](https://github.com/joarwilk/flowgen) from 1.15.0 to 1.16.0.
    - [Release notes](https://github.com/joarwilk/flowgen/releases)
    - [Commits](https://github.com/joarwilk/flowgen/commits)
    
    ---
    updated-dependencies:
    - dependency-name: flowgen
      dependency-type: direct:development
      update-type: version-update:semver-minor
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    4875fc4 View commit details
  9. chore: bump semantic-release from 18.0.0 to 18.0.1 in /web3.js (#21436)

    Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 18.0.0 to 18.0.1.
    - [Release notes](https://github.com/semantic-release/semantic-release/releases)
    - [Commits](semantic-release/semantic-release@v18.0.0...v18.0.1)
    
    ---
    updated-dependencies:
    - dependency-name: semantic-release
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    ad43f38 View commit details
  10. chore: bump @commitlint/config-conventional in /web3.js (#21438)

    Bumps [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) from 14.1.0 to 15.0.0.
    - [Release notes](https://github.com/conventional-changelog/commitlint/releases)
    - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
    - [Commits](https://github.com/conventional-changelog/commitlint/commits/v15.0.0/@commitlint/config-conventional)
    
    ---
    updated-dependencies:
    - dependency-name: "@commitlint/config-conventional"
      dependency-type: direct:development
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    9c00e9d View commit details
  11. chore: bump rollup from 2.60.0 to 2.60.1 in /web3.js (#21439)

    Bumps [rollup](https://github.com/rollup/rollup) from 2.60.0 to 2.60.1.
    - [Release notes](https://github.com/rollup/rollup/releases)
    - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
    - [Commits](rollup/rollup@v2.60.0...v2.60.1)
    
    ---
    updated-dependencies:
    - dependency-name: rollup
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 25, 2021
    Copy the full SHA
    e900c5b View commit details

Commits on Nov 29, 2021

  1. fix: refine stacktrace attribution of errors thrown from middleware (…

    …#21470)
    
    * Refine middleware types to include the method signature and to express the nullability of the middleware.
    
    * Make sure that the stacktrace does not involve middleware unless the error originated from the middleware itself.
    
    Co-authored-by: steveluscher <github@steveluscher.com>
    steveluscher and steveluscher authored Nov 29, 2021
    Copy the full SHA
    45923ca View commit details
  2. chore: bump puppeteer from 11.0.0 to 12.0.0 in /web3.js (#21482)

    Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 11.0.0 to 12.0.0.
    - [Release notes](https://github.com/puppeteer/puppeteer/releases)
    - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/CHANGELOG.md)
    - [Commits](puppeteer/puppeteer@v11.0.0...v12.0.0)
    
    ---
    updated-dependencies:
    - dependency-name: puppeteer
      dependency-type: direct:development
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 29, 2021
    Copy the full SHA
    523d41d View commit details

Commits on Nov 30, 2021

  1. chore: bump puppeteer from 12.0.0 to 12.0.1 in /web3.js (#21505)

    Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 12.0.0 to 12.0.1.
    - [Release notes](https://github.com/puppeteer/puppeteer/releases)
    - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/CHANGELOG.md)
    - [Commits](puppeteer/puppeteer@v12.0.0...v12.0.1)
    
    ---
    updated-dependencies:
    - dependency-name: puppeteer
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 30, 2021
    Copy the full SHA
    aee5ffb View commit details
  2. chore: bump @types/express-serve-static-core in /web3.js (#21510)

    Bumps [@types/express-serve-static-core](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express-serve-static-core) from 4.17.25 to 4.17.26.
    - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
    - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express-serve-static-core)
    
    ---
    updated-dependencies:
    - dependency-name: "@types/express-serve-static-core"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 30, 2021
    Copy the full SHA
    4967230 View commit details
  3. chore: bump @types/node from 16.11.10 to 16.11.11 in /web3.js (#21511)

    Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.11.10 to 16.11.11.
    - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
    - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
    
    ---
    updated-dependencies:
    - dependency-name: "@types/node"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 30, 2021
    Copy the full SHA
    c9db75a View commit details
  4. chore: bump rollup from 2.60.1 to 2.60.2 in /web3.js (#21512)

    Bumps [rollup](https://github.com/rollup/rollup) from 2.60.1 to 2.60.2.
    - [Release notes](https://github.com/rollup/rollup/releases)
    - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
    - [Commits](rollup/rollup@v2.60.1...v2.60.2)
    
    ---
    updated-dependencies:
    - dependency-name: rollup
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Nov 30, 2021
    Copy the full SHA
    28c0510 View commit details
  5. fix(web3.js): VoteAccount.fromAccountData() throws range error (#21091)

    * fix(vote-account): rangeError [ERR_OUT_OF_RANGE] error
    
    The web3 buffer layout is out-of-date with the current `VoteState` implementation. The buffer layout
    is updated to match the structure in
    https://github.com/solana-labs/solana/blob/master/account-decoder/src/parse_vote.rs
    
    fix #20786
    
    * docs(vote account): update reference to match new payload
    
    * fix(vote-account): update buffer layout for prior voters
    
    Update buffer layout for prior voters to match serialized data
    
    * fix(vote-account): response showing buffers instead of public keys
    
    transform buffers into public keys
    
    * refactor(vote account): extract parsing into function calls
    
    * feat(vote account): address PR comments
    
    * fix(web3.js vote account): start prior voters array from given index
    
    * fix(web3.js vote account): incorrect data for prior voters array
    
    * Update web3.js/src/vote-account.ts
    
    Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
    
    Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
    cogoo and jstarry authored Nov 30, 2021
    Copy the full SHA
    addccdc View commit details

Commits on Dec 1, 2021

  1. chore: upgrade to Rust 2021

    mvines committed Dec 1, 2021
    Copy the full SHA
    8d18805 View commit details

Commits on Dec 8, 2021

  1. chore: bump prettier from 2.5.0 to 2.5.1 in /web3.js (#21685)

    Bumps [prettier](https://github.com/prettier/prettier) from 2.5.0 to 2.5.1.
    - [Release notes](https://github.com/prettier/prettier/releases)
    - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
    - [Commits](prettier/prettier@2.5.0...2.5.1)
    
    ---
    updated-dependencies:
    - dependency-name: prettier
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Dec 8, 2021
    Copy the full SHA
    0e9615b View commit details
  2. chore: bump @types/node from 16.11.11 to 16.11.12 in /web3.js (#21689)

    Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 16.11.11 to 16.11.12.
    - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
    - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
    
    ---
    updated-dependencies:
    - dependency-name: "@types/node"
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Dec 8, 2021
    Copy the full SHA
    31c4052 View commit details

Commits on Dec 20, 2021

  1. Copy the full SHA
    32dbd62 View commit details

Commits on Dec 29, 2021

  1. fix: add Sysvar PubKeys

    * web3.js: Add Sysvar PubKeys
    
    * remove fees sysvar
    fanatid authored Dec 29, 2021
    Copy the full SHA
    5e7f643 View commit details

Commits on Dec 30, 2021

  1. chore: fix typo in AccountInfo docs (#22196)

    Victor Wu authored Dec 30, 2021
    Copy the full SHA
    fee44e5 View commit details

Commits on Jan 4, 2022

  1. Copy the full SHA
    deaf95e View commit details
  2. fix: add owner to token balance type

    vpontis authored and mvines committed Jan 4, 2022
    Copy the full SHA
    2cb7c1f View commit details

Commits on Jan 8, 2022

  1. Copy the full SHA
    410780a View commit details

Commits on Jan 11, 2022

  1. feat: add Connection.getFeeForMessage (#22128)

    * web3.js: add Connection.getFeeForMessage
    
    * throw if value is null
    
    * fix null value
    
    * fix types
    fanatid authored Jan 11, 2022
    Copy the full SHA
    03268b6 View commit details

Commits on Jan 21, 2022

  1. Copy the full SHA
    308962c View commit details
  2. Copy the full SHA
    46aa013 View commit details
  3. chore: add test timeouts

    mvines committed Jan 21, 2022
    Copy the full SHA
    51948b9 View commit details
  4. shellcheck

    mvines committed Jan 21, 2022
    Copy the full SHA
    6a00eab View commit details

Commits on Jan 22, 2022

  1. Copy the full SHA
    fc5b447 View commit details
  2. Copy the full SHA
    6d1c701 View commit details

Commits on Jan 24, 2022

  1. Copy the full SHA
    7d92227 View commit details

Commits on Jan 27, 2022

  1. chore: update Connection to non-deprecated endpoints (#22800)

    * chore: remove usage of `getConfirmedTransaction`
    
    * chore: use `getBlock` instead of `getConfirmedBlock`
    
    * chore: add `getLatestBlockhash` and test
    joncinque authored Jan 27, 2022
    Copy the full SHA
    c2e5fd9 View commit details

Commits on Jan 31, 2022

  1. chore: fixes typo (#22830)

    mkarots authored Jan 31, 2022
    Copy the full SHA
    db54ebb View commit details

Commits on Feb 3, 2022

  1. fix: enable maxRetries option to SendOptions (#22893)

    * Web3.js - enable maxRetries option to SendOptions
    
    * Prettier
    hareeshnagaraj authored Feb 3, 2022
    Copy the full SHA
    237ee3e View commit details

Commits on Feb 4, 2022

  1. fix: swallow error if socket has already been closed (#22934)

    * Swallow error if socket has already been closed
    
    * fix: log error
    marty-mcflai authored Feb 4, 2022
    Copy the full SHA
    21e29f0 View commit details
  2. Copy the full SHA
    2652c54 View commit details

Commits on Feb 8, 2022

  1. Copy the full SHA
    a3602ec View commit details

Commits on Feb 9, 2022

  1. Copy the full SHA
    829cf65 View commit details

Commits on Feb 15, 2022

  1. chore: bump vm2 from 3.9.5 to 3.9.7 in /web3.js (#23125)

    Bumps [vm2](https://github.com/patriksimek/vm2) from 3.9.5 to 3.9.7.
    - [Release notes](https://github.com/patriksimek/vm2/releases)
    - [Changelog](https://github.com/patriksimek/vm2/blob/master/CHANGELOG.md)
    - [Commits](patriksimek/vm2@3.9.5...3.9.7)
    
    ---
    updated-dependencies:
    - dependency-name: vm2
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Feb 15, 2022
    Copy the full SHA
    f947609 View commit details

Commits on Feb 17, 2022

  1. Copy the full SHA
    834f1ff View commit details

Commits on Feb 21, 2022

  1. fix: simulateTransaction accounts items can be null (#23229)

    * fix: simulated accounts can be null
    
    * Use Missing rather than token program id
    
    Co-authored-by: Arrowana <8245419+Arrowana@users.noreply.github.com>
    Arrowana and Arrowana authored Feb 21, 2022
    Copy the full SHA
    7970c03 View commit details

Commits on Feb 22, 2022

  1. Add script for running nightly rustfmt on all workspaces (#23244)

    * Add script for running nightly rustfmt on all workspaces
    
    * invalidate ci cache
    jstarry authored Feb 22, 2022
    Copy the full SHA
    ebcfe5e View commit details

Commits on Mar 2, 2022

  1. 5
    Copy the full SHA
    dfda2cc View commit details
  2. feat: add getMultipleAccountsInfoAndContext method to Connection

    Similar to `getAccountInfoAndContext`.
    kklas authored and mvines committed Mar 2, 2022
    Copy the full SHA
    8ee1be4 View commit details

Commits on Mar 8, 2022

  1. fix: update 'borsh' dependency to v0.7.0 (#22425)

    Fixes issue with usage of 'global' when used in the browser.
    
    Currently the web3.js distributable is built with a commonJS rollup, but if you
    use the npm package with another packager, it will fail when it hits the call
    to 'global' inside the browser. Borsh fixed this in v0.7.0
    mdp authored Mar 8, 2022
    Copy the full SHA
    8853e2a View commit details

Commits on Mar 9, 2022

  1. chore: bump follow-redirects from 1.13.1 to 1.14.8 in /web3.js (#23122)

    Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.13.1 to 1.14.8.
    - [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
    - [Commits](follow-redirects/follow-redirects@v1.13.1...v1.14.8)
    
    ---
    updated-dependencies:
    - dependency-name: follow-redirects
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Mar 9, 2022
    Copy the full SHA
    881471b View commit details
Showing with 20,005 additions and 25,788 deletions.
  1. +0 −1 .eslintignore
  2. +1 −8 .eslintrc.js
  3. +0 −11 .flowconfig
  4. +48 −0 .github/workflows/cicd.yml
  5. +0 −6 .mocharc.js
  6. +0 −1 .prettierignore
  7. +15 −8 .travis/before_install.sh
  8. +1 −3 .travis/script.sh
  9. +8 −14 README.md
  10. +0 −25 examples/get_account_info.js
  11. +0 −37 examples/send_sol.js
  12. +0 −21 mocha.html
  13. +3,912 −23,738 package-lock.json
  14. +30 −32 package.json
  15. +90 −32 rollup.config.js
  16. +6 −3 scripts/typegen.sh
  17. +4 −0 src/__forks__/browser/fetch-impl.ts
  18. +4 −0 src/__forks__/react-native/fetch-impl.ts
  19. +433 −0 src/address-lookup-table-program.ts
  20. +278 −0 src/compute-budget.ts
  21. +1,782 −783 src/connection.ts
  22. +21 −4 src/ed25519-program.ts
  23. +41 −0 src/errors.ts
  24. +13 −0 src/fetch-impl.ts
  25. +5 −0 src/index.ts
  26. +15 −5 src/instruction.ts
  27. +89 −22 src/layout.ts
  28. +18 −4 src/loader.ts
  29. +22 −5 src/message.ts
  30. +15 −2 src/nonce-account.ts
  31. +35 −7 src/publickey.ts
  32. +15 −1 src/secp256k1-program.ts
  33. +150 −38 src/stake-program.ts
  34. +145 −44 src/system-program.ts
  35. +16 −4 src/sysvar.ts
  36. +10 −0 src/transaction-constants.ts
  37. +199 −43 src/transaction.ts
  38. +2 −0 src/util/__forks__/react-native/url-impl.ts
  39. +43 −0 src/util/bigint.ts
  40. +2 −2 src/util/cluster.ts
  41. +2 −0 src/util/{url.ts → makeWebsocketUrl.ts}
  42. +53 −7 src/util/send-and-confirm-raw-transaction.ts
  43. +21 −6 src/util/send-and-confirm-transaction.ts
  44. +35 −0 src/util/tx-expiry-custom-errors.ts
  45. +2 −0 src/util/url-impl.ts
  46. +137 −36 src/vote-account.ts
  47. +413 −0 src/vote-program.ts
  48. +266 −0 test/address-lookup-table-program.test.ts
  49. +22 −3 test/bpf-loader.test.ts
  50. +220 −0 test/compute-budget.test.ts
  51. +951 −0 test/connection-subscriptions.test.ts
  52. +1,551 −647 test/connection.test.ts
  53. +1 −1 test/fixtures/noop-program/Cargo.toml
  54. +0 −2 test/fixtures/noop-program/Xargo.toml
  55. BIN test/fixtures/noop-program/solana_bpf_rust_noop.so
  56. +10 −4 test/fixtures/noop-program/src/lib.rs
  57. +77 −22 test/mocks/rpc-http.ts
  58. +25 −6 test/mocks/rpc-websockets.ts
  59. +24 −0 test/publickey.test.ts
  60. +0 −61 test/rollup.config.js
  61. +115 −31 test/stake-program.test.ts
  62. +12 −2 test/system-program.test.ts
  63. +1 −1 test/transaction-payer.test.ts
  64. +488 −54 test/transaction.test.ts
  65. +265 −0 test/vote-program.test.ts
  66. +3 −0 test/websocket.test.ts
  67. +3 −1 tsconfig.json
  68. +1 −0 typedoc.json
  69. +7,839 −0 yarn.lock
1 change: 0 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
/deploy
/doc
/lib
/module.flow.js
/.eslintrc.js
/test/.eslintrc.js
/test/dist
9 changes: 1 addition & 8 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -31,17 +31,10 @@ module.exports = {
'newlines-between': 'always',
},
],
indent: [
'error',
2,
{
MemberExpression: 1,
SwitchCase: 1,
},
],
'linebreak-style': ['error', 'unix'],
'no-console': [0],
'no-trailing-spaces': ['error'],
'no-undef': 'off',
'no-unused-vars': 'off',
quotes: [
'error',
11 changes: 0 additions & 11 deletions .flowconfig

This file was deleted.

48 changes: 48 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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: "lts/*"
cache: "npm"

- name: Build
run: |
sh -c "$(curl -sSfL https://release.solana.com/edge/install)"
PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
solana --version
npm install -g npm@7
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
- 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
6 changes: 0 additions & 6 deletions .mocharc.js

This file was deleted.

1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
test/dist
module.flow.js
declarations
23 changes: 15 additions & 8 deletions .travis/before_install.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
# |source| this file

wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main"
sudo apt-get update
sudo apt-get install -y clang-7 --allow-unauthenticated
sudo apt-get install -y openssl --allow-unauthenticated
sudo apt-get install -y libssl-dev --allow-unauthenticated
sudo apt-get install -y libssl1.1 --allow-unauthenticated
clang-7 --version
if [[ -n $TRAVIS ]]; then
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main"
sudo apt-get update
sudo apt-get install -y clang-7 --allow-unauthenticated
sudo apt-get install -y openssl --allow-unauthenticated
sudo apt-get install -y libssl-dev --allow-unauthenticated
sudo apt-get install -y libssl1.1 --allow-unauthenticated
clang-7 --version
fi

sh -c "$(curl -sSfL https://release.solana.com/edge/install)"
PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
solana --version

if [[ -n $GITHUB_ACTIONS ]]; then
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"
npm install -g npm@7
fi
4 changes: 1 addition & 3 deletions .travis/script.sh
Original file line number Diff line number Diff line change
@@ -9,8 +9,6 @@ ls -l lib
test -r lib/index.iife.js
test -r lib/index.cjs.js
test -r lib/index.esm.js
npm run doc
npm run lint
npm run ok
npm run codecov
npm run test:live-with-test-validator
npm run test:browser-with-test-validator
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -23,7 +23,10 @@

This is the Solana Javascript API built on the Solana [JSON RPC API](https://docs.solana.com/apps/jsonrpc-api)

[Latest API Documentation](https://solana-labs.github.io/solana-web3.js/)
## 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

@@ -84,20 +87,11 @@ console.log(solanaWeb3);
console.log(solanaWeb3);
```

## Examples

Example scripts for the web3.js repo and native programs:

- [Web3 Examples](https://github.com/solana-labs/solana/tree/master/web3.js/examples)

Example scripts for the Solana Program Library:

- [Token Program Examples](https://github.com/solana-labs/solana-program-library/tree/master/token/js/examples)

## Flow
## Flow Support (Discontinued)

A [Flow library definition](https://flow.org/en/docs/libdefs/) is provided at
https://unpkg.com/@solana/web3.js@latest/module.flow.js.
Flow types are no longer supported in new releases. The last release with Flow support is v1.37.2 and its
[Flow library definition](https://flow.org/en/docs/libdefs/) is provided at
https://unpkg.com/@solana/web3.js@v1.37.2/module.flow.js.
Download the file and add the following line under the [libs] section of your project's `.flowconfig` to
activate it:

25 changes: 0 additions & 25 deletions examples/get_account_info.js

This file was deleted.

37 changes: 0 additions & 37 deletions examples/send_sol.js

This file was deleted.

21 changes: 0 additions & 21 deletions mocha.html

This file was deleted.

27,650 changes: 3,912 additions & 23,738 deletions package-lock.json

Large diffs are not rendered by default.

62 changes: 30 additions & 32 deletions package.json
Original file line number Diff line number Diff line change
@@ -20,9 +20,10 @@
"access": "public"
},
"browser": {
"./lib/index.cjs.js": "./lib/index.browser.esm.js",
"./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",
@@ -33,46 +34,43 @@
],
"files": [
"/lib",
"/module.flow.js",
"/src"
],
"scripts": {
"build": "npm run clean; cross-env NODE_ENV=production rollup -c; npm run type:gen; npm run flow:gen; npm run flow:check",
"build:browser-test": "rollup -c test/rollup.config.js",
"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 --treatWarningsAsErrors",
"flow:check": "flow check-contents < module.flow.js",
"flow:gen": "flowgen lib/index.d.ts -o module.flow.js",
"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",
"ok": "run-s lint test doc",
"type:check": "tsc -p tsconfig.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": "mocha -r ts-node/register './test/**/*.test.ts'",
"test:cover": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' nyc --reporter=lcov mocha -r ts-node/register './test/**/*.test.ts'",
"test:browser": "TEST_LIVE=1 npm run build:browser-test && mocha-headless-chrome -f http://localhost:8080/mocha.html --timeout 180000",
"test:browser-with-server": "start-server-and-test 'http-server -p 8080' 8080 test:browser",
"test:browser-with-test-validator": "start-server-and-test 'solana-test-validator --reset --quiet' http://localhost:8899/health test:browser-with-server",
"test": "cross-env TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\", \"target\": \"es2019\" }' ts-mocha --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",
"@ethersproject/sha2": "^5.5.0",
"@solana/buffer-layout": "^3.0.0",
"@solana/buffer-layout": "^4.0.0",
"bigint-buffer": "^1.1.5",
"bn.js": "^5.0.0",
"borsh": "^0.4.0",
"borsh": "^0.7.0",
"bs58": "^4.0.1",
"buffer": "6.0.1",
"cross-fetch": "^3.1.4",
"fast-stable-stringify": "^1.0.0",
"jayson": "^3.4.4",
"js-sha3": "^0.8.0",
"rpc-websockets": "^7.4.2",
"node-fetch": "2",
"react-native-url-polyfill": "^1.3.0",
"rpc-websockets": "^7.5.0",
"secp256k1": "^4.0.2",
"superstruct": "^0.14.2",
"tweetnacl": "^1.0.0"
@@ -84,26 +82,28 @@
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.16",
"@babel/register": "^7.12.13",
"@commitlint/config-conventional": "^14.1.0",
"@commitlint/travis-cli": "^14.1.0",
"@rollup/plugin-alias": "^3.1.2",
"@commitlint/config-conventional": "^17.0.2",
"@commitlint/travis-cli": "^17.0.0",
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-babel": "^5.2.3",
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-commonjs": "^22.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-multi-entry": "^4.0.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-replace": "^3.0.0",
"@solana/spl-token": "^0.1.2",
"@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": "^9.0.0",
"@types/mz": "^2.7.3",
"@types/node": "^16.0.0",
"@types/node": "^17.0.24",
"@types/node-fetch": "2",
"@types/secp256k1": "^4.0.1",
"@types/sinon": "^10.0.0",
"@types/sinon-chai": "^3.2.8",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"chai": "^4.3.0",
@@ -112,29 +112,27 @@
"cross-env": "7.0.3",
"eslint": "^7.19.0",
"eslint-config-prettier": "^8.0.0",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-mocha": "^10.0.4",
"eslint-plugin-prettier": "^4.0.0",
"esm": "^3.2.25",
"flow-bin": "^0.150.0",
"flowgen": "^1.13.0",
"http-server": "^14.0.0",
"mocha": "^8.2.1",
"mocha-headless-chrome": "^3.1.0",
"mockttp": "^2.0.1",
"mz": "^2.7.0",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"prettier": "^2.3.0",
"puppeteer": "^11.0.0",
"rimraf": "3.0.2",
"rollup": "2.60.0",
"rollup": "2.70.2",
"rollup-plugin-dts": "^4.0.0",
"rollup-plugin-node-polyfills": "^0.2.1",
"rollup-plugin-terser": "^7.0.2",
"semantic-release": "^18.0.0",
"sinon": "^12.0.0",
"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.22.2",
122 changes: 90 additions & 32 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import alias from '@rollup/plugin-alias';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import * as fs from 'fs';
import json from '@rollup/plugin-json';
import path from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import {terser} from 'rollup-plugin-terser';
@@ -9,12 +12,47 @@ const env = process.env.NODE_ENV;
const extensions = ['.js', '.ts'];

function generateConfig(configType, format) {
const browser = configType === 'browser';
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,
@@ -33,32 +71,40 @@ function generateConfig(configType, format) {
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) {
if (warning.code !== 'CIRCULAR_DEPENDENCY') {
rollupWarn(warning);
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 (configType !== 'browser') {
if (!browser) {
// Prevent dependencies from being bundled
config.external = [
/@babel\/runtime/,
'@solana/buffer-layout',
'bigint-buffer',
'bn.js',
'borsh',
'bs58',
'buffer',
'crypto-hash',
'jayson/lib/client/browser',
'js-sha3',
'cross-fetch',
'node-fetch',
'rpc-websockets',
'secp256k1',
'superstruct',
@@ -68,20 +114,52 @@ function generateConfig(configType, format) {

switch (configType) {
case 'browser':
case 'react-native':
switch (format) {
case 'esm': {
case 'iife': {
config.external = ['http', 'https', 'node-fetch'];

config.output = [
{
file: 'lib/index.browser.esm.js',
format: 'es',
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',
'bigint-buffer',
'bn.js',
'borsh',
'bs58',
@@ -91,6 +169,8 @@ function generateConfig(configType, format) {
'https',
'jayson/lib/client/browser',
'js-sha3',
'node-fetch',
'react-native-url-polyfill',
'rpc-websockets',
'secp256k1',
'superstruct',
@@ -99,29 +179,6 @@ function generateConfig(configType, format) {

break;
}
case 'iife': {
config.external = ['http', 'https'];

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:
throw new Error(`Unknown format: ${format}`);
}

// TODO: Find a workaround to avoid resolving the following JSON file:
@@ -152,6 +209,7 @@ function generateConfig(configType, format) {

export default [
generateConfig('node'),
generateConfig('browser', 'esm'),
generateConfig('browser'),
generateConfig('browser', 'iife'),
generateConfig('react-native'),
];
9 changes: 6 additions & 3 deletions scripts/typegen.sh
Original file line number Diff line number Diff line change
@@ -7,13 +7,16 @@ npx tsc -p tsconfig.d.json -d
npx rollup -c rollup.config.types.js

# Replace export with closing brace for module declaration
sed -i '$s/export {.*};/}/' lib/index.d.ts
sed -i.bak '$s/export {.*};/}/' lib/index.d.ts

# Replace declare's with export's
sed -i 's/declare/export/g' lib/index.d.ts
sed -i.bak 's/declare/export/g' lib/index.d.ts

# Prepend declare module line
sed -i '2s;^;declare module "@solana/web3.js" {\n;' lib/index.d.ts
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
4 changes: 4 additions & 0 deletions src/__forks__/browser/fetch-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const Headers = globalThis.Headers;
export const Request = globalThis.Request;
export const Response = globalThis.Response;
export default globalThis.fetch;
4 changes: 4 additions & 0 deletions src/__forks__/react-native/fetch-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const Headers = globalThis.Headers;
export const Request = globalThis.Request;
export const Response = globalThis.Response;
export default globalThis.fetch;
433 changes: 433 additions & 0 deletions src/address-lookup-table-program.ts

Large diffs are not rendered by default.

278 changes: 278 additions & 0 deletions src/compute-budget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
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 './util/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',
);

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,
});
}
}
2,565 changes: 1,782 additions & 783 deletions src/connection.ts

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions src/ed25519-program.ts
Original file line number Diff line number Diff line change
@@ -30,7 +30,19 @@ export type CreateEd25519InstructionWithPrivateKeyParams = {
instructionIndex?: number;
};

const ED25519_INSTRUCTION_LAYOUT = BufferLayout.struct([
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'),
@@ -82,17 +94,22 @@ export class Ed25519Program {

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: instructionIndex,
signatureInstructionIndex: index,
publicKeyOffset,
publicKeyInstructionIndex: instructionIndex,
publicKeyInstructionIndex: index,
messageDataOffset,
messageDataSize: message.length,
messageInstructionIndex: instructionIndex,
messageInstructionIndex: index,
},
instructionData,
);
41 changes: 41 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -7,3 +7,44 @@ export class SendTransactionError extends Error {
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';
}
}
13 changes: 13 additions & 0 deletions src/fetch-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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);
}
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export * from './account';
export * from './address-lookup-table-program';
export * from './blockhash';
export * from './bpf-loader-deprecated';
export * from './bpf-loader';
export * from './compute-budget';
export * from './connection';
export * from './epoch-schedule';
export * from './ed25519-program';
@@ -15,13 +17,16 @@ export * from './stake-program';
export * from './system-program';
export * from './secp256k1-program';
export * from './transaction';
export * from './transaction-constants';
export * from './validator-info';
export * from './vote-account';
export * from './vote-program';
export * from './sysvar';
export * from './errors';
export * from './util/borsh-schema';
export * from './util/send-and-confirm-transaction';
export * from './util/send-and-confirm-raw-transaction';
export * from './util/tx-expiry-custom-errors';
export * from './util/cluster';

/**
20 changes: 15 additions & 5 deletions src/instruction.ts
Original file line number Diff line number Diff line change
@@ -3,21 +3,28 @@ import * as BufferLayout from '@solana/buffer-layout';

import * as Layout from './layout';

export interface IInstructionInputData {
readonly instruction: number;
}

/**
* @internal
*/
export type InstructionType = {
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;
layout: BufferLayout.Layout<TInputData>;
};

/**
* Populate a buffer of instruction data using an InstructionType
* @internal
*/
export function encodeData(type: InstructionType, fields?: any): Buffer {
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);
@@ -30,8 +37,11 @@ export function encodeData(type: InstructionType, fields?: any): Buffer {
* Decode instruction data buffer using an InstructionType
* @internal
*/
export function decodeData(type: InstructionType, buffer: Buffer): any {
let data;
export function decodeData<TInputData extends IInstructionInputData>(
type: InstructionType<TInputData>,
buffer: Buffer,
): TInputData {
let data: TInputData;
try {
data = type.layout.decode(buffer);
} catch (err) {
111 changes: 89 additions & 22 deletions src/layout.ts
Original file line number Diff line number Diff line change
@@ -4,24 +4,47 @@ import * as BufferLayout from '@solana/buffer-layout';
/**
* Layout for a public key
*/
export const publicKey = (
property: string = 'publicKey',
): BufferLayout.Layout => {
export const publicKey = (property: string = 'publicKey') => {
return BufferLayout.blob(32, property);
};

/**
* Layout for a 64bit unsigned value
*/
export const uint64 = (property: string = 'uint64'): BufferLayout.Layout => {
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') => {
const rsl = BufferLayout.struct(
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'),
@@ -32,44 +55,54 @@ export const rustString = (property: string = 'string') => {
const _decode = rsl.decode.bind(rsl);
const _encode = rsl.encode.bind(rsl);

rsl.decode = (buffer: any, offset: any) => {
const data = _decode(buffer, offset);
return data['chars'].toString('utf8');
const rslShim = rsl as unknown as IRustStringShim;

rslShim.decode = (b: Uint8Array, offset?: number) => {
const data = _decode(b, offset);
return data['chars'].toString();
};

rsl.encode = (str: any, buffer: any, offset: any) => {
rslShim.encode = (str: string, b: Uint8Array, offset?: number) => {
const data = {
chars: Buffer.from(str, 'utf8'),
};
return _encode(data, buffer, offset);
return _encode(data, b, offset);
};

(rsl as any).alloc = (str: any) => {
rslShim.alloc = (str: string) => {
return (
BufferLayout.u32().span +
BufferLayout.u32().span +
Buffer.from(str, 'utf8').length
);
};

return rsl;
return rslShim;
};

/**
* Layout for an Authorized object
*/
export const authorized = (property: string = 'authorized') => {
return BufferLayout.struct(
[publicKey('staker'), publicKey('withdrawer')],
property,
);
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(
return BufferLayout.struct<
Readonly<{
custodian: Uint8Array;
epoch: number;
unixTimestamp: number;
}>
>(
[
BufferLayout.ns64('unixTimestamp'),
BufferLayout.ns64('epoch'),
@@ -79,14 +112,48 @@ export const lockup = (property: string = 'lockup') => {
);
};

/**
* 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,
);
};

export function getAlloc(type: any, fields: any): number {
let alloc = 0;
type.layout.fields.forEach((item: any) => {
const getItemAlloc = (item: any): number => {
if (item.span >= 0) {
alloc += item.span;
return item.span;
} else if (typeof item.alloc === 'function') {
alloc += item.alloc(fields[item.property]);
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);
}
}
// Couldn't determine allocated size of layout
return 0;
};

let alloc = 0;
type.layout.fields.forEach((item: any) => {
alloc += getItemAlloc(item);
});

return alloc;
}
22 changes: 18 additions & 4 deletions src/loader.ts
Original file line number Diff line number Diff line change
@@ -2,13 +2,15 @@ import {Buffer} from 'buffer';
import * as BufferLayout from '@solana/buffer-layout';

import {PublicKey} from './publickey';
import {Transaction, PACKET_DATA_SIZE} from './transaction';
import {Transaction} from './transaction';
import {SYSVAR_RENT_PUBKEY} from './sysvar';
import {sendAndConfirmTransaction} from './util/send-and-confirm-transaction';
import {sleep} from './util/sleep';
import type {Connection} from './connection';
import type {Signer} from './keypair';
import {SystemProgram} from './system-program';
import {IInstructionInputData} from './instruction';
import {PACKET_DATA_SIZE} from './transaction-constants';

// Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the
// rest of the Transaction fields
@@ -137,7 +139,15 @@ export class Loader {
}
}

const dataLayout = BufferLayout.struct([
const dataLayout = BufferLayout.struct<
Readonly<{
bytes: number[];
bytesLength: number;
bytesLengthPadding: number;
instruction: number;
offset: number;
}>
>([
BufferLayout.u32('instruction'),
BufferLayout.u32('offset'),
BufferLayout.u32('bytesLength'),
@@ -160,7 +170,9 @@ export class Loader {
{
instruction: 0, // Load instruction
offset,
bytes,
bytes: bytes as number[],
bytesLength: 0,
bytesLengthPadding: 0,
},
data,
);
@@ -189,7 +201,9 @@ export class Loader {

// Finalize the account loaded with program data for execution
{
const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction')]);
const dataLayout = BufferLayout.struct<IInstructionInputData>([
BufferLayout.u32('instruction'),
]);

const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
27 changes: 22 additions & 5 deletions src/message.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import * as BufferLayout from '@solana/buffer-layout';
import {PublicKey} from './publickey';
import type {Blockhash} from './blockhash';
import * as Layout from './layout';
import {PACKET_DATA_SIZE} from './transaction';
import {PACKET_DATA_SIZE} from './transaction-constants';
import * as shortvec from './util/shortvec-encoding';
import {toBuffer} from './util/to-buffer';

@@ -118,7 +118,7 @@ export class Message {

const instructions = this.instructions.map(instruction => {
const {accounts, programIdIndex} = instruction;
const data = bs58.decode(instruction.data);
const data = Array.from(bs58.decode(instruction.data));

let keyIndicesCount: number[] = [];
shortvec.encodeLength(keyIndicesCount, accounts.length);
@@ -129,7 +129,7 @@ export class Message {
return {
programIdIndex,
keyIndicesCount: Buffer.from(keyIndicesCount),
keyIndices: Buffer.from(accounts),
keyIndices: accounts,
dataLength: Buffer.from(dataCount),
data,
};
@@ -142,7 +142,15 @@ export class Message {
let instructionBufferLength = instructionCount.length;

instructions.forEach(instruction => {
const instructionLayout = BufferLayout.struct([
const instructionLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
keyIndices: number[];
keyIndicesCount: Uint8Array;
programIdIndex: number;
}>
>([
BufferLayout.u8('programIdIndex'),

BufferLayout.blob(
@@ -170,7 +178,16 @@ export class Message {
});
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength);

const signDataLayout = BufferLayout.struct([
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'),
17 changes: 15 additions & 2 deletions src/nonce-account.ts
Original file line number Diff line number Diff line change
@@ -13,12 +13,25 @@ import {toBuffer} from './util/to-buffer';
*
* @internal
*/
const NonceAccountLayout = BufferLayout.struct([
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([FeeCalculatorLayout], 'feeCalculator'),
BufferLayout.struct<Readonly<{lamportsPerSignature: number}>>(
[FeeCalculatorLayout],
'feeCalculator',
),
]);

export const NONCE_ACCOUNT_LENGTH = NonceAccountLayout.span;
42 changes: 35 additions & 7 deletions src/publickey.ts
Original file line number Diff line number Diff line change
@@ -87,6 +87,10 @@ export class PublicKey extends Struct {
return bs58.encode(this.toBytes());
}

toJSON(): string {
return this.toBase58();
}

/**
* Return the byte array representation of the public key
*/
@@ -139,10 +143,10 @@ export class PublicKey extends Struct {
* Derive a program address from seeds and a program ID.
*/
/* eslint-disable require-await */
static async createProgramAddress(
static createProgramAddressSync(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): Promise<PublicKey> {
): PublicKey {
let buffer = Buffer.alloc(0);
seeds.forEach(function (seed) {
if (seed.length > MAX_SEED_LENGTH) {
@@ -163,23 +167,35 @@ export class PublicKey extends Struct {
return new PublicKey(publicKeyBytes);
}

/**
* Async version of createProgramAddressSync
* For backwards compatibility
*/
/* 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 async findProgramAddress(
static findProgramAddressSync(
seeds: Array<Buffer | Uint8Array>,
programId: PublicKey,
): Promise<[PublicKey, number]> {
): [PublicKey, number] {
let nonce = 255;
let address;
while (nonce != 0) {
try {
const seedsWithNonce = seeds.concat(Buffer.from([nonce]));
address = await this.createProgramAddress(seedsWithNonce, programId);
address = this.createProgramAddressSync(seedsWithNonce, programId);
} catch (err) {
if (err instanceof TypeError) {
throw err;
@@ -192,11 +208,23 @@ export class PublicKey extends Struct {
throw new Error(`Unable to find a viable program address nonce`);
}

/**
* Async version of findProgramAddressSync
* For backwards compatibility
*/
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(pubkey: Uint8Array): boolean {
return is_on_curve(pubkey) == 1;
static isOnCurve(pubkeyData: PublicKeyInitData): boolean {
const pubkey = new PublicKey(pubkeyData);
return is_on_curve(pubkey.toBytes()) == 1;
}
}

16 changes: 15 additions & 1 deletion src/secp256k1-program.ts
Original file line number Diff line number Diff line change
@@ -46,7 +46,21 @@ export type CreateSecp256k1InstructionWithPrivateKeyParams = {
instructionIndex?: number;
};

const SECP256K1_INSTRUCTION_LAYOUT = BufferLayout.struct([
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'),
188 changes: 150 additions & 38 deletions src/stake-program.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import * as BufferLayout from '@solana/buffer-layout';

import {encodeData, decodeData, InstructionType} from './instruction';
import {
encodeData,
decodeData,
InstructionType,
IInstructionInputData,
} from './instruction';
import * as Layout from './layout';
import {PublicKey} from './publickey';
import {SystemProgram} from './system-program';
@@ -40,6 +45,11 @@ export class Authorized {
}
}

type AuthorizedRaw = Readonly<{
staker: Uint8Array;
withdrawer: Uint8Array;
}>;

/**
* Stake account lockup info
*/
@@ -66,6 +76,12 @@ export class Lockup {
static default: Lockup = new Lockup(0, 0, PublicKey.default);
}

type LockupRaw = Readonly<{
custodian: Uint8Array;
epoch: number;
unixTimestamp: number;
}>;

/**
* Create stake account transaction params
*/
@@ -147,6 +163,18 @@ export type SplitStakeParams = {
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
*/
@@ -417,73 +445,119 @@ export class StakeInstruction {
* An enumeration of valid StakeInstructionType's
*/
export type StakeInstructionType =
| 'AuthorizeWithSeed'
// 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'
| 'Merge';
| '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: {
[type in StakeInstructionType]: InstructionType;
} = Object.freeze({
export const STAKE_INSTRUCTION_LAYOUTS = Object.freeze<{
[Instruction in StakeInstructionType]: InstructionType<
StakeInstructionInputData[Instruction]
>;
}>({
Initialize: {
index: 0,
layout: BufferLayout.struct([
layout: BufferLayout.struct<StakeInstructionInputData['Initialize']>([
BufferLayout.u32('instruction'),
Layout.authorized(),
Layout.lockup(),
]),
},
Authorize: {
index: 1,
layout: BufferLayout.struct([
layout: BufferLayout.struct<StakeInstructionInputData['Authorize']>([
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('stakeAuthorizationType'),
]),
},
Delegate: {
index: 2,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
layout: BufferLayout.struct<StakeInstructionInputData['Delegate']>([
BufferLayout.u32('instruction'),
]),
},
Split: {
index: 3,
layout: BufferLayout.struct([
layout: BufferLayout.struct<StakeInstructionInputData['Split']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
Withdraw: {
index: 4,
layout: BufferLayout.struct([
layout: BufferLayout.struct<StakeInstructionInputData['Withdraw']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
},
Deactivate: {
index: 5,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
layout: BufferLayout.struct<StakeInstructionInputData['Deactivate']>([
BufferLayout.u32('instruction'),
]),
},
Merge: {
index: 7,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
layout: BufferLayout.struct<StakeInstructionInputData['Merge']>([
BufferLayout.u32('instruction'),
]),
},
AuthorizeWithSeed: {
index: 8,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('stakeAuthorizationType'),
Layout.rustString('authoritySeed'),
Layout.publicKey('authorityOwner'),
]),
layout: BufferLayout.struct<StakeInstructionInputData['AuthorizeWithSeed']>(
[
BufferLayout.u32('instruction'),
Layout.publicKey('newAuthorized'),
BufferLayout.u32('stakeAuthorizationType'),
Layout.rustString('authoritySeed'),
Layout.publicKey('authorityOwner'),
],
),
},
});

@@ -527,8 +601,8 @@ export class StakeProgram {
* Max space of a Stake account
*
* This is generated from the solana-stake-program StakeState struct as
* `std::mem::size_of::<StakeState>()`:
* https://docs.rs/solana-stake-program/1.4.4/solana_stake_program/stake_state/enum.StakeState.html
* `StakeState::size_of()`:
* https://docs.rs/solana-stake-program/latest/solana_stake_program/stake_state/enum.StakeState.html
*/
static space: number = 200;

@@ -706,25 +780,13 @@ export class StakeProgram {
}

/**
* Generate a Transaction that splits Stake tokens into another stake account
* @internal
*/
static split(params: SplitStakeParams): Transaction {
static splitInstruction(params: SplitStakeParams): TransactionInstruction {
const {stakePubkey, authorizedPubkey, splitStakePubkey, lamports} = params;

const transaction = new Transaction();
transaction.add(
SystemProgram.createAccount({
fromPubkey: authorizedPubkey,
newAccountPubkey: splitStakePubkey,
lamports: 0,
space: this.space,
programId: this.programId,
}),
);
const type = STAKE_INSTRUCTION_LAYOUTS.Split;
const data = encodeData(type, {lamports});

return transaction.add({
return new TransactionInstruction({
keys: [
{pubkey: stakePubkey, isSigner: false, isWritable: true},
{pubkey: splitStakePubkey, isSigner: false, isWritable: true},
@@ -735,6 +797,56 @@ export class StakeProgram {
});
}

/**
* 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.
*/
189 changes: 145 additions & 44 deletions src/system-program.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import * as BufferLayout from '@solana/buffer-layout';

import {encodeData, decodeData, InstructionType} from './instruction';
import {
encodeData,
decodeData,
InstructionType,
IInstructionInputData,
} from './instruction';
import * as Layout from './layout';
import {NONCE_ACCOUNT_LENGTH} from './nonce-account';
import {PublicKey} from './publickey';
import {SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY} from './sysvar';
import {Transaction, TransactionInstruction} from './transaction';
import {toBuffer} from './util/to-buffer';
import {u64} from './util/bigint';

/**
* Create account system transaction params
@@ -33,7 +39,7 @@ export type TransferParams = {
/** Account that will receive transferred lamports */
toPubkey: PublicKey;
/** Amount of lamports to transfer */
lamports: number;
lamports: number | bigint;
};

/**
@@ -195,7 +201,33 @@ export type TransferWithSeedParams = {
/** Account that will receive transferred lamports */
toPubkey: PublicKey;
/** Amount of lamports to transfer */
lamports: number;
lamports: number | bigint;
/** Seed to use to derive the funding account address */
seed: string;
/** Program id to use to derive the funding account address */
programId: PublicKey;
};

/** Decoded transfer system transaction instruction */
export type DecodedTransferInstruction = {
/** Account that will transfer lamports */
fromPubkey: PublicKey;
/** Account that will receive transferred lamports */
toPubkey: PublicKey;
/** Amount of lamports to transfer */
lamports: bigint;
};

/** Decoded transferWithSeed system transaction instruction */
export type DecodedTransferWithSeedInstruction = {
/** Account that will transfer lamports */
fromPubkey: PublicKey;
/** Base public key to use to derive the funding account address */
basePubkey: PublicKey;
/** Account that will receive transferred lamports */
toPubkey: PublicKey;
/** Amount of lamports to transfer */
lamports: bigint;
/** Seed to use to derive the funding account address */
seed: string;
/** Program id to use to derive the funding account address */
@@ -263,7 +295,9 @@ export class SystemInstruction {
/**
* Decode a transfer system instruction and retrieve the instruction params.
*/
static decodeTransfer(instruction: TransactionInstruction): TransferParams {
static decodeTransfer(
instruction: TransactionInstruction,
): DecodedTransferInstruction {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 2);

@@ -284,7 +318,7 @@ export class SystemInstruction {
*/
static decodeTransferWithSeed(
instruction: TransactionInstruction,
): TransferWithSeedParams {
): DecodedTransferWithSeedInstruction {
this.checkProgramId(instruction.programId);
this.checkKeyLength(instruction.keys, 3);

@@ -517,6 +551,10 @@ export class SystemInstruction {
* An enumeration of valid SystemInstructionType's
*/
export type SystemInstructionType =
// FIXME
// It would be preferable for this type to be `keyof SystemInstructionInputData`
// but Typedoc does not transpile `keyof` expressions.
// See https://github.com/TypeStrong/typedoc/issues/1894
| 'AdvanceNonceAccount'
| 'Allocate'
| 'AllocateWithSeed'
@@ -528,18 +566,72 @@ export type SystemInstructionType =
| 'InitializeNonceAccount'
| 'Transfer'
| 'TransferWithSeed'
| 'WithdrawNonceAccount';
| 'WithdrawNonceAccount'
| 'UpgradeNonceAccount';

type SystemInstructionInputData = {
AdvanceNonceAccount: IInstructionInputData;
Allocate: IInstructionInputData & {
space: number;
};
AllocateWithSeed: IInstructionInputData & {
base: Uint8Array;
programId: Uint8Array;
seed: string;
space: number;
};
Assign: IInstructionInputData & {
programId: Uint8Array;
};
AssignWithSeed: IInstructionInputData & {
base: Uint8Array;
seed: string;
programId: Uint8Array;
};
AuthorizeNonceAccount: IInstructionInputData & {
authorized: Uint8Array;
};
Create: IInstructionInputData & {
lamports: number;
programId: Uint8Array;
space: number;
};
CreateWithSeed: IInstructionInputData & {
base: Uint8Array;
lamports: number;
programId: Uint8Array;
seed: string;
space: number;
};
InitializeNonceAccount: IInstructionInputData & {
authorized: Uint8Array;
};
Transfer: IInstructionInputData & {
lamports: bigint;
};
TransferWithSeed: IInstructionInputData & {
lamports: bigint;
programId: Uint8Array;
seed: string;
};
WithdrawNonceAccount: IInstructionInputData & {
lamports: number;
};
UpgradeNonceAccount: IInstructionInputData;
};

/**
* An enumeration of valid system InstructionType's
* @internal
*/
export const SYSTEM_INSTRUCTION_LAYOUTS: {
[type in SystemInstructionType]: InstructionType;
} = Object.freeze({
export const SYSTEM_INSTRUCTION_LAYOUTS = Object.freeze<{
[Instruction in SystemInstructionType]: InstructionType<
SystemInstructionInputData[Instruction]
>;
}>({
Create: {
index: 0,
layout: BufferLayout.struct([
layout: BufferLayout.struct<SystemInstructionInputData['Create']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
BufferLayout.ns64('space'),
@@ -548,21 +640,21 @@ export const SYSTEM_INSTRUCTION_LAYOUTS: {
},
Assign: {
index: 1,
layout: BufferLayout.struct([
layout: BufferLayout.struct<SystemInstructionInputData['Assign']>([
BufferLayout.u32('instruction'),
Layout.publicKey('programId'),
]),
},
Transfer: {
index: 2,
layout: BufferLayout.struct([
layout: BufferLayout.struct<SystemInstructionInputData['Transfer']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
u64('lamports'),
]),
},
CreateWithSeed: {
index: 3,
layout: BufferLayout.struct([
layout: BufferLayout.struct<SystemInstructionInputData['CreateWithSeed']>([
BufferLayout.u32('instruction'),
Layout.publicKey('base'),
Layout.rustString('seed'),
@@ -573,49 +665,50 @@ export const SYSTEM_INSTRUCTION_LAYOUTS: {
},
AdvanceNonceAccount: {
index: 4,
layout: BufferLayout.struct([BufferLayout.u32('instruction')]),
layout: BufferLayout.struct<
SystemInstructionInputData['AdvanceNonceAccount']
>([BufferLayout.u32('instruction')]),
},
WithdrawNonceAccount: {
index: 5,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
]),
layout: BufferLayout.struct<
SystemInstructionInputData['WithdrawNonceAccount']
>([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]),
},
InitializeNonceAccount: {
index: 6,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('authorized'),
]),
layout: BufferLayout.struct<
SystemInstructionInputData['InitializeNonceAccount']
>([BufferLayout.u32('instruction'), Layout.publicKey('authorized')]),
},
AuthorizeNonceAccount: {
index: 7,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('authorized'),
]),
layout: BufferLayout.struct<
SystemInstructionInputData['AuthorizeNonceAccount']
>([BufferLayout.u32('instruction'), Layout.publicKey('authorized')]),
},
Allocate: {
index: 8,
layout: BufferLayout.struct([
layout: BufferLayout.struct<SystemInstructionInputData['Allocate']>([
BufferLayout.u32('instruction'),
BufferLayout.ns64('space'),
]),
},
AllocateWithSeed: {
index: 9,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
Layout.publicKey('base'),
Layout.rustString('seed'),
BufferLayout.ns64('space'),
Layout.publicKey('programId'),
]),
layout: BufferLayout.struct<SystemInstructionInputData['AllocateWithSeed']>(
[
BufferLayout.u32('instruction'),
Layout.publicKey('base'),
Layout.rustString('seed'),
BufferLayout.ns64('space'),
Layout.publicKey('programId'),
],
),
},
AssignWithSeed: {
index: 10,
layout: BufferLayout.struct([
layout: BufferLayout.struct<SystemInstructionInputData['AssignWithSeed']>([
BufferLayout.u32('instruction'),
Layout.publicKey('base'),
Layout.rustString('seed'),
@@ -624,12 +717,20 @@ export const SYSTEM_INSTRUCTION_LAYOUTS: {
},
TransferWithSeed: {
index: 11,
layout: BufferLayout.struct([
BufferLayout.u32('instruction'),
BufferLayout.ns64('lamports'),
Layout.rustString('seed'),
Layout.publicKey('programId'),
]),
layout: BufferLayout.struct<SystemInstructionInputData['TransferWithSeed']>(
[
BufferLayout.u32('instruction'),
u64('lamports'),
Layout.rustString('seed'),
Layout.publicKey('programId'),
],
),
},
UpgradeNonceAccount: {
index: 12,
layout: BufferLayout.struct<
SystemInstructionInputData['UpgradeNonceAccount']
>([BufferLayout.u32('instruction')]),
},
});

@@ -681,7 +782,7 @@ export class SystemProgram {
if ('basePubkey' in params) {
const type = SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed;
data = encodeData(type, {
lamports: params.lamports,
lamports: BigInt(params.lamports),
seed: params.seed,
programId: toBuffer(params.programId.toBuffer()),
});
@@ -692,7 +793,7 @@ export class SystemProgram {
];
} else {
const type = SYSTEM_INSTRUCTION_LAYOUTS.Transfer;
data = encodeData(type, {lamports: params.lamports});
data = encodeData(type, {lamports: BigInt(params.lamports)});
keys = [
{pubkey: params.fromPubkey, isSigner: true, isWritable: true},
{pubkey: params.toPubkey, isSigner: false, isWritable: true},
20 changes: 16 additions & 4 deletions src/sysvar.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,14 @@ 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',
);
@@ -16,10 +24,14 @@ export const SYSVAR_REWARDS_PUBKEY = new PublicKey(
'SysvarRewards111111111111111111111111111111',
);

export const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey(
'SysvarStakeHistory1111111111111111111111111',
export const SYSVAR_SLOT_HASHES_PUBKEY = new PublicKey(
'SysvarS1otHashes111111111111111111111111111',
);

export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
'Sysvar1nstructions1111111111111111111111111',
export const SYSVAR_SLOT_HISTORY_PUBKEY = new PublicKey(
'SysvarS1otHistory11111111111111111111111111',
);

export const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey(
'SysvarStakeHistory1111111111111111111111111',
);
10 changes: 10 additions & 0 deletions src/transaction-constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* 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 SIGNATURE_LENGTH_IN_BYTES = 64;
242 changes: 199 additions & 43 deletions src/transaction.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/util/__forks__/react-native/url-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {default} from 'react-native-url-polyfill';
export * from 'react-native-url-polyfill';
43 changes: 43 additions & 0 deletions src/util/bigint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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);
4 changes: 2 additions & 2 deletions src/util/cluster.ts
Original file line number Diff line number Diff line change
@@ -2,12 +2,12 @@ const endpoint = {
http: {
devnet: 'http://api.devnet.solana.com',
testnet: 'http://api.testnet.solana.com',
'mainnet-beta': 'http://api.mainnet-beta.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',
'mainnet-beta': 'https://api.mainnet-beta.solana.com/',
},
};

2 changes: 2 additions & 0 deletions src/util/url.ts → src/util/makeWebsocketUrl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {URL} from './url-impl';

export function makeWebsocketUrl(endpoint: string) {
let url = new URL(endpoint);
const useHttps = url.protocol === 'https:';
60 changes: 53 additions & 7 deletions src/util/send-and-confirm-raw-transaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type {Buffer} from 'buffer';

import {Connection} from '../connection';
import {
BlockheightBasedTransactionConfirmationStrategy,
Connection,
} from '../connection';
import type {TransactionSignature} from '../transaction';
import type {ConfirmOptions} from '../connection';

@@ -11,30 +14,73 @@ import type {ConfirmOptions} from '../connection';
*
* @param {Connection} connection
* @param {Buffer} rawTransaction
* @param {BlockheightBasedTransactionConfirmationStrategy} confirmationStrategy
* @param {ConfirmOptions} [options]
* @returns {Promise<TransactionSignature>}
*/
export async function sendAndConfirmRawTransaction(
connection: Connection,
rawTransaction: Buffer,
confirmationStrategy: BlockheightBasedTransactionConfirmationStrategy,
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:
| BlockheightBasedTransactionConfirmationStrategy
| ConfirmOptions
| undefined,
maybeConfirmOptions?: ConfirmOptions,
): Promise<TransactionSignature> {
let confirmationStrategy:
| BlockheightBasedTransactionConfirmationStrategy
| undefined;
let options: ConfirmOptions | undefined;
if (
confirmationStrategyOrConfirmOptions &&
Object.prototype.hasOwnProperty.call(
confirmationStrategyOrConfirmOptions,
'lastValidBlockHeight',
)
) {
confirmationStrategy =
confirmationStrategyOrConfirmOptions as BlockheightBasedTransactionConfirmationStrategy;
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 status = (
await connection.confirmTransaction(
signature,
options && options.commitment,
)
).value;
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(
27 changes: 21 additions & 6 deletions src/util/send-and-confirm-transaction.ts
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@ export async function sendAndConfirmTransaction(
const sendOptions = options && {
skipPreflight: options.skipPreflight,
preflightCommitment: options.preflightCommitment || options.commitment,
maxRetries: options.maxRetries,
minContextSlot: options.minContextSlot,
};

const signature = await connection.sendTransaction(
@@ -32,12 +34,25 @@ export async function sendAndConfirmTransaction(
sendOptions,
);

const status = (
await connection.confirmTransaction(
signature,
options && options.commitment,
)
).value;
const status =
transaction.recentBlockhash != null &&
transaction.lastValidBlockHeight != null
? (
await connection.confirmTransaction(
{
signature: signature,
blockhash: transaction.recentBlockhash,
lastValidBlockHeight: transaction.lastValidBlockHeight,
},
options && options.commitment,
)
).value
: (
await connection.confirmTransaction(
signature,
options && options.commitment,
)
).value;

if (status.err) {
throw new Error(
35 changes: 35 additions & 0 deletions src/util/tx-expiry-custom-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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',
});
2 changes: 2 additions & 0 deletions src/util/url-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const URL = globalThis.URL;
export const URLSearchParams = globalThis.URLSearchParams;
173 changes: 137 additions & 36 deletions src/vote-account.ts
Original file line number Diff line number Diff line change
@@ -17,24 +17,69 @@ export type Lockout = {
/**
* History of how many credits earned by the end of each epoch
*/
export type EpochCredits = {
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([
const VoteAccountLayout = BufferLayout.struct<VoteAccountData>([
Layout.publicKey('nodePubkey'),
Layout.publicKey('authorizedVoterPubkey'),
Layout.publicKey('authorizedWithdrawerPubkey'),
Layout.publicKey('authorizedWithdrawer'),
BufferLayout.u8('commission'),
BufferLayout.nu64(), // votes.length
BufferLayout.seq(
BufferLayout.seq<Lockout>(
BufferLayout.struct([
BufferLayout.nu64('slot'),
BufferLayout.u32('confirmationCount'),
@@ -44,11 +89,33 @@ const VoteAccountLayout = BufferLayout.struct([
),
BufferLayout.u8('rootSlotValid'),
BufferLayout.nu64('rootSlot'),
BufferLayout.nu64('epoch'),
BufferLayout.nu64('credits'),
BufferLayout.nu64('lastEpochCredits'),
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(
BufferLayout.seq<EpochCredits>(
BufferLayout.struct([
BufferLayout.nu64('epoch'),
BufferLayout.nu64('credits'),
@@ -57,50 +124,51 @@ const VoteAccountLayout = BufferLayout.struct([
BufferLayout.offset(BufferLayout.u32(), -8),
'epochCredits',
),
BufferLayout.struct<BlockTimestamp>(
[BufferLayout.nu64('slot'), BufferLayout.nu64('timestamp')],
'lastTimestamp',
),
]);

type VoteAccountArgs = {
nodePubkey: PublicKey;
authorizedVoterPubkey: PublicKey;
authorizedWithdrawerPubkey: PublicKey;
authorizedWithdrawer: PublicKey;
commission: number;
votes: Array<Lockout>;
rootSlot: number | null;
epoch: number;
credits: number;
lastEpochCredits: number;
epochCredits: Array<EpochCredits>;
votes: Lockout[];
authorizedVoters: AuthorizedVoter[];
priorVoters: PriorVoter[];
epochCredits: EpochCredits[];
lastTimestamp: BlockTimestamp;
};

/**
* VoteAccount class
*/
export class VoteAccount {
nodePubkey: PublicKey;
authorizedVoterPubkey: PublicKey;
authorizedWithdrawerPubkey: PublicKey;
authorizedWithdrawer: PublicKey;
commission: number;
votes: Array<Lockout>;
rootSlot: number | null;
epoch: number;
credits: number;
lastEpochCredits: number;
epochCredits: Array<EpochCredits>;
votes: Lockout[];
authorizedVoters: AuthorizedVoter[];
priorVoters: PriorVoter[];
epochCredits: EpochCredits[];
lastTimestamp: BlockTimestamp;

/**
* @internal
*/
constructor(args: VoteAccountArgs) {
this.nodePubkey = args.nodePubkey;
this.authorizedVoterPubkey = args.authorizedVoterPubkey;
this.authorizedWithdrawerPubkey = args.authorizedWithdrawerPubkey;
this.authorizedWithdrawer = args.authorizedWithdrawer;
this.commission = args.commission;
this.votes = args.votes;
this.rootSlot = args.rootSlot;
this.epoch = args.epoch;
this.credits = args.credits;
this.lastEpochCredits = args.lastEpochCredits;
this.votes = args.votes;
this.authorizedVoters = args.authorizedVoters;
this.priorVoters = args.priorVoters;
this.epochCredits = args.epochCredits;
this.lastTimestamp = args.lastTimestamp;
}

/**
@@ -112,7 +180,8 @@ export class VoteAccount {
static fromAccountData(
buffer: Buffer | Uint8Array | Array<number>,
): VoteAccount {
const va = VoteAccountLayout.decode(toBuffer(buffer), 0);
const versionOffset = 4;
const va = VoteAccountLayout.decode(toBuffer(buffer), versionOffset);

let rootSlot: number | null = va.rootSlot;
if (!va.rootSlotValid) {
@@ -121,15 +190,47 @@ export class VoteAccount {

return new VoteAccount({
nodePubkey: new PublicKey(va.nodePubkey),
authorizedVoterPubkey: new PublicKey(va.authorizedVoterPubkey),
authorizedWithdrawerPubkey: new PublicKey(va.authorizedWithdrawerPubkey),
authorizedWithdrawer: new PublicKey(va.authorizedWithdrawer),
commission: va.commission,
votes: va.votes,
rootSlot,
epoch: va.epoch,
credits: va.credits,
lastEpochCredits: va.lastEpochCredits,
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),
];
}
413 changes: 413 additions & 0 deletions src/vote-program.ts

Large diffs are not rendered by default.

266 changes: 266 additions & 0 deletions test/address-lookup-table-program.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';

import {
Keypair,
AddressLookupTableProgram,
Transaction,
AddressLookupTableInstruction,
Connection,
sendAndConfirmTransaction,
} from '../src';
import {sleep} from '../src/util/sleep';
import {helpers} from './mocks/rpc-http';
import {url} from './url';

use(chaiAsPromised);

describe('AddressLookupTableProgram', () => {
it('createAddressLookupTable', () => {
const recentSlot = 0;
const authorityPubkey = Keypair.generate().publicKey;
const payerPubkey = Keypair.generate().publicKey;
const [instruction] = AddressLookupTableProgram.createLookupTable({
authority: authorityPubkey,
payer: payerPubkey,
recentSlot,
});

const transaction = new Transaction().add(instruction);
const createLutParams = {
authority: authorityPubkey,
payer: payerPubkey,
recentSlot,
};
expect(transaction.instructions).to.have.length(1);
expect(createLutParams).to.eql(
AddressLookupTableInstruction.decodeCreateLookupTable(instruction),
);
});

it('extendLookupTableWithPayer', () => {
const lutAddress = Keypair.generate().publicKey;
const authorityPubkey = Keypair.generate().publicKey;
const payerPubkey = Keypair.generate().publicKey;

const addressesToAdd = [
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey,
];

const instruction = AddressLookupTableProgram.extendLookupTable({
lookupTable: lutAddress,
authority: authorityPubkey,
payer: payerPubkey,
addresses: addressesToAdd,
});
const transaction = new Transaction().add(instruction);
const extendLutParams = {
lookupTable: lutAddress,
authority: authorityPubkey,
payer: payerPubkey,
addresses: addressesToAdd,
};
expect(transaction.instructions).to.have.length(1);
expect(extendLutParams).to.eql(
AddressLookupTableInstruction.decodeExtendLookupTable(instruction),
);
});

it('extendLookupTableWithoutPayer', () => {
const lutAddress = Keypair.generate().publicKey;
const authorityPubkey = Keypair.generate().publicKey;

const addressesToAdd = [
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey,
Keypair.generate().publicKey,
];

const instruction = AddressLookupTableProgram.extendLookupTable({
lookupTable: lutAddress,
authority: authorityPubkey,
addresses: addressesToAdd,
});
const transaction = new Transaction().add(instruction);
const extendLutParams = {
lookupTable: lutAddress,
authority: authorityPubkey,
payer: undefined,
addresses: addressesToAdd,
};
expect(transaction.instructions).to.have.length(1);
expect(extendLutParams).to.eql(
AddressLookupTableInstruction.decodeExtendLookupTable(instruction),
);
});

it('closeLookupTable', () => {
const lutAddress = Keypair.generate().publicKey;
const authorityPubkey = Keypair.generate().publicKey;
const recipientPubkey = Keypair.generate().publicKey;

const instruction = AddressLookupTableProgram.closeLookupTable({
lookupTable: lutAddress,
authority: authorityPubkey,
recipient: recipientPubkey,
});
const transaction = new Transaction().add(instruction);
const closeLutParams = {
lookupTable: lutAddress,
authority: authorityPubkey,
recipient: recipientPubkey,
};
expect(transaction.instructions).to.have.length(1);
expect(closeLutParams).to.eql(
AddressLookupTableInstruction.decodeCloseLookupTable(instruction),
);
});

it('freezeLookupTable', () => {
const lutAddress = Keypair.generate().publicKey;
const authorityPubkey = Keypair.generate().publicKey;

const instruction = AddressLookupTableProgram.freezeLookupTable({
lookupTable: lutAddress,
authority: authorityPubkey,
});
const transaction = new Transaction().add(instruction);
const freezeLutParams = {
lookupTable: lutAddress,
authority: authorityPubkey,
};
expect(transaction.instructions).to.have.length(1);
expect(freezeLutParams).to.eql(
AddressLookupTableInstruction.decodeFreezeLookupTable(instruction),
);
});

it('deactivateLookupTable', () => {
const lutAddress = Keypair.generate().publicKey;
const authorityPubkey = Keypair.generate().publicKey;

const instruction = AddressLookupTableProgram.deactivateLookupTable({
lookupTable: lutAddress,
authority: authorityPubkey,
});

const transaction = new Transaction().add(instruction);
const deactivateLutParams = {
lookupTable: lutAddress,
authority: authorityPubkey,
};
expect(transaction.instructions).to.have.length(1);
expect(deactivateLutParams).to.eql(
AddressLookupTableInstruction.decodeDeactivateLookupTable(instruction),
);
});

if (process.env.TEST_LIVE) {
it('live address lookup table actions', async () => {
const connection = new Connection(url, 'confirmed');
const authority = Keypair.generate();
const payer = Keypair.generate();

const slot = await connection.getSlot('confirmed');
const payerMinBalance =
await connection.getMinimumBalanceForRentExemption(44 * 10);

const [createInstruction, lutAddress] =
AddressLookupTableProgram.createLookupTable({
authority: authority.publicKey,
payer: payer.publicKey,
recentSlot: slot,
});

await helpers.airdrop({
connection,
address: payer.publicKey,
amount: payerMinBalance,
});

await helpers.airdrop({
connection,
address: authority.publicKey,
amount: payerMinBalance,
});

// Creating a new lut
const createLutTransaction = new Transaction();
createLutTransaction.add(createInstruction);
createLutTransaction.feePayer = payer.publicKey;

await sendAndConfirmTransaction(
connection,
createLutTransaction,
[authority, payer],
{preflightCommitment: 'confirmed'},
);

await sleep(500);

// Extending a lut without a payer
await helpers.airdrop({
connection,
address: lutAddress,
amount: payerMinBalance,
});

const extendWithoutPayerInstruction =
AddressLookupTableProgram.extendLookupTable({
lookupTable: lutAddress,
authority: authority.publicKey,
addresses: [...Array(10)].map(() => Keypair.generate().publicKey),
});
const extendLutWithoutPayerTransaction = new Transaction();
extendLutWithoutPayerTransaction.add(extendWithoutPayerInstruction);

await sendAndConfirmTransaction(
connection,
extendLutWithoutPayerTransaction,
[authority],
{preflightCommitment: 'confirmed'},
);

// Extending an lut with a payer
const extendWithPayerInstruction =
AddressLookupTableProgram.extendLookupTable({
lookupTable: lutAddress,
authority: authority.publicKey,
payer: payer.publicKey,
addresses: [...Array(10)].map(() => Keypair.generate().publicKey),
});

const extendLutWithPayerTransaction = new Transaction();
extendLutWithPayerTransaction.add(extendWithPayerInstruction);

await sendAndConfirmTransaction(
connection,
extendLutWithPayerTransaction,
[authority, payer],
{preflightCommitment: 'confirmed'},
);

//deactivating the lut
const deactivateInstruction =
AddressLookupTableProgram.deactivateLookupTable({
lookupTable: lutAddress,
authority: authority.publicKey,
});

const deactivateLutTransaction = new Transaction();
deactivateLutTransaction.add(deactivateInstruction);
await sendAndConfirmTransaction(
connection,
deactivateLutTransaction,
[authority],
{preflightCommitment: 'confirmed'},
);

// After deactivation, LUTs can be closed *only* after a short perioid of time
}).timeout(10 * 1000);
}
});
25 changes: 22 additions & 3 deletions test/bpf-loader.test.ts
Original file line number Diff line number Diff line change
@@ -147,6 +147,25 @@ if (process.env.TEST_LIVE) {
);
});

it('simulate transaction with returnData', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
const {err, returnData} = (
await connection.simulateTransaction(simulatedTransaction, [
payerAccount,
])
).value;
const expectedReturnData = new Uint8Array([1, 2, 3]);
var decodedData = Buffer.from(returnData.data[0], returnData.data[1]);
expect(err).to.be.null;
expect(returnData.programId).to.eql(program.publicKey.toString());
expect(decodedData).to.eql(expectedReturnData);
});

it('deprecated - simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction().add({
keys: [
@@ -176,9 +195,9 @@ if (process.env.TEST_LIVE) {
});

it('simulate transaction without signature verification', async () => {
const simulatedTransaction = new Transaction({
feePayer: payerAccount.publicKey,
}).add({
const simulatedTransaction = new Transaction();
simulatedTransaction.feePayer = payerAccount.publicKey;
simulatedTransaction.add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
220 changes: 220 additions & 0 deletions test/compute-budget.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import {expect, use} from 'chai';
import chaiAsPromised from 'chai-as-promised';

import {
Keypair,
Connection,
LAMPORTS_PER_SOL,
Transaction,
ComputeBudgetProgram,
ComputeBudgetInstruction,
sendAndConfirmTransaction,
} from '../src';
import {helpers} from './mocks/rpc-http';
import {url} from './url';

use(chaiAsPromised);

describe('ComputeBudgetProgram', () => {
it('requestUnits', () => {
const params = {
units: 150000,
additionalFee: LAMPORTS_PER_SOL,
};
const ix = ComputeBudgetProgram.requestUnits(params);
const decodedParams = ComputeBudgetInstruction.decodeRequestUnits(ix);
expect(params).to.eql(decodedParams);
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
'RequestUnits',
);
});

it('requestHeapFrame', () => {
const params = {
bytes: 33 * 1024,
};
const ix = ComputeBudgetProgram.requestHeapFrame(params);
const decodedParams = ComputeBudgetInstruction.decodeRequestHeapFrame(ix);
expect(decodedParams).to.eql(params);
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
'RequestHeapFrame',
);
});

it('setComputeUnitLimit', () => {
const params = {
units: 50_000,
};
const ix = ComputeBudgetProgram.setComputeUnitLimit(params);
const decodedParams =
ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix);
expect(decodedParams).to.eql(params);
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
'SetComputeUnitLimit',
);
});

it('setComputeUnitPrice', () => {
const params = {
microLamports: 100_000,
};
const ix = ComputeBudgetProgram.setComputeUnitPrice(params);
const expectedParams = {
...params,
microLamports: BigInt(params.microLamports),
};
const decodedParams =
ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix);
expect(decodedParams).to.eql(expectedParams);
expect(ComputeBudgetInstruction.decodeInstructionType(ix)).to.eq(
'SetComputeUnitPrice',
);
});

if (process.env.TEST_LIVE) {
it('send live request units ix', async () => {
const connection = new Connection(url, 'confirmed');
const FEE_AMOUNT = LAMPORTS_PER_SOL;
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
const baseAccount = Keypair.generate();
const basePubkey = baseAccount.publicKey;
await helpers.airdrop({
connection,
address: basePubkey,
amount: STARTING_AMOUNT,
});

const additionalFeeTooHighTransaction = new Transaction().add(
ComputeBudgetProgram.requestUnits({
units: 150_000,
additionalFee: STARTING_AMOUNT,
}),
);

await expect(
sendAndConfirmTransaction(
connection,
additionalFeeTooHighTransaction,
[baseAccount],
{preflightCommitment: 'confirmed'},
),
).to.be.rejected;

const validAdditionalFeeTransaction = new Transaction().add(
ComputeBudgetProgram.requestUnits({
units: 150_000,
additionalFee: FEE_AMOUNT,
}),
);
await sendAndConfirmTransaction(
connection,
validAdditionalFeeTransaction,
[baseAccount],
{preflightCommitment: 'confirmed'},
);
expect(await connection.getBalance(baseAccount.publicKey)).to.be.at.most(
STARTING_AMOUNT - FEE_AMOUNT,
);
});

it('send live request heap ix', async () => {
const connection = new Connection(url, 'confirmed');
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
const baseAccount = Keypair.generate();
const basePubkey = baseAccount.publicKey;
await helpers.airdrop({
connection,
address: basePubkey,
amount: STARTING_AMOUNT,
});

async function expectRequestHeapFailure(bytes: number) {
const requestHeapFrameTransaction = new Transaction().add(
ComputeBudgetProgram.requestHeapFrame({bytes}),
);
await expect(
sendAndConfirmTransaction(
connection,
requestHeapFrameTransaction,
[baseAccount],
{preflightCommitment: 'confirmed'},
),
).to.be.rejected;
}
const NOT_MULTIPLE_OF_1024 = 33 * 1024 + 1;
const BELOW_MIN = 1024;
const ABOVE_MAX = 257 * 1024;
await expectRequestHeapFailure(NOT_MULTIPLE_OF_1024);
await expectRequestHeapFailure(BELOW_MIN);
await expectRequestHeapFailure(ABOVE_MAX);

const VALID_BYTES = 33 * 1024;
const requestHeapFrameTransaction = new Transaction().add(
ComputeBudgetProgram.requestHeapFrame({bytes: VALID_BYTES}),
);
await sendAndConfirmTransaction(
connection,
requestHeapFrameTransaction,
[baseAccount],
{preflightCommitment: 'confirmed'},
);
});

it('send live compute unit ixs', async () => {
const connection = new Connection(url, 'confirmed');
const FEE_AMOUNT = LAMPORTS_PER_SOL;
const STARTING_AMOUNT = 2 * LAMPORTS_PER_SOL;
const baseAccount = Keypair.generate();
const basePubkey = baseAccount.publicKey;
await helpers.airdrop({
connection,
address: basePubkey,
amount: STARTING_AMOUNT,
});

// lamport fee = 2B * 1M / 1M = 2 SOL
const prioritizationFeeTooHighTransaction = new Transaction()
.add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 2_000_000_000,
}),
)
.add(
ComputeBudgetProgram.setComputeUnitLimit({
units: 1_000_000,
}),
);

await expect(
sendAndConfirmTransaction(
connection,
prioritizationFeeTooHighTransaction,
[baseAccount],
{preflightCommitment: 'confirmed'},
),
).to.be.rejected;

// lamport fee = 1B * 1M / 1M = 1 SOL
const validPrioritizationFeeTransaction = new Transaction()
.add(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 1_000_000_000,
}),
)
.add(
ComputeBudgetProgram.setComputeUnitLimit({
units: 1_000_000,
}),
);
await sendAndConfirmTransaction(
connection,
validPrioritizationFeeTransaction,
[baseAccount],
{preflightCommitment: 'confirmed'},
);
expect(await connection.getBalance(baseAccount.publicKey)).to.be.at.most(
STARTING_AMOUNT - FEE_AMOUNT,
);
});
}
});
Loading