@exodus/solana-lib 1.2.1 → 1.2.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/solana-lib",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
4
4
  "description": "Exodus internal Solana low-level library",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -15,12 +15,12 @@
15
15
  "dependencies": {
16
16
  "@exodus/asset-lib": "^3.4.3",
17
17
  "@exodus/assets": "^8.0.0",
18
+ "@exodus/buffer-layout": "^1.2.0-exodus1",
18
19
  "@exodus/models": "^8.4.0",
19
- "@exodus/solana-spl-token": "^0.0.13-exodus1",
20
- "@exodus/solana-web3.js": "^0.87.1-exodus4",
21
20
  "bn.js": "~4.11.0",
22
21
  "bs58": "~4.0.1",
22
+ "create-hash": "~1.1.3",
23
23
  "tweetnacl": "^1.0.3"
24
24
  },
25
- "gitHead": "51f71e5bcebd886116bf433c8fe79859a185d16c"
25
+ "gitHead": "f5a2d444a2789ca118671566f32805dba061ce5a"
26
26
  }
package/src/constants.js CHANGED
@@ -1,7 +1,8 @@
1
- import { PublicKey, SystemProgram } from '@exodus/solana-web3.js'
2
- export { TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
1
+ import { PublicKey, SystemProgram } from './vendor'
3
2
 
4
3
  export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
5
4
  'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'
6
5
  )
7
6
  export const SYSTEM_PROGRAM_ID = SystemProgram.programId
7
+
8
+ export const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
package/src/encode.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // @flow
2
- import { PublicKey } from '@exodus/solana-web3.js'
3
- import { TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
4
- import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID } from './constants'
2
+ import { PublicKey } from './vendor'
3
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID } from './constants'
5
4
  import { getPublicKey } from './keypair'
6
5
  import bs58 from 'bs58'
7
6
  import BN from 'bn.js'
@@ -0,0 +1,108 @@
1
+ import assert from 'assert'
2
+ import BN from 'bn.js'
3
+ import * as BufferLayout from '@exodus/buffer-layout'
4
+ import { Account, PublicKey, TransactionInstruction } from '../vendor'
5
+ import * as Layout from '../vendor/utils/layout'
6
+
7
+ // Extracted from https://github.com/ExodusMovement/solana-spl-token/blob/master/src/index.js#L263
8
+
9
+ /**
10
+ * 64-bit value
11
+ */
12
+ export class U64 extends BN {
13
+ /**
14
+ * Convert to Buffer representation
15
+ */
16
+ toBuffer(): typeof Buffer {
17
+ const a = super.toArray().reverse()
18
+ const b = Buffer.from(a)
19
+ if (b.length === 8) {
20
+ return b
21
+ }
22
+ assert(b.length < 8, 'u64 too large')
23
+
24
+ const zeroPad = Buffer.alloc(8)
25
+ b.copy(zeroPad)
26
+ return zeroPad
27
+ }
28
+
29
+ /**
30
+ * Construct a u64 from Buffer representation
31
+ */
32
+ static fromBuffer(buffer: typeof Buffer): U64 {
33
+ assert(buffer.length === 8, `Invalid buffer length: ${buffer.length}`)
34
+ return new U64(
35
+ [...buffer]
36
+ .reverse()
37
+ .map((i) => `00${i.toString(16)}`.slice(-2))
38
+ .join(''),
39
+ 16
40
+ )
41
+ }
42
+ }
43
+
44
+ const u64 = U64 // alias
45
+
46
+ /**
47
+ * An ERC20-like Token
48
+ */
49
+ export class Token {
50
+ /**
51
+ * Construct a Transfer instruction
52
+ *
53
+ * @param programId SPL Token program account
54
+ * @param source Source account
55
+ * @param destination Destination account
56
+ * @param owner Owner of the source account
57
+ * @param multiSigners Signing accounts if `authority` is a multiSig
58
+ * @param amount Number of tokens to transfer
59
+ */
60
+ static createTransferInstruction(
61
+ programId: PublicKey,
62
+ source: PublicKey,
63
+ destination: PublicKey,
64
+ owner: PublicKey,
65
+ multiSigners: Array<Account>,
66
+ amount: number | u64
67
+ ): TransactionInstruction {
68
+ const dataLayout = BufferLayout.struct([
69
+ BufferLayout.u8('instruction'),
70
+ Layout.uint64('amount'),
71
+ ])
72
+
73
+ const data = Buffer.alloc(dataLayout.span)
74
+ dataLayout.encode(
75
+ {
76
+ instruction: 3, // Transfer instruction
77
+ amount: new U64(amount).toBuffer(),
78
+ },
79
+ data
80
+ )
81
+
82
+ let keys = [
83
+ { pubkey: source, isSigner: false, isWritable: true },
84
+ { pubkey: destination, isSigner: false, isWritable: true },
85
+ ]
86
+ if (multiSigners.length === 0) {
87
+ keys.push({
88
+ pubkey: owner,
89
+ isSigner: true,
90
+ isWritable: false,
91
+ })
92
+ } else {
93
+ keys.push({ pubkey: owner, isSigner: false, isWritable: false })
94
+ multiSigners.forEach((signer) =>
95
+ keys.push({
96
+ pubkey: signer.publicKey,
97
+ isSigner: true,
98
+ isWritable: false,
99
+ })
100
+ )
101
+ }
102
+ return new TransactionInstruction({
103
+ keys,
104
+ programId: programId,
105
+ data,
106
+ })
107
+ }
108
+ }
@@ -1,12 +1,7 @@
1
- import {
2
- PublicKey,
3
- TransactionInstruction,
4
- SystemProgram,
5
- SYSVAR_RENT_PUBKEY,
6
- } from '@exodus/solana-web3.js'
7
- import { Token, TOKEN_PROGRAM_ID } from '@exodus/solana-spl-token'
8
- import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID } from './constants'
9
- import { findAssociatedTokenAddress } from './encode'
1
+ import { Token } from './spl-token'
2
+ import { PublicKey, TransactionInstruction, SystemProgram, SYSVAR_RENT_PUBKEY } from '../vendor'
3
+ import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID } from '../constants'
4
+ import { findAssociatedTokenAddress } from '../encode'
10
5
 
11
6
  // https://github.com/paul-schaaf/spl-token-ui/blob/main/src/solana/token/associatedToken.ts#L59
12
7
  export const createAssociatedTokenAccount = (
@@ -1,9 +1,12 @@
1
1
  // @flow
2
2
  import assert from 'assert'
3
3
 
4
- import { Account, SystemProgram, Transaction, PublicKey } from '@exodus/solana-web3.js'
5
4
  import { getKeyPairFromPrivateKey } from './keypair'
6
- import { createAssociatedTokenAccount, createTokenTransferInstruction } from './helpers'
5
+ import {
6
+ createAssociatedTokenAccount,
7
+ createTokenTransferInstruction,
8
+ } from './helpers/tokenTransfer'
9
+ import { PublicKey, Account, Transaction, SystemProgram } from './vendor'
7
10
  import TOKENS from './tokens'
8
11
  import bs58 from 'bs58'
9
12
 
@@ -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,8 @@
1
+ export * from './account'
2
+ export * from './instruction'
3
+ export * from './message'
4
+ export * from './nonce-account'
5
+ export * from './publickey'
6
+ export * from './system-program'
7
+ export * from './sysvar'
8
+ export * from './transaction'
@@ -0,0 +1,46 @@
1
+ // @flow
2
+
3
+ import * as BufferLayout from '@exodus/buffer-layout'
4
+
5
+ import * as Layout from './utils/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,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 './utils/blockhash'
8
+ import * as Layout from './utils/layout'
9
+ import { PACKET_DATA_SIZE } from './transaction'
10
+ import * as shortvec from './utils/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 { PublicKey } from './publickey'
5
+ import type { Blockhash } from './utils/blockhash'
6
+ import * as Layout from './utils/layout'
7
+ import type { FeeCalculator } from './utils/fee-calculator'
8
+ import { FeeCalculatorLayout } from './utils/fee-calculator'
9
+ import { toBuffer } from './utils/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
+ }