@bbachain/web3.js
Version:
BBAChain Javascript API
1,618 lines (1,354 loc) • 294 kB
JavaScript
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