UNPKG

@bbachain/web3.js

Version:

BBAChain Javascript API

1,618 lines (1,354 loc) 294 kB
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 '@bbachain/buffer-layout'; import { blob } from '@bbachain/buffer-layout'; import { toBigIntLE, toBufferLE } from 'bigint-buffer'; import { coerce, instance, string, tuple, literal, unknown, type, number, array, nullable, optional, boolean, record, union, create, any, assert as assert$1 } from 'superstruct'; import RpcClient from 'jayson/lib/client/browser'; import RpcWebSocketCommonClient from 'rpc-websockets/dist/lib/client'; import createRpc from 'rpc-websockets/dist/lib/client/websocket.browser'; 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/ */ 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 Struct { constructor(properties) { Object.assign(this, properties); } encode() { return Buffer.from(serialize(BBACHAIN_SCHEMA, this)); } static decode(data) { return deserialize(BBACHAIN_SCHEMA, this, data); } static decodeUnchecked(data) { return deserializeUnchecked(BBACHAIN_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 BBACHAIN_SCHEMA = new Map(); let _Symbol$toStringTag; /** * 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 */ _Symbol$toStringTag = Symbol.toStringTag; 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() > 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.default = new PublicKey('11111111111111111111111111111111'); BBACHAIN_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'); /** * 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 BBAChain 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]; } } /** * 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["NONCE_INVALID"] = 3] = "NONCE_INVALID"; })(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.minNonceContextSlot = 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, '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 */ 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. return x.pubkey.toBase58().localeCompare(y.pubkey.toBase58()); }); // Move fee payer to the front const feePayerIndex = uniqueMetas.findIndex(x => { return x.pubkey.equals(feePayer); }); if (feePayerIndex > -1) { const [payerMeta] = uniqueMetas.splice(feePayerIndex, 1); payerMeta.isSigner = true; payerMeta.isWritable = true; uniqueMetas.unshift(payerMeta); } else { uniqueMetas.unshift({ pubkey: feePayer, isSigner: true, isWritable: true }); } // Disallow unknown signers for (const signature of this.signatures) { const uniqueIndex = uniqueMetas.findIndex(x => { return x.pubkey.equals(signature.publicKey); }); if (uniqueIndex > -1) { if (!uniqueMetas[uniqueIndex].isSigner) { uniqueMetas[uniqueIndex].isSigner = true; console.warn('Transaction references a signature that is unnecessary, ' + 'only the fee payer and instruction signer accounts should sign a transaction. ' + 'This behavior is deprecated and will throw an error in the next major version release.'); } } else { throw new Error(`unknown signer: ${signature.publicKey.toString()}`); } } let numRequiredSignatures = 0; let numReadonlySignedAccounts = 0; let numReadonlyUnsignedAccounts = 0; // Split out signing from non-signing keys and count header values const signedKeys = []; 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