@xswap-link/sdk 0.10.8 → 0.10.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +10 -1
- package/CHANGELOG.md +12 -0
- package/dist/index.global.js +331 -331
- package/dist/index.js +9 -9
- package/dist/index.mjs +9 -9
- package/package.json +6 -3
- package/src/components/Swap/SwapView/ConfirmationView/TxOverview/index.tsx +106 -9
- package/src/components/Swap/SwapView/SwapButton/index.tsx +11 -11
- package/src/constants/index.ts +18 -0
- package/src/context/SwapProvider.tsx +7 -3
- package/src/services/api.ts +1 -0
- package/src/services/svm/README.md +483 -0
- package/src/services/svm/bindings/accounts/AllowedOfframp.ts +73 -0
- package/src/services/svm/bindings/accounts/Config.ts +153 -0
- package/src/services/svm/bindings/accounts/DestChain.ts +113 -0
- package/src/services/svm/bindings/accounts/Nonce.ts +97 -0
- package/src/services/svm/bindings/accounts/index.ts +15 -0
- package/src/services/svm/bindings/accounts/tokenAdminRegistry.ts +128 -0
- package/src/services/svm/bindings/errors/anchor.ts +773 -0
- package/src/services/svm/bindings/errors/custom.ts +375 -0
- package/src/services/svm/bindings/errors/index.ts +62 -0
- package/src/services/svm/bindings/instructions/ccipSend.ts +112 -0
- package/src/services/svm/bindings/instructions/getFee.ts +73 -0
- package/src/services/svm/bindings/instructions/index.ts +4 -0
- package/src/services/svm/bindings/programId.ts +6 -0
- package/src/services/svm/bindings/types/BaseChain.ts +92 -0
- package/src/services/svm/bindings/types/BaseConfig.ts +184 -0
- package/src/services/svm/bindings/types/CodeVersion.ts +88 -0
- package/src/services/svm/bindings/types/CrossChainAmount.ts +53 -0
- package/src/services/svm/bindings/types/DestChainConfig.ts +76 -0
- package/src/services/svm/bindings/types/DestChainState.ts +76 -0
- package/src/services/svm/bindings/types/GetFeeResult.ts +72 -0
- package/src/services/svm/bindings/types/LockOrBurnInV1.ts +102 -0
- package/src/services/svm/bindings/types/LockOrBurnOutV1.ts +79 -0
- package/src/services/svm/bindings/types/RampMessageHeader.ts +94 -0
- package/src/services/svm/bindings/types/RateLimitConfig.ts +72 -0
- package/src/services/svm/bindings/types/RateLimitTokenBucket.ts +76 -0
- package/src/services/svm/bindings/types/ReleaseOrMintInV1.ts +156 -0
- package/src/services/svm/bindings/types/ReleaseOrMintOutV1.ts +53 -0
- package/src/services/svm/bindings/types/RemoteAddress.ts +61 -0
- package/src/services/svm/bindings/types/RemoteConfig.ts +86 -0
- package/src/services/svm/bindings/types/RestoreOnAction.ts +120 -0
- package/src/services/svm/bindings/types/SVM2AnyMessage.ts +128 -0
- package/src/services/svm/bindings/types/SVM2AnyRampMessage.ts +166 -0
- package/src/services/svm/bindings/types/SVM2AnyTokenTransfer.ts +118 -0
- package/src/services/svm/bindings/types/SVMTokenAmount.ts +64 -0
- package/src/services/svm/bindings/types/index.ts +78 -0
- package/src/services/svm/core/client/accounts.ts +97 -0
- package/src/services/svm/core/client/events.ts +95 -0
- package/src/services/svm/core/client/fee.ts +279 -0
- package/src/services/svm/core/client/index.ts +150 -0
- package/src/services/svm/core/client/send.ts +607 -0
- package/src/services/svm/core/client/utils.ts +131 -0
- package/src/services/svm/core/models.ts +236 -0
- package/src/services/svm/index.ts +32 -0
- package/src/services/svm/utils/conversion.ts +62 -0
- package/src/services/svm/utils/errors.ts +51 -0
- package/src/services/svm/utils/keypair.ts +19 -0
- package/src/services/svm/utils/logger.ts +171 -0
- package/src/services/svm/utils/pdas/common.ts +15 -0
- package/src/services/svm/utils/pdas/feeQuoter.ts +68 -0
- package/src/services/svm/utils/pdas/index.ts +12 -0
- package/src/services/svm/utils/pdas/receiver.ts +58 -0
- package/src/services/svm/utils/pdas/rmnRemote.ts +23 -0
- package/src/services/svm/utils/pdas/router.ts +328 -0
- package/src/services/svm/utils/pdas/tokenpool.ts +161 -0
- package/src/services/svm/utils/transaction.ts +132 -0
- package/src/utils/validation.ts +7 -3
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as CodeVersion from "./CodeVersion";
|
|
2
|
+
import * as RestoreOnAction from "./RestoreOnAction";
|
|
3
|
+
|
|
4
|
+
export { RampMessageHeader } from "./RampMessageHeader";
|
|
5
|
+
export type {
|
|
6
|
+
RampMessageHeaderFields,
|
|
7
|
+
RampMessageHeaderJSON,
|
|
8
|
+
} from "./RampMessageHeader";
|
|
9
|
+
export { SVM2AnyRampMessage } from "./SVM2AnyRampMessage";
|
|
10
|
+
export type {
|
|
11
|
+
SVM2AnyRampMessageFields,
|
|
12
|
+
SVM2AnyRampMessageJSON,
|
|
13
|
+
} from "./SVM2AnyRampMessage";
|
|
14
|
+
export { SVM2AnyTokenTransfer } from "./SVM2AnyTokenTransfer";
|
|
15
|
+
export type {
|
|
16
|
+
SVM2AnyTokenTransferFields,
|
|
17
|
+
SVM2AnyTokenTransferJSON,
|
|
18
|
+
} from "./SVM2AnyTokenTransfer";
|
|
19
|
+
export { SVM2AnyMessage } from "./SVM2AnyMessage";
|
|
20
|
+
export type {
|
|
21
|
+
SVM2AnyMessageFields,
|
|
22
|
+
SVM2AnyMessageJSON,
|
|
23
|
+
} from "./SVM2AnyMessage";
|
|
24
|
+
export { SVMTokenAmount } from "./SVMTokenAmount";
|
|
25
|
+
export type {
|
|
26
|
+
SVMTokenAmountFields,
|
|
27
|
+
SVMTokenAmountJSON,
|
|
28
|
+
} from "./SVMTokenAmount";
|
|
29
|
+
export { CrossChainAmount } from "./CrossChainAmount";
|
|
30
|
+
export type {
|
|
31
|
+
CrossChainAmountFields,
|
|
32
|
+
CrossChainAmountJSON,
|
|
33
|
+
} from "./CrossChainAmount";
|
|
34
|
+
export { GetFeeResult } from "./GetFeeResult";
|
|
35
|
+
export type { GetFeeResultFields, GetFeeResultJSON } from "./GetFeeResult";
|
|
36
|
+
export { DestChainState } from "./DestChainState";
|
|
37
|
+
export type {
|
|
38
|
+
DestChainStateFields,
|
|
39
|
+
DestChainStateJSON,
|
|
40
|
+
} from "./DestChainState";
|
|
41
|
+
export { DestChainConfig } from "./DestChainConfig";
|
|
42
|
+
export type {
|
|
43
|
+
DestChainConfigFields,
|
|
44
|
+
DestChainConfigJSON,
|
|
45
|
+
} from "./DestChainConfig";
|
|
46
|
+
export { CodeVersion };
|
|
47
|
+
|
|
48
|
+
export type CodeVersionKind = CodeVersion.Default | CodeVersion.V1;
|
|
49
|
+
export type CodeVersionJSON = CodeVersion.DefaultJSON | CodeVersion.V1JSON;
|
|
50
|
+
|
|
51
|
+
export { RestoreOnAction };
|
|
52
|
+
|
|
53
|
+
export type RestoreOnActionKind =
|
|
54
|
+
| RestoreOnAction.None
|
|
55
|
+
| RestoreOnAction.Upgrade
|
|
56
|
+
| RestoreOnAction.Rollback;
|
|
57
|
+
export type RestoreOnActionJSON =
|
|
58
|
+
| RestoreOnAction.NoneJSON
|
|
59
|
+
| RestoreOnAction.UpgradeJSON
|
|
60
|
+
| RestoreOnAction.RollbackJSON;
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
RemoteAddress,
|
|
64
|
+
RemoteAddressFields,
|
|
65
|
+
RemoteAddressJSON,
|
|
66
|
+
} from "./RemoteAddress";
|
|
67
|
+
export {
|
|
68
|
+
RemoteConfigFields,
|
|
69
|
+
RemoteConfigJSON,
|
|
70
|
+
RemoteConfig,
|
|
71
|
+
} from "./RemoteConfig";
|
|
72
|
+
export {
|
|
73
|
+
RateLimitTokenBucketFields,
|
|
74
|
+
RateLimitTokenBucketJSON,
|
|
75
|
+
RateLimitTokenBucket,
|
|
76
|
+
} from "./RateLimitTokenBucket";
|
|
77
|
+
|
|
78
|
+
export { RateLimitConfig, RateLimitConfigFields, RateLimitConfigJSON } from "./RateLimitConfig";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as anchor from "@coral-xyz/anchor";
|
|
2
|
+
import { AnchorProvider } from "@coral-xyz/anchor";
|
|
3
|
+
import { PublicKey } from "@solana/web3.js";
|
|
4
|
+
import { CCIPContext } from "../models";
|
|
5
|
+
import {
|
|
6
|
+
tokenAdminRegistry,
|
|
7
|
+
tokenAdminRegistryFields,
|
|
8
|
+
} from "../../bindings/accounts";
|
|
9
|
+
import { findTokenAdminRegistryPDA } from "../../utils/pdas";
|
|
10
|
+
import { createLogger, Logger, LogLevel } from "../../utils/logger";
|
|
11
|
+
import { createErrorEnhancer } from "../../utils/errors";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Token Admin Registry account type
|
|
15
|
+
*/
|
|
16
|
+
export type TokenAdminRegistry = tokenAdminRegistryFields;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Client for reading CCIP-related accounts
|
|
20
|
+
*/
|
|
21
|
+
export class CCIPAccountReader {
|
|
22
|
+
readonly provider: AnchorProvider;
|
|
23
|
+
readonly programId: PublicKey;
|
|
24
|
+
private readonly logger: Logger;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new CCIPAccountReader using a context
|
|
28
|
+
* @param context SDK context with provider, config and logger
|
|
29
|
+
*/
|
|
30
|
+
constructor(readonly context: CCIPContext) {
|
|
31
|
+
this.logger =
|
|
32
|
+
context.logger ??
|
|
33
|
+
createLogger("account-reader", { level: LogLevel.INFO });
|
|
34
|
+
|
|
35
|
+
// Use the provider from the context to create an AnchorProvider
|
|
36
|
+
this.provider = new AnchorProvider(
|
|
37
|
+
context.provider.connection,
|
|
38
|
+
context.provider.wallet as any, // Cast to any to satisfy AnchorProvider
|
|
39
|
+
{}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Set Anchor provider globally
|
|
43
|
+
anchor.setProvider(this.provider);
|
|
44
|
+
|
|
45
|
+
// Use router from config
|
|
46
|
+
this.programId = context.config.ccipRouterProgramId;
|
|
47
|
+
|
|
48
|
+
this.logger.debug(
|
|
49
|
+
`CCIPAccountReader initialized: programId=${this.programId.toString()}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetches a token admin registry account
|
|
55
|
+
* @param mint Token mint
|
|
56
|
+
* @returns Token admin registry account
|
|
57
|
+
*/
|
|
58
|
+
async getTokenAdminRegistry(mint: PublicKey): Promise<TokenAdminRegistry> {
|
|
59
|
+
const enhanceError = createErrorEnhancer(this.logger);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
this.logger.debug(
|
|
63
|
+
`Fetching token admin registry for mint: ${mint.toString()}`
|
|
64
|
+
);
|
|
65
|
+
this.logger.trace(`Program ID: ${this.programId.toString()}`);
|
|
66
|
+
const [pda] = findTokenAdminRegistryPDA(mint, this.programId);
|
|
67
|
+
this.logger.trace(`Token admin registry PDA: ${pda.toString()}`);
|
|
68
|
+
|
|
69
|
+
// Use the generated tokenAdminRegistry.fetch method
|
|
70
|
+
const tokenRegistry = await tokenAdminRegistry.fetch(
|
|
71
|
+
this.context.provider.connection,
|
|
72
|
+
pda,
|
|
73
|
+
this.programId
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!tokenRegistry) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Token admin registry not found for mint: ${mint.toString()}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.logger.trace("Retrieved token admin registry:", {
|
|
83
|
+
mint: tokenRegistry.mint.toString(),
|
|
84
|
+
administrator: tokenRegistry.administrator.toString(),
|
|
85
|
+
lookupTable: tokenRegistry.lookupTable.toString(),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return tokenRegistry;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
throw enhanceError(error, {
|
|
91
|
+
operation: "getTokenAdminRegistry",
|
|
92
|
+
mint: mint.toString(),
|
|
93
|
+
programId: this.programId.toString(),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { createErrorEnhancer } from "../../utils/errors";
|
|
4
|
+
import { CCIPContext } from "../models";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses a CCIP message sent event from a transaction
|
|
8
|
+
*
|
|
9
|
+
* @param context SDK context with provider, config and logger
|
|
10
|
+
* @param txSignature Transaction signature
|
|
11
|
+
* @returns Parsed event data with messageId if available
|
|
12
|
+
*/
|
|
13
|
+
export async function parseCCIPMessageSentEvent(
|
|
14
|
+
context: CCIPContext,
|
|
15
|
+
txSignature: string,
|
|
16
|
+
): Promise<{
|
|
17
|
+
messageId?: string;
|
|
18
|
+
}> {
|
|
19
|
+
if (!context.logger) {
|
|
20
|
+
throw new Error("Logger is required for parseCCIPMessageSentEvent");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const logger = context.logger;
|
|
24
|
+
const config = context.config;
|
|
25
|
+
const connection = context.provider.connection;
|
|
26
|
+
|
|
27
|
+
const enhanceError = createErrorEnhancer(logger);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
logger.info(
|
|
31
|
+
`Parsing CCIP message sent event for transaction: ${txSignature}`,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Get transaction details with logs
|
|
35
|
+
logger.debug(`Fetching transaction details with logs`);
|
|
36
|
+
const tx = await connection.getParsedTransaction(txSignature, {
|
|
37
|
+
commitment: "confirmed",
|
|
38
|
+
maxSupportedTransactionVersion: 0,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!tx || !tx.meta || !tx.meta.logMessages) {
|
|
42
|
+
logger.warn(`No transaction logs found for ${txSignature}`);
|
|
43
|
+
return { messageId: undefined };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get the router program ID as string for comparison
|
|
47
|
+
const routerProgramId = config.ccipRouterProgramId.toString();
|
|
48
|
+
logger.debug(
|
|
49
|
+
`Looking for program return log from CCIP Router: ${routerProgramId}`,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Log messages in TRACE mode
|
|
53
|
+
logger.trace("Transaction logs:", tx.meta.logMessages);
|
|
54
|
+
|
|
55
|
+
// Look for the program return log from the CCIP Router program
|
|
56
|
+
const programReturnLog = tx.meta.logMessages.find((log) =>
|
|
57
|
+
log.includes(`Program return: ${routerProgramId}`),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (programReturnLog) {
|
|
61
|
+
logger.debug(`Found CCIP program return log`);
|
|
62
|
+
|
|
63
|
+
// Extract the base64 data after the program ID
|
|
64
|
+
const parts = programReturnLog.split(
|
|
65
|
+
`Program return: ${routerProgramId} `,
|
|
66
|
+
);
|
|
67
|
+
if (parts.length > 1) {
|
|
68
|
+
const base64Data = parts[1].trim();
|
|
69
|
+
logger.trace(`Extracted base64 data: ${base64Data}`);
|
|
70
|
+
|
|
71
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
72
|
+
|
|
73
|
+
// The buffer should contain the messageId (32 bytes)
|
|
74
|
+
const messageIdHex = "0x" + buffer.toString("hex");
|
|
75
|
+
logger.info(`Successfully extracted messageId: ${messageIdHex}`);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
messageId: messageIdHex,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
logger.warn(
|
|
84
|
+
`Could not find CCIP Router program return log in transaction logs`,
|
|
85
|
+
);
|
|
86
|
+
return { messageId: undefined };
|
|
87
|
+
} catch (error) {
|
|
88
|
+
enhanceError(new Error(`Failed to parse message ID from transaction`), {
|
|
89
|
+
operation: "parseCCIPMessageSentEvent",
|
|
90
|
+
txSignature,
|
|
91
|
+
error: error instanceof Error ? error.message : String(error),
|
|
92
|
+
});
|
|
93
|
+
return { messageId: undefined };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import {
|
|
4
|
+
PublicKey,
|
|
5
|
+
TransactionMessage,
|
|
6
|
+
VersionedTransaction,
|
|
7
|
+
} from "@solana/web3.js";
|
|
8
|
+
import { NATIVE_MINT } from "@solana/spl-token";
|
|
9
|
+
import { AccountMeta } from "@solana/web3.js";
|
|
10
|
+
import { createErrorEnhancer } from "../../utils/errors";
|
|
11
|
+
import { CCIPFeeRequest, CCIPContext, CCIPCoreConfig } from "../models";
|
|
12
|
+
import * as types from "../../bindings/types";
|
|
13
|
+
import { GetFeeResult } from "../../bindings/types/GetFeeResult";
|
|
14
|
+
import {
|
|
15
|
+
findFqConfigPDA,
|
|
16
|
+
findFqDestChainPDA,
|
|
17
|
+
findFqBillingTokenConfigPDA,
|
|
18
|
+
findFqPerChainPerTokenConfigPDA,
|
|
19
|
+
findConfigPDA,
|
|
20
|
+
findDestChainStatePDA,
|
|
21
|
+
} from "../../utils/pdas";
|
|
22
|
+
import {
|
|
23
|
+
getFee,
|
|
24
|
+
GetFeeAccounts,
|
|
25
|
+
GetFeeArgs,
|
|
26
|
+
} from "../../bindings/instructions/getFee";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculates the fee for a CCIP message
|
|
30
|
+
*
|
|
31
|
+
* @param context SDK context with provider, config and logger
|
|
32
|
+
* @param request Fee request parameters
|
|
33
|
+
* @returns Fee result
|
|
34
|
+
*/
|
|
35
|
+
export async function calculateFee(
|
|
36
|
+
context: CCIPContext,
|
|
37
|
+
request: CCIPFeeRequest,
|
|
38
|
+
): Promise<types.GetFeeResult> {
|
|
39
|
+
const logger = context.logger;
|
|
40
|
+
const config = context.config;
|
|
41
|
+
const connection = context.provider.connection;
|
|
42
|
+
const signerPublicKey = context.provider.getAddress();
|
|
43
|
+
|
|
44
|
+
if (!logger) {
|
|
45
|
+
throw new Error("Logger is required for calculateFee");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const enhanceError = createErrorEnhancer(logger);
|
|
49
|
+
const selectorBigInt = BigInt(request.destChainSelector.toString());
|
|
50
|
+
|
|
51
|
+
logger.info(
|
|
52
|
+
`Calculating fee for destination chain ${request.destChainSelector.toString()}`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const feeTokenMint = request.message.feeToken.equals(PublicKey.default)
|
|
56
|
+
? NATIVE_MINT
|
|
57
|
+
: request.message.feeToken;
|
|
58
|
+
|
|
59
|
+
logger.debug(
|
|
60
|
+
`Using fee token: ${feeTokenMint.toString()} (${
|
|
61
|
+
request.message.feeToken.equals(PublicKey.default)
|
|
62
|
+
? "Native SOL"
|
|
63
|
+
: "SPL Token"
|
|
64
|
+
})`,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Build the accounts needed for the getFee instruction
|
|
68
|
+
logger.debug(`Building accounts for getFee instruction`);
|
|
69
|
+
const accounts = await buildGetFeeAccounts(
|
|
70
|
+
config,
|
|
71
|
+
selectorBigInt,
|
|
72
|
+
feeTokenMint,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
logger.trace("Fee accounts:", {
|
|
76
|
+
config: accounts.config.toString(),
|
|
77
|
+
destChainState: accounts.destChainState.toString(),
|
|
78
|
+
feeQuoter: accounts.feeQuoter.toString(),
|
|
79
|
+
feeQuoterConfig: accounts.feeQuoterConfig.toString(),
|
|
80
|
+
feeQuoterDestChain: accounts.feeQuoterDestChain.toString(),
|
|
81
|
+
feeQuoterBillingTokenConfig:
|
|
82
|
+
accounts.feeQuoterBillingTokenConfig.toString(),
|
|
83
|
+
feeQuoterLinkTokenConfig: accounts.feeQuoterLinkTokenConfig.toString(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Create the getFee instruction arguments
|
|
87
|
+
logger.debug(`Creating getFee instruction arguments`);
|
|
88
|
+
const args: GetFeeArgs = {
|
|
89
|
+
destChainSelector: request.destChainSelector,
|
|
90
|
+
message: {
|
|
91
|
+
receiver: request.message.receiver,
|
|
92
|
+
data: request.message.data,
|
|
93
|
+
tokenAmounts: request.message.tokenAmounts,
|
|
94
|
+
feeToken: request.message.feeToken,
|
|
95
|
+
extraArgs: request.message.extraArgs,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Create instruction
|
|
100
|
+
logger.debug(`Creating getFee instruction`);
|
|
101
|
+
const instruction = getFee(args, accounts, config.ccipRouterProgramId);
|
|
102
|
+
|
|
103
|
+
// Build and add token-specific remaining accounts for each token in tokenAmounts
|
|
104
|
+
const remainingAccounts: AccountMeta[] = [];
|
|
105
|
+
|
|
106
|
+
// Process each token in tokenAmounts
|
|
107
|
+
logger.debug(
|
|
108
|
+
`Processing ${request.message.tokenAmounts.length} token amounts for remaining accounts`,
|
|
109
|
+
);
|
|
110
|
+
for (const tokenAmount of request.message.tokenAmounts) {
|
|
111
|
+
try {
|
|
112
|
+
logger.trace(
|
|
113
|
+
`Processing token: ${tokenAmount.token.toString()}, amount: ${tokenAmount.amount.toString()}`,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Find the token billing config PDA
|
|
117
|
+
const [tokenBillingConfig] = findFqBillingTokenConfigPDA(
|
|
118
|
+
tokenAmount.token,
|
|
119
|
+
config.feeQuoterProgramId,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Find the per chain per token config PDA
|
|
123
|
+
const [perChainPerTokenConfig] = findFqPerChainPerTokenConfigPDA(
|
|
124
|
+
selectorBigInt,
|
|
125
|
+
tokenAmount.token,
|
|
126
|
+
config.feeQuoterProgramId,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
logger.trace(`Found token configs:`, {
|
|
130
|
+
tokenBillingConfig: tokenBillingConfig.toString(),
|
|
131
|
+
perChainPerTokenConfig: perChainPerTokenConfig.toString(),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Add these accounts to the remaining accounts
|
|
135
|
+
remainingAccounts.push(
|
|
136
|
+
{ pubkey: tokenBillingConfig, isWritable: false, isSigner: false },
|
|
137
|
+
{ pubkey: perChainPerTokenConfig, isWritable: false, isSigner: false },
|
|
138
|
+
);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// Log the error with context but continue with other tokens
|
|
141
|
+
enhanceError(error, {
|
|
142
|
+
operation: "getFee:processToken",
|
|
143
|
+
token: tokenAmount.token.toString(),
|
|
144
|
+
amount: tokenAmount.amount.toString(),
|
|
145
|
+
destChainSelector: selectorBigInt.toString(),
|
|
146
|
+
});
|
|
147
|
+
// Continue with other tokens if one fails
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Add remaining accounts to the instruction
|
|
152
|
+
if (remainingAccounts.length > 0) {
|
|
153
|
+
logger.debug(
|
|
154
|
+
`Adding ${remainingAccounts.length} remaining accounts to the instruction`,
|
|
155
|
+
);
|
|
156
|
+
instruction.keys.push(...remainingAccounts);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Log complete instruction accounts in TRACE mode
|
|
160
|
+
logger.trace(
|
|
161
|
+
"Complete instruction accounts:",
|
|
162
|
+
instruction.keys.map((key, index) => ({
|
|
163
|
+
index,
|
|
164
|
+
pubkey: key.pubkey.toString(),
|
|
165
|
+
isSigner: key.isSigner,
|
|
166
|
+
isWritable: key.isWritable,
|
|
167
|
+
})),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Get recent blockhash
|
|
171
|
+
logger.debug(`Getting recent blockhash for transaction`);
|
|
172
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
173
|
+
|
|
174
|
+
// Create transaction
|
|
175
|
+
logger.debug(`Creating versioned transaction for simulation`);
|
|
176
|
+
const messageV0 = new TransactionMessage({
|
|
177
|
+
payerKey: signerPublicKey,
|
|
178
|
+
recentBlockhash: blockhash,
|
|
179
|
+
instructions: [instruction],
|
|
180
|
+
}).compileToV0Message();
|
|
181
|
+
|
|
182
|
+
const tx = new VersionedTransaction(messageV0);
|
|
183
|
+
await context.provider.signTransaction(tx);
|
|
184
|
+
|
|
185
|
+
// Simulate transaction to get the return data
|
|
186
|
+
logger.debug(`Simulating transaction to get fee result`);
|
|
187
|
+
const simulation = await connection.simulateTransaction(tx, {
|
|
188
|
+
commitment: "confirmed",
|
|
189
|
+
sigVerify: false,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Parse the return data
|
|
193
|
+
if (simulation.value.logs) {
|
|
194
|
+
logger.trace(`Simulation logs:`, simulation.value.logs);
|
|
195
|
+
|
|
196
|
+
const ccipReturnLog = simulation.value.logs.find((log) =>
|
|
197
|
+
log.includes(`Program return: ${config.ccipRouterProgramId.toString()}`),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (ccipReturnLog) {
|
|
201
|
+
logger.debug(`Found CCIP program return log`);
|
|
202
|
+
const parts = ccipReturnLog.split(
|
|
203
|
+
`Program return: ${config.ccipRouterProgramId.toString()} `,
|
|
204
|
+
);
|
|
205
|
+
if (parts.length > 1) {
|
|
206
|
+
const base64Data = parts[1].trim();
|
|
207
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
208
|
+
|
|
209
|
+
// Use the proper bindings to decode the result
|
|
210
|
+
logger.debug(`Decoding fee result data`);
|
|
211
|
+
const feeResultData = GetFeeResult.layout().decode(buffer);
|
|
212
|
+
const result = GetFeeResult.fromDecoded(feeResultData);
|
|
213
|
+
|
|
214
|
+
logger.info(
|
|
215
|
+
`Fee calculation complete: ${result.amount.toString()} tokens`,
|
|
216
|
+
);
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
logger.error(`Could not find CCIP program return log in simulation logs`);
|
|
222
|
+
} else {
|
|
223
|
+
logger.error(`Simulation did not return any logs`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
throw enhanceError(
|
|
227
|
+
new Error("Could not parse fee from transaction return data"),
|
|
228
|
+
{
|
|
229
|
+
operation: "getFee",
|
|
230
|
+
destChainSelector: request.destChainSelector.toString(),
|
|
231
|
+
feeToken: request.message.feeToken.toString(),
|
|
232
|
+
simulationStatus: simulation?.value?.err || "No specific error",
|
|
233
|
+
hasLogs: !!simulation?.value?.logs,
|
|
234
|
+
logCount: simulation?.value?.logs?.length || 0,
|
|
235
|
+
},
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Build accounts required for the getFee instruction
|
|
241
|
+
* @param config SDK configuration
|
|
242
|
+
* @param selectorBigInt Chain selector as BigInt
|
|
243
|
+
* @param feeTokenMint Fee token mint address
|
|
244
|
+
* @returns GetFeeAccounts object with all required accounts
|
|
245
|
+
*/
|
|
246
|
+
async function buildGetFeeAccounts(
|
|
247
|
+
config: CCIPCoreConfig,
|
|
248
|
+
selectorBigInt: bigint,
|
|
249
|
+
feeTokenMint: PublicKey,
|
|
250
|
+
): Promise<GetFeeAccounts> {
|
|
251
|
+
const [configPDA] = findConfigPDA(config.ccipRouterProgramId);
|
|
252
|
+
const [destChainState] = findDestChainStatePDA(
|
|
253
|
+
selectorBigInt,
|
|
254
|
+
config.ccipRouterProgramId,
|
|
255
|
+
);
|
|
256
|
+
const [feeQuoterConfig] = findFqConfigPDA(config.feeQuoterProgramId);
|
|
257
|
+
const [fqDestChain] = findFqDestChainPDA(
|
|
258
|
+
selectorBigInt,
|
|
259
|
+
config.feeQuoterProgramId,
|
|
260
|
+
);
|
|
261
|
+
const [fqBillingTokenConfig] = findFqBillingTokenConfigPDA(
|
|
262
|
+
feeTokenMint,
|
|
263
|
+
config.feeQuoterProgramId,
|
|
264
|
+
);
|
|
265
|
+
const [fqLinkBillingTokenConfig] = findFqBillingTokenConfigPDA(
|
|
266
|
+
config.linkTokenMint,
|
|
267
|
+
config.feeQuoterProgramId,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
config: configPDA,
|
|
272
|
+
destChainState: destChainState,
|
|
273
|
+
feeQuoter: config.feeQuoterProgramId,
|
|
274
|
+
feeQuoterConfig: feeQuoterConfig,
|
|
275
|
+
feeQuoterDestChain: fqDestChain,
|
|
276
|
+
feeQuoterBillingTokenConfig: fqBillingTokenConfig,
|
|
277
|
+
feeQuoterLinkTokenConfig: fqLinkBillingTokenConfig,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { TransactionInstruction } from "@solana/web3.js";
|
|
4
|
+
import { createLogger, Logger, LogLevel } from "../../utils/logger";
|
|
5
|
+
import {
|
|
6
|
+
CCIPContext,
|
|
7
|
+
CCIPSendRequest,
|
|
8
|
+
CCIPSendOptions,
|
|
9
|
+
CCIPFeeRequest,
|
|
10
|
+
CCIPSendResult,
|
|
11
|
+
ExtraArgsOptions,
|
|
12
|
+
CCIPCoreConfig,
|
|
13
|
+
CCIPProvider,
|
|
14
|
+
} from "../models";
|
|
15
|
+
import * as types from "../../bindings/types";
|
|
16
|
+
|
|
17
|
+
// Import functionality from separate modules
|
|
18
|
+
import { calculateFee } from "./fee";
|
|
19
|
+
import { parseCCIPMessageSentEvent } from "./events";
|
|
20
|
+
import { createExtraArgs } from "./utils";
|
|
21
|
+
import { sendCCIPMessage } from "./send";
|
|
22
|
+
import { CCIPAccountReader } from "./accounts";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Main client class for interacting with CCIP on Solana
|
|
26
|
+
*
|
|
27
|
+
* Features:
|
|
28
|
+
* - Message sending with optional skipPreflight for low compute limit transactions
|
|
29
|
+
* - Fee calculation for CCIP messages
|
|
30
|
+
* - Message ID parsing from transaction results
|
|
31
|
+
* - ExtraArgs generation for cross-chain messages
|
|
32
|
+
*/
|
|
33
|
+
export class CCIPClient {
|
|
34
|
+
private readonly context: CCIPContext;
|
|
35
|
+
private readonly accountReader: CCIPAccountReader;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new CCIP client with context
|
|
39
|
+
* @param context CCIPContext containing provider, config, and logger
|
|
40
|
+
*/
|
|
41
|
+
constructor(context: CCIPContext) {
|
|
42
|
+
// Initialize context
|
|
43
|
+
this.context = {
|
|
44
|
+
provider: context.provider,
|
|
45
|
+
config: context.config,
|
|
46
|
+
logger:
|
|
47
|
+
context.logger ?? createLogger("client", { level: LogLevel.INFO }),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Initialize account reader with the same context
|
|
51
|
+
this.accountReader = new CCIPAccountReader(this.context);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get the provider for this client
|
|
56
|
+
*/
|
|
57
|
+
get provider(): CCIPProvider {
|
|
58
|
+
return this.context.provider;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the configuration for this client
|
|
63
|
+
*/
|
|
64
|
+
get config(): CCIPCoreConfig {
|
|
65
|
+
return this.context.config;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the logger for this client
|
|
70
|
+
*/
|
|
71
|
+
get logger(): Logger {
|
|
72
|
+
return this.context.logger;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the account reader for this client
|
|
77
|
+
*/
|
|
78
|
+
getAccountReader(): CCIPAccountReader {
|
|
79
|
+
return this.accountReader;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Calculates the fee for a CCIP message
|
|
84
|
+
* @param request Fee request
|
|
85
|
+
* @returns Fee result
|
|
86
|
+
*/
|
|
87
|
+
async getFee(request: CCIPFeeRequest): Promise<types.GetFeeResult> {
|
|
88
|
+
return calculateFee(this.context, request);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Sends a CCIP message
|
|
93
|
+
* @param request Send request
|
|
94
|
+
* @param computeBudgetInstruction Optional compute budget instruction
|
|
95
|
+
* @param sendOptions Optional send options (skipPreflight, etc.)
|
|
96
|
+
* @returns Transaction signature
|
|
97
|
+
*/
|
|
98
|
+
async send(
|
|
99
|
+
request: CCIPSendRequest,
|
|
100
|
+
computeBudgetInstruction?: TransactionInstruction,
|
|
101
|
+
sendOptions?: CCIPSendOptions,
|
|
102
|
+
): Promise<string> {
|
|
103
|
+
return sendCCIPMessage(
|
|
104
|
+
this.context,
|
|
105
|
+
request,
|
|
106
|
+
this.accountReader,
|
|
107
|
+
computeBudgetInstruction,
|
|
108
|
+
sendOptions,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sends a CCIP message and returns the message ID
|
|
114
|
+
* @param request Send request
|
|
115
|
+
* @param computeBudgetInstruction Optional compute budget instruction
|
|
116
|
+
* @param sendOptions Optional send options (skipPreflight, etc.)
|
|
117
|
+
* @returns Send result with transaction signature and message ID
|
|
118
|
+
*/
|
|
119
|
+
async sendWithMessageId(
|
|
120
|
+
request: CCIPSendRequest,
|
|
121
|
+
computeBudgetInstruction?: TransactionInstruction,
|
|
122
|
+
sendOptions?: CCIPSendOptions,
|
|
123
|
+
): Promise<CCIPSendResult> {
|
|
124
|
+
const txSignature = await this.send(
|
|
125
|
+
request,
|
|
126
|
+
computeBudgetInstruction,
|
|
127
|
+
sendOptions,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Parse the CCIPMessageSent event to get the messageId
|
|
131
|
+
const eventData = await parseCCIPMessageSentEvent(
|
|
132
|
+
this.context,
|
|
133
|
+
txSignature,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
txSignature,
|
|
138
|
+
messageId: eventData.messageId,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Creates the extra arguments for a CCIP message
|
|
144
|
+
* @param options Options for creating extra arguments
|
|
145
|
+
* @returns Extra arguments buffer
|
|
146
|
+
*/
|
|
147
|
+
createExtraArgs(options?: ExtraArgsOptions): Buffer {
|
|
148
|
+
return createExtraArgs(options, this.context.logger);
|
|
149
|
+
}
|
|
150
|
+
}
|