Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
39983bd
feat: add transfer pool ownership op solana
mervin-link Aug 17, 2026
df46d8d
feat: add accept pool ownership op solana
mervin-link Aug 17, 2026
d296442
feat: add transfer authority op solana
mervin-link Aug 17, 2026
5547e60
fix: update export barrel
mervin-link Aug 18, 2026
126ec98
feat: add mint tokens op solana
mervin-link Aug 18, 2026
e46e1ef
fix: merge conflicts
mervin-link Aug 21, 2026
0eda9e2
Merge branch 'feat/DAPP-11191-transfer-authority' into feat/DAPP-1119…
mervin-link Aug 21, 2026
2120d95
fix: address comments
mervin-link Aug 24, 2026
7aa7ba8
fix: merge conflicts
mervin-link Aug 24, 2026
cbeffaf
fix: address comments
mervin-link Aug 24, 2026
2c3c86f
feat: add set can accept liquidity op solana
mervin-link Aug 24, 2026
24b7262
fix: add lock release token pool idl
mervin-link Aug 24, 2026
759f9c4
feat: add set rebalancer op solana
mervin-link Aug 24, 2026
61f2dd1
fix: update tsdoc
mervin-link Aug 25, 2026
f5de283
feat: add approve token op solana
mervin-link Aug 25, 2026
7db0b40
feat: add provider liquidity op solana
mervin-link Aug 25, 2026
95aaa54
fix: merge conflicts
mervin-link Aug 27, 2026
b56a872
fix: address comments
mervin-link Aug 27, 2026
da3d0a8
fix: revert unrelated changes
mervin-link Aug 27, 2026
af16159
Merge branch 'feat/DAPP-11229-approve-token' into feat/DAPP-11223-pro…
mervin-link Aug 27, 2026
37166c7
fix: add preflight checks
mervin-link Aug 27, 2026
2b09e87
fix: update tsdoc
mervin-link Aug 27, 2026
8ecbfc7
fix: extract validate pool liquidity config
mervin-link Aug 27, 2026
072d8fe
feat: add withdraw liquidity op solana
mervin-link Aug 26, 2026
c5e0bac
fix: merge conflicts
mervin-link Aug 27, 2026
9ba8621
fix: add preflight checks
mervin-link Aug 27, 2026
d89ba8a
feat: add update metadata authority op solana
mervin-link Aug 27, 2026
6d8b1a0
Merge branch 'cct-sdk' into feat/DAPP-11223-provide-liquidity
mervin-link Aug 28, 2026
40997e9
fix: lint errors
mervin-link Aug 28, 2026
48d2bc3
Merge branch 'feat/DAPP-11223-provide-liquidity' into feat/DAPP-11224…
mervin-link Aug 28, 2026
8ce4a3d
fix: lint errors
mervin-link Aug 28, 2026
8a7c987
fix: merge conflicts
mervin-link Aug 28, 2026
7eec581
fix: address comments
mervin-link Aug 31, 2026
a53f911
fix: refactor unit tests
mervin-link Aug 31, 2026
53989ad
Merge branch 'cct-sdk' into feat/DAPP-11230-update-metadata-authority
mervin-link Sep 2, 2026
6e657e3
Merge branch 'feat/DAPP-11230-update-metadata-authority' into fix/sol…
mervin-link Sep 2, 2026
e034631
fix: refactor export
mervin-link Sep 2, 2026
c68e439
feat: add owner override pending admint op solana
mervin-link Sep 2, 2026
992fc16
fix: update tsdoc and add preflight check
mervin-link Sep 2, 2026
4bb5493
fix: update tsdoc @example
mervin-link Sep 3, 2026
8a8bf60
feat: add includeApproval option in provide liquidity
mervin-link Sep 4, 2026
a9bde75
feat: add createRecipientATA option to mint tokens op
mervin-link Sep 4, 2026
85f9058
feat: add get token info op solana
mervin-link Sep 7, 2026
45b968d
Merge branch 'cct-sdk' into feat/DAPP-11371-get-token-info
mervin-link Sep 8, 2026
5549095
fix: address comments
mervin-link Sep 8, 2026
5f41774
Merge branch 'cct-sdk' into feat/DAPP-11371-get-token-info
mervin-link Sep 8, 2026
f0639c5
fix: package lock file - remove stale axios version
mervin-link Sep 8, 2026
f2357e1
feat(cct-sdk):Add createPoolSignerATA option to deploy token pool (#412)
mervin-link Sep 8, 2026
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
2 changes: 2 additions & 0 deletions ccip-sdk/src/cct/solana/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ describe('SolanaTokenManager (cct/solana)', () => {
getTokenAdminRegistryFor: async (address: string) =>
address === reader ? pool : address === overrideAddress ? overrideRouter : account,
getSupportedTokens: async () => [mint],
getTokenInfo: async () => ({ symbol: 'TKN', decimals: 6 }),
getTokenPoolRemotes: async () => ({}),
getRegistryTokenConfig: async (router: string) =>
router === overrideRouter
Expand Down Expand Up @@ -779,6 +780,7 @@ describe('SolanaTokenManager (cct/solana)', () => {
remoteChainSelector,
}),
],
['getTokenInfo', () => cct.getTokenInfo({ tokenAddress: mint })],
[
'getTokenPoolState',
() =>
Expand Down
34 changes: 34 additions & 0 deletions ccip-sdk/src/cct/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,11 @@ import {
type GenerateSetTokenAuthorityResult,
type GenerateUpdateMetadataAuthorityParams,
type GenerateUpdateMetadataAuthorityResult,
type GetTokenInfoParams,
type GetTokenInfoResult,
ApproveToken,
CreateTokenAccount,
GetTokenInfo,
MintTokens,
SetTokenAuthority,
UpdateMetadataAuthority,
Expand All @@ -190,6 +193,7 @@ export class SolanaTokenManager extends TokenManager<typeof ChainFamily.Solana>
// Token operations
readonly #approveToken = new ApproveToken()
readonly #createTokenAccount = new CreateTokenAccount()
readonly #getTokenInfo = new GetTokenInfo()
readonly #mintTokens = new MintTokens()
readonly #setTokenAuthority = new SetTokenAuthority()
readonly #updateMetadataAuthority = new UpdateMetadataAuthority()
Expand Down Expand Up @@ -2230,6 +2234,36 @@ export class SolanaTokenManager extends TokenManager<typeof ChainFamily.Solana>
return this.#transferAdmin.execute(this.chain, opts)
}

/**
* Reads an SPL token mint's metadata, program, supply, initialization state, and mint/freeze
* authorities.
*
* @remarks Metadata comes from {@link SolanaChain.getTokenInfo}; mint state comes directly from
* the SPL Token or Token-2022 mint account. Supply is in base units. Solana-only; no EVM CCT
* equivalent exists.
*
* @see {@link setTokenAuthority} Sets the mint or freeze authorities returned here.
* @see {@link updateMetadataAuthority} Updates the Metaplex metadata associated with this mint.
* @see {@link getTokenPoolState} Reads pool configuration rather than mint state.
* @see {@link SolanaChain.getTokenInfo} Reads the underlying token metadata.
*
* @throws {@link CCTParamsInvalidError} If `tokenAddress` is not a valid Solana public key.
* @throws {@link CCIPSplTokenInvalidError} If the token metadata is not a valid SPL token.
* @throws {@link CCIPTokenMintNotFoundError} If the mint account does not exist.
* @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program.
* @throws {@link CCIPTokenDataParseError} If the mint data cannot be parsed.
*
* @example
* ```ts
* const cct = SolanaTokenManager.fromChain(chain)
* const info = await cct.getTokenInfo({ tokenAddress: mint })
* console.log(`${info.symbol}: ${info.decimals} decimals`)
* ```
*/
getTokenInfo(opts: GetTokenInfoParams): Promise<GetTokenInfoResult> {
return this.#getTokenInfo.query(this.chain, opts)
}

/**
* Reads all, or one selected, Solana token pool remote-chain configurations.
*
Expand Down
86 changes: 86 additions & 0 deletions ccip-sdk/src/cct/solana/token/operations/get-token-info.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'

import { MINT_SIZE, MintLayout, TOKEN_PROGRAM_ID } from '@solana/spl-token'
import { Keypair, PublicKey } from '@solana/web3.js'

import { CCIPTokenDataParseError } from '../../../../errors/index.ts'
import type { SolanaChain } from '../../../../solana/index.ts'
import { GetTokenInfo } from './get-token-info.ts'

const tokenAddress = Keypair.generate().publicKey.toBase58()
const mintAuthority = Keypair.generate().publicKey

function mintData(): Buffer {
const data = Buffer.alloc(MINT_SIZE)
MintLayout.encode(
{
mintAuthorityOption: 1,
mintAuthority,
supply: 1_000_000n,
decimals: 6,
isInitialized: true,
freezeAuthorityOption: 0,
freezeAuthority: PublicKey.default,
},
data,
)
return data
}

describe('GetTokenInfo (cct/solana)', () => {
describe('query', () => {
it('delegates metadata to SolanaChain.getTokenInfo and reads mint state', async () => {
const metadata = { symbol: 'TKN', decimals: 6, name: 'Token' }
let received: string | undefined
const chain = {
connection: {
getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID, data: mintData() }),
},
getTokenInfo: async (token: string) => {
received = token
return metadata
},
} as unknown as SolanaChain

assert.deepEqual(await new GetTokenInfo().query(chain, { tokenAddress }), {
...metadata,
tokenProgram: TOKEN_PROGRAM_ID.toBase58(),
supply: 1_000_000n,
isInitialized: true,
mintAuthority: mintAuthority.toBase58(),
freezeAuthority: null,
})
assert.equal(received, tokenAddress)
})

it('rejects non-mint SPL accounts before fetching metadata', async () => {
let metadataCalls = 0
const chain = {
connection: {
getAccountInfo: async () => ({ owner: TOKEN_PROGRAM_ID, data: Buffer.alloc(1) }),
},
getTokenInfo: async () => {
metadataCalls++
return { symbol: 'TKN', decimals: 6 }
},
} as unknown as SolanaChain

await assert.rejects(new GetTokenInfo().query(chain, { tokenAddress }), (error: unknown) => {
assert.ok(error instanceof CCIPTokenDataParseError)
assert.equal(error.context.token, tokenAddress)
assert.ok(error.cause instanceof Error)
return true
})
assert.equal(metadataCalls, 0)
})
})

describe('validation', () => {
it('validates the mint address before querying', async () => {
await assert.rejects(
new GetTokenInfo().query({} as SolanaChain, { tokenAddress: 'not-a-public-key' }),
)
})
})
})
84 changes: 84 additions & 0 deletions ccip-sdk/src/cct/solana/token/operations/get-token-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { unpackMint } from '@solana/spl-token'
import type { PublicKey } from '@solana/web3.js'

import type { TokenInfo } from '../../../../chain.ts'
import { CCIPTokenDataParseError } from '../../../../errors/index.ts'
import type { SolanaChain } from '../../../../solana/index.ts'
import { resolveTokenMint } from '../../../../solana/utils.ts'
import { SolanaQuery } from '../../query.ts'
import { parsePublicKey } from '../../validate.ts'

/** Parameters for reading an SPL token mint's metadata. */
export type GetTokenInfoParams = {
/** SPL token mint address. */
tokenAddress: string
}

/** SPL token metadata and mint state. */
export type GetTokenInfoResult = TokenInfo & {
/** SPL Token or Token-2022 program that owns the mint. */
tokenProgram: string
/** Total minted supply in base units. */
supply: bigint
/** Whether the mint account has been initialized. */
isInitialized: boolean
/** Authority allowed to mint new tokens, or null for fixed-supply tokens. */
mintAuthority: string | null
/** Authority allowed to freeze token accounts, or null when freezing is disabled. */
freezeAuthority: string | null
}

type ParsedGetTokenInfoParams = GetTokenInfoParams & { mint: PublicKey }

/**
* Reads an SPL token mint's metadata and state.
*
* @throws {@link CCTParamsInvalidError} If `tokenAddress` is not a valid Solana public key.
* @throws {@link CCIPSplTokenInvalidError} If the token metadata is not a valid SPL token.
* @throws {@link CCIPTokenMintNotFoundError} If the mint account does not exist.
* @throws {@link CCIPTokenMintInvalidError} If the mint is not owned by an SPL Token program.
* @throws {@link CCIPTokenDataParseError} If the mint data cannot be parsed.
*/
export class GetTokenInfo extends SolanaQuery<
GetTokenInfoParams,
GetTokenInfoResult,
ParsedGetTokenInfoParams
> {
readonly name = 'getTokenInfo'

/** Converts and validates the mint address. */
protected prepare(params: GetTokenInfoParams): ParsedGetTokenInfoParams {
return {
...params,
mint: parsePublicKey(this.name, 'tokenAddress', params.tokenAddress),
}
}

/** Delegates metadata lookup and reads the mint's SPL state. */
protected async read(
chain: SolanaChain,
{ mint, tokenAddress }: ParsedGetTokenInfoParams,
): Promise<GetTokenInfoResult> {
const account = await resolveTokenMint(chain.connection, mint)

let state
try {
state = unpackMint(mint, account, account.owner)
} catch (cause) {
throw new CCIPTokenDataParseError(tokenAddress, {
cause: cause instanceof Error ? cause : undefined,
})
}

const info = await chain.getTokenInfo(tokenAddress)

return {
...info,
tokenProgram: account.owner.toBase58(),
supply: state.supply,
isInitialized: state.isInitialized,
mintAuthority: state.mintAuthority?.toBase58() ?? null,
freezeAuthority: state.freezeAuthority?.toBase58() ?? null,
}
}
}
1 change: 1 addition & 0 deletions ccip-sdk/src/cct/solana/token/operations/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './approve-token.ts'
export * from './create-token-account.ts'
export * from './deploy-token.ts'
export * from './get-token-info.ts'
export * from './mint-tokens.ts'
export * from './set-token-authority.ts'
export * from './update-metadata-authority.ts'
3 changes: 2 additions & 1 deletion ccip-sdk/src/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ export async function getDestTokenAmount({
let sourceTokenAddress, sourcePoolAddress, destTokenAddress
if ('destTokenAddress' in tokenAmount) {
;({ destTokenAddress, sourcePoolAddress, sourceTokenAddress } = tokenAmount)
} else if (!source) return tokenAmount // if we don't have a source, assume we were already given a dest `{token, amount}`
} else if (!source)
return tokenAmount // if we don't have a source, assume we were already given a dest `{token, amount}`
else {
;({ destTokenAddress, sourceTokenAddress, sourcePoolAddress } =
await sourceToDestTokenAddresses({
Expand Down
17 changes: 0 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading