@exodus/solana-lib 1.6.3 → 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 +2 -2
- package/src/constants.js +8 -0
- package/src/encode.js +35 -4
- package/src/helpers/metaplex-transfer.js +222 -0
- package/src/tx/create-unsigned-tx.js +2 -0
- package/src/tx/sign-unsigned-tx.js +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/solana-lib",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.4",
|
|
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": "
|
|
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 {
|
|
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
|
-
|
|
100
|
+
MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(),
|
|
97
101
|
new PublicKey(tokenMintAddress).toBuffer(),
|
|
98
102
|
],
|
|
99
|
-
|
|
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)
|