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 { getU64Codec, getU64Encoder } from '@solana/codecs-numbers'; 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 }); } } function u64(property) { const layout = blob(8 /* bytes */, property); const decode = layout.decode.bind(layout); const encode = layout.encode.bind(layout); const bigIntLayout = layout; const codec = getU64Codec(); bigIntLayout.decode = (buffer, offset) => { const src = decode(buffer, offset); return codec.decode(src); }; bigIntLayout.encode = (bigInt, buffer, offset) => { const src = codec.encode(bigInt); return encode(src, buffer, offset); }; return bigIntLayout; } /** * 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()), costUnits: 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()), costUnits: 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 "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 { context, value: { blockhash } } = await this.getLatestBlockhashAndContext(commitment); const feeCalculator = { get lamportsPerSignature() { throw new Error('The capability to fetch `lamportsPerSignature` using the `getRecentBlockhash` API is ' + 'no longer offered by the network. Use the `getFeeForMessage` API to obtain the fee ' + 'for a given message.'); }, toJSON() { return {}; } }; return { context, value: { blockhash, feeCalculator } }; } /** * 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('getBlock', 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('getBlock', 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('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; 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('getTransaction', 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: '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 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(), getU64Encoder().encode(params.recentSlot)], 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