diff --git a/ccip-sdk/src/solana/exec.ts b/ccip-sdk/src/solana/exec.ts index abc7a9d2..42bb8412 100644 --- a/ccip-sdk/src/solana/exec.ts +++ b/ccip-sdk/src/solana/exec.ts @@ -13,13 +13,14 @@ import { import BN from 'bn.js' import { hexlify } from 'ethers' +import { CCIPError } from '../errors/CCIPError.ts' import { CCIPSolanaLookupTableNotFoundError } from '../errors/index.ts' import { type ExecutionInput, type WithLogger, ChainFamily } from '../types.ts' +import { bytesToBuffer, getDataBytes, toLeArray } from '../utils.ts' import { IDL as CCIP_OFFRAMP_IDL } from './idl/1.6.0/CCIP_OFFRAMP.ts' import { encodeSolanaOffchainTokenData } from './offchain.ts' import type { CCIPMessage_V1_6_Solana, UnsignedSolanaTx } from './types.ts' -import { bytesToBuffer, getDataBytes, toLeArray } from '../utils.ts' -import { simulationProvider } from './utils.ts' +import { simulateTransaction, simulationProvider } from './utils.ts' type ExecAlt = { initialIxs: TransactionInstruction[] @@ -415,6 +416,7 @@ async function autoDeriveExecutionAccounts({ let tokenIndex = 0 const [configPDA] = PublicKey.findProgramAddressSync([Buffer.from('config')], offramp.programId) + const resolvedLookupTables: AddressLookupTableAccount[] = [] while (stage) { const params: IdlTypes['DeriveAccountsExecuteParams'] = { @@ -438,19 +440,40 @@ async function autoDeriveExecutionAccounts({ })) } - // Execute as a view call to get the response - const response = (await offramp.methods + // Build instruction and simulate as V0 transaction (supports address lookup tables) + const ix = await offramp.methods .deriveAccountsExecute(params, stage) .accounts({ config: configPDA, }) .remainingAccounts(askWith) - .view() - .catch((error: unknown) => { - logger.error('Error deriving accounts:', error) - logger.error('Params:', params) - throw error as Error - })) as IdlTypes['DeriveAccountsResponse'] + .instruction() + + const simResult = await simulateTransaction( + { connection: offramp.provider.connection, logger }, + { + payerKey: payer, + instructions: [ix], + addressLookupTableAccounts: resolvedLookupTables.length ? resolvedLookupTables : undefined, + }, + ).catch((error: unknown) => { + logger.error('Error deriving accounts:', error) + logger.error('Params:', params) + throw error as Error + }) + + // Decode return data from simulation (skip 8-byte Anchor discriminator) + if (!simResult.returnData?.data[0]) { + throw new CCIPError( + 'SOLANA_FEE_RESULT_INVALID', + 'No return data from deriveAccountsExecute simulation', + ) + } + const response: IdlTypes['DeriveAccountsResponse'] = + offramp.coder.types.decode( + 'DeriveAccountsResponse', + Buffer.from(simResult.returnData.data[0], 'base64'), + ) // Check if we're at the start of a token transfer const isStartOfToken = /^TokenTransferStaticAccounts\/\d+\/0$/.test(response.currentStage) @@ -480,8 +503,12 @@ async function autoDeriveExecutionAccounts({ isSigner: meta.isSigner, })) - // Collect lookup tables - lookupTables.push(...response.lookUpTablesToSave) + // Collect lookup tables and resolve them for next iteration's V0 simulation + for (const lt of response.lookUpTablesToSave) { + lookupTables.push(lt) + const res = await offramp.provider.connection.getAddressLookupTable(lt) + if (res.value) resolvedLookupTables.push(res.value) + } stage = response.nextStage } diff --git a/ccip-sdk/src/solana/offchain.ts b/ccip-sdk/src/solana/offchain.ts index 1f881dfd..ad530365 100644 --- a/ccip-sdk/src/solana/offchain.ts +++ b/ccip-sdk/src/solana/offchain.ts @@ -41,5 +41,9 @@ export function encodeSolanaOffchainTokenData(data: OffchainTokenData): string { const encoded = cctpTokenPoolCoder.types.encode('MessageAndAttestation', messageAndAttestation) return hexlify(encoded) } + if (data?._tag === 'lbtc' && data.attestation) { + // LBTC attestation is already a hex string, just return it as-is + return hexlify(bytesToBuffer(data.attestation)) + } return '0x' }