Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 39 additions & 12 deletions ccip-sdk/src/solana/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down Expand Up @@ -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<typeof CCIP_OFFRAMP_IDL>['DeriveAccountsExecuteParams'] = {
Expand All @@ -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<typeof CCIP_OFFRAMP_IDL>['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<typeof CCIP_OFFRAMP_IDL>['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)
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 4 additions & 0 deletions ccip-sdk/src/solana/offchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Loading