@@ -2,11 +2,17 @@ import {
22 UpdateV1InstructionAccounts ,
33 UpdateV1InstructionArgs ,
44 fetchMetadataFromSeeds ,
5+ safeFetchMetadataFromSeeds ,
56 updateV1 ,
67} from '@metaplex-foundation/mpl-token-metadata'
78import { publicKey , transactionBuilder } from '@metaplex-foundation/umi'
89import { toWeb3JsTransaction } from '@metaplex-foundation/umi-web3js-adapters'
9- import { TOKEN_2022_PROGRAM_ID , TOKEN_PROGRAM_ID , createUpdateFieldInstruction } from '@solana/spl-token'
10+ import {
11+ TOKEN_2022_PROGRAM_ID ,
12+ TOKEN_PROGRAM_ID ,
13+ createUpdateFieldInstruction ,
14+ getTokenMetadata ,
15+ } from '@solana/spl-token'
1016import { Keypair , PublicKey , SystemProgram , TransactionMessage , VersionedTransaction } from '@solana/web3.js'
1117import bs58 from 'bs58'
1218import { task } from 'hardhat/config'
@@ -26,9 +32,9 @@ interface UpdateMetadataTaskArgs {
2632 vaultPda : string
2733}
2834
29- // Auto-detects metadata type based on mint owner :
30- // - TOKEN_PROGRAM_ID -> Metaplex metadata
31- // - TOKEN_2022_PROGRAM_ID -> Token Extensions metadata
35+ // Auto-detects metadata type:
36+ // - SPL Token mints ( TOKEN_PROGRAM_ID) -> always use Metaplex metadata
37+ // - Token-2022 mints -> probe for Metaplex metadata PDA first; if exists, use Metaplex; otherwise use Token Extensions
3238//
3339// Example:
3440// pnpm hardhat lz:oft:solana:update-metadata --eid <EID> --mint <MINT_ADDRESS> --name <NEW_TOKEN_NAME>
4955 async ( { eid, name, mint : mintStr , sellerFeeBasisPoints, symbol, uri, vaultPda } : UpdateMetadataTaskArgs ) => {
5056 const isTestnet = eid == EndpointId . SOLANA_V2_TESTNET
5157
52- const { connection } = await deriveConnection ( eid )
58+ const { umi , connection } = await deriveConnection ( eid )
5359 const mintPubkey = new PublicKey ( mintStr )
5460 const mintAccountInfo = await connection . getAccountInfo ( mintPubkey )
5561
5965
6066 const mintOwner = mintAccountInfo . owner . toBase58 ( )
6167
62- if ( mintOwner === TOKEN_2022_PROGRAM_ID . toBase58 ( ) ) {
63- console . log ( 'Detected Token-2022 mint, using Token Extensions metadata' )
64- await updateTokenExtensionsMetadata ( {
65- eid,
66- mintStr,
67- name,
68- symbol,
69- uri,
70- vaultPda,
71- isTestnet,
72- } )
73- } else if ( mintOwner === TOKEN_PROGRAM_ID . toBase58 ( ) ) {
68+ if ( mintOwner === TOKEN_PROGRAM_ID . toBase58 ( ) ) {
69+ // SPL Token mints always use Metaplex metadata
7470 console . log ( 'Detected SPL Token mint, using Metaplex metadata' )
7571 await updateMetaplexMetadata ( {
7672 eid,
8278 vaultPda,
8379 isTestnet,
8480 } )
81+ } else if ( mintOwner === TOKEN_2022_PROGRAM_ID . toBase58 ( ) ) {
82+ // Token-2022 mints could use either Metaplex or Token Extensions metadata
83+ // Probe for Metaplex metadata PDA first
84+ const metaplexMetadata = await safeFetchMetadataFromSeeds ( umi , { mint : publicKey ( mintStr ) } )
85+
86+ if ( metaplexMetadata ) {
87+ console . log ( 'Detected Token-2022 mint with Metaplex metadata' )
88+ await updateMetaplexMetadata ( {
89+ eid,
90+ mintStr,
91+ name,
92+ symbol,
93+ uri,
94+ sellerFeeBasisPoints,
95+ vaultPda,
96+ isTestnet,
97+ } )
98+ } else {
99+ console . log ( 'Detected Token-2022 mint with Token Extensions metadata' )
100+ await updateTokenExtensionsMetadata ( {
101+ eid,
102+ mintStr,
103+ name,
104+ symbol,
105+ uri,
106+ vaultPda,
107+ isTestnet,
108+ } )
109+ }
85110 } else {
86111 throw new Error ( `Unknown mint owner program: ${ mintOwner } ` )
87112 }
@@ -183,7 +208,23 @@ async function updateTokenExtensionsMetadata({
183208
184209 const keypair = privateKeyStr ? Keypair . fromSecretKey ( bs58 . decode ( privateKeyStr ) ) : undefined
185210 const mintPubkey = new PublicKey ( mintStr )
186- const updateAuthority = vaultPda ? new PublicKey ( vaultPda ) : keypair ! . publicKey
211+ const expectedAuthority = vaultPda ? new PublicKey ( vaultPda ) : keypair ! . publicKey
212+
213+ // Fetch Token Extensions metadata to verify update authority
214+ const tokenMetadata = await getTokenMetadata ( connection , mintPubkey , 'confirmed' , TOKEN_2022_PROGRAM_ID )
215+ if ( ! tokenMetadata ) {
216+ throw new Error ( `Token Extensions metadata not found for mint: ${ mintStr } ` )
217+ }
218+
219+ // Verify update authority
220+ if ( ! tokenMetadata . updateAuthority ) {
221+ throw new Error ( 'Metadata has no update authority set (metadata is immutable)' )
222+ }
223+ if ( tokenMetadata . updateAuthority . toBase58 ( ) !== expectedAuthority . toBase58 ( ) ) {
224+ throw new Error (
225+ `Update authority mismatch. Expected: ${ expectedAuthority . toBase58 ( ) } , Actual: ${ tokenMetadata . updateAuthority . toBase58 ( ) } `
226+ )
227+ }
187228
188229 // Get current mint account info to calculate rent difference
189230 const mintAccountInfo = await connection . getAccountInfo ( mintPubkey )
@@ -196,16 +237,26 @@ async function updateTokenExtensionsMetadata({
196237
197238 // Calculate additional bytes needed for new values and add rent if needed
198239 // Token Extensions metadata may need more space when updating to longer values
199- if ( ! vaultPda ) {
200- // Estimate new total size: current size + length increases for each field
201- // Add a buffer of 100 bytes to be safe
202- const estimatedNewSize =
203- mintAccountInfo . data . length + ( name ?. length ?? 0 ) + ( symbol ?. length ?? 0 ) + ( uri ?. length ?? 0 ) + 100
204- const rentExemptLamports = await connection . getMinimumBalanceForRentExemption ( estimatedNewSize )
205- const currentLamports = mintAccountInfo . lamports
206- const additionalLamports = rentExemptLamports - currentLamports
207-
208- if ( additionalLamports > 0 ) {
240+ // Estimate new total size: current size + length increases for each field
241+ // Add a buffer of 100 bytes to be safe
242+ const estimatedNewSize =
243+ mintAccountInfo . data . length + ( name ?. length ?? 0 ) + ( symbol ?. length ?? 0 ) + ( uri ?. length ?? 0 ) + 100
244+ const rentExemptLamports = await connection . getMinimumBalanceForRentExemption ( estimatedNewSize )
245+ const currentLamports = mintAccountInfo . lamports
246+ const additionalLamports = rentExemptLamports - currentLamports
247+
248+ if ( additionalLamports > 0 ) {
249+ if ( vaultPda ) {
250+ // For multisig, include rent transfer from vault to mint
251+ console . log ( `Adding ${ additionalLamports } lamports rent transfer from vault to mint` )
252+ instructions . push (
253+ SystemProgram . transfer ( {
254+ fromPubkey : new PublicKey ( vaultPda ) ,
255+ toPubkey : mintPubkey ,
256+ lamports : additionalLamports ,
257+ } )
258+ )
259+ } else {
209260 console . log ( `Adding ${ additionalLamports } lamports for additional rent` )
210261 instructions . push (
211262 SystemProgram . transfer ( {
@@ -222,7 +273,7 @@ async function updateTokenExtensionsMetadata({
222273 createUpdateFieldInstruction ( {
223274 programId : TOKEN_2022_PROGRAM_ID ,
224275 metadata : mintPubkey ,
225- updateAuthority,
276+ updateAuthority : expectedAuthority ,
226277 field : 'name' ,
227278 value : name ,
228279 } )
@@ -234,7 +285,7 @@ async function updateTokenExtensionsMetadata({
234285 createUpdateFieldInstruction ( {
235286 programId : TOKEN_2022_PROGRAM_ID ,
236287 metadata : mintPubkey ,
237- updateAuthority,
288+ updateAuthority : expectedAuthority ,
238289 field : 'symbol' ,
239290 value : symbol ,
240291 } )
@@ -246,7 +297,7 @@ async function updateTokenExtensionsMetadata({
246297 createUpdateFieldInstruction ( {
247298 programId : TOKEN_2022_PROGRAM_ID ,
248299 metadata : mintPubkey ,
249- updateAuthority,
300+ updateAuthority : expectedAuthority ,
250301 field : 'uri' ,
251302 value : uri ,
252303 } )
@@ -260,7 +311,7 @@ async function updateTokenExtensionsMetadata({
260311 const { blockhash } = await connection . getLatestBlockhash ( )
261312
262313 const message = new TransactionMessage ( {
263- payerKey : vaultPda ? updateAuthority : keypair ! . publicKey ,
314+ payerKey : vaultPda ? expectedAuthority : keypair ! . publicKey ,
264315 recentBlockhash : blockhash ,
265316 instructions,
266317 } ) . compileToV0Message ( )
0 commit comments