diff --git a/examples/oapp-solana/README.md b/examples/oapp-solana/README.md index 66df439d6..e8f19f638 100644 --- a/examples/oapp-solana/README.md +++ b/examples/oapp-solana/README.md @@ -299,6 +299,26 @@ npx hardhat --network arbitrum-sepolia lz:oapp:send --from-eid 40231 --dst-eid 4 Congratulations, you have now successfully set up an EVM <> Solana OApp. +### Viewing Sent Strings + +After sending cross-chain messages you can inspect the stored string on either side directly from the terminal: + +- Solana store account: + + ```bash + npx hardhat lz:oapp:solana:debug --eid 40168 --action store + ``` + + Use the Solana endpoint ID that matches your environment (e.g., `40168` for Devnet, `30168` for Mainnet) and optionally pass `--store ` if you need to override the derived PDA. + +- EVM contract storage: + + ```bash + npx hardhat lz:oapp:evm:debug --network arbitrum-sepolia --contract-name MyOApp + ``` + + Switch `--network` to the EVM chain you deployed to and supply a different `--contract-name` if your deployment artifact uses another name. The task performs a read-only call to the `data()` getter and prints the latest received message. + ### Running tests The `test` command will execute the hardhat and forge tests: diff --git a/examples/oapp-solana/layerzero.config.ts b/examples/oapp-solana/layerzero.config.ts index 9c24d632c..bad0ae470 100644 --- a/examples/oapp-solana/layerzero.config.ts +++ b/examples/oapp-solana/layerzero.config.ts @@ -38,14 +38,14 @@ const SOLANA_ENFORCED_OPTIONS: OAppEnforcedOption[] = [ // Arbitrum <-> Solana // With the config generator, pathways declared are automatically bidirectional -// i.e. if you declare A,B there's no need to declare B,A +// i.e. if you declare Arbitrum,Solana there's no need to declare Solana,Arbitrum const pathways: TwoWayConfig[] = [ [ - arbitrumContract, // Chain A contract - solanaContract, // Chain B contract + arbitrumContract, // Arbitrum contract + solanaContract, // Solana contract [['LayerZero Labs'], []], // [ requiredDVN[], [ optionalDVN[], threshold ] ] - [1, 32], // [A to B confirmations, B to A confirmations] - [SOLANA_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Chain B enforcedOptions, Chain A enforcedOptions + [20, 32], // [Arbitrum to Solana outbound confirmations, Solana to Arbitrum outbound confirmations] + [SOLANA_ENFORCED_OPTIONS, EVM_ENFORCED_OPTIONS], // Arbitrum to Solana enforcedOptions, Solana to Arbitrum enforcedOptions ], ] diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/endpointSettings.ts b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/endpointSettings.ts index 0e2d2c4af..632449de6 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/endpointSettings.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/endpointSettings.ts @@ -64,10 +64,7 @@ export function getEndpointSettingsAccountDataSerializer(): Serializer< ], { description: 'EndpointSettingsAccountData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([221, 232, 73, 56, 10, 66, 72, 14]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([221, 232, 73, 56, 10, 66, 72, 14]) }) ) as Serializer } @@ -124,7 +121,7 @@ export async function safeFetchAllEndpointSettings( } export function getEndpointSettingsGpaBuilder(context: Pick) { - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') return gpaBuilder(context, programId) .registerFields<{ discriminator: Uint8Array diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/lzReceiveTypesAccounts.ts b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/lzReceiveTypesAccounts.ts index e22ebdbdc..1cfdc3200 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/lzReceiveTypesAccounts.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/lzReceiveTypesAccounts.ts @@ -25,17 +25,19 @@ import { mapSerializer, publicKey as publicKeySerializer, struct, + u8, } from '@metaplex-foundation/umi/serializers' -/** LzReceiveTypesAccounts includes accounts that are used in the LzReceiveTypes instruction. */ export type LzReceiveTypesAccounts = Account export type LzReceiveTypesAccountsAccountData = { discriminator: Uint8Array store: PublicKey + alt: PublicKey + bump: number } -export type LzReceiveTypesAccountsAccountDataArgs = { store: PublicKey } +export type LzReceiveTypesAccountsAccountDataArgs = { store: PublicKey; alt: PublicKey; bump: number } export function getLzReceiveTypesAccountsAccountDataSerializer(): Serializer< LzReceiveTypesAccountsAccountDataArgs, @@ -46,13 +48,12 @@ export function getLzReceiveTypesAccountsAccountDataSerializer(): Serializer< [ ['discriminator', bytes({ size: 8 })], ['store', publicKeySerializer()], + ['alt', publicKeySerializer()], + ['bump', u8()], ], { description: 'LzReceiveTypesAccountsAccountData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([248, 87, 167, 117, 5, 251, 21, 126]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([248, 87, 167, 117, 5, 251, 21, 126]) }) ) as Serializer } @@ -109,16 +110,18 @@ export async function safeFetchAllLzReceiveTypesAccounts( } export function getLzReceiveTypesAccountsGpaBuilder(context: Pick) { - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') return gpaBuilder(context, programId) - .registerFields<{ discriminator: Uint8Array; store: PublicKey }>({ + .registerFields<{ discriminator: Uint8Array; store: PublicKey; alt: PublicKey; bump: number }>({ discriminator: [0, bytes({ size: 8 })], store: [8, publicKeySerializer()], + alt: [40, publicKeySerializer()], + bump: [72, u8()], }) .deserializeUsing((account) => deserializeLzReceiveTypesAccounts(account)) .whereField('discriminator', new Uint8Array([248, 87, 167, 117, 5, 251, 21, 126])) } export function getLzReceiveTypesAccountsSize(): number { - return 40 + return 73 } diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/peerConfig.ts b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/peerConfig.ts index c8cf3b6ea..3614f6db3 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/peerConfig.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/peerConfig.ts @@ -31,11 +31,7 @@ export type PeerConfigAccountData = { bump: number } -export type PeerConfigAccountDataArgs = { - peerAddress: Uint8Array - enforcedOptions: EnforcedOptionsArgs - bump: number -} +export type PeerConfigAccountDataArgs = { peerAddress: Uint8Array; enforcedOptions: EnforcedOptionsArgs; bump: number } export function getPeerConfigAccountDataSerializer(): Serializer { return mapSerializer( @@ -48,10 +44,7 @@ export function getPeerConfigAccountDataSerializer(): Serializer ({ - ...value, - discriminator: new Uint8Array([181, 157, 86, 198, 33, 193, 94, 203]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([181, 157, 86, 198, 33, 193, 94, 203]) }) ) as Serializer } @@ -108,7 +101,7 @@ export async function safeFetchAllPeerConfig( } export function getPeerConfigGpaBuilder(context: Pick) { - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') return gpaBuilder(context, programId) .registerFields<{ discriminator: Uint8Array diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/store.ts b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/store.ts index 2ba28e4e3..645241147 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/accounts/store.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/accounts/store.ts @@ -39,12 +39,7 @@ export type StoreAccountData = { string: string } -export type StoreAccountDataArgs = { - admin: PublicKey - bump: number - endpointProgram: PublicKey - string: string -} +export type StoreAccountDataArgs = { admin: PublicKey; bump: number; endpointProgram: PublicKey; string: string } export function getStoreAccountDataSerializer(): Serializer { return mapSerializer( @@ -58,10 +53,7 @@ export function getStoreAccountDataSerializer(): Serializer ({ - ...value, - discriminator: new Uint8Array([130, 48, 247, 244, 182, 191, 30, 26]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([130, 48, 247, 244, 182, 191, 30, 26]) }) ) as Serializer } @@ -118,7 +110,7 @@ export async function safeFetchAllStore( } export function getStoreGpaBuilder(context: Pick) { - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') return gpaBuilder(context, programId) .registerFields<{ discriminator: Uint8Array diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/errors/myOapp.ts b/examples/oapp-solana/lib/client/generated/my_oapp/errors/myOapp.ts index 7296cf805..03e8bef9f 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/errors/myOapp.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/errors/myOapp.ts @@ -12,9 +12,9 @@ type ProgramErrorConstructor = new (program: Program, cause?: Error) => ProgramE const codeToErrorMap: Map = new Map() const nameToErrorMap: Map = new Map() -/** InvalidMessageType */ -export class InvalidMessageTypeError extends ProgramError { - override readonly name: string = 'InvalidMessageType' +/** InvalidLength: */ +export class InvalidLengthError extends ProgramError { + override readonly name: string = 'InvalidLength' readonly code: number = 0x1770 // 6000 @@ -22,8 +22,34 @@ export class InvalidMessageTypeError extends ProgramError { super('', program, cause) } } -codeToErrorMap.set(0x1770, InvalidMessageTypeError) -nameToErrorMap.set('InvalidMessageType', InvalidMessageTypeError) +codeToErrorMap.set(0x1770, InvalidLengthError) +nameToErrorMap.set('InvalidLength', InvalidLengthError) + +/** BodyTooShort: */ +export class BodyTooShortError extends ProgramError { + override readonly name: string = 'BodyTooShort' + + readonly code: number = 0x1771 // 6001 + + constructor(program: Program, cause?: Error) { + super('', program, cause) + } +} +codeToErrorMap.set(0x1771, BodyTooShortError) +nameToErrorMap.set('BodyTooShort', BodyTooShortError) + +/** InvalidUtf8: */ +export class InvalidUtf8Error extends ProgramError { + override readonly name: string = 'InvalidUtf8' + + readonly code: number = 0x1772 // 6002 + + constructor(program: Program, cause?: Error) { + super('', program, cause) + } +} +codeToErrorMap.set(0x1772, InvalidUtf8Error) +nameToErrorMap.set('InvalidUtf8', InvalidUtf8Error) /** * Attempts to resolve a custom program error from the provided error code. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/index.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/index.ts index 148e47ba1..0668265aa 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/index.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/index.ts @@ -8,7 +8,8 @@ export * from './initStore' export * from './lzReceive' -export * from './lzReceiveTypes' +export * from './lzReceiveTypesInfo' +export * from './lzReceiveTypesV2' export * from './quoteSend' export * from './send' export * from './setPeerConfig' diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/initStore.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/initStore.ts index 0d9c61325..2cbe31027 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/initStore.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/initStore.ts @@ -21,20 +21,14 @@ export type InitStoreInstructionAccounts = { payer?: Signer store: PublicKey | Pda lzReceiveTypesAccounts: PublicKey | Pda + alt?: PublicKey | Pda systemProgram?: PublicKey | Pda } // Data. -export type InitStoreInstructionData = { - discriminator: Uint8Array - admin: PublicKey - endpoint: PublicKey -} +export type InitStoreInstructionData = { discriminator: Uint8Array; admin: PublicKey; endpoint: PublicKey } -export type InitStoreInstructionDataArgs = { - admin: PublicKey - endpoint: PublicKey -} +export type InitStoreInstructionDataArgs = { admin: PublicKey; endpoint: PublicKey } export function getInitStoreInstructionDataSerializer(): Serializer< InitStoreInstructionDataArgs, @@ -49,10 +43,7 @@ export function getInitStoreInstructionDataSerializer(): Serializer< ], { description: 'InitStoreInstructionData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([250, 74, 6, 95, 163, 188, 19, 181]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([250, 74, 6, 95, 163, 188, 19, 181]) }) ) as Serializer } @@ -65,30 +56,15 @@ export function initStore( input: InitStoreInstructionAccounts & InitStoreInstructionArgs ): TransactionBuilder { // Program ID. - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') // Accounts. const resolvedAccounts = { - payer: { - index: 0, - isWritable: true as boolean, - value: input.payer ?? null, - }, - store: { - index: 1, - isWritable: true as boolean, - value: input.store ?? null, - }, - lzReceiveTypesAccounts: { - index: 2, - isWritable: true as boolean, - value: input.lzReceiveTypesAccounts ?? null, - }, - systemProgram: { - index: 3, - isWritable: false as boolean, - value: input.systemProgram ?? null, - }, + payer: { index: 0, isWritable: true as boolean, value: input.payer ?? null }, + store: { index: 1, isWritable: true as boolean, value: input.store ?? null }, + lzReceiveTypesAccounts: { index: 2, isWritable: true as boolean, value: input.lzReceiveTypesAccounts ?? null }, + alt: { index: 3, isWritable: false as boolean, value: input.alt ?? null }, + systemProgram: { index: 4, isWritable: false as boolean, value: input.systemProgram ?? null }, } satisfies ResolvedAccountsWithIndices // Arguments. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceive.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceive.ts index 863ba4eae..5201a9e7d 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceive.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceive.ts @@ -13,15 +13,19 @@ import { LzReceiveParams, LzReceiveParamsArgs, getLzReceiveParamsSerializer } fr // Accounts. export type LzReceiveInstructionAccounts = { + /** + * OApp Store PDA. This account represents the "address" of your OApp on + * Solana and can contain any state relevant to your application. + * Customize the fields in `Store` as needed. + */ + store: PublicKey | Pda + /** Peer config PDA for the sending chain. Ensures `params.sender` can only be the allowed peer from that remote chain. */ peer: PublicKey | Pda } // Data. -export type LzReceiveInstructionData = { - discriminator: Uint8Array - params: LzReceiveParams -} +export type LzReceiveInstructionData = { discriminator: Uint8Array; params: LzReceiveParams } export type LzReceiveInstructionDataArgs = { params: LzReceiveParamsArgs } @@ -37,10 +41,7 @@ export function getLzReceiveInstructionDataSerializer(): Serializer< ], { description: 'LzReceiveInstructionData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([8, 179, 120, 109, 33, 118, 189, 80]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([8, 179, 120, 109, 33, 118, 189, 80]) }) ) as Serializer } @@ -53,15 +54,11 @@ export function lzReceive( input: LzReceiveInstructionAccounts & LzReceiveInstructionArgs ): TransactionBuilder { // Program ID. - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') // Accounts. const resolvedAccounts = { - store: { - index: 0, - isWritable: true as boolean, - value: input.store ?? null, - }, + store: { index: 0, isWritable: true as boolean, value: input.store ?? null }, peer: { index: 1, isWritable: false as boolean, value: input.peer ?? null }, } satisfies ResolvedAccountsWithIndices diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypesInfo.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypesInfo.ts new file mode 100644 index 000000000..695e91b44 --- /dev/null +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypesInfo.ts @@ -0,0 +1,81 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { Context, Pda, PublicKey, TransactionBuilder, transactionBuilder } from '@metaplex-foundation/umi' +import { Serializer, bytes, mapSerializer, struct } from '@metaplex-foundation/umi/serializers' +import { ResolvedAccount, ResolvedAccountsWithIndices, getAccountMetasAndSigners } from '../shared' +import { LzReceiveParams, LzReceiveParamsArgs, getLzReceiveParamsSerializer } from '../types' + +// Accounts. +export type LzReceiveTypesInfoInstructionAccounts = { + store: PublicKey | Pda + /** + * PDA account containing the versioned data structure for V2 + * Contains the accounts needed to construct lz_receive_types_v2 instruction + */ + + lzReceiveTypesAccounts: PublicKey | Pda +} + +// Data. +export type LzReceiveTypesInfoInstructionData = { discriminator: Uint8Array; params: LzReceiveParams } + +export type LzReceiveTypesInfoInstructionDataArgs = { params: LzReceiveParamsArgs } + +export function getLzReceiveTypesInfoInstructionDataSerializer(): Serializer< + LzReceiveTypesInfoInstructionDataArgs, + LzReceiveTypesInfoInstructionData +> { + return mapSerializer( + struct( + [ + ['discriminator', bytes({ size: 8 })], + ['params', getLzReceiveParamsSerializer()], + ], + { description: 'LzReceiveTypesInfoInstructionData' } + ), + (value) => ({ ...value, discriminator: new Uint8Array([43, 148, 213, 93, 101, 127, 37, 170]) }) + ) as Serializer +} + +// Args. +export type LzReceiveTypesInfoInstructionArgs = LzReceiveTypesInfoInstructionDataArgs + +// Instruction. +export function lzReceiveTypesInfo( + context: Pick, + input: LzReceiveTypesInfoInstructionAccounts & LzReceiveTypesInfoInstructionArgs +): TransactionBuilder { + // Program ID. + const programId = context.programs.getPublicKey('myOapp', '') + + // Accounts. + const resolvedAccounts = { + store: { index: 0, isWritable: false as boolean, value: input.store ?? null }, + lzReceiveTypesAccounts: { index: 1, isWritable: false as boolean, value: input.lzReceiveTypesAccounts ?? null }, + } satisfies ResolvedAccountsWithIndices + + // Arguments. + const resolvedArgs: LzReceiveTypesInfoInstructionArgs = { ...input } + + // Accounts in order. + const orderedAccounts: ResolvedAccount[] = Object.values(resolvedAccounts).sort((a, b) => a.index - b.index) + + // Keys and Signers. + const [keys, signers] = getAccountMetasAndSigners(orderedAccounts, 'programId', programId) + + // Data. + const data = getLzReceiveTypesInfoInstructionDataSerializer().serialize( + resolvedArgs as LzReceiveTypesInfoInstructionDataArgs + ) + + // Bytes Created On Chain. + const bytesCreatedOnChain = 0 + + return transactionBuilder([{ instruction: { keys, programId, data }, signers, bytesCreatedOnChain }]) +} diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypes.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypesV2.ts similarity index 51% rename from examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypes.ts rename to examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypesV2.ts index 9de0e5d9d..0566ed2c8 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypes.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/lzReceiveTypesV2.ts @@ -12,59 +12,49 @@ import { ResolvedAccount, ResolvedAccountsWithIndices, getAccountMetasAndSigners import { LzReceiveParams, LzReceiveParamsArgs, getLzReceiveParamsSerializer } from '../types' // Accounts. -export type LzReceiveTypesInstructionAccounts = { +export type LzReceiveTypesV2InstructionAccounts = { store: PublicKey | Pda } // Data. -export type LzReceiveTypesInstructionData = { - discriminator: Uint8Array - params: LzReceiveParams -} +export type LzReceiveTypesV2InstructionData = { discriminator: Uint8Array; params: LzReceiveParams } -export type LzReceiveTypesInstructionDataArgs = { params: LzReceiveParamsArgs } +export type LzReceiveTypesV2InstructionDataArgs = { params: LzReceiveParamsArgs } -export function getLzReceiveTypesInstructionDataSerializer(): Serializer< - LzReceiveTypesInstructionDataArgs, - LzReceiveTypesInstructionData +export function getLzReceiveTypesV2InstructionDataSerializer(): Serializer< + LzReceiveTypesV2InstructionDataArgs, + LzReceiveTypesV2InstructionData > { - return mapSerializer( - struct( + return mapSerializer( + struct( [ ['discriminator', bytes({ size: 8 })], ['params', getLzReceiveParamsSerializer()], ], - { description: 'LzReceiveTypesInstructionData' } + { description: 'LzReceiveTypesV2InstructionData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([221, 17, 246, 159, 248, 128, 31, 96]), - }) - ) as Serializer + (value) => ({ ...value, discriminator: new Uint8Array([109, 157, 200, 142, 138, 223, 159, 164]) }) + ) as Serializer } // Args. -export type LzReceiveTypesInstructionArgs = LzReceiveTypesInstructionDataArgs +export type LzReceiveTypesV2InstructionArgs = LzReceiveTypesV2InstructionDataArgs // Instruction. -export function lzReceiveTypes( +export function lzReceiveTypesV2( context: Pick, - input: LzReceiveTypesInstructionAccounts & LzReceiveTypesInstructionArgs + input: LzReceiveTypesV2InstructionAccounts & LzReceiveTypesV2InstructionArgs ): TransactionBuilder { // Program ID. - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') // Accounts. const resolvedAccounts = { - store: { - index: 0, - isWritable: false as boolean, - value: input.store ?? null, - }, + store: { index: 0, isWritable: false as boolean, value: input.store ?? null }, } satisfies ResolvedAccountsWithIndices // Arguments. - const resolvedArgs: LzReceiveTypesInstructionArgs = { ...input } + const resolvedArgs: LzReceiveTypesV2InstructionArgs = { ...input } // Accounts in order. const orderedAccounts: ResolvedAccount[] = Object.values(resolvedAccounts).sort((a, b) => a.index - b.index) @@ -73,8 +63,8 @@ export function lzReceiveTypes( const [keys, signers] = getAccountMetasAndSigners(orderedAccounts, 'programId', programId) // Data. - const data = getLzReceiveTypesInstructionDataSerializer().serialize( - resolvedArgs as LzReceiveTypesInstructionDataArgs + const data = getLzReceiveTypesV2InstructionDataSerializer().serialize( + resolvedArgs as LzReceiveTypesV2InstructionDataArgs ) // Bytes Created On Chain. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/quoteSend.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/quoteSend.ts index d6f460d2c..5eb6bd92a 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/quoteSend.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/quoteSend.ts @@ -51,10 +51,7 @@ export function getQuoteSendInstructionDataSerializer(): Serializer< ], { description: 'QuoteSendInstructionData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([207, 0, 49, 214, 160, 211, 76, 211]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([207, 0, 49, 214, 160, 211, 76, 211]) }) ) as Serializer } @@ -67,21 +64,13 @@ export function quoteSend( input: QuoteSendInstructionAccounts & QuoteSendInstructionArgs ): TransactionBuilder { // Program ID. - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') // Accounts. const resolvedAccounts = { - store: { - index: 0, - isWritable: false as boolean, - value: input.store ?? null, - }, + store: { index: 0, isWritable: false as boolean, value: input.store ?? null }, peer: { index: 1, isWritable: false as boolean, value: input.peer ?? null }, - endpoint: { - index: 2, - isWritable: false as boolean, - value: input.endpoint ?? null, - }, + endpoint: { index: 2, isWritable: false as boolean, value: input.endpoint ?? null }, } satisfies ResolvedAccountsWithIndices // Arguments. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/send.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/send.ts index 005a63753..0be1ba0ae 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/send.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/send.ts @@ -12,7 +12,13 @@ import { ResolvedAccount, ResolvedAccountsWithIndices, getAccountMetasAndSigners // Accounts. export type SendInstructionAccounts = { + /** + * Configuration for the destination chain. Holds the peer address and any + * enforced messaging options. + */ + peer: PublicKey | Pda + /** OApp Store PDA that signs the send instruction */ store: PublicKey | Pda endpoint: PublicKey | Pda } @@ -48,10 +54,7 @@ export function getSendInstructionDataSerializer(): Serializer ({ - ...value, - discriminator: new Uint8Array([102, 251, 20, 187, 65, 75, 12, 69]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([102, 251, 20, 187, 65, 75, 12, 69]) }) ) as Serializer } @@ -64,21 +67,13 @@ export function send( input: SendInstructionAccounts & SendInstructionArgs ): TransactionBuilder { // Program ID. - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') // Accounts. const resolvedAccounts = { peer: { index: 0, isWritable: false as boolean, value: input.peer ?? null }, - store: { - index: 1, - isWritable: false as boolean, - value: input.store ?? null, - }, - endpoint: { - index: 2, - isWritable: false as boolean, - value: input.endpoint ?? null, - }, + store: { index: 1, isWritable: false as boolean, value: input.store ?? null }, + endpoint: { index: 2, isWritable: false as boolean, value: input.endpoint ?? null }, } satisfies ResolvedAccountsWithIndices // Arguments. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/setPeerConfig.ts b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/setPeerConfig.ts index 68fc3c594..e2dd6869e 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/instructions/setPeerConfig.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/instructions/setPeerConfig.ts @@ -13,23 +13,19 @@ import { PeerConfigParam, PeerConfigParamArgs, getPeerConfigParamSerializer } fr // Accounts. export type SetPeerConfigInstructionAccounts = { + /** Admin of the OApp store */ admin: Signer + /** Peer configuration PDA for a specific remote chain */ peer: PublicKey | Pda + /** Store PDA of this OApp */ store: PublicKey | Pda systemProgram?: PublicKey | Pda } // Data. -export type SetPeerConfigInstructionData = { - discriminator: Uint8Array - remoteEid: number - config: PeerConfigParam -} +export type SetPeerConfigInstructionData = { discriminator: Uint8Array; remoteEid: number; config: PeerConfigParam } -export type SetPeerConfigInstructionDataArgs = { - remoteEid: number - config: PeerConfigParamArgs -} +export type SetPeerConfigInstructionDataArgs = { remoteEid: number; config: PeerConfigParamArgs } export function getSetPeerConfigInstructionDataSerializer(): Serializer< SetPeerConfigInstructionDataArgs, @@ -44,10 +40,7 @@ export function getSetPeerConfigInstructionDataSerializer(): Serializer< ], { description: 'SetPeerConfigInstructionData' } ), - (value) => ({ - ...value, - discriminator: new Uint8Array([79, 187, 168, 57, 139, 140, 93, 47]), - }) + (value) => ({ ...value, discriminator: new Uint8Array([79, 187, 168, 57, 139, 140, 93, 47]) }) ) as Serializer } @@ -60,26 +53,14 @@ export function setPeerConfig( input: SetPeerConfigInstructionAccounts & SetPeerConfigInstructionArgs ): TransactionBuilder { // Program ID. - const programId = context.programs.getPublicKey('myOapp', 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay') + const programId = context.programs.getPublicKey('myOapp', '') // Accounts. const resolvedAccounts = { - admin: { - index: 0, - isWritable: true as boolean, - value: input.admin ?? null, - }, + admin: { index: 0, isWritable: true as boolean, value: input.admin ?? null }, peer: { index: 1, isWritable: true as boolean, value: input.peer ?? null }, - store: { - index: 2, - isWritable: false as boolean, - value: input.store ?? null, - }, - systemProgram: { - index: 3, - isWritable: false as boolean, - value: input.systemProgram ?? null, - }, + store: { index: 2, isWritable: false as boolean, value: input.store ?? null }, + systemProgram: { index: 3, isWritable: false as boolean, value: input.systemProgram ?? null }, } satisfies ResolvedAccountsWithIndices // Arguments. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/programs/myOapp.ts b/examples/oapp-solana/lib/client/generated/my_oapp/programs/myOapp.ts index d767c0131..8cc8afeb2 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/programs/myOapp.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/programs/myOapp.ts @@ -9,12 +9,11 @@ import { ClusterFilter, Context, Program, PublicKey } from '@metaplex-foundation/umi' import { getMyOappErrorFromCode, getMyOappErrorFromName } from '../errors' -export const MY_OAPP_PROGRAM_ID = - 'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay' as PublicKey<'HFyiETGKEUS9tr87K1HXmVJHkqQRtw8wShRNTMkKKxay'> +export const MY_OAPP_PROGRAM_ID = '' as PublicKey<''> export function createMyOappProgram(): Program { return { - name: 'myoapp', + name: 'myOapp', publicKey: MY_OAPP_PROGRAM_ID, getErrorFromCode(code: number, cause?: Error) { return getMyOappErrorFromCode(code, this, cause) diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/shared/index.ts b/examples/oapp-solana/lib/client/generated/my_oapp/shared/index.ts index 70554c289..4689b2826 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/shared/index.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/shared/index.ts @@ -51,10 +51,7 @@ export function expectPda(value: PublicKey | Pda | Signer | null | undefined): P * Defines an instruction account to resolve. * @internal */ -export type ResolvedAccount = { - isWritable: boolean - value: T -} +export type ResolvedAccount = { isWritable: boolean; value: T } /** * Defines a set of instruction account to resolve. diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/accountMetaRef.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/accountMetaRef.ts new file mode 100644 index 000000000..d7a990a26 --- /dev/null +++ b/examples/oapp-solana/lib/client/generated/my_oapp/types/accountMetaRef.ts @@ -0,0 +1,43 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { Serializer, bool, struct } from '@metaplex-foundation/umi/serializers' +import { AddressLocator, AddressLocatorArgs, getAddressLocatorSerializer } from '.' + +/** + * Account metadata for V2 execution planning. + * Used by the Executor to construct the final transaction. + * + * V2 removes the legacy is_signer flag from V1's AccountMeta. + * Instead, signer roles are explicitly declared through AddressLocator variants. + * This provides clearer semantics and enables multiple signer support. + */ + +export type AccountMetaRef = { + /** The account address locator - supports multiple resolution strategies */ + pubkey: AddressLocator + /** Whether the account should be writable in the final transaction */ + isWritable: boolean +} + +export type AccountMetaRefArgs = { + /** The account address locator - supports multiple resolution strategies */ + pubkey: AddressLocatorArgs + /** Whether the account should be writable in the final transaction */ + isWritable: boolean +} + +export function getAccountMetaRefSerializer(): Serializer { + return struct( + [ + ['pubkey', getAddressLocatorSerializer()], + ['isWritable', bool()], + ], + { description: 'AccountMetaRef' } + ) as Serializer +} diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/addressLocator.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/addressLocator.ts new file mode 100644 index 000000000..5ef83846a --- /dev/null +++ b/examples/oapp-solana/lib/client/generated/my_oapp/types/addressLocator.ts @@ -0,0 +1,86 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi' +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + dataEnum, + publicKey as publicKeySerializer, + struct, + tuple, + u8, + unit, +} from '@metaplex-foundation/umi/serializers' + +/** + * A generic account locator used in LZ execution planning for V2. + * Can reference the address directly, via ALT, or as a placeholder. + * + * This enum enables the compact account referencing design of V2, supporting: + * - OApps to request multiple signer accounts, not just a single Executor EOA + * - Dynamic creation of writable EOA-based data accounts + * - Efficient encoding of addresses via ALTs, reducing account list size + * + * The legacy is_signer flag is removed. Instead, signer roles are explicitly + * declared through Payer and indexed Signer(u8) variants. + */ + +export type AddressLocator = + | { __kind: 'Address'; fields: [PublicKey] } + | { __kind: 'AltIndex'; fields: [number, number] } + | { __kind: 'Payer' } + | { __kind: 'Signer'; fields: [number] } + | { __kind: 'Context' } + +export type AddressLocatorArgs = AddressLocator + +export function getAddressLocatorSerializer(): Serializer { + return dataEnum( + [ + [ + 'Address', + struct>([['fields', tuple([publicKeySerializer()])]]), + ], + ['AltIndex', struct>([['fields', tuple([u8(), u8()])]])], + ['Payer', unit()], + ['Signer', struct>([['fields', tuple([u8()])]])], + ['Context', unit()], + ], + { description: 'AddressLocator' } + ) as Serializer +} + +// Data Enum Helpers. +export function addressLocator( + kind: 'Address', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind +export function addressLocator( + kind: 'AltIndex', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind +export function addressLocator(kind: 'Payer'): GetDataEnumKind +export function addressLocator( + kind: 'Signer', + data: GetDataEnumKindContent['fields'] +): GetDataEnumKind +export function addressLocator(kind: 'Context'): GetDataEnumKind +export function addressLocator( + kind: K, + data?: any +): Extract { + return Array.isArray(data) ? { __kind: kind, fields: data } : { __kind: kind, ...(data ?? {}) } +} +export function isAddressLocator( + kind: K, + value: AddressLocator +): value is AddressLocator & { __kind: K } { + return value.__kind === kind +} diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/index.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/index.ts index 5cc85a6f0..3c6132257 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/types/index.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/types/index.ts @@ -6,8 +6,11 @@ * @see https://github.com/kinobi-so/kinobi */ +export * from './accountMetaRef' +export * from './addressLocator' export * from './enforcedOptions' -export * from './lzAccount' +export * from './instruction' export * from './lzReceiveParams' +export * from './lzReceiveTypesV2Result' export * from './messagingFee' export * from './peerConfigParam' diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/instruction.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/instruction.ts new file mode 100644 index 000000000..53dbbf974 --- /dev/null +++ b/examples/oapp-solana/lib/client/generated/my_oapp/types/instruction.ts @@ -0,0 +1,125 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi' +import { + GetDataEnumKind, + GetDataEnumKindContent, + Serializer, + array, + bytes, + dataEnum, + publicKey as publicKeySerializer, + struct, + u32, +} from '@metaplex-foundation/umi/serializers' +import { AccountMetaRef, AccountMetaRefArgs, getAccountMetaRefSerializer } from '.' + +/** + * The list of instructions that can be executed in the LzReceive transaction. + * + * V2's multi-instruction model enables complex patterns such as: + * - Preprocessing steps before lz_receive (e.g., account initialization) + * - Postprocessing steps after lz_receive (e.g., verification, cleanup) + * - ABA messaging patterns with additional LayerZero sends + * - Conditional execution flows based on message content + */ + +export type Instruction = + | { + __kind: 'LzReceive' + /** + * Account list for the lz_receive instruction + * Uses AddressLocator for flexible address resolution + */ + accounts: Array + } + | { + __kind: 'Standard' + /** Target program ID for the custom instruction */ + programId: PublicKey + /** + * Account list for the custom instruction + * Uses same AddressLocator system as LzReceive + */ + accounts: Array + /** + * Instruction data payload + * Raw bytes containing the instruction's parameters + */ + data: Uint8Array + } + +export type InstructionArgs = + | { + __kind: 'LzReceive' + /** + * Account list for the lz_receive instruction + * Uses AddressLocator for flexible address resolution + */ + accounts: Array + } + | { + __kind: 'Standard' + /** Target program ID for the custom instruction */ + programId: PublicKey + /** + * Account list for the custom instruction + * Uses same AddressLocator system as LzReceive + */ + accounts: Array + /** + * Instruction data payload + * Raw bytes containing the instruction's parameters + */ + data: Uint8Array + } + +export function getInstructionSerializer(): Serializer { + return dataEnum( + [ + [ + 'LzReceive', + struct>([ + ['accounts', array(getAccountMetaRefSerializer())], + ]), + ], + [ + 'Standard', + struct>([ + ['programId', publicKeySerializer()], + ['accounts', array(getAccountMetaRefSerializer())], + ['data', bytes({ size: u32() })], + ]), + ], + ], + { description: 'Instruction' } + ) as Serializer +} + +// Data Enum Helpers. +export function instruction( + kind: 'LzReceive', + data: GetDataEnumKindContent +): GetDataEnumKind +export function instruction( + kind: 'Standard', + data: GetDataEnumKindContent +): GetDataEnumKind +export function instruction( + kind: K, + data?: any +): Extract { + return Array.isArray(data) ? { __kind: kind, fields: data } : { __kind: kind, ...(data ?? {}) } +} +export function isInstruction( + kind: K, + value: Instruction +): value is Instruction & { __kind: K } { + return value.__kind === kind +} diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/lzAccount.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/lzAccount.ts deleted file mode 100644 index 15771c19b..000000000 --- a/examples/oapp-solana/lib/client/generated/my_oapp/types/lzAccount.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * This code was AUTOGENERATED using the kinobi library. - * Please DO NOT EDIT THIS FILE, instead use visitors - * to add features, then rerun kinobi to update it. - * - * @see https://github.com/kinobi-so/kinobi - */ - -import { PublicKey } from '@metaplex-foundation/umi' -import { Serializer, bool, publicKey as publicKeySerializer, struct } from '@metaplex-foundation/umi/serializers' - -/** same to anchor_lang::prelude::AccountMeta */ -export type LzAccount = { - pubkey: PublicKey - isSigner: boolean - isWritable: boolean -} - -export type LzAccountArgs = LzAccount - -export function getLzAccountSerializer(): Serializer { - return struct( - [ - ['pubkey', publicKeySerializer()], - ['isSigner', bool()], - ['isWritable', bool()], - ], - { description: 'LzAccount' } - ) as Serializer -} diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/lzReceiveTypesV2Result.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/lzReceiveTypesV2Result.ts new file mode 100644 index 000000000..8b36124ed --- /dev/null +++ b/examples/oapp-solana/lib/client/generated/my_oapp/types/lzReceiveTypesV2Result.ts @@ -0,0 +1,66 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { PublicKey } from '@metaplex-foundation/umi' +import { Serializer, array, publicKey as publicKeySerializer, struct, u8 } from '@metaplex-foundation/umi/serializers' +import { Instruction, InstructionArgs, getInstructionSerializer } from '.' + +/** + * Output of the lz_receive_types_v2 instruction. + * + * This structure enables the multi-instruction execution model where OApps can + * define multiple instructions to be executed atomically by the Executor. + * The Executor constructs a single transaction containing all returned instructions. + */ + +export type LzReceiveTypesV2Result = { + /** The version of context account */ + contextVersion: number + /** + * ALTs required for this execution context + * Used by the Executor to resolve AltIndex references in AccountMetaRef + * Enables efficient account list compression for complex transactions + */ + alts: Array + /** + * The complete list of instructions required for LzReceive execution + * MUST include exactly one LzReceive instruction + * MAY include additional Standard instructions for preprocessing/postprocessing + * Instructions are executed in the order returned + */ + instructions: Array +} + +export type LzReceiveTypesV2ResultArgs = { + /** The version of context account */ + contextVersion: number + /** + * ALTs required for this execution context + * Used by the Executor to resolve AltIndex references in AccountMetaRef + * Enables efficient account list compression for complex transactions + */ + alts: Array + /** + * The complete list of instructions required for LzReceive execution + * MUST include exactly one LzReceive instruction + * MAY include additional Standard instructions for preprocessing/postprocessing + * Instructions are executed in the order returned + */ + instructions: Array +} + +export function getLzReceiveTypesV2ResultSerializer(): Serializer { + return struct( + [ + ['contextVersion', u8()], + ['alts', array(publicKeySerializer())], + ['instructions', array(getInstructionSerializer())], + ], + { description: 'LzReceiveTypesV2Result' } + ) as Serializer +} diff --git a/examples/oapp-solana/lib/client/generated/my_oapp/types/messagingFee.ts b/examples/oapp-solana/lib/client/generated/my_oapp/types/messagingFee.ts index 310352e1a..1d4f6ea2f 100644 --- a/examples/oapp-solana/lib/client/generated/my_oapp/types/messagingFee.ts +++ b/examples/oapp-solana/lib/client/generated/my_oapp/types/messagingFee.ts @@ -10,10 +10,7 @@ import { Serializer, struct, u64 } from '@metaplex-foundation/umi/serializers' export type MessagingFee = { nativeFee: bigint; lzTokenFee: bigint } -export type MessagingFeeArgs = { - nativeFee: number | bigint - lzTokenFee: number | bigint -} +export type MessagingFeeArgs = { nativeFee: number | bigint; lzTokenFee: number | bigint } export function getMessagingFeeSerializer(): Serializer { return struct( diff --git a/examples/oapp-solana/lib/client/myoapp.ts b/examples/oapp-solana/lib/client/myoapp.ts index bda8a02d0..61251e309 100644 --- a/examples/oapp-solana/lib/client/myoapp.ts +++ b/examples/oapp-solana/lib/client/myoapp.ts @@ -94,7 +94,7 @@ export class MyOApp { return accounts.safeFetchStore({ rpc }, count, { commitment }) } - initStore(payer: Signer, admin: PublicKey): WrappedInstruction { + initStore(payer: Signer, admin: PublicKey, alt?: PublicKey): WrappedInstruction { const [oapp] = this.pda.oapp() const remainingAccounts = this.endpointSDK.getRegisterOappIxAccountMetaForCPI(payer.publicKey, oapp) return instructions @@ -104,6 +104,7 @@ export class MyOApp { payer, store: oapp, lzReceiveTypesAccounts: this.pda.lzReceiveTypesAccounts()[0], + alt, // args admin: admin, diff --git a/examples/oapp-solana/lib/client/pda.ts b/examples/oapp-solana/lib/client/pda.ts index 06f0ae057..4d3774303 100644 --- a/examples/oapp-solana/lib/client/pda.ts +++ b/examples/oapp-solana/lib/client/pda.ts @@ -6,6 +6,8 @@ import { OmniAppPDA } from '@layerzerolabs/lz-solana-sdk-v2/umi' const eddsa = createWeb3JsEddsa() +export const LZ_RECEIVE_TYPES_SEED = 'LzReceiveTypes' + export class MyOAppPDA extends OmniAppPDA { static STORE_SEED = 'Store' static NONCE_SEED = 'Nonce' @@ -38,4 +40,10 @@ export class MyOAppPDA extends OmniAppPDA { sender, ]) } + + // seeds = [LZ_RECEIVE_TYPES_SEED, &store.key().to_bytes()] + lzReceiveTypesAccounts(): Pda { + const [store] = this.oapp() + return eddsa.findPda(this.programId, [Buffer.from(LZ_RECEIVE_TYPES_SEED, 'utf8'), publicKeyBytes(store)]) + } } diff --git a/examples/oapp-solana/lib/scripts/generate.ts b/examples/oapp-solana/lib/scripts/generate.ts index 9f66122c4..947d175a0 100644 --- a/examples/oapp-solana/lib/scripts/generate.ts +++ b/examples/oapp-solana/lib/scripts/generate.ts @@ -14,7 +14,17 @@ async function generateTypeScriptSDK(): Promise { // This is also acceptable as the client SDK requires the program ID to be provided at runtime. console.error('Generating TypeScript SDK to %s. IDL from %s', generatedSDKDir, anchorIdlPath) const kinobi = createFromRoot(rootNodeFromAnchor(anchorIdl as AnchorIdl)) - void kinobi.accept(renderVisitor(generatedSDKDir)) + void kinobi.accept( + renderVisitor(generatedSDKDir, { + prettierOptions: { + semi: false, + singleQuote: true, + tabWidth: 4, + printWidth: 120, + trailingComma: 'es5', + }, + }) + ) } ;(async (): Promise => { diff --git a/examples/oapp-solana/programs/my_oapp/src/instructions/init_store.rs b/examples/oapp-solana/programs/my_oapp/src/instructions/init_store.rs index 7fa831edf..eaa83384e 100644 --- a/examples/oapp-solana/programs/my_oapp/src/instructions/init_store.rs +++ b/examples/oapp-solana/programs/my_oapp/src/instructions/init_store.rs @@ -1,4 +1,9 @@ use crate::*; +use anchor_lang::{ + solana_program::{ + address_lookup_table::program::ID as ALT_PROGRAM_ID, + }, +}; use oapp::endpoint::{instructions::RegisterOAppParams, ID as ENDPOINT_ID}; #[derive(Accounts)] @@ -24,6 +29,8 @@ pub struct InitStore<'info> { bump )] pub lz_receive_types_accounts: Account<'info, LzReceiveTypesAccounts>, + #[account(owner = ALT_PROGRAM_ID)] + pub alt: Option>, pub system_program: Program<'info, System>, } @@ -36,6 +43,13 @@ impl InitStore<'_> { ctx.accounts.store.bump = ctx.bumps.store; ctx.accounts.store.endpoint_program = params.endpoint; ctx.accounts.lz_receive_types_accounts.store = ctx.accounts.store.key(); + ctx.accounts.lz_receive_types_accounts.alt = ctx + .accounts + .alt + .as_ref() + .map(|a| a.key()) + .unwrap_or_default(); + ctx.accounts.lz_receive_types_accounts.bump = ctx.bumps.lz_receive_types_accounts; // the above lines are required for all OApp implementations // the line below is specific to this string-passing example diff --git a/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive.rs b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive.rs index 41552f85f..3f87ca21b 100644 --- a/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive.rs +++ b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive.rs @@ -12,6 +12,8 @@ use oapp::{ #[derive(Accounts)] #[instruction(params: LzReceiveParams)] pub struct LzReceive<'info> { + #[account(mut)] + pub payer: Signer<'info>, /// OApp Store PDA. This account represents the "address" of your OApp on /// Solana and can contain any state relevant to your application. /// Customize the fields in `Store` as needed. @@ -57,6 +59,8 @@ impl LzReceive<'_> { let store = &mut ctx.accounts.store; store.string = string_value; + // You can add logic here to handle compose if the message contains a compose message + Ok(()) } } diff --git a/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types.rs b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types.rs deleted file mode 100644 index adcaffbc6..000000000 --- a/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::*; -use oapp::endpoint_cpi::{get_accounts_for_clear, LzAccount}; -use oapp::{endpoint::ID as ENDPOINT_ID, LzReceiveParams}; - -/// `lz_receive_types` is queried off-chain by the Executor before calling -/// `lz_receive`. It must return **every** account that will be touched by the -/// actual `lz_receive` instruction as well as the accounts required by -/// `Endpoint::clear`. -/// -/// The return order must match exactly what `lz_receive` expects or the -/// cross-program invocation will fail. -#[derive(Accounts)] -pub struct LzReceiveTypes<'info> { - #[account(seeds = [STORE_SEED], bump = store.bump)] - pub store: Account<'info, Store>, -} - -impl LzReceiveTypes<'_> { - pub fn apply( - ctx: &Context, - params: &LzReceiveParams, - ) -> Result> { - // 1. The store PDA is always the first account and is mutable. If your - // program derives the store PDA with additional seeds, ensure the same - // seeds are used when providing the store account. - let store = ctx.accounts.store.key(); - - // 2. The peer PDA for the remote chain needs to be retrieved, for later verification of the `params.sender`. - let peer_seeds = [PEER_SEED, &store.to_bytes(), ¶ms.src_eid.to_be_bytes()]; - let (peer, _) = Pubkey::find_program_address(&peer_seeds, ctx.program_id); - - // Accounts used directly by `lz_receive` - let mut accounts = vec![ - // store (mutable) - LzAccount { pubkey: store, is_signer: false, is_writable: true }, - // peer (read-only) - LzAccount { pubkey: peer, is_signer: false, is_writable: false } - ]; - - // Append the additional accounts required for `Endpoint::clear` - let accounts_for_clear = get_accounts_for_clear( - ENDPOINT_ID, - &store, - params.src_eid, - ¶ms.sender, - params.nonce, - ); - accounts.extend(accounts_for_clear); - - Ok(accounts) - } -} diff --git a/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types_info.rs b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types_info.rs new file mode 100644 index 000000000..817f00e36 --- /dev/null +++ b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types_info.rs @@ -0,0 +1,69 @@ +use oapp::{ + lz_receive_types_v2::{LzReceiveTypesV2Accounts, LZ_RECEIVE_TYPES_VERSION}, + LzReceiveParams, LZ_RECEIVE_TYPES_SEED, +}; + +use crate::*; + +/// LzReceiveTypesInfo instruction implements the versioning mechanism introduced in V2. +/// +/// This instruction addresses the compatibility risk of the original LzReceiveType V1 design, +/// which lacked any formal versioning mechanism. The LzReceiveTypesInfo instruction allows +/// the Executor to determine how to interpret the structure of the data returned by +/// lz_receive_types() for different versions. +/// +/// Returns (version, versioned_data): +/// - version: u8 — A protocol-defined version identifier for the LzReceiveType logic and return +/// type +/// - versioned_data: Any — A version-specific structure that the Executor decodes based on the +/// version +/// +/// For Version 2, the versioned_data contains LzReceiveTypesV2Accounts which provides information +/// needed to construct the call to lz_receive_types_v2. +#[derive(Accounts)] +pub struct LzReceiveTypesInfo<'info> { + #[account(seeds = [STORE_SEED], bump = store.bump)] + pub store: Account<'info, Store>, + + /// PDA account containing the versioned data structure for V2 + /// Contains the accounts needed to construct lz_receive_types_v2 instruction + #[account(seeds = [LZ_RECEIVE_TYPES_SEED, &store.key().to_bytes()], bump = lz_receive_types_accounts.bump)] + pub lz_receive_types_accounts: Account<'info, LzReceiveTypesAccounts>, +} + +impl LzReceiveTypesInfo<'_> { + /// Returns the version and versioned data for LzReceiveTypes + /// + /// Version Compatibility: + /// - Forward Compatibility: Executors must gracefully reject unknown versions + /// - Backward Compatibility: Version 1 OApps do not implement lz_receive_types_info; Executors + /// may fall back to assuming V1 if the version instruction is missing or unimplemented + /// + /// For V2, returns: + /// - version: 2 (u8) + /// - versioned_data: LzReceiveTypesV2Accounts containing the accounts needed for + /// lz_receive_types_v2 + pub fn apply( + ctx: &Context, + _params: &LzReceiveParams, + ) -> Result<(u8, LzReceiveTypesV2Accounts)> { + let receive_types_account = &ctx.accounts.lz_receive_types_accounts; + + // If there are accounts that need to be dynamically derived based on contents of the message, + // ...you can access params.message + + let required_accounts = if receive_types_account.alt == Pubkey::default() { + vec![ + receive_types_account.store + // You can include more accounts here if necessary + ] + } else { + vec![ + receive_types_account.store, + receive_types_account.alt, + // You can include more accounts here if necessary + ] + }; + Ok((LZ_RECEIVE_TYPES_VERSION, LzReceiveTypesV2Accounts { accounts: required_accounts })) + } +} \ No newline at end of file diff --git a/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types_v2.rs b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types_v2.rs new file mode 100644 index 000000000..12faad6aa --- /dev/null +++ b/examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types_v2.rs @@ -0,0 +1,76 @@ +use crate::*; + +use anchor_lang::solana_program; +use oapp::{ + common::{ + compact_accounts_with_alts, AccountMetaRef, AddressLocator, EXECUTION_CONTEXT_VERSION_1, + }, + endpoint::ID as ENDPOINT_ID, + lz_receive_types_v2::{ + get_accounts_for_clear, + Instruction, LzReceiveTypesV2Result, + }, + LzReceiveParams, +}; + + +#[derive(Accounts)] +#[instruction(params: LzReceiveParams)] +pub struct LzReceiveTypesV2<'info> { + #[account(seeds = [STORE_SEED], bump = store.bump)] + pub store: Account<'info, Store>, + // Note: include more accounts here if you had done so in the previous steps +} + +impl LzReceiveTypesV2<'_> { + /// Returns the execution plan for lz_receive with a minimal account set. + pub fn apply( + ctx: &Context, + params: &LzReceiveParams, + ) -> Result { + // 1. Store PDA (writable) – matches LzReceive's `store` account + let store_key = ctx.accounts.store.key(); + + // 2. Derive peer PDA using store key + src_eid to match LzReceive + let peer_seeds = [PEER_SEED, store_key.as_ref(), ¶ms.src_eid.to_be_bytes()]; + let (peer, _) = Pubkey::find_program_address(&peer_seeds, ctx.program_id); + + let mut accounts = vec![ + // payer + AccountMetaRef { pubkey: AddressLocator::Payer, is_writable: true }, + // store (writable) + AccountMetaRef { pubkey: store_key.into(), is_writable: true }, + // peer + AccountMetaRef { pubkey: peer.into(), is_writable: false } + ]; + + // Add accounts required for LayerZero's clear operation + // These accounts handle the core message verification and processing + let accounts_for_clear: Vec = get_accounts_for_clear( + ENDPOINT_ID, + &store_key, + params.src_eid, + ¶ms.sender, + params.nonce, + ); + accounts.extend(accounts_for_clear); + + // You can add handling of compose message here. Use accounts.extend() to add accounts needed for the send compose operation. + + // Return the execution plan (no clear/compose helper accounts) + Ok(LzReceiveTypesV2Result { + context_version: EXECUTION_CONTEXT_VERSION_1, + // In this example, ALTs are passed in via remaining_accounts + // This decision allows for flexibility in terms of passing in any number of ALTs without needing to change the accounts struct + // However, if you need stronger schema guarantees and require only a single ALT, you may opt to have it passed in explicitly via ctx.accounts.alt (or similar) + alts: ctx.remaining_accounts.iter().map(|alt| alt.key()).collect(), + instructions: vec![ + // You can add additional instructions before the LzReceive instruction + Instruction::LzReceive { + accounts: compact_accounts_with_alts(&ctx.remaining_accounts, accounts)?, + }, + // You can add additional instructions after the LzReceive instruction + ], + }) + } +} \ No newline at end of file diff --git a/examples/oapp-solana/programs/my_oapp/src/instructions/mod.rs b/examples/oapp-solana/programs/my_oapp/src/instructions/mod.rs index a09baf414..aea6d160e 100644 --- a/examples/oapp-solana/programs/my_oapp/src/instructions/mod.rs +++ b/examples/oapp-solana/programs/my_oapp/src/instructions/mod.rs @@ -1,7 +1,8 @@ pub mod send; pub mod init_store; pub mod lz_receive; -pub mod lz_receive_types; +pub mod lz_receive_types_info; +pub mod lz_receive_types_v2; pub mod quote_send; pub mod set_peer_config; @@ -9,6 +10,7 @@ pub mod set_peer_config; pub use send::*; pub use init_store::*; pub use lz_receive::*; -pub use lz_receive_types::*; +pub use lz_receive_types_info::*; +pub use lz_receive_types_v2::*; pub use quote_send::*; pub use set_peer_config::*; diff --git a/examples/oapp-solana/programs/my_oapp/src/lib.rs b/examples/oapp-solana/programs/my_oapp/src/lib.rs index 232c7b009..eeba63444 100644 --- a/examples/oapp-solana/programs/my_oapp/src/lib.rs +++ b/examples/oapp-solana/programs/my_oapp/src/lib.rs @@ -5,7 +5,11 @@ mod state; use anchor_lang::prelude::*; use instructions::*; -use oapp::{endpoint::MessagingFee, endpoint_cpi::LzAccount, LzReceiveParams}; +use oapp::{ + endpoint::MessagingFee, + LzReceiveParams, + lz_receive_types_v2::{LzReceiveTypesV2Accounts, LzReceiveTypesV2Result} +}; use solana_helper::program_id_from_env; use state::*; @@ -57,12 +61,19 @@ pub mod my_oapp { LzReceive::apply(&mut ctx, ¶ms) } - // handler that returns the list of accounts required to execute lz_receive - pub fn lz_receive_types( - ctx: Context, + pub fn lz_receive_types_v2( + ctx: Context, params: LzReceiveParams, - ) -> Result> { - LzReceiveTypes::apply(&ctx, ¶ms) + ) -> Result { + LzReceiveTypesV2::apply(&ctx, ¶ms) + } + + // returns the version and the accounts required to execute lz_receive_types_v2 + pub fn lz_receive_types_info( + ctx: Context, + params: LzReceiveParams, + ) -> Result<(u8, LzReceiveTypesV2Accounts)> { + LzReceiveTypesInfo::apply(&ctx, ¶ms) } } diff --git a/examples/oapp-solana/programs/my_oapp/src/state/store.rs b/examples/oapp-solana/programs/my_oapp/src/state/store.rs index 89263751e..a0554907f 100644 --- a/examples/oapp-solana/programs/my_oapp/src/state/store.rs +++ b/examples/oapp-solana/programs/my_oapp/src/state/store.rs @@ -2,9 +2,9 @@ use crate::*; #[account] pub struct Store { - pub admin: Pubkey, // This is required and should be consistent. - pub bump: u8, // This is required and should be consistent. - pub endpoint_program: Pubkey, // This is required and should be consistent. + pub admin: Pubkey, + pub bump: u8, // the bump of the store PDA + pub endpoint_program: Pubkey, pub string: String, // This is specific to this string-passing example. // You can add more fields as needed for your OApp implementation. } @@ -17,7 +17,9 @@ impl Store { // The LzReceiveTypesAccounts PDA is used by the Executor as a prerequisite to calling `lz_receive`. #[account] pub struct LzReceiveTypesAccounts { - pub store: Pubkey, // This is required and should be consistent. + pub store: Pubkey, // Note: This is used as your OApp address. + pub alt: Pubkey, // Note: in this example, we store a single ALT. You can modify this to store a Vec of Pubkeys too. + pub bump: u8, // the bump of the lz_receive_types_accounts PDA } impl LzReceiveTypesAccounts { diff --git a/examples/oapp-solana/tasks/evm/debug.ts b/examples/oapp-solana/tasks/evm/debug.ts new file mode 100644 index 000000000..8b8600f82 --- /dev/null +++ b/examples/oapp-solana/tasks/evm/debug.ts @@ -0,0 +1,29 @@ +import { task, types } from 'hardhat/config' +import { ActionType, HardhatRuntimeEnvironment } from 'hardhat/types' + +import { DebugLogger } from '../common/utils' + +interface DebugTaskArgs { + contractName: string +} + +const action: ActionType = async ({ contractName }, hre: HardhatRuntimeEnvironment) => { + const contract = await hre.ethers.getContract(contractName) + const readableContract = contract as unknown as { data: () => Promise } + + const storedData = await readableContract.data() + + DebugLogger.header('EVM OApp Store Information') + DebugLogger.keyValue('Network', hre.network.name) + DebugLogger.keyValue('Contract Name', contractName) + DebugLogger.keyValue('Contract Address', contract.address) + DebugLogger.keyValue('String', storedData) + DebugLogger.separator() +} + +task('lz:oapp:evm:debug', 'Reads the stored string data from the EVM OApp', action).addOptionalParam( + 'contractName', + 'Name of the deployed EVM OApp contract (default: MyOApp)', + 'MyOApp', + types.string +) diff --git a/examples/oapp-solana/tasks/index.ts b/examples/oapp-solana/tasks/index.ts index a9233bd4d..c2c878bab 100644 --- a/examples/oapp-solana/tasks/index.ts +++ b/examples/oapp-solana/tasks/index.ts @@ -5,3 +5,4 @@ import './solana/oappCreate' import './solana/initConfig' import './solana/retryMessage' import './solana/debug' +import './evm/debug' diff --git a/examples/oapp-solana/tasks/solana/oappCreate.ts b/examples/oapp-solana/tasks/solana/oappCreate.ts index 7693ac0d9..6de992ad3 100644 --- a/examples/oapp-solana/tasks/solana/oappCreate.ts +++ b/examples/oapp-solana/tasks/solana/oappCreate.ts @@ -15,17 +15,22 @@ interface Args { * The endpoint ID for the Solana network. */ eid: EndpointId + /** + * Optional Address Lookup Table (ALT) address. + */ + alt?: string } -const action: ActionType = async ({ programId, eid }, hre: HardhatRuntimeEnvironment) => { - // TODO: accept program ID as a param - +const action: ActionType = async ({ programId, eid, alt }, hre: HardhatRuntimeEnvironment) => { const isTestnet = eid == EndpointId.SOLANA_V2_TESTNET const myoappInstance: myoapp.MyOApp = new myoapp.MyOApp(publicKey(programId)) const [oapp] = myoappInstance.pda.oapp() const { umi, umiWalletSigner } = await deriveConnection(eid) - const txBuilder = transactionBuilder().add(myoappInstance.initStore(umiWalletSigner, umiWalletSigner.publicKey)) + const altPubkey = alt ? publicKey(alt) : undefined + const txBuilder = transactionBuilder().add( + myoappInstance.initStore(umiWalletSigner, umiWalletSigner.publicKey, altPubkey) + ) const tx = await txBuilder.sendAndConfirm(umi) console.log(`createTx: ${getExplorerTxLink(bs58.encode(tx.signature), isTestnet)}`) saveSolanaDeployment(eid, programId, oapp) @@ -34,3 +39,4 @@ const action: ActionType = async ({ programId, eid }, hre: HardhatRuntimeE task('lz:oapp:solana:create', 'inits the oapp account', action) .addParam('programId', 'The program ID of the OApp', undefined, types.string, false) .addParam('eid', 'The endpoint ID for the Solana network.', undefined, types.int, false) + .addOptionalParam('alt', 'Address Lookup Table (ALT) address', undefined, types.string)