@exodus/solana-lib 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/package.json +26 -0
- package/src/encode.js +29 -0
- package/src/fee-data/index.js +1 -0
- package/src/fee-data/solana.js +9 -0
- package/src/helpers/account.js +38 -0
- package/src/helpers/blockhash.js +6 -0
- package/src/helpers/fee-calculator.js +17 -0
- package/src/helpers/instruction.js +46 -0
- package/src/helpers/layout.js +80 -0
- package/src/helpers/message.js +216 -0
- package/src/helpers/nonce-account.js +46 -0
- package/src/helpers/publickey.js +209 -0
- package/src/helpers/shortvec-encoding.js +30 -0
- package/src/helpers/system-program.js +782 -0
- package/src/helpers/sysvar.js +16 -0
- package/src/helpers/transaction.js +593 -0
- package/src/helpers/util/to-buffer.js +9 -0
- package/src/index.js +6 -0
- package/src/keypair.js +32 -0
- package/src/transaction.js +64 -0
- package/src/tx/create-and-sign-tx.js +8 -0
- package/src/tx/create-unsigned-tx.js +21 -0
- package/src/tx/index.js +4 -0
- package/src/tx/parse-unsigned-tx.js +16 -0
- package/src/tx/sign-unsigned-tx.js +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Solana lib · [](https://www.npmjs.com/package/@exodus/solana-lib)
|
|
2
|
+
|
|
3
|
+
Web wallet example by Solana: https://github.com/solana-labs/example-webwallet
|
|
4
|
+
|
|
5
|
+
Official lib sdk: https://github.com/solana-labs/solana-web3.js
|
|
6
|
+
|
|
7
|
+
- The smallest unit is called Lamports. 9 digits precision (a Lamport is 0.000000001 SOL)
|
|
8
|
+
- In addition to fees, Solana has the "Rent" concept, paid every epoch by wallets. If you keep a certain amount of SOL locked (accountReserve) you'd have rent-exemption (https://docs.solana.com/apps/rent#rent-exemption), `0.01 SOL` is more than enough (https://github.com/solana-labs/solana/issues/12332)
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@exodus/solana-lib",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Exodus internal Solana low-level library",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/",
|
|
8
|
+
"!src/__tests__"
|
|
9
|
+
],
|
|
10
|
+
"author": "Exodus",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "restricted"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@exodus/asset-lib": "^3.4.3",
|
|
17
|
+
"@exodus/assets": "^8.0.0",
|
|
18
|
+
"@exodus/buffer-layout": "^1.2.0-exodus0",
|
|
19
|
+
"@exodus/models": "^8.2.0",
|
|
20
|
+
"bn.js": "~4.11.0",
|
|
21
|
+
"bs58": "~4.0.1",
|
|
22
|
+
"create-hash": "~1.1.3",
|
|
23
|
+
"tweetnacl": "^1.0.3"
|
|
24
|
+
},
|
|
25
|
+
"gitHead": "7afc2c44aff00085db4d14b7696a3fc88f165628"
|
|
26
|
+
}
|
package/src/encode.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import { getPublicKey } from './keypair'
|
|
3
|
+
import bs58 from 'bs58'
|
|
4
|
+
import BN from 'bn.js'
|
|
5
|
+
|
|
6
|
+
export function getAddressFromPublicKey(publicKey: string | Buffer): string {
|
|
7
|
+
return bs58.encode(Buffer.from(publicKey, 'hex'))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getAddressFromPrivateKey(privateKey: string | Buffer): string {
|
|
11
|
+
return getAddressFromPublicKey(getPublicKey(privateKey))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isValidAddress(address: string): boolean {
|
|
15
|
+
try {
|
|
16
|
+
// assume base 58 encoding by default
|
|
17
|
+
const decoded = bs58.decode(address)
|
|
18
|
+
if (decoded.length !== 32) {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
const _bn = new BN(decoded)
|
|
22
|
+
if (_bn.byteLength() > 32) {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
return true
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as solana } from './solana'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import type { KeyPair } from 'tweetnacl'
|
|
3
|
+
import { PublicKey } from './publickey'
|
|
4
|
+
import { getKeyPairFromPrivateKey } from '../keypair'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* An account key pair (public and secret keys).
|
|
8
|
+
*/
|
|
9
|
+
export class Account {
|
|
10
|
+
_keypair: KeyPair
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a new Account object
|
|
14
|
+
*
|
|
15
|
+
* @param secretKey Secret key for the account
|
|
16
|
+
*/
|
|
17
|
+
constructor(secretKey: Buffer | Uint8Array | Array<number>) {
|
|
18
|
+
if (secretKey) {
|
|
19
|
+
this._keypair = getKeyPairFromPrivateKey(Buffer.from(secretKey, 'hex'))
|
|
20
|
+
} else {
|
|
21
|
+
throw new Error('Please provide a secretKey')
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The public key for this account
|
|
27
|
+
*/
|
|
28
|
+
get publicKey(): PublicKey {
|
|
29
|
+
return new PublicKey(this._keypair.publicKey)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The **unencrypted** secret key for this account
|
|
34
|
+
*/
|
|
35
|
+
get secretKey(): Buffer {
|
|
36
|
+
return this._keypair.secretKey
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* https://github.com/solana-labs/solana/blob/90bedd7e067b5b8f3ddbb45da00a4e9cabb22c62/sdk/src/fee_calculator.rs#L7-L11
|
|
6
|
+
*
|
|
7
|
+
* @private
|
|
8
|
+
*/
|
|
9
|
+
export const FeeCalculatorLayout = BufferLayout.nu64('lamportsPerSignature')
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} FeeCalculator
|
|
13
|
+
* @property {number} lamportsPerSignature lamports Cost in lamports to validate a signature
|
|
14
|
+
*/
|
|
15
|
+
export type FeeCalculator = {
|
|
16
|
+
lamportsPerSignature: number,
|
|
17
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
4
|
+
|
|
5
|
+
import * as Layout from './layout'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} InstructionType
|
|
9
|
+
* @property (index} The Instruction index (from solana upstream program)
|
|
10
|
+
* @property (BufferLayout} The BufferLayout to use to build data
|
|
11
|
+
*/
|
|
12
|
+
export type InstructionType = {|
|
|
13
|
+
index: number,
|
|
14
|
+
layout: typeof BufferLayout,
|
|
15
|
+
|}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Populate a buffer of instruction data using an InstructionType
|
|
19
|
+
*/
|
|
20
|
+
export function encodeData(type: InstructionType, fields: Object): Buffer {
|
|
21
|
+
const allocLength = type.layout.span >= 0 ? type.layout.span : Layout.getAlloc(type, fields)
|
|
22
|
+
const data = Buffer.alloc(allocLength)
|
|
23
|
+
const layoutFields = Object.assign({ instruction: type.index }, fields)
|
|
24
|
+
type.layout.encode(layoutFields, data)
|
|
25
|
+
return data
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Decode instruction data buffer using an InstructionType
|
|
30
|
+
*/
|
|
31
|
+
export function decodeData(type: InstructionType, buffer: Buffer): Object {
|
|
32
|
+
let data
|
|
33
|
+
try {
|
|
34
|
+
data = type.layout.decode(buffer)
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw new Error('invalid instruction; ' + err)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (data.instruction !== type.index) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`invalid instruction; instruction index mismatch ${data.instruction} != ${type.index}`
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return data
|
|
46
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Layout for a public key
|
|
7
|
+
*/
|
|
8
|
+
export const publicKey = (property: string = 'publicKey'): Object => {
|
|
9
|
+
return BufferLayout.blob(32, property)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Layout for a 64bit unsigned value
|
|
14
|
+
*/
|
|
15
|
+
export const uint64 = (property: string = 'uint64'): Object => {
|
|
16
|
+
return BufferLayout.blob(8, property)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Layout for a Rust String type
|
|
21
|
+
*/
|
|
22
|
+
export const rustString = (property: string = 'string') => {
|
|
23
|
+
const rsl = BufferLayout.struct(
|
|
24
|
+
[
|
|
25
|
+
BufferLayout.u32('length'),
|
|
26
|
+
BufferLayout.u32('lengthPadding'),
|
|
27
|
+
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
|
|
28
|
+
],
|
|
29
|
+
property
|
|
30
|
+
)
|
|
31
|
+
const _decode = rsl.decode.bind(rsl)
|
|
32
|
+
const _encode = rsl.encode.bind(rsl)
|
|
33
|
+
|
|
34
|
+
rsl.decode = (buffer, offset) => {
|
|
35
|
+
const data = _decode(buffer, offset)
|
|
36
|
+
return data.chars.toString('utf8')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
rsl.encode = (str, buffer, offset) => {
|
|
40
|
+
const data = {
|
|
41
|
+
chars: Buffer.from(str, 'utf8'),
|
|
42
|
+
}
|
|
43
|
+
return _encode(data, buffer, offset)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
rsl.alloc = (str) => {
|
|
47
|
+
return BufferLayout.u32().span + BufferLayout.u32().span + Buffer.from(str, 'utf8').length
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return rsl
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Layout for an Authorized object
|
|
55
|
+
*/
|
|
56
|
+
export const authorized = (property: string = 'authorized') => {
|
|
57
|
+
return BufferLayout.struct([publicKey('staker'), publicKey('withdrawer')], property)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Layout for a Lockup object
|
|
62
|
+
*/
|
|
63
|
+
export const lockup = (property: string = 'lockup') => {
|
|
64
|
+
return BufferLayout.struct(
|
|
65
|
+
[BufferLayout.ns64('unixTimestamp'), BufferLayout.ns64('epoch'), publicKey('custodian')],
|
|
66
|
+
property
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getAlloc(type: Object, fields: Object): number {
|
|
71
|
+
let alloc = 0
|
|
72
|
+
type.layout.fields.forEach((item) => {
|
|
73
|
+
if (item.span >= 0) {
|
|
74
|
+
alloc += item.span
|
|
75
|
+
} else if (typeof item.alloc === 'function') {
|
|
76
|
+
alloc += item.alloc(fields[item.property])
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
return alloc
|
|
80
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import bs58 from 'bs58'
|
|
4
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
5
|
+
|
|
6
|
+
import { PublicKey } from './publickey'
|
|
7
|
+
import type { Blockhash } from './blockhash'
|
|
8
|
+
import * as Layout from './layout'
|
|
9
|
+
import { PACKET_DATA_SIZE } from './transaction'
|
|
10
|
+
import * as shortvec from './shortvec-encoding'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The message header, identifying signed and read-only account
|
|
14
|
+
*
|
|
15
|
+
* @typedef {Object} MessageHeader
|
|
16
|
+
* @property {number} numRequiredSignatures The number of signatures required for this message to be considered valid. The
|
|
17
|
+
* signatures must match the first `numRequiredSignatures` of `accountKeys`.
|
|
18
|
+
* @property {number} numReadonlySignedAccounts: The last `numReadonlySignedAccounts` of the signed keys are read-only accounts
|
|
19
|
+
* @property {number} numReadonlyUnsignedAccounts The last `numReadonlySignedAccounts` of the unsigned keys are read-only accounts
|
|
20
|
+
*/
|
|
21
|
+
export type MessageHeader = {
|
|
22
|
+
numRequiredSignatures: number,
|
|
23
|
+
numReadonlySignedAccounts: number,
|
|
24
|
+
numReadonlyUnsignedAccounts: number,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* An instruction to execute by a program
|
|
29
|
+
*
|
|
30
|
+
* @typedef {Object} CompiledInstruction
|
|
31
|
+
* @property {number} programIdIndex Index into the transaction keys array indicating the program account that executes this instruction
|
|
32
|
+
* @property {number[]} accounts Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
|
33
|
+
* @property {string} data The program input data encoded as base 58
|
|
34
|
+
*/
|
|
35
|
+
export type CompiledInstruction = {
|
|
36
|
+
programIdIndex: number,
|
|
37
|
+
accounts: number[],
|
|
38
|
+
data: string,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Message constructor arguments
|
|
43
|
+
*
|
|
44
|
+
* @typedef {Object} MessageArgs
|
|
45
|
+
* @property {MessageHeader} header The message header, identifying signed and read-only `accountKeys`
|
|
46
|
+
* @property {string[]} accounts All the account keys used by this transaction
|
|
47
|
+
* @property {Blockhash} recentBlockhash The hash of a recent ledger block
|
|
48
|
+
* @property {CompiledInstruction[]} instructions Instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
|
|
49
|
+
*/
|
|
50
|
+
type MessageArgs = {
|
|
51
|
+
header: MessageHeader,
|
|
52
|
+
accountKeys: string[],
|
|
53
|
+
recentBlockhash: Blockhash,
|
|
54
|
+
instructions: CompiledInstruction[],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const PUBKEY_LENGTH = 32
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* List of instructions to be processed atomically
|
|
61
|
+
*/
|
|
62
|
+
export class Message {
|
|
63
|
+
header: MessageHeader
|
|
64
|
+
accountKeys: PublicKey[]
|
|
65
|
+
recentBlockhash: Blockhash
|
|
66
|
+
instructions: CompiledInstruction[]
|
|
67
|
+
|
|
68
|
+
constructor(args: MessageArgs) {
|
|
69
|
+
this.header = args.header
|
|
70
|
+
this.accountKeys = args.accountKeys.map((account) => new PublicKey(account))
|
|
71
|
+
this.recentBlockhash = args.recentBlockhash
|
|
72
|
+
this.instructions = args.instructions
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
isAccountWritable(index: number): boolean {
|
|
76
|
+
return (
|
|
77
|
+
index < this.header.numRequiredSignatures - this.header.numReadonlySignedAccounts ||
|
|
78
|
+
(index >= this.header.numRequiredSignatures &&
|
|
79
|
+
index < this.accountKeys.length - this.header.numReadonlyUnsignedAccounts)
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
findSignerIndex(signer: PublicKey): number {
|
|
84
|
+
const index = this.accountKeys.findIndex((accountKey) => {
|
|
85
|
+
return accountKey.equals(signer)
|
|
86
|
+
})
|
|
87
|
+
if (index < 0) {
|
|
88
|
+
throw new Error(`unknown signer: ${signer.toString()}`)
|
|
89
|
+
}
|
|
90
|
+
return index
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
serialize(): Buffer {
|
|
94
|
+
const numKeys = this.accountKeys.length
|
|
95
|
+
|
|
96
|
+
let keyCount = []
|
|
97
|
+
shortvec.encodeLength(keyCount, numKeys)
|
|
98
|
+
|
|
99
|
+
const instructions = this.instructions.map((instruction) => {
|
|
100
|
+
const { accounts, programIdIndex } = instruction
|
|
101
|
+
const data = bs58.decode(instruction.data)
|
|
102
|
+
|
|
103
|
+
let keyIndicesCount = []
|
|
104
|
+
shortvec.encodeLength(keyIndicesCount, accounts.length)
|
|
105
|
+
|
|
106
|
+
let dataCount = []
|
|
107
|
+
shortvec.encodeLength(dataCount, data.length)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
programIdIndex,
|
|
111
|
+
keyIndicesCount: Buffer.from(keyIndicesCount),
|
|
112
|
+
keyIndices: Buffer.from(accounts),
|
|
113
|
+
dataLength: Buffer.from(dataCount),
|
|
114
|
+
data,
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
let instructionCount = []
|
|
119
|
+
shortvec.encodeLength(instructionCount, instructions.length)
|
|
120
|
+
let instructionBuffer = Buffer.alloc(PACKET_DATA_SIZE)
|
|
121
|
+
Buffer.from(instructionCount).copy(instructionBuffer)
|
|
122
|
+
let instructionBufferLength = instructionCount.length
|
|
123
|
+
|
|
124
|
+
instructions.forEach((instruction) => {
|
|
125
|
+
const instructionLayout = BufferLayout.struct([
|
|
126
|
+
BufferLayout.u8('programIdIndex'),
|
|
127
|
+
|
|
128
|
+
BufferLayout.blob(instruction.keyIndicesCount.length, 'keyIndicesCount'),
|
|
129
|
+
BufferLayout.seq(BufferLayout.u8('keyIndex'), instruction.keyIndices.length, 'keyIndices'),
|
|
130
|
+
BufferLayout.blob(instruction.dataLength.length, 'dataLength'),
|
|
131
|
+
BufferLayout.seq(BufferLayout.u8('userdatum'), instruction.data.length, 'data'),
|
|
132
|
+
])
|
|
133
|
+
const length = instructionLayout.encode(
|
|
134
|
+
instruction,
|
|
135
|
+
instructionBuffer,
|
|
136
|
+
instructionBufferLength
|
|
137
|
+
)
|
|
138
|
+
instructionBufferLength += length
|
|
139
|
+
})
|
|
140
|
+
instructionBuffer = instructionBuffer.slice(0, instructionBufferLength)
|
|
141
|
+
|
|
142
|
+
const signDataLayout = BufferLayout.struct([
|
|
143
|
+
BufferLayout.blob(1, 'numRequiredSignatures'),
|
|
144
|
+
BufferLayout.blob(1, 'numReadonlySignedAccounts'),
|
|
145
|
+
BufferLayout.blob(1, 'numReadonlyUnsignedAccounts'),
|
|
146
|
+
BufferLayout.blob(keyCount.length, 'keyCount'),
|
|
147
|
+
BufferLayout.seq(Layout.publicKey('key'), numKeys, 'keys'),
|
|
148
|
+
Layout.publicKey('recentBlockhash'),
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
const transaction = {
|
|
152
|
+
numRequiredSignatures: Buffer.from([this.header.numRequiredSignatures]),
|
|
153
|
+
numReadonlySignedAccounts: Buffer.from([this.header.numReadonlySignedAccounts]),
|
|
154
|
+
numReadonlyUnsignedAccounts: Buffer.from([this.header.numReadonlyUnsignedAccounts]),
|
|
155
|
+
keyCount: Buffer.from(keyCount),
|
|
156
|
+
keys: this.accountKeys.map((key) => key.toBuffer()),
|
|
157
|
+
recentBlockhash: bs58.decode(this.recentBlockhash),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let signData = Buffer.alloc(2048)
|
|
161
|
+
const length = signDataLayout.encode(transaction, signData)
|
|
162
|
+
instructionBuffer.copy(signData, length)
|
|
163
|
+
return signData.slice(0, length + instructionBuffer.length)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Decode a compiled message into a Message object.
|
|
168
|
+
*/
|
|
169
|
+
static from(buffer: Buffer | Uint8Array | Array<number>): Message {
|
|
170
|
+
// Slice up wire data
|
|
171
|
+
let byteArray = [...buffer]
|
|
172
|
+
|
|
173
|
+
const numRequiredSignatures = byteArray.shift()
|
|
174
|
+
const numReadonlySignedAccounts = byteArray.shift()
|
|
175
|
+
const numReadonlyUnsignedAccounts = byteArray.shift()
|
|
176
|
+
|
|
177
|
+
const accountCount = shortvec.decodeLength(byteArray)
|
|
178
|
+
let accountKeys = []
|
|
179
|
+
for (let i = 0; i < accountCount; i++) {
|
|
180
|
+
const account = byteArray.slice(0, PUBKEY_LENGTH)
|
|
181
|
+
byteArray = byteArray.slice(PUBKEY_LENGTH)
|
|
182
|
+
accountKeys.push(bs58.encode(Buffer.from(account)))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const recentBlockhash = byteArray.slice(0, PUBKEY_LENGTH)
|
|
186
|
+
byteArray = byteArray.slice(PUBKEY_LENGTH)
|
|
187
|
+
|
|
188
|
+
const instructionCount = shortvec.decodeLength(byteArray)
|
|
189
|
+
let instructions = []
|
|
190
|
+
for (let i = 0; i < instructionCount; i++) {
|
|
191
|
+
let instruction = {}
|
|
192
|
+
instruction.programIdIndex = byteArray.shift()
|
|
193
|
+
const accountCount = shortvec.decodeLength(byteArray)
|
|
194
|
+
instruction.accounts = byteArray.slice(0, accountCount)
|
|
195
|
+
byteArray = byteArray.slice(accountCount)
|
|
196
|
+
const dataLength = shortvec.decodeLength(byteArray)
|
|
197
|
+
const data = byteArray.slice(0, dataLength)
|
|
198
|
+
instruction.data = bs58.encode(Buffer.from(data))
|
|
199
|
+
byteArray = byteArray.slice(dataLength)
|
|
200
|
+
instructions.push(instruction)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const messageArgs = {
|
|
204
|
+
header: {
|
|
205
|
+
numRequiredSignatures,
|
|
206
|
+
numReadonlySignedAccounts,
|
|
207
|
+
numReadonlyUnsignedAccounts,
|
|
208
|
+
},
|
|
209
|
+
recentBlockhash: bs58.encode(Buffer.from(recentBlockhash)),
|
|
210
|
+
accountKeys,
|
|
211
|
+
instructions,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return new Message(messageArgs)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as BufferLayout from '@exodus/buffer-layout'
|
|
3
|
+
|
|
4
|
+
import type { Blockhash } from './blockhash'
|
|
5
|
+
import * as Layout from './layout'
|
|
6
|
+
import { PublicKey } from './publickey'
|
|
7
|
+
import type { FeeCalculator } from './fee-calculator'
|
|
8
|
+
import { FeeCalculatorLayout } from './fee-calculator'
|
|
9
|
+
import { toBuffer } from './util/to-buffer'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* See https://github.com/solana-labs/solana/blob/0ea2843ec9cdc517572b8e62c959f41b55cf4453/sdk/src/nonce_state.rs#L29-L32
|
|
13
|
+
*
|
|
14
|
+
* @private
|
|
15
|
+
*/
|
|
16
|
+
const NonceAccountLayout = BufferLayout.struct([
|
|
17
|
+
BufferLayout.u32('version'),
|
|
18
|
+
BufferLayout.u32('state'),
|
|
19
|
+
Layout.publicKey('authorizedPubkey'),
|
|
20
|
+
Layout.publicKey('nonce'),
|
|
21
|
+
BufferLayout.struct([FeeCalculatorLayout], 'feeCalculator'),
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
export const NONCE_ACCOUNT_LENGTH = NonceAccountLayout.span
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* NonceAccount class
|
|
28
|
+
*/
|
|
29
|
+
export class NonceAccount {
|
|
30
|
+
authorizedPubkey: PublicKey
|
|
31
|
+
nonce: Blockhash
|
|
32
|
+
feeCalculator: FeeCalculator
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Deserialize NonceAccount from the account data.
|
|
36
|
+
*
|
|
37
|
+
* @param buffer account data
|
|
38
|
+
* @return NonceAccount
|
|
39
|
+
*/
|
|
40
|
+
static fromAccountData(buffer: Buffer | Uint8Array | Array<number>): NonceAccount {
|
|
41
|
+
const nonceAccount = NonceAccountLayout.decode(toBuffer(buffer), 0)
|
|
42
|
+
nonceAccount.authorizedPubkey = new PublicKey(nonceAccount.authorizedPubkey)
|
|
43
|
+
nonceAccount.nonce = new PublicKey(nonceAccount.nonce).toString()
|
|
44
|
+
return nonceAccount
|
|
45
|
+
}
|
|
46
|
+
}
|