110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
import * as BufferLayout from '@solana/buffer-layout';
|
|
|
|
import {Signer} from '../keypair';
|
|
import assert from '../utils/assert';
|
|
import {VersionedMessage} from '../message/versioned';
|
|
import {SIGNATURE_LENGTH_IN_BYTES} from './constants';
|
|
import * as shortvec from '../utils/shortvec-encoding';
|
|
import * as Layout from '../layout';
|
|
import {sign} from '../utils/ed25519';
|
|
|
|
export type TransactionVersion = 'legacy' | 0;
|
|
|
|
/**
|
|
* Versioned transaction class
|
|
*/
|
|
export class VersionedTransaction {
|
|
signatures: Array<Uint8Array>;
|
|
message: VersionedMessage;
|
|
|
|
get version(): TransactionVersion {
|
|
return this.message.version;
|
|
}
|
|
|
|
constructor(message: VersionedMessage, signatures?: Array<Uint8Array>) {
|
|
if (signatures !== undefined) {
|
|
assert(
|
|
signatures.length === message.header.numRequiredSignatures,
|
|
'Expected signatures length to be equal to the number of required signatures',
|
|
);
|
|
this.signatures = signatures;
|
|
} else {
|
|
const defaultSignatures = [];
|
|
for (let i = 0; i < message.header.numRequiredSignatures; i++) {
|
|
defaultSignatures.push(new Uint8Array(SIGNATURE_LENGTH_IN_BYTES));
|
|
}
|
|
this.signatures = defaultSignatures;
|
|
}
|
|
this.message = message;
|
|
}
|
|
|
|
serialize(): Uint8Array {
|
|
const serializedMessage = this.message.serialize();
|
|
|
|
const encodedSignaturesLength = Array<number>();
|
|
shortvec.encodeLength(encodedSignaturesLength, this.signatures.length);
|
|
|
|
const transactionLayout = BufferLayout.struct<{
|
|
encodedSignaturesLength: Uint8Array;
|
|
signatures: Array<Uint8Array>;
|
|
serializedMessage: Uint8Array;
|
|
}>([
|
|
BufferLayout.blob(
|
|
encodedSignaturesLength.length,
|
|
'encodedSignaturesLength',
|
|
),
|
|
BufferLayout.seq(
|
|
Layout.signature(),
|
|
this.signatures.length,
|
|
'signatures',
|
|
),
|
|
BufferLayout.blob(serializedMessage.length, 'serializedMessage'),
|
|
]);
|
|
|
|
const serializedTransaction = new Uint8Array(2048);
|
|
const serializedTransactionLength = transactionLayout.encode(
|
|
{
|
|
encodedSignaturesLength: new Uint8Array(encodedSignaturesLength),
|
|
signatures: this.signatures,
|
|
serializedMessage,
|
|
},
|
|
serializedTransaction,
|
|
);
|
|
|
|
return serializedTransaction.slice(0, serializedTransactionLength);
|
|
}
|
|
|
|
static deserialize(serializedTransaction: Uint8Array): VersionedTransaction {
|
|
let byteArray = [...serializedTransaction];
|
|
|
|
const signatures = [];
|
|
const signaturesLength = shortvec.decodeLength(byteArray);
|
|
for (let i = 0; i < signaturesLength; i++) {
|
|
signatures.push(
|
|
new Uint8Array(byteArray.splice(0, SIGNATURE_LENGTH_IN_BYTES)),
|
|
);
|
|
}
|
|
|
|
const message = VersionedMessage.deserialize(new Uint8Array(byteArray));
|
|
return new VersionedTransaction(message, signatures);
|
|
}
|
|
|
|
sign(signers: Array<Signer>) {
|
|
const messageData = this.message.serialize();
|
|
const signerPubkeys = this.message.staticAccountKeys.slice(
|
|
0,
|
|
this.message.header.numRequiredSignatures,
|
|
);
|
|
for (const signer of signers) {
|
|
const signerIndex = signerPubkeys.findIndex(pubkey =>
|
|
pubkey.equals(signer.publicKey),
|
|
);
|
|
assert(
|
|
signerIndex >= 0,
|
|
`Cannot sign with non signer key ${signer.publicKey.toBase58()}`,
|
|
);
|
|
this.signatures[signerIndex] = sign(messageData, signer.secretKey);
|
|
}
|
|
}
|
|
}
|