@exodus/solana-lib 1.6.10 → 1.7.0

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.6.10",
3
+ "version": "1.7.0",
4
4
  "description": "Exodus internal Solana low-level library",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -27,5 +27,5 @@
27
27
  "lodash": "^4.17.11",
28
28
  "tweetnacl": "^1.0.3"
29
29
  },
30
- "gitHead": "1210a44d1b6a8fc0375589f7e2439aa3fd0f6b01"
30
+ "gitHead": "d0aa3d128fe488e0d4849923e5b134b80b6fef97"
31
31
  }
package/src/index.js CHANGED
@@ -12,6 +12,4 @@ export {
12
12
  } from './vendor'
13
13
  export { default as Transaction } from './transaction'
14
14
  export { U64 } from './helpers/spl-token'
15
- export { default as createGetKeyIdentifier } from './key-identifier'
16
- export { default as getTransactionStrategy } from './helpers/transaction-strategy'
17
- export { default as VersionedTransaction } from './versioned-transaction'
15
+ export { createGetKeyIdentifier, getSupportedPurposes } from './key-identifier'
@@ -1,36 +1,57 @@
1
1
  import assert from 'minimalistic-assert'
2
- import { createGetKeyIdentifier, unhardenDerivationIndex } from '@exodus/key-utils'
2
+ import {
3
+ createGetKeyIdentifier as _createGetKeyIdentifier,
4
+ unhardenDerivationIndex,
5
+ } from '@exodus/key-utils'
3
6
 
4
- export default ({ bip44, assetName, keyType } = {}) => (params = {}) => {
7
+ export const getSupportedPurposes = () => [44]
8
+
9
+ export const createGetKeyIdentifier = ({ bip44 } = {}) => (partialParams = {}) => {
5
10
  assert(typeof bip44 === 'number', 'bip44 must be a number')
6
11
 
7
- const { compatibilityMode, purpose, accountIndex = 0, addressIndex = 0 } = params
12
+ const params = {
13
+ chainIndex: 0,
14
+ addressIndex: 0,
15
+ ...partialParams,
16
+ }
8
17
  const unhardenedBip44 = unhardenDerivationIndex(bip44)
18
+ const { compatibilityMode, purpose, accountIndex, addressIndex } = params
19
+
20
+ const allowedPurposes = getSupportedPurposes()
21
+ assert(
22
+ allowedPurposes.includes(purpose),
23
+ `purpose was ${purpose}, which is not allowed. Can be one of the following: ${allowedPurposes.join(
24
+ ', '
25
+ )}`
26
+ )
9
27
 
10
28
  switch (compatibilityMode) {
11
29
  case 'phantom':
12
30
  // Phantom doesn't use chainIndex (normal vs change address)
13
31
  return {
14
- assetName,
15
32
  derivationAlgorithm: 'SLIP10',
16
33
  derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'/${addressIndex}'`,
17
- keyType,
34
+ keyType: 'nacl',
35
+ }
36
+ case 'ledger':
37
+ return {
38
+ derivationAlgorithm: 'SLIP10',
39
+ derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'`,
40
+ keyType: 'nacl',
18
41
  }
19
42
  case 'trust':
20
43
  return {
21
- assetName,
22
44
  derivationAlgorithm: 'SLIP10',
23
45
  derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'`,
24
- keyType,
46
+ keyType: 'nacl',
25
47
  }
26
48
  case 'mathwallet':
27
49
  return {
28
- assetName,
29
50
  derivationAlgorithm: 'BIP32',
30
51
  derivationPath: `m/${purpose}'/${unhardenedBip44}'/${accountIndex}'/${addressIndex}`,
31
- keyType,
52
+ keyType: 'nacl',
32
53
  }
33
54
  default:
34
- return createGetKeyIdentifier({ bip44, assetName, keyType })(params)
55
+ return _createGetKeyIdentifier({ bip44, keyType: 'nacl' })(params)
35
56
  }
36
57
  }
@@ -0,0 +1,47 @@
1
+ import base58 from 'bs58'
2
+
3
+ export function isVersionedTransaction(tx) {
4
+ return Number.isInteger(tx.version)
5
+ }
6
+
7
+ export function isLegacyTransaction(tx) {
8
+ return !isVersionedTransaction(tx)
9
+ }
10
+
11
+ export function getTxId(tx) {
12
+ const signature = getFirstSignature(tx)
13
+ if (signature === null) {
14
+ throw new Error('Cannot get transaction ID of unsigned transaction')
15
+ }
16
+ return base58.encode(signature)
17
+ }
18
+
19
+ function getFirstSignature(tx) {
20
+ if (isVersionedTransaction(tx)) {
21
+ // Versioned
22
+ if (tx.signatures.length > 0) {
23
+ return tx.signatures[0]
24
+ }
25
+ } else {
26
+ // Legacy
27
+ if (tx.signatures.length > 0) {
28
+ return tx.signatures[0].signature
29
+ }
30
+ }
31
+
32
+ return null
33
+ }
34
+
35
+ export const extractTransaction = ({ tx }) => {
36
+ const serializedTransaction = tx.serialize({
37
+ // Override the default, we don't require all signatures
38
+ // when interacting with dApps. Only affects legacy transactions,
39
+ // because versioned transactions won't be doing this check anymore.
40
+ requireAllSignatures: false,
41
+ verifySignatures: true,
42
+ })
43
+ const rawTx = Buffer.from(serializedTransaction).toString('base64')
44
+ const txId = getTxId(tx)
45
+
46
+ return { txId, rawTx }
47
+ }
package/src/tx/index.js CHANGED
@@ -5,4 +5,5 @@ export * from './create-and-sign-tx'
5
5
  export * from './simulate-and-sign-tx'
6
6
  export * from './decode-tx-instructions'
7
7
  export * from './build-raw-transaction'
8
+ export * from './sign-hardware'
8
9
  export { default as signMessage } from './sign-message'
@@ -0,0 +1,185 @@
1
+ import { asset } from '@exodus/solana-meta'
2
+
3
+ import { Transaction, PublicKey } from '../'
4
+ import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer'
5
+
6
+ /**
7
+ * Prepares the transaction to be signed (exodus & ledger).
8
+ * @param {UnsignedTx} unsignedTx
9
+ * @returns a Solana Web3.js Transaction object
10
+ */
11
+ export function prepareForSigning(unsignedTx) {
12
+ const { amount: unitAmount, from, method, transaction } = unsignedTx.txData
13
+
14
+ if (transaction) {
15
+ // unsignedTx contained a transaction in web3.js format
16
+ return transaction
17
+ } else {
18
+ // Create
19
+ const address = from
20
+ const amount = unitAmount ? asset.currency.baseUnit(unitAmount).toNumber() : unitAmount
21
+
22
+ const txData = { ...unsignedTx.txData, address, amount }
23
+
24
+ const { transaction } = createTx({ txData, method })
25
+
26
+ if (!transaction.feePayer) {
27
+ transaction.feePayer = new PublicKey(from)
28
+ }
29
+ return transaction
30
+ }
31
+ }
32
+
33
+ const createTx = ({ txData, method }) => {
34
+ let tx
35
+ switch (method) {
36
+ case 'delegate':
37
+ tx = createDelegateTransaction(txData)
38
+ break
39
+ case 'undelegate':
40
+ tx = createUndelegateTransaction(txData)
41
+ break
42
+ case 'withdraw':
43
+ tx = createWithdrawTransaction(txData)
44
+ break
45
+ case 'closeAccount':
46
+ tx = createCloseAccountTransaction(txData)
47
+ break
48
+ case 'initializeEscrow': {
49
+ tx = createMagicEdenInitializeEscrowTransaction(txData)
50
+ break
51
+ }
52
+ case 'cancelEscrow':
53
+ tx = createMagicEdenCancelEscrowTransaction(txData)
54
+ break
55
+ case 'exchange':
56
+ tx = createMagicEdenExchangeTransaction(txData)
57
+ break
58
+ case 'metaplexTransfer':
59
+ tx = createMetaplexTransferTransaction(txData)
60
+ break
61
+ default:
62
+ // SOL and Token tx
63
+ tx = createTokenTransaction(txData)
64
+ break
65
+ }
66
+ return tx
67
+ }
68
+
69
+ const createDelegateTransaction = ({ address, amount, pool, recentBlockhash, seed }) =>
70
+ Transaction.createStakeAccountTransaction({
71
+ address,
72
+ amount,
73
+ recentBlockhash,
74
+ seed,
75
+ pool,
76
+ })
77
+
78
+ const createUndelegateTransaction = ({ address, recentBlockhash, stakeAddresses }) =>
79
+ Transaction.undelegate({
80
+ address,
81
+ recentBlockhash,
82
+ stakeAddresses,
83
+ })
84
+
85
+ const createWithdrawTransaction = ({ address, amount, recentBlockhash, stakeAddresses }) =>
86
+ Transaction.withdraw({
87
+ address,
88
+ amount,
89
+ recentBlockhash,
90
+ stakeAddresses,
91
+ })
92
+
93
+ const createMagicEdenInitializeEscrowTransaction = ({
94
+ escrowAddress,
95
+ escrowBump,
96
+ initializerDepositTokenAddress,
97
+ recentBlockhash,
98
+ takerAmount,
99
+ initializerAddress,
100
+ }) =>
101
+ Transaction.magicEdenInitializeEscrow({
102
+ escrowAddress,
103
+ escrowBump,
104
+ initializerAddress,
105
+ initializerDepositTokenAddress,
106
+ recentBlockhash,
107
+ takerAmount,
108
+ })
109
+
110
+ const createMagicEdenCancelEscrowTransaction = ({
111
+ escrowAddress,
112
+ initializerAddress,
113
+ initializerDepositTokenAddress,
114
+ pdaAddress,
115
+ recentBlockhash,
116
+ }) =>
117
+ Transaction.magicEdenCancelEscrow({
118
+ escrowAddress,
119
+ initializerAddress,
120
+ initializerDepositTokenAddress,
121
+ pdaAddress,
122
+ recentBlockhash,
123
+ })
124
+
125
+ const createMagicEdenExchangeTransaction = ({
126
+ creators,
127
+ escrowAddress,
128
+ expectedMintAddress,
129
+ expectedTakerAmount,
130
+ initializerAddress,
131
+ initializerDepositTokenAddress,
132
+ metadataAddress,
133
+ pdaAddress,
134
+ recentBlockhash,
135
+ takerAddress,
136
+ }) =>
137
+ Transaction.magicEdenExchange({
138
+ creators,
139
+ escrowAddress,
140
+ expectedMintAddress,
141
+ expectedTakerAmount,
142
+ initializerAddress,
143
+ initializerDepositTokenAddress,
144
+ metadataAddress,
145
+ pdaAddress,
146
+ recentBlockhash,
147
+ takerAddress,
148
+ })
149
+
150
+ const createTokenTransaction = ({
151
+ amount,
152
+ destinationAddressType,
153
+ feePayer,
154
+ from,
155
+ fromTokenAddresses,
156
+ instructions,
157
+ isAssociatedTokenAccountActive,
158
+ recentBlockhash,
159
+ to,
160
+ tokenMintAddress,
161
+ memo,
162
+ reference,
163
+ }) =>
164
+ new Transaction({
165
+ amount,
166
+ destinationAddressType,
167
+ feePayer,
168
+ from,
169
+ fromTokenAddresses,
170
+ instructions,
171
+ isAssociatedTokenAccountActive,
172
+ recentBlockhash,
173
+ to,
174
+ tokenMintAddress,
175
+ memo,
176
+ reference,
177
+ })
178
+
179
+ const createCloseAccountTransaction = ({ account, programId, recentBlockhash, walletPublicKey }) =>
180
+ Transaction.createCloseAccount({
181
+ account,
182
+ programId,
183
+ recentBlockhash,
184
+ walletPublicKey,
185
+ })
@@ -0,0 +1,44 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { prepareForSigning } from './prepare-for-signing'
4
+ import { PublicKey } from '../vendor'
5
+ import { isVersionedTransaction, isLegacyTransaction, extractTransaction } from './common'
6
+
7
+ export async function signHardware({ unsignedTx, hardwareDevice, accountIndex }) {
8
+ assert(hardwareDevice, 'expected hardwareDevice to be defined')
9
+ assert(Number.isInteger(accountIndex) && accountIndex >= 0, 'expected accountIndex to be integer')
10
+
11
+ const tx = prepareForSigning(unsignedTx)
12
+ const signatures = await signWithHardwareWallet({ tx, hardwareDevice, accountIndex })
13
+ applySignatures({ tx, signatures })
14
+
15
+ return extractTransaction({ tx })
16
+ }
17
+
18
+ const signWithHardwareWallet = async ({ tx, hardwareDevice, accountIndex }) => {
19
+ assert(hardwareDevice, `hardwareDevice required`)
20
+ assert(Number.isInteger(accountIndex), `accountIndex required`)
21
+
22
+ const messageToSign = isVersionedTransaction(tx)
23
+ ? tx.message.serialize()
24
+ : tx.compileMessage().serialize()
25
+
26
+ const signatures = await hardwareDevice.signTransaction({
27
+ assetName: 'solana',
28
+ signableTransaction: Buffer.from(messageToSign),
29
+ derivationPaths: [`m/44'/501'/${accountIndex}'`],
30
+ })
31
+
32
+ return signatures
33
+ }
34
+
35
+ const applySignatures = ({ tx, signatures }) => {
36
+ if (isLegacyTransaction(tx)) {
37
+ const publicKeys = signatures.map(({ publicKey }) => new PublicKey(publicKey))
38
+ tx.setSigners(...publicKeys)
39
+ }
40
+
41
+ signatures.forEach(({ publicKey, signature }) => {
42
+ tx.addSignature(new PublicKey(publicKey), signature)
43
+ })
44
+ }
@@ -1,185 +1,29 @@
1
- import { asset } from '@exodus/solana-meta'
1
+ import assert from 'minimalistic-assert'
2
2
 
3
- import { Transaction, getTransactionStrategy } from '../'
4
- import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer'
3
+ import { prepareForSigning } from './prepare-for-signing'
4
+ import { getKeyPairFromPrivateKey } from '../keypair'
5
+ import { Account } from '../vendor'
6
+ import { extractTransaction, isVersionedTransaction } from './common'
5
7
 
6
8
  export function signUnsignedTx(unsignedTx, privateKey) {
7
- const { amount: unitAmount, from, method, transaction } = unsignedTx.txData
9
+ assert(privateKey, 'Please provide a secretKey')
8
10
 
9
- if (transaction) {
10
- return _signTx({ tx: transaction, privateKey })
11
- }
12
-
13
- const address = from
14
- const amount = unitAmount ? asset.currency.baseUnit(unitAmount).toNumber() : unitAmount
11
+ const tx = prepareForSigning(unsignedTx)
15
12
 
16
- const txData = { ...unsignedTx.txData, address, amount }
13
+ _signTx({ tx, privateKey })
17
14
 
18
- const tx = createTx({ txData, method })
19
- return _signTx({ tx, privateKey })
15
+ return extractTransaction({ tx })
20
16
  }
21
17
 
22
18
  // Signs plain tx.
23
19
  const _signTx = ({ tx, privateKey }) => {
24
- const _Transaction = getTransactionStrategy(tx)
25
-
26
- _Transaction.sign(tx, privateKey)
27
- const rawTx = _Transaction.serialize(tx)
28
- const txId = _Transaction.getTxId(tx)
29
-
30
- return { txId, rawTx }
31
- }
32
-
33
- const createTx = ({ txData, method }) => {
34
- let tx
35
- switch (method) {
36
- case 'delegate':
37
- tx = createDelegateTransaction(txData)
38
- break
39
- case 'undelegate':
40
- tx = createUndelegateTransaction(txData)
41
- break
42
- case 'withdraw':
43
- tx = createWithdrawTransaction(txData)
44
- break
45
- case 'closeAccount':
46
- tx = createCloseAccountTransaction(txData)
47
- break
48
- case 'initializeEscrow': {
49
- tx = createMagicEdenInitializeEscrowTransaction(txData)
50
- break
51
- }
52
- case 'cancelEscrow':
53
- tx = createMagicEdenCancelEscrowTransaction(txData)
54
- break
55
- case 'exchange':
56
- tx = createMagicEdenExchangeTransaction(txData)
57
- break
58
- case 'metaplexTransfer':
59
- tx = createMetaplexTransferTransaction(txData)
60
- break
61
- default:
62
- // SOL and Token tx
63
- tx = createTokenTransaction(txData)
64
- break
20
+ const { secretKey } = getKeyPairFromPrivateKey(privateKey)
21
+ const account = new Account(secretKey)
22
+ if (isVersionedTransaction(tx)) {
23
+ // VersionedTransaction
24
+ tx.sign([account])
25
+ } else {
26
+ // Legacy Transactions
27
+ tx.sign(account)
65
28
  }
66
- return tx
67
29
  }
68
-
69
- const createDelegateTransaction = ({ address, amount, pool, recentBlockhash, seed }) =>
70
- Transaction.createStakeAccountTransaction({
71
- address,
72
- amount,
73
- recentBlockhash,
74
- seed,
75
- pool,
76
- })
77
-
78
- const createUndelegateTransaction = ({ address, recentBlockhash, stakeAddresses }) =>
79
- Transaction.undelegate({
80
- address,
81
- recentBlockhash,
82
- stakeAddresses,
83
- })
84
-
85
- const createWithdrawTransaction = ({ address, amount, recentBlockhash, stakeAddresses }) =>
86
- Transaction.withdraw({
87
- address,
88
- amount,
89
- recentBlockhash,
90
- stakeAddresses,
91
- })
92
-
93
- const createMagicEdenInitializeEscrowTransaction = ({
94
- escrowAddress,
95
- escrowBump,
96
- initializerDepositTokenAddress,
97
- recentBlockhash,
98
- takerAmount,
99
- initializerAddress,
100
- }) =>
101
- Transaction.magicEdenInitializeEscrow({
102
- escrowAddress,
103
- escrowBump,
104
- initializerAddress,
105
- initializerDepositTokenAddress,
106
- recentBlockhash,
107
- takerAmount,
108
- })
109
-
110
- const createMagicEdenCancelEscrowTransaction = ({
111
- escrowAddress,
112
- initializerAddress,
113
- initializerDepositTokenAddress,
114
- pdaAddress,
115
- recentBlockhash,
116
- }) =>
117
- Transaction.magicEdenCancelEscrow({
118
- escrowAddress,
119
- initializerAddress,
120
- initializerDepositTokenAddress,
121
- pdaAddress,
122
- recentBlockhash,
123
- })
124
-
125
- const createMagicEdenExchangeTransaction = ({
126
- creators,
127
- escrowAddress,
128
- expectedMintAddress,
129
- expectedTakerAmount,
130
- initializerAddress,
131
- initializerDepositTokenAddress,
132
- metadataAddress,
133
- pdaAddress,
134
- recentBlockhash,
135
- takerAddress,
136
- }) =>
137
- Transaction.magicEdenExchange({
138
- creators,
139
- escrowAddress,
140
- expectedMintAddress,
141
- expectedTakerAmount,
142
- initializerAddress,
143
- initializerDepositTokenAddress,
144
- metadataAddress,
145
- pdaAddress,
146
- recentBlockhash,
147
- takerAddress,
148
- })
149
-
150
- const createTokenTransaction = ({
151
- amount,
152
- destinationAddressType,
153
- feePayer,
154
- from,
155
- fromTokenAddresses,
156
- instructions,
157
- isAssociatedTokenAccountActive,
158
- recentBlockhash,
159
- to,
160
- tokenMintAddress,
161
- memo,
162
- reference,
163
- }) =>
164
- new Transaction({
165
- amount,
166
- destinationAddressType,
167
- feePayer,
168
- from,
169
- fromTokenAddresses,
170
- instructions,
171
- isAssociatedTokenAccountActive,
172
- recentBlockhash,
173
- to,
174
- tokenMintAddress,
175
- memo,
176
- reference,
177
- })
178
-
179
- const createCloseAccountTransaction = ({ account, programId, recentBlockhash, walletPublicKey }) =>
180
- Transaction.createCloseAccount({
181
- account,
182
- programId,
183
- recentBlockhash,
184
- walletPublicKey,
185
- })
@@ -51,6 +51,14 @@ export class PublicKey {
51
51
  return bs58.encode(this.toBuffer())
52
52
  }
53
53
 
54
+ /**
55
+ * Return the byte array representation of the public key in big endian
56
+ */
57
+ toBytes() {
58
+ const buf = this.toBuffer()
59
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength)
60
+ }
61
+
54
62
  /**
55
63
  * Return the Buffer representation of the public key
56
64
  */
@@ -1,25 +0,0 @@
1
- import { Transaction, VersionedTransaction } from '..'
2
- import { SUPPORTED_TRANSACTION_VERSIONS } from '../constants'
3
-
4
- const isVersionedTransactionType = (transaction) => {
5
- // new transaction types have a version field, either a string(legacy) or a number(>=0)
6
- return transaction.version !== undefined
7
- }
8
-
9
- const isVersionedTransactionTypeSupported = (transaction) => {
10
- return SUPPORTED_TRANSACTION_VERSIONS.has(transaction.version)
11
- }
12
-
13
- const getTransactionStrategy = (transaction) => {
14
- if (isVersionedTransactionType(transaction)) {
15
- if (isVersionedTransactionTypeSupported(transaction)) {
16
- return VersionedTransaction
17
- }
18
-
19
- throw new Error(`unsupported transaction version: ${transaction.version}`)
20
- }
21
-
22
- return Transaction
23
- }
24
-
25
- export default getTransactionStrategy
@@ -1,45 +0,0 @@
1
- import base58 from 'bs58'
2
- import { getKeyPairFromPrivateKey } from './keypair'
3
- import { Account } from './vendor'
4
-
5
- function getFirstSignature(tx) {
6
- if (tx.signatures.length > 0) {
7
- return tx.signatures[0]
8
- }
9
-
10
- return null
11
- }
12
-
13
- class VersionedTx {
14
- // tx is not supported from the vendored library (solana-lib/src/vendor).
15
- // We also can't make any guarantees that Account from vendored lib with version X
16
- // will be compatible with the tx built by an outside library with version Y.
17
- static sign(tx, privateKey, extraSigners = []) {
18
- if (!privateKey) {
19
- throw new Error('Please provide a secretKey')
20
- }
21
- const { secretKey } = getKeyPairFromPrivateKey(privateKey)
22
- const signers = [new Account(secretKey), ...extraSigners]
23
-
24
- tx.sign(signers)
25
-
26
- if (!(tx.signatures && tx.signatures.length)) {
27
- throw new Error('!Signature')
28
- }
29
- }
30
-
31
- static serialize(tx) {
32
- const serializedTx = tx.serialize()
33
- return Buffer.from(serializedTx).toString('base64')
34
- }
35
-
36
- static getTxId(tx) {
37
- const signature = getFirstSignature(tx)
38
- if (signature === null) {
39
- throw new Error('Cannot get transaction ID of unsigned transaction')
40
- }
41
- return base58.encode(signature)
42
- }
43
- }
44
-
45
- export default VersionedTx