2018-09-13 18:48:51 -07:00
|
|
|
import nacl from 'tweetnacl';
|
|
|
|
import bs58 from 'bs58';
|
2021-02-01 18:53:24 -08:00
|
|
|
import {Buffer} from 'buffer';
|
2018-09-13 18:48:51 -07:00
|
|
|
|
2020-04-30 09:05:29 -07:00
|
|
|
import {Message} from './message';
|
2018-10-23 19:13:59 -07:00
|
|
|
import {PublicKey} from './publickey';
|
2019-01-31 01:16:07 -08:00
|
|
|
import * as shortvec from './util/shortvec-encoding';
|
2021-03-14 19:42:53 -07:00
|
|
|
import {toBuffer} from './util/to-buffer';
|
2021-07-08 22:33:41 -07:00
|
|
|
import invariant from './util/assert';
|
2021-05-07 01:59:51 -07:00
|
|
|
import type {Signer} from './keypair';
|
|
|
|
import type {Blockhash} from './blockhash';
|
|
|
|
import type {CompiledInstruction} from './message';
|
2018-09-13 18:48:51 -07:00
|
|
|
|
|
|
|
/**
|
2021-03-31 03:48:41 -07:00
|
|
|
* Transaction signature as base-58 encoded string
|
2018-09-13 18:48:51 -07:00
|
|
|
*/
|
|
|
|
export type TransactionSignature = string;
|
|
|
|
|
2019-08-30 16:22:11 -07:00
|
|
|
/**
|
|
|
|
* Default (empty) signature
|
|
|
|
*
|
|
|
|
* Signatures are 64 bytes in length
|
|
|
|
*/
|
2020-01-13 15:17:32 -08:00
|
|
|
const DEFAULT_SIGNATURE = Buffer.alloc(64).fill(0);
|
2019-08-30 16:22:11 -07:00
|
|
|
|
2018-11-27 18:05:23 -08:00
|
|
|
/**
|
|
|
|
* Maximum over-the-wire size of a Transaction
|
2019-05-28 08:51:46 -07:00
|
|
|
*
|
|
|
|
* 1280 is IPv6 minimum MTU
|
|
|
|
* 40 bytes is the size of the IPv6 header
|
|
|
|
* 8 bytes is the size of the fragment header
|
2018-11-27 18:05:23 -08:00
|
|
|
*/
|
2019-05-28 08:51:46 -07:00
|
|
|
export const PACKET_DATA_SIZE = 1280 - 40 - 8;
|
2018-11-27 18:05:23 -08:00
|
|
|
|
2019-11-16 08:28:14 -08:00
|
|
|
const SIGNATURE_LENGTH = 64;
|
|
|
|
|
2020-04-30 09:05:29 -07:00
|
|
|
/**
|
|
|
|
* Account metadata used to define instructions
|
|
|
|
*/
|
|
|
|
export type AccountMeta = {
|
2021-03-31 03:48:41 -07:00
|
|
|
/** An account's public key */
|
2021-03-14 20:01:35 -07:00
|
|
|
pubkey: PublicKey;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** True if an instruction requires a transaction signature matching `pubkey` */
|
2021-03-14 20:01:35 -07:00
|
|
|
isSigner: boolean;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** True if the `pubkey` can be loaded as a read-write account. */
|
2021-03-14 20:01:35 -07:00
|
|
|
isWritable: boolean;
|
2020-04-30 09:05:29 -07:00
|
|
|
};
|
|
|
|
|
2018-10-23 15:17:43 -07:00
|
|
|
/**
|
|
|
|
* List of TransactionInstruction object fields that may be initialized at construction
|
|
|
|
*/
|
2021-03-14 20:01:35 -07:00
|
|
|
export type TransactionInstructionCtorFields = {
|
|
|
|
keys: Array<AccountMeta>;
|
|
|
|
programId: PublicKey;
|
|
|
|
data?: Buffer;
|
|
|
|
};
|
2018-10-23 15:17:43 -07:00
|
|
|
|
2020-09-11 15:04:36 -07:00
|
|
|
/**
|
|
|
|
* Configuration object for Transaction.serialize()
|
|
|
|
*/
|
|
|
|
export type SerializeConfig = {
|
2021-03-31 03:48:41 -07:00
|
|
|
/** Require all transaction signatures be present (default: true) */
|
2021-03-14 20:01:35 -07:00
|
|
|
requireAllSignatures?: boolean;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** Verify provided signatures (default: true) */
|
2021-03-14 20:01:35 -07:00
|
|
|
verifySignatures?: boolean;
|
2020-09-11 15:04:36 -07:00
|
|
|
};
|
|
|
|
|
2018-10-23 15:17:43 -07:00
|
|
|
/**
|
|
|
|
* Transaction Instruction class
|
|
|
|
*/
|
|
|
|
export class TransactionInstruction {
|
|
|
|
/**
|
|
|
|
* Public keys to include in this transaction
|
2019-04-10 12:31:50 -07:00
|
|
|
* Boolean represents whether this pubkey needs to sign the transaction
|
2018-10-23 15:17:43 -07:00
|
|
|
*/
|
2021-03-14 19:16:45 -07:00
|
|
|
keys: Array<AccountMeta>;
|
2018-10-23 15:17:43 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Program Id to execute
|
|
|
|
*/
|
|
|
|
programId: PublicKey;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Program input
|
|
|
|
*/
|
2019-03-14 13:27:47 -07:00
|
|
|
data: Buffer = Buffer.alloc(0);
|
2018-10-23 15:17:43 -07:00
|
|
|
|
2021-03-14 19:16:45 -07:00
|
|
|
constructor(opts: TransactionInstructionCtorFields) {
|
|
|
|
this.programId = opts.programId;
|
|
|
|
this.keys = opts.keys;
|
|
|
|
if (opts.data) {
|
|
|
|
this.data = opts.data;
|
|
|
|
}
|
2018-10-23 15:17:43 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-28 10:46:09 -08:00
|
|
|
/**
|
2021-03-29 01:12:47 -07:00
|
|
|
* Pair of signature and corresponding public key
|
2018-11-28 10:46:09 -08:00
|
|
|
*/
|
2021-03-29 01:12:47 -07:00
|
|
|
export type SignaturePubkeyPair = {
|
2021-03-14 20:01:35 -07:00
|
|
|
signature: Buffer | null;
|
|
|
|
publicKey: PublicKey;
|
|
|
|
};
|
2018-11-28 10:46:09 -08:00
|
|
|
|
2018-09-18 12:46:59 -07:00
|
|
|
/**
|
|
|
|
* List of Transaction object fields that may be initialized at construction
|
2018-09-20 15:35:41 -07:00
|
|
|
*
|
2018-09-18 12:46:59 -07:00
|
|
|
*/
|
2021-09-13 14:37:18 -07:00
|
|
|
export type TransactionCtorFields = {
|
2021-03-31 03:48:41 -07:00
|
|
|
/** A recent blockhash */
|
2021-03-14 20:01:35 -07:00
|
|
|
recentBlockhash?: Blockhash | null;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** Optional nonce information used for offline nonce'd transactions */
|
2021-03-14 20:01:35 -07:00
|
|
|
nonceInfo?: NonceInformation | null;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** The transaction fee payer */
|
2021-03-14 20:01:35 -07:00
|
|
|
feePayer?: PublicKey | null;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** One or more signatures */
|
2021-03-14 20:01:35 -07:00
|
|
|
signatures?: Array<SignaturePubkeyPair>;
|
|
|
|
};
|
2018-09-18 12:46:59 -07:00
|
|
|
|
2020-01-06 17:12:04 -08:00
|
|
|
/**
|
2021-03-31 03:48:41 -07:00
|
|
|
* Nonce information to be used to build an offline Transaction.
|
2020-01-06 17:12:04 -08:00
|
|
|
*/
|
2021-09-13 14:37:18 -07:00
|
|
|
export type NonceInformation = {
|
2021-03-31 03:48:41 -07:00
|
|
|
/** The current blockhash stored in the nonce */
|
2021-03-14 20:01:35 -07:00
|
|
|
nonce: Blockhash;
|
2021-03-31 03:48:41 -07:00
|
|
|
/** AdvanceNonceAccount Instruction */
|
2021-03-14 20:01:35 -07:00
|
|
|
nonceInstruction: TransactionInstruction;
|
|
|
|
};
|
2020-01-06 17:12:04 -08:00
|
|
|
|
2018-09-13 18:48:51 -07:00
|
|
|
/**
|
2018-10-23 15:17:43 -07:00
|
|
|
* Transaction class
|
2018-09-13 18:48:51 -07:00
|
|
|
*/
|
|
|
|
export class Transaction {
|
|
|
|
/**
|
2018-11-18 08:48:14 -08:00
|
|
|
* Signatures for the transaction. Typically created by invoking the
|
2018-11-28 10:06:17 -08:00
|
|
|
* `sign()` method
|
2018-09-13 18:48:51 -07:00
|
|
|
*/
|
2018-11-18 08:48:14 -08:00
|
|
|
signatures: Array<SignaturePubkeyPair> = [];
|
|
|
|
|
|
|
|
/**
|
2019-05-08 09:33:04 -07:00
|
|
|
* The first (payer) Transaction signature
|
2018-11-18 08:48:14 -08:00
|
|
|
*/
|
|
|
|
get signature(): Buffer | null {
|
|
|
|
if (this.signatures.length > 0) {
|
|
|
|
return this.signatures[0].signature;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2018-09-13 18:48:51 -07:00
|
|
|
|
2020-09-09 23:04:09 -07:00
|
|
|
/**
|
2020-10-24 18:59:38 -07:00
|
|
|
* The transaction fee payer
|
2020-09-09 23:04:09 -07:00
|
|
|
*/
|
2021-03-14 20:01:35 -07:00
|
|
|
feePayer?: PublicKey;
|
2020-09-09 23:04:09 -07:00
|
|
|
|
2018-09-18 12:46:59 -07:00
|
|
|
/**
|
2018-10-23 15:17:43 -07:00
|
|
|
* The instructions to atomically execute
|
2018-09-18 12:46:59 -07:00
|
|
|
*/
|
2018-10-23 15:17:43 -07:00
|
|
|
instructions: Array<TransactionInstruction> = [];
|
2018-09-18 12:46:59 -07:00
|
|
|
|
2018-09-13 18:48:51 -07:00
|
|
|
/**
|
2020-10-24 18:59:38 -07:00
|
|
|
* A recent transaction id. Must be populated by the caller
|
2018-09-13 18:48:51 -07:00
|
|
|
*/
|
2021-03-14 20:01:35 -07:00
|
|
|
recentBlockhash?: Blockhash;
|
2018-09-13 18:48:51 -07:00
|
|
|
|
2020-01-06 17:12:04 -08:00
|
|
|
/**
|
|
|
|
* Optional Nonce information. If populated, transaction will use a durable
|
|
|
|
* Nonce hash instead of a recentBlockhash. Must be populated by the caller
|
|
|
|
*/
|
2021-03-14 20:01:35 -07:00
|
|
|
nonceInfo?: NonceInformation;
|
2020-01-06 17:12:04 -08:00
|
|
|
|
2018-10-23 19:13:59 -07:00
|
|
|
/**
|
|
|
|
* Construct an empty Transaction
|
|
|
|
*/
|
2018-09-18 12:46:59 -07:00
|
|
|
constructor(opts?: TransactionCtorFields) {
|
|
|
|
opts && Object.assign(this, opts);
|
|
|
|
}
|
|
|
|
|
2018-10-23 19:13:59 -07:00
|
|
|
/**
|
2018-11-28 10:06:17 -08:00
|
|
|
* Add one or more instructions to this Transaction
|
2018-10-23 19:13:59 -07:00
|
|
|
*/
|
2018-11-28 10:06:17 -08:00
|
|
|
add(
|
2019-05-23 16:18:13 -07:00
|
|
|
...items: Array<
|
2021-03-14 20:01:35 -07:00
|
|
|
Transaction | TransactionInstruction | TransactionInstructionCtorFields
|
2019-05-23 16:18:13 -07:00
|
|
|
>
|
2018-11-28 10:06:17 -08:00
|
|
|
): Transaction {
|
|
|
|
if (items.length === 0) {
|
|
|
|
throw new Error('No instructions');
|
2018-10-23 15:17:43 -07:00
|
|
|
}
|
2018-11-28 10:06:17 -08:00
|
|
|
|
2020-07-30 10:48:25 -07:00
|
|
|
items.forEach((item: any) => {
|
|
|
|
if ('instructions' in item) {
|
2018-11-28 10:06:17 -08:00
|
|
|
this.instructions = this.instructions.concat(item.instructions);
|
2020-07-30 10:48:25 -07:00
|
|
|
} else if ('data' in item && 'programId' in item && 'keys' in item) {
|
2019-05-23 16:18:13 -07:00
|
|
|
this.instructions.push(item);
|
2018-11-28 10:06:17 -08:00
|
|
|
} else {
|
|
|
|
this.instructions.push(new TransactionInstruction(item));
|
|
|
|
}
|
|
|
|
});
|
2018-10-23 15:17:43 -07:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2018-09-13 18:48:51 -07:00
|
|
|
/**
|
2020-04-30 09:05:29 -07:00
|
|
|
* Compile transaction data
|
2018-09-13 18:48:51 -07:00
|
|
|
*/
|
2020-04-30 09:05:29 -07:00
|
|
|
compileMessage(): Message {
|
2020-01-06 17:12:04 -08:00
|
|
|
const {nonceInfo} = this;
|
2020-01-07 16:57:56 -08:00
|
|
|
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
|
2020-01-06 17:12:04 -08:00
|
|
|
this.recentBlockhash = nonceInfo.nonce;
|
|
|
|
this.instructions.unshift(nonceInfo.nonceInstruction);
|
|
|
|
}
|
2019-03-04 08:06:33 -08:00
|
|
|
const {recentBlockhash} = this;
|
|
|
|
if (!recentBlockhash) {
|
|
|
|
throw new Error('Transaction recentBlockhash required');
|
2018-09-13 18:48:51 -07:00
|
|
|
}
|
2018-10-23 15:17:43 -07:00
|
|
|
|
2018-10-23 19:13:59 -07:00
|
|
|
if (this.instructions.length < 1) {
|
2021-06-02 12:39:59 -07:00
|
|
|
console.warn('No instructions provided');
|
2018-10-23 15:17:43 -07:00
|
|
|
}
|
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
let feePayer: PublicKey;
|
|
|
|
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');
|
2020-09-09 23:04:09 -07:00
|
|
|
}
|
|
|
|
|
2021-03-14 19:16:45 -07:00
|
|
|
for (let i = 0; i < this.instructions.length; i++) {
|
|
|
|
if (this.instructions[i].programId === undefined) {
|
|
|
|
throw new Error(
|
|
|
|
`Transaction instruction index ${i} has undefined program id`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-30 09:05:29 -07:00
|
|
|
const programIds: string[] = [];
|
|
|
|
const accountMetas: AccountMeta[] = [];
|
2018-10-23 19:13:59 -07:00
|
|
|
this.instructions.forEach(instruction => {
|
2020-04-30 09:05:29 -07:00
|
|
|
instruction.keys.forEach(accountMeta => {
|
2020-09-12 18:30:51 -07:00
|
|
|
accountMetas.push({...accountMeta});
|
2019-04-10 12:31:50 -07:00
|
|
|
});
|
2019-05-23 09:42:11 -07:00
|
|
|
|
|
|
|
const programId = instruction.programId.toString();
|
2019-05-24 15:07:16 -07:00
|
|
|
if (!programIds.includes(programId)) {
|
|
|
|
programIds.push(programId);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-07-01 19:26:36 -07:00
|
|
|
// Append programID account metas
|
|
|
|
programIds.forEach(programId => {
|
|
|
|
accountMetas.push({
|
|
|
|
pubkey: new PublicKey(programId),
|
|
|
|
isSigner: false,
|
|
|
|
isWritable: false,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Sort. Prioritizing first by signer, then by writable
|
2020-04-30 09:05:29 -07:00
|
|
|
accountMetas.sort(function (x, y) {
|
2022-01-07 21:38:58 -08:00
|
|
|
const pubkeySorting = x.pubkey
|
|
|
|
.toBase58()
|
|
|
|
.localeCompare(y.pubkey.toBase58());
|
2020-01-07 16:57:56 -08:00
|
|
|
const checkSigner = x.isSigner === y.isSigner ? 0 : x.isSigner ? -1 : 1;
|
2020-01-08 12:59:58 -08:00
|
|
|
const checkWritable =
|
2022-01-07 21:38:58 -08:00
|
|
|
x.isWritable === y.isWritable ? pubkeySorting : x.isWritable ? -1 : 1;
|
2020-01-07 16:57:56 -08:00
|
|
|
return checkSigner || checkWritable;
|
|
|
|
});
|
|
|
|
|
2020-07-01 19:26:36 -07:00
|
|
|
// Cull duplicate account metas
|
|
|
|
const uniqueMetas: AccountMeta[] = [];
|
|
|
|
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;
|
|
|
|
} else {
|
|
|
|
uniqueMetas.push(accountMeta);
|
2020-01-07 16:57:56 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
// 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) {
|
2020-07-01 19:26:36 -07:00
|
|
|
const uniqueIndex = uniqueMetas.findIndex(x => {
|
2020-09-09 23:04:09 -07:00
|
|
|
return x.pubkey.equals(signature.publicKey);
|
2020-07-01 19:26:36 -07:00
|
|
|
});
|
|
|
|
if (uniqueIndex > -1) {
|
2020-10-24 18:59:38 -07:00
|
|
|
if (!uniqueMetas[uniqueIndex].isSigner) {
|
2020-09-09 23:04:09 -07:00
|
|
|
uniqueMetas[uniqueIndex].isSigner = true;
|
2020-10-24 18:59:38 -07:00
|
|
|
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.',
|
|
|
|
);
|
2020-09-09 23:04:09 -07:00
|
|
|
}
|
|
|
|
} else {
|
2020-09-12 18:30:51 -07:00
|
|
|
throw new Error(`unknown signer: ${signature.publicKey.toString()}`);
|
2019-05-23 09:42:11 -07:00
|
|
|
}
|
2020-10-24 18:59:38 -07:00
|
|
|
}
|
2018-10-23 19:13:59 -07:00
|
|
|
|
2020-09-12 18:30:51 -07:00
|
|
|
let numRequiredSignatures = 0;
|
|
|
|
let numReadonlySignedAccounts = 0;
|
|
|
|
let numReadonlyUnsignedAccounts = 0;
|
|
|
|
|
|
|
|
// Split out signing from non-signing keys and count header values
|
2020-07-01 19:26:36 -07:00
|
|
|
const signedKeys: string[] = [];
|
|
|
|
const unsignedKeys: string[] = [];
|
|
|
|
uniqueMetas.forEach(({pubkey, isSigner, isWritable}) => {
|
|
|
|
if (isSigner) {
|
|
|
|
signedKeys.push(pubkey.toString());
|
2020-09-12 18:30:51 -07:00
|
|
|
numRequiredSignatures += 1;
|
|
|
|
if (!isWritable) {
|
2020-07-01 19:26:36 -07:00
|
|
|
numReadonlySignedAccounts += 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
unsignedKeys.push(pubkey.toString());
|
|
|
|
if (!isWritable) {
|
|
|
|
numReadonlyUnsignedAccounts += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const accountKeys = signedKeys.concat(unsignedKeys);
|
2020-04-30 09:05:29 -07:00
|
|
|
const instructions: CompiledInstruction[] = this.instructions.map(
|
|
|
|
instruction => {
|
|
|
|
const {data, programId} = instruction;
|
|
|
|
return {
|
2020-06-10 22:15:14 -07:00
|
|
|
programIdIndex: accountKeys.indexOf(programId.toString()),
|
2020-10-24 18:59:38 -07:00
|
|
|
accounts: instruction.keys.map(meta =>
|
|
|
|
accountKeys.indexOf(meta.pubkey.toString()),
|
2019-04-10 12:31:50 -07:00
|
|
|
),
|
2020-04-30 09:05:29 -07:00
|
|
|
data: bs58.encode(data),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
);
|
2018-10-23 19:13:59 -07:00
|
|
|
|
|
|
|
instructions.forEach(instruction => {
|
2018-11-18 08:48:14 -08:00
|
|
|
invariant(instruction.programIdIndex >= 0);
|
2020-04-30 09:05:29 -07:00
|
|
|
instruction.accounts.forEach(keyIndex => invariant(keyIndex >= 0));
|
2018-10-23 19:13:59 -07:00
|
|
|
});
|
2018-10-10 10:46:15 -07:00
|
|
|
|
2020-04-30 09:05:29 -07:00
|
|
|
return new Message({
|
|
|
|
header: {
|
2020-09-12 18:30:51 -07:00
|
|
|
numRequiredSignatures,
|
2020-04-30 09:05:29 -07:00
|
|
|
numReadonlySignedAccounts,
|
|
|
|
numReadonlyUnsignedAccounts,
|
|
|
|
},
|
2020-06-10 22:15:14 -07:00
|
|
|
accountKeys,
|
2020-04-30 09:05:29 -07:00
|
|
|
recentBlockhash,
|
|
|
|
instructions,
|
2019-01-31 01:16:07 -08:00
|
|
|
});
|
2020-04-30 09:05:29 -07:00
|
|
|
}
|
2018-10-10 10:46:15 -07:00
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
/**
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2020-10-24 18:59:38 -07:00
|
|
|
*/
|
|
|
|
_compile(): Message {
|
|
|
|
const message = this.compileMessage();
|
|
|
|
const signedKeys = message.accountKeys.slice(
|
|
|
|
0,
|
|
|
|
message.header.numRequiredSignatures,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (this.signatures.length === signedKeys.length) {
|
|
|
|
const valid = this.signatures.every((pair, index) => {
|
|
|
|
return signedKeys[index].equals(pair.publicKey);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (valid) return message;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.signatures = signedKeys.map(publicKey => ({
|
|
|
|
signature: null,
|
|
|
|
publicKey,
|
|
|
|
}));
|
|
|
|
|
|
|
|
return message;
|
|
|
|
}
|
|
|
|
|
2020-04-30 09:05:29 -07:00
|
|
|
/**
|
|
|
|
* Get a buffer of the Transaction data that need to be covered by signatures
|
|
|
|
*/
|
|
|
|
serializeMessage(): Buffer {
|
2020-10-24 18:59:38 -07:00
|
|
|
return this._compile().serialize();
|
2018-09-13 18:48:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-09 23:04:09 -07:00
|
|
|
* Specify the public keys which will be used to sign the Transaction.
|
|
|
|
* The first signer will be used as the transaction fee payer account.
|
|
|
|
*
|
|
|
|
* Signatures can be added with either `partialSign` or `addSignature`
|
2020-10-24 18:59:38 -07:00
|
|
|
*
|
|
|
|
* @deprecated Deprecated since v0.84.0. Only the fee payer needs to be
|
|
|
|
* specified and it can be set in the Transaction constructor or with the
|
|
|
|
* `feePayer` property.
|
2020-09-09 23:04:09 -07:00
|
|
|
*/
|
|
|
|
setSigners(...signers: Array<PublicKey>) {
|
|
|
|
if (signers.length === 0) {
|
|
|
|
throw new Error('No signers');
|
|
|
|
}
|
|
|
|
|
2020-09-12 18:30:51 -07:00
|
|
|
const seen = new Set();
|
|
|
|
this.signatures = signers
|
|
|
|
.filter(publicKey => {
|
|
|
|
const key = publicKey.toString();
|
|
|
|
if (seen.has(key)) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
seen.add(key);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.map(publicKey => ({signature: null, publicKey}));
|
2020-09-09 23:04:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-05-07 01:59:51 -07:00
|
|
|
* Sign the Transaction with the specified signers. Multiple signatures may
|
2018-11-18 08:48:14 -08:00
|
|
|
* be applied to a Transaction. The first signature is considered "primary"
|
2020-10-24 18:59:38 -07:00
|
|
|
* and is used identify and confirm transactions.
|
|
|
|
*
|
|
|
|
* If the Transaction `feePayer` is not set, the first signer will be used
|
|
|
|
* as the transaction fee payer account.
|
2018-11-18 08:48:14 -08:00
|
|
|
*
|
|
|
|
* Transaction fields should not be modified after the first call to `sign`,
|
|
|
|
* as doing so may invalidate the signature and cause the Transaction to be
|
|
|
|
* rejected.
|
2018-09-13 18:48:51 -07:00
|
|
|
*
|
2019-03-04 08:06:33 -08:00
|
|
|
* The Transaction must be assigned a valid `recentBlockhash` before invoking this method
|
2018-09-13 18:48:51 -07:00
|
|
|
*/
|
2021-05-07 01:59:51 -07:00
|
|
|
sign(...signers: Array<Signer>) {
|
2020-09-09 23:04:09 -07:00
|
|
|
if (signers.length === 0) {
|
|
|
|
throw new Error('No signers');
|
|
|
|
}
|
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
// Dedupe signers
|
2020-09-12 18:30:51 -07:00
|
|
|
const seen = new Set();
|
2020-10-24 18:59:38 -07:00
|
|
|
const uniqueSigners = [];
|
|
|
|
for (const signer of signers) {
|
|
|
|
const key = signer.publicKey.toString();
|
|
|
|
if (seen.has(key)) {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
seen.add(key);
|
|
|
|
uniqueSigners.push(signer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.signatures = uniqueSigners.map(signer => ({
|
|
|
|
signature: null,
|
|
|
|
publicKey: signer.publicKey,
|
|
|
|
}));
|
2020-09-09 23:04:09 -07:00
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
const message = this._compile();
|
|
|
|
this._partialSign(message, ...uniqueSigners);
|
|
|
|
this._verifySignatures(message.serialize(), true);
|
2018-11-28 10:06:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-09-09 23:04:09 -07:00
|
|
|
* Partially sign a transaction with the specified accounts. All accounts must
|
2020-10-24 18:59:38 -07:00
|
|
|
* correspond to either the fee payer or a signer account in the transaction
|
|
|
|
* instructions.
|
2018-11-28 10:06:17 -08:00
|
|
|
*
|
2020-09-09 23:04:09 -07:00
|
|
|
* All the caveats from the `sign` method apply to `partialSign`
|
2018-11-28 10:06:17 -08:00
|
|
|
*/
|
2021-05-07 01:59:51 -07:00
|
|
|
partialSign(...signers: Array<Signer>) {
|
2020-09-09 23:04:09 -07:00
|
|
|
if (signers.length === 0) {
|
2018-11-18 08:48:14 -08:00
|
|
|
throw new Error('No signers');
|
|
|
|
}
|
2020-07-30 10:48:25 -07:00
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
// Dedupe signers
|
|
|
|
const seen = new Set();
|
|
|
|
const uniqueSigners = [];
|
|
|
|
for (const signer of signers) {
|
|
|
|
const key = signer.publicKey.toString();
|
|
|
|
if (seen.has(key)) {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
seen.add(key);
|
|
|
|
uniqueSigners.push(signer);
|
|
|
|
}
|
|
|
|
}
|
2020-09-12 18:30:51 -07:00
|
|
|
|
2020-10-24 18:59:38 -07:00
|
|
|
const message = this._compile();
|
|
|
|
this._partialSign(message, ...uniqueSigners);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2020-10-24 18:59:38 -07:00
|
|
|
*/
|
2021-05-07 01:59:51 -07:00
|
|
|
_partialSign(message: Message, ...signers: Array<Signer>) {
|
2020-09-12 18:30:51 -07:00
|
|
|
const signData = message.serialize();
|
2020-09-09 23:04:09 -07:00
|
|
|
signers.forEach(signer => {
|
|
|
|
const signature = nacl.sign.detached(signData, signer.secretKey);
|
2021-03-14 19:42:53 -07:00
|
|
|
this._addSignature(signer.publicKey, toBuffer(signature));
|
2018-11-18 08:48:14 -08:00
|
|
|
});
|
2018-09-13 18:48:51 -07:00
|
|
|
}
|
|
|
|
|
2018-11-28 10:06:17 -08:00
|
|
|
/**
|
2020-09-09 23:04:09 -07:00
|
|
|
* Add an externally created signature to a transaction. The public key
|
2020-10-24 18:59:38 -07:00
|
|
|
* must correspond to either the fee payer or a signer account in the transaction
|
|
|
|
* instructions.
|
2020-04-23 14:44:19 -07:00
|
|
|
*/
|
|
|
|
addSignature(pubkey: PublicKey, signature: Buffer) {
|
2020-10-24 18:59:38 -07:00
|
|
|
this._compile(); // Ensure signatures array is populated
|
|
|
|
this._addSignature(pubkey, signature);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2020-10-24 18:59:38 -07:00
|
|
|
*/
|
|
|
|
_addSignature(pubkey: PublicKey, signature: Buffer) {
|
2020-04-23 14:44:19 -07:00
|
|
|
invariant(signature.length === 64);
|
|
|
|
|
2018-11-28 10:06:17 -08:00
|
|
|
const index = this.signatures.findIndex(sigpair =>
|
2020-04-23 14:44:19 -07:00
|
|
|
pubkey.equals(sigpair.publicKey),
|
2018-11-28 10:06:17 -08:00
|
|
|
);
|
|
|
|
if (index < 0) {
|
2020-09-12 18:30:51 -07:00
|
|
|
throw new Error(`unknown signer: ${pubkey.toString()}`);
|
2018-11-28 10:06:17 -08:00
|
|
|
}
|
|
|
|
|
2019-01-31 01:16:07 -08:00
|
|
|
this.signatures[index].signature = Buffer.from(signature);
|
2018-11-28 10:06:17 -08:00
|
|
|
}
|
|
|
|
|
2020-03-03 12:39:00 -08:00
|
|
|
/**
|
|
|
|
* Verify signatures of a complete, signed Transaction
|
|
|
|
*/
|
|
|
|
verifySignatures(): boolean {
|
2020-09-11 15:04:36 -07:00
|
|
|
return this._verifySignatures(this.serializeMessage(), true);
|
2020-08-10 23:35:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2020-08-10 23:35:56 -07:00
|
|
|
*/
|
2020-09-11 15:04:36 -07:00
|
|
|
_verifySignatures(signData: Buffer, requireAllSignatures: boolean): boolean {
|
2020-03-03 12:39:00 -08:00
|
|
|
for (const {signature, publicKey} of this.signatures) {
|
2020-09-11 15:04:36 -07:00
|
|
|
if (signature === null) {
|
|
|
|
if (requireAllSignatures) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (
|
2021-03-30 23:51:41 -07:00
|
|
|
!nacl.sign.detached.verify(signData, signature, publicKey.toBuffer())
|
2020-09-11 15:04:36 -07:00
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-03-03 12:39:00 -08:00
|
|
|
}
|
|
|
|
}
|
2020-09-11 15:04:36 -07:00
|
|
|
return true;
|
2020-03-03 12:39:00 -08:00
|
|
|
}
|
|
|
|
|
2018-09-13 18:48:51 -07:00
|
|
|
/**
|
|
|
|
* Serialize the Transaction in the wire format.
|
|
|
|
*/
|
2020-09-11 15:04:36 -07:00
|
|
|
serialize(config?: SerializeConfig): Buffer {
|
|
|
|
const {requireAllSignatures, verifySignatures} = Object.assign(
|
|
|
|
{requireAllSignatures: true, verifySignatures: true},
|
|
|
|
config,
|
|
|
|
);
|
|
|
|
|
2020-04-24 09:20:27 -07:00
|
|
|
const signData = this.serializeMessage();
|
2020-09-11 15:04:36 -07:00
|
|
|
if (
|
|
|
|
verifySignatures &&
|
|
|
|
!this._verifySignatures(signData, requireAllSignatures)
|
|
|
|
) {
|
2020-09-12 23:24:45 -07:00
|
|
|
throw new Error('Signature verification failed');
|
2020-08-10 23:35:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return this._serialize(signData);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2020-08-10 23:35:56 -07:00
|
|
|
*/
|
|
|
|
_serialize(signData: Buffer): Buffer {
|
|
|
|
const {signatures} = this;
|
2021-03-14 20:01:35 -07:00
|
|
|
const signatureCount: number[] = [];
|
2019-01-31 01:16:07 -08:00
|
|
|
shortvec.encodeLength(signatureCount, signatures.length);
|
|
|
|
const transactionLength =
|
|
|
|
signatureCount.length + signatures.length * 64 + signData.length;
|
2019-02-02 10:00:20 -08:00
|
|
|
const wireTransaction = Buffer.alloc(transactionLength);
|
2018-11-18 08:48:14 -08:00
|
|
|
invariant(signatures.length < 256);
|
2019-02-02 10:00:20 -08:00
|
|
|
Buffer.from(signatureCount).copy(wireTransaction, 0);
|
2018-11-18 08:48:14 -08:00
|
|
|
signatures.forEach(({signature}, index) => {
|
2019-08-30 16:22:11 -07:00
|
|
|
if (signature !== null) {
|
|
|
|
invariant(signature.length === 64, `signature has invalid length`);
|
|
|
|
Buffer.from(signature).copy(
|
|
|
|
wireTransaction,
|
|
|
|
signatureCount.length + index * 64,
|
|
|
|
);
|
|
|
|
}
|
2018-11-18 08:48:14 -08:00
|
|
|
});
|
2019-01-31 01:16:07 -08:00
|
|
|
signData.copy(
|
|
|
|
wireTransaction,
|
2019-02-02 10:00:20 -08:00
|
|
|
signatureCount.length + signatures.length * 64,
|
2019-01-31 01:16:07 -08:00
|
|
|
);
|
2018-11-18 08:48:14 -08:00
|
|
|
invariant(
|
2018-11-27 18:05:23 -08:00
|
|
|
wireTransaction.length <= PACKET_DATA_SIZE,
|
|
|
|
`Transaction too large: ${wireTransaction.length} > ${PACKET_DATA_SIZE}`,
|
2018-11-16 19:28:57 -08:00
|
|
|
);
|
2018-09-13 18:48:51 -07:00
|
|
|
return wireTransaction;
|
|
|
|
}
|
2018-10-23 15:17:43 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Deprecated method
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2018-10-23 15:17:43 -07:00
|
|
|
*/
|
|
|
|
get keys(): Array<PublicKey> {
|
2018-11-18 08:48:14 -08:00
|
|
|
invariant(this.instructions.length === 1);
|
2019-04-10 12:31:50 -07:00
|
|
|
return this.instructions[0].keys.map(keyObj => keyObj.pubkey);
|
2018-10-23 15:17:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deprecated method
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2018-10-23 15:17:43 -07:00
|
|
|
*/
|
|
|
|
get programId(): PublicKey {
|
2018-11-18 08:48:14 -08:00
|
|
|
invariant(this.instructions.length === 1);
|
2018-10-23 15:17:43 -07:00
|
|
|
return this.instructions[0].programId;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deprecated method
|
2021-03-14 20:01:35 -07:00
|
|
|
* @internal
|
2018-10-23 15:17:43 -07:00
|
|
|
*/
|
2019-03-14 13:27:47 -07:00
|
|
|
get data(): Buffer {
|
2018-11-18 08:48:14 -08:00
|
|
|
invariant(this.instructions.length === 1);
|
2019-03-14 13:27:47 -07:00
|
|
|
return this.instructions[0].data;
|
2018-10-23 15:17:43 -07:00
|
|
|
}
|
2019-01-31 01:16:07 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Parse a wire transaction into a Transaction object.
|
|
|
|
*/
|
2020-02-12 16:25:22 -08:00
|
|
|
static from(buffer: Buffer | Uint8Array | Array<number>): Transaction {
|
2019-01-31 01:16:07 -08:00
|
|
|
// Slice up wire data
|
|
|
|
let byteArray = [...buffer];
|
|
|
|
|
|
|
|
const signatureCount = shortvec.decodeLength(byteArray);
|
|
|
|
let signatures = [];
|
|
|
|
for (let i = 0; i < signatureCount; i++) {
|
|
|
|
const signature = byteArray.slice(0, SIGNATURE_LENGTH);
|
|
|
|
byteArray = byteArray.slice(SIGNATURE_LENGTH);
|
2020-01-13 15:17:32 -08:00
|
|
|
signatures.push(bs58.encode(Buffer.from(signature)));
|
2019-01-31 01:16:07 -08:00
|
|
|
}
|
|
|
|
|
2020-08-12 15:01:39 -07:00
|
|
|
return Transaction.populate(Message.from(byteArray), signatures);
|
2019-11-16 08:28:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-10 22:15:14 -07:00
|
|
|
* Populate Transaction object from message and signatures
|
2019-11-16 08:28:14 -08:00
|
|
|
*/
|
2021-09-16 14:10:28 -07:00
|
|
|
static populate(
|
|
|
|
message: Message,
|
|
|
|
signatures: Array<string> = [],
|
|
|
|
): Transaction {
|
2019-11-16 08:28:14 -08:00
|
|
|
const transaction = new Transaction();
|
2020-04-30 09:05:29 -07:00
|
|
|
transaction.recentBlockhash = message.recentBlockhash;
|
2020-10-24 18:59:38 -07:00
|
|
|
if (message.header.numRequiredSignatures > 0) {
|
|
|
|
transaction.feePayer = message.accountKeys[0];
|
|
|
|
}
|
2020-04-30 09:05:29 -07:00
|
|
|
signatures.forEach((signature, index) => {
|
2019-01-31 01:16:07 -08:00
|
|
|
const sigPubkeyPair = {
|
2019-08-30 16:22:11 -07:00
|
|
|
signature:
|
2020-04-30 09:05:29 -07:00
|
|
|
signature == bs58.encode(DEFAULT_SIGNATURE)
|
2019-08-30 16:22:11 -07:00
|
|
|
? null
|
2020-04-30 09:05:29 -07:00
|
|
|
: bs58.decode(signature),
|
|
|
|
publicKey: message.accountKeys[index],
|
2019-01-31 01:16:07 -08:00
|
|
|
};
|
|
|
|
transaction.signatures.push(sigPubkeyPair);
|
2020-04-30 09:05:29 -07:00
|
|
|
});
|
2019-05-23 17:24:38 -07:00
|
|
|
|
2020-04-30 09:05:29 -07:00
|
|
|
message.instructions.forEach(instruction => {
|
|
|
|
const keys = instruction.accounts.map(account => {
|
|
|
|
const pubkey = message.accountKeys[account];
|
|
|
|
return {
|
2019-04-10 12:31:50 -07:00
|
|
|
pubkey,
|
2021-09-16 14:10:28 -07:00
|
|
|
isSigner:
|
|
|
|
transaction.signatures.some(
|
|
|
|
keyObj => keyObj.publicKey.toString() === pubkey.toString(),
|
|
|
|
) || message.isAccountSigner(account),
|
2020-04-30 09:05:29 -07:00
|
|
|
isWritable: message.isAccountWritable(account),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
transaction.instructions.push(
|
|
|
|
new TransactionInstruction({
|
|
|
|
keys,
|
|
|
|
programId: message.accountKeys[instruction.programIdIndex],
|
|
|
|
data: bs58.decode(instruction.data),
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2019-01-31 01:16:07 -08:00
|
|
|
return transaction;
|
|
|
|
}
|
2018-09-14 08:27:40 -07:00
|
|
|
}
|