@exodus/solana-lib 1.6.1 → 1.6.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.6.1",
3
+ "version": "1.6.4",
4
4
  "description": "Exodus internal Solana low-level library",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -14,9 +14,9 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@exodus/asset-lib": "^3.7.1",
17
- "@exodus/assets": "^8.0.74",
18
17
  "@exodus/buffer-layout": "^1.2.0-exodus1",
19
18
  "@exodus/models": "^8.10.4",
19
+ "@exodus/solana-meta": "^1.0.2",
20
20
  "@exodus/solana-spl-token": "0.1.8-exodus.1",
21
21
  "@project-serum/serum": "0.13.64",
22
22
  "bn.js": "^4.11.0",
@@ -26,5 +26,5 @@
26
26
  "lodash": "^4.17.11",
27
27
  "tweetnacl": "^1.0.3"
28
28
  },
29
- "gitHead": "145ae526c51aefa39ffda8c63c70b30724aa9881"
29
+ "gitHead": "123d67b94e313496373a6ec97447361bb6bec6d7"
30
30
  }
package/src/constants.js CHANGED
@@ -15,6 +15,14 @@ export const MAGIC_EDEN_ESCROW_PROGRAM_ID = new PublicKey(
15
15
  'MEisE1HzehtrDpAAT8PnLHjpSSkRYakotTuJRPjTpo8'
16
16
  )
17
17
 
18
+ export const MPL_TOKEN_METADATA_PROGRAM_ID = new PublicKey(
19
+ 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
20
+ )
21
+
22
+ export const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
23
+ 'Sysvar1nstructions1111111111111111111111111'
24
+ )
25
+
18
26
  export const SEED = 'stake:0'
19
27
 
20
28
  export const LAMPORTS_PER_SOL = 1000000000
package/src/encode.js CHANGED
@@ -1,7 +1,12 @@
1
1
  // @flow
2
2
  import assert from 'assert'
3
3
  import { PublicKey, StakeProgram } from './vendor'
4
- import { SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID, TOKEN_PROGRAM_ID, SEED } from './constants'
4
+ import {
5
+ SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
6
+ TOKEN_PROGRAM_ID,
7
+ SEED,
8
+ MPL_TOKEN_METADATA_PROGRAM_ID,
9
+ } from './constants'
5
10
  import { getPublicKey, getKeyPairFromPrivateKey } from './keypair'
6
11
  import bs58 from 'bs58'
7
12
  import BN from 'bn.js'
@@ -87,19 +92,45 @@ export function createStakeAddress(walletAddress: string, seed = SEED): string {
87
92
 
88
93
  // get Metaplex Metadata account
89
94
  export function getMetadataAccount(tokenMintAddress: string): string {
90
- const METADATA_PROGRAM_ID = 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
91
95
  const METADATA_PREFIX = 'metadata'
92
96
 
93
97
  return PublicKey.findProgramAddress(
94
98
  [
95
99
  Buffer.from(METADATA_PREFIX),
96
- new PublicKey(METADATA_PROGRAM_ID).toBuffer(),
100
+ MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(),
97
101
  new PublicKey(tokenMintAddress).toBuffer(),
98
102
  ],
99
- new PublicKey(METADATA_PROGRAM_ID)
103
+ MPL_TOKEN_METADATA_PROGRAM_ID
100
104
  )[0].toBase58() // returns encoded PublicKey
101
105
  }
102
106
 
107
+ // metaplex NFT Master Edition PDA
108
+ export function getMasterEditionPDA(tokenMintAddress: string): string {
109
+ return PublicKey.findProgramAddress(
110
+ [
111
+ Buffer.from('metadata', 'utf8'),
112
+ MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(),
113
+ new PublicKey(tokenMintAddress).toBuffer(),
114
+ Buffer.from('edition', 'utf8'),
115
+ ],
116
+ MPL_TOKEN_METADATA_PROGRAM_ID
117
+ )[0].toBase58()
118
+ }
119
+
120
+ // metaplex TokenRecord PDA
121
+ export function getTokenRecordPDA(tokenMintAddress: string, token: string): string {
122
+ return PublicKey.findProgramAddress(
123
+ [
124
+ Buffer.from('metadata', 'utf8'),
125
+ MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(),
126
+ new PublicKey(tokenMintAddress).toBuffer(),
127
+ Buffer.from('token_record', 'utf8'),
128
+ new PublicKey(token).toBuffer(),
129
+ ],
130
+ MPL_TOKEN_METADATA_PROGRAM_ID
131
+ )[0].toBase58()
132
+ }
133
+
103
134
  export function deserializeMetaplexMetadata(rawData: Buffer) {
104
135
  const metadata = deserializeUnchecked(METADATA_SCHEMA, Metadata, rawData)
105
136
 
@@ -1,5 +1,5 @@
1
1
  import { FeeData } from '@exodus/asset-lib'
2
- import assets from '@exodus/assets'
2
+ import { asset } from '@exodus/solana-meta'
3
3
 
4
4
  export default new FeeData({
5
5
  config: {
@@ -7,5 +7,5 @@ export default new FeeData({
7
7
  fuelThreshold: '0.000015 SOL',
8
8
  },
9
9
  mainKey: 'fee',
10
- currency: assets.solana.currency,
10
+ currency: asset.currency,
11
11
  })
@@ -0,0 +1,222 @@
1
+ import * as BufferLayout from '@exodus/buffer-layout'
2
+
3
+ import { Transaction, PublicKey, TransactionInstruction } from '../vendor'
4
+ import {
5
+ MPL_TOKEN_METADATA_PROGRAM_ID,
6
+ SYSTEM_PROGRAM_ID,
7
+ SYSVAR_INSTRUCTIONS_PUBKEY,
8
+ TOKEN_PROGRAM_ID,
9
+ } from '../constants'
10
+
11
+ import { ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'
12
+ import {
13
+ findAssociatedTokenAddress,
14
+ getMasterEditionPDA,
15
+ getMetadataAccount,
16
+ getTokenRecordPDA,
17
+ } from '../encode'
18
+
19
+ const TRANSFER_INSTRUCTION_DISCRIMINATOR = 49
20
+ const TOKEN_AUTH_RULES_ID = new PublicKey('auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg')
21
+
22
+ export const TOKEN_STANDARD = {
23
+ NonFungible: 0,
24
+ FungibleAsset: 1,
25
+ Fungible: 2,
26
+ NonFungibleEdition: 3,
27
+ ProgrammableNonFungible: 4,
28
+ }
29
+
30
+ export const AUTHORITY_TYPE = {
31
+ None: 0,
32
+ Metadata: 1,
33
+ Holder: 2,
34
+ MetadataDelegate: 3,
35
+ TokenDelegate: 4,
36
+ }
37
+
38
+ export const prepareMetaplexTransferTx = ({
39
+ token,
40
+ tokenOwner,
41
+ destination,
42
+ destinationOwner,
43
+ mint,
44
+ metadata,
45
+ edition,
46
+ ownerTokenRecord,
47
+ destinationTokenRecord,
48
+ authority,
49
+ payer,
50
+ authorizationRulesProgram,
51
+ authorizationRules,
52
+ amount,
53
+ authorityType,
54
+ programId = MPL_TOKEN_METADATA_PROGRAM_ID,
55
+ }) => {
56
+ const transaction = new Transaction()
57
+ const data = encodeData({ amount, authorityType })
58
+ const keys = [
59
+ {
60
+ pubkey: token,
61
+ isWritable: true,
62
+ isSigner: false,
63
+ },
64
+ {
65
+ pubkey: tokenOwner,
66
+ isWritable: false,
67
+ isSigner: false,
68
+ },
69
+ {
70
+ pubkey: destination,
71
+ isWritable: true,
72
+ isSigner: false,
73
+ },
74
+ {
75
+ pubkey: destinationOwner,
76
+ isWritable: false,
77
+ isSigner: false,
78
+ },
79
+ {
80
+ pubkey: mint,
81
+ isWritable: false,
82
+ isSigner: false,
83
+ },
84
+ {
85
+ pubkey: metadata,
86
+ isWritable: true,
87
+ isSigner: false,
88
+ },
89
+ {
90
+ pubkey: edition ?? programId,
91
+ isWritable: false,
92
+ isSigner: false,
93
+ },
94
+ {
95
+ pubkey: ownerTokenRecord ?? programId,
96
+ isWritable: ownerTokenRecord != null,
97
+ isSigner: false,
98
+ },
99
+ {
100
+ pubkey: destinationTokenRecord ?? programId,
101
+ isWritable: destinationTokenRecord != null,
102
+ isSigner: false,
103
+ },
104
+ {
105
+ pubkey: authority,
106
+ isWritable: false,
107
+ isSigner: true,
108
+ },
109
+ {
110
+ pubkey: payer,
111
+ isWritable: true,
112
+ isSigner: true,
113
+ },
114
+ {
115
+ pubkey: SYSTEM_PROGRAM_ID,
116
+ isWritable: false,
117
+ isSigner: false,
118
+ },
119
+ {
120
+ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY,
121
+ isWritable: false,
122
+ isSigner: false,
123
+ },
124
+ {
125
+ pubkey: TOKEN_PROGRAM_ID,
126
+ isWritable: false,
127
+ isSigner: false,
128
+ },
129
+ {
130
+ pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
131
+ isWritable: false,
132
+ isSigner: false,
133
+ },
134
+ {
135
+ pubkey: authorizationRulesProgram ?? programId,
136
+ isWritable: false,
137
+ isSigner: false,
138
+ },
139
+ {
140
+ pubkey: authorizationRules ?? programId,
141
+ isWritable: false,
142
+ isSigner: false,
143
+ },
144
+ ]
145
+
146
+ transaction.add(
147
+ new TransactionInstruction({
148
+ programId,
149
+ keys,
150
+ data,
151
+ })
152
+ )
153
+
154
+ return transaction
155
+ }
156
+
157
+ export function createMetaplexTransferTransaction({
158
+ from,
159
+ to,
160
+ tokenMintAddress,
161
+ tokenStandard,
162
+ recentBlockhash,
163
+ amount = 1,
164
+ }) {
165
+ const fromAccount = findAssociatedTokenAddress(from, tokenMintAddress)
166
+
167
+ const toAccount = findAssociatedTokenAddress(to, tokenMintAddress)
168
+
169
+ const metadata = getMetadataAccount(tokenMintAddress)
170
+ const edition = getMasterEditionPDA(tokenMintAddress)
171
+ const ownerTokenRecord = getTokenRecordPDA(tokenMintAddress, fromAccount)
172
+ const destinationTokenRecord = getTokenRecordPDA(tokenMintAddress, toAccount)
173
+ const isProgrammable = tokenStandard === TOKEN_STANDARD.ProgrammableNonFungible
174
+
175
+ const transaction = prepareMetaplexTransferTx({
176
+ token: new PublicKey(fromAccount),
177
+ tokenOwner: new PublicKey(from),
178
+ destination: new PublicKey(toAccount),
179
+ destinationOwner: new PublicKey(to),
180
+ mint: new PublicKey(tokenMintAddress),
181
+ metadata: new PublicKey(metadata),
182
+ edition: new PublicKey(edition),
183
+ ownerTokenRecord: isProgrammable ? new PublicKey(ownerTokenRecord) : undefined,
184
+ destinationTokenRecord: isProgrammable ? new PublicKey(destinationTokenRecord) : undefined,
185
+ authority: new PublicKey(from),
186
+ payer: new PublicKey(from),
187
+ authorizationRulesProgram: TOKEN_AUTH_RULES_ID,
188
+ amount,
189
+ })
190
+
191
+ transaction.recentBlockhash = recentBlockhash
192
+
193
+ return transaction
194
+ }
195
+
196
+ export function encodeData({ amount, authorityType }) {
197
+ const TransferArgsV1 = BufferLayout.struct(
198
+ [
199
+ BufferLayout.u8('variant'), // For variant 'V1' of TransferArgs
200
+ BufferLayout.nu64('amount'),
201
+ BufferLayout.u8('authorizationData'),
202
+ ],
203
+ 'transferArgs'
204
+ )
205
+
206
+ const TransferInstructionLayout = BufferLayout.struct([
207
+ BufferLayout.u8('instructionDiscriminator'),
208
+ TransferArgsV1,
209
+ ])
210
+
211
+ const buffer = Buffer.alloc(TransferInstructionLayout.span)
212
+ const data = {
213
+ instructionDiscriminator: TRANSFER_INSTRUCTION_DISCRIMINATOR,
214
+ transferArgs: {
215
+ variant: 0, // Variant 'V1' is represented as 0
216
+ amount,
217
+ },
218
+ }
219
+ TransferInstructionLayout.encode(data, buffer)
220
+
221
+ return buffer
222
+ }
@@ -12,6 +12,7 @@ export function createUnsignedTx({
12
12
  destinationAddressType,
13
13
  isAssociatedTokenAccountActive, // true when recipient balance !== 0
14
14
  fromTokenAddresses, // sender token addresses
15
+ tokenStandard,
15
16
  // Program interactions:
16
17
  method,
17
18
  // Staking related:
@@ -46,6 +47,7 @@ export function createUnsignedTx({
46
47
  destinationAddressType,
47
48
  isAssociatedTokenAccountActive,
48
49
  fromTokenAddresses,
50
+ tokenStandard,
49
51
  // Staking related:
50
52
  method,
51
53
  stakeAddresses,
@@ -1,8 +1,7 @@
1
- import assets from '@exodus/assets'
1
+ import { asset } from '@exodus/solana-meta'
2
2
  import type { UnsignedTransaction, ParsedTransaction } from '@exodus/models/lib/types'
3
3
 
4
4
  export function parseUnsignedTx(unsignedTx: UnsignedTransaction): ParsedTransaction {
5
- const asset = assets.solana
6
5
  const {
7
6
  from,
8
7
  to,
@@ -32,7 +31,6 @@ export function parseUnsignedTx(unsignedTx: UnsignedTransaction): ParsedTransact
32
31
  const amount = asset.currency.baseUnit(txData.amount)
33
32
  const fee = asset.currency.baseUnit(txData.fee)
34
33
  return {
35
- asset,
36
34
  from: [from],
37
35
  to,
38
36
  amount,
@@ -1,8 +1,8 @@
1
- import { merge } from 'lodash'
2
- import assets from '@exodus/assets'
1
+ import { asset } from '@exodus/solana-meta'
3
2
  import type { UnsignedTransaction, SignedTransaction } from '@exodus/models/lib/types'
4
3
 
5
4
  import { Transaction, getTransactionStrategy } from '../'
5
+ import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer'
6
6
 
7
7
  export function signUnsignedTx(
8
8
  unsignedTx: UnsignedTransaction,
@@ -14,17 +14,12 @@ export function signUnsignedTx(
14
14
  return _signTx({ tx: transaction, privateKey })
15
15
  }
16
16
 
17
- const asset = assets.solana
18
17
  const address = from
19
18
  const amount = unitAmount ? asset.currency.baseUnit(unitAmount).toNumber() : unitAmount
20
19
 
21
- const args = merge(unsignedTx.txData, {
22
- asset,
23
- address,
24
- amount,
25
- })
20
+ const txData = { ...unsignedTx.txData, address, amount }
26
21
 
27
- const tx = createTx({ txData: args, method })
22
+ const tx = createTx({ txData, method })
28
23
  return _signTx({ tx, privateKey })
29
24
  }
30
25
 
@@ -64,6 +59,9 @@ const createTx = ({ txData, method }) => {
64
59
  case 'exchange':
65
60
  tx = createMagicEdenExchangeTransaction(txData)
66
61
  break
62
+ case 'metaplexTransfer':
63
+ tx = createMetaplexTransferTransaction(txData)
64
+ break
67
65
  default:
68
66
  // SOL and Token tx
69
67
  tx = createTokenTransaction(txData)
@@ -12,15 +12,17 @@ export function getTransactionSimulationParams(transactionMessage) {
12
12
  commitment: 'confirmed',
13
13
  }
14
14
 
15
- let accountAddresses = []
15
+ const accountAddresses = new Set()
16
+
16
17
  if (!transactionMessage.accountKeys) {
17
18
  const programIds = new Set(
18
19
  transactionMessage.instructions.map((instruction) => instruction.programId.toString())
19
20
  )
21
+
20
22
  transactionMessage.instructions.forEach((instruction) => {
21
23
  instruction.keys.forEach((key) => {
22
24
  if (!programIds.has(key.pubkey.toString())) {
23
- accountAddresses.push(key.pubkey.toString())
25
+ accountAddresses.add(key.pubkey.toString())
24
26
  }
25
27
  })
26
28
  })
@@ -35,19 +37,19 @@ export function getTransactionSimulationParams(transactionMessage) {
35
37
  )
36
38
  }
37
39
 
38
- accountAddresses = transactionMessage.accountKeys
40
+ transactionMessage.accountKeys
39
41
  .filter((_, index) => !indexToProgramIds.has(index))
40
- .map((account) => account.toString())
42
+ .forEach((account) => accountAddresses.add(account.toString()))
41
43
  }
42
44
 
43
45
  config['accounts'] = {
44
46
  encoding: 'base64',
45
- addresses: accountAddresses,
47
+ addresses: Array.from(accountAddresses),
46
48
  }
47
49
 
48
50
  return {
49
51
  config,
50
- accountAddresses,
52
+ accountAddresses: Array.from(accountAddresses),
51
53
  }
52
54
  }
53
55