import { type Commitment, type VersionedTransactionResponse, Connection, PublicKey } from '@solana/web3.js'; import { type BytesLike } from 'ethers'; import type { PickDeep } from 'type-fest'; import { type ChainContext, type ChainStatic, type GetBalanceOpts, type LogFilter, type TokenInfo, type TokenPoolRemote, type TokenPrice, type TokenTransferFeeOpts, Chain } from '../chain.ts'; import { type EVMExtraArgsV2, type ExtraArgs, type SVMExtraArgsV1 } from '../extra-args.ts'; import type { LeafHasher } from '../hasher/common.ts'; import { type AnyMessage, type CCIPExecution, type CCIPMessage, type CCIPRequest, type CCIPVerifications, type ChainLog, type ChainTransaction, type CommitReport, type ExecutionReceipt, type Lane, type MergeArrayElements, type NetworkInfo, type WithLogger, CCIPVersion, ChainFamily } from '../types.ts'; import { type CCIPMessage_V1_6_Solana, type UnsignedSolanaTx } from './types.ts'; export type { UnsignedSolanaTx }; /** Solana-specific log structure with transaction reference and log level. */ export type SolanaLog = ChainLog & { tx: SolanaTransaction; data: string; level: number; }; /** Solana-specific transaction structure with versioned transaction response. */ export type SolanaTransaction = MergeArrayElements; /** * Solana chain implementation supporting Solana networks. * * Provides methods for sending CCIP cross-chain messages, querying message * status, fetching fee quotes, and manually executing pending messages on * Solana networks. * * @remarks * Solana uses CCIP v1.6+ protocol only. * * @example Create from RPC URL * ```typescript * import { SolanaChain } from '@chainlink/ccip-sdk' * * const chain = await SolanaChain.fromUrl('https://api.devnet.solana.com') * console.log(`Connected to: ${chain.network.name}`) * ``` * * @example Query messages in a transaction * ```typescript * const requests = await chain.getMessagesInTx('5abc123...') * for (const req of requests) { * console.log(`Message ID: ${req.message.messageId}`) * } * ``` */ export declare class SolanaChain extends Chain { static readonly family: "SVM"; static readonly decimals = 9; connection: Connection; commitment: Commitment; /** * Creates a new SolanaChain instance. * @param connection - Solana connection instance. * @param network - Network information for this chain. */ constructor(connection: Connection, network: NetworkInfo, ctx?: ChainContext); /** * Creates a Solana connection from a URL. * @param url - RPC endpoint URL (https://, http://, wss://, or ws://). * @param ctx - context containing logger. * @returns Solana Connection instance. * @throws {@link CCIPDataFormatUnsupportedError} if URL format is invalid */ static _getConnection(url: string, ctx?: WithLogger): Connection; /** * Creates a SolanaChain instance from an existing connection. * @param connection - Solana Connection instance. * @param ctx - context containing logger. * @returns A new SolanaChain instance. */ static fromConnection(connection: Connection, ctx?: ChainContext): Promise; /** * Creates a SolanaChain instance from an RPC URL. * * @param url - RPC endpoint URL (https://, http://, wss://, or ws://). * @param ctx - Optional context containing logger and API client configuration. * @returns A new SolanaChain instance connected to the specified network. * @throws {@link CCIPChainNotFoundError} if chain cannot be identified from genesis hash * * @example * ```typescript * // Create from devnet URL * const chain = await SolanaChain.fromUrl('https://api.devnet.solana.com') * * // With custom logger * const chain = await SolanaChain.fromUrl(url, { logger: customLogger }) * ``` */ static fromUrl(url: string, ctx?: ChainContext): Promise; /** * {@inheritDoc Chain.getBlockTimestamp} * @throws {@link CCIPBlockTimeNotFoundError} if block time cannot be retrieved */ getBlockTimestamp(block: number | 'latest' | 'finalized'): Promise; /** * {@inheritDoc Chain.getTransaction} * @throws {@link CCIPTransactionNotFoundError} if transaction not found */ getTransaction(hash: string): Promise; /** * Internal method to get transactions for an address with pagination. * @param opts - Log filter options. * @returns Async generator of Solana transactions. */ getTransactionsForAddress(opts: Omit): AsyncGenerator; /** * Retrieves logs from Solana transactions with enhanced chronological ordering. * * Behavior: * - If opts.startBlock or opts.startTime is provided: * * Fetches ALL signatures for the address going back in time * * Continues fetching until finding signatures older than the start target * * Filters out signatures older than start criteria * * Returns logs in chronological order (oldest first) * * - If opts.startBlock and opts.startTime are omitted: * * Uses slot 0 as the forward start for non-watch queries * * @param opts - Log filter options containing: * - `startBlock`: Starting slot number (inclusive) * Solana's special case: if startBlock=0, fetch only one page of getSignaturesForAddress * - `startTime`: Starting Unix timestamp (inclusive) * - `endBlock`: Ending slot number (inclusive) * - `endBefore`: Fetch signatures before this transaction * - `address`: Program address to filter logs by (required for Solana) * - `topics`: Array of topics to filter logs by (optional); either 0x-8B discriminants or event names * - `watch`: Watch for new logs * - `programs`: Special option to allow querying by address of interest, but yielding matching * logs from specific (string address) program or any (true) * @returns AsyncIterableIterator of parsed ChainLog objects. * @throws {@link CCIPLogsAddressRequiredError} if address is not provided * @throws {@link CCIPTopicsInvalidError} if topics contain invalid values */ getLogs(opts: LogFilter & { programs?: string[] | true; }): AsyncGenerator; /** {@inheritDoc Chain.getMessagesInBatch} */ getMessagesInBatch>(request: R, range: Pick, opts?: Pick): Promise; /** {@inheritDoc Chain.typeAndVersion} */ typeAndVersion(address: string): Promise<[type: string, version: string, typeAndVersion: string, suffix?: string | undefined]>; /** {@inheritDoc Chain.getRouterForOnRamp} */ getRouterForOnRamp(onRamp: string, _destChainSelector: bigint): Promise; /** * {@inheritDoc Chain.getRouterForOffRamp} * @throws {@link CCIPSolanaRefAddressesNotFoundError} if reference addresses PDA not found */ getRouterForOffRamp(offRamp: string, _sourceChainSelector: bigint): Promise; /** {@inheritDoc Chain.getNativeTokenForRouter} */ getNativeTokenForRouter(_router: string): Promise; /** * {@inheritDoc Chain.getOffRampsForRouter} * @throws {@link CCIPSolanaOffRampEventsNotFoundError} if no OffRamp events found */ getOffRampsForRouter(router: string, sourceChainSelector: bigint): Promise; /** {@inheritDoc Chain.getOnRampForRouter} */ getOnRampForRouter(router: string, _destChainSelector: bigint): Promise; /** {@inheritDoc Chain.getOnRampsForOffRamp} */ getOnRampsForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise; /** * {@inheritDoc Chain.getTokenForTokenPool} * @throws {@link CCIPTokenPoolInfoNotFoundError} if token pool info not found */ getTokenForTokenPool(tokenPool: string): Promise; /** * {@inheritDoc Chain.getTokenInfo} * @throws {@link CCIPSplTokenInvalidError} if token is not a valid SPL token * @throws {@link CCIPTokenDataParseError} if token data cannot be parsed */ getTokenInfo(token: string): Promise; /** * {@inheritDoc Chain.getBalance} * @throws {@link CCIPTokenAccountNotFoundError} if token account not found */ getBalance(opts: GetBalanceOpts): Promise; /** * Fetches token metadata from Metaplex. * @param mintPublicKey - Token mint public key. * @returns Token name and symbol, or null if not found. */ _fetchTokenMetadata(mintPublicKey: PublicKey): Promise<{ name: string; symbol: string; } | null>; /** * Decodes a CCIP message from a Solana log event. * @param log - Log with data field. * @returns Decoded CCIPMessage or undefined if not valid. * @throws {@link CCIPExtraArgsInvalidError} if extra args cannot be decoded */ static decodeMessage({ data }: { data: unknown; }): CCIPMessage | undefined; /** * Decodes extra arguments from Solana CCIP messages. * @param extraArgs - Encoded extra arguments bytes. * @returns Decoded EVMExtraArgsV2 or undefined if unknown format. * @throws {@link CCIPExtraArgsLengthInvalidError} if extra args length is invalid */ static decodeExtraArgs(extraArgs: BytesLike): (EVMExtraArgsV2 & { _tag: 'EVMExtraArgsV2'; }) | undefined; /** * Encodes extra arguments for Solana CCIP messages. * @param args - Extra arguments to encode. * @returns Encoded extra arguments as hex string. * @throws {@link CCIPSolanaExtraArgsEncodingError} if SVMExtraArgsV1 encoding is attempted */ static encodeExtraArgs(args: ExtraArgs): string; /** * Decodes commit reports from a Solana log event. * @param log - Log with data field. * @param lane - Lane info for filtering. * @returns Array of CommitReport or undefined if not valid. * @throws {@link CCIPLogDataMissingError} if log data is missing */ static decodeCommits(log: Pick, lane?: Omit): CommitReport[] | undefined; /** * Decodes an execution receipt from a Solana log event. * @param log - Log with data, tx, and index fields. * @returns ExecutionReceipt or undefined if not valid. * @throws {@link CCIPLogDataMissingError} if log data is missing * @throws {@link CCIPExecutionStateInvalidError} if execution state is invalid */ static decodeReceipt(log: Pick): ExecutionReceipt | undefined; /** * Converts bytes to a Solana address (Base58). * @param bytes - Bytes to convert. * @returns Base58-encoded Solana address. */ static getAddress(bytes: BytesLike): string; /** * Validates a transaction hash format for Solana */ static isTxHash(v: unknown): v is string; /** * Gets the leaf hasher for Solana destination chains. * @param lane - Lane configuration. * @returns Leaf hasher function. */ static getDestLeafHasher(lane: Lane, ctx?: WithLogger): LeafHasher; /** * {@inheritDoc Chain.getTokenAdminRegistryFor} * @throws {@link CCIPContractNotRouterError} if address is not a Router */ getTokenAdminRegistryFor(address: string): Promise; /** {@inheritDoc Chain.getFee} */ getFee({ router, destChainSelector, message }: Parameters[0]): Promise; /** * {@inheritDoc Chain.generateUnsignedSendMessage} * @returns instructions - array of instructions; `ccipSend` is last, after any approval * lookupTables - array of lookup tables for `ccipSend` call * mainIndex - instructions.length - 1 */ generateUnsignedSendMessage(opts: Parameters[0]): Promise; /** * {@inheritDoc Chain.sendMessage} * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Solana wallet */ sendMessage(opts: Parameters[0]): Promise; /** * {@inheritDoc Chain.generateUnsignedExecute} * @returns instructions - array of instructions to execute the report * lookupTables - array of lookup tables for `manuallyExecute` call * mainIndex - index of the `manuallyExecute` instruction in the array; last unless * forceLookupTable is set, in which case last is ALT deactivation tx, and manuallyExecute is * second to last * @throws {@link CCIPExecutionReportChainMismatchError} if message is not a Solana message */ generateUnsignedExecute({ payer, ...opts }: Parameters[0]): Promise; /** * {@inheritDoc Chain.execute} * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Solana wallet */ execute(opts: Parameters[0] & { waitDeactivation?: boolean; clearLeftoverAccounts?: boolean; }): Promise; /** {@inheritDoc Chain.estimateReceiveExecution} */ estimateReceiveExecution(opts: Parameters>[0]): Promise; /** * Clean up and recycle buffers and address lookup tables owned by wallet * @param opts - cleanUp options * - wallet - wallet instance to sign txs * - waitDeactivation - Whether to wait for lookup table deactivation cool down period * (513 slots) to pass before closing; by default, we deactivate (if needed) and move on, to * close other ready ALTs * @throws {@link CCIPWalletInvalidError} if wallet is not a valid Solana wallet */ cleanUpBuffers(opts: { wallet: unknown; waitDeactivation?: boolean; }): Promise; /** * Parses raw Solana data into typed structures. * @param data - Raw data to parse. * @returns Parsed data or undefined. */ static parse(data: unknown): { sourceChainSelector: bigint; destChainSelector: bigint; messageNumber: bigint; executionGasLimit: number; ccipReceiveGasLimit: number; finality: import("../extra-args.ts").FinalityRequested; ccvAndExecutorHash: string; onRampAddress: string; offRampAddress: string; sender: string; receiver: string; destBlob: string; data: string; tokenAmounts: import("../messages.ts").MessageV1["tokenTransfer"]; sequenceNumber: import("../messages.ts").MessageV1["messageNumber"]; feeTokenAmount: bigint; encodedMessage: string; feeToken: string; messageId: string; receipts: readonly { destBytesOverhead: bigint; destGasLimit: bigint; extraArgs: string; feeTokenAmount: bigint; issuer: string; }[]; tokenAmountBeforeTokenPoolFees: bigint; verifierBlobs: readonly string[]; } | { data: string; feeToken: string; feeTokenAmount: bigint; gasLimit: bigint; messageId: string; nonce: bigint; receiver: string; sender: string; sequenceNumber: bigint; sourceChainSelector: bigint; sourceTokenData: readonly string[]; strict: boolean; tokenAmounts: readonly { amount: bigint; token: string; }[]; } | { [k: string]: string; program: string; } | { program: string; message: unknown; } | import("../evm/messages.ts").CCIPMessage_V1_6_EVM | CCIPMessage_V1_6_Solana | (import("../extra-args.ts").EVMExtraArgsV1 & { allowOutOfOrderExecution: boolean; } & { _tag: "EVMExtraArgsV2"; }) | undefined; /** * Solana specialization: use getProgramAccounts to fetch commit reports from PDAs */ getVerifications(opts: Parameters[0]): Promise; /** {@inheritDoc Chain.getExecutionReceipts} */ getExecutionReceipts(opts: Parameters[0]): AsyncIterableIterator; /** * {@inheritDoc Chain.getRegistryTokenConfig} * @throws {@link CCIPTokenNotConfiguredError} if token is not configured in registry */ getRegistryTokenConfig(registry: string, token: string): Promise<{ administrator: string; pendingAdministrator?: string; tokenPool?: string; }>; /** * {@inheritDoc Chain.getTokenPoolConfig} * @throws {@link CCIPTokenPoolStateNotFoundError} if token pool state not found */ getTokenPoolConfig(tokenPool: string, _feeOpts?: TokenTransferFeeOpts): Promise<{ token: string; router: string; tokenPoolProgram: string; typeAndVersion?: string; }>; /** * {@inheritDoc Chain.getTokenPoolRemotes} * @throws {@link CCIPTokenPoolStateNotFoundError} if token pool state not found * @throws {@link CCIPTokenPoolChainConfigNotFoundError} if chain config not found for specified selector */ getTokenPoolRemotes(tokenPool: string, remoteChainSelector?: bigint): Promise>; /** {@inheritDoc Chain.getSupportedTokens} */ getSupportedTokens(router: string): Promise; /** {@inheritDoc Chain.getFeeTokens} */ getFeeTokens(router: string): Promise>; /** {@inheritDoc Chain.getTokenPrice} */ getTokenPrice(opts: { router: string; token: string; timestamp?: number; }): Promise; /** * Gets the router configuration from the Config PDA. * @param router - Router program address. * @returns Router configuration including feeQuoter. */ _getRouterConfig(router: string): Promise<{ defaultCodeVersion: ({ v1?: undefined; } & { default: Record; }) | ({ default?: undefined; } & { v1: Record; }); feeAggregator: PublicKey; feeQuoter: PublicKey; linkTokenMint: PublicKey; owner: PublicKey; proposedOwner: PublicKey; rmnRemote: PublicKey; svmChainSelector: import("bn.js"); version: number; }>; /** * Returns a copy of a message, populating missing fields like `extraArgs` with defaults. * It's expected to return a message suitable at least for basic token transfers. * * @remarks * Solana-specific receiver/tokenReceiver handling: * - Explicit `tokenReceiver` in extraArgs: both `receiver` and `tokenReceiver` are kept as provided. * - Tokens but no explicit `tokenReceiver`: `receiver` is set to `PublicKey.default` and * `tokenReceiver` is set to `message.receiver`. * - No tokens: `tokenReceiver` is set to `PublicKey.default` and `receiver` is `message.receiver`. * * Accepts `gasLimit` as an alias for `computeUnits` in extraArgs. * * @param message - AnyMessage (from source), containing at least `receiver` * @returns A message suitable for `sendMessage` to this destination chain family * @throws {@link CCIPArgumentInvalidError} if tokenReceiver missing when sending tokens with data * @throws {@link CCIPArgumentInvalidError} if extraArgs contains unknown fields for SVMExtraArgsV1 */ static buildMessageForDest(message: Parameters[0]): AnyMessage & { extraArgs: SVMExtraArgsV1; }; } //# sourceMappingURL=index.d.ts.map