@across-protocol/sdk 4.3.12 → 4.3.14

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.
Files changed (59) hide show
  1. package/dist/cjs/arch/svm/BlockUtils.d.ts +1 -0
  2. package/dist/cjs/arch/svm/BlockUtils.js +69 -52
  3. package/dist/cjs/arch/svm/BlockUtils.js.map +1 -1
  4. package/dist/cjs/arch/svm/SpokeUtils.d.ts +21 -10
  5. package/dist/cjs/arch/svm/SpokeUtils.js +303 -49
  6. package/dist/cjs/arch/svm/SpokeUtils.js.map +1 -1
  7. package/dist/cjs/arch/svm/eventsClient.js +1 -1
  8. package/dist/cjs/arch/svm/eventsClient.js.map +1 -1
  9. package/dist/cjs/arch/svm/types.d.ts +7 -0
  10. package/dist/cjs/arch/svm/utils.d.ts +8 -3
  11. package/dist/cjs/arch/svm/utils.js +82 -5
  12. package/dist/cjs/arch/svm/utils.js.map +1 -1
  13. package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
  14. package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js +5 -2
  15. package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
  16. package/dist/cjs/clients/BundleDataClient/utils/SuperstructUtils.d.ts +18 -18
  17. package/dist/cjs/clients/HubPoolClient.d.ts +1 -0
  18. package/dist/cjs/clients/HubPoolClient.js +11 -0
  19. package/dist/cjs/clients/HubPoolClient.js.map +1 -1
  20. package/dist/esm/arch/svm/BlockUtils.d.ts +7 -0
  21. package/dist/esm/arch/svm/BlockUtils.js +76 -54
  22. package/dist/esm/arch/svm/BlockUtils.js.map +1 -1
  23. package/dist/esm/arch/svm/SpokeUtils.d.ts +51 -10
  24. package/dist/esm/arch/svm/SpokeUtils.js +296 -10
  25. package/dist/esm/arch/svm/SpokeUtils.js.map +1 -1
  26. package/dist/esm/arch/svm/eventsClient.js +1 -1
  27. package/dist/esm/arch/svm/eventsClient.js.map +1 -1
  28. package/dist/esm/arch/svm/types.d.ts +7 -0
  29. package/dist/esm/arch/svm/utils.d.ts +35 -3
  30. package/dist/esm/arch/svm/utils.js +104 -4
  31. package/dist/esm/arch/svm/utils.js.map +1 -1
  32. package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
  33. package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js +5 -2
  34. package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
  35. package/dist/esm/clients/BundleDataClient/utils/SuperstructUtils.d.ts +18 -18
  36. package/dist/esm/clients/HubPoolClient.d.ts +1 -0
  37. package/dist/esm/clients/HubPoolClient.js +19 -0
  38. package/dist/esm/clients/HubPoolClient.js.map +1 -1
  39. package/dist/types/arch/svm/BlockUtils.d.ts +7 -0
  40. package/dist/types/arch/svm/BlockUtils.d.ts.map +1 -1
  41. package/dist/types/arch/svm/SpokeUtils.d.ts +51 -10
  42. package/dist/types/arch/svm/SpokeUtils.d.ts.map +1 -1
  43. package/dist/types/arch/svm/types.d.ts +7 -0
  44. package/dist/types/arch/svm/types.d.ts.map +1 -1
  45. package/dist/types/arch/svm/utils.d.ts +35 -3
  46. package/dist/types/arch/svm/utils.d.ts.map +1 -1
  47. package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts +1 -1
  48. package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts.map +1 -1
  49. package/dist/types/clients/BundleDataClient/utils/SuperstructUtils.d.ts +18 -18
  50. package/dist/types/clients/HubPoolClient.d.ts +1 -0
  51. package/dist/types/clients/HubPoolClient.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/arch/svm/BlockUtils.ts +33 -16
  54. package/src/arch/svm/SpokeUtils.ts +277 -12
  55. package/src/arch/svm/eventsClient.ts +1 -1
  56. package/src/arch/svm/types.ts +8 -0
  57. package/src/arch/svm/utils.ts +100 -9
  58. package/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts +5 -2
  59. package/src/clients/HubPoolClient.ts +23 -0
@@ -1,7 +1,8 @@
1
- import { SvmSpokeClient } from "@across-protocol/contracts";
1
+ import { MessageTransmitterClient, SvmSpokeClient, TokenMessengerMinterClient } from "@across-protocol/contracts";
2
2
  import { decodeFillStatusAccount, fetchState } from "@across-protocol/contracts/dist/src/svm/clients/SvmSpoke";
3
- import { intToU8Array32 } from "@across-protocol/contracts/dist/src/svm/web3-v1/conversionUtils";
4
3
  import { hashNonEmptyMessage } from "@across-protocol/contracts/dist/src/svm/web3-v1";
4
+ import { intToU8Array32 } from "@across-protocol/contracts/dist/src/svm/web3-v1/conversionUtils";
5
+ import { SYSTEM_PROGRAM_ADDRESS } from "@solana-program/system";
5
6
  import {
6
7
  ASSOCIATED_TOKEN_PROGRAM_ADDRESS,
7
8
  TOKEN_PROGRAM_ADDRESS,
@@ -10,48 +11,60 @@ import {
10
11
  getCreateAssociatedTokenIdempotentInstruction,
11
12
  } from "@solana-program/token";
12
13
  import {
14
+ AccountRole,
13
15
  Address,
16
+ IAccountMeta,
17
+ KeyPairSigner,
18
+ ReadonlyUint8Array,
14
19
  appendTransactionMessageInstruction,
15
20
  fetchEncodedAccount,
16
21
  fetchEncodedAccounts,
17
22
  getAddressEncoder,
23
+ getBase64EncodedWireTransaction,
18
24
  getProgramDerivedAddress,
25
+ getSignatureFromTransaction,
19
26
  getU32Encoder,
20
27
  getU64Encoder,
21
28
  pipe,
22
- ReadonlyUint8Array,
29
+ signTransactionMessageWithSigners,
23
30
  some,
24
31
  type TransactionSigner,
25
32
  } from "@solana/kit";
26
33
  import assert from "assert";
27
34
  import { arrayify, hexZeroPad, hexlify } from "ethers/lib/utils";
28
35
  import { Logger } from "winston";
29
- import { SYSTEM_PROGRAM_ADDRESS } from "@solana-program/system";
36
+ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "../../constants";
30
37
  import { DepositWithBlock, FillStatus, FillWithBlock, RelayData, RelayExecutionEventInfo } from "../../interfaces";
31
38
  import {
32
39
  BigNumber,
33
40
  EvmAddress,
34
- SvmAddress,
35
41
  Address as SdkAddress,
42
+ SvmAddress,
43
+ bs58,
44
+ chainIsProd,
36
45
  chainIsSvm,
37
46
  chunk,
38
47
  isUnsafeDepositId,
39
48
  keccak256,
49
+ mapAsync,
40
50
  toAddressType,
41
51
  } from "../../utils";
42
- import { SvmCpiEventsClient } from "./eventsClient";
43
52
  import {
44
53
  bigToU8a32,
45
54
  createDefaultTransaction,
55
+ getCCTPNoncePda,
46
56
  getEventAuthority,
47
57
  getFillStatusPda,
58
+ getSelfAuthority,
48
59
  getStatePda,
60
+ isDepositForBurnEvent,
61
+ simulateAndDecode,
49
62
  toAddress,
50
63
  unwrapEventData,
51
- } from "./utils";
52
- import { CHAIN_IDs } from "../../constants";
53
- import { isSolanaError, SVM_NO_BLOCK_AT_SLOT } from "./provider";
54
- import { SVMEventNames, SVMProvider } from "./types";
64
+ } from "./";
65
+ import { SvmCpiEventsClient } from "./eventsClient";
66
+ import { SVM_NO_BLOCK_AT_SLOT, isSolanaError } from "./provider";
67
+ import { AttestedCCTPMessage, SVMEventNames, SVMProvider } from "./types";
55
68
 
56
69
  /**
57
70
  * @note: Average Solana slot duration is about 400-500ms. We can be conservative
@@ -169,7 +182,10 @@ export async function findDeposit(
169
182
  return undefined;
170
183
  }
171
184
 
172
- const unwrappedDepositEvent = unwrapEventData(depositEvent.data) as Record<string, unknown>;
185
+ const unwrappedDepositEvent = unwrapEventData(depositEvent.data, ["depositId", "outputAmount"]) as Record<
186
+ string,
187
+ unknown
188
+ >;
173
189
  const destinationChainId = unwrappedDepositEvent.destinationChainId as number;
174
190
  // Return the deposit event with block info
175
191
  return {
@@ -358,7 +374,7 @@ export async function findFillEvent(
358
374
 
359
375
  if (fillEvents.length > 0) {
360
376
  const rawFillEvent = fillEvents[0];
361
- const eventData = unwrapEventData(rawFillEvent.data) as FillWithBlock & {
377
+ const eventData = unwrapEventData(rawFillEvent.data, ["depositId", "inputAmount"]) as FillWithBlock & {
362
378
  depositor: string;
363
379
  recipient: string;
364
380
  inputToken: string;
@@ -796,6 +812,19 @@ export const createCloseFillPdaInstruction = async (
796
812
  );
797
813
  };
798
814
 
815
+ export const createReceiveMessageInstruction = async (
816
+ signer: TransactionSigner,
817
+ solanaClient: SVMProvider,
818
+ input: MessageTransmitterClient.ReceiveMessageInput,
819
+ remainingAccounts: IAccountMeta<string>[]
820
+ ) => {
821
+ const receiveMessageIx = await MessageTransmitterClient.getReceiveMessageInstruction(input);
822
+ (receiveMessageIx.accounts as IAccountMeta<string>[]).push(...remainingAccounts);
823
+ return pipe(await createDefaultTransaction(solanaClient, signer), (tx) =>
824
+ appendTransactionMessageInstruction(receiveMessageIx, tx)
825
+ );
826
+ };
827
+
799
828
  export async function getAssociatedTokenAddress(
800
829
  owner: SvmAddress,
801
830
  mint: SvmAddress,
@@ -1048,3 +1077,239 @@ export async function getFillRelayDelegatePda(
1048
1077
 
1049
1078
  return pda;
1050
1079
  }
1080
+
1081
+ /**
1082
+ * Checks if a CCTP message has been processed.
1083
+ * @param solanaClient The Solana client.
1084
+ * @param signer The signer of the transaction.
1085
+ * @param nonce The nonce to check.
1086
+ * @param sourceDomain The source domain.
1087
+ * @returns True if the message has been processed, false otherwise.
1088
+ */
1089
+ export const hasCCTPV1MessageBeenProcessed = async (
1090
+ solanaClient: SVMProvider,
1091
+ signer: KeyPairSigner,
1092
+ nonce: number,
1093
+ sourceDomain: number
1094
+ ): Promise<boolean> => {
1095
+ const noncePda = await getCCTPNoncePda(solanaClient, signer, nonce, sourceDomain);
1096
+ const isNonceUsedIx = await MessageTransmitterClient.getIsNonceUsedInstruction({
1097
+ nonce: nonce,
1098
+ usedNonces: noncePda,
1099
+ });
1100
+ const parserFunction = (buf: Buffer): boolean => {
1101
+ if (buf.length != 1) {
1102
+ throw new Error("Invalid buffer length for isNonceUsedIx");
1103
+ }
1104
+ return Boolean(buf[0]);
1105
+ };
1106
+ return await simulateAndDecode(solanaClient, isNonceUsedIx, signer, parserFunction);
1107
+ };
1108
+
1109
+ /**
1110
+ * Returns the account metas for a tokenless message.
1111
+ * @returns The account metas for a tokenless message.
1112
+ */
1113
+ export async function getAccountMetasForTokenlessMessage(): Promise<IAccountMeta<string>[]> {
1114
+ const statePda = await getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS);
1115
+ return [
1116
+ { address: statePda, role: AccountRole.READONLY },
1117
+ { address: await getSelfAuthority(), role: AccountRole.READONLY },
1118
+ { address: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS, role: AccountRole.READONLY },
1119
+ { address: statePda, role: AccountRole.WRITABLE },
1120
+ { address: await getEventAuthority(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS), role: AccountRole.READONLY },
1121
+ { address: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS, role: AccountRole.READONLY },
1122
+ ];
1123
+ }
1124
+
1125
+ /**
1126
+ * Returns the account metas for a deposit message.
1127
+ * @param message The CCTP message.
1128
+ * @param hubChainId The chain ID of the hub.
1129
+ * @param tokenMessengerMinter The token messenger minter address.
1130
+ * @returns The account metas for a deposit message.
1131
+ */
1132
+ async function getAccountMetasForDepositMessage(
1133
+ message: AttestedCCTPMessage,
1134
+ hubChainId: number,
1135
+ tokenMessengerMinter: Address
1136
+ ): Promise<IAccountMeta<string>[]> {
1137
+ const l1Usdc = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[hubChainId]);
1138
+ const l2Usdc = SvmAddress.from(
1139
+ TOKEN_SYMBOLS_MAP.USDC.addresses[chainIsProd(hubChainId) ? CHAIN_IDs.SOLANA : CHAIN_IDs.SOLANA_DEVNET]
1140
+ );
1141
+
1142
+ const [tokenMessengerPda] = await getProgramDerivedAddress({
1143
+ programAddress: tokenMessengerMinter,
1144
+ seeds: ["token_messenger"],
1145
+ });
1146
+
1147
+ const [tokenMinterPda] = await getProgramDerivedAddress({
1148
+ programAddress: tokenMessengerMinter,
1149
+ seeds: ["token_minter"],
1150
+ });
1151
+
1152
+ const [localTokenPda] = await getProgramDerivedAddress({
1153
+ programAddress: tokenMessengerMinter,
1154
+ seeds: ["local_token", bs58.decode(l2Usdc.toBase58())],
1155
+ });
1156
+
1157
+ const [tokenMessengerEventAuthorityPda] = await getProgramDerivedAddress({
1158
+ programAddress: tokenMessengerMinter,
1159
+ seeds: ["__event_authority"],
1160
+ });
1161
+
1162
+ const [custodyTokenAccountPda] = await getProgramDerivedAddress({
1163
+ programAddress: tokenMessengerMinter,
1164
+ seeds: ["custody", bs58.decode(l2Usdc.toBase58())],
1165
+ });
1166
+
1167
+ const state = await getStatePda(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS);
1168
+ const tokenProgram = TOKEN_PROGRAM_ADDRESS;
1169
+ const vault = await getAssociatedTokenAddress(
1170
+ SvmAddress.from(state),
1171
+ SvmAddress.from(l2Usdc.toBase58()),
1172
+ tokenProgram
1173
+ );
1174
+
1175
+ // Define accounts dependent on deposit information.
1176
+ const [tokenPairPda] = await getProgramDerivedAddress({
1177
+ programAddress: tokenMessengerMinter,
1178
+ seeds: [
1179
+ new Uint8Array(Buffer.from("token_pair")),
1180
+ new Uint8Array(Buffer.from(String(message.sourceDomain))),
1181
+ new Uint8Array(Buffer.from(l1Usdc.toBytes32().slice(2), "hex")),
1182
+ ],
1183
+ });
1184
+
1185
+ const [remoteTokenMessengerPda] = await getProgramDerivedAddress({
1186
+ programAddress: tokenMessengerMinter,
1187
+ seeds: ["remote_token_messenger", String(message.sourceDomain)],
1188
+ });
1189
+
1190
+ return [
1191
+ { address: tokenMessengerPda, role: AccountRole.READONLY },
1192
+ { address: remoteTokenMessengerPda, role: AccountRole.READONLY },
1193
+ { address: tokenMinterPda, role: AccountRole.WRITABLE },
1194
+ { address: localTokenPda, role: AccountRole.WRITABLE },
1195
+ { address: tokenPairPda, role: AccountRole.READONLY },
1196
+ { address: vault, role: AccountRole.WRITABLE },
1197
+ { address: custodyTokenAccountPda, role: AccountRole.WRITABLE },
1198
+ { address: TOKEN_PROGRAM_ADDRESS, role: AccountRole.READONLY },
1199
+ { address: tokenMessengerEventAuthorityPda, role: AccountRole.READONLY },
1200
+ { address: tokenMessengerMinter, role: AccountRole.READONLY },
1201
+ ];
1202
+ }
1203
+
1204
+ /**
1205
+ * Returns the CCTP v1 receive message transaction.
1206
+ * @param solanaClient The Solana client.
1207
+ * @param signer The signer of the transaction.
1208
+ * @param message The CCTP message.
1209
+ * @param hubChainId The chain ID of the hub.
1210
+ * @returns The CCTP v1 receive message transaction.
1211
+ */
1212
+ export async function getCCTPV1ReceiveMessageTx(
1213
+ solanaClient: SVMProvider,
1214
+ signer: KeyPairSigner,
1215
+ message: AttestedCCTPMessage,
1216
+ hubChainId: number
1217
+ ) {
1218
+ const [messageTransmitterPda] = await getProgramDerivedAddress({
1219
+ programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
1220
+ seeds: ["message_transmitter"],
1221
+ });
1222
+
1223
+ const [eventAuthorityPda] = await getProgramDerivedAddress({
1224
+ programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
1225
+ seeds: ["__event_authority"],
1226
+ });
1227
+
1228
+ const cctpMessageReceiver = isDepositForBurnEvent(message)
1229
+ ? TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS
1230
+ : SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS;
1231
+
1232
+ const [authorityPda] = await getProgramDerivedAddress({
1233
+ programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
1234
+ seeds: ["message_transmitter_authority", bs58.decode(cctpMessageReceiver)],
1235
+ });
1236
+
1237
+ // Notice: message.nonce is only valid for v1 messages
1238
+ const usedNonces = await getCCTPNoncePda(solanaClient, signer, message.nonce, message.sourceDomain);
1239
+
1240
+ // Notice: for Svm tokenless messages, we currently only support very specific finalizations: Hub -> Spoke relayRootBundle calls
1241
+ const accountMetas: IAccountMeta<string>[] = isDepositForBurnEvent(message)
1242
+ ? await getAccountMetasForDepositMessage(
1243
+ message,
1244
+ hubChainId,
1245
+ TokenMessengerMinterClient.TOKEN_MESSENGER_MINTER_PROGRAM_ADDRESS
1246
+ )
1247
+ : await getAccountMetasForTokenlessMessage();
1248
+
1249
+ const messageBytes = message.messageBytes.startsWith("0x")
1250
+ ? Buffer.from(message.messageBytes.slice(2), "hex")
1251
+ : Buffer.from(message.messageBytes, "hex");
1252
+
1253
+ const input: MessageTransmitterClient.ReceiveMessageInput = {
1254
+ program: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
1255
+ payer: signer,
1256
+ caller: signer,
1257
+ authorityPda,
1258
+ messageTransmitter: messageTransmitterPda,
1259
+ eventAuthority: eventAuthorityPda,
1260
+ usedNonces,
1261
+ receiver: SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS,
1262
+ systemProgram: SYSTEM_PROGRAM_ADDRESS,
1263
+ message: messageBytes,
1264
+ attestation: Buffer.from(message.attestation.slice(2), "hex"),
1265
+ };
1266
+
1267
+ return createReceiveMessageInstruction(signer, solanaClient, input, accountMetas);
1268
+ }
1269
+
1270
+ /**
1271
+ * Finalizes CCTP deposits and messages on Solana.
1272
+ *
1273
+ * @param solanaClient The Solana client.
1274
+ * @param attestedMessages The CCTP messages to Solana.
1275
+ * @param signer A base signer to be converted into a Solana signer.
1276
+ * @param simulate Whether to simulate the transaction.
1277
+ * @param hubChainId The chain ID of the hub.
1278
+ * @returns A list of executed transaction signatures.
1279
+ */
1280
+
1281
+ export function finalizeCCTPV1Messages(
1282
+ solanaClient: SVMProvider,
1283
+ attestedMessages: AttestedCCTPMessage[],
1284
+ signer: KeyPairSigner,
1285
+ simulate = false,
1286
+ hubChainId = 1
1287
+ ): Promise<string[]> {
1288
+ return mapAsync(attestedMessages, async (message) => {
1289
+ const receiveMessageIx = await getCCTPV1ReceiveMessageTx(solanaClient, signer, message, hubChainId);
1290
+
1291
+ if (simulate) {
1292
+ const result = await solanaClient
1293
+ .simulateTransaction(
1294
+ getBase64EncodedWireTransaction(await signTransactionMessageWithSigners(receiveMessageIx)),
1295
+ {
1296
+ encoding: "base64",
1297
+ }
1298
+ )
1299
+ .send();
1300
+ if (result.value.err) {
1301
+ throw new Error(result.value.err.toString());
1302
+ }
1303
+ return "";
1304
+ }
1305
+
1306
+ const signedTransaction = await signTransactionMessageWithSigners(receiveMessageIx);
1307
+ const signature = getSignatureFromTransaction(signedTransaction);
1308
+ const encodedTransaction = getBase64EncodedWireTransaction(signedTransaction);
1309
+ await solanaClient
1310
+ .sendTransaction(encodedTransaction, { preflightCommitment: "confirmed", encoding: "base64" })
1311
+ .send();
1312
+
1313
+ return signature;
1314
+ });
1315
+ }
@@ -326,7 +326,7 @@ export class SvmCpiEventsClient {
326
326
  return fillEvents.map((event) => {
327
327
  const unwrappedEventData = unwrapEventData(event as Record<string, unknown>, [
328
328
  "depositId",
329
- "outputAmount",
329
+ "inputAmount",
330
330
  ]) as Record<"data", Fill> &
331
331
  Record<
332
332
  "data",
@@ -61,3 +61,11 @@ export type RpcClient = {
61
61
  rpc: SVMProvider;
62
62
  rpcSubscriptions: RpcSubscriptions<SignatureNotificationsApi & SlotNotificationsApi>;
63
63
  };
64
+
65
+ export type AttestedCCTPMessage = {
66
+ nonce: number;
67
+ sourceDomain: number;
68
+ messageBytes: string;
69
+ attestation: string;
70
+ type: "transfer" | "message";
71
+ };
@@ -1,24 +1,29 @@
1
- import bs58 from "bs58";
2
- import { ethers } from "ethers";
1
+ import { MessageTransmitterClient, SvmSpokeClient } from "@across-protocol/contracts";
3
2
  import { BN, BorshEventCoder, Idl } from "@coral-xyz/anchor";
4
3
  import {
5
4
  Address,
5
+ IInstruction,
6
+ KeyPairSigner,
6
7
  address,
8
+ appendTransactionMessageInstruction,
9
+ createTransactionMessage,
7
10
  getAddressEncoder,
11
+ getBase64EncodedWireTransaction,
8
12
  getProgramDerivedAddress,
9
- getU64Encoder,
10
13
  getU32Encoder,
14
+ getU64Encoder,
11
15
  isAddress,
12
- type TransactionSigner,
13
16
  pipe,
14
- createTransactionMessage,
15
17
  setTransactionMessageFeePayerSigner,
16
18
  setTransactionMessageLifetimeUsingBlockhash,
19
+ signTransactionMessageWithSigners,
20
+ type TransactionSigner,
17
21
  } from "@solana/kit";
18
- import { SvmSpokeClient } from "@across-protocol/contracts";
22
+ import bs58 from "bs58";
23
+ import { ethers } from "ethers";
19
24
  import { FillType, RelayData } from "../../interfaces";
20
- import { BigNumber, getRelayDataHash, isDefined, isUint8Array, Address as SdkAddress } from "../../utils";
21
- import { EventName, SVMEventNames, SVMProvider } from "./types";
25
+ import { BigNumber, Address as SdkAddress, getRelayDataHash, isDefined, isUint8Array } from "../../utils";
26
+ import { AttestedCCTPMessage, EventName, SVMEventNames, SVMProvider } from "./types";
22
27
 
23
28
  export { isSolanaError } from "@solana/kit";
24
29
 
@@ -145,7 +150,7 @@ export function getEventName(rawName: string): EventName {
145
150
  */
146
151
  export function unwrapEventData(
147
152
  data: unknown,
148
- uint8ArrayKeysAsBigInt: string[] = ["depositId", "outputAmount"],
153
+ uint8ArrayKeysAsBigInt: string[] = ["depositId", "outputAmount", "inputAmount"],
149
154
  currentKey?: string
150
155
  ): unknown {
151
156
  // Handle null/undefined
@@ -334,6 +339,18 @@ export async function getEventAuthority(programId: Address): Promise<Address> {
334
339
  return eventAuthority;
335
340
  }
336
341
 
342
+ /**
343
+ * Returns the PDA for the Self Authority.
344
+ * @returns The PDA for the Self Authority.
345
+ */
346
+ export const getSelfAuthority = async () => {
347
+ const [selfAuthority] = await getProgramDerivedAddress({
348
+ programAddress: address(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
349
+ seeds: ["self_authority"],
350
+ });
351
+ return selfAuthority;
352
+ };
353
+
337
354
  /**
338
355
  * Returns a random SVM address.
339
356
  */
@@ -358,8 +375,82 @@ export const createDefaultTransaction = async (rpcClient: SVMProvider, signer: T
358
375
  );
359
376
  };
360
377
 
378
+ /**
379
+ * Simulates a transaction and decodes the result using a parser function.
380
+ * @param solanaClient - The Solana client.
381
+ * @param ix - The instruction to simulate.
382
+ * @param signer - The signer of the transaction.
383
+ * @param parser - The parser function to decode the result.
384
+ * @returns The decoded result.
385
+ */
386
+ export const simulateAndDecode = async <P extends (buf: Buffer) => unknown>(
387
+ solanaClient: SVMProvider,
388
+ ix: IInstruction,
389
+ signer: KeyPairSigner,
390
+ parser: P
391
+ ): Promise<ReturnType<P>> => {
392
+ const simulationTx = appendTransactionMessageInstruction(ix, await createDefaultTransaction(solanaClient, signer));
393
+
394
+ const simulationResult = await solanaClient
395
+ .simulateTransaction(getBase64EncodedWireTransaction(await signTransactionMessageWithSigners(simulationTx)), {
396
+ encoding: "base64",
397
+ })
398
+ .send();
399
+
400
+ if (!simulationResult.value.returnData?.data[0]) {
401
+ throw new Error("No return data");
402
+ }
403
+
404
+ return parser(Buffer.from(simulationResult.value.returnData.data[0], "base64")) as ReturnType<P>;
405
+ };
406
+
407
+ /**
408
+ * Returns the PDA for the CCTP nonce.
409
+ * @param solanaClient The Solana client.
410
+ * @param signer The signer of the transaction.
411
+ * @param nonce The nonce to get the PDA for.
412
+ * @param sourceDomain The source domain.
413
+ * @returns The PDA for the CCTP nonce.
414
+ */
415
+ export const getCCTPNoncePda = async (
416
+ solanaClient: SVMProvider,
417
+ signer: KeyPairSigner,
418
+ nonce: number,
419
+ sourceDomain: number
420
+ ) => {
421
+ const [messageTransmitterPda] = await getProgramDerivedAddress({
422
+ programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
423
+ seeds: ["message_transmitter"],
424
+ });
425
+ const getNonceIx = await MessageTransmitterClient.getGetNoncePdaInstruction({
426
+ messageTransmitter: messageTransmitterPda,
427
+ nonce,
428
+ sourceDomain: sourceDomain,
429
+ });
430
+
431
+ const parserFunction = (buf: Buffer): Address => {
432
+ if (buf.length === 32) {
433
+ return address(bs58.encode(buf));
434
+ }
435
+ throw new Error("Invalid buffer");
436
+ };
437
+
438
+ return await simulateAndDecode(solanaClient, getNonceIx, signer, parserFunction);
439
+ };
440
+
441
+ /**
442
+ * Checks if a CCTP message is a deposit for burn event.
443
+ * @param event The CCTP message event.
444
+ * @returns True if the message is a deposit for burn event, false otherwise.
445
+ */
446
+ export function isDepositForBurnEvent(event: AttestedCCTPMessage): boolean {
447
+ return event.type === "transfer";
448
+ }
449
+
361
450
  /**
362
451
  * Convert a bigint (0 ≤ n < 2^256) to a 32-byte Uint8Array (big-endian).
452
+ * @param n The bigint to convert.
453
+ * @returns The 32-byte Uint8Array.
363
454
  */
364
455
  export function bigintToU8a32(n: bigint): Uint8Array {
365
456
  if (n < BigInt(0) || n > ethers.constants.MaxUint256.toBigInt()) {
@@ -25,7 +25,8 @@ export async function getWidestPossibleExpectedBlockRange(
25
25
  endBlockBuffers: number[],
26
26
  clients: Clients,
27
27
  latestMainnetBlock: number,
28
- enabledChains: number[]
28
+ enabledChains: number[],
29
+ optimistic: boolean = false
29
30
  ): Promise<number[][]> {
30
31
  // We impose a buffer on the head of the chain to increase the probability that the received blocks are final.
31
32
  // Reducing the latest block that we query also gives partially filled deposits slightly more buffer for relayers
@@ -85,7 +86,9 @@ export async function getWidestPossibleExpectedBlockRange(
85
86
  // Chain has advanced far enough including the buffer, return range from previous proposed end block + 1 to
86
87
  // latest block for chain minus buffer.
87
88
  return [
88
- clients.hubPoolClient.getNextBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId),
89
+ optimistic
90
+ ? clients.hubPoolClient.getOptimisticBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId)
91
+ : clients.hubPoolClient.getNextBundleStartBlockNumber(chainIds, latestMainnetBlock, chainId),
89
92
  latestPossibleBundleEndBlockNumbers[index],
90
93
  ];
91
94
  });
@@ -789,6 +789,29 @@ export class HubPoolClient extends BaseAbstractClient {
789
789
  return endBlock > 0 ? endBlock + 1 : 0;
790
790
  }
791
791
 
792
+ // @dev Returns the start block of the next bundle assuming that if there is a currently outstanding root bundle proposal, it will pass liveness.
793
+ getOptimisticBundleStartBlockNumber(chainIdList: number[], latestMainnetBlock: number, chainId: number): number {
794
+ // If there is no pending root bundle, then return block ranges based on the latest fully executed root bundle.
795
+ if (!this.hasPendingProposal()) {
796
+ return this.getNextBundleStartBlockNumber(chainIdList, latestMainnetBlock, chainId);
797
+ }
798
+ // We cannot normally index `this.proposedRootBundles` since a bundle there may have been previously disputed, so only index `this.proposedRootBundles`
799
+ // if we have a pending proposal, since this must mean that the pending root bundle is the most recent proposed root bundle.
800
+ const latestProposedBundle = this.proposedRootBundles[this.proposedRootBundles.length - 1];
801
+
802
+ // If there is no previous root bundle, then return 0.
803
+ if (!isDefined(latestProposedBundle)) {
804
+ return 0;
805
+ }
806
+
807
+ // Otherwise, get the bundle end block for the optimistic bundle.
808
+ const optimisticEndBlock = this.getBundleEndBlockForChain(latestProposedBundle, chainId, chainIdList);
809
+
810
+ // As above, this assumes that chain ID's are only added to the chain ID list over time, and that chains are never
811
+ // deleted.
812
+ return optimisticEndBlock > 0 ? optimisticEndBlock + 1 : 0;
813
+ }
814
+
792
815
  getLatestExecutedRootBundleContainingL1Token(
793
816
  block: number,
794
817
  chain: number,