diff --git a/packages/protocol-utils-evm/.eslintignore b/packages/protocol-utils-evm/.eslintignore
new file mode 100644
index 000000000..db4c6d9b6
--- /dev/null
+++ b/packages/protocol-utils-evm/.eslintignore
@@ -0,0 +1,2 @@
+dist
+node_modules
\ No newline at end of file
diff --git a/packages/protocol-utils-evm/.prettierignore b/packages/protocol-utils-evm/.prettierignore
new file mode 100644
index 000000000..763301fc0
--- /dev/null
+++ b/packages/protocol-utils-evm/.prettierignore
@@ -0,0 +1,2 @@
+dist/
+node_modules/
\ No newline at end of file
diff --git a/packages/protocol-utils-evm/README.md b/packages/protocol-utils-evm/README.md
new file mode 100644
index 000000000..948e87333
--- /dev/null
+++ b/packages/protocol-utils-evm/README.md
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+@layerzerolabs/protocol-utils-evm
+
+
+
+
+
+
+
+
+
+
+
+Utilities for LayerZero EVM protocol contracts.
+
+## Installation
+
+```bash
+npm install --save @layerzerolabs/protocol-utils-evm
+```
+
+```bash
+yarn install @layerzerolabs/protocol-utils-evm
+```
+
+```bash
+pnpm install @layerzerolabs/protocol-utils-evm
+```
diff --git a/packages/protocol-utils-evm/jest.config.js b/packages/protocol-utils-evm/jest.config.js
new file mode 100644
index 000000000..16148cfb1
--- /dev/null
+++ b/packages/protocol-utils-evm/jest.config.js
@@ -0,0 +1,8 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1',
+ },
+};
diff --git a/packages/protocol-utils-evm/package.json b/packages/protocol-utils-evm/package.json
new file mode 100644
index 000000000..eb54c9b54
--- /dev/null
+++ b/packages/protocol-utils-evm/package.json
@@ -0,0 +1,69 @@
+{
+ "name": "@layerzerolabs/protocol-utils-evm",
+ "version": "0.0.1",
+ "private": true,
+ "description": "Utilities for LayerZero EVM protocol contracts",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/LayerZero-Labs/lz-utils.git",
+ "directory": "packages/protocol-utils-evm"
+ },
+ "license": "MIT",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "require": "./dist/index.js",
+ "import": "./dist/index.mjs"
+ },
+ "./*": {
+ "types": "./dist/*.d.ts",
+ "require": "./dist/*.js",
+ "import": "./dist/*.mjs"
+ }
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "./dist/index.*"
+ ],
+ "scripts": {
+ "prebuild": "npx tsc --noEmit -p tsconfig.build.json",
+ "build": "npx tsup",
+ "clean": "rm -rf dist",
+ "dev": "npx tsup --watch",
+ "lint": "npx eslint '**/*.{js,ts,json}'",
+ "test": "jest --passWithNoTests"
+ },
+ "dependencies": {
+ "p-memoize": "~4.0.1"
+ },
+ "devDependencies": {
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/contracts": "^5.7.0",
+ "@ethersproject/providers": "^5.7.0",
+ "@layerzerolabs/lz-definitions": "~1.5.68",
+ "@layerzerolabs/protocol-utils": "~0.0.1",
+ "@layerzerolabs/test-utils": "~0.0.1",
+ "@layerzerolabs/utils": "~0.0.1",
+ "@layerzerolabs/utils-evm": "~0.0.1",
+ "@types/jest": "^29.5.10",
+ "fast-check": "^3.14.0",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.1.1",
+ "ts-node": "^10.9.1",
+ "tsup": "~8.0.1",
+ "typescript": "^5.2.2"
+ },
+ "peerDependencies": {
+ "@ethersproject/abstract-provider": "^5.7.0",
+ "@ethersproject/abstract-signer": "^5.7.0",
+ "@ethersproject/contracts": "^5.7.0",
+ "@ethersproject/providers": "^5.7.0",
+ "@layerzerolabs/lz-definitions": "~1.5.68",
+ "@layerzerolabs/protocol-utils": "~0.0.1",
+ "@layerzerolabs/utils": "~0.0.1",
+ "@layerzerolabs/utils-evm": "~0.0.1"
+ }
+}
\ No newline at end of file
diff --git a/packages/protocol-utils-evm/src/endpoint/index.ts b/packages/protocol-utils-evm/src/endpoint/index.ts
new file mode 100644
index 000000000..7db67b18a
--- /dev/null
+++ b/packages/protocol-utils-evm/src/endpoint/index.ts
@@ -0,0 +1 @@
+export * from './sdk'
diff --git a/packages/protocol-utils-evm/src/endpoint/sdk.ts b/packages/protocol-utils-evm/src/endpoint/sdk.ts
new file mode 100644
index 000000000..6fd33e56e
--- /dev/null
+++ b/packages/protocol-utils-evm/src/endpoint/sdk.ts
@@ -0,0 +1,62 @@
+import type { IEndpoint } from '@layerzerolabs/protocol-utils'
+import { formatEid, type Address, type OmniTransaction } from '@layerzerolabs/utils'
+import type { EndpointId } from '@layerzerolabs/lz-definitions'
+import { ignoreZero, makeZero, omniContractToPoint, type OmniContract } from '@layerzerolabs/utils-evm'
+
+export class Endpoint implements IEndpoint {
+ constructor(public readonly contract: OmniContract) {}
+
+ async defaultReceiveLibrary(eid: EndpointId): Promise {
+ return ignoreZero(await this.contract.contract.defaultReceiveLibrary(eid))
+ }
+
+ async setDefaultReceiveLibrary(
+ eid: EndpointId,
+ lib: string | null | undefined,
+ gracePeriod: number = 0
+ ): Promise {
+ const data = this.contract.contract.interface.encodeFunctionData('setDefaultReceiveLibrary', [
+ eid,
+ makeZero(lib),
+ gracePeriod,
+ ])
+
+ return {
+ ...this.createTransaction(data),
+ description: `Setting default receive library for ${formatEid(eid)} to ${makeZero(lib)}`,
+ }
+ }
+
+ async defaultSendLibrary(eid: EndpointId): Promise {
+ return ignoreZero(await this.contract.contract.defaultSendLibrary(eid))
+ }
+
+ async setDefaultSendLibrary(eid: EndpointId, lib: Address | null | undefined): Promise {
+ const data = this.contract.contract.interface.encodeFunctionData('setDefaultSendLibrary', [eid, makeZero(lib)])
+
+ return {
+ ...this.createTransaction(data),
+ description: `Setting default send library for ${formatEid(eid)} to ${lib}`,
+ }
+ }
+
+ isRegisteredLibrary(lib: Address): Promise {
+ return this.contract.contract.isRegisteredLibrary(lib)
+ }
+
+ async registerLibrary(lib: string): Promise {
+ const data = this.contract.contract.interface.encodeFunctionData('registerLibrary', [lib])
+
+ return {
+ ...this.createTransaction(data),
+ description: `Registering library ${lib}`,
+ }
+ }
+
+ protected createTransaction(data: string): OmniTransaction {
+ return {
+ point: omniContractToPoint(this.contract),
+ data,
+ }
+ }
+}
diff --git a/packages/protocol-utils-evm/src/index.ts b/packages/protocol-utils-evm/src/index.ts
new file mode 100644
index 000000000..ff12d505a
--- /dev/null
+++ b/packages/protocol-utils-evm/src/index.ts
@@ -0,0 +1,2 @@
+export * from './endpoint'
+export * from './uln302'
diff --git a/packages/protocol-utils-evm/src/uln302/index.ts b/packages/protocol-utils-evm/src/uln302/index.ts
new file mode 100644
index 000000000..7db67b18a
--- /dev/null
+++ b/packages/protocol-utils-evm/src/uln302/index.ts
@@ -0,0 +1 @@
+export * from './sdk'
diff --git a/packages/protocol-utils-evm/src/uln302/schema.ts b/packages/protocol-utils-evm/src/uln302/schema.ts
new file mode 100644
index 000000000..e77f21fde
--- /dev/null
+++ b/packages/protocol-utils-evm/src/uln302/schema.ts
@@ -0,0 +1,23 @@
+import { AddressSchema } from '@layerzerolabs/utils'
+import { BigNumberishBigintSchema } from '@layerzerolabs/utils-evm'
+import { z } from 'zod'
+
+/**
+ * Schema for parsing an ethers-specific UlnConfig into a common format
+ */
+export const Uln302UlnConfigSchema = z.object({
+ confirmations: BigNumberishBigintSchema,
+ requiredDVNs: z.array(AddressSchema),
+ optionalDVNs: z.array(AddressSchema),
+ optionalDVNThreshold: z.coerce.number().int().nonnegative(),
+})
+
+/**
+ * Schema for parsing a common UlnConfig into a ethers-specific format
+ */
+export const Uln302UlnConfigInputSchema = Uln302UlnConfigSchema.transform((config) => ({
+ ...config,
+ confirmations: String(config.confirmations),
+ requiredDVNCount: config.requiredDVNs.length,
+ optionalDVNCount: config.optionalDVNs.length,
+}))
diff --git a/packages/protocol-utils-evm/src/uln302/sdk.ts b/packages/protocol-utils-evm/src/uln302/sdk.ts
new file mode 100644
index 000000000..cc77763e0
--- /dev/null
+++ b/packages/protocol-utils-evm/src/uln302/sdk.ts
@@ -0,0 +1,51 @@
+import type { EndpointId } from '@layerzerolabs/lz-definitions'
+import type { IUln302, Uln302ExecutorConfig, Uln302UlnConfig } from '@layerzerolabs/protocol-utils'
+import { formatEid, type OmniTransaction } from '@layerzerolabs/utils'
+import { omniContractToPoint, type OmniContract } from '@layerzerolabs/utils-evm'
+import { Uln302UlnConfigInputSchema, Uln302UlnConfigSchema } from './schema'
+
+export class Uln302 implements IUln302 {
+ constructor(public readonly contract: OmniContract) {}
+
+ async getUlnConfig(eid: EndpointId, address: string): Promise {
+ const config = await this.contract.contract.getUlnConfig(address, eid)
+
+ // Now we convert the ethers-specific object into the common structure
+ //
+ // Here we need to spread the config into an object because what ethers gives us
+ // is actually an array with extra properties
+ return Uln302UlnConfigSchema.parse({ ...config })
+ }
+
+ async setDefaultExecutorConfig(eid: EndpointId, config: Uln302ExecutorConfig): Promise {
+ const data = this.contract.contract.interface.encodeFunctionData('setDefaultExecutorConfigs', [
+ [{ eid, config }],
+ ])
+
+ return this.createTransaction(data)
+ }
+
+ async setDefaultUlnConfig(eid: EndpointId, config: Uln302UlnConfig): Promise {
+ const serializedConfig = Uln302UlnConfigInputSchema.parse(config)
+ const data = this.contract.contract.interface.encodeFunctionData('setDefaultUlnConfigs', [
+ [
+ {
+ eid,
+ config: serializedConfig,
+ },
+ ],
+ ])
+
+ return {
+ ...this.createTransaction(data),
+ description: `Setting default ULN config for ${formatEid(eid)}: ${JSON.stringify(serializedConfig)}`,
+ }
+ }
+
+ protected createTransaction(data: string): OmniTransaction {
+ return {
+ point: omniContractToPoint(this.contract),
+ data,
+ }
+ }
+}
diff --git a/packages/protocol-utils-evm/tsconfig.build.json b/packages/protocol-utils-evm/tsconfig.build.json
new file mode 100644
index 000000000..0507620e8
--- /dev/null
+++ b/packages/protocol-utils-evm/tsconfig.build.json
@@ -0,0 +1,4 @@
+{
+ "extends": "./tsconfig.json",
+ "exclude": ["node_modules", "dist", "test"]
+}
diff --git a/packages/protocol-utils-evm/tsconfig.json b/packages/protocol-utils-evm/tsconfig.json
new file mode 100644
index 000000000..acecf2754
--- /dev/null
+++ b/packages/protocol-utils-evm/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "exclude": ["dist", "node_modules"],
+ "include": ["src", "test", "*.config.ts"],
+ "compilerOptions": {
+ "types": ["node", "jest"],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/packages/protocol-utils-evm/tsup.config.ts b/packages/protocol-utils-evm/tsup.config.ts
new file mode 100644
index 000000000..7ef46a5ad
--- /dev/null
+++ b/packages/protocol-utils-evm/tsup.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'tsup'
+
+export default defineConfig([
+ {
+ entry: ['src/index.ts'],
+ outDir: './dist',
+ clean: true,
+ dts: true,
+ sourcemap: true,
+ splitting: false,
+ treeshake: true,
+ format: ['esm', 'cjs'],
+ },
+])
diff --git a/packages/protocol-utils/.eslintignore b/packages/protocol-utils/.eslintignore
new file mode 100644
index 000000000..0f295f243
--- /dev/null
+++ b/packages/protocol-utils/.eslintignore
@@ -0,0 +1,3 @@
+.turbo
+dist
+node_modules
\ No newline at end of file
diff --git a/packages/protocol-utils/.eslintrc.json b/packages/protocol-utils/.eslintrc.json
new file mode 100644
index 000000000..be97c53fb
--- /dev/null
+++ b/packages/protocol-utils/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../../.eslintrc.json"
+}
diff --git a/packages/protocol-utils/README.md b/packages/protocol-utils/README.md
new file mode 100644
index 000000000..75fac67c1
--- /dev/null
+++ b/packages/protocol-utils/README.md
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+@layerzerolabs/protocol-utils
+
+
+
+
+
+
+
+
+
+
+
+## Installation
+
+```bash
+npm install --save @layerzerolabs/protocol-utils
+```
+
+```bash
+yarn install @layerzerolabs/protocol-utils
+```
+
+```bash
+pnpm install @layerzerolabs/protocol-utils
+```
diff --git a/packages/protocol-utils/jest.config.js b/packages/protocol-utils/jest.config.js
new file mode 100644
index 000000000..16148cfb1
--- /dev/null
+++ b/packages/protocol-utils/jest.config.js
@@ -0,0 +1,8 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1',
+ },
+};
diff --git a/packages/protocol-utils/package.json b/packages/protocol-utils/package.json
new file mode 100644
index 000000000..f875c9f06
--- /dev/null
+++ b/packages/protocol-utils/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@layerzerolabs/protocol-utils",
+ "version": "0.0.1",
+ "private": true,
+ "description": "Utilities for working with LayerZero protocol contracts",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/LayerZero-Labs/lz-utils.git",
+ "directory": "packages/protocol-utils"
+ },
+ "license": "MIT",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "require": "./dist/index.js",
+ "import": "./dist/index.mjs"
+ }
+ },
+ "main": "dist/index.js",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "files": [
+ "dist/",
+ "LICENSE"
+ ],
+ "scripts": {
+ "prebuild": "tsc -noEmit",
+ "build": "npx tsup",
+ "clean": "rm -rf dist",
+ "lint": "npx eslint '**/*.{js,ts,json}'",
+ "test": "jest --passWithNoTests"
+ },
+ "devDependencies": {
+ "@layerzerolabs/lz-definitions": "~1.5.68",
+ "@layerzerolabs/test-utils": "~0.0.1",
+ "@layerzerolabs/utils": "~0.0.1",
+ "@types/jest": "^29.5.10",
+ "fast-check": "^3.14.0",
+ "jest": "^29.7.0",
+ "ts-jest": "^29.1.1",
+ "ts-node": "^10.9.1",
+ "tslib": "~2.6.2",
+ "tsup": "~8.0.1",
+ "typescript": "^5.2.2",
+ "zod": "^3.22.4"
+ },
+ "peerDependencies": {
+ "@layerzerolabs/lz-definitions": "~1.5.68",
+ "@layerzerolabs/utils": "~0.0.1",
+ "zod": "^3.22.4"
+ }
+}
\ No newline at end of file
diff --git a/packages/protocol-utils/src/endpoint/config.ts b/packages/protocol-utils/src/endpoint/config.ts
new file mode 100644
index 000000000..78d7a87cd
--- /dev/null
+++ b/packages/protocol-utils/src/endpoint/config.ts
@@ -0,0 +1,58 @@
+import { flattenTransactions, type OmniTransaction } from '@layerzerolabs/utils'
+import type { EndpointFactory, EndpointOmniGraph } from './types'
+
+export type EndpointConfigurator = (graph: EndpointOmniGraph, createSdk: EndpointFactory) => Promise
+
+export const configureEndpoint: EndpointConfigurator = async (graph, createSdk) =>
+ flattenTransactions([
+ ...(await configureEndpointDefaultReceiveLibraries(graph, createSdk)),
+ ...(await configureEndpointDefaultSendLibraries(graph, createSdk)),
+ ])
+
+export const configureEndpointDefaultReceiveLibraries: EndpointConfigurator = async (graph, createSdk) =>
+ flattenTransactions(
+ await Promise.all(
+ graph.connections.map(async ({ vector: { from, to }, config }): Promise => {
+ const sdk = await createSdk(from)
+ const address = await sdk.defaultReceiveLibrary(to.eid)
+
+ // If the library is already set as default, do nothing
+ if (config.defaultReceiveLibrary === address) return []
+
+ // We need to check whether the library has been registered before we set is as default
+ const isRegistered = await sdk.isRegisteredLibrary(config.defaultReceiveLibrary)
+
+ return flattenTransactions([
+ // We only want to register the library if it has not been registered yet
+ isRegistered ? undefined : await sdk.registerLibrary(config.defaultReceiveLibrary),
+ await sdk.setDefaultReceiveLibrary(
+ to.eid,
+ config.defaultReceiveLibrary,
+ config.defaultReceiveLibraryGracePeriod
+ ),
+ ])
+ })
+ )
+ )
+
+export const configureEndpointDefaultSendLibraries: EndpointConfigurator = async (graph, createSdk) =>
+ flattenTransactions(
+ await Promise.all(
+ graph.connections.map(async ({ vector: { from, to }, config }): Promise => {
+ const sdk = await createSdk(from)
+ const address = await sdk.defaultSendLibrary(to.eid)
+
+ // If the library is already set as default, do nothing
+ if (config.defaultSendLibrary === address) return []
+
+ // We need to check whether the library has been registered before we set is as default
+ const isRegistered = await sdk.isRegisteredLibrary(config.defaultSendLibrary)
+
+ return flattenTransactions([
+ // We only want to register the library if it has not been registered yet
+ isRegistered ? undefined : await sdk.registerLibrary(config.defaultSendLibrary),
+ await sdk.setDefaultSendLibrary(to.eid, config.defaultSendLibrary),
+ ])
+ })
+ )
+ )
diff --git a/packages/protocol-utils/src/endpoint/index.ts b/packages/protocol-utils/src/endpoint/index.ts
new file mode 100644
index 000000000..39bdac610
--- /dev/null
+++ b/packages/protocol-utils/src/endpoint/index.ts
@@ -0,0 +1,2 @@
+export * from './config'
+export * from './types'
diff --git a/packages/protocol-utils/src/endpoint/types.ts b/packages/protocol-utils/src/endpoint/types.ts
new file mode 100644
index 000000000..d3f8ec1f5
--- /dev/null
+++ b/packages/protocol-utils/src/endpoint/types.ts
@@ -0,0 +1,27 @@
+import type { Address, OmniGraph, OmniPointBasedFactory, OmniTransaction } from '@layerzerolabs/utils'
+import type { EndpointId } from '@layerzerolabs/lz-definitions'
+
+export interface IEndpoint {
+ defaultReceiveLibrary(eid: EndpointId): Promise
+ setDefaultReceiveLibrary(
+ eid: EndpointId,
+ lib: Address | null | undefined,
+ gracePeriod?: number
+ ): Promise
+
+ defaultSendLibrary(eid: EndpointId): Promise
+ setDefaultSendLibrary(eid: EndpointId, lib: Address | null | undefined): Promise
+
+ isRegisteredLibrary(lib: Address): Promise
+ registerLibrary(lib: Address): Promise
+}
+
+export interface EndpointEdgeConfig {
+ defaultReceiveLibrary: Address
+ defaultReceiveLibraryGracePeriod?: number
+ defaultSendLibrary: Address
+}
+
+export type EndpointOmniGraph = OmniGraph
+
+export type EndpointFactory = OmniPointBasedFactory
diff --git a/packages/protocol-utils/src/index.ts b/packages/protocol-utils/src/index.ts
new file mode 100644
index 000000000..ff12d505a
--- /dev/null
+++ b/packages/protocol-utils/src/index.ts
@@ -0,0 +1,2 @@
+export * from './endpoint'
+export * from './uln302'
diff --git a/packages/protocol-utils/src/uln302/config.ts b/packages/protocol-utils/src/uln302/config.ts
new file mode 100644
index 000000000..ff8a6f074
--- /dev/null
+++ b/packages/protocol-utils/src/uln302/config.ts
@@ -0,0 +1,36 @@
+import { flattenTransactions, type OmniTransaction } from '@layerzerolabs/utils'
+import type { Uln302Factory, Uln302OmniGraph } from './types'
+
+export type Uln302Configurator = (graph: Uln302OmniGraph, createSdk: Uln302Factory) => Promise
+
+export const configureUln302: Uln302Configurator = async (graph, createSdk) =>
+ flattenTransactions([
+ ...(await configureUln302DefaultExecutorConfigs(graph, createSdk)),
+ ...(await configureUln302DefaultUlnConfigs(graph, createSdk)),
+ ])
+
+export const configureUln302DefaultExecutorConfigs: Uln302Configurator = async (graph, createSdk) =>
+ flattenTransactions(
+ await Promise.all(
+ graph.contracts.map(async ({ point, config }): Promise => {
+ const sdk = await createSdk(point)
+
+ return Promise.all(
+ config.defaultExecutorConfigs.map(([eid, config]) => sdk.setDefaultExecutorConfig(eid, config))
+ )
+ })
+ )
+ )
+
+export const configureUln302DefaultUlnConfigs: Uln302Configurator = async (graph, createSdk) =>
+ flattenTransactions(
+ await Promise.all(
+ graph.contracts.map(async ({ point, config }): Promise => {
+ const sdk = await createSdk(point)
+
+ return Promise.all(
+ config.defaultUlnConfigs.map(([eid, config]) => sdk.setDefaultUlnConfig(eid, config))
+ )
+ })
+ )
+ )
diff --git a/packages/protocol-utils/src/uln302/index.ts b/packages/protocol-utils/src/uln302/index.ts
new file mode 100644
index 000000000..39bdac610
--- /dev/null
+++ b/packages/protocol-utils/src/uln302/index.ts
@@ -0,0 +1,2 @@
+export * from './config'
+export * from './types'
diff --git a/packages/protocol-utils/src/uln302/types.ts b/packages/protocol-utils/src/uln302/types.ts
new file mode 100644
index 000000000..9cbe79577
--- /dev/null
+++ b/packages/protocol-utils/src/uln302/types.ts
@@ -0,0 +1,29 @@
+import type { Address, OmniGraph, OmniPointBasedFactory, OmniTransaction } from '@layerzerolabs/utils'
+import type { EndpointId } from '@layerzerolabs/lz-definitions'
+
+export interface IUln302 {
+ getUlnConfig(eid: EndpointId, address: Address): Promise
+ setDefaultExecutorConfig(eid: EndpointId, config: Uln302ExecutorConfig): Promise
+ setDefaultUlnConfig(eid: EndpointId, config: Uln302UlnConfig): Promise
+}
+
+export interface Uln302ExecutorConfig {
+ maxMessageSize: number
+ executor: string
+}
+
+export interface Uln302UlnConfig {
+ confirmations: bigint | string | number
+ optionalDVNThreshold: number
+ requiredDVNs: string[]
+ optionalDVNs: string[]
+}
+
+export interface Uln302NodeConfig {
+ defaultExecutorConfigs: [eid: EndpointId, config: Uln302ExecutorConfig][]
+ defaultUlnConfigs: [eid: EndpointId, config: Uln302UlnConfig][]
+}
+
+export type Uln302OmniGraph = OmniGraph
+
+export type Uln302Factory = OmniPointBasedFactory
diff --git a/packages/protocol-utils/tsconfig.json b/packages/protocol-utils/tsconfig.json
new file mode 100644
index 000000000..f083b2ecb
--- /dev/null
+++ b/packages/protocol-utils/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "extends": "../../tsconfig.json",
+ "exclude": ["dist", "node_modules"],
+ "include": ["src", "test"],
+ "compilerOptions": {
+ "types": ["node", "jest"],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/packages/protocol-utils/tsup.config.ts b/packages/protocol-utils/tsup.config.ts
new file mode 100644
index 000000000..b0e373950
--- /dev/null
+++ b/packages/protocol-utils/tsup.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'tsup'
+
+export default defineConfig({
+ entry: ['src/index.ts'],
+ outDir: './dist',
+ clean: true,
+ dts: true,
+ sourcemap: true,
+ splitting: false,
+ treeshake: true,
+ format: ['esm', 'cjs'],
+})
diff --git a/packages/ua-utils-evm-hardhat-test/package.json b/packages/ua-utils-evm-hardhat-test/package.json
index 058bcaa10..2d76e2e4c 100644
--- a/packages/ua-utils-evm-hardhat-test/package.json
+++ b/packages/ua-utils-evm-hardhat-test/package.json
@@ -11,7 +11,6 @@
"license": "MIT",
"scripts": {
"lint": "npx eslint '**/*.{js,ts,json}'",
- "pretest": "npx hardhat --network vengaboys deploy --reset && npx hardhat --network britney deploy --reset",
"test": "npx hardhat test"
},
"devDependencies": {
@@ -26,6 +25,8 @@
"@layerzerolabs/lz-definitions": "~1.5.68",
"@layerzerolabs/lz-evm-sdk-v1": "~1.5.68",
"@layerzerolabs/lz-evm-sdk-v2": "~1.5.68",
+ "@layerzerolabs/protocol-utils": "~0.0.1",
+ "@layerzerolabs/protocol-utils-evm": "~0.0.1",
"@layerzerolabs/ua-utils": "~0.1.0",
"@layerzerolabs/ua-utils-evm": "~0.0.1",
"@layerzerolabs/ua-utils-evm-hardhat": "~0.0.1",
diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts
new file mode 100644
index 000000000..aa9d6235d
--- /dev/null
+++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/endpoint.ts
@@ -0,0 +1,172 @@
+import {
+ createConnectedContractFactory,
+ createLogger,
+ createNetworkEnvironmentFactory,
+ createSignerFactory,
+ OmniGraphBuilderHardhat,
+ type OmniGraphHardhat,
+} from '@layerzerolabs/utils-evm-hardhat'
+import deploy from '../../deploy/001_bootstrap'
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+import { omniContractToPoint } from '@layerzerolabs/utils-evm'
+import {
+ configureEndpoint,
+ EndpointEdgeConfig,
+ EndpointFactory,
+ Uln302NodeConfig,
+ Uln302ExecutorConfig,
+ configureUln302,
+ Uln302Factory,
+ Uln302UlnConfig,
+} from '@layerzerolabs/protocol-utils'
+import { Endpoint, Uln302 } from '@layerzerolabs/protocol-utils-evm'
+import { formatOmniPoint } from '@layerzerolabs/utils'
+
+export const ethEndpoint = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' }
+export const ethReceiveUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'ReceiveUln302' }
+export const ethSendUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'SendUln302' }
+export const avaxEndpoint = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'EndpointV2' }
+export const avaxReceiveUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'ReceiveUln302' }
+export const avaxSendUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'SendUln302' }
+
+export const defaultExecutorConfig: Uln302ExecutorConfig = {
+ maxMessageSize: 1024,
+ executor: '0x0000000000000000000000000000000000000001',
+}
+
+export const defaultUlnConfig: Uln302UlnConfig = {
+ confirmations: BigInt(1),
+ requiredDVNs: ['0x0000000000000000000000000000000000000002', '0x0000000000000000000000000000000000000003'],
+ optionalDVNs: [],
+ optionalDVNThreshold: 0,
+}
+
+/**
+ * Helper function that deploys a fresh endpoint infrastructure:
+ *
+ * - EndpointV2
+ * - ReceiveUln302
+ * - SendUln302
+ *
+ * After deploying, it will wire up the elements with minimal configuration
+ */
+export const setupDefaultEndpoint = async (): Promise => {
+ // This is the tooling we are going to need
+ const logger = createLogger()
+ const environmentFactory = createNetworkEnvironmentFactory()
+ const contractFactory = createConnectedContractFactory()
+ const signerFactory = createSignerFactory()
+ const endpointSdkFactory: EndpointFactory = async (point) => new Endpoint(await contractFactory(point))
+ const ulnSdkFactory: Uln302Factory = async (point) => new Uln302(await contractFactory(point))
+
+ // First we deploy the endpoint
+ await deploy(await environmentFactory(EndpointId.ETHEREUM_MAINNET))
+ await deploy(await environmentFactory(EndpointId.AVALANCHE_MAINNET))
+
+ // For the graphs, we'll also need the pointers to the contracts
+ const ethSendUlnPoint = omniContractToPoint(await contractFactory(ethSendUln))
+ const avaxSendUlnPoint = omniContractToPoint(await contractFactory(avaxSendUln))
+ const ethReceiveUlnPoint = omniContractToPoint(await contractFactory(ethReceiveUln))
+ const avaxReceiveUlnPoint = omniContractToPoint(await contractFactory(avaxReceiveUln))
+
+ // This is the graph for SendUln302
+ const sendUlnConfig: OmniGraphHardhat = {
+ contracts: [
+ {
+ contract: ethSendUln,
+ config: {
+ defaultUlnConfigs: [[EndpointId.AVALANCHE_MAINNET, defaultUlnConfig]],
+ defaultExecutorConfigs: [[EndpointId.AVALANCHE_MAINNET, defaultExecutorConfig]],
+ },
+ },
+ {
+ contract: avaxSendUln,
+ config: {
+ defaultUlnConfigs: [[EndpointId.ETHEREUM_MAINNET, defaultUlnConfig]],
+ defaultExecutorConfigs: [[EndpointId.ETHEREUM_MAINNET, defaultExecutorConfig]],
+ },
+ },
+ ],
+ connections: [],
+ }
+
+ // This is the graph for ReceiveUln302
+ const receiveUlnConfig: OmniGraphHardhat = {
+ contracts: [
+ {
+ contract: ethReceiveUln,
+ config: {
+ defaultUlnConfigs: [[EndpointId.AVALANCHE_MAINNET, defaultUlnConfig]],
+ defaultExecutorConfigs: [],
+ },
+ },
+ {
+ contract: avaxReceiveUln,
+ config: {
+ defaultUlnConfigs: [[EndpointId.ETHEREUM_MAINNET, defaultUlnConfig]],
+ defaultExecutorConfigs: [],
+ },
+ },
+ ],
+ connections: [],
+ }
+
+ // This is the graph for EndpointV2
+ const config: OmniGraphHardhat = {
+ contracts: [
+ {
+ contract: ethEndpoint,
+ config: undefined,
+ },
+ {
+ contract: avaxEndpoint,
+ config: undefined,
+ },
+ ],
+ connections: [
+ {
+ from: ethEndpoint,
+ to: avaxEndpoint,
+ config: {
+ defaultReceiveLibrary: ethReceiveUlnPoint.address,
+ defaultSendLibrary: ethSendUlnPoint.address,
+ },
+ },
+ {
+ from: avaxEndpoint,
+ to: ethEndpoint,
+ config: {
+ defaultReceiveLibrary: avaxReceiveUlnPoint.address,
+ defaultSendLibrary: avaxSendUlnPoint.address,
+ },
+ },
+ ],
+ }
+
+ // Now we compile a list of all the transactions that need to be executed for the ULNs and Endpoints
+ const builderEndpoint = await OmniGraphBuilderHardhat.fromConfig(config, contractFactory)
+ const endpointTransactions = await configureEndpoint(builderEndpoint.graph, endpointSdkFactory)
+ const builderSendUln = await OmniGraphBuilderHardhat.fromConfig(sendUlnConfig, contractFactory)
+ const sendUlnTransactions = await configureUln302(builderSendUln.graph, ulnSdkFactory)
+ const builderReceiveUln = await OmniGraphBuilderHardhat.fromConfig(receiveUlnConfig, contractFactory)
+ const receiveUlnTransactions = await configureUln302(builderReceiveUln.graph, ulnSdkFactory)
+
+ const transactions = [...sendUlnTransactions, ...receiveUlnTransactions, ...endpointTransactions]
+
+ logger.debug(`Executing ${transactions.length} transactions`)
+
+ for (const transaction of transactions) {
+ const signer = await signerFactory(transaction.point.eid)
+ const description = transaction.description ?? '[no description]'
+
+ logger.debug(`${formatOmniPoint(transaction.point)}: ${description}`)
+
+ const response = await signer.signAndSend(transaction)
+ logger.debug(`${formatOmniPoint(transaction.point)}: ${description}: ${response.transactionHash}`)
+
+ const receipt = await response.wait()
+ logger.debug(`${formatOmniPoint(transaction.point)}: ${description}: ${receipt.transactionHash}`)
+ }
+
+ logger.debug(`Done configuring endpoint`)
+}
diff --git a/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts b/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts
new file mode 100644
index 000000000..886b012ea
--- /dev/null
+++ b/packages/ua-utils-evm-hardhat-test/test/__utils__/oapp.ts
@@ -0,0 +1,10 @@
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+import { createNetworkEnvironmentFactory } from '@layerzerolabs/utils-evm-hardhat'
+import deploy from '../../deploy/002_oapp'
+
+export const deployOApp = async () => {
+ const environmentFactory = createNetworkEnvironmentFactory()
+
+ await deploy(await environmentFactory(EndpointId.ETHEREUM_MAINNET))
+ await deploy(await environmentFactory(EndpointId.AVALANCHE_MAINNET))
+}
diff --git a/packages/ua-utils-evm-hardhat-test/test/config.test.ts b/packages/ua-utils-evm-hardhat-test/test/config.test.ts
deleted file mode 100644
index 9d9ca6527..000000000
--- a/packages/ua-utils-evm-hardhat-test/test/config.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { expect } from 'chai'
-import { describe } from 'mocha'
-import { getNetworkRuntimeEnvironment } from '@layerzerolabs/utils-evm-hardhat'
-import { HardhatRuntimeEnvironment } from 'hardhat/types'
-
-const NETWORK_NAMES = ['vengaboys', 'britney']
-
-describe('config', () => {
- NETWORK_NAMES.forEach((networkName) => {
- describe(`Network '${networkName}`, () => {
- let environment: HardhatRuntimeEnvironment
-
- before(async () => {
- environment = await getNetworkRuntimeEnvironment(networkName)
- })
-
- it('should have an endpoint deployed', async () => {
- const endpoint = await environment.ethers.getContract('EndpointV2')
- const eid = await endpoint.eid()
-
- expect(environment.network.config.eid).to.be.a('number')
- expect(eid).to.eql(environment.network.config.eid)
- })
- })
- })
-})
diff --git a/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts b/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts
new file mode 100644
index 000000000..b9e0246c3
--- /dev/null
+++ b/packages/ua-utils-evm-hardhat-test/test/endpoint/config.test.ts
@@ -0,0 +1,76 @@
+import { createConnectedContractFactory } from '@layerzerolabs/utils-evm-hardhat'
+import type { OmniPoint } from '@layerzerolabs/utils'
+import { omniContractToPoint } from '@layerzerolabs/utils-evm'
+import { expect } from 'chai'
+import { describe } from 'mocha'
+import { EndpointId } from '@layerzerolabs/lz-definitions'
+import { defaultUlnConfig, setupDefaultEndpoint } from '../__utils__/endpoint'
+import { Endpoint, Uln302 } from '@layerzerolabs/protocol-utils-evm'
+
+describe('endpoint/config', () => {
+ const ethEndpoint = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'EndpointV2' }
+ const ethReceiveUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'ReceiveUln302' }
+ const ethSendUln = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'SendUln302' }
+ const avaxEndpoint = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'EndpointV2' }
+ const avaxReceiveUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'ReceiveUln302' }
+ const avaxSendUln = { eid: EndpointId.AVALANCHE_MAINNET, contractName: 'SendUln302' }
+
+ beforeEach(async () => {
+ await setupDefaultEndpoint()
+ })
+
+ describe('endpoint', () => {
+ it('should have default libraries configured', async () => {
+ // This is the required tooling we need to set up
+ const connectedContractFactory = createConnectedContractFactory()
+ const sdkFactory = async (point: OmniPoint) => new Endpoint(await connectedContractFactory(point))
+
+ // Now for the purposes of the test, we need to get coordinates of our contracts
+ const ethEndpointPoint = omniContractToPoint(await connectedContractFactory(ethEndpoint))
+ const avaxEndpointPoint = omniContractToPoint(await connectedContractFactory(avaxEndpoint))
+
+ const ethEndpointSdk = await sdkFactory(ethEndpointPoint)
+ const avaxEndpointSdk = await sdkFactory(avaxEndpointPoint)
+
+ // First let's check the send libraries
+ const ethDefaultSendLib = await ethEndpointSdk.defaultSendLibrary(avaxEndpointPoint.eid)
+ const avaxDefaultSendLib = await avaxEndpointSdk.defaultSendLibrary(ethEndpointPoint.eid)
+
+ const ethSendUlnPoint = omniContractToPoint(await connectedContractFactory(ethSendUln))
+ const avaxSendUlnPoint = omniContractToPoint(await connectedContractFactory(avaxSendUln))
+
+ expect(ethDefaultSendLib).to.equal(ethSendUlnPoint.address)
+ expect(avaxDefaultSendLib).to.equal(avaxSendUlnPoint.address)
+
+ // Then let's check the receive libraries
+ const ethDefaultReceiveLib = await ethEndpointSdk.defaultReceiveLibrary(avaxEndpointPoint.eid)
+ const avaxDefaultReceiveLib = await avaxEndpointSdk.defaultReceiveLibrary(ethEndpointPoint.eid)
+
+ const ethReceiveUlnPoint = omniContractToPoint(await connectedContractFactory(ethReceiveUln))
+ const avaxReceiveUlnPoint = omniContractToPoint(await connectedContractFactory(avaxReceiveUln))
+
+ expect(ethDefaultReceiveLib).to.equal(ethReceiveUlnPoint.address)
+ expect(avaxDefaultReceiveLib).to.equal(avaxReceiveUlnPoint.address)
+ })
+ })
+
+ describe('sendUln302', () => {
+ it('should have default executors configured', async () => {
+ // This is the required tooling we need to set up
+ const connectedContractFactory = createConnectedContractFactory()
+ const sdkFactory = async (point: OmniPoint) => new Uln302(await connectedContractFactory(point))
+
+ const ethSendUlnPoint = omniContractToPoint(await connectedContractFactory(ethSendUln))
+ const avaxSendUlnPoint = omniContractToPoint(await connectedContractFactory(avaxSendUln))
+
+ const ethSendUlnSdk = await sdkFactory(ethSendUlnPoint)
+ const avaxSendUlnSdk = await sdkFactory(avaxSendUlnPoint)
+
+ const ethConfig = await ethSendUlnSdk.getUlnConfig(avaxSendUlnPoint.eid, avaxSendUlnPoint.address)
+ const avaxConfig = await avaxSendUlnSdk.getUlnConfig(ethSendUlnPoint.eid, ethSendUlnPoint.address)
+
+ expect(ethConfig).to.eql(defaultUlnConfig)
+ expect(avaxConfig).to.eql(defaultUlnConfig)
+ })
+ })
+})
diff --git a/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts b/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts
index 635230ae3..975b13bf5 100644
--- a/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts
+++ b/packages/ua-utils-evm-hardhat-test/test/oapp/config.test.ts
@@ -12,6 +12,8 @@ import { omniContractToPoint } from '@layerzerolabs/utils-evm'
import { expect } from 'chai'
import { describe } from 'mocha'
import { EndpointId } from '@layerzerolabs/lz-definitions'
+import { setupDefaultEndpoint } from '../__utils__/endpoint'
+import { deployOApp } from '../__utils__/oapp'
describe('oapp/config', () => {
const ethContract = { eid: EndpointId.ETHEREUM_MAINNET, contractName: 'DefaultOApp' }
@@ -43,6 +45,11 @@ describe('oapp/config', () => {
],
}
+ beforeEach(async () => {
+ await setupDefaultEndpoint()
+ await deployOApp()
+ })
+
it('should return all setPeer transactions', async () => {
// This is the required tooling we need to set up
const contractFactory = createContractFactory()