@coolwallet/sol
Version:
Coolwallet Solana sdk
501 lines (462 loc) • 19.6 kB
text/typescript
import * as BufferLayout from '@solana/buffer-layout';
import { encodeLength, numberToStringHex } from '../utils/stringUtil';
import { publicKey } from '../utils/commonLayout';
import { PACKET_DATA_SIZE, PADDING_PUBLICKEY, PUBLIC_KEY_LENGTH, VERSION_PREFIX_MASK } from '../config/params';
import { CompiledInstruction, CompliedInstruction, SerializedInstruction } from '../config/types';
import * as shortvec from '../utils/shortvec-encoding';
import {
encodeAndPaddingSeedInstructionBuffer,
encodeInstructionBuffer,
encodeSignData,
initAndPaddingComputeBudgetInstructionBuffer,
} from '../utils/bufferUtils';
type MessageHeader = {
numRequiredSignatures: number;
numReadonlySignedAccounts: number;
numReadonlyUnsignedAccounts: number;
};
type MessageArgs = {
header: MessageHeader;
accountKeys: string[];
recentBlockhash: string;
instructions: CompliedInstruction[];
};
export class Message {
header: MessageHeader;
accountKeys: string[];
recentBlockhash: string;
instructions: CompliedInstruction[];
constructor(message: MessageArgs) {
this.header = message.header;
this.accountKeys = message.accountKeys;
this.recentBlockhash = message.recentBlockhash;
this.instructions = message.instructions;
}
serializeHeader(): string {
return (
numberToStringHex(this.header.numRequiredSignatures, 2) +
numberToStringHex(this.header.numReadonlySignedAccounts, 2) +
numberToStringHex(this.header.numReadonlyUnsignedAccounts, 2)
);
}
encodedLength(): { keyCount: number[]; instructionCount: number[]; instructions: SerializedInstruction[] } {
// encode key count
const numKeys = this.accountKeys.length;
const keyCount: number[] = [];
encodeLength(keyCount, numKeys);
// extract input instructions to serialize instructions
const instructions = this.instructions.map((instruction) => {
const { accounts, programIdIndex } = instruction;
const data = Array.from(Buffer.from(instruction.data, 'hex'));
const keyIndicesCount: number[] = [];
encodeLength(keyIndicesCount, accounts.length);
const dataCount: number[] = [];
encodeLength(dataCount, data.length);
return {
programIdIndex,
keyIndicesCount: Buffer.from(keyIndicesCount),
keyIndices: accounts,
dataLength: Buffer.from(dataCount),
data,
};
});
// encode instruction count
const instructionCount: number[] = [];
encodeLength(instructionCount, instructions.length);
return { keyCount, instructionCount, instructions };
}
serialize(): Buffer {
const { keyCount, instructionCount, instructions } = this.encodedLength();
// encode instruction
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE);
Buffer.from(instructionCount).copy(instructionBuffer);
let instructionBufferLength = instructionCount.length;
instructions.forEach((instruction) => {
const instructionLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
keyIndices: number[];
keyIndicesCount: Uint8Array;
programIdIndex: number;
}>
>([
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'),
]);
instructionBufferLength += instructionLayout.encode(instruction, instructionBuffer, instructionBufferLength);
});
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength);
// encode sign data
const signDataLayout = BufferLayout.struct<
Readonly<{
keyCount: Uint8Array;
keys: Uint8Array[];
numReadonlySignedAccounts: Uint8Array;
numReadonlyUnsignedAccounts: Uint8Array;
numRequiredSignatures: Uint8Array;
recentBlockhash: Uint8Array;
}>
>([
BufferLayout.blob(1, 'numRequiredSignatures'),
BufferLayout.blob(1, 'numReadonlySignedAccounts'),
BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'),
BufferLayout.blob(keyCount.length, 'keyCount'),
BufferLayout.seq(publicKey('key'), this.accountKeys.length, '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) => Buffer.from(key, 'hex')),
recentBlockhash: Buffer.from(this.recentBlockhash, 'hex'),
};
const signData = Buffer.alloc(2048); // sign data max length
const length = signDataLayout.encode(transaction, signData);
instructionBuffer.copy(signData, length);
// return signData.slice(0, length + instructionBuffer.length).toString('hex');
return signData.slice(0, length + instructionBuffer.length);
}
serializeTransferMessage(): string {
const { keyCount, instructions } = this.encodedLength();
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE);
let gasPrice;
let gasLimit;
let transfer;
let instructionBufferLength = 0;
if (instructions.length === 3) {
[gasPrice, gasLimit, transfer] = instructions;
const gasPriceLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(gasPrice.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), gasPrice.keyIndices.length, 'keyIndices'),
BufferLayout.blob(gasPrice.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), gasPrice.data.length, 'data'),
]);
instructionBufferLength = gasPriceLayout.encode(gasPrice, instructionBuffer, 0);
const gasLimitLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(gasLimit.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), gasLimit.keyIndices.length, 'keyIndices'),
BufferLayout.blob(gasLimit.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), gasLimit.data.length, 'data'),
]);
instructionBufferLength += gasLimitLayout.encode(gasLimit, instructionBuffer, instructionBufferLength);
} else {
[transfer] = instructions;
}
const transferLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(transfer.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), transfer.keyIndices.length, 'keyIndices'),
BufferLayout.blob(transfer.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), transfer.data.length, 'data'),
]);
instructionBufferLength += transferLayout.encode(transfer, instructionBuffer, instructionBufferLength);
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength);
let accountKeys = [...this.accountKeys];
for (let i = this.accountKeys.length; i < 6; i++) {
accountKeys = accountKeys.concat(PADDING_PUBLICKEY);
}
const signDataLayout = BufferLayout.struct<
Readonly<{
keyCount: Uint8Array;
keys: Uint8Array[];
recentBlockhash: Uint8Array;
}>
>([
BufferLayout.blob(keyCount.length, 'keyCount'),
BufferLayout.seq(publicKey('key'), accountKeys.length, 'keys'),
publicKey('recentBlockhash'),
]);
const transaction = {
keyCount: Buffer.from(keyCount),
keys: accountKeys.map((key) => Buffer.from(key, 'hex')),
recentBlockhash: Buffer.from(this.recentBlockhash, 'hex'),
};
const signData = Buffer.alloc(2048); // sign data max length
const length = signDataLayout.encode(transaction, signData);
instructionBuffer.copy(signData, length);
return signData.slice(0, length + instructionBuffer.length).toString('hex');
}
serializeCreateAndTransferSPLToken(): string {
const { keyCount, instructions } = this.encodedLength();
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE);
let associateAccount;
let gasPrice;
let gasLimit;
let tokenTransfer;
let instructionBufferLength = 0;
if (instructions.length === 4) {
[associateAccount, gasPrice, gasLimit, tokenTransfer] = instructions;
const associateAccountLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(associateAccount.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), associateAccount.keyIndices.length, 'keyIndices'),
BufferLayout.blob(associateAccount.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), associateAccount.data.length, 'data'),
]);
instructionBufferLength = associateAccountLayout.encode(associateAccount, instructionBuffer, 0);
const gasPriceLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(gasPrice.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), gasPrice.keyIndices.length, 'keyIndices'),
BufferLayout.blob(gasPrice.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), gasPrice.data.length, 'data'),
]);
instructionBufferLength += gasPriceLayout.encode(gasPrice, instructionBuffer, instructionBufferLength);
const gasLimitLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(gasLimit.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), gasLimit.keyIndices.length, 'keyIndices'),
BufferLayout.blob(gasLimit.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), gasLimit.data.length, 'data'),
]);
instructionBufferLength += gasLimitLayout.encode(gasLimit, instructionBuffer, instructionBufferLength);
} else {
[associateAccount, tokenTransfer] = instructions;
const associateAccountLayout = BufferLayout.struct<
Readonly<{
data: number[];
dataLength: Uint8Array;
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(associateAccount.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), associateAccount.keyIndices.length, 'keyIndices'),
BufferLayout.blob(associateAccount.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), associateAccount.data.length, 'data'),
]);
instructionBufferLength = associateAccountLayout.encode(associateAccount, instructionBuffer, 0);
}
const tokenTransferLayout = BufferLayout.struct<
Readonly<{
programIdIndex: number;
keyIndices: number[];
keyIndicesCount: Uint8Array;
data: number[];
dataLength: Uint8Array;
}>
>([
BufferLayout.u8('programIdIndex'),
BufferLayout.blob(tokenTransfer.keyIndicesCount.length, 'keyIndicesCount'),
BufferLayout.seq(BufferLayout.u8('keyIndex'), tokenTransfer.keyIndices.length, 'keyIndices'),
BufferLayout.blob(tokenTransfer.dataLength.length, 'dataLength'),
BufferLayout.seq(BufferLayout.u8('userdatum'), tokenTransfer.data.length, 'data'),
]);
instructionBufferLength += tokenTransferLayout.encode(tokenTransfer, instructionBuffer, instructionBufferLength);
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength);
let accountKeys = [...this.accountKeys];
for (let i = this.accountKeys.length; i < 9; i++) {
accountKeys = accountKeys.concat(Buffer.alloc(32).toString('hex'));
}
const signDataLayout = BufferLayout.struct<
Readonly<{
keyCount: Uint8Array;
keys: Uint8Array[];
recentBlockhash: Uint8Array;
}>
>([
BufferLayout.blob(keyCount.length, 'keyCount'),
BufferLayout.seq(publicKey('key'), accountKeys.length, 'keys'),
publicKey('recentBlockhash'),
]);
const transaction = {
keyCount: Buffer.from(keyCount),
keys: accountKeys.map((key) => Buffer.from(key, 'hex')),
recentBlockhash: Buffer.from(this.recentBlockhash, 'hex'),
};
const signData = Buffer.alloc(2048); // sign data max length
const length = signDataLayout.encode(transaction, signData);
instructionBuffer.copy(signData, length);
return signData.slice(0, length + instructionBuffer.length).toString('hex');
}
reservePaddingAccountKeys(maxAccountKeyLength: number): string[] {
const accountKeys = this.accountKeys;
const needPaddingKeyLength = maxAccountKeyLength - this.accountKeys.length;
for (let i = 0; i < needPaddingKeyLength; i++) {
accountKeys.push(PADDING_PUBLICKEY);
}
return accountKeys;
}
serializeDelegateAndCreateAccountWithSeed(): string {
const { keyCount, instructionCount, instructions } = this.encodedLength();
const initAndPaddingInstruction = initAndPaddingComputeBudgetInstructionBuffer(
this.accountKeys,
instructions,
instructionCount
);
const instructionBuffer = encodeAndPaddingSeedInstructionBuffer(
this.accountKeys,
instructions,
initAndPaddingInstruction.instructionBuffer,
initAndPaddingInstruction.instructionBufferLength
);
/**
* signerPubkey
* stakeAccountPubkey
* 11111111111111111111111111111111
* validatorPubkey
* Stake11111111111111111111111111111111111111
* StakeConfig11111111111111111111111111111111
* SysvarC1ock11111111111111111111111111111111
* SysvarRent111111111111111111111111111111111
* SysvarStakeHistory1111111111111111111111111
* ComputeBudget111111111111111111111111111111 (optional)
*/
const newAccountKeys = this.reservePaddingAccountKeys(10);
return encodeSignData(keyCount, newAccountKeys, this.recentBlockhash, instructionBuffer).toString('hex');
}
serializeUndelegate(): string {
const { keyCount, instructionCount, instructions } = this.encodedLength();
const initAndPaddingInstruction = initAndPaddingComputeBudgetInstructionBuffer(
this.accountKeys,
instructions,
instructionCount
);
const instructionBuffer = encodeInstructionBuffer(
instructions,
initAndPaddingInstruction.instructionBuffer,
initAndPaddingInstruction.instructionBufferLength
);
/**
* signerPubkey
* stakeAccountPubkey
* Stake11111111111111111111111111111111111111
* SysvarC1ock11111111111111111111111111111111
* ComputeBudget111111111111111111111111111111 (optional)
*/
const newAccountKeys = this.reservePaddingAccountKeys(5);
return encodeSignData(keyCount, newAccountKeys, this.recentBlockhash, instructionBuffer).toString('hex');
}
serializeWithdraw(): string {
const { keyCount, instructionCount, instructions } = this.encodedLength();
const initAndPaddingInstruction = initAndPaddingComputeBudgetInstructionBuffer(
this.accountKeys,
instructions,
instructionCount
);
const instructionBuffer = encodeInstructionBuffer(
instructions,
initAndPaddingInstruction.instructionBuffer,
initAndPaddingInstruction.instructionBufferLength
);
/**
* signerPubkey
* stakeAccountPubkey
* withdrawToPubkey (optional)
* Stake11111111111111111111111111111111111111
* SysvarC1ock11111111111111111111111111111111
* SysvarStakeHistory1111111111111111111111111
* ComputeBudget111111111111111111111111111111 (optional)
*/
const newAccountKeys = this.reservePaddingAccountKeys(7);
return encodeSignData(keyCount, newAccountKeys, this.recentBlockhash, instructionBuffer).toString('hex');
}
/**
* Decode a compiled message into a Message object.
*/
static from(buffer: Buffer | Uint8Array | Array<number>): Message {
// 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 = shortvec.decodeLength(byteArray);
const accountKeys = [];
for (let i = 0; i < accountCount; i++) {
const account = byteArray.slice(0, PUBLIC_KEY_LENGTH);
byteArray = byteArray.slice(PUBLIC_KEY_LENGTH);
accountKeys.push(Buffer.from(account).toString('hex'));
}
const recentBlockhash = byteArray.slice(0, PUBLIC_KEY_LENGTH);
byteArray = byteArray.slice(PUBLIC_KEY_LENGTH);
const instructionCount = shortvec.decodeLength(byteArray);
const instructions: CompiledInstruction[] = [];
for (let i = 0; i < instructionCount; i++) {
const programIdIndex = byteArray.shift()!;
const instructionAccountCount = shortvec.decodeLength(byteArray);
const accounts = byteArray.slice(0, instructionAccountCount);
byteArray = byteArray.slice(instructionAccountCount);
const dataLength = shortvec.decodeLength(byteArray);
const dataSlice = byteArray.slice(0, dataLength);
const data = Buffer.from(dataSlice).toString('hex');
byteArray = byteArray.slice(dataLength);
instructions.push({
programIdIndex,
accounts,
data,
});
}
const messageArgs = {
header: {
numRequiredSignatures,
numReadonlySignedAccounts,
numReadonlyUnsignedAccounts,
},
recentBlockhash: Buffer.from(recentBlockhash).toString('hex'),
accountKeys,
instructions,
};
return new Message(messageArgs);
}
}