feat: expose transaction message publicly
This commit is contained in:
parent
cd439bed0d
commit
14a41bc47b
|
@ -352,21 +352,54 @@ declare module '@solana/web3.js' {
|
||||||
fields: Record<string, object>,
|
fields: Record<string, object>,
|
||||||
): Buffer;
|
): Buffer;
|
||||||
|
|
||||||
|
// === src/message.js ===
|
||||||
|
export type MessageHeader = {
|
||||||
|
numRequiredSignatures: number;
|
||||||
|
numReadonlySignedAccounts: number;
|
||||||
|
numReadonlyUnsignedAccounts: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CompiledInstruction = {
|
||||||
|
programIdIndex: number;
|
||||||
|
accounts: number[];
|
||||||
|
data: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MessageArgs = {
|
||||||
|
header: MessageHeader;
|
||||||
|
accountKeys: PublicKey[];
|
||||||
|
recentBlockhash: Blockhash;
|
||||||
|
instructions: CompiledInstruction[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Message {
|
||||||
|
header: MessageHeader;
|
||||||
|
accountKeys: PublicKey[];
|
||||||
|
recentBlockhash: Blockhash;
|
||||||
|
instructions: CompiledInstruction[];
|
||||||
|
|
||||||
|
constructor(args: MessageArgs);
|
||||||
|
isAccountWritable(account: PublicKey): boolean;
|
||||||
|
serialize(): Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
// === src/transaction.js ===
|
// === src/transaction.js ===
|
||||||
export type TransactionSignature = string;
|
export type TransactionSignature = string;
|
||||||
|
|
||||||
|
export type AccountMeta = {
|
||||||
|
pubkey: PublicKey;
|
||||||
|
isSigner: boolean;
|
||||||
|
isWritable: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type TransactionInstructionCtorFields = {
|
export type TransactionInstructionCtorFields = {
|
||||||
keys?: Array<{pubkey: PublicKey; isSigner: boolean; isWritable: boolean}>;
|
keys?: Array<AccountMeta>;
|
||||||
programId?: PublicKey;
|
programId?: PublicKey;
|
||||||
data?: Buffer;
|
data?: Buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class TransactionInstruction {
|
export class TransactionInstruction {
|
||||||
keys: Array<{
|
keys: Array<AccountMeta>;
|
||||||
pubkey: PublicKey;
|
|
||||||
isSigner: boolean;
|
|
||||||
isWritable: boolean;
|
|
||||||
}>;
|
|
||||||
programId: PublicKey;
|
programId: PublicKey;
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
|
|
||||||
|
@ -403,6 +436,7 @@ declare module '@solana/web3.js' {
|
||||||
Transaction | TransactionInstruction | TransactionInstructionCtorFields
|
Transaction | TransactionInstruction | TransactionInstructionCtorFields
|
||||||
>
|
>
|
||||||
): Transaction;
|
): Transaction;
|
||||||
|
compileMessage(): Message;
|
||||||
serializeMessage(): Buffer;
|
serializeMessage(): Buffer;
|
||||||
sign(...signers: Array<Account>): void;
|
sign(...signers: Array<Account>): void;
|
||||||
signPartial(...partialSigners: Array<PublicKey | Account>): void;
|
signPartial(...partialSigners: Array<PublicKey | Account>): void;
|
||||||
|
|
|
@ -362,17 +362,54 @@ declare module '@solana/web3.js' {
|
||||||
|
|
||||||
declare export function encodeData(type: InstructionType, fields: {}): Buffer;
|
declare export function encodeData(type: InstructionType, fields: {}): Buffer;
|
||||||
|
|
||||||
|
// === src/message.js ===
|
||||||
|
declare export type MessageHeader = {
|
||||||
|
numRequiredSignatures: number,
|
||||||
|
numReadonlySignedAccounts: number,
|
||||||
|
numReadonlyUnsignedAccounts: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
declare export type CompiledInstruction = {
|
||||||
|
programIdIndex: number,
|
||||||
|
accounts: number[],
|
||||||
|
data: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
declare export type MessageArgs = {
|
||||||
|
header: MessageHeader,
|
||||||
|
accountKeys: PublicKey[],
|
||||||
|
recentBlockhash: Blockhash,
|
||||||
|
instructions: CompiledInstruction[],
|
||||||
|
};
|
||||||
|
|
||||||
|
declare export class Message {
|
||||||
|
header: MessageHeader;
|
||||||
|
accountKeys: PublicKey[];
|
||||||
|
recentBlockhash: Blockhash;
|
||||||
|
instructions: CompiledInstruction[];
|
||||||
|
|
||||||
|
constructor(args: MessageArgs): Message;
|
||||||
|
isAccountWritable(account: PublicKey): boolean;
|
||||||
|
serialize(): Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
// === src/transaction.js ===
|
// === src/transaction.js ===
|
||||||
declare export type TransactionSignature = string;
|
declare export type TransactionSignature = string;
|
||||||
|
|
||||||
|
declare export type AccountMeta = {
|
||||||
|
pubkey: PublicKey,
|
||||||
|
isSigner: boolean,
|
||||||
|
isWritable: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
declare type TransactionInstructionCtorFields = {|
|
declare type TransactionInstructionCtorFields = {|
|
||||||
keys: ?Array<{pubkey: PublicKey, isSigner: boolean, isWritable: boolean}>,
|
keys: ?Array<AccountMeta>,
|
||||||
programId?: PublicKey,
|
programId?: PublicKey,
|
||||||
data?: Buffer,
|
data?: Buffer,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
declare export class TransactionInstruction {
|
declare export class TransactionInstruction {
|
||||||
keys: Array<{pubkey: PublicKey, isSigner: boolean, isWritable: boolean}>;
|
keys: Array<AccountMeta>;
|
||||||
programId: PublicKey;
|
programId: PublicKey;
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
|
|
||||||
|
@ -411,6 +448,7 @@ declare module '@solana/web3.js' {
|
||||||
Transaction | TransactionInstruction | TransactionInstructionCtorFields,
|
Transaction | TransactionInstruction | TransactionInstructionCtorFields,
|
||||||
>
|
>
|
||||||
): Transaction;
|
): Transaction;
|
||||||
|
compileMessage(): Message;
|
||||||
serializeMessage(): Buffer;
|
serializeMessage(): Buffer;
|
||||||
sign(...signers: Array<Account>): void;
|
sign(...signers: Array<Account>): void;
|
||||||
signPartial(...partialSigners: Array<PublicKey | Account>): void;
|
signPartial(...partialSigners: Array<PublicKey | Account>): void;
|
||||||
|
|
|
@ -4,6 +4,7 @@ export {BpfLoader} from './bpf-loader';
|
||||||
export {BudgetProgram} from './budget-program';
|
export {BudgetProgram} from './budget-program';
|
||||||
export {Connection} from './connection';
|
export {Connection} from './connection';
|
||||||
export {Loader} from './loader';
|
export {Loader} from './loader';
|
||||||
|
export {Message} from './message';
|
||||||
export {NonceAccount, NONCE_ACCOUNT_LENGTH} from './nonce-account';
|
export {NonceAccount, NONCE_ACCOUNT_LENGTH} from './nonce-account';
|
||||||
export {PublicKey} from './publickey';
|
export {PublicKey} from './publickey';
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import bs58 from 'bs58';
|
||||||
|
import * as BufferLayout from 'buffer-layout';
|
||||||
|
|
||||||
|
import {PublicKey} from './publickey';
|
||||||
|
import type {Blockhash} from './blockhash';
|
||||||
|
import * as Layout from './layout';
|
||||||
|
import {PACKET_DATA_SIZE} from './transaction';
|
||||||
|
import * as shortvec from './util/shortvec-encoding';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message header, identifying signed and read-only account
|
||||||
|
*
|
||||||
|
* @typedef {Object} MessageHeader
|
||||||
|
* @property {number} numRequiredSignatures The number of signatures required for this message to be considered valid
|
||||||
|
* @property {number} numReadonlySignedAccounts: The last `numReadonlySignedAccounts` of the signed keys are read-only accounts
|
||||||
|
* @property {number} numReadonlyUnsignedAccounts The last `numReadonlySignedAccounts` of the unsigned keys are read-only accounts
|
||||||
|
*/
|
||||||
|
export type MessageHeader = {
|
||||||
|
numRequiredSignatures: number,
|
||||||
|
numReadonlySignedAccounts: number,
|
||||||
|
numReadonlyUnsignedAccounts: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instruction to execute by a program
|
||||||
|
*
|
||||||
|
* @typedef {Object} CompiledInstruction
|
||||||
|
* @property {number} programIdIndex Index into the transaction keys array indicating the program account that executes this instruction
|
||||||
|
* @property {number[]} accounts Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
||||||
|
* @property {string} data The program input data encoded as base 58
|
||||||
|
*/
|
||||||
|
export type CompiledInstruction = {
|
||||||
|
programIdIndex: number,
|
||||||
|
accounts: number[],
|
||||||
|
data: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message constructor arguments
|
||||||
|
*
|
||||||
|
* @typedef {Object} MessageArgs
|
||||||
|
* @property {MessageHeader} header The message header, identifying signed and read-only `accountKeys`
|
||||||
|
* @property {PublicKey[]} accounts All the account keys used by this transaction
|
||||||
|
* @property {Blockhash} recentBlockhash The hash of a recent ledger block
|
||||||
|
* @property {CompiledInstruction[]} instructions Instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
|
||||||
|
*/
|
||||||
|
type MessageArgs = {
|
||||||
|
header: MessageHeader,
|
||||||
|
accountKeys: PublicKey[],
|
||||||
|
recentBlockhash: Blockhash,
|
||||||
|
instructions: CompiledInstruction[],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of instructions to be processed atomically
|
||||||
|
*/
|
||||||
|
export class Message {
|
||||||
|
header: MessageHeader;
|
||||||
|
accountKeys: PublicKey[];
|
||||||
|
recentBlockhash: Blockhash;
|
||||||
|
instructions: CompiledInstruction[];
|
||||||
|
|
||||||
|
constructor(args: MessageArgs) {
|
||||||
|
this.header = args.header;
|
||||||
|
this.accountKeys = args.accountKeys;
|
||||||
|
this.recentBlockhash = args.recentBlockhash;
|
||||||
|
this.instructions = args.instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAccountWritable(index: number): boolean {
|
||||||
|
return (
|
||||||
|
index <
|
||||||
|
this.header.numRequiredSignatures -
|
||||||
|
this.header.numReadonlySignedAccounts ||
|
||||||
|
(index >= this.header.numRequiredSignatures &&
|
||||||
|
index <
|
||||||
|
this.accountKeys.length - this.header.numReadonlyUnsignedAccounts)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): Buffer {
|
||||||
|
const numKeys = this.accountKeys.length;
|
||||||
|
|
||||||
|
let keyCount = [];
|
||||||
|
shortvec.encodeLength(keyCount, numKeys);
|
||||||
|
|
||||||
|
const instructions = this.instructions.map(instruction => {
|
||||||
|
const {accounts, programIdIndex} = instruction;
|
||||||
|
const data = bs58.decode(instruction.data);
|
||||||
|
|
||||||
|
let keyIndicesCount = [];
|
||||||
|
shortvec.encodeLength(keyIndicesCount, accounts.length);
|
||||||
|
|
||||||
|
let dataCount = [];
|
||||||
|
shortvec.encodeLength(dataCount, data.length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
programIdIndex,
|
||||||
|
keyIndicesCount: Buffer.from(keyIndicesCount),
|
||||||
|
keyIndices: Buffer.from(accounts),
|
||||||
|
dataLength: Buffer.from(dataCount),
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let instructionCount = [];
|
||||||
|
shortvec.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(Layout.publicKey('key'), numKeys, 'keys'),
|
||||||
|
Layout.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 => key.toBuffer()),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import invariant from 'assert';
|
import invariant from 'assert';
|
||||||
import * as BufferLayout from 'buffer-layout';
|
|
||||||
import nacl from 'tweetnacl';
|
import nacl from 'tweetnacl';
|
||||||
import bs58 from 'bs58';
|
import bs58 from 'bs58';
|
||||||
|
|
||||||
import * as Layout from './layout';
|
import type {CompiledInstruction} from './message';
|
||||||
|
import {Message} from './message';
|
||||||
import {PublicKey} from './publickey';
|
import {PublicKey} from './publickey';
|
||||||
import {Account} from './account';
|
import {Account} from './account';
|
||||||
import * as shortvec from './util/shortvec-encoding';
|
import * as shortvec from './util/shortvec-encoding';
|
||||||
|
@ -35,6 +35,20 @@ export const PACKET_DATA_SIZE = 1280 - 40 - 8;
|
||||||
const PUBKEY_LENGTH = 32;
|
const PUBKEY_LENGTH = 32;
|
||||||
const SIGNATURE_LENGTH = 64;
|
const SIGNATURE_LENGTH = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account metadata used to define instructions
|
||||||
|
*
|
||||||
|
* @typedef {Object} AccountMeta
|
||||||
|
* @property {PublicKey} pubkey An account's public key
|
||||||
|
* @property {boolean} isSigner True if an instruction requires a transaction signature matching `pubkey`
|
||||||
|
* @property {boolean} isWritable True if the `pubkey` can be loaded as a read-write account.
|
||||||
|
*/
|
||||||
|
export type AccountMeta = {
|
||||||
|
pubkey: PublicKey,
|
||||||
|
isSigner: boolean,
|
||||||
|
isWritable: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of TransactionInstruction object fields that may be initialized at construction
|
* List of TransactionInstruction object fields that may be initialized at construction
|
||||||
*
|
*
|
||||||
|
@ -44,7 +58,7 @@ const SIGNATURE_LENGTH = 64;
|
||||||
* @property {?Buffer} data
|
* @property {?Buffer} data
|
||||||
*/
|
*/
|
||||||
export type TransactionInstructionCtorFields = {|
|
export type TransactionInstructionCtorFields = {|
|
||||||
keys?: Array<{pubkey: PublicKey, isSigner: boolean, isWritable: boolean}>,
|
keys?: Array<AccountMeta>,
|
||||||
programId?: PublicKey,
|
programId?: PublicKey,
|
||||||
data?: Buffer,
|
data?: Buffer,
|
||||||
|};
|
|};
|
||||||
|
@ -57,11 +71,7 @@ export class TransactionInstruction {
|
||||||
* Public keys to include in this transaction
|
* Public keys to include in this transaction
|
||||||
* Boolean represents whether this pubkey needs to sign the transaction
|
* Boolean represents whether this pubkey needs to sign the transaction
|
||||||
*/
|
*/
|
||||||
keys: Array<{
|
keys: Array<AccountMeta> = [];
|
||||||
pubkey: PublicKey,
|
|
||||||
isSigner: boolean,
|
|
||||||
isWritable: boolean,
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program Id to execute
|
* Program Id to execute
|
||||||
|
@ -90,8 +100,8 @@ type SignaturePubkeyPair = {|
|
||||||
* List of Transaction object fields that may be initialized at construction
|
* List of Transaction object fields that may be initialized at construction
|
||||||
*
|
*
|
||||||
* @typedef {Object} TransactionCtorFields
|
* @typedef {Object} TransactionCtorFields
|
||||||
* @property (?recentBlockhash} A recent block hash
|
* @property {?Blockhash} recentBlockhash A recent blockhash
|
||||||
* @property (?signatures} One or more signatures
|
* @property {?Array<SignaturePubkeyPair>} signatures One or more signatures
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
type TransactionCtorFields = {|
|
type TransactionCtorFields = {|
|
||||||
|
@ -104,8 +114,8 @@ type TransactionCtorFields = {|
|
||||||
* NonceInformation to be used to build a Transaction.
|
* NonceInformation to be used to build a Transaction.
|
||||||
*
|
*
|
||||||
* @typedef {Object} NonceInformation
|
* @typedef {Object} NonceInformation
|
||||||
* @property {nonce} The current Nonce blockhash
|
* @property {Blockhash} nonce The current Nonce blockhash
|
||||||
* @property {nonceInstruction} The AdvanceNonceAccount Instruction
|
* @property {TransactionInstruction} nonceInstruction AdvanceNonceAccount Instruction
|
||||||
*/
|
*/
|
||||||
type NonceInformation = {|
|
type NonceInformation = {|
|
||||||
nonce: Blockhash,
|
nonce: Blockhash,
|
||||||
|
@ -180,9 +190,9 @@ export class Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a buffer of the Transaction data that need to be covered by signatures
|
* Compile transaction data
|
||||||
*/
|
*/
|
||||||
serializeMessage(): Buffer {
|
compileMessage(): Message {
|
||||||
const {nonceInfo} = this;
|
const {nonceInfo} = this;
|
||||||
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
|
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
|
||||||
this.recentBlockhash = nonceInfo.nonce;
|
this.recentBlockhash = nonceInfo.nonce;
|
||||||
|
@ -197,16 +207,15 @@ export class Transaction {
|
||||||
throw new Error('No instructions provided');
|
throw new Error('No instructions provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = this.signatures.map(({publicKey}) => publicKey.toString());
|
|
||||||
let numReadonlySignedAccounts = 0;
|
let numReadonlySignedAccounts = 0;
|
||||||
let numReadonlyUnsignedAccounts = 0;
|
let numReadonlyUnsignedAccounts = 0;
|
||||||
|
|
||||||
const programIds = [];
|
const keys = this.signatures.map(({publicKey}) => publicKey.toString());
|
||||||
|
const programIds: string[] = [];
|
||||||
const allKeys = [];
|
const accountMetas: AccountMeta[] = [];
|
||||||
this.instructions.forEach(instruction => {
|
this.instructions.forEach(instruction => {
|
||||||
instruction.keys.forEach(keySignerPair => {
|
instruction.keys.forEach(accountMeta => {
|
||||||
allKeys.push(keySignerPair);
|
accountMetas.push(accountMeta);
|
||||||
});
|
});
|
||||||
|
|
||||||
const programId = instruction.programId.toString();
|
const programId = instruction.programId.toString();
|
||||||
|
@ -215,31 +224,29 @@ export class Transaction {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
allKeys.sort(function (x, y) {
|
accountMetas.sort(function (x, y) {
|
||||||
const checkSigner = x.isSigner === y.isSigner ? 0 : x.isSigner ? -1 : 1;
|
const checkSigner = x.isSigner === y.isSigner ? 0 : x.isSigner ? -1 : 1;
|
||||||
const checkWritable =
|
const checkWritable =
|
||||||
x.isWritable === y.isWritable ? 0 : x.isWritable ? -1 : 1;
|
x.isWritable === y.isWritable ? 0 : x.isWritable ? -1 : 1;
|
||||||
return checkSigner || checkWritable;
|
return checkSigner || checkWritable;
|
||||||
});
|
});
|
||||||
|
|
||||||
allKeys.forEach(keySignerPair => {
|
accountMetas.forEach(({pubkey, isSigner, isWritable}) => {
|
||||||
const keyStr = keySignerPair.pubkey.toString();
|
const keyStr = pubkey.toString();
|
||||||
if (!keys.includes(keyStr)) {
|
if (!keys.includes(keyStr)) {
|
||||||
if (keySignerPair.isSigner) {
|
keys.push(keyStr);
|
||||||
|
if (isSigner) {
|
||||||
this.signatures.push({
|
this.signatures.push({
|
||||||
signature: null,
|
signature: null,
|
||||||
publicKey: keySignerPair.pubkey,
|
publicKey: pubkey,
|
||||||
});
|
});
|
||||||
if (!keySignerPair.isWritable) {
|
if (!isWritable) {
|
||||||
numReadonlySignedAccounts += 1;
|
numReadonlySignedAccounts += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!isWritable) {
|
||||||
if (!keySignerPair.isWritable) {
|
|
||||||
numReadonlyUnsignedAccounts += 1;
|
numReadonlyUnsignedAccounts += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keys.push(keyStr);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
programIds.forEach(programId => {
|
programIds.forEach(programId => {
|
||||||
|
@ -249,92 +256,41 @@ export class Transaction {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let keyCount = [];
|
const instructions: CompiledInstruction[] = this.instructions.map(
|
||||||
shortvec.encodeLength(keyCount, keys.length);
|
instruction => {
|
||||||
|
|
||||||
const instructions = this.instructions.map(instruction => {
|
|
||||||
const {data, programId} = instruction;
|
const {data, programId} = instruction;
|
||||||
let keyIndicesCount = [];
|
|
||||||
shortvec.encodeLength(keyIndicesCount, instruction.keys.length);
|
|
||||||
let dataCount = [];
|
|
||||||
shortvec.encodeLength(dataCount, instruction.data.length);
|
|
||||||
return {
|
return {
|
||||||
programIdIndex: keys.indexOf(programId.toString()),
|
programIdIndex: keys.indexOf(programId.toString()),
|
||||||
keyIndicesCount: Buffer.from(keyIndicesCount),
|
accounts: instruction.keys.map(keyObj =>
|
||||||
keyIndices: Buffer.from(
|
|
||||||
instruction.keys.map(keyObj =>
|
|
||||||
keys.indexOf(keyObj.pubkey.toString()),
|
keys.indexOf(keyObj.pubkey.toString()),
|
||||||
),
|
),
|
||||||
),
|
data: bs58.encode(data),
|
||||||
dataLength: Buffer.from(dataCount),
|
|
||||||
data,
|
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
instructions.forEach(instruction => {
|
instructions.forEach(instruction => {
|
||||||
invariant(instruction.programIdIndex >= 0);
|
invariant(instruction.programIdIndex >= 0);
|
||||||
instruction.keyIndices.forEach(keyIndex => invariant(keyIndex >= 0));
|
instruction.accounts.forEach(keyIndex => invariant(keyIndex >= 0));
|
||||||
});
|
});
|
||||||
|
|
||||||
let instructionCount = [];
|
return new Message({
|
||||||
shortvec.encodeLength(instructionCount, instructions.length);
|
header: {
|
||||||
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE);
|
numRequiredSignatures: this.signatures.length,
|
||||||
Buffer.from(instructionCount).copy(instructionBuffer);
|
numReadonlySignedAccounts,
|
||||||
let instructionBufferLength = instructionCount.length;
|
numReadonlyUnsignedAccounts,
|
||||||
|
},
|
||||||
instructions.forEach(instruction => {
|
accountKeys: keys.map(k => new PublicKey(k)),
|
||||||
const instructionLayout = BufferLayout.struct([
|
recentBlockhash,
|
||||||
BufferLayout.u8('programIdIndex'),
|
instructions,
|
||||||
|
|
||||||
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'),
|
* Get a buffer of the Transaction data that need to be covered by signatures
|
||||||
BufferLayout.blob(1, 'numReadonlySignedAccounts'),
|
*/
|
||||||
BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'),
|
serializeMessage(): Buffer {
|
||||||
BufferLayout.blob(keyCount.length, 'keyCount'),
|
return this.compileMessage().serialize();
|
||||||
BufferLayout.seq(Layout.publicKey('key'), keys.length, 'keys'),
|
|
||||||
Layout.publicKey('recentBlockhash'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const transaction = {
|
|
||||||
numRequiredSignatures: Buffer.from([this.signatures.length]),
|
|
||||||
numReadonlySignedAccounts: Buffer.from([numReadonlySignedAccounts]),
|
|
||||||
numReadonlyUnsignedAccounts: Buffer.from([numReadonlyUnsignedAccounts]),
|
|
||||||
keyCount: Buffer.from(keyCount),
|
|
||||||
keys: keys.map(key => new PublicKey(key).toBuffer()),
|
|
||||||
recentBlockhash: Buffer.from(bs58.decode(recentBlockhash)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signData = Buffer.alloc(2048);
|
|
||||||
const length = signDataLayout.encode(transaction, signData);
|
|
||||||
instructionBuffer.copy(signData, length);
|
|
||||||
signData = signData.slice(0, length + instructionBuffer.length);
|
|
||||||
|
|
||||||
return signData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -517,11 +473,8 @@ export class Transaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
const numRequiredSignatures = byteArray.shift();
|
const numRequiredSignatures = byteArray.shift();
|
||||||
// byteArray = byteArray.slice(1); // Skip numRequiredSignatures byte
|
|
||||||
const numReadonlySignedAccounts = byteArray.shift();
|
const numReadonlySignedAccounts = byteArray.shift();
|
||||||
// byteArray = byteArray.slice(1); // Skip numReadonlySignedAccounts byte
|
|
||||||
const numReadonlyUnsignedAccounts = byteArray.shift();
|
const numReadonlyUnsignedAccounts = byteArray.shift();
|
||||||
// byteArray = byteArray.slice(1); // Skip numReadonlyUnsignedAccounts byte
|
|
||||||
|
|
||||||
const accountCount = shortvec.decodeLength(byteArray);
|
const accountCount = shortvec.decodeLength(byteArray);
|
||||||
let accounts = [];
|
let accounts = [];
|
||||||
|
@ -549,15 +502,18 @@ export class Transaction {
|
||||||
instructions.push(instruction);
|
instructions.push(instruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Transaction._populate(
|
const message = {
|
||||||
signatures,
|
header: {
|
||||||
accounts,
|
|
||||||
instructions,
|
|
||||||
recentBlockhash,
|
|
||||||
numRequiredSignatures,
|
numRequiredSignatures,
|
||||||
numReadonlySignedAccounts,
|
numReadonlySignedAccounts,
|
||||||
numReadonlyUnsignedAccounts,
|
numReadonlyUnsignedAccounts,
|
||||||
);
|
},
|
||||||
|
recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)),
|
||||||
|
accountKeys: accounts.map(account => new PublicKey(account)),
|
||||||
|
instructions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Transaction._populate(signatures, new Message(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -574,82 +530,60 @@ export class Transaction {
|
||||||
rpcResult.message.header.numReadonlySignedAccounts;
|
rpcResult.message.header.numReadonlySignedAccounts;
|
||||||
const numReadonlyUnsignedAccounts =
|
const numReadonlyUnsignedAccounts =
|
||||||
rpcResult.message.header.numReadonlyUnsignedAccounts;
|
rpcResult.message.header.numReadonlyUnsignedAccounts;
|
||||||
return Transaction._populate(
|
|
||||||
signatures,
|
const message = {
|
||||||
accounts,
|
header: {
|
||||||
instructions,
|
|
||||||
recentBlockhash,
|
|
||||||
numRequiredSignatures,
|
numRequiredSignatures,
|
||||||
numReadonlySignedAccounts,
|
numReadonlySignedAccounts,
|
||||||
numReadonlyUnsignedAccounts,
|
numReadonlyUnsignedAccounts,
|
||||||
);
|
},
|
||||||
|
recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)),
|
||||||
|
accountKeys: accounts.map(account => new PublicKey(account)),
|
||||||
|
instructions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Transaction._populate(signatures, new Message(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate Transaction object
|
* Populate Transaction object
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
static _populate(
|
static _populate(signatures: Array<string>, message: Message): Transaction {
|
||||||
signatures: Array<string>,
|
|
||||||
accounts: Array<string>,
|
|
||||||
instructions: Array<any>,
|
|
||||||
recentBlockhash: Array<number>,
|
|
||||||
numRequiredSignatures: number,
|
|
||||||
numReadonlySignedAccounts: number,
|
|
||||||
numReadonlyUnsignedAccounts: number,
|
|
||||||
): Transaction {
|
|
||||||
function isWritable(
|
|
||||||
i: number,
|
|
||||||
numRequiredSignatures: number,
|
|
||||||
numReadonlySignedAccounts: number,
|
|
||||||
numReadonlyUnsignedAccounts: number,
|
|
||||||
numKeys: number,
|
|
||||||
): boolean {
|
|
||||||
return (
|
|
||||||
i < numRequiredSignatures - numReadonlySignedAccounts ||
|
|
||||||
(i >= numRequiredSignatures &&
|
|
||||||
i < numKeys - numReadonlyUnsignedAccounts)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const transaction = new Transaction();
|
const transaction = new Transaction();
|
||||||
transaction.recentBlockhash = new PublicKey(recentBlockhash).toBase58();
|
transaction.recentBlockhash = message.recentBlockhash;
|
||||||
for (let i = 0; i < signatures.length; i++) {
|
signatures.forEach((signature, index) => {
|
||||||
const sigPubkeyPair = {
|
const sigPubkeyPair = {
|
||||||
signature:
|
signature:
|
||||||
signatures[i] == bs58.encode(DEFAULT_SIGNATURE)
|
signature == bs58.encode(DEFAULT_SIGNATURE)
|
||||||
? null
|
? null
|
||||||
: bs58.decode(signatures[i]),
|
: bs58.decode(signature),
|
||||||
publicKey: new PublicKey(accounts[i]),
|
publicKey: message.accountKeys[index],
|
||||||
};
|
};
|
||||||
transaction.signatures.push(sigPubkeyPair);
|
transaction.signatures.push(sigPubkeyPair);
|
||||||
}
|
});
|
||||||
for (let i = 0; i < instructions.length; i++) {
|
|
||||||
let instructionData = {
|
|
||||||
keys: [],
|
|
||||||
programId: new PublicKey(accounts[instructions[i].programIdIndex]),
|
|
||||||
data: bs58.decode(instructions[i].data),
|
|
||||||
};
|
|
||||||
for (let j = 0; j < instructions[i].accounts.length; j++) {
|
|
||||||
const pubkey = new PublicKey(accounts[instructions[i].accounts[j]]);
|
|
||||||
|
|
||||||
instructionData.keys.push({
|
message.instructions.forEach(instruction => {
|
||||||
|
const keys = instruction.accounts.map(account => {
|
||||||
|
const pubkey = message.accountKeys[account];
|
||||||
|
return {
|
||||||
pubkey,
|
pubkey,
|
||||||
isSigner: transaction.signatures.some(
|
isSigner: transaction.signatures.some(
|
||||||
keyObj => keyObj.publicKey.toString() === pubkey.toString(),
|
keyObj => keyObj.publicKey.toString() === pubkey.toString(),
|
||||||
),
|
),
|
||||||
isWritable: isWritable(
|
isWritable: message.isAccountWritable(account),
|
||||||
j,
|
};
|
||||||
numRequiredSignatures,
|
|
||||||
numReadonlySignedAccounts,
|
|
||||||
numReadonlyUnsignedAccounts,
|
|
||||||
accounts.length,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
let instruction = new TransactionInstruction(instructionData);
|
transaction.instructions.push(
|
||||||
transaction.instructions.push(instruction);
|
new TransactionInstruction({
|
||||||
}
|
keys,
|
||||||
|
programId: message.accountKeys[instruction.programIdIndex],
|
||||||
|
data: bs58.decode(instruction.data),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue