UNPKG

@coolwallet/sol

Version:
501 lines (462 loc) 19.6 kB
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); } }