@chainlink/ccip-sdk 0.90.2 → 0.91.0
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/README.md +35 -26
- package/dist/aptos/exec.d.ts +4 -5
- package/dist/aptos/exec.d.ts.map +1 -1
- package/dist/aptos/exec.js +5 -14
- package/dist/aptos/exec.js.map +1 -1
- package/dist/aptos/hasher.d.ts +18 -0
- package/dist/aptos/hasher.d.ts.map +1 -1
- package/dist/aptos/hasher.js +18 -0
- package/dist/aptos/hasher.js.map +1 -1
- package/dist/aptos/index.d.ts +127 -28
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +199 -70
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.d.ts +18 -0
- package/dist/aptos/logs.d.ts.map +1 -1
- package/dist/aptos/logs.js +21 -3
- package/dist/aptos/logs.js.map +1 -1
- package/dist/aptos/send.d.ts +22 -5
- package/dist/aptos/send.d.ts.map +1 -1
- package/dist/aptos/send.js +23 -15
- package/dist/aptos/send.js.map +1 -1
- package/dist/aptos/token.d.ts +6 -0
- package/dist/aptos/token.d.ts.map +1 -1
- package/dist/aptos/token.js +6 -0
- package/dist/aptos/token.js.map +1 -1
- package/dist/aptos/types.d.ts +16 -1
- package/dist/aptos/types.d.ts.map +1 -1
- package/dist/aptos/types.js +13 -0
- package/dist/aptos/types.js.map +1 -1
- package/dist/aptos/utils.d.ts +1 -1
- package/dist/aptos/utils.js +1 -1
- package/dist/chain.d.ts +185 -99
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +38 -15
- package/dist/chain.js.map +1 -1
- package/dist/commits.d.ts +4 -10
- package/dist/commits.d.ts.map +1 -1
- package/dist/commits.js +2 -1
- package/dist/commits.js.map +1 -1
- package/dist/evm/const.d.ts +5 -0
- package/dist/evm/const.d.ts.map +1 -1
- package/dist/evm/const.js +5 -0
- package/dist/evm/const.js.map +1 -1
- package/dist/evm/errors.d.ts +5 -0
- package/dist/evm/errors.d.ts.map +1 -1
- package/dist/evm/errors.js +6 -1
- package/dist/evm/errors.js.map +1 -1
- package/dist/evm/hasher.d.ts +16 -2
- package/dist/evm/hasher.d.ts.map +1 -1
- package/dist/evm/hasher.js +17 -3
- package/dist/evm/hasher.js.map +1 -1
- package/dist/evm/index.d.ts +176 -31
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +312 -154
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/logs.d.ts +20 -0
- package/dist/evm/logs.d.ts.map +1 -0
- package/dist/evm/logs.js +194 -0
- package/dist/evm/logs.js.map +1 -0
- package/dist/evm/messages.d.ts +11 -2
- package/dist/evm/messages.d.ts.map +1 -1
- package/dist/evm/messages.js +4 -2
- package/dist/evm/messages.js.map +1 -1
- package/dist/evm/offchain.d.ts +7 -2
- package/dist/evm/offchain.d.ts.map +1 -1
- package/dist/evm/offchain.js +12 -7
- package/dist/evm/offchain.js.map +1 -1
- package/dist/execution.d.ts +19 -62
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +28 -31
- package/dist/execution.js.map +1 -1
- package/dist/extra-args.d.ts +35 -5
- package/dist/extra-args.d.ts.map +1 -1
- package/dist/extra-args.js +10 -5
- package/dist/extra-args.js.map +1 -1
- package/dist/gas.d.ts +6 -8
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +7 -9
- package/dist/gas.js.map +1 -1
- package/dist/hasher/common.d.ts +3 -2
- package/dist/hasher/common.d.ts.map +1 -1
- package/dist/hasher/common.js +2 -2
- package/dist/hasher/common.js.map +1 -1
- package/dist/hasher/hasher.d.ts +8 -2
- package/dist/hasher/hasher.d.ts.map +1 -1
- package/dist/hasher/hasher.js +8 -3
- package/dist/hasher/hasher.js.map +1 -1
- package/dist/hasher/merklemulti.d.ts +11 -9
- package/dist/hasher/merklemulti.d.ts.map +1 -1
- package/dist/hasher/merklemulti.js +17 -16
- package/dist/hasher/merklemulti.js.map +1 -1
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/requests.d.ts +39 -25
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +42 -35
- package/dist/requests.js.map +1 -1
- package/dist/selectors.d.ts +1 -1
- package/dist/solana/cleanup.d.ts +14 -10
- package/dist/solana/cleanup.d.ts.map +1 -1
- package/dist/solana/cleanup.js +35 -33
- package/dist/solana/cleanup.js.map +1 -1
- package/dist/solana/exec.d.ts +19 -11
- package/dist/solana/exec.d.ts.map +1 -1
- package/dist/solana/exec.js +86 -163
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/hasher.d.ts +7 -2
- package/dist/solana/hasher.d.ts.map +1 -1
- package/dist/solana/hasher.js +7 -2
- package/dist/solana/hasher.js.map +1 -1
- package/dist/solana/index.d.ts +202 -84
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +367 -252
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/offchain.d.ts +8 -18
- package/dist/solana/offchain.d.ts.map +1 -1
- package/dist/solana/offchain.js +29 -83
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/patchBorsh.d.ts +5 -1
- package/dist/solana/patchBorsh.d.ts.map +1 -1
- package/dist/solana/patchBorsh.js +57 -46
- package/dist/solana/patchBorsh.js.map +1 -1
- package/dist/solana/send.d.ts +28 -10
- package/dist/solana/send.d.ts.map +1 -1
- package/dist/solana/send.js +44 -77
- package/dist/solana/send.js.map +1 -1
- package/dist/solana/types.d.ts +22 -1
- package/dist/solana/types.d.ts.map +1 -1
- package/dist/solana/types.js +12 -1
- package/dist/solana/types.js.map +1 -1
- package/dist/solana/utils.d.ts +58 -4
- package/dist/solana/utils.d.ts.map +1 -1
- package/dist/solana/utils.js +110 -7
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/hasher.d.ts +18 -0
- package/dist/sui/hasher.d.ts.map +1 -1
- package/dist/sui/hasher.js +18 -0
- package/dist/sui/hasher.js.map +1 -1
- package/dist/sui/index.d.ts +99 -12
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +108 -19
- package/dist/sui/index.js.map +1 -1
- package/dist/sui/types.d.ts +6 -0
- package/dist/sui/types.d.ts.map +1 -1
- package/dist/sui/types.js +5 -0
- package/dist/sui/types.js.map +1 -1
- package/dist/supported-chains.d.ts +2 -1
- package/dist/supported-chains.d.ts.map +1 -1
- package/dist/supported-chains.js.map +1 -1
- package/dist/types.d.ts +127 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +67 -46
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +143 -21
- package/dist/utils.js.map +1 -1
- package/package.json +13 -9
- package/src/aptos/exec.ts +7 -18
- package/src/aptos/hasher.ts +18 -0
- package/src/aptos/index.ts +288 -110
- package/src/aptos/logs.ts +21 -3
- package/src/aptos/send.ts +25 -22
- package/src/aptos/token.ts +6 -0
- package/src/aptos/types.ts +26 -2
- package/src/aptos/utils.ts +1 -1
- package/src/chain.ts +243 -108
- package/src/commits.ts +6 -7
- package/src/evm/const.ts +5 -0
- package/src/evm/errors.ts +6 -1
- package/src/evm/hasher.ts +20 -4
- package/src/evm/index.ts +416 -214
- package/src/evm/logs.ts +255 -0
- package/src/evm/messages.ts +11 -5
- package/src/evm/offchain.ts +13 -4
- package/src/execution.ts +40 -32
- package/src/extra-args.ts +38 -6
- package/src/gas.ts +7 -9
- package/src/hasher/common.ts +3 -2
- package/src/hasher/hasher.ts +12 -4
- package/src/hasher/merklemulti.ts +17 -16
- package/src/index.ts +29 -23
- package/src/requests.ts +64 -46
- package/src/selectors.ts +1 -1
- package/src/solana/cleanup.ts +49 -34
- package/src/solana/exec.ts +128 -272
- package/src/solana/hasher.ts +13 -4
- package/src/solana/index.ts +483 -356
- package/src/solana/offchain.ts +32 -102
- package/src/solana/patchBorsh.ts +65 -50
- package/src/solana/send.ts +52 -111
- package/src/solana/types.ts +44 -3
- package/src/solana/utils.ts +143 -19
- package/src/sui/hasher.ts +18 -0
- package/src/sui/index.ts +143 -31
- package/src/sui/types.ts +6 -0
- package/src/supported-chains.ts +2 -1
- package/src/types.ts +130 -18
- package/src/utils.ts +168 -26
- package/tsconfig.json +2 -1
package/dist/solana/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { BorshAccountsCoder, BorshCoder, Program } from '@coral-xyz/anchor';
|
|
3
3
|
import { NATIVE_MINT } from '@solana/spl-token';
|
|
4
|
-
import { Connection,
|
|
4
|
+
import { Connection, PublicKey, SYSVAR_CLOCK_PUBKEY, SystemProgram, } from '@solana/web3.js';
|
|
5
5
|
import bs58 from 'bs58';
|
|
6
|
-
import { concat, dataLength, dataSlice, encodeBase58, encodeBase64,
|
|
7
|
-
import
|
|
8
|
-
import { Chain,
|
|
6
|
+
import { concat, dataLength, dataSlice, encodeBase58, encodeBase64, hexlify, isHexString, toBigInt, } from 'ethers';
|
|
7
|
+
import { memoize } from 'micro-memoize';
|
|
8
|
+
import { Chain, } from "../chain.js";
|
|
9
9
|
import { EVMExtraArgsV2Tag } from "../extra-args.js";
|
|
10
10
|
import SELECTORS from "../selectors.js";
|
|
11
11
|
import { supportedChains } from "../supported-chains.js";
|
|
12
|
-
import { CCIPVersion, ExecutionState, } from "../types.js";
|
|
13
|
-
import { createRateLimitedFetch, decodeAddress, decodeOnRampAddress, getDataBytes, leToBigInt, networkInfo, parseTypeAndVersion, toLeArray, } from "../utils.js";
|
|
12
|
+
import { CCIPVersion, ChainFamily, ExecutionState, } from "../types.js";
|
|
13
|
+
import { createRateLimitedFetch, decodeAddress, decodeOnRampAddress, getDataBytes, leToBigInt, networkInfo, parseTypeAndVersion, toLeArray, util, } from "../utils.js";
|
|
14
14
|
import { cleanUpBuffers } from "./cleanup.js";
|
|
15
|
-
import {
|
|
15
|
+
import { generateUnsignedExecuteReport } from "./exec.js";
|
|
16
16
|
import { getV16SolanaLeafHasher } from "./hasher.js";
|
|
17
17
|
import { IDL as BASE_TOKEN_POOL } from "./idl/1.6.0/BASE_TOKEN_POOL.js";
|
|
18
18
|
import { IDL as BURN_MINT_TOKEN_POOL } from "./idl/1.6.0/BURN_MINT_TOKEN_POOL.js";
|
|
@@ -20,8 +20,11 @@ import { IDL as CCIP_CCTP_TOKEN_POOL } from "./idl/1.6.0/CCIP_CCTP_TOKEN_POOL.js
|
|
|
20
20
|
import { IDL as CCIP_OFFRAMP_IDL } from "./idl/1.6.0/CCIP_OFFRAMP.js";
|
|
21
21
|
import { IDL as CCIP_ROUTER_IDL } from "./idl/1.6.0/CCIP_ROUTER.js";
|
|
22
22
|
import { fetchSolanaOffchainTokenData } from "./offchain.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
23
|
+
import { generateUnsignedCcipSend, getFee } from "./send.js";
|
|
24
|
+
import { isWallet } from "./types.js";
|
|
25
|
+
import { bytesToBuffer, getErrorFromLogs, hexDiscriminator, parseSolanaLogs, simulateAndSendTxs, simulationProvider, } from "./utils.js";
|
|
26
|
+
import { fetchAllMessagesInBatch, fetchCCIPRequestById, fetchCCIPRequestsInTx, } from "../requests.js";
|
|
27
|
+
import { patchBorsh } from "./patchBorsh.js";
|
|
25
28
|
const routerCoder = new BorshCoder(CCIP_ROUTER_IDL);
|
|
26
29
|
const offrampCoder = new BorshCoder(CCIP_OFFRAMP_IDL);
|
|
27
30
|
const tokenPoolCoder = new BorshCoder({
|
|
@@ -40,114 +43,106 @@ const cctpTokenPoolCoder = new BorshCoder({
|
|
|
40
43
|
const unknownTokens = {
|
|
41
44
|
'4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU': 'USDC', // devnet
|
|
42
45
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Solana chain implementation supporting Solana networks.
|
|
48
|
+
*/
|
|
46
49
|
export class SolanaChain extends Chain {
|
|
50
|
+
static {
|
|
51
|
+
patchBorsh();
|
|
52
|
+
supportedChains[ChainFamily.Solana] = SolanaChain;
|
|
53
|
+
}
|
|
47
54
|
static family = ChainFamily.Solana;
|
|
48
55
|
static decimals = 9;
|
|
49
|
-
network;
|
|
50
56
|
connection;
|
|
51
57
|
commitment = 'confirmed';
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new SolanaChain instance.
|
|
60
|
+
* @param connection - Solana connection instance.
|
|
61
|
+
* @param network - Network information for this chain.
|
|
62
|
+
*/
|
|
63
|
+
constructor(connection, network, ctx) {
|
|
64
|
+
super(network, ctx);
|
|
59
65
|
this.connection = connection;
|
|
60
66
|
// Memoize expensive operations
|
|
61
|
-
this.typeAndVersion =
|
|
67
|
+
this.typeAndVersion = memoize(this.typeAndVersion.bind(this), {
|
|
62
68
|
maxArgs: 1,
|
|
63
|
-
|
|
69
|
+
async: true,
|
|
64
70
|
});
|
|
65
|
-
this.getBlockTimestamp =
|
|
66
|
-
|
|
71
|
+
this.getBlockTimestamp = memoize(this.getBlockTimestamp.bind(this), {
|
|
72
|
+
async: true,
|
|
67
73
|
maxSize: 100,
|
|
68
|
-
|
|
74
|
+
forceUpdate: (key) => typeof key[key.length - 1] !== 'number',
|
|
69
75
|
});
|
|
70
|
-
this.getTransaction =
|
|
76
|
+
this.getTransaction = memoize(this.getTransaction.bind(this), {
|
|
71
77
|
maxSize: 100,
|
|
72
78
|
maxArgs: 1,
|
|
73
79
|
});
|
|
74
|
-
this.
|
|
75
|
-
this.
|
|
76
|
-
this.
|
|
77
|
-
this._getSignaturesForAddress = moize.default((programId, before) => this.connection.getSignaturesForAddress(new PublicKey(programId), { limit: 1000, before }, 'confirmed'), {
|
|
80
|
+
this.getTokenForTokenPool = memoize(this.getTokenForTokenPool.bind(this));
|
|
81
|
+
this.getTokenInfo = memoize(this.getTokenInfo.bind(this));
|
|
82
|
+
this.connection.getSignaturesForAddress = memoize(this.connection.getSignaturesForAddress.bind(this.connection), {
|
|
78
83
|
maxSize: 100,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
async: true,
|
|
85
|
+
// if options.before is defined, caches for long, otherwise for short (recent signatures)
|
|
86
|
+
expires: (key) => (key[1] ? 2 ** 31 - 1 : 5e3),
|
|
87
|
+
transformKey: ([address, options, commitment]) => [
|
|
88
|
+
address.toBase58(),
|
|
89
|
+
options?.before,
|
|
90
|
+
options?.until,
|
|
91
|
+
options?.limit,
|
|
92
|
+
commitment,
|
|
93
|
+
],
|
|
85
94
|
});
|
|
86
95
|
// cache account info for 30 seconds
|
|
87
|
-
this.connection.getAccountInfo =
|
|
96
|
+
this.connection.getAccountInfo = memoize(this.connection.getAccountInfo.bind(this.connection), {
|
|
88
97
|
maxSize: 100,
|
|
89
98
|
maxArgs: 2,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
this._getRouterConfig = moize.default(this._getRouterConfig.bind(this), {
|
|
94
|
-
maxArgs: 1,
|
|
95
|
-
});
|
|
96
|
-
this.listFeeTokens = moize.default(this.listFeeTokens.bind(this), {
|
|
97
|
-
maxArgs: 1,
|
|
99
|
+
expires: 30e3,
|
|
100
|
+
transformKey: ([address, commitment]) => [address.toString(), commitment],
|
|
98
101
|
});
|
|
102
|
+
this._getRouterConfig = memoize(this._getRouterConfig.bind(this), { maxArgs: 1 });
|
|
103
|
+
this.getFeeTokens = memoize(this.getFeeTokens.bind(this), { maxArgs: 1 });
|
|
104
|
+
this.getOffRampsForRouter = memoize(this.getOffRampsForRouter.bind(this), { maxArgs: 1 });
|
|
99
105
|
}
|
|
100
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Creates a Solana connection from a URL.
|
|
108
|
+
* @param url - RPC endpoint URL (https://, http://, wss://, or ws://).
|
|
109
|
+
* @param ctx - context containing logger.
|
|
110
|
+
* @returns Solana Connection instance.
|
|
111
|
+
*/
|
|
112
|
+
static _getConnection(url, ctx) {
|
|
113
|
+
const { logger = console } = ctx ?? {};
|
|
101
114
|
if (!url.startsWith('http') && !url.startsWith('ws')) {
|
|
102
115
|
throw new Error(`Invalid Solana RPC URL format (should be https://, http://, wss://, or ws://): ${url}`);
|
|
103
116
|
}
|
|
104
117
|
const config = { commitment: 'confirmed' };
|
|
105
118
|
if (url.includes('.solana.com')) {
|
|
106
|
-
config.fetch = createRateLimitedFetch(
|
|
107
|
-
|
|
108
|
-
maxRetries: 3,
|
|
109
|
-
windowMs: 11e3,
|
|
110
|
-
}); // public nodes
|
|
111
|
-
console.warn('Using rate-limited fetch for public solana nodes, commands may be slow');
|
|
119
|
+
config.fetch = createRateLimitedFetch(undefined, ctx); // public nodes
|
|
120
|
+
logger.warn('Using rate-limited fetch for public solana nodes, commands may be slow');
|
|
112
121
|
}
|
|
113
122
|
return new Connection(url, config);
|
|
114
123
|
}
|
|
115
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Creates a SolanaChain instance from an existing connection.
|
|
126
|
+
* @param connection - Solana Connection instance.
|
|
127
|
+
* @param ctx - context containing logger.
|
|
128
|
+
* @returns A new SolanaChain instance.
|
|
129
|
+
*/
|
|
130
|
+
static async fromConnection(connection, ctx) {
|
|
116
131
|
// Get genesis hash to use as chainId
|
|
117
|
-
return new SolanaChain(connection, networkInfo(await connection.getGenesisHash()));
|
|
118
|
-
}
|
|
119
|
-
static async fromUrl(url) {
|
|
120
|
-
const connection = this._getConnection(url);
|
|
121
|
-
return this.fromConnection(connection);
|
|
122
|
-
}
|
|
123
|
-
async destroy() {
|
|
124
|
-
// Solana Connection doesn't have an explicit destroy method
|
|
125
|
-
// The memoized functions will be garbage collected when the instance is destroyed
|
|
126
|
-
}
|
|
127
|
-
static getWallet(_opts) {
|
|
128
|
-
throw new Error('Wallet not implemented');
|
|
132
|
+
return new SolanaChain(connection, networkInfo(await connection.getGenesisHash()), ctx);
|
|
129
133
|
}
|
|
130
134
|
/**
|
|
131
|
-
*
|
|
132
|
-
* @param
|
|
133
|
-
* @param
|
|
134
|
-
*
|
|
135
|
-
* @returns Wallet, after caching in instance
|
|
135
|
+
* Creates a SolanaChain instance from an RPC URL.
|
|
136
|
+
* @param url - RPC endpoint URL.
|
|
137
|
+
* @param ctx - context containing logger.
|
|
138
|
+
* @returns A new SolanaChain instance.
|
|
136
139
|
*/
|
|
137
|
-
async
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return new Wallet(Keypair.fromSecretKey(opts.wallet.startsWith('0x') ? getBytes(opts.wallet) : bs58.decode(opts.wallet)));
|
|
141
|
-
}
|
|
142
|
-
catch (_) {
|
|
143
|
-
// pass
|
|
144
|
-
}
|
|
145
|
-
return this.constructor.getWallet(opts);
|
|
146
|
-
}
|
|
147
|
-
async getWalletAddress(opts) {
|
|
148
|
-
return (await this.getWallet(opts)).publicKey.toBase58();
|
|
140
|
+
static async fromUrl(url, ctx) {
|
|
141
|
+
const connection = this._getConnection(url, ctx);
|
|
142
|
+
return this.fromConnection(connection, ctx);
|
|
149
143
|
}
|
|
150
144
|
// cached
|
|
145
|
+
/** {@inheritDoc Chain.getBlockTimestamp} */
|
|
151
146
|
async getBlockTimestamp(block) {
|
|
152
147
|
if (block === 'finalized') {
|
|
153
148
|
const slot = await this.connection.getSlot('finalized');
|
|
@@ -163,7 +158,7 @@ export class SolanaChain extends Chain {
|
|
|
163
158
|
}
|
|
164
159
|
return blockTime;
|
|
165
160
|
}
|
|
166
|
-
|
|
161
|
+
/** {@inheritDoc Chain.getTransaction} */
|
|
167
162
|
async getTransaction(hash) {
|
|
168
163
|
const tx = await this.connection.getTransaction(hash, {
|
|
169
164
|
commitment: 'confirmed',
|
|
@@ -173,7 +168,7 @@ export class SolanaChain extends Chain {
|
|
|
173
168
|
throw new Error(`Transaction not found: ${hash}`);
|
|
174
169
|
if (tx.blockTime) {
|
|
175
170
|
;
|
|
176
|
-
this.getBlockTimestamp.set([tx.slot], Promise.resolve(tx.blockTime));
|
|
171
|
+
this.getBlockTimestamp.cache.set([tx.slot], Promise.resolve(tx.blockTime));
|
|
177
172
|
}
|
|
178
173
|
else {
|
|
179
174
|
tx.blockTime = await this.getBlockTimestamp(tx.slot);
|
|
@@ -187,7 +182,6 @@ export class SolanaChain extends Chain {
|
|
|
187
182
|
}))
|
|
188
183
|
: [];
|
|
189
184
|
const chainTx = {
|
|
190
|
-
chain: this,
|
|
191
185
|
hash,
|
|
192
186
|
logs: [],
|
|
193
187
|
blockNumber: tx.slot,
|
|
@@ -200,48 +194,54 @@ export class SolanaChain extends Chain {
|
|
|
200
194
|
chainTx.logs = logs_.map((l) => Object.assign(l, { tx: chainTx }));
|
|
201
195
|
return chainTx;
|
|
202
196
|
}
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
/**
|
|
198
|
+
* Internal method to get transactions for an address with pagination.
|
|
199
|
+
* @param opts - Log filter options.
|
|
200
|
+
* @returns Async generator of Solana transactions.
|
|
201
|
+
*/
|
|
202
|
+
async *getTransactionsForAddress(opts) {
|
|
205
203
|
if (!opts.address)
|
|
206
204
|
throw new Error('Program address is required for Solana log filtering');
|
|
207
205
|
let allSignatures;
|
|
206
|
+
const limit = Math.min(opts?.page || 1000, 1000);
|
|
208
207
|
if (opts.startBlock || opts.startTime) {
|
|
209
208
|
// forward collect all matching sigs in array
|
|
210
|
-
|
|
211
|
-
let batch
|
|
212
|
-
|
|
213
|
-
batch = await this.
|
|
209
|
+
allSignatures = [];
|
|
210
|
+
let batch;
|
|
211
|
+
do {
|
|
212
|
+
batch = await this.connection.getSignaturesForAddress(new PublicKey(opts.address), { limit, before: allSignatures[allSignatures.length - 1]?.signature }, 'confirmed');
|
|
214
213
|
while (batch.length > 0 &&
|
|
215
214
|
(batch[batch.length - 1].slot < (opts.startBlock || 0) ||
|
|
216
215
|
(batch[batch.length - 1].blockTime || -1) < (opts.startTime || 0))) {
|
|
217
|
-
batch.
|
|
218
|
-
popped = true;
|
|
216
|
+
batch.length--; // truncate tail of txs which are older than requested start
|
|
219
217
|
}
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
218
|
+
allSignatures.push(...batch); // concat in descending order
|
|
219
|
+
} while (batch.length >= limit);
|
|
220
|
+
allSignatures.reverse();
|
|
223
221
|
while (opts.endBlock &&
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
allSignatures.length > 0 &&
|
|
223
|
+
allSignatures[allSignatures.length - 1].slot > opts.endBlock) {
|
|
224
|
+
allSignatures.length--; // truncate head (after reverse) of txs newer than requested end
|
|
227
225
|
}
|
|
228
|
-
allSignatures = allSigs;
|
|
229
226
|
}
|
|
230
227
|
else {
|
|
231
228
|
allSignatures = async function* () {
|
|
232
229
|
let batch;
|
|
233
|
-
|
|
234
|
-
batch = await this.
|
|
235
|
-
|
|
236
|
-
:
|
|
237
|
-
?
|
|
238
|
-
:
|
|
230
|
+
do {
|
|
231
|
+
batch = await this.connection.getSignaturesForAddress(new PublicKey(opts.address), {
|
|
232
|
+
limit,
|
|
233
|
+
before: batch?.length
|
|
234
|
+
? batch[batch.length - 1].signature
|
|
235
|
+
: opts.endBefore
|
|
236
|
+
? opts.endBefore
|
|
237
|
+
: undefined,
|
|
238
|
+
}, 'confirmed');
|
|
239
239
|
for (const sig of batch) {
|
|
240
240
|
if (opts.endBlock && sig.slot > opts.endBlock)
|
|
241
241
|
continue;
|
|
242
242
|
yield sig;
|
|
243
243
|
}
|
|
244
|
-
}
|
|
244
|
+
} while (batch.length >= limit);
|
|
245
245
|
}.call(this); // generate backwards until depleting getSignaturesForAddress
|
|
246
246
|
}
|
|
247
247
|
// Process signatures
|
|
@@ -263,17 +263,16 @@ export class SolanaChain extends Chain {
|
|
|
263
263
|
* * Fetches signatures in reverse chronological order (newest first)
|
|
264
264
|
* * Returns logs in reverse chronological order (newest first)
|
|
265
265
|
*
|
|
266
|
-
* @param opts - Log filter options
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* @
|
|
276
|
-
* @returns AsyncIterableIterator of parsed Log_ objects
|
|
266
|
+
* @param opts - Log filter options containing:
|
|
267
|
+
* - `startBlock`: Starting slot number (inclusive)
|
|
268
|
+
* - `startTime`: Starting Unix timestamp (inclusive)
|
|
269
|
+
* - `endBlock`: Ending slot number (inclusive)
|
|
270
|
+
* - `address`: Program address to filter logs by (required for Solana)
|
|
271
|
+
* - `topics`: Array of topics to filter logs by (optional); either 0x-8B discriminants or event names
|
|
272
|
+
* - `programs`: Special option to allow querying by address of interest, but yielding matching
|
|
273
|
+
* logs from specific (string address) program or any (true)
|
|
274
|
+
* - `commit`: Special param for fetching ExecutionReceipts, to narrow down the search
|
|
275
|
+
* @returns AsyncIterableIterator of parsed Log_ objects.
|
|
277
276
|
*/
|
|
278
277
|
async *getLogs(opts) {
|
|
279
278
|
let programs;
|
|
@@ -298,20 +297,43 @@ export class SolanaChain extends Chain {
|
|
|
298
297
|
opts.topics.push(...opts.topics.filter((t) => !isHexString(t, 8)).map((t) => hexDiscriminator(t)));
|
|
299
298
|
}
|
|
300
299
|
// Process signatures and yield logs
|
|
301
|
-
for await (const tx of this.
|
|
300
|
+
for await (const tx of this.getTransactionsForAddress(opts)) {
|
|
302
301
|
for (const log of tx.logs) {
|
|
303
302
|
// Filter and yield logs from the specified program, and which match event discriminant or log prefix
|
|
304
303
|
if ((programs !== true && !programs.includes(log.address)) ||
|
|
305
304
|
(opts.topics?.length &&
|
|
306
305
|
!opts.topics.some((t) => t === log.topics[0] || (typeof log.data === 'string' && log.data.startsWith(t)))))
|
|
307
306
|
continue;
|
|
308
|
-
yield
|
|
307
|
+
yield log;
|
|
309
308
|
}
|
|
310
309
|
}
|
|
311
310
|
}
|
|
311
|
+
/** {@inheritDoc Chain.fetchRequestsInTx} */
|
|
312
|
+
async fetchRequestsInTx(tx) {
|
|
313
|
+
return fetchCCIPRequestsInTx(this, typeof tx === 'string' ? await this.getTransaction(tx) : tx);
|
|
314
|
+
}
|
|
315
|
+
/** {@inheritDoc Chain.fetchRequestById} */
|
|
316
|
+
fetchRequestById(messageId, onRamp, opts) {
|
|
317
|
+
if (!onRamp)
|
|
318
|
+
throw new Error('onRamp is required');
|
|
319
|
+
return fetchCCIPRequestById(this, messageId, { address: onRamp, ...opts });
|
|
320
|
+
}
|
|
321
|
+
/** {@inheritDoc Chain.fetchAllMessagesInBatch} */
|
|
322
|
+
async fetchAllMessagesInBatch(request, commit, opts) {
|
|
323
|
+
const [destChainStatePda] = PublicKey.findProgramAddressSync([Buffer.from('dest_chain_state'), toLeArray(request.lane.destChainSelector, 8)], new PublicKey(request.log.address));
|
|
324
|
+
// fetchAllMessagesInBatch pass opts back to getLogs; use it to narrow getLogs filter only to
|
|
325
|
+
// txs touching destChainStatePda
|
|
326
|
+
const opts_ = {
|
|
327
|
+
...opts,
|
|
328
|
+
programs: [request.log.address],
|
|
329
|
+
address: destChainStatePda.toBase58(),
|
|
330
|
+
};
|
|
331
|
+
return fetchAllMessagesInBatch(this, request, commit, opts_);
|
|
332
|
+
}
|
|
333
|
+
/** {@inheritDoc Chain.typeAndVersion} */
|
|
312
334
|
async typeAndVersion(address) {
|
|
313
335
|
const program = new Program(CCIP_OFFRAMP_IDL, // `typeVersion` schema should be the same
|
|
314
|
-
new PublicKey(address), simulationProvider(this
|
|
336
|
+
new PublicKey(address), simulationProvider(this));
|
|
315
337
|
// Create the typeVersion instruction
|
|
316
338
|
const returnDataString = (await program.methods
|
|
317
339
|
.typeVersion()
|
|
@@ -322,9 +344,11 @@ export class SolanaChain extends Chain {
|
|
|
322
344
|
res[1] = CCIPVersion.V1_6;
|
|
323
345
|
return res;
|
|
324
346
|
}
|
|
347
|
+
/** {@inheritDoc Chain.getRouterForOnRamp} */
|
|
325
348
|
getRouterForOnRamp(onRamp, _destChainSelector) {
|
|
326
349
|
return Promise.resolve(onRamp); // Solana's router is also the onRamp
|
|
327
350
|
}
|
|
351
|
+
/** {@inheritDoc Chain.getRouterForOffRamp} */
|
|
328
352
|
async getRouterForOffRamp(offRamp, _sourceChainSelector) {
|
|
329
353
|
const offRamp_ = new PublicKey(offRamp);
|
|
330
354
|
const program = new Program(CCIP_OFFRAMP_IDL, offRamp_, {
|
|
@@ -338,9 +362,11 @@ export class SolanaChain extends Chain {
|
|
|
338
362
|
const { router } = program.coder.accounts.decode('referenceAddresses', referenceAddressesPda.data);
|
|
339
363
|
return router.toBase58();
|
|
340
364
|
}
|
|
365
|
+
/** {@inheritDoc Chain.getNativeTokenForRouter} */
|
|
341
366
|
getNativeTokenForRouter(_router) {
|
|
342
367
|
return Promise.resolve(NATIVE_MINT.toBase58());
|
|
343
368
|
}
|
|
369
|
+
/** {@inheritDoc Chain.getOffRampsForRouter} */
|
|
344
370
|
async getOffRampsForRouter(router, sourceChainSelector) {
|
|
345
371
|
// feeQuoter is present in router's config, and has a DestChainState account which is updated by
|
|
346
372
|
// the offramps, so we can use it to narrow the search for the offramp
|
|
@@ -355,9 +381,11 @@ export class SolanaChain extends Chain {
|
|
|
355
381
|
}
|
|
356
382
|
throw new Error(`Could not find OffRamp events in feeQuoter=${feeQuoter.toString()} txs`);
|
|
357
383
|
}
|
|
384
|
+
/** {@inheritDoc Chain.getOnRampForRouter} */
|
|
358
385
|
getOnRampForRouter(router, _destChainSelector) {
|
|
359
386
|
return Promise.resolve(router); // solana's Router is also the OnRamp
|
|
360
387
|
}
|
|
388
|
+
/** {@inheritDoc Chain.getOnRampForOffRamp} */
|
|
361
389
|
async getOnRampForOffRamp(offRamp, sourceChainSelector) {
|
|
362
390
|
const program = new Program(CCIP_OFFRAMP_IDL, new PublicKey(offRamp), {
|
|
363
391
|
connection: this.connection,
|
|
@@ -367,9 +395,11 @@ export class SolanaChain extends Chain {
|
|
|
367
395
|
const { config: { onRamp }, } = await program.account.sourceChain.fetch(statePda);
|
|
368
396
|
return decodeAddress(new Uint8Array(onRamp.bytes.slice(0, onRamp.len)), networkInfo(sourceChainSelector).family);
|
|
369
397
|
}
|
|
398
|
+
/** {@inheritDoc Chain.getCommitStoreForOffRamp} */
|
|
370
399
|
getCommitStoreForOffRamp(offRamp) {
|
|
371
400
|
return Promise.resolve(offRamp); // Solana supports only CCIP>=1.6, for which OffRamp and CommitStore are the same
|
|
372
401
|
}
|
|
402
|
+
/** {@inheritDoc Chain.getTokenForTokenPool} */
|
|
373
403
|
async getTokenForTokenPool(tokenPool) {
|
|
374
404
|
const tokenPoolInfo = await this.connection.getAccountInfo(new PublicKey(tokenPool));
|
|
375
405
|
if (!tokenPoolInfo)
|
|
@@ -377,6 +407,7 @@ export class SolanaChain extends Chain {
|
|
|
377
407
|
const { config } = tokenPoolCoder.accounts.decode('state', tokenPoolInfo.data);
|
|
378
408
|
return config.mint.toString();
|
|
379
409
|
}
|
|
410
|
+
/** {@inheritDoc Chain.getTokenInfo} */
|
|
380
411
|
async getTokenInfo(token) {
|
|
381
412
|
const mint = new PublicKey(token);
|
|
382
413
|
const mintInfo = await this.connection.getParsedAccountInfo(mint);
|
|
@@ -408,7 +439,7 @@ export class SolanaChain extends Chain {
|
|
|
408
439
|
}
|
|
409
440
|
catch (error) {
|
|
410
441
|
// Metaplex metadata fetch failed, keep the default values
|
|
411
|
-
|
|
442
|
+
this.logger.debug(`Failed to fetch Metaplex metadata for token ${token}:`, error);
|
|
412
443
|
}
|
|
413
444
|
}
|
|
414
445
|
return {
|
|
@@ -421,6 +452,11 @@ export class SolanaChain extends Chain {
|
|
|
421
452
|
throw new Error(`Unable to parse token data for ${token}`);
|
|
422
453
|
}
|
|
423
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Fetches token metadata from Metaplex.
|
|
457
|
+
* @param mintPublicKey - Token mint public key.
|
|
458
|
+
* @returns Token name and symbol, or null if not found.
|
|
459
|
+
*/
|
|
424
460
|
async _fetchTokenMetadata(mintPublicKey) {
|
|
425
461
|
try {
|
|
426
462
|
// Token Metadata Program ID
|
|
@@ -467,34 +503,30 @@ export class SolanaChain extends Chain {
|
|
|
467
503
|
return name || symbol ? { name, symbol } : null;
|
|
468
504
|
}
|
|
469
505
|
catch (error) {
|
|
470
|
-
|
|
506
|
+
this.logger.debug('Error fetching token metadata:', error);
|
|
471
507
|
return null;
|
|
472
508
|
}
|
|
473
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Decodes a CCIP message from a Solana log event.
|
|
512
|
+
* @param log - Log with data field.
|
|
513
|
+
* @returns Decoded CCIPMessage or undefined if not valid.
|
|
514
|
+
*/
|
|
474
515
|
static decodeMessage({ data }) {
|
|
475
516
|
if (!data || typeof data !== 'string')
|
|
476
517
|
return undefined;
|
|
477
|
-
|
|
518
|
+
// Verify the discriminant matches CCIPMessageSent
|
|
478
519
|
try {
|
|
479
|
-
|
|
520
|
+
if (dataSlice(getDataBytes(data), 0, 8) !== hexDiscriminator('CCIPMessageSent'))
|
|
521
|
+
return;
|
|
480
522
|
}
|
|
481
523
|
catch (_) {
|
|
482
524
|
return;
|
|
483
525
|
}
|
|
484
|
-
const
|
|
485
|
-
if (
|
|
526
|
+
const decoded = routerCoder.events.decode(data);
|
|
527
|
+
if (decoded?.name !== 'CCIPMessageSent')
|
|
486
528
|
return;
|
|
487
|
-
|
|
488
|
-
// Manually parse event header (discriminator + event-level fields)
|
|
489
|
-
let offset = 8;
|
|
490
|
-
// Parse event-level fields
|
|
491
|
-
const _destChainSelector = eventDataBuffer.readBigUInt64LE(offset);
|
|
492
|
-
offset += 8;
|
|
493
|
-
const _sequenceNumber = eventDataBuffer.readBigUInt64LE(offset);
|
|
494
|
-
offset += 8;
|
|
495
|
-
// Now decode the SVM2AnyRampMessage struct using BorshCoder
|
|
496
|
-
const messageBytes = eventDataBuffer.subarray(offset);
|
|
497
|
-
const message = routerCoder.types.decode('SVM2AnyRampMessage', messageBytes);
|
|
529
|
+
const message = decoded.data.message;
|
|
498
530
|
// Convert BN/number types to bigints
|
|
499
531
|
const sourceChainSelector = BigInt(message.header.sourceChainSelector.toString());
|
|
500
532
|
const destChainSelector = BigInt(message.header.destChainSelector.toString());
|
|
@@ -547,6 +579,11 @@ export class SolanaChain extends Chain {
|
|
|
547
579
|
...rest,
|
|
548
580
|
};
|
|
549
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Decodes extra arguments from Solana CCIP messages.
|
|
584
|
+
* @param extraArgs - Encoded extra arguments bytes.
|
|
585
|
+
* @returns Decoded EVMExtraArgsV2 or undefined if unknown format.
|
|
586
|
+
*/
|
|
550
587
|
static decodeExtraArgs(extraArgs) {
|
|
551
588
|
const data = getDataBytes(extraArgs), tag = dataSlice(data, 0, 4);
|
|
552
589
|
switch (tag) {
|
|
@@ -565,6 +602,11 @@ export class SolanaChain extends Chain {
|
|
|
565
602
|
return;
|
|
566
603
|
}
|
|
567
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Encodes extra arguments for Solana CCIP messages.
|
|
607
|
+
* @param args - Extra arguments to encode.
|
|
608
|
+
* @returns Encoded extra arguments as hex string.
|
|
609
|
+
*/
|
|
568
610
|
static encodeExtraArgs(args) {
|
|
569
611
|
if ('computeUnits' in args)
|
|
570
612
|
throw new Error('Solana can only encode EVMExtraArgsV2');
|
|
@@ -575,35 +617,33 @@ export class SolanaChain extends Chain {
|
|
|
575
617
|
'allowOutOfOrderExecution' in args && args.allowOutOfOrderExecution ? '0x01' : '0x00',
|
|
576
618
|
]);
|
|
577
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* Decodes commit reports from a Solana log event.
|
|
622
|
+
* @param log - Log with data field.
|
|
623
|
+
* @param lane - Lane info for filtering.
|
|
624
|
+
* @returns Array of CommitReport or undefined if not valid.
|
|
625
|
+
*/
|
|
578
626
|
static decodeCommits(log, lane) {
|
|
579
627
|
// Check if this is a CommitReportAccepted event by looking at the discriminant
|
|
580
628
|
if (!log.data || typeof log.data !== 'string') {
|
|
581
629
|
throw new Error('Log data is missing or not a string');
|
|
582
630
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
631
|
+
try {
|
|
632
|
+
// Verify the discriminant matches CommitReportAccepted
|
|
633
|
+
if (dataSlice(getDataBytes(log.data), 0, 8) !== hexDiscriminator('CommitReportAccepted'))
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
catch (_) {
|
|
588
637
|
return;
|
|
589
|
-
// Skip the 8-byte discriminant and decode the event data manually
|
|
590
|
-
let offset = 8;
|
|
591
|
-
// Decode Option<MerkleRoot> - first byte indicates Some(1) or None(0)
|
|
592
|
-
const hasValue = eventDataBuffer.readUInt8(offset);
|
|
593
|
-
offset += 1;
|
|
594
|
-
if (!hasValue)
|
|
595
|
-
return [];
|
|
596
|
-
// Decode MerkleRoot struct using the types decoder
|
|
597
|
-
// We need to read the remaining bytes as a MerkleRoot struct
|
|
598
|
-
const merkleRootBytes = eventDataBuffer.subarray(offset);
|
|
599
|
-
const merkleRootData = offrampCoder.types.decode('MerkleRoot', merkleRootBytes);
|
|
600
|
-
if (!merkleRootData) {
|
|
601
|
-
throw new Error('Failed to decode MerkleRoot data');
|
|
602
638
|
}
|
|
639
|
+
const decoded = offrampCoder.events.decode(log.data);
|
|
640
|
+
if (decoded?.name !== 'CommitReportAccepted' || !decoded.data?.merkleRoot)
|
|
641
|
+
return;
|
|
642
|
+
const merkleRoot = decoded.data.merkleRoot;
|
|
603
643
|
// Verify the source chain selector matches our lane
|
|
604
|
-
const sourceChainSelector = BigInt(
|
|
644
|
+
const sourceChainSelector = BigInt(merkleRoot.sourceChainSelector.toString());
|
|
605
645
|
// Convert the onRampAddress from bytes to the proper format
|
|
606
|
-
const onRampAddress = decodeOnRampAddress(
|
|
646
|
+
const onRampAddress = decodeOnRampAddress(merkleRoot.onRampAddress, networkInfo(sourceChainSelector).family);
|
|
607
647
|
if (lane) {
|
|
608
648
|
if (sourceChainSelector !== lane.sourceChainSelector)
|
|
609
649
|
return;
|
|
@@ -615,40 +655,48 @@ export class SolanaChain extends Chain {
|
|
|
615
655
|
{
|
|
616
656
|
sourceChainSelector,
|
|
617
657
|
onRampAddress,
|
|
618
|
-
minSeqNr: BigInt(
|
|
619
|
-
maxSeqNr: BigInt(
|
|
620
|
-
merkleRoot: hexlify(
|
|
658
|
+
minSeqNr: BigInt(merkleRoot.minSeqNr.toString()),
|
|
659
|
+
maxSeqNr: BigInt(merkleRoot.maxSeqNr.toString()),
|
|
660
|
+
merkleRoot: hexlify(getDataBytes(merkleRoot.merkleRoot)),
|
|
621
661
|
},
|
|
622
662
|
];
|
|
623
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Decodes an execution receipt from a Solana log event.
|
|
666
|
+
* @param log - Log with data, tx, and index fields.
|
|
667
|
+
* @returns ExecutionReceipt or undefined if not valid.
|
|
668
|
+
*/
|
|
624
669
|
static decodeReceipt(log) {
|
|
625
670
|
// Check if this is a ExecutionStateChanged event by looking at the discriminant
|
|
626
671
|
if (!log.data || typeof log.data !== 'string') {
|
|
627
672
|
throw new Error('Log data is missing or not a string');
|
|
628
673
|
}
|
|
629
|
-
|
|
630
|
-
|
|
674
|
+
try {
|
|
675
|
+
// Verify the discriminant matches ExecutionStateChanged
|
|
676
|
+
if (dataSlice(getDataBytes(log.data), 0, 8) !== hexDiscriminator('ExecutionStateChanged'))
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
catch (_) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const decoded = offrampCoder.events.decode(log.data);
|
|
683
|
+
if (decoded?.name !== 'ExecutionStateChanged')
|
|
631
684
|
return;
|
|
632
|
-
const
|
|
633
|
-
// Note: We manually decode the event fields rather than using BorshCoder
|
|
634
|
-
// since ExecutionStateChanged is an event, not a defined type
|
|
635
|
-
// Skip the 8-byte discriminant and manually decode the event fields
|
|
636
|
-
let offset = 8;
|
|
637
|
-
// Decode sourceChainSelector (u64)
|
|
638
|
-
const sourceChainSelector = eventDataBuffer.readBigUInt64LE(offset);
|
|
639
|
-
offset += 8;
|
|
640
|
-
// Decode sequenceNumber (u64)
|
|
641
|
-
const sequenceNumber = eventDataBuffer.readBigUInt64LE(offset);
|
|
642
|
-
offset += 8;
|
|
643
|
-
// Decode messageId ([u8; 32])
|
|
644
|
-
const messageId = hexlify(eventDataBuffer.subarray(offset, offset + 32));
|
|
645
|
-
offset += 32;
|
|
646
|
-
// Decode messageHash ([u8; 32])
|
|
647
|
-
const messageHash = hexlify(eventDataBuffer.subarray(offset, offset + 32));
|
|
648
|
-
offset += 32;
|
|
685
|
+
const messageId = hexlify(getDataBytes(decoded.data.messageId));
|
|
649
686
|
// Decode state enum (MessageExecutionState)
|
|
650
687
|
// Enum discriminant is a single byte: Untouched=0, InProgress=1, Success=2, Failure=3
|
|
651
|
-
let state
|
|
688
|
+
let state;
|
|
689
|
+
if (decoded.data.state.inProgress) {
|
|
690
|
+
state = ExecutionState.InProgress;
|
|
691
|
+
}
|
|
692
|
+
else if (decoded.data.state.success) {
|
|
693
|
+
state = ExecutionState.Success;
|
|
694
|
+
}
|
|
695
|
+
else if (decoded.data.state.failure) {
|
|
696
|
+
state = ExecutionState.Failed;
|
|
697
|
+
}
|
|
698
|
+
else
|
|
699
|
+
throw new Error(`Invalid ExecutionState: ${util.inspect(decoded.data.state)}`);
|
|
652
700
|
let returnData;
|
|
653
701
|
if (log.tx) {
|
|
654
702
|
// use only last receipt per tx+message (i.e. skip intermediary InProgress=1 states for Solana)
|
|
@@ -670,14 +718,19 @@ export class SolanaChain extends Chain {
|
|
|
670
718
|
}
|
|
671
719
|
}
|
|
672
720
|
return {
|
|
673
|
-
sourceChainSelector,
|
|
674
|
-
sequenceNumber,
|
|
721
|
+
sourceChainSelector: BigInt(decoded.data.sourceChainSelector.toString()),
|
|
722
|
+
sequenceNumber: BigInt(decoded.data.sequenceNumber.toString()),
|
|
675
723
|
messageId,
|
|
676
|
-
messageHash,
|
|
724
|
+
messageHash: hexlify(getDataBytes(decoded.data.messageHash)),
|
|
677
725
|
state,
|
|
678
726
|
returnData,
|
|
679
727
|
};
|
|
680
728
|
}
|
|
729
|
+
/**
|
|
730
|
+
* Converts bytes to a Solana address (Base58).
|
|
731
|
+
* @param bytes - Bytes to convert.
|
|
732
|
+
* @returns Base58-encoded Solana address.
|
|
733
|
+
*/
|
|
681
734
|
static getAddress(bytes) {
|
|
682
735
|
try {
|
|
683
736
|
if (typeof bytes === 'string' && bs58.decode(bytes).length === 32)
|
|
@@ -688,9 +741,15 @@ export class SolanaChain extends Chain {
|
|
|
688
741
|
}
|
|
689
742
|
return encodeBase58(getDataBytes(bytes));
|
|
690
743
|
}
|
|
691
|
-
|
|
692
|
-
|
|
744
|
+
/**
|
|
745
|
+
* Gets the leaf hasher for Solana destination chains.
|
|
746
|
+
* @param lane - Lane configuration.
|
|
747
|
+
* @returns Leaf hasher function.
|
|
748
|
+
*/
|
|
749
|
+
static getDestLeafHasher(lane, ctx) {
|
|
750
|
+
return getV16SolanaLeafHasher(lane, ctx);
|
|
693
751
|
}
|
|
752
|
+
/** {@inheritDoc Chain.getTokenAdminRegistryFor} */
|
|
694
753
|
async getTokenAdminRegistryFor(address) {
|
|
695
754
|
const [type] = await this.typeAndVersion(address);
|
|
696
755
|
if (!type.includes('Router'))
|
|
@@ -698,52 +757,114 @@ export class SolanaChain extends Chain {
|
|
|
698
757
|
// Solana implements TokenAdminRegistry in the Router/OnRamp program
|
|
699
758
|
return address;
|
|
700
759
|
}
|
|
760
|
+
/** {@inheritDoc Chain.getFee} */
|
|
761
|
+
getFee(router, destChainSelector, message) {
|
|
762
|
+
return getFee(this, router, destChainSelector, message);
|
|
763
|
+
}
|
|
701
764
|
/**
|
|
702
|
-
*
|
|
765
|
+
* Raw/unsigned version of [[sendMessage]]
|
|
766
|
+
*
|
|
767
|
+
* @param sender - sender/feePayer address
|
|
768
|
+
* @param router - router address
|
|
769
|
+
* @param destChainSelector - destination chain selector
|
|
770
|
+
* @param message - AnyMessage to send (with or without fee)
|
|
771
|
+
* @param approveMax - approve max amount of tokens if needed, instead of only what's needed
|
|
772
|
+
* @returns instructions - array of instructions; `ccipSend` is last, after any approval
|
|
773
|
+
* lookupTables - array of lookup tables for `ccipSend` call
|
|
774
|
+
* mainIndex - instructions.length - 1
|
|
703
775
|
*/
|
|
704
|
-
|
|
705
|
-
|
|
776
|
+
async generateUnsignedSendMessage(sender, router, destChainSelector, message, opts) {
|
|
777
|
+
if (!message.fee)
|
|
778
|
+
message.fee = await this.getFee(router, destChainSelector, message);
|
|
779
|
+
return generateUnsignedCcipSend(this, new PublicKey(sender), new PublicKey(router), destChainSelector, message, opts);
|
|
706
780
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
781
|
+
/** {@inheritDoc Chain.sendMessage} */
|
|
782
|
+
async sendMessage(router, destChainSelector, message, opts) {
|
|
783
|
+
const wallet = opts.wallet;
|
|
784
|
+
if (!isWallet(wallet))
|
|
785
|
+
throw new Error(`Expected Wallet, got=${util.inspect(wallet)}`);
|
|
786
|
+
const unsigned = await this.generateUnsignedSendMessage(wallet.publicKey.toBase58(), router, destChainSelector, message, opts);
|
|
787
|
+
const hash = await simulateAndSendTxs(this, wallet, unsigned);
|
|
788
|
+
return (await this.fetchRequestsInTx(await this.getTransaction(hash)))[0];
|
|
712
789
|
}
|
|
790
|
+
/** {@inheritDoc Chain.fetchOffchainTokenData} */
|
|
713
791
|
async fetchOffchainTokenData(request) {
|
|
714
|
-
return fetchSolanaOffchainTokenData(
|
|
792
|
+
return fetchSolanaOffchainTokenData(request, this);
|
|
715
793
|
}
|
|
716
|
-
|
|
794
|
+
/**
|
|
795
|
+
* Raw/unsigned version of [[executeReport]]
|
|
796
|
+
* @param payer - payer address of the execution transaction
|
|
797
|
+
* @param offRamp - OffRamp contract address
|
|
798
|
+
* @param execReport_ - ExecutionReport of a dest=Solana message
|
|
799
|
+
* @param opts - execute report options
|
|
800
|
+
* - forceBuffer - Whether to force the use of a buffer account
|
|
801
|
+
* - forceLookupTable - Whether to force creation of a lookup table for the call
|
|
802
|
+
* @returns instructions - array of instructions to execute the report
|
|
803
|
+
* lookupTables - array of lookup tables for `manuallyExecute` call
|
|
804
|
+
* mainIndex - index of the `manuallyExecute` instruction in the array; last unless
|
|
805
|
+
* forceLookupTable is set, in which case last is ALT deactivation tx, and manuallyExecute is
|
|
806
|
+
* second to last
|
|
807
|
+
*/
|
|
808
|
+
async generateUnsignedExecuteReport(payer, offRamp, execReport_, opts) {
|
|
717
809
|
if (!('computeUnits' in execReport_.message))
|
|
718
810
|
throw new Error("ExecutionReport's message not for Solana");
|
|
719
811
|
const execReport = execReport_;
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
812
|
+
const offRamp_ = new PublicKey(offRamp);
|
|
813
|
+
return generateUnsignedExecuteReport(this, new PublicKey(payer), offRamp_, execReport, opts);
|
|
814
|
+
}
|
|
815
|
+
/** {@inheritDoc Chain.executeReport} */
|
|
816
|
+
async executeReport(offRamp, execReport, opts) {
|
|
817
|
+
const wallet = opts.wallet;
|
|
818
|
+
if (!isWallet(wallet))
|
|
819
|
+
throw new Error(`Expected Wallet, got=${util.inspect(wallet)}`);
|
|
820
|
+
let hash;
|
|
821
|
+
do {
|
|
725
822
|
try {
|
|
726
|
-
await this.
|
|
823
|
+
const unsigned = await this.generateUnsignedExecuteReport(wallet.publicKey.toBase58(), offRamp, execReport, opts);
|
|
824
|
+
hash = await simulateAndSendTxs(this, wallet, unsigned, opts?.gasLimit);
|
|
727
825
|
}
|
|
728
826
|
catch (err) {
|
|
729
|
-
|
|
827
|
+
if (!(err instanceof Error) ||
|
|
828
|
+
!['encoding overruns Uint8Array', 'too large'].some((e) => err.message.includes(e)))
|
|
829
|
+
throw err;
|
|
830
|
+
// in case of failure to serialize a report, first try buffering (because it gets
|
|
831
|
+
// auto-closed upon successful execution), then ALTs (need a grace period ~3min after
|
|
832
|
+
// deactivation before they can be closed/recycled)
|
|
833
|
+
if (!opts?.forceBuffer)
|
|
834
|
+
opts = { ...opts, forceBuffer: true };
|
|
835
|
+
else if (!opts?.forceLookupTable)
|
|
836
|
+
opts = { ...opts, forceLookupTable: true };
|
|
837
|
+
else
|
|
838
|
+
throw err;
|
|
730
839
|
}
|
|
840
|
+
} while (!hash);
|
|
841
|
+
try {
|
|
842
|
+
await this.cleanUpBuffers(opts);
|
|
731
843
|
}
|
|
732
|
-
|
|
844
|
+
catch (err) {
|
|
845
|
+
this.logger.warn('Error while trying to clean up buffers:', err);
|
|
846
|
+
}
|
|
847
|
+
return this.getTransaction(hash);
|
|
733
848
|
}
|
|
734
849
|
/**
|
|
735
850
|
* Clean up and recycle buffers and address lookup tables owned by wallet
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
740
|
-
*
|
|
851
|
+
* @param opts - cleanUp options
|
|
852
|
+
* - wallet - wallet instance to sign txs
|
|
853
|
+
* - waitDeactivation - Whether to wait for lookup table deactivation cool down period
|
|
854
|
+
* (513 slots) to pass before closing; by default, we deactivate (if needed) and move on, to
|
|
855
|
+
* close other ready ALTs
|
|
741
856
|
*/
|
|
742
857
|
async cleanUpBuffers(opts) {
|
|
743
|
-
const wallet =
|
|
744
|
-
|
|
745
|
-
|
|
858
|
+
const wallet = opts.wallet;
|
|
859
|
+
if (!isWallet(wallet))
|
|
860
|
+
throw new Error(`Expected Wallet, got=${util.inspect(wallet)}`);
|
|
861
|
+
await cleanUpBuffers(this, wallet, this.getLogs.bind(this), opts);
|
|
746
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* Parses raw Solana data into typed structures.
|
|
865
|
+
* @param data - Raw data to parse.
|
|
866
|
+
* @returns Parsed data or undefined.
|
|
867
|
+
*/
|
|
747
868
|
static parse(data) {
|
|
748
869
|
if (!data)
|
|
749
870
|
return;
|
|
@@ -794,8 +915,8 @@ export class SolanaChain extends Chain {
|
|
|
794
915
|
bytes: encodeBase58(toLeArray(request.lane.sourceChainSelector, 8)),
|
|
795
916
|
},
|
|
796
917
|
},
|
|
797
|
-
//
|
|
798
|
-
// this should be ~256 around seqNum, i.e. big chance of a match
|
|
918
|
+
// hack: memcmp report.min with msg.sequenceNumber's without least-significant byte;
|
|
919
|
+
// this should be ~256 around seqNum, i.e. big chance of a match; requires PDAs to be alive
|
|
799
920
|
{
|
|
800
921
|
memcmp: {
|
|
801
922
|
offset: 8 + 1 + 8 + 32 + 8 + 1,
|
|
@@ -827,38 +948,25 @@ export class SolanaChain extends Chain {
|
|
|
827
948
|
// in case we can't find it, fallback to generic iterating txs
|
|
828
949
|
return super.fetchCommitReport(commitStore, request, hints);
|
|
829
950
|
}
|
|
830
|
-
|
|
831
|
-
async *fetchExecutionReceipts(offRamp,
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
for await (const log of this.getLogs({
|
|
846
|
-
...hints,
|
|
847
|
-
programs: [offRamp],
|
|
848
|
-
address: commitReportPda.toBase58(),
|
|
849
|
-
topics: ['ExecutionStateChanged'],
|
|
850
|
-
})) {
|
|
851
|
-
const receipt = this.constructor.decodeReceipt(log);
|
|
852
|
-
if (!receipt || !messageIds.has(receipt.messageId))
|
|
853
|
-
continue;
|
|
854
|
-
if (onlyLast || receipt.state === ExecutionState.Success)
|
|
855
|
-
messageIds.delete(receipt.messageId);
|
|
856
|
-
const timestamp = await this.getBlockTimestamp(log.blockNumber);
|
|
857
|
-
yield { receipt, log, timestamp };
|
|
858
|
-
if (!messageIds.size)
|
|
859
|
-
break;
|
|
951
|
+
/** {@inheritDoc Chain.fetchExecutionReceipts} */
|
|
952
|
+
async *fetchExecutionReceipts(offRamp, request, commit, opts) {
|
|
953
|
+
let opts_ = opts;
|
|
954
|
+
if (commit) {
|
|
955
|
+
// if we know of commit, use `commit_report` PDA as more specialized address
|
|
956
|
+
const [commitReportPda] = PublicKey.findProgramAddressSync([
|
|
957
|
+
Buffer.from('commit_report'),
|
|
958
|
+
toLeArray(request.lane.sourceChainSelector, 8),
|
|
959
|
+
bytesToBuffer(commit.report.merkleRoot),
|
|
960
|
+
], new PublicKey(offRamp));
|
|
961
|
+
opts_ = {
|
|
962
|
+
...opts,
|
|
963
|
+
programs: [offRamp],
|
|
964
|
+
address: commitReportPda.toBase58(),
|
|
965
|
+
};
|
|
860
966
|
}
|
|
967
|
+
yield* super.fetchExecutionReceipts(offRamp, request, commit, opts_);
|
|
861
968
|
}
|
|
969
|
+
/** {@inheritDoc Chain.getRegistryTokenConfig} */
|
|
862
970
|
async getRegistryTokenConfig(registry, token) {
|
|
863
971
|
const registry_ = new PublicKey(registry);
|
|
864
972
|
const tokenMint = new PublicKey(token);
|
|
@@ -893,6 +1001,7 @@ export class SolanaChain extends Chain {
|
|
|
893
1001
|
}
|
|
894
1002
|
return config;
|
|
895
1003
|
}
|
|
1004
|
+
/** {@inheritDoc Chain.getTokenPoolConfigs} */
|
|
896
1005
|
async getTokenPoolConfigs(tokenPool) {
|
|
897
1006
|
// `tokenPool` is actually a State PDA in the tokenPoolProgram
|
|
898
1007
|
const tokenPoolState = await this.connection.getAccountInfo(new PublicKey(tokenPool));
|
|
@@ -915,6 +1024,7 @@ export class SolanaChain extends Chain {
|
|
|
915
1024
|
typeAndVersion,
|
|
916
1025
|
};
|
|
917
1026
|
}
|
|
1027
|
+
/** {@inheritDoc Chain.getTokenPoolRemotes} */
|
|
918
1028
|
async getTokenPoolRemotes(tokenPool, remoteChainSelector) {
|
|
919
1029
|
// `tokenPool` is actually a State PDA in the tokenPoolProgram
|
|
920
1030
|
const tokenPoolState = await this.connection.getAccountInfo(new PublicKey(tokenPool));
|
|
@@ -1023,11 +1133,12 @@ export class SolanaChain extends Chain {
|
|
|
1023
1133
|
};
|
|
1024
1134
|
}
|
|
1025
1135
|
catch (err) {
|
|
1026
|
-
|
|
1136
|
+
this.logger.warn('Failed to decode ChainConfig account:', err);
|
|
1027
1137
|
}
|
|
1028
1138
|
}
|
|
1029
1139
|
return remotes;
|
|
1030
1140
|
}
|
|
1141
|
+
/** {@inheritDoc Chain.getSupportedTokens} */
|
|
1031
1142
|
async getSupportedTokens(router) {
|
|
1032
1143
|
// `mint` offset in TokenAdminRegistry account data; more robust against changes in layout
|
|
1033
1144
|
const mintOffset = 8 + 1 + 32 + 32 + 32 + 16 * 2; // = 137
|
|
@@ -1053,7 +1164,8 @@ export class SolanaChain extends Chain {
|
|
|
1053
1164
|
}
|
|
1054
1165
|
return res;
|
|
1055
1166
|
}
|
|
1056
|
-
|
|
1167
|
+
/** {@inheritDoc Chain.getFeeTokens} */
|
|
1168
|
+
async getFeeTokens(router) {
|
|
1057
1169
|
const { feeQuoter } = await this._getRouterConfig(router);
|
|
1058
1170
|
const tokenConfigs = await this.connection.getProgramAccounts(feeQuoter, {
|
|
1059
1171
|
filters: [
|
|
@@ -1066,11 +1178,15 @@ export class SolanaChain extends Chain {
|
|
|
1066
1178
|
],
|
|
1067
1179
|
});
|
|
1068
1180
|
return Object.fromEntries(await Promise.all(tokenConfigs.map(async (acc) => {
|
|
1069
|
-
const token = new PublicKey(acc.account.data.subarray(10, 10 + 32));
|
|
1070
|
-
return [token
|
|
1181
|
+
const token = new PublicKey(acc.account.data.subarray(10, 10 + 32)).toBase58();
|
|
1182
|
+
return [token, await this.getTokenInfo(token)];
|
|
1071
1183
|
})));
|
|
1072
1184
|
}
|
|
1073
|
-
|
|
1185
|
+
/**
|
|
1186
|
+
* Gets the router configuration from the Config PDA.
|
|
1187
|
+
* @param router - Router program address.
|
|
1188
|
+
* @returns Router configuration including feeQuoter.
|
|
1189
|
+
*/
|
|
1074
1190
|
async _getRouterConfig(router) {
|
|
1075
1191
|
const program = new Program(CCIP_ROUTER_IDL, new PublicKey(router), {
|
|
1076
1192
|
connection: this.connection,
|
|
@@ -1081,5 +1197,4 @@ export class SolanaChain extends Chain {
|
|
|
1081
1197
|
return program.account.config.fetch(configPda);
|
|
1082
1198
|
}
|
|
1083
1199
|
}
|
|
1084
|
-
supportedChains[ChainFamily.Solana] = SolanaChain;
|
|
1085
1200
|
//# sourceMappingURL=index.js.map
|