From 84da4c0c3228a7c391d160f0bdd26db70ba933bb Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:42:59 -0800 Subject: [PATCH 1/9] default confirmations --- .changeset/giant-schools-matter.md | 5 +++++ examples/oft-solana/tasks/solana/debug.ts | 24 +++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 .changeset/giant-schools-matter.md diff --git a/.changeset/giant-schools-matter.md b/.changeset/giant-schools-matter.md new file mode 100644 index 0000000000..276c883db0 --- /dev/null +++ b/.changeset/giant-schools-matter.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oft-solana-example": patch +--- + +update Solana OFT debug script to state when default confirmations is used diff --git a/examples/oft-solana/tasks/solana/debug.ts b/examples/oft-solana/tasks/solana/debug.ts index ba8e29ec21..2e422af07d 100644 --- a/examples/oft-solana/tasks/solana/debug.ts +++ b/examples/oft-solana/tasks/solana/debug.ts @@ -391,10 +391,14 @@ function printOAppReceiveConfigs( // Print each property in the object DebugLogger.keyValue(`${oAppReceiveConfigIndexesToKeys[i]}`, '', 2) for (const [propKey, propVal] of Object.entries(item)) { - const valueDisplay = - (propKey === 'requiredDVNs' || propKey === 'optionalDVNs') && Array.isArray(propVal) - ? formatDvnAddresses(propVal as string[], metadata, chainKey) - : String(propVal) + let valueDisplay: string + if ((propKey === 'requiredDVNs' || propKey === 'optionalDVNs') && Array.isArray(propVal)) { + valueDisplay = formatDvnAddresses(propVal as string[], metadata, chainKey) + } else if (propKey === 'confirmations' && String(propVal) === '0') { + valueDisplay = '0 (Use Pathway Defaults)' + } else { + valueDisplay = String(propVal) + } DebugLogger.keyValue(`${propKey}`, valueDisplay, 3) } } else { @@ -427,10 +431,14 @@ function printOAppSendConfigs( if (typeof item === 'object' && item !== null) { DebugLogger.keyValue(`${sendOappConfigIndexesToKeys[i]}`, '', 2) for (const [propKey, propVal] of Object.entries(item)) { - const valueDisplay = - (propKey === 'requiredDVNs' || propKey === 'optionalDVNs') && Array.isArray(propVal) - ? formatDvnAddresses(propVal as string[], metadata, chainKey) - : String(propVal) + let valueDisplay: string + if ((propKey === 'requiredDVNs' || propKey === 'optionalDVNs') && Array.isArray(propVal)) { + valueDisplay = formatDvnAddresses(propVal as string[], metadata, chainKey) + } else if (propKey === 'confirmations' && String(propVal) === '0') { + valueDisplay = '0 (Use Pathway Defaults)' + } else { + valueDisplay = String(propVal) + } DebugLogger.keyValue(`${propKey}`, valueDisplay, 3) } } else { From 3b7052f1d8a21da9d7596bb05ad71477141f81fb Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:14:51 +0800 Subject: [PATCH 2/9] remove warning that appears when running create --- examples/oft-solana/tasks/solana/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/oft-solana/tasks/solana/index.ts b/examples/oft-solana/tasks/solana/index.ts index 996087bad0..296f7c0496 100644 --- a/examples/oft-solana/tasks/solana/index.ts +++ b/examples/oft-solana/tasks/solana/index.ts @@ -157,7 +157,6 @@ export const getSolanaDeployment = ( const filePath = path.join(outputDir, 'OFT.json') // Note: if you have multiple deployments, change this filename to refer to the desired deployment file if (!existsSync(filePath)) { - DebugLogger.printWarning(KnownWarnings.SOLANA_DEPLOYMENT_NOT_FOUND) throw new Error(`Could not find Solana deployment file for eid ${eid} at: ${filePath}`) } From c04cd67c7e9ae0a64a2b5a931f8451a68b5c7a34 Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:20:44 +0800 Subject: [PATCH 3/9] update metadata handles token2022 too --- .changeset/curvy-owls-tie.md | 5 + .../oft-solana/tasks/solana/updateMetadata.ts | 283 +++++++++++++++--- 2 files changed, 241 insertions(+), 47 deletions(-) create mode 100644 .changeset/curvy-owls-tie.md diff --git a/.changeset/curvy-owls-tie.md b/.changeset/curvy-owls-tie.md new file mode 100644 index 0000000000..006ffc197b --- /dev/null +++ b/.changeset/curvy-owls-tie.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oft-solana-example": patch +--- + +update metadata script supports token2022 diff --git a/examples/oft-solana/tasks/solana/updateMetadata.ts b/examples/oft-solana/tasks/solana/updateMetadata.ts index 3c10b3e538..3c8471120d 100644 --- a/examples/oft-solana/tasks/solana/updateMetadata.ts +++ b/examples/oft-solana/tasks/solana/updateMetadata.ts @@ -6,6 +6,8 @@ import { } from '@metaplex-foundation/mpl-token-metadata' import { publicKey, transactionBuilder } from '@metaplex-foundation/umi' import { toWeb3JsTransaction } from '@metaplex-foundation/umi-web3js-adapters' +import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, createUpdateFieldInstruction } from '@solana/spl-token' +import { Keypair, PublicKey, SystemProgram, TransactionMessage, VersionedTransaction } from '@solana/web3.js' import bs58 from 'bs58' import { task } from 'hardhat/config' @@ -24,69 +26,256 @@ interface UpdateMetadataTaskArgs { vaultPda: string } -// note that if URI is specified, then the name and symbol in there would be used and will override the 'outer' name and symbol +// Auto-detects metadata type based on mint owner: +// - TOKEN_PROGRAM_ID -> Metaplex metadata +// - TOKEN_2022_PROGRAM_ID -> Token Extensions metadata +// // Example: // pnpm hardhat lz:oft:solana:update-metadata --eid --mint --name // If Update Authority is a multisig (Vault PDA): -// // pnpm hardhat lz:oft:solana:update-metadata --eid --mint --name --vault-pda -task('lz:oft:solana:update-metadata', 'Updates the metaplex metadata of the SPL Token') +// pnpm hardhat lz:oft:solana:update-metadata --eid --mint --name --vault-pda +task( + 'lz:oft:solana:update-metadata', + 'Updates the metadata of the SPL Token (auto-detects Metaplex or Token Extensions)' +) .addParam('eid', 'Solana mainnet (30168) or testnet (40168)', undefined, devtoolsTypes.eid) .addParam('mint', 'The Token mint public key', undefined, devtoolsTypes.string) .addOptionalParam('name', 'Token Name', undefined, devtoolsTypes.string) .addOptionalParam('symbol', 'Token Symbol', undefined, devtoolsTypes.string) - .addOptionalParam('sellerFeeBasisPoints', 'Seller fee basis points', undefined, devtoolsTypes.int) + .addOptionalParam('sellerFeeBasisPoints', 'Seller fee basis points (Metaplex only)', undefined, devtoolsTypes.int) .addOptionalParam('uri', 'URI for token metadata', undefined, devtoolsTypes.string) - .addOptionalParam('vaultPda', 'The Vault PDA public key', undefined, devtoolsTypes.string) + .addOptionalParam('vaultPda', 'The Vault PDA public key (update authority)', undefined, devtoolsTypes.string) .setAction( async ({ eid, name, mint: mintStr, sellerFeeBasisPoints, symbol, uri, vaultPda }: UpdateMetadataTaskArgs) => { - const { umi, umiWalletSigner } = await deriveConnection(eid, { - noopSigner: vaultPda ? publicKey(vaultPda) : undefined, - }) - - const mint = publicKey(mintStr) - - const initialMetadata = await fetchMetadataFromSeeds(umi, { mint }) - - if (!vaultPda && initialMetadata.updateAuthority !== umiWalletSigner.publicKey.toString()) { - throw new Error('Only the update authority can update the metadata') - } + const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET - if (vaultPda && initialMetadata.updateAuthority !== publicKey(vaultPda).toString()) { - throw new Error('Provided vaultPda is not the current update authority on this metadata') - } + const { connection } = await deriveConnection(eid) + const mintPubkey = new PublicKey(mintStr) + const mintAccountInfo = await connection.getAccountInfo(mintPubkey) - if (initialMetadata.isMutable == false) { - throw new Error('Metadata is not mutable') + if (!mintAccountInfo) { + throw new Error(`Mint account not found: ${mintStr}`) } - const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET + const mintOwner = mintAccountInfo.owner.toBase58() - const updateV1Args: UpdateV1InstructionAccounts & UpdateV1InstructionArgs = { - mint, - authority: umiWalletSigner, - data: { - ...initialMetadata, - name: name || initialMetadata.name, - symbol: symbol || initialMetadata.symbol, - uri: uri || initialMetadata.uri, - sellerFeeBasisPoints: - sellerFeeBasisPoints != undefined ? sellerFeeBasisPoints : initialMetadata.sellerFeeBasisPoints, - }, - } - const updateIxn = updateV1(umi, updateV1Args) - const txBuilder = transactionBuilder().add(updateIxn) - if (vaultPda) { - txBuilder.setFeePayer(umiWalletSigner).useV0() - // Include a recent blockhash before building - const web3JsTxn = toWeb3JsTransaction(await txBuilder.buildWithLatestBlockhash(umi)) - const base58 = bs58.encode(new Uint8Array(web3JsTxn.message.serialize())) - console.log('==== Import the following base58 txn data into the Squads UI ====') - console.log(base58) - // output txn data as base58 + if (mintOwner === TOKEN_2022_PROGRAM_ID.toBase58()) { + console.log('Detected Token-2022 mint, using Token Extensions metadata') + await updateTokenExtensionsMetadata({ + eid, + mintStr, + name, + symbol, + uri, + vaultPda, + isTestnet, + }) + } else if (mintOwner === TOKEN_PROGRAM_ID.toBase58()) { + console.log('Detected SPL Token mint, using Metaplex metadata') + await updateMetaplexMetadata({ + eid, + mintStr, + name, + symbol, + uri, + sellerFeeBasisPoints, + vaultPda, + isTestnet, + }) } else { - // submit the txn - const createTokenTx = await txBuilder.sendAndConfirm(umi) - console.log(`createTokenTx: ${getExplorerTxLink(bs58.encode(createTokenTx.signature), isTestnet)}`) + throw new Error(`Unknown mint owner program: ${mintOwner}`) } } ) + +async function updateMetaplexMetadata({ + eid, + mintStr, + name, + symbol, + uri, + sellerFeeBasisPoints, + vaultPda, + isTestnet, +}: { + eid: EndpointId + mintStr: string + name?: string + symbol?: string + uri?: string + sellerFeeBasisPoints?: number + vaultPda?: string + isTestnet: boolean +}) { + const { umi, umiWalletSigner } = await deriveConnection(eid, { + noopSigner: vaultPda ? publicKey(vaultPda) : undefined, + }) + + const mint = publicKey(mintStr) + + const initialMetadata = await fetchMetadataFromSeeds(umi, { mint }) + + if (!vaultPda && initialMetadata.updateAuthority !== umiWalletSigner.publicKey.toString()) { + throw new Error('Only the update authority can update the metadata') + } + + if (vaultPda && initialMetadata.updateAuthority !== publicKey(vaultPda).toString()) { + throw new Error('Provided vaultPda is not the current update authority on this metadata') + } + + if (initialMetadata.isMutable == false) { + throw new Error('Metadata is not mutable') + } + + const updateV1Args: UpdateV1InstructionAccounts & UpdateV1InstructionArgs = { + mint, + authority: umiWalletSigner, + data: { + ...initialMetadata, + name: name || initialMetadata.name, + symbol: symbol || initialMetadata.symbol, + uri: uri || initialMetadata.uri, + sellerFeeBasisPoints: + sellerFeeBasisPoints != undefined ? sellerFeeBasisPoints : initialMetadata.sellerFeeBasisPoints, + }, + } + const updateIxn = updateV1(umi, updateV1Args) + const txBuilder = transactionBuilder().add(updateIxn) + if (vaultPda) { + txBuilder.setFeePayer(umiWalletSigner).useV0() + // Include a recent blockhash before building + const web3JsTxn = toWeb3JsTransaction(await txBuilder.buildWithLatestBlockhash(umi)) + const base58 = bs58.encode(new Uint8Array(web3JsTxn.message.serialize())) + console.log('==== Import the following base58 txn data into the Squads UI ====') + console.log(base58) + // output txn data as base58 + } else { + // submit the txn + const createTokenTx = await txBuilder.sendAndConfirm(umi) + console.log(`Transaction: ${getExplorerTxLink(bs58.encode(createTokenTx.signature), isTestnet)}`) + } +} + +async function updateTokenExtensionsMetadata({ + eid, + mintStr, + name, + symbol, + uri, + vaultPda, + isTestnet, +}: { + eid: EndpointId + mintStr: string + name?: string + symbol?: string + uri?: string + vaultPda?: string + isTestnet: boolean +}) { + const { connection } = await deriveConnection(eid) + + // Get the keypair from environment + const privateKeyStr = process.env.SOLANA_PRIVATE_KEY + if (!privateKeyStr && !vaultPda) { + throw new Error('SOLANA_PRIVATE_KEY environment variable is required for non-multisig transactions') + } + + const keypair = privateKeyStr ? Keypair.fromSecretKey(bs58.decode(privateKeyStr)) : undefined + const mintPubkey = new PublicKey(mintStr) + const updateAuthority = vaultPda ? new PublicKey(vaultPda) : keypair!.publicKey + + // Get current mint account info to calculate rent difference + const mintAccountInfo = await connection.getAccountInfo(mintPubkey) + if (!mintAccountInfo) { + throw new Error(`Mint account not found: ${mintStr}`) + } + + // Build instructions for each field that needs updating + const instructions = [] + + // Calculate additional bytes needed for new values and add rent if needed + // Token Extensions metadata may need more space when updating to longer values + if (!vaultPda) { + // Estimate new total size: current size + length increases for each field + // Add a buffer of 100 bytes to be safe + const estimatedNewSize = + mintAccountInfo.data.length + (name?.length ?? 0) + (symbol?.length ?? 0) + (uri?.length ?? 0) + 100 + const rentExemptLamports = await connection.getMinimumBalanceForRentExemption(estimatedNewSize) + const currentLamports = mintAccountInfo.lamports + const additionalLamports = rentExemptLamports - currentLamports + + if (additionalLamports > 0) { + console.log(`Adding ${additionalLamports} lamports for additional rent`) + instructions.push( + SystemProgram.transfer({ + fromPubkey: keypair!.publicKey, + toPubkey: mintPubkey, + lamports: additionalLamports, + }) + ) + } + } + + if (name) { + instructions.push( + createUpdateFieldInstruction({ + programId: TOKEN_2022_PROGRAM_ID, + metadata: mintPubkey, + updateAuthority, + field: 'name', + value: name, + }) + ) + } + + if (symbol) { + instructions.push( + createUpdateFieldInstruction({ + programId: TOKEN_2022_PROGRAM_ID, + metadata: mintPubkey, + updateAuthority, + field: 'symbol', + value: symbol, + }) + ) + } + + if (uri) { + instructions.push( + createUpdateFieldInstruction({ + programId: TOKEN_2022_PROGRAM_ID, + metadata: mintPubkey, + updateAuthority, + field: 'uri', + value: uri, + }) + ) + } + + if (instructions.length === 0) { + throw new Error('At least one of --name, --symbol, or --uri must be provided') + } + + const { blockhash } = await connection.getLatestBlockhash() + + const message = new TransactionMessage({ + payerKey: vaultPda ? updateAuthority : keypair!.publicKey, + recentBlockhash: blockhash, + instructions, + }).compileToV0Message() + + const transaction = new VersionedTransaction(message) + + if (vaultPda) { + const base58Tx = bs58.encode(transaction.message.serialize()) + console.log('==== Import the following base58 txn data into the Squads UI ====') + console.log(base58Tx) + } else { + // Sign and send the transaction + transaction.sign([keypair!]) + const signature = await connection.sendTransaction(transaction) + await connection.confirmTransaction(signature, 'confirmed') + console.log(`Transaction: ${getExplorerTxLink(signature, isTestnet)}`) + } +} From c253cc578b94e78320891c8ee3c58f36dd7a64c1 Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:39:32 +0800 Subject: [PATCH 4/9] fix --- .../oft-solana/tasks/solana/updateMetadata.ts | 115 +++++++++++++----- 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/examples/oft-solana/tasks/solana/updateMetadata.ts b/examples/oft-solana/tasks/solana/updateMetadata.ts index 3c8471120d..5a4e75f1a7 100644 --- a/examples/oft-solana/tasks/solana/updateMetadata.ts +++ b/examples/oft-solana/tasks/solana/updateMetadata.ts @@ -2,11 +2,17 @@ import { UpdateV1InstructionAccounts, UpdateV1InstructionArgs, fetchMetadataFromSeeds, + safeFetchMetadataFromSeeds, updateV1, } from '@metaplex-foundation/mpl-token-metadata' import { publicKey, transactionBuilder } from '@metaplex-foundation/umi' import { toWeb3JsTransaction } from '@metaplex-foundation/umi-web3js-adapters' -import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, createUpdateFieldInstruction } from '@solana/spl-token' +import { + TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, + createUpdateFieldInstruction, + getTokenMetadata, +} from '@solana/spl-token' import { Keypair, PublicKey, SystemProgram, TransactionMessage, VersionedTransaction } from '@solana/web3.js' import bs58 from 'bs58' import { task } from 'hardhat/config' @@ -26,9 +32,9 @@ interface UpdateMetadataTaskArgs { vaultPda: string } -// Auto-detects metadata type based on mint owner: -// - TOKEN_PROGRAM_ID -> Metaplex metadata -// - TOKEN_2022_PROGRAM_ID -> Token Extensions metadata +// Auto-detects metadata type: +// - SPL Token mints (TOKEN_PROGRAM_ID) -> always use Metaplex metadata +// - Token-2022 mints -> probe for Metaplex metadata PDA first; if exists, use Metaplex; otherwise use Token Extensions // // Example: // pnpm hardhat lz:oft:solana:update-metadata --eid --mint --name @@ -49,7 +55,7 @@ task( async ({ eid, name, mint: mintStr, sellerFeeBasisPoints, symbol, uri, vaultPda }: UpdateMetadataTaskArgs) => { const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET - const { connection } = await deriveConnection(eid) + const { umi, connection } = await deriveConnection(eid) const mintPubkey = new PublicKey(mintStr) const mintAccountInfo = await connection.getAccountInfo(mintPubkey) @@ -59,18 +65,8 @@ task( const mintOwner = mintAccountInfo.owner.toBase58() - if (mintOwner === TOKEN_2022_PROGRAM_ID.toBase58()) { - console.log('Detected Token-2022 mint, using Token Extensions metadata') - await updateTokenExtensionsMetadata({ - eid, - mintStr, - name, - symbol, - uri, - vaultPda, - isTestnet, - }) - } else if (mintOwner === TOKEN_PROGRAM_ID.toBase58()) { + if (mintOwner === TOKEN_PROGRAM_ID.toBase58()) { + // SPL Token mints always use Metaplex metadata console.log('Detected SPL Token mint, using Metaplex metadata') await updateMetaplexMetadata({ eid, @@ -82,6 +78,35 @@ task( vaultPda, isTestnet, }) + } else if (mintOwner === TOKEN_2022_PROGRAM_ID.toBase58()) { + // Token-2022 mints could use either Metaplex or Token Extensions metadata + // Probe for Metaplex metadata PDA first + const metaplexMetadata = await safeFetchMetadataFromSeeds(umi, { mint: publicKey(mintStr) }) + + if (metaplexMetadata) { + console.log('Detected Token-2022 mint with Metaplex metadata') + await updateMetaplexMetadata({ + eid, + mintStr, + name, + symbol, + uri, + sellerFeeBasisPoints, + vaultPda, + isTestnet, + }) + } else { + console.log('Detected Token-2022 mint with Token Extensions metadata') + await updateTokenExtensionsMetadata({ + eid, + mintStr, + name, + symbol, + uri, + vaultPda, + isTestnet, + }) + } } else { throw new Error(`Unknown mint owner program: ${mintOwner}`) } @@ -183,7 +208,23 @@ async function updateTokenExtensionsMetadata({ const keypair = privateKeyStr ? Keypair.fromSecretKey(bs58.decode(privateKeyStr)) : undefined const mintPubkey = new PublicKey(mintStr) - const updateAuthority = vaultPda ? new PublicKey(vaultPda) : keypair!.publicKey + const expectedAuthority = vaultPda ? new PublicKey(vaultPda) : keypair!.publicKey + + // Fetch Token Extensions metadata to verify update authority + const tokenMetadata = await getTokenMetadata(connection, mintPubkey, 'confirmed', TOKEN_2022_PROGRAM_ID) + if (!tokenMetadata) { + throw new Error(`Token Extensions metadata not found for mint: ${mintStr}`) + } + + // Verify update authority + if (!tokenMetadata.updateAuthority) { + throw new Error('Metadata has no update authority set (metadata is immutable)') + } + if (tokenMetadata.updateAuthority.toBase58() !== expectedAuthority.toBase58()) { + throw new Error( + `Update authority mismatch. Expected: ${expectedAuthority.toBase58()}, Actual: ${tokenMetadata.updateAuthority.toBase58()}` + ) + } // Get current mint account info to calculate rent difference const mintAccountInfo = await connection.getAccountInfo(mintPubkey) @@ -196,16 +237,26 @@ async function updateTokenExtensionsMetadata({ // Calculate additional bytes needed for new values and add rent if needed // Token Extensions metadata may need more space when updating to longer values - if (!vaultPda) { - // Estimate new total size: current size + length increases for each field - // Add a buffer of 100 bytes to be safe - const estimatedNewSize = - mintAccountInfo.data.length + (name?.length ?? 0) + (symbol?.length ?? 0) + (uri?.length ?? 0) + 100 - const rentExemptLamports = await connection.getMinimumBalanceForRentExemption(estimatedNewSize) - const currentLamports = mintAccountInfo.lamports - const additionalLamports = rentExemptLamports - currentLamports - - if (additionalLamports > 0) { + // Estimate new total size: current size + length increases for each field + // Add a buffer of 100 bytes to be safe + const estimatedNewSize = + mintAccountInfo.data.length + (name?.length ?? 0) + (symbol?.length ?? 0) + (uri?.length ?? 0) + 100 + const rentExemptLamports = await connection.getMinimumBalanceForRentExemption(estimatedNewSize) + const currentLamports = mintAccountInfo.lamports + const additionalLamports = rentExemptLamports - currentLamports + + if (additionalLamports > 0) { + if (vaultPda) { + // For multisig, include rent transfer from vault to mint + console.log(`Adding ${additionalLamports} lamports rent transfer from vault to mint`) + instructions.push( + SystemProgram.transfer({ + fromPubkey: new PublicKey(vaultPda), + toPubkey: mintPubkey, + lamports: additionalLamports, + }) + ) + } else { console.log(`Adding ${additionalLamports} lamports for additional rent`) instructions.push( SystemProgram.transfer({ @@ -222,7 +273,7 @@ async function updateTokenExtensionsMetadata({ createUpdateFieldInstruction({ programId: TOKEN_2022_PROGRAM_ID, metadata: mintPubkey, - updateAuthority, + updateAuthority: expectedAuthority, field: 'name', value: name, }) @@ -234,7 +285,7 @@ async function updateTokenExtensionsMetadata({ createUpdateFieldInstruction({ programId: TOKEN_2022_PROGRAM_ID, metadata: mintPubkey, - updateAuthority, + updateAuthority: expectedAuthority, field: 'symbol', value: symbol, }) @@ -246,7 +297,7 @@ async function updateTokenExtensionsMetadata({ createUpdateFieldInstruction({ programId: TOKEN_2022_PROGRAM_ID, metadata: mintPubkey, - updateAuthority, + updateAuthority: expectedAuthority, field: 'uri', value: uri, }) @@ -260,7 +311,7 @@ async function updateTokenExtensionsMetadata({ const { blockhash } = await connection.getLatestBlockhash() const message = new TransactionMessage({ - payerKey: vaultPda ? updateAuthority : keypair!.publicKey, + payerKey: vaultPda ? expectedAuthority : keypair!.publicKey, recentBlockhash: blockhash, instructions, }).compileToV0Message() From 8d3a3da8869de6ee7179b4f48c51efacc41e535a Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:42:48 +0800 Subject: [PATCH 5/9] fix --- examples/oft-solana/tasks/solana/updateMetadata.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/oft-solana/tasks/solana/updateMetadata.ts b/examples/oft-solana/tasks/solana/updateMetadata.ts index 5a4e75f1a7..82ac3f9719 100644 --- a/examples/oft-solana/tasks/solana/updateMetadata.ts +++ b/examples/oft-solana/tasks/solana/updateMetadata.ts @@ -96,6 +96,9 @@ task( isTestnet, }) } else { + if (sellerFeeBasisPoints != undefined) { + console.warn('sellerFeeBasisPoints is ignored for Token Extensions metadata') + } console.log('Detected Token-2022 mint with Token Extensions metadata') await updateTokenExtensionsMetadata({ eid, @@ -239,8 +242,10 @@ async function updateTokenExtensionsMetadata({ // Token Extensions metadata may need more space when updating to longer values // Estimate new total size: current size + length increases for each field // Add a buffer of 100 bytes to be safe - const estimatedNewSize = - mintAccountInfo.data.length + (name?.length ?? 0) + (symbol?.length ?? 0) + (uri?.length ?? 0) + 100 + const nameDelta = name ? Math.max(0, name.length - (tokenMetadata.name?.length ?? 0)) : 0 + const symbolDelta = symbol ? Math.max(0, symbol.length - (tokenMetadata.symbol?.length ?? 0)) : 0 + const uriDelta = uri ? Math.max(0, uri.length - (tokenMetadata.uri?.length ?? 0)) : 0 + const estimatedNewSize = mintAccountInfo.data.length + nameDelta + symbolDelta + uriDelta + 100 const rentExemptLamports = await connection.getMinimumBalanceForRentExemption(estimatedNewSize) const currentLamports = mintAccountInfo.lamports const additionalLamports = rentExemptLamports - currentLamports From a1eb591551b6c3742fb6e8ec1e8e1960981a258d Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:44:00 +0800 Subject: [PATCH 6/9] update --- examples/oft-solana/tasks/solana/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/oft-solana/tasks/solana/index.ts b/examples/oft-solana/tasks/solana/index.ts index 296f7c0496..299cd42513 100644 --- a/examples/oft-solana/tasks/solana/index.ts +++ b/examples/oft-solana/tasks/solana/index.ts @@ -166,10 +166,14 @@ export const getSolanaDeployment = ( /** * Safely load the OFT store PDA for a given Solana endpoint. - * Logs a warning if the deployment file is missing or malformed, - * and returns null so consumers can decide how to proceed. + * Returns null silently if deployment file doesn't exist (expected during create). + * Logs a warning only if the file exists but is malformed. */ export const getOftStoreAddress = (eid: EndpointId): string | null => { + const filePath = path.join('deployments', endpointIdToNetwork(eid), 'OFT.json') + if (!existsSync(filePath)) { + return null + } try { const { oftStore } = getSolanaDeployment(eid) if (!oftStore) { From 3b9f85e9b6df92fce8295a3420d909413a3305be Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:47:10 +0800 Subject: [PATCH 7/9] fix --- examples/oft-solana/tasks/solana/updateMetadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft-solana/tasks/solana/updateMetadata.ts b/examples/oft-solana/tasks/solana/updateMetadata.ts index 82ac3f9719..666fe7f431 100644 --- a/examples/oft-solana/tasks/solana/updateMetadata.ts +++ b/examples/oft-solana/tasks/solana/updateMetadata.ts @@ -324,7 +324,7 @@ async function updateTokenExtensionsMetadata({ const transaction = new VersionedTransaction(message) if (vaultPda) { - const base58Tx = bs58.encode(transaction.message.serialize()) + const base58Tx = bs58.encode(new Uint8Array(transaction.message.serialize())) console.log('==== Import the following base58 txn data into the Squads UI ====') console.log(base58Tx) } else { From 8edd5835fd1d34baada36d9d7539d6f963958e82 Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:48:41 +0800 Subject: [PATCH 8/9] changeset --- .changeset/quiet-wolves-sleep.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quiet-wolves-sleep.md diff --git a/.changeset/quiet-wolves-sleep.md b/.changeset/quiet-wolves-sleep.md new file mode 100644 index 0000000000..67553fbfbe --- /dev/null +++ b/.changeset/quiet-wolves-sleep.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/oft-solana-example": patch +--- + +fix: remove spurious warning when running lz:oft:solana:create before deployment file exists From fc50ddf1cc064713937ab339d79f06111cd091bb Mon Sep 17 00:00:00 2001 From: nazreen <10964594+nazreen@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:49:55 +0800 Subject: [PATCH 9/9] lockfiles --- examples/oft-hyperliquid/pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/oft-hyperliquid/pnpm-lock.yaml b/examples/oft-hyperliquid/pnpm-lock.yaml index a6515afce4..6c7a7c199b 100644 --- a/examples/oft-hyperliquid/pnpm-lock.yaml +++ b/examples/oft-hyperliquid/pnpm-lock.yaml @@ -19,8 +19,8 @@ devDependencies: specifier: ~2.3.39 version: 2.3.44(typescript@5.9.3) '@layerzerolabs/hyperliquid-composer': - specifier: ^2.0.5 - version: 2.0.5(@layerzerolabs/lz-evm-messagelib-v2@3.0.142)(@layerzerolabs/lz-evm-protocol-v2@3.0.142)(@layerzerolabs/lz-evm-v1-0.7@3.0.142)(@layerzerolabs/oapp-evm@0.4.1)(@openzeppelin/contracts-upgradeable@5.4.0)(@openzeppelin/contracts@5.4.0)(@types/node@18.18.14)(typescript@5.9.3) + specifier: ^2.0.6 + version: 2.0.6(@layerzerolabs/lz-evm-messagelib-v2@3.0.142)(@layerzerolabs/lz-evm-protocol-v2@3.0.142)(@layerzerolabs/lz-evm-v1-0.7@3.0.142)(@layerzerolabs/oapp-evm@0.4.1)(@openzeppelin/contracts-upgradeable@5.4.0)(@openzeppelin/contracts@5.4.0)(@types/node@18.18.14)(typescript@5.9.3) '@layerzerolabs/lz-definitions': specifier: ^3.0.59 version: 3.0.142 @@ -1364,8 +1364,8 @@ packages: typescript: 5.9.3 dev: true - /@layerzerolabs/hyperliquid-composer@2.0.5(@layerzerolabs/lz-evm-messagelib-v2@3.0.142)(@layerzerolabs/lz-evm-protocol-v2@3.0.142)(@layerzerolabs/lz-evm-v1-0.7@3.0.142)(@layerzerolabs/oapp-evm@0.4.1)(@openzeppelin/contracts-upgradeable@5.4.0)(@openzeppelin/contracts@5.4.0)(@types/node@18.18.14)(typescript@5.9.3): - resolution: {integrity: sha512-6hcJE2CPD0Km/0u/uw1zsdUM8VojpJav+hn/3iWD0E6JsRA3f3dkzTxYTIq02aFEXDkpGUCfPxHT1aKmRMLsZQ==} + /@layerzerolabs/hyperliquid-composer@2.0.6(@layerzerolabs/lz-evm-messagelib-v2@3.0.142)(@layerzerolabs/lz-evm-protocol-v2@3.0.142)(@layerzerolabs/lz-evm-v1-0.7@3.0.142)(@layerzerolabs/oapp-evm@0.4.1)(@openzeppelin/contracts-upgradeable@5.4.0)(@openzeppelin/contracts@5.4.0)(@types/node@18.18.14)(typescript@5.9.3): + resolution: {integrity: sha512-mELOIHnCx0Skd2g/BGaEN73TZ7LUTBLv20bCsJpRGx3SCNt0DjklAcJP+szhcRDBv84WgVAiyAwM2WtB+f24Bg==} hasBin: true peerDependencies: '@layerzerolabs/lz-evm-messagelib-v2': ^3.0.12