@layerzerolabs/oapp-solana-contracts 1.2.11

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 (40) hide show
  1. package/Anchor.toml +23 -0
  2. package/Cargo.toml +26 -0
  3. package/README.md +181 -0
  4. package/idls/endpoint.json +3520 -0
  5. package/macros/Cargo.toml +23 -0
  6. package/macros/src/composer.rs +43 -0
  7. package/macros/src/instructions/composer/lz_compose.rs +17 -0
  8. package/macros/src/instructions/composer/lz_compose_types_info.rs +29 -0
  9. package/macros/src/instructions/composer/lz_compose_types_v2.rs +17 -0
  10. package/macros/src/instructions/composer/mod.rs +23 -0
  11. package/macros/src/instructions/mod.rs +4 -0
  12. package/macros/src/instructions/oapp/accept_default_admin_transfer.rs +50 -0
  13. package/macros/src/instructions/oapp/get_enforced_options.rs +57 -0
  14. package/macros/src/instructions/oapp/get_peer.rs +55 -0
  15. package/macros/src/instructions/oapp/init_enforced_options.rs +87 -0
  16. package/macros/src/instructions/oapp/is_compose_msg_sender.rs +44 -0
  17. package/macros/src/instructions/oapp/lz_receive.rs +33 -0
  18. package/macros/src/instructions/oapp/lz_receive_types_info.rs +28 -0
  19. package/macros/src/instructions/oapp/lz_receive_types_v2.rs +17 -0
  20. package/macros/src/instructions/oapp/mod.rs +41 -0
  21. package/macros/src/instructions/oapp/next_nonce.rs +52 -0
  22. package/macros/src/instructions/oapp/set_enforced_options.rs +101 -0
  23. package/macros/src/instructions/oapp/set_peer.rs +74 -0
  24. package/macros/src/lib.rs +81 -0
  25. package/macros/src/oapp.rs +86 -0
  26. package/macros/src/state_types.rs +79 -0
  27. package/package.json +61 -0
  28. package/rust-toolchain.toml +3 -0
  29. package/rustfmt.toml +23 -0
  30. package/src/common.rs +156 -0
  31. package/src/endpoint_cpi.rs +386 -0
  32. package/src/errors.rs +16 -0
  33. package/src/events.rs +26 -0
  34. package/src/lib.rs +82 -0
  35. package/src/lz_compose_types_v2.rs +88 -0
  36. package/src/lz_receive_types_v2.rs +147 -0
  37. package/src/oapp_info.rs +278 -0
  38. package/src/options.rs +147 -0
  39. package/src/types.rs +114 -0
  40. package/src/utils.rs +14 -0
@@ -0,0 +1,386 @@
1
+ //! CPI helpers for invoking the LayerZero Endpoint program.
2
+ //!
3
+ //! Each function in this module is a thin wrapper that:
4
+ //! 1. Validates the caller-supplied authority key matches the expected account in the accounts
5
+ //! slice.
6
+ //! 2. Validates that `accounts[0]` is the Endpoint program itself (checked against
7
+ //! `endpoint_program`) and that enough accounts were supplied.
8
+ //! 3. Invokes the corresponding Endpoint instruction, signing with the OApp's PDA seeds.
9
+ //!
10
+ //! **Important:** The `accounts` slice must follow the field declaration order of the
11
+ //! corresponding `cpi::accounts::*` struct, with `endpoint_program` prepended at index 0.
12
+ //! See each wrapper's "Accounts layout" section for the exact positional order.
13
+ //!
14
+ //! These wrappers are the primary interface OApp programs use to interact with the
15
+ //! LayerZero Endpoint for cross-chain messaging operations.
16
+ //!
17
+ //! The `endpoint` IDL (`idls/endpoint.json`) was obtained with:
18
+ //!
19
+ //! ```text
20
+ //! anchor idl fetch 76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6 --provider.cluster mainnet
21
+ //! ```
22
+
23
+ use crate::endpoint::{
24
+ self,
25
+ cpi::accounts::{Clear, ClearCompose, Quote, RegisterOapp, Send, SendCompose, SetDelegate},
26
+ types::{
27
+ ClearComposeParams, ClearParams, MessagingFee, MessagingReceipt, QuoteParams,
28
+ RegisterOAppParams, SendComposeParams, SendParams, SetDelegateParams,
29
+ },
30
+ };
31
+ use anchor_lang::{
32
+ error,
33
+ prelude::{AccountInfo, CpiContext, ErrorCode, Key, Pubkey, Result},
34
+ require, require_keys_eq, ToAccountInfos, ToAccountMetas,
35
+ };
36
+
37
+ pub const REGISTER_OAPP_MIN_ACCOUNTS_LEN: usize = 7;
38
+ pub const SET_DELEGATE_MIN_ACCOUNTS_LEN: usize = 5;
39
+ pub const SEND_MIN_ACCOUNTS_LEN: usize = 10;
40
+ pub const QUOTE_MIN_ACCOUNTS_LEN: usize = 7;
41
+ pub const CLEAR_MIN_ACCOUNTS_LEN: usize = 8;
42
+ pub const SEND_COMPOSE_MIN_ACCOUNTS_LEN: usize = 7;
43
+ pub const CLEAR_COMPOSE_MIN_ACCOUNTS_LEN: usize = 5;
44
+
45
+ fn validate(accounts: &[AccountInfo], endpoint_program: Pubkey, min_len: usize) -> Result<()> {
46
+ require!(accounts.len() >= min_len, ErrorCode::AccountNotEnoughKeys);
47
+ require_keys_eq!(accounts[0].key(), endpoint_program, ErrorCode::InvalidProgramId);
48
+ Ok(())
49
+ }
50
+
51
+ fn make_cpi_ctx<'info, T>(
52
+ accounts: &[AccountInfo<'info>],
53
+ cpi_accounts: T,
54
+ min_len: usize,
55
+ ) -> CpiContext<'info, 'info, 'info, 'info, T>
56
+ where
57
+ T: ToAccountMetas + ToAccountInfos<'info>,
58
+ {
59
+ let mut ctx = CpiContext::new(accounts[0].key(), cpi_accounts);
60
+ if accounts.len() > min_len {
61
+ ctx = ctx.with_remaining_accounts(accounts[min_len..].to_vec());
62
+ }
63
+ ctx
64
+ }
65
+
66
+ /// Registers the OApp with the LayerZero Endpoint.
67
+ ///
68
+ /// Must be called once during program initialization to establish the OApp's identity
69
+ /// with the Endpoint. After registration, the Endpoint recognizes this program as a
70
+ /// valid sender/receiver of cross-chain messages.
71
+ ///
72
+ /// # Accounts layout
73
+ /// The `accounts` slice must follow the `RegisterOapp` struct field order:
74
+ ///
75
+ /// 0. `endpoint_program`
76
+ /// 1. `payer`
77
+ /// 2. `oapp`
78
+ /// 3. `oapp_registry`
79
+ /// 4. `system_program`
80
+ /// 5. `event_authority`
81
+ /// 6. `program`
82
+ /// 7. remaining accounts (variable length)
83
+ ///
84
+ /// # Accounts validation
85
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
86
+ /// - `accounts[2]` must match the provided `oapp` key (the OApp PDA).
87
+ pub fn register_oapp(
88
+ endpoint_program: Pubkey,
89
+ oapp: Pubkey,
90
+ accounts: &[AccountInfo],
91
+ seeds: &[&[u8]],
92
+ params: RegisterOAppParams,
93
+ ) -> Result<()> {
94
+ validate(accounts, endpoint_program, REGISTER_OAPP_MIN_ACCOUNTS_LEN)?;
95
+ require_keys_eq!(accounts[2].key(), oapp, ErrorCode::ConstraintAddress);
96
+
97
+ let cpi_accounts = RegisterOapp {
98
+ payer: accounts[1].clone(),
99
+ oapp: accounts[2].clone(),
100
+ oapp_registry: accounts[3].clone(),
101
+ system_program: accounts[4].clone(),
102
+ event_authority: accounts[5].clone(),
103
+ program: accounts[6].clone(),
104
+ };
105
+ endpoint::cpi::register_oapp(
106
+ make_cpi_ctx(accounts, cpi_accounts, REGISTER_OAPP_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
107
+ params,
108
+ )
109
+ }
110
+
111
+ /// Sets a delegate address authorized to configure the OApp's Endpoint settings.
112
+ ///
113
+ /// The delegate can manage messaging libraries, executors, and other Endpoint-level
114
+ /// configuration on behalf of the OApp admin.
115
+ ///
116
+ /// # Accounts layout
117
+ /// The `accounts` slice must follow the `SetDelegate` struct field order:
118
+ ///
119
+ /// 0. `endpoint_program`
120
+ /// 1. `oapp`
121
+ /// 2. `oapp_registry`
122
+ /// 3. `event_authority`
123
+ /// 4. `program`
124
+ /// 5. remaining accounts (variable length)
125
+ ///
126
+ /// # Accounts validation
127
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
128
+ /// - `accounts[1]` must match the provided `oapp` key.
129
+ pub fn set_delegate(
130
+ endpoint_program: Pubkey,
131
+ oapp: Pubkey,
132
+ accounts: &[AccountInfo],
133
+ seeds: &[&[u8]],
134
+ params: SetDelegateParams,
135
+ ) -> Result<()> {
136
+ validate(accounts, endpoint_program, SET_DELEGATE_MIN_ACCOUNTS_LEN)?;
137
+ require_keys_eq!(accounts[1].key(), oapp, ErrorCode::ConstraintAddress);
138
+
139
+ let cpi_accounts = SetDelegate {
140
+ oapp: accounts[1].clone(),
141
+ oapp_registry: accounts[2].clone(),
142
+ event_authority: accounts[3].clone(),
143
+ program: accounts[4].clone(),
144
+ };
145
+ endpoint::cpi::set_delegate(
146
+ make_cpi_ctx(accounts, cpi_accounts, SET_DELEGATE_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
147
+ params,
148
+ )
149
+ }
150
+
151
+ /// Sends a cross-chain message through the LayerZero Endpoint.
152
+ ///
153
+ /// This is the core outbound messaging function. The Endpoint assigns a nonce,
154
+ /// emits a packet event for DVNs to verify, and charges the messaging fee.
155
+ ///
156
+ /// # Accounts layout
157
+ /// The `accounts` slice must follow the `Send` struct field order:
158
+ ///
159
+ /// 0. `endpoint_program`
160
+ /// 1. `sender`
161
+ /// 2. `send_library_program`
162
+ /// 3. `send_library_config`
163
+ /// 4. `default_send_library_config`
164
+ /// 5. `send_library_info`
165
+ /// 6. `endpoint`
166
+ /// 7. `nonce`
167
+ /// 8. `event_authority`
168
+ /// 9. `program`
169
+ /// 10. remaining accounts (variable length)
170
+ ///
171
+ /// # Accounts validation
172
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
173
+ /// - `accounts[1]` must match the provided `sender` key.
174
+ /// - The `sender` key must appear exactly once in the accounts slice to prevent duplicate-account
175
+ /// exploits that could manipulate fee deduction.
176
+ ///
177
+ /// # Returns
178
+ /// A [`MessagingReceipt`] containing the assigned nonce and fee breakdown.
179
+ pub fn send(
180
+ endpoint_program: Pubkey,
181
+ sender: Pubkey,
182
+ accounts: &[AccountInfo],
183
+ seeds: &[&[u8]],
184
+ params: SendParams,
185
+ ) -> Result<MessagingReceipt> {
186
+ validate(accounts, endpoint_program, SEND_MIN_ACCOUNTS_LEN)?;
187
+ require_keys_eq!(accounts[1].key(), sender, ErrorCode::ConstraintAddress);
188
+ require!(
189
+ accounts.iter().filter(|acc| acc.key == &sender).count() <= 1,
190
+ ErrorCode::ConstraintAddress
191
+ );
192
+
193
+ let cpi_accounts = Send {
194
+ sender: accounts[1].clone(),
195
+ send_library_program: accounts[2].clone(),
196
+ send_library_config: accounts[3].clone(),
197
+ default_send_library_config: accounts[4].clone(),
198
+ send_library_info: accounts[5].clone(),
199
+ endpoint: accounts[6].clone(),
200
+ nonce: accounts[7].clone(),
201
+ event_authority: accounts[8].clone(),
202
+ program: accounts[9].clone(),
203
+ };
204
+ let rtn = endpoint::cpi::send(
205
+ make_cpi_ctx(accounts, cpi_accounts, SEND_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
206
+ params,
207
+ )?;
208
+ Ok(rtn.get())
209
+ }
210
+
211
+ /// Queries the Endpoint for the fee required to send a cross-chain message.
212
+ ///
213
+ /// This is a read-only CPI — no PDA signer seeds are needed. Call this before
214
+ /// [`send`] to determine the [`MessagingFee`] the caller must pay.
215
+ ///
216
+ /// # Accounts layout
217
+ /// The `accounts` slice must follow the `Quote` struct field order:
218
+ ///
219
+ /// 0. `endpoint_program`
220
+ /// 1. `send_library_program`
221
+ /// 2. `send_library_config`
222
+ /// 3. `default_send_library_config`
223
+ /// 4. `send_library_info`
224
+ /// 5. `endpoint`
225
+ /// 6. `nonce`
226
+ /// 7. remaining accounts (variable length)
227
+ ///
228
+ /// # Accounts validation
229
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
230
+ pub fn quote(
231
+ endpoint_program: Pubkey,
232
+ accounts: &[AccountInfo],
233
+ params: QuoteParams,
234
+ ) -> Result<MessagingFee> {
235
+ validate(accounts, endpoint_program, QUOTE_MIN_ACCOUNTS_LEN)?;
236
+
237
+ let cpi_accounts = Quote {
238
+ send_library_program: accounts[1].clone(),
239
+ send_library_config: accounts[2].clone(),
240
+ default_send_library_config: accounts[3].clone(),
241
+ send_library_info: accounts[4].clone(),
242
+ endpoint: accounts[5].clone(),
243
+ nonce: accounts[6].clone(),
244
+ };
245
+ let result =
246
+ endpoint::cpi::quote(make_cpi_ctx(accounts, cpi_accounts, QUOTE_MIN_ACCOUNTS_LEN), params)?;
247
+ Ok(result.get())
248
+ }
249
+
250
+ /// Marks an inbound message as received by clearing it from the Endpoint.
251
+ ///
252
+ /// Called during `lz_receive` processing to acknowledge that the OApp has consumed the
253
+ /// message payload for a given (srcEid, sender, nonce) tuple. This advances the
254
+ /// inbound nonce and allows subsequent messages to be delivered.
255
+ ///
256
+ /// # Accounts layout
257
+ /// The `accounts` slice must follow the `Clear` struct field order:
258
+ ///
259
+ /// 0. `endpoint_program`
260
+ /// 1. `signer`
261
+ /// 2. `oapp_registry`
262
+ /// 3. `nonce`
263
+ /// 4. `payload_hash`
264
+ /// 5. `endpoint`
265
+ /// 6. `event_authority`
266
+ /// 7. `program`
267
+ /// 8. remaining accounts (variable length)
268
+ ///
269
+ /// # Accounts validation
270
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
271
+ /// - `accounts[1]` is the `signer` — pinned to `receiver`. The OApp PDA path signs via `seeds`; the
272
+ /// delegate path requires the delegate to already be a signer. The Endpoint accepts either the
273
+ /// OApp PDA (`params.receiver`) or the registered delegate as `signer`.
274
+ ///
275
+ /// # Returns
276
+ /// The payload hash (`[u8; 32]`) of the cleared message.
277
+ pub fn clear(
278
+ endpoint_program: Pubkey,
279
+ receiver: Pubkey,
280
+ accounts: &[AccountInfo],
281
+ seeds: &[&[u8]],
282
+ params: ClearParams,
283
+ ) -> Result<[u8; 32]> {
284
+ validate(accounts, endpoint_program, CLEAR_MIN_ACCOUNTS_LEN)?;
285
+ require_keys_eq!(accounts[1].key(), receiver, ErrorCode::ConstraintAddress);
286
+
287
+ let cpi_accounts = Clear {
288
+ signer: accounts[1].clone(),
289
+ oapp_registry: accounts[2].clone(),
290
+ nonce: accounts[3].clone(),
291
+ payload_hash: accounts[4].clone(),
292
+ endpoint: accounts[5].clone(),
293
+ event_authority: accounts[6].clone(),
294
+ program: accounts[7].clone(),
295
+ };
296
+ let result = endpoint::cpi::clear(
297
+ make_cpi_ctx(accounts, cpi_accounts, CLEAR_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
298
+ params,
299
+ )?;
300
+ Ok(result.get())
301
+ }
302
+
303
+ /// Sends a composed message to another program on the same chain.
304
+ ///
305
+ /// Composition allows an OApp to trigger follow-up logic in a separate program after
306
+ /// receiving a cross-chain message. The Endpoint stores the composed message hash,
307
+ /// and the target program's `lz_compose` handler is invoked to process it.
308
+ ///
309
+ /// # Accounts layout
310
+ /// The `accounts` slice must follow the `SendCompose` struct field order:
311
+ ///
312
+ /// 0. `endpoint_program`
313
+ /// 1. `from`
314
+ /// 2. `payer`
315
+ /// 3. `compose_message`
316
+ /// 4. `system_program`
317
+ /// 5. `event_authority`
318
+ /// 6. `program`
319
+ /// 7. remaining accounts (variable length)
320
+ ///
321
+ /// # Accounts validation
322
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
323
+ /// - `accounts[1]` must match the provided `from` key (the sending OApp PDA).
324
+ pub fn send_compose(
325
+ endpoint_program: Pubkey,
326
+ from: Pubkey,
327
+ accounts: &[AccountInfo],
328
+ seeds: &[&[u8]],
329
+ params: SendComposeParams,
330
+ ) -> Result<()> {
331
+ validate(accounts, endpoint_program, SEND_COMPOSE_MIN_ACCOUNTS_LEN)?;
332
+ require_keys_eq!(accounts[1].key(), from, ErrorCode::ConstraintAddress);
333
+
334
+ let cpi_accounts = SendCompose {
335
+ from: accounts[1].clone(),
336
+ payer: accounts[2].clone(),
337
+ compose_message: accounts[3].clone(),
338
+ system_program: accounts[4].clone(),
339
+ event_authority: accounts[5].clone(),
340
+ program: accounts[6].clone(),
341
+ };
342
+ endpoint::cpi::send_compose(
343
+ make_cpi_ctx(accounts, cpi_accounts, SEND_COMPOSE_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
344
+ params,
345
+ )
346
+ }
347
+
348
+ /// Clears (acknowledges) a composed message after processing.
349
+ ///
350
+ /// Called by the target program's `lz_compose` handler after it has consumed the
351
+ /// composed message. This marks the composition as complete in the Endpoint.
352
+ ///
353
+ /// # Accounts layout
354
+ /// The `accounts` slice must follow the `ClearCompose` struct field order:
355
+ ///
356
+ /// 0. `endpoint_program`
357
+ /// 1. `to`
358
+ /// 2. `compose_message`
359
+ /// 3. `event_authority`
360
+ /// 4. `program`
361
+ /// 5. remaining accounts (variable length)
362
+ ///
363
+ /// # Accounts validation
364
+ /// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
365
+ /// - `accounts[1]` must match the provided `to` key (the receiving program's PDA).
366
+ pub fn clear_compose(
367
+ endpoint_program: Pubkey,
368
+ to: Pubkey,
369
+ accounts: &[AccountInfo],
370
+ seeds: &[&[u8]],
371
+ params: ClearComposeParams,
372
+ ) -> Result<()> {
373
+ validate(accounts, endpoint_program, CLEAR_COMPOSE_MIN_ACCOUNTS_LEN)?;
374
+ require_keys_eq!(accounts[1].key(), to, ErrorCode::ConstraintAddress);
375
+
376
+ let cpi_accounts = ClearCompose {
377
+ to: accounts[1].clone(),
378
+ compose_message: accounts[2].clone(),
379
+ event_authority: accounts[3].clone(),
380
+ program: accounts[4].clone(),
381
+ };
382
+ endpoint::cpi::clear_compose(
383
+ make_cpi_ctx(accounts, cpi_accounts, CLEAR_COMPOSE_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
384
+ params,
385
+ )
386
+ }
package/src/errors.rs ADDED
@@ -0,0 +1,16 @@
1
+ use anchor_lang::error_code;
2
+
3
+ /// Custom error codes for OApp operations
4
+ #[error_code]
5
+ pub enum OAppError {
6
+ #[msg("Invalid address lookup table")]
7
+ InvalidAddressLookupTable,
8
+ #[msg("Invalid options")]
9
+ InvalidOptions,
10
+ #[msg("Invalid options length")]
11
+ InvalidOptionsLength,
12
+ #[msg("No peer set for the given EID")]
13
+ NoPeer,
14
+ #[msg("TLV update would change entry size")]
15
+ TlvLengthMismatch,
16
+ }
package/src/events.rs ADDED
@@ -0,0 +1,26 @@
1
+ use anchor_lang::prelude::*;
2
+
3
+ /// Emitted when a peer is set for a destination endpoint.
4
+ ///
5
+ /// `oapp` is the OApp-store PDA this peer entry belongs to. A single OApp
6
+ /// program manages many independent OApp-store instances (mirroring how the
7
+ /// SPL Token program manages many `Mint` accounts), so the event carries the
8
+ /// store pubkey to let off-chain consumers attribute each change to the right
9
+ /// instance.
10
+ #[event]
11
+ pub struct PeerSet {
12
+ pub oapp: Pubkey,
13
+ pub eid: u32,
14
+ pub peer: [u8; 32],
15
+ }
16
+
17
+ /// Emitted when enforced options are set for a destination endpoint and message type.
18
+ ///
19
+ /// See [`PeerSet`] for the rationale of the `oapp` field.
20
+ #[event]
21
+ pub struct EnforcedOptionsSet {
22
+ pub oapp: Pubkey,
23
+ pub eid: u32,
24
+ pub msg_type: u16,
25
+ pub options: Vec<u8>,
26
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,82 @@
1
+ //! LayerZero OApp SDK for Solana.
2
+ //!
3
+ //! Provides the core building blocks for Solana programs that integrate with the
4
+ //! LayerZero V2 cross-chain messaging protocol as an Omnichain Application (OApp).
5
+ //!
6
+ //! # What's included
7
+ //!
8
+ //! - **PDA seeds** – canonical constants for deriving all protocol-related accounts (peers,
9
+ //! enforced options, nonces, payload hashes, etc.).
10
+ //! - **Endpoint CPI helpers** – thin wrappers around cross-program invocations to the LayerZero
11
+ //! Endpoint program (send, quote, compose, etc.).
12
+ //! - **Account macros** – re-exported from [`oapp_macros`] to generate boilerplate Anchor account
13
+ //! structs for standard OApp instructions.
14
+ //! - **Types & traits** – shared parameter / return structs, the [`OAppState`] trait for PDA signer
15
+ //! seeds, and messaging option builders.
16
+
17
+ pub mod common;
18
+ pub mod endpoint_cpi;
19
+ pub mod events;
20
+ pub mod lz_compose_types_v2;
21
+ pub mod lz_receive_types_v2;
22
+ pub mod oapp_info;
23
+ pub mod options;
24
+ pub mod types;
25
+ pub mod utils;
26
+
27
+ mod errors;
28
+ use anchor_lang::prelude::*;
29
+ // `idls/endpoint.json` is vendored here because `declare_program!` reads the
30
+ // IDL at compile time, and downstream consumers of this crate cannot rely on
31
+ // a workspace-relative path to the Endpoint program's own build output.
32
+ declare_program!(endpoint);
33
+
34
+ pub use errors::*;
35
+
36
+ // Re-export procedural macros so consumers can `use oapp::oapp`
37
+ pub use oapp_macros::*;
38
+
39
+ // OApp state & config
40
+ pub const OAPP_SEED: &[u8] = b"OApp";
41
+ pub const OAPP_INFO_SEED: &[u8] = b"OAppInfo";
42
+ pub const PEER_SEED: &[u8] = b"Peer";
43
+ pub const ENFORCED_OPTIONS_SEED: &[u8] = b"EnforcedOptions";
44
+
45
+ // Messaging
46
+ pub const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes";
47
+ pub const LZ_RECEIVE_TYPES_SEED: &[u8] = b"LzReceiveTypes";
48
+ pub const NONCE_SEED: &[u8] = b"Nonce";
49
+ pub const PENDING_NONCE_SEED: &[u8] = b"PendingNonce";
50
+ pub const PAYLOAD_HASH_SEED: &[u8] = b"PayloadHash";
51
+ pub const COMPOSED_MESSAGE_HASH_SEED: &[u8] = b"ComposedMessageHash";
52
+
53
+ // Endpoint & library config
54
+ pub const ENDPOINT_SEED: &[u8] = b"Endpoint";
55
+ pub const MESSAGE_LIB_SEED: &[u8] = b"MessageLib";
56
+ pub const SEND_LIBRARY_CONFIG_SEED: &[u8] = b"SendLibraryConfig";
57
+ pub const RECEIVE_LIBRARY_CONFIG_SEED: &[u8] = b"ReceiveLibraryConfig";
58
+
59
+ // Event
60
+ pub const EVENT_SEED: &[u8] = b"__event_authority";
61
+
62
+ // PDA seed helpers below use `Box::leak` to produce the `&'static [&'static [u8]]`
63
+ // shape required by Anchor's `#[account(seeds = ...)]` attribute. Each call leaks
64
+ // ~64 bytes. **SVM-only**: harmless on-chain because the BPF VM allocator is torn
65
+ // down at the end of every transaction, so leaks never accumulate. Do not call
66
+ // these from long-running off-chain Rust (native test runners, indexers, CLIs) —
67
+ // derive PDAs with stack-local arrays in those contexts instead.
68
+
69
+ /// PDA seeds for an OApp's `lz_receive_types_accounts` PDA.
70
+ /// Seed shape: [LZ_RECEIVE_TYPES_SEED, oapp_key]
71
+ pub fn lz_receive_types_seeds(oapp_key: &Pubkey) -> &'static [&'static [u8]] {
72
+ let key_bytes: &'static [u8; 32] = Box::leak(Box::new(oapp_key.to_bytes()));
73
+ Box::leak(Box::new([LZ_RECEIVE_TYPES_SEED, key_bytes.as_slice()]))
74
+ }
75
+
76
+ /// Trait for OApp state types.
77
+ ///
78
+ /// Defines the minimal interface for PDA signer seeds used in CPI invocations.
79
+ /// `N` is the number of seed segments (e.g. 3 for `[PREFIX, key, bump]`).
80
+ pub trait OAppState<const N: usize> {
81
+ fn signer_seeds(&self) -> [&[u8]; N];
82
+ }
@@ -0,0 +1,88 @@
1
+ use crate::{common::AccountMetaRef, COMPOSED_MESSAGE_HASH_SEED, EVENT_SEED};
2
+ use anchor_lang::prelude::*;
3
+ use solana_keccak_hasher::hash;
4
+
5
+ /// Version identifier returned by `lz_compose_types_info` for the V2 flow.
6
+ pub const LZ_COMPOSE_TYPES_VERSION: u8 = 2;
7
+
8
+ /// Payload returned from `lz_compose_types_info` when the version is V2.
9
+ ///
10
+ /// The executor uses these accounts to construct the follow-up
11
+ /// `lz_compose_types_v2` call.
12
+ #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
13
+ pub struct LzComposeTypesV2Accounts {
14
+ pub accounts: Vec<Pubkey>,
15
+ }
16
+
17
+ /// Output of the lz_compose_types_v2 instruction.
18
+ ///
19
+ /// This structure enables the multi-instruction execution model where composers
20
+ /// can define multiple instructions to be executed atomically by the Executor.
21
+ /// The Executor constructs a single transaction containing all returned instructions.
22
+ #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
23
+ pub struct LzComposeTypesV2Result {
24
+ /// The version of context account
25
+ pub context_version: u8,
26
+ /// ALTs required for this execution context
27
+ /// Used by the Executor to resolve AltIndex references in AccountMetaRef
28
+ pub alts: Vec<Pubkey>,
29
+ /// The complete list of instructions required for LzCompose execution
30
+ /// MUST include exactly one LzCompose instruction
31
+ /// MAY include additional Standard instructions for preprocessing/postprocessing
32
+ /// Instructions are executed in the order returned
33
+ pub instructions: Vec<ComposeInstruction>,
34
+ }
35
+
36
+ /// The list of instructions that can be executed in the LzCompose transaction.
37
+ #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
38
+ pub enum ComposeInstruction {
39
+ /// The main LzCompose instruction (exactly one required per transaction)
40
+ /// This instruction processes the incoming composed message
41
+ LzCompose {
42
+ /// Account list for the lz_compose instruction
43
+ /// Uses AddressLocator for flexible address resolution
44
+ accounts: Vec<AccountMetaRef>,
45
+ },
46
+ /// Arbitrary custom instruction for preprocessing/postprocessing
47
+ Standard {
48
+ /// Target program ID for the custom instruction
49
+ program_id: Pubkey,
50
+ /// Account list for the custom instruction
51
+ accounts: Vec<AccountMetaRef>,
52
+ /// Instruction data payload
53
+ data: Vec<u8>,
54
+ },
55
+ }
56
+
57
+ /// V2 version of get_accounts_for_clear_compose that returns AccountMetaRef
58
+ pub fn get_accounts_for_clear_compose(
59
+ endpoint_program: Pubkey,
60
+ from: &Pubkey,
61
+ to: &Pubkey,
62
+ guid: &[u8; 32],
63
+ index: u16,
64
+ composed_message: &[u8],
65
+ ) -> Vec<AccountMetaRef> {
66
+ let (composed_message_account, _) = Pubkey::find_program_address(
67
+ &[
68
+ COMPOSED_MESSAGE_HASH_SEED,
69
+ &from.to_bytes(),
70
+ &to.to_bytes(),
71
+ &guid[..],
72
+ &index.to_be_bytes(),
73
+ &hash(composed_message).to_bytes(),
74
+ ],
75
+ &endpoint_program,
76
+ );
77
+
78
+ let (event_authority_account, _) =
79
+ Pubkey::find_program_address(&[EVENT_SEED], &endpoint_program);
80
+
81
+ vec![
82
+ AccountMetaRef { pubkey: endpoint_program.into(), is_writable: false },
83
+ AccountMetaRef { pubkey: (*to).into(), is_writable: false },
84
+ AccountMetaRef { pubkey: composed_message_account.into(), is_writable: true },
85
+ AccountMetaRef { pubkey: event_authority_account.into(), is_writable: false },
86
+ AccountMetaRef { pubkey: endpoint_program.into(), is_writable: false },
87
+ ]
88
+ }