@exodus/solana-lib 1.6.3 → 1.6.5

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.3",
3
+ "version": "1.6.5",
4
4
  "description": "Exodus internal Solana low-level library",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -26,5 +26,5 @@
26
26
  "lodash": "^4.17.11",
27
27
  "tweetnacl": "^1.0.3"
28
28
  },
29
- "gitHead": "a7b27e4f299057c16dda446f006fa61a945a6cbf"
29
+ "gitHead": "44fca2a6ee944fb8e96c0a8918845b1dc7cc5242"
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
 
@@ -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,
@@ -2,6 +2,7 @@ import { asset } from '@exodus/solana-meta'
2
2
  import type { UnsignedTransaction, SignedTransaction } from '@exodus/models/lib/types'
3
3
 
4
4
  import { Transaction, getTransactionStrategy } from '../'
5
+ import { createMetaplexTransferTransaction } from '../helpers/metaplex-transfer'
5
6
 
6
7
  export function signUnsignedTx(
7
8
  unsignedTx: UnsignedTransaction,
@@ -58,6 +59,9 @@ const createTx = ({ txData, method }) => {
58
59
  case 'exchange':
59
60
  tx = createMagicEdenExchangeTransaction(txData)
60
61
  break
62
+ case 'metaplexTransfer':
63
+ tx = createMetaplexTransferTransaction(txData)
64
+ break
61
65
  default:
62
66
  // SOL and Token tx
63
67
  tx = createTokenTransaction(txData)
@@ -14,7 +14,13 @@ export function getTransactionSimulationParams(transactionMessage) {
14
14
 
15
15
  const accountAddresses = new Set()
16
16
 
17
- if (!transactionMessage.accountKeys) {
17
+ // Get account keys from `staticAccountKeys` if it's a versioned transaction
18
+ // https://solana-labs.github.io/solana-web3.js/classes/Message.html#staticAccountKeys
19
+ if (transactionMessage.staticAccountKeys) {
20
+ transactionMessage.staticAccountKeys.forEach((account) =>
21
+ accountAddresses.add(account.toString())
22
+ )
23
+ } else if (!transactionMessage.accountKeys) {
18
24
  const programIds = new Set(
19
25
  transactionMessage.instructions.map((instruction) => instruction.programId.toString())
20
26
  )