diff --git a/examples/oapp-aptos-move/README.md b/examples/oapp-aptos-move/README.md index 6e67decd52..4e406d1901 100644 --- a/examples/oapp-aptos-move/README.md +++ b/examples/oapp-aptos-move/README.md @@ -179,14 +179,17 @@ Ensure that in move.layerzero.config.ts, all of your evm contracts have the owne ] ``` -If you are wiring solana to move-vm, create a file in `deployments/solana-mainnet/MyOFT.json` (or `deployments/solana-testnet/MyOFT.json` if you are using testnet) and add the following field: +If you are wiring solana to move-vm, in your config add: -```json -{ - "address": -} +```ts +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET as EndpointId, + address: "YOUR_DEPLOYED_SOLANA_OAPP_ADDRESS", +}; ``` +Note: Solana OApp must be deployed with the Solana example. + Commands: ### To wire from EVM to Move-VM: diff --git a/examples/oapp-aptos-move/scripts/aptos-move-send.ts b/examples/oapp-aptos-move/scripts/aptos-move-send.ts index feb14c6372..e4dbcc2bab 100644 --- a/examples/oapp-aptos-move/scripts/aptos-move-send.ts +++ b/examples/oapp-aptos-move/scripts/aptos-move-send.ts @@ -11,6 +11,7 @@ import { Account, + AccountAddressInput, Aptos, AptosConfig, Ed25519PrivateKey, @@ -21,25 +22,26 @@ import { SimpleTransaction, } from '@aptos-labs/ts-sdk' import * as dotenv from 'dotenv' +import { ethers } from 'ethers' -import { EndpointId } from '@layerzerolabs/lz-definitions' +import { Chain, EndpointId, getNetworkForChainId } from '@layerzerolabs/lz-definitions' import { Options } from '@layerzerolabs/lz-v2-utilities' // Load environment variables from .env file dotenv.config() // Validate required environment variables -if (!process.env.APTOS_PRIVATE_KEY || !process.env.ACCOUNT_ADDRESS) { - throw new Error('Please set APTOS_PRIVATE_KEY and ACCOUNT_ADDRESS in your .env file') +if (!process.env.APTOS_PRIVATE_KEY || !process.env.APTOS_ACCOUNT_ADDRESS) { + throw new Error('Please set APTOS_PRIVATE_KEY and APTOS_ACCOUNT_ADDRESS in your .env file') } // Configuration constants const APTOS_PRIVATE_KEY = process.env.APTOS_PRIVATE_KEY -const ACCOUNT_ADDRESS = process.env.ACCOUNT_ADDRESS +const ACCOUNT_ADDRESS = process.env.APTOS_ACCOUNT_ADDRESS // OApp configuration -const OAPP_ADDRESS = '' // Set your OApp's address on Aptos -const REMOTE_EID = EndpointId.BSC_V2_TESTNET // Destination chain endpoint ID +const OAPP_ADDRESS = '0x' // Set your OApp's address on Aptos +const REMOTE_EID = EndpointId.SOLANA_V2_TESTNET // Destination chain endpoint ID const NETWORK = Network.TESTNET // Aptos network configuration // Initialize Aptos account and client @@ -59,10 +61,23 @@ async function send() { const options = Options.newOptions().addExecutorLzReceiveOption(BigInt(30000)) const extraOptions = options.toBytes() - // Prepare the message - const message = 'Hello, EVM!' - const messageBytes = new TextEncoder().encode(message) - const messageArray = new Uint8Array(messageBytes) + let messageArray: Uint8Array + if (getNetworkForChainId(REMOTE_EID).chainName === Chain.SOLANA) { + const message = 'Hello, Solana!' + + const messageHex = ethers.utils.solidityPack( + ['uint256', 'bytes'], + [ethers.utils.toUtf8Bytes(message).length, ethers.utils.toUtf8Bytes(message)] + ) + // Convert hex string to Uint8Array + messageArray = ethers.utils.arrayify(messageHex) + } else { + const message = 'Hello, EVM!' + const messageBytes = new TextEncoder().encode(message) + messageArray = new Uint8Array(messageBytes) + } + + // The Following is the code required for packaging a string message to be sent to Solana: // Get fee quote for the cross-chain message const quote = await aptos.view({ @@ -79,7 +94,7 @@ async function send() { console.log('ZRO fee:', quote[1]) // Verify account has sufficient balance - const balance = await aptos.account.getAccountAPTAmount({ accountAddress: ACCOUNT_ADDRESS }) + const balance = await aptos.account.getAccountAPTAmount({ accountAddress: ACCOUNT_ADDRESS as AccountAddressInput }) console.log('Account balance:', Number(balance) / 100000000, 'APT') if (Number(balance) < Number(quote[0])) { @@ -96,7 +111,7 @@ async function send() { // Create the transaction const transaction: SimpleTransaction = await aptos.transaction.build.simple({ - sender: ACCOUNT_ADDRESS, + sender: ACCOUNT_ADDRESS as AccountAddressInput, data: payload, options: { maxGasAmount: 100000, diff --git a/examples/oapp-solana/docs/move.layerzero.config.ts b/examples/oapp-solana/docs/move.layerzero.config.ts new file mode 100644 index 0000000000..11d6a0b953 --- /dev/null +++ b/examples/oapp-solana/docs/move.layerzero.config.ts @@ -0,0 +1,121 @@ +// NOTE: This config is an example for wiring Solana -> Aptos. +// See wiring-to-aptos.md for more information. +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities' +import { OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' + +enum MsgType { + SEND = 1, + SEND_AND_CALL = 2, +} +const aptosContract: OmniPointHardhat = { + eid: EndpointId.APTOS_V2_TESTNET as EndpointId, + address: 'YOUR_DEPLOYED_APTOS_CONTRACT_ADDRESS', +} +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET as EndpointId, + address: 'YOUR_DEPLOYED_SOLANA_OAPP_ADDRESS', +} + +const layerZeroDVNAptosAddress = '0x756f8ab056688d22687740f4a9aeec3b361170b28d08b719e28c4d38eed1043e' +const layerZeroDVNSolanaAddress = '4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb' + +const config: OAppOmniGraphHardhat = { + contracts: [ + { + contract: solanaContract, + config: { + owner: 'YOUR_SOLANA_OWNER_ADDRESS', + delegate: 'YOUR_SOLANA_OWNER_ADDRESS', + }, + }, + { + contract: aptosContract, + config: { + owner: 'YOUR_APTOS_ACCOUNT_ADDRESS', + delegate: 'YOUR_APTOS_ACCOUNT_ADDRESS', + }, + }, + ], + connections: [ + { + from: aptosContract, + to: solanaContract, + config: { + enforcedOptions: [ + { + msgType: MsgType.SEND_AND_CALL, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 5_000, + value: 0, + }, + ], + sendLibrary: '0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690', + receiveLibraryConfig: { + receiveLibrary: '0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690', + gracePeriod: BigInt(0), + }, + sendConfig: { + executorConfig: { + maxMessageSize: 10_000, + executor: '0x93353700091200ef9fdc536ce6a86182cc7e62da25f94356be9421c6310b9585', + }, + ulnConfig: { + confirmations: BigInt(10), + requiredDVNs: [layerZeroDVNAptosAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + receiveConfig: { + ulnConfig: { + confirmations: BigInt(15), + requiredDVNs: [layerZeroDVNAptosAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + }, + }, + { + from: solanaContract, + to: aptosContract, + config: { + enforcedOptions: [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 200_000, + value: 0, + }, + ], + sendLibrary: '7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH', + receiveLibraryConfig: { + receiveLibrary: '7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH', + gracePeriod: BigInt(0), + }, + sendConfig: { + executorConfig: { + maxMessageSize: 10_000, + executor: 'AwrbHeCyniXaQhiJZkLhgWdUCteeWSGaSN1sTfLiY7xK', + }, + ulnConfig: { + confirmations: BigInt(15), + requiredDVNs: [layerZeroDVNSolanaAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + receiveConfig: { + ulnConfig: { + confirmations: BigInt(10), + requiredDVNs: [layerZeroDVNSolanaAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + }, + }, + ], +} +export default config diff --git a/examples/oapp-solana/docs/wiring-to-aptos.md b/examples/oapp-solana/docs/wiring-to-aptos.md new file mode 100644 index 0000000000..5728de5ff3 --- /dev/null +++ b/examples/oapp-solana/docs/wiring-to-aptos.md @@ -0,0 +1,63 @@ +### Wiring Solana to Move-VM (Aptos, Initia, Movement, etc.) + +:warning: **Important Limitations for Solana ↔ Move-VM Pathways** + +Currently, you can only perform **one-way wiring from Solana → Move-VM** using this example. For the reverse pathway (Move-VM → Solana), you must use the [OApp Aptos Move example](https://github.com/LayerZero-Labs/devtools/tree/main/examples/oapp-aptos-move). Please follow the deployment instructions in the README.md of the `oapp-aptos-move` example for deploying to Move-VM and configuring the Move-VM → Solana pathway. + +#### Configuration Steps for Solana → Aptos + +:information_source: **For bidirectional Solana ↔ Aptos communication, deploy both examples:** + +- **Solana → Aptos**: Use this example +- **Aptos → Solana**: Use the [OApp Aptos Move example](https://github.com/LayerZero-Labs/devtools/tree/main/examples/oapp-aptos-move) + +#### Deployment Process + +1. **Create and Deploy Solana Example** + + - Create the Solana example: `LZ_ENABLE_SOLANA_OAPP_EXAMPLE=1 npx create-lz-oapp@latest` + - Deploy to Solana following the deployment instructions in the Solana example README + +2. **Create and Deploy Aptos Move Example** + + - Create the Aptos Move example: `LZ_ENABLE_EXPERIMENTAL_MOVE_VM_EXAMPLES=1 npx create-lz-oapp@latest` + - Deploy to Aptos following the deployment and initialization instructions in the Aptos example README + + Your directory structure should look like this: + + ``` + your-folder/ + ├── your-aptos-example/ + └── your-solana-example/ + ``` + +3. **Configure Solana Example** + + - Navigate to the Solana example directory + - Use the example configuration at `./docs/move.layerzero.config.ts` + - Fill out the configuration with your desired values + - Replace `./layerzero.config.ts` with the completed `move.layerzero.config.ts` file + +4. **Wire Solana to Aptos** + + Execute the following commands in the Solana example directory: + + ```bash + npx hardhat lz:oapp:solana:init-config --oapp-config move.layerzero.config.ts + ``` + + ```bash + npx hardhat lz:oapp:wire --oapp-config move.layerzero.config.ts + ``` + + If these commands succeed, you have successfully wired the Solana contract to the Aptos Move contract. + +5. **Wire Aptos to Solana** + + - Transfer the `move.layerzero.config.ts` file from the Solana example to the Aptos Move example directory + - In the Aptos Move example directory, run: + ```bash + pnpm run lz:sdk:move:wire --oapp-config move.layerzero.config.ts + ``` + + If this command succeeds, you are ready to test the bidirectional pathway. diff --git a/examples/oapp-solana/tasks/aptos/aptosEndpointV2.ts b/examples/oapp-solana/tasks/aptos/aptosEndpointV2.ts new file mode 100644 index 0000000000..4ff52e84d1 --- /dev/null +++ b/examples/oapp-solana/tasks/aptos/aptosEndpointV2.ts @@ -0,0 +1,325 @@ +import { OmniAddress, OmniPoint, OmniTransaction } from '@layerzerolabs/devtools' +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { + IEndpointV2, + MessageParams, + MessagingFee, + SetConfigParam, + Timeout, + Uln302ConfigType, + Uln302ExecutorConfig, + Uln302SetExecutorConfig, + Uln302SetUlnConfig, + Uln302UlnConfig, + UlnReadSetUlnConfig, + UlnReadUlnConfig, + UlnReadUlnUserConfig, +} from '@layerzerolabs/protocol-devtools' + +/** + * Minimal "AptosEndpointV2" skeleton that implements IEndpointV2 for Aptos. + * All methods here return placeholder or dummy values for now. + */ +export class AptosEndpointV2 implements IEndpointV2 { + // The devtools often expect the "point" property from IOmniSDK + public point: OmniPoint + + constructor(point: OmniPoint) { + this.point = point + } + + // + // ---------------------------------------------------------- + // Required by IOmniSDK (the base) or the IEndpointV2 extension + // ---------------------------------------------------------- + + // The devtools might call this to fetch a "Uln302" object. + // For now, return a dummy object that implements IUln302 + async getUln302SDK(_address: OmniAddress) { + return { + point: this.point, + // placeholders to avoid compile errors: + async getUlnConfig(_eid: EndpointId, _address: OmniAddress, _type: Uln302ConfigType) { + return {} as Uln302UlnConfig + }, + async getAppUlnConfig(_eid: EndpointId, _address: OmniAddress, _type: Uln302ConfigType) { + return {} as Uln302UlnConfig + }, + async hasAppUlnConfig( + _eid: EndpointId, + _oapp: OmniAddress, + _config: Uln302UlnConfig, + _type: Uln302ConfigType + ) { + return false + }, + async setDefaultUlnConfig(_eid: EndpointId, _config: Uln302UlnConfig) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + }, + async getExecutorConfig(_eid: EndpointId, _address: OmniAddress) { + return { + maxMessageSize: 1024, + executor: '0x0', + } as Uln302ExecutorConfig + }, + async getAppExecutorConfig(_eid: EndpointId, _address: OmniAddress): Promise { + return { + maxMessageSize: 1024, + executor: '0x0', + } as Uln302ExecutorConfig + }, + async hasAppExecutorConfig(_eid: EndpointId, _oapp: OmniAddress, _config: Uln302ExecutorConfig) { + return false + }, + async setDefaultExecutorConfig(_eid: EndpointId, _config: Uln302ExecutorConfig) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + }, + } + } + + // The devtools might call this to fetch a "UlnRead" object. + // For now, return a dummy object that implements IUlnRead + async getUlnReadSDK(_address: OmniAddress) { + return { + point: this.point, + async getUlnConfig(_channelId: number, _address: OmniAddress) { + return { + executor: '0x0', + requiredDVNs: [], + optionalDVNs: [], + optionalDVNThreshold: 0, + } as UlnReadUlnConfig + }, + async getAppUlnConfig(_channelId: number, _address: OmniAddress) { + return { + executor: '0x0', + requiredDVNs: [], + optionalDVNs: [], + optionalDVNThreshold: 0, + } as UlnReadUlnConfig + }, + async hasAppUlnConfig(_channelId: number, _oapp: OmniAddress, _config: UlnReadUlnConfig) { + return false + }, + async setDefaultUlnConfig(_channelId: number, _config: UlnReadUlnConfig) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + }, + } + } + + // + // ---------------------------------------------------------- + // Required by IEndpointV2 specifically + // ---------------------------------------------------------- + + async getDelegate(_oapp: OmniAddress): Promise { + return undefined + } + + async isDelegate(_oapp: OmniAddress, _delegate: OmniAddress): Promise { + return false + } + + async getDefaultReceiveLibrary(_eid: EndpointId): Promise { + return undefined + } + + async setDefaultReceiveLibrary(_eid: EndpointId, _uln: OmniAddress, _gracePeriod?: bigint) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + } + + async getDefaultSendLibrary(_eid: EndpointId): Promise { + return undefined + } + + async setDefaultSendLibrary(_eid: EndpointId, _uln: OmniAddress) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + } + + async isRegisteredLibrary(_uln: OmniAddress): Promise { + return false + } + + async registerLibrary(_uln: OmniAddress) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + } + + async getSendLibrary(_sender: OmniAddress, _dstEid: EndpointId): Promise { + return '0x0' + } + + async getReceiveLibrary(_receiver: OmniAddress, _srcEid: EndpointId): Promise<[OmniAddress | undefined, boolean]> { + return ['0x0', false] + } + + async getDefaultReceiveLibraryTimeout(_eid: EndpointId): Promise { + return { lib: '0x0', expiry: BigInt(0) } + } + + async getReceiveLibraryTimeout(_receiver: OmniAddress, _srcEid: EndpointId): Promise { + return { lib: '0x0', expiry: BigInt(0) } + } + + async setSendLibrary(_oapp: OmniAddress, _eid: EndpointId, _uln: OmniAddress) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + } + + async isDefaultSendLibrary(_sender: OmniAddress, _dstEid: EndpointId): Promise { + return false + } + + async setReceiveLibrary(_oapp: OmniAddress, _eid: EndpointId, _uln: OmniAddress, _gracePeriod: bigint) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + } + + async setReceiveLibraryTimeout(_oapp: OmniAddress, _eid: EndpointId, _uln: OmniAddress, _expiry: bigint) { + return { + point: this.point, + data: '0x00', + } as OmniTransaction + } + + async getExecutorConfig(_oapp: OmniAddress, _uln: OmniAddress, _eid: EndpointId) { + return { + maxMessageSize: 1024, + executor: '0x0', + } as Uln302ExecutorConfig + } + + async getAppExecutorConfig(_oapp: OmniAddress, _uln: OmniAddress, _eid: EndpointId) { + return { + maxMessageSize: 1024, + executor: '0x0', + } as Uln302ExecutorConfig + } + + async hasAppExecutorConfig(_oapp: OmniAddress, _uln: OmniAddress, _eid: EndpointId, _config: Uln302ExecutorConfig) { + return false + } + + async setExecutorConfig(_oapp: OmniAddress, _uln: OmniAddress, _setExecutorConfig: Uln302SetExecutorConfig[]) { + // Possibly return multiple OmniTransactions if the devtools call expects them + return [ + { + point: this.point, + data: '0x00', + }, + ] as OmniTransaction[] + } + + async getUlnConfig(_oapp: OmniAddress, _uln: OmniAddress, _eid: EndpointId, _type: Uln302ConfigType) { + return { + confirmations: BigInt(0), + requiredDVNs: [], + optionalDVNs: [], + optionalDVNThreshold: 0, + } as Uln302UlnConfig + } + + async getAppUlnConfig(_oapp: OmniAddress, _uln: OmniAddress, _eid: EndpointId, _type: Uln302ConfigType) { + return { + confirmations: BigInt(0), + requiredDVNs: [], + optionalDVNs: [], + optionalDVNThreshold: 0, + } as Uln302UlnConfig + } + + async getAppUlnReadConfig(_oapp: OmniAddress, _uln: OmniAddress, _channelId: number) { + return { + executor: '0x0', + requiredDVNs: [], + optionalDVNs: [], + optionalDVNThreshold: 0, + } as UlnReadUlnConfig + } + + async hasAppUlnConfig( + _oapp: OmniAddress, + _uln: OmniAddress, + _eid: EndpointId, + _config: any, + _type: Uln302ConfigType + ) { + return false + } + + async hasAppUlnReadConfig( + _oapp: OmniAddress, + _uln: OmniAddress, + _channelId: number, + _config: UlnReadUlnUserConfig + ) { + return false + } + + async setUlnConfig(_oapp: OmniAddress, _uln: OmniAddress, _setUlnConfig: Uln302SetUlnConfig[]) { + return [ + { + point: this.point, + data: '0x00', + }, + ] as OmniTransaction[] + } + + async setUlnReadConfig(_oapp: OmniAddress, _uln: OmniAddress, _setUlnConfig: UlnReadSetUlnConfig[]) { + return [ + { + point: this.point, + data: '0x00', + }, + ] as OmniTransaction[] + } + + async getUlnConfigParams(_uln: OmniAddress, _setUlnConfig: Uln302SetUlnConfig[]) { + return [] as SetConfigParam[] + } + + async getUlnReadConfigParams(_uln: OmniAddress, _setUlnConfig: UlnReadSetUlnConfig[]) { + return [] as SetConfigParam[] + } + + async getExecutorConfigParams(_uln: OmniAddress, _setExecutorConfig: Uln302SetExecutorConfig[]) { + return [] as SetConfigParam[] + } + + async setConfig(_oapp: OmniAddress, _uln: OmniAddress, _setConfigParam: SetConfigParam[]) { + return [ + { + point: this.point, + data: '0x00', + }, + ] as OmniTransaction[] + } + + async quote(_params: MessageParams, _sender: OmniAddress): Promise { + return { + nativeFee: BigInt(0), + lzTokenFee: BigInt(0), + } + } +} diff --git a/examples/oapp-solana/tasks/aptos/aptosSdkFactory.ts b/examples/oapp-solana/tasks/aptos/aptosSdkFactory.ts new file mode 100644 index 0000000000..a6b6aec3a0 --- /dev/null +++ b/examples/oapp-solana/tasks/aptos/aptosSdkFactory.ts @@ -0,0 +1,66 @@ +import { OmniAddress, OmniPoint, OmniTransaction } from '@layerzerolabs/devtools' +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' +import { IOApp, OAppEnforcedOptionParam } from '@layerzerolabs/ua-devtools' + +import { AptosEndpointV2 } from './aptosEndpointV2' + +export function createAptosOAppFactory() { + return async function (point: OmniPoint): Promise { + const supportedChaintypes = [ChainType.APTOS, ChainType.INITIA] + if (!supportedChaintypes.includes(endpointIdToChainType(point.eid))) { + throw new Error(`Aptos SDK factory can only create SDKs for Aptos networks. Received EID ${point.eid}.`) + } + + const createStubTransaction = (description: string): OmniTransaction => ({ + point, + data: `0x`, + description: `[APTOS STUB] ${description}`, + }) + + return { + point, + async getOwner(): Promise { + return undefined + }, + async hasOwner(owner: OmniAddress): Promise { + return false + }, + async setOwner(owner: OmniAddress): Promise { + return createStubTransaction(`setOwner(${owner})`) + }, + async getEndpointSDK() { + return new AptosEndpointV2(point) + }, + async getPeer(eid: EndpointId): Promise { + return undefined + }, + async hasPeer(eid: EndpointId, peer: OmniAddress): Promise { + return false + }, + async setPeer(eid: EndpointId, peer: OmniAddress | null | undefined): Promise { + return createStubTransaction(`setPeer(${eid}, ${peer})`) + }, + async getDelegate(): Promise { + return undefined + }, + async setDelegate(address: OmniAddress): Promise { + return createStubTransaction(`setDelegate(${address})`) + }, + async isDelegate(): Promise { + return false + }, + async getEnforcedOptions(): Promise { + return {} + }, + async setEnforcedOptions(enforcedOptions: OAppEnforcedOptionParam[]): Promise { + return createStubTransaction(`setEnforcedOptions(${enforcedOptions.length} options)`) + }, + async getCallerBpsCap(): Promise { + return BigInt(0) + }, + async setCallerBpsCap(callerBpsCap: bigint): Promise { + return createStubTransaction(`setCallerBpsCap(${callerBpsCap})`) + }, + } + } +} diff --git a/examples/oapp-solana/tasks/aptos/aptosSignerFactory.ts b/examples/oapp-solana/tasks/aptos/aptosSignerFactory.ts new file mode 100644 index 0000000000..11fc45bbbf --- /dev/null +++ b/examples/oapp-solana/tasks/aptos/aptosSignerFactory.ts @@ -0,0 +1,54 @@ +import { + OmniPoint, + OmniSigner, + OmniTransaction, + OmniTransactionReceipt, + OmniTransactionResponse, + formatEid, +} from '@layerzerolabs/devtools' +import { ChainType, EndpointId, endpointIdToChainType } from '@layerzerolabs/lz-definitions' + +export function createAptosSignerFactory(): ( + eid: EndpointId +) => Promise>> { + return async function (eid: EndpointId): Promise>> { + if (endpointIdToChainType(eid) !== ChainType.APTOS && endpointIdToChainType(eid) !== ChainType.INITIA) { + throw new Error(`createAptosSignerFactory() called with Move VM EID: ${formatEid(eid)}`) + } + + const aptosSigner: OmniSigner> = { + // The devtools signature requires these members: + eid, + getPoint: () => { + const point: OmniPoint = { + eid, + address: '0x0', + } + return point + }, + + /** + * sign(omniTx) => Promise + * Build & sign an Aptos entry function call, returning the BCS as a hex string. + */ + sign: async (omniTx: OmniTransaction): Promise => { + return '0x0' + }, + + /** + * signAndSend(omniTx) => Promise> + * Just calls sign(...) to get the BCS hex, then submit it. + */ + signAndSend: async (omniTx: OmniTransaction) => { + return { + transactionHash: '0x0', + wait: async (_confirmations?: number) => { + return { transactionHash: '0x0' } + }, + } + }, + } + + return aptosSigner + } +} diff --git a/examples/oapp-solana/tasks/aptos/index.ts b/examples/oapp-solana/tasks/aptos/index.ts new file mode 100644 index 0000000000..80f69f7420 --- /dev/null +++ b/examples/oapp-solana/tasks/aptos/index.ts @@ -0,0 +1,3 @@ +export { AptosEndpointV2 } from './aptosEndpointV2' +export { createAptosOAppFactory } from './aptosSdkFactory' +export { createAptosSignerFactory } from './aptosSignerFactory' diff --git a/examples/oapp-solana/tasks/common/utils.ts b/examples/oapp-solana/tasks/common/utils.ts index a994d2bfb6..e3d2c5fbed 100644 --- a/examples/oapp-solana/tasks/common/utils.ts +++ b/examples/oapp-solana/tasks/common/utils.ts @@ -8,7 +8,6 @@ import { OmniSigner, OmniTransactionReceipt, OmniTransactionResponse, - firstFactory, formatEid, } from '@layerzerolabs/devtools' import { createConnectedContractFactory } from '@layerzerolabs/devtools-evm-hardhat' @@ -31,6 +30,7 @@ import { } from '@layerzerolabs/ua-devtools-evm-hardhat' import { createSimpleOAppFactory } from '../../lib/factory' +import { createAptosOAppFactory } from '../aptos' export const createSolanaConnectionFactory = () => createConnectionFactory( @@ -50,6 +50,7 @@ export const createSdkFactory = ( // We do this by using the firstFactory helper function that is provided by the devtools package. // This function will try to execute the factories one by one and return the first one that succeeds. const evmSdkfactory = createOAppFactory(createConnectedContractFactory()) + const aptosSdkFactory = createAptosOAppFactory() const solanaSdkFactory = createSimpleOAppFactory( // The first parameter to createOFTFactory is a user account factory // @@ -76,7 +77,21 @@ export const createSdkFactory = ( // // We do this by using the firstFactory helper function that is provided by the devtools package. // This function will try to execute the factories one by one and return the first one that succeeds. - return firstFactory<[OmniPoint], IOApp>(evmSdkfactory, solanaSdkFactory) + return async (point: OmniPoint): Promise => { + if (endpointIdToChainType(point.eid) === ChainType.SOLANA) { + return solanaSdkFactory(point) + } else if (endpointIdToChainType(point.eid) === ChainType.EVM) { + return evmSdkfactory(point) + } else if ( + endpointIdToChainType(point.eid) === ChainType.APTOS || + endpointIdToChainType(point.eid) === ChainType.INITIA + ) { + return aptosSdkFactory(point) + } else { + console.error(`Unsupported chain type for EID ${point.eid}`) + throw new Error(`Unsupported chain type for EID ${point.eid}`) + } + } } export const createSolanaSignerFactory = ( diff --git a/examples/oapp-solana/tasks/common/wire.ts b/examples/oapp-solana/tasks/common/wire.ts index c76e27b3bd..31de828c66 100644 --- a/examples/oapp-solana/tasks/common/wire.ts +++ b/examples/oapp-solana/tasks/common/wire.ts @@ -14,6 +14,7 @@ import { TASK_LZ_OWNABLE_TRANSFER_OWNERSHIP, } from '@layerzerolabs/ua-devtools-evm-hardhat' +import { createAptosSignerFactory } from '../aptos' import { getSolanaDeployment, useWeb3Js } from '../solana' import { findSolanaEndpointIdInGraph } from '../solana/utils' @@ -104,6 +105,7 @@ task(TASK_LZ_OAPP_WIRE) // We'll also need a signer factory const solanaSignerFactory = createSolanaSignerFactory(keypair, connectionFactory, args.multisigKey) + const aptosSignerFactory = createAptosSignerFactory() // // @@ -169,6 +171,17 @@ task(TASK_LZ_OAPP_WIRE) ...subtaskArgs, configurator: configurator ?? subtaskArgs.configurator, sdkFactory, + graph: { + ...subtaskArgs.graph, + contracts: subtaskArgs.graph.contracts.filter((contract) => { + const chainType = endpointIdToChainType(contract.point.eid) + return chainType !== ChainType.APTOS && chainType !== ChainType.INITIA + }), + connections: subtaskArgs.graph.connections.filter((connection) => { + const fromChainType = endpointIdToChainType(connection.vector.from.eid) + return fromChainType !== ChainType.APTOS && fromChainType !== ChainType.INITIA + }), + }, }) } ) @@ -181,7 +194,7 @@ task(TASK_LZ_OAPP_WIRE) subtask(SUBTASK_LZ_SIGN_AND_SEND, 'Sign OFT transactions', (args: SignAndSendTaskArgs, _hre, runSuper) => runSuper({ ...args, - createSigner: firstFactory(solanaSignerFactory, args.createSigner), + createSigner: firstFactory(aptosSignerFactory, solanaSignerFactory, args.createSigner), }) ) diff --git a/examples/oft-aptos-move/README.md b/examples/oft-aptos-move/README.md index 6521104217..a6c9c0e320 100644 --- a/examples/oft-aptos-move/README.md +++ b/examples/oft-aptos-move/README.md @@ -206,14 +206,17 @@ Ensure that in move.layerzero.config.ts, all of your evm contracts have the owne ] ``` -If you are wiring solana to move-vm, create a file in `deployments/solana-mainnet/MyOFT.json` (solana-testnet if you are using testnet) and add the following field: +If you are wiring your Move-VM OFT to Solana, in your config add: -```json -{ - "address": -} +```ts +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET as EndpointId, + address: "YOUR_SOLANA_OFT_STORE_ADDRESS", +}; ``` +Note: Solana OFT can only be deployed from the Solana example. + ### To wire from EVM to Move-VM: ```bash diff --git a/examples/oft-solana/docs/Aptos.md b/examples/oft-solana/docs/Aptos.md deleted file mode 100644 index 19eb3e48c0..0000000000 --- a/examples/oft-solana/docs/Aptos.md +++ /dev/null @@ -1,29 +0,0 @@ -### Wiring Solana to Move-VM (Aptos, Initia, Movement, etc.) - -:warning: **Important limitations for Solana ↔ Move-VM pathways:** - -Currently, you can only do **one-way wiring from Solana → Move-VM** using this example. For the reverse pathway (Move-VM → Solana), you must use the [OFT Aptos Move example](https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-aptos-move). Please follow the instructions in the README.md of the `oft-aptos-move` example for deploying to Move-VM and wiring the Move-VM → Solana pathway. - -#### Configuration Requirements for Solana → Aptos - -When setting up Solana to Aptos wiring, ensure you: - -1. **Set the Aptos contract address**: In your `layerzero.config.ts`, manually specify the `address` field for your Aptos contract: - -```typescript -const aptosContract: OmniPointHardhat = { - eid: EndpointId.APTOS_V2_TESTNET, - address: "0xYOUR_DEPLOYED_APTOS_CONTRACT_ADDRESS", // Replace with your actual Aptos contract address -}; -``` - -2. **Use manual configuration**: The simple config generator (`generateConnectionsConfig`) does not currently support Aptos pathways. You must use the regular configuration format. - -3. **Configuration resources**: - - [LayerZero Configuration Documentation](https://docs.layerzero.network/v2/developers/evm/create-lz-oapp/configuring-pathways) - - [Example Aptos Move config file](https://github.com/LayerZero-Labs/devtools/blob/main/examples/oft-aptos-move/move.layerzero.config.ts) - -:information_source: For bidirectional Solana ↔ Aptos communication, deploy both: - -- Solana → Aptos: Use this example -- Aptos → Solana: Use the [OFT Aptos Move example](https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-aptos-move) diff --git a/examples/oft-solana/docs/move.layerzero.config.ts b/examples/oft-solana/docs/move.layerzero.config.ts new file mode 100644 index 0000000000..ac5091bb12 --- /dev/null +++ b/examples/oft-solana/docs/move.layerzero.config.ts @@ -0,0 +1,121 @@ +// NOTE: This config is an example for wiring Solana -> Aptos. +// See wiring-to-aptos.md for more information. +import { EndpointId } from '@layerzerolabs/lz-definitions' +import { ExecutorOptionType } from '@layerzerolabs/lz-v2-utilities' +import { OAppOmniGraphHardhat, OmniPointHardhat } from '@layerzerolabs/toolbox-hardhat' + +enum MsgType { + SEND = 1, + SEND_AND_CALL = 2, +} +const aptosContract: OmniPointHardhat = { + eid: EndpointId.APTOS_V2_TESTNET as EndpointId, + address: 'YOUR_DEPLOYED_APTOS_OFT_ADDRESS', +} +const solanaContract: OmniPointHardhat = { + eid: EndpointId.SOLANA_V2_TESTNET as EndpointId, + address: 'YOUR_DEPLOYED_SOLANA_OAPP_ADDRESS', +} + +const layerZeroDVNAptosAddress = '0x756f8ab056688d22687740f4a9aeec3b361170b28d08b719e28c4d38eed1043e' +const layerZeroDVNSolanaAddress = '4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb' + +const config: OAppOmniGraphHardhat = { + contracts: [ + { + contract: solanaContract, + config: { + owner: 'YOUR_SOLANA_OWNER_ADDRESS', + delegate: 'YOUR_SOLANA_OWNER_ADDRESS', + }, + }, + { + contract: aptosContract, + config: { + owner: 'YOUR_APTOS_ACCOUNT_ADDRESS', + delegate: 'YOUR_APTOS_ACCOUNT_ADDRESS', + }, + }, + ], + connections: [ + { + from: aptosContract, + to: solanaContract, + config: { + enforcedOptions: [ + { + msgType: MsgType.SEND_AND_CALL, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 5_000, + value: 0, + }, + ], + sendLibrary: '0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690', + receiveLibraryConfig: { + receiveLibrary: '0xcc1c03aed42e2841211865758b5efe93c0dde2cb7a2a5dc6cf25a4e33ad23690', + gracePeriod: BigInt(0), + }, + sendConfig: { + executorConfig: { + maxMessageSize: 10_000, + executor: '0x93353700091200ef9fdc536ce6a86182cc7e62da25f94356be9421c6310b9585', + }, + ulnConfig: { + confirmations: BigInt(10), + requiredDVNs: [layerZeroDVNAptosAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + receiveConfig: { + ulnConfig: { + confirmations: BigInt(15), + requiredDVNs: [layerZeroDVNAptosAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + }, + }, + { + from: solanaContract, + to: aptosContract, + config: { + enforcedOptions: [ + { + msgType: 1, + optionType: ExecutorOptionType.LZ_RECEIVE, + gas: 200_000, + value: 0, + }, + ], + sendLibrary: '7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH', + receiveLibraryConfig: { + receiveLibrary: '7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH', + gracePeriod: BigInt(0), + }, + sendConfig: { + executorConfig: { + maxMessageSize: 10_000, + executor: 'AwrbHeCyniXaQhiJZkLhgWdUCteeWSGaSN1sTfLiY7xK', + }, + ulnConfig: { + confirmations: BigInt(15), + requiredDVNs: [layerZeroDVNSolanaAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + receiveConfig: { + ulnConfig: { + confirmations: BigInt(10), + requiredDVNs: [layerZeroDVNSolanaAddress], + optionalDVNs: [], + optionalDVNThreshold: 0, + }, + }, + }, + }, + ], +} +export default config diff --git a/examples/oft-solana/docs/wiring-to-aptos.md b/examples/oft-solana/docs/wiring-to-aptos.md new file mode 100644 index 0000000000..79a6ce17b4 --- /dev/null +++ b/examples/oft-solana/docs/wiring-to-aptos.md @@ -0,0 +1,63 @@ +### Wiring Solana to Move-VM (Aptos, Initia, Movement, etc.) + +:warning: **Important Limitations for Solana ↔ Move-VM Pathways** + +Currently, you can only perform **one-way wiring from Solana → Move-VM** using this example. For the reverse pathway (Move-VM → Solana), you must use the [OFT Aptos Move example](https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-aptos-move). Please follow the deployment instructions in the README.md of the `oft-aptos-move` example for deploying to Move-VM and configuring the Move-VM → Solana pathway. + +#### Configuration Steps for Solana → Aptos + +:information_source: **For bidirectional Solana ↔ Aptos communication, deploy both examples:** + +- **Solana → Aptos**: Use this example +- **Aptos → Solana**: Use the [OFT Aptos Move example](https://github.com/LayerZero-Labs/devtools/tree/main/examples/oft-aptos-move) + +#### Deployment Process + +1. **Create and Deploy Solana Example** + + - Create the Solana example: `LZ_ENABLE_SOLANA_OAPP_EXAMPLE=1 npx create-lz-oapp@latest` + - Deploy to Solana following the deployment instructions in the Solana example README + +2. **Create and Deploy Aptos Move Example** + + - Create the Aptos Move example: `LZ_ENABLE_EXPERIMENTAL_MOVE_VM_EXAMPLES=1 npx create-lz-oapp@latest` + - Deploy to Aptos following the deployment and initialization instructions in the Aptos example README + + Your directory structure should look like this: + + ``` + your-folder/ + ├── your-aptos-example/ + └── your-solana-example/ + ``` + +3. **Configure Solana Example** + + - Navigate to the Solana example directory + - Use the example configuration at `./docs/move.layerzero.config.ts` + - Fill out the configuration with your desired values + - Replace `./layerzero.config.ts` with the completed `move.layerzero.config.ts` file + +4. **Wire Solana to Aptos** + + Execute the following commands in the Solana example directory: + + ```bash + npx hardhat lz:oapp:solana:init-config --oapp-config move.layerzero.config.ts + ``` + + ```bash + npx hardhat lz:oapp:wire --oapp-config move.layerzero.config.ts + ``` + + If these commands succeed, you have successfully wired the Solana contract to the Aptos Move contract. + +5. **Wire Aptos to Solana** + + - Transfer the `move.layerzero.config.ts` file from the Solana example to the Aptos Move example directory + - In the Aptos Move example directory, run: + ```bash + pnpm run lz:sdk:move:wire --oapp-config move.layerzero.config.ts + ``` + + If this command succeeds, you are ready to test the bidirectional pathway.