UNPKG

@exodus/solana-web3.js

Version:
1,663 lines (1,387 loc) 152 kB
import { Buffer } from 'buffer'; import { randomBytes } from '@exodus/crypto/randomBytes'; import * as curve25519 from '@exodus/crypto/curve25519'; import BN from 'bn.js'; import bs58 from 'bs58'; import { hashSync } from '@exodus/crypto/hash'; import { serialize, deserialize, deserializeUnchecked } from 'borsh'; import * as BufferLayout from '@exodus/solana-buffer-layout'; import { blob } from '@exodus/solana-buffer-layout'; import { type, string, optional, assert as assert$1 } from 'superstruct'; import { toBigIntLE, toBufferLE } from '@exodus/bigint-buffer'; // @ts-ignore /** * 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/ */ const generatePrivateKey = () => randomBytes(32); const generateKeypair = () => { const privateKey = generatePrivateKey(); const publicKey = curve25519.edwardsToPublicSync({ privateKey }); const secretKey = new Uint8Array(64); secretKey.set(privateKey, 0); secretKey.set(publicKey, 32); return { publicKey, secretKey }; }; // Input: 32-byte seed const getPublicKey = s => curve25519.edwardsToPublicSync({ privateKey: s }); const sign = (message, secretKey) => curve25519.signDetachedSync({ message, privateKey: secretKey.subarray(0, 32) }); function verify(signature, message, publicKey) { return curve25519.verifyDetachedSync({ signature, message, publicKey }); } // Check that a pubkey is on the curve. function isOnCurve(p) { try { curve25519.edwardsToMontgomeryPublicSync({ publicKey: p }); // checks public key validity return true; } catch { return false; } } 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 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(); /** * 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 */ function isPublicKeyData(value) { return value._bn !== undefined; } // local counter used by PublicKey.unique() let uniquePublicKeyCounter = 1; /** * A public key */ class PublicKey extends Struct { /** @internal */ /** * Create a new PublicKey object * @param value ed25519 public key as buffer or base-58 encoded string */ constructor(value) { super({}); 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() > 32) { throw new Error(`Invalid public key input`); } } } /** * Returns a unique PublicKey for tests and benchmarks using acounter */ static unique() { const key = new PublicKey(uniquePublicKeyCounter); uniquePublicKeyCounter += 1; return new PublicKey(key.toBuffer()); } /** * Default public key value. (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 */ toBytes() { return this.toBuffer(); } /** * Return the Buffer representation of the public key */ 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; } /** * 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 = hashSync('sha256', buffer, 'uint8'); 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 = hashSync('sha256', buffer, 'uint8'); 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 */ /* 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 */ 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.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 { /** @internal */ /** @internal */ /** * 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) { this._publicKey = void 0; 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'); 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 { /** The maximum number of slots in each epoch */ /** The number of slots before beginning of an epoch to calculate a leader schedule for that epoch */ /** Indicates whether epochs start short and grow */ /** The first epoch with `slotsPerEpoch` slots */ /** The first slot of `firstNormalEpoch` */ constructor(slotsPerEpoch, leaderScheduleSlotOffset, warmup, firstNormalEpoch, firstNormalSlot) { this.slotsPerEpoch = void 0; this.leaderScheduleSlotOffset = void 0; this.warmup = void 0; this.firstNormalEpoch = void 0; 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; } } } class SendTransactionError extends Error { constructor(message, logs) { super(message); this.logs = void 0; this.logs = logs; } } // 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'; } } /** * 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. */ /** * 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 keypair ed25519 keypair */ constructor(keypair) { this._keypair = void 0; this._keypair = keypair !== null && keypair !== void 0 ? keypair : generateKeypair(); } /** * Generate a new random 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 */ 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 */ 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 */ get publicKey() { return new PublicKey(this._keypair.publicKey); } /** * The raw secret key for this keypair */ get secretKey() { return new Uint8Array(this._keypair.secretKey); } } /** * 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 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) { 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 64bit unsigned value */ const uint64 = (property = 'uint64') => { return BufferLayout.blob(8, 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 || (keyMeta.isSigner = accountMeta.isSigner); keyMeta.isWritable || (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]; } } /** * An instruction to execute by a program * * @property {number} programIdIndex * @property {number[]} accounts * @property {string} data */ /** * 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 = byteArray.shift(); if (numRequiredSignatures !== (numRequiredSignatures & VERSION_PREFIX_MASK)) { throw new Error('Versioned messages must be deserialized with VersionedMessage.deserialize()'); } const numReadonlySignedAccounts = byteArray.shift(); const numReadonlyUnsignedAccounts = byteArray.shift(); const accountCount = decodeLength(byteArray); let accountKeys = []; for (let i = 0; i < accountCount; i++) { const account = byteArray.slice(0, PUBLIC_KEY_LENGTH); byteArray = byteArray.slice(PUBLIC_KEY_LENGTH); accountKeys.push(new PublicKey(Buffer.from(account))); } const recentBlockhash = byteArray.slice(0, PUBLIC_KEY_LENGTH); byteArray = byteArray.slice(PUBLIC_KEY_LENGTH); const instructionCount = decodeLength(byteArray); let instructions = []; for (let i = 0; i < instructionCount; i++) { const programIdIndex = byteArray.shift(); const accountCount = decodeLength(byteArray); const accounts = byteArray.slice(0, accountCount); byteArray = byteArray.slice(accountCount); const dataLength = decodeLength(byteArray); const dataSlice = byteArray.slice(0, dataLength); const data = bs58.encode(Buffer.from(dataSlice)); byteArray = byteArray.slice(dataLength); instructions.push({ programIdIndex, accounts, data }); } const messageArgs = { header: { numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts }, recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)), accountKeys, instructions }; return new Message(messageArgs); } } /** * 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 = byteArray.shift(); 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: byteArray.shift(), numReadonlySignedAccounts: byteArray.shift(), numReadonlyUnsignedAccounts: byteArray.shift() }; const staticAccountKeys = []; const staticAccountKeysLength = decodeLength(byteArray); for (let i = 0; i < staticAccountKeysLength; i++) { staticAccountKeys.push(new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH))); } const recentBlockhash = bs58.encode(byteArray.splice(0, PUBLIC_KEY_LENGTH)); const instructionCount = decodeLength(byteArray); const compiledInstructions = []; for (let i = 0; i < instructionCount; i++) { const programIdIndex = byteArray.shift(); const accountKeyIndexesLength = decodeLength(byteArray); const accountKeyIndexes = byteArray.splice(0, accountKeyIndexesLength); const dataLength = decodeLength(byteArray); const data = new Uint8Array(byteArray.splice(0, dataLength)); compiledInstructions.push({ programIdIndex, accountKeyIndexes, data }); } const addressTableLookupsCount = decodeLength(byteArray); const addressTableLookups = []; for (let i = 0; i < addressTableLookupsCount; i++) { const accountKey = new PublicKey(byteArray.splice(0, PUBLIC_KEY_LENGTH)); const writableIndexesLength = decodeLength(byteArray); const writableIndexes = byteArray.splice(0, writableIndexesLength); const readonlyIndexesLength = decodeLength(byteArray); const readonlyIndexes = byteArray.splice(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`); } } }; /** * Transaction signature as base-58 encoded string */ let TransactionStatus; /** * Default (empty) signature */ (function (TransactionStatus) { TransactionStatus[TransactionStatus["BLOCKHEIGHT_EXCEEDED"] = 0] = "BLOCKHEIGHT_EXCEEDED"; TransactionStatus[TransactionStatus["PROCESSED"] = 1] = "PROCESSED"; TransactionStatus[TransactionStatus["TIMED_OUT"] = 2] = "TIMED_OUT"; })(TransactionStatus || (TransactionStatus = {})); const DEFAULT_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH_IN_BYTES).fill(0); /** * Account metadata used to define instructions */ /** * Transaction Instruction class */ class TransactionInstruction { /** * Public keys to include in this transaction * Boolean represents whether this pubkey needs to sign the transaction */ /** * Program Id to execute */ /** * Program input */ constructor(opts) { this.keys = void 0; this.programId = void 0; 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 */ /** * Transaction class */ class Transaction { /** * Signatures for the transaction. Typically created by invoking the * `sign()` method */ /** * The first (payer) Transaction signature */ get signature() { if (this.signatures.length > 0) { return this.signatures[0].signature; } return null; } /** * The transaction fee payer */ /** * Construct an empty Transaction */ constructor(opts) { this.signatures = []; this.feePayer = void 0; this.instructions = []; this.recentBlockhash = void 0; this.lastValidBlockHeight = void 0; this.nonceInfo = void 0; this._message = void 0; 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, '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.f