diff --git a/.gitignore b/.gitignore index 9b1ee42e..fb008cf9 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,5 @@ dist # Finder (MacOS) folder config .DS_Store + +plugins/javy_chainlink_sdk/target/ \ No newline at end of file diff --git a/bun.lock b/bun.lock index 3fc34eac..51153326 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@bufbuild/protoc-gen-es": "2.6.3", "@standard-schema/spec": "1.0.0", "rxjs": "7.8.2", + "viem": "2.34.0", "zod": "3.25.76", }, "devDependencies": { @@ -21,6 +22,8 @@ }, }, "packages": { + "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "@biomejs/biome": ["@biomejs/biome@2.1.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.1.3", "@biomejs/cli-darwin-x64": "2.1.3", "@biomejs/cli-linux-arm64": "2.1.3", "@biomejs/cli-linux-arm64-musl": "2.1.3", "@biomejs/cli-linux-x64": "2.1.3", "@biomejs/cli-linux-x64-musl": "2.1.3", "@biomejs/cli-win32-arm64": "2.1.3", "@biomejs/cli-win32-x64": "2.1.3" }, "bin": { "biome": "bin/biome" } }, "sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.1.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA=="], @@ -61,12 +64,24 @@ "@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.6.3", "", { "dependencies": { "@bufbuild/protobuf": "2.6.3", "@typescript/vfs": "^1.5.2", "typescript": "5.4.5" } }, "sha512-VceMuxeRukxGeABfo34SXq0VqY1MU+mzS+PBf0HAWo97ylFut8F6sQ3mV0tKiM08UQ/xQco7lxCn83BkoxrWrA=="], + "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + + "@noble/curves": ["@noble/curves@1.9.6", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA=="], + + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], + + "@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + + "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], @@ -77,6 +92,8 @@ "@typescript/vfs": ["@typescript/vfs@1.6.1", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA=="], + "abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], @@ -91,6 +108,8 @@ "dprint-node": ["dprint-node@1.0.8", "", { "dependencies": { "detect-libc": "^1.0.3" } }, "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg=="], + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], @@ -105,12 +124,16 @@ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="], + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "ox": ["ox@0.8.7", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.0.8", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-W1f0FiMf9NZqtHPEDEAEkyzZDwbIKfmH2qmQx8NNiQ/9JhxrSblmtLJsSfTtQG5YKowLOnBlLVguCyxm/7ztxw=="], + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], @@ -135,6 +158,10 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "viem": ["viem@2.34.0", "", { "dependencies": { "@noble/curves": "1.9.6", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.0.8", "isows": "1.0.7", "ox": "0.8.7", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-HJZG9Wt0DLX042MG0PK17tpataxtdAEhpta9/Q44FqKwy3xZMI5Lx4jF+zZPuXFuYjZ68R0PXqRwlswHs6r4gA=="], + + "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="], diff --git a/package.json b/package.json index 255502a0..d0517fa4 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@bufbuild/protoc-gen-es": "2.6.3", "@standard-schema/spec": "1.0.0", "rxjs": "7.8.2", + "viem": "2.34.0", "zod": "3.25.76" }, "devDependencies": { diff --git a/src/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen.ts b/src/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen.ts index 2a07901e..40465fa6 100644 --- a/src/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen.ts +++ b/src/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen.ts @@ -60,6 +60,8 @@ import { * * Capability ID: evm@1.0.0 * Default Mode: Mode.DON + * Capability Name: evm + * Capability Version: 1.0.0 */ export class ClientCapability { /** The capability ID for this service */ @@ -68,8 +70,29 @@ export class ClientCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "evm"; + static readonly CAPABILITY_VERSION = "1.0.0"; + + /** Available chain selectors */ + static readonly SUPPORTED_CHAINS = { + "avalanche-mainnet": 6433500567565415381n, + "avalanche-testnet-fuji": 14767482510784806043n, + "binance_smart_chain-mainnet-opbnb-1": 465944652040885897n, + "binance_smart_chain-testnet-opbnb-1": 13274425992935471758n, + "ethereum-mainnet": 5009297550715157269n, + "ethereum-mainnet-arbitrum-1": 4949039107694359620n, + "ethereum-mainnet-optimism-1": 3734403246176062136n, + "ethereum-testnet-sepolia": 16015286601757825753n, + "ethereum-testnet-sepolia-arbitrum-1": 3478487238524512106n, + "ethereum-testnet-sepolia-base-1": 10344971235874465080n, + "ethereum-testnet-sepolia-optimism-1": 5224473277236331295n, + "polygon-mainnet": 4051577828743386545n, + "polygon-testnet-amoy": 16281711391670634445n + } as const; + constructor( - private readonly mode: Mode = ClientCapability.DEFAULT_MODE + private readonly mode: Mode = ClientCapability.DEFAULT_MODE, + private readonly chainSelector?: bigint ) {} async callContract(input: CallContractRequestJson): Promise { @@ -77,16 +100,20 @@ export class ClientCapability { typeUrl: getTypeUrl(CallContractRequestSchema), value: toBinary(CallContractRequestSchema, fromJson(CallContractRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "CallContract", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "CallContract", mode: this.mode, }); @@ -94,7 +121,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "CallContract", mode: this.mode, }); @@ -109,16 +136,20 @@ export class ClientCapability { typeUrl: getTypeUrl(FilterLogsRequestSchema), value: toBinary(FilterLogsRequestSchema, fromJson(FilterLogsRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "FilterLogs", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "FilterLogs", mode: this.mode, }); @@ -126,7 +157,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "FilterLogs", mode: this.mode, }); @@ -141,16 +172,20 @@ export class ClientCapability { typeUrl: getTypeUrl(BalanceAtRequestSchema), value: toBinary(BalanceAtRequestSchema, fromJson(BalanceAtRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "BalanceAt", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "BalanceAt", mode: this.mode, }); @@ -158,7 +193,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "BalanceAt", mode: this.mode, }); @@ -173,16 +208,20 @@ export class ClientCapability { typeUrl: getTypeUrl(EstimateGasRequestSchema), value: toBinary(EstimateGasRequestSchema, fromJson(EstimateGasRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "EstimateGas", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "EstimateGas", mode: this.mode, }); @@ -190,7 +229,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "EstimateGas", mode: this.mode, }); @@ -205,16 +244,20 @@ export class ClientCapability { typeUrl: getTypeUrl(GetTransactionByHashRequestSchema), value: toBinary(GetTransactionByHashRequestSchema, fromJson(GetTransactionByHashRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "GetTransactionByHash", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "GetTransactionByHash", mode: this.mode, }); @@ -222,7 +265,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "GetTransactionByHash", mode: this.mode, }); @@ -237,16 +280,20 @@ export class ClientCapability { typeUrl: getTypeUrl(GetTransactionReceiptRequestSchema), value: toBinary(GetTransactionReceiptRequestSchema, fromJson(GetTransactionReceiptRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "GetTransactionReceipt", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "GetTransactionReceipt", mode: this.mode, }); @@ -254,7 +301,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "GetTransactionReceipt", mode: this.mode, }); @@ -269,16 +316,20 @@ export class ClientCapability { typeUrl: getTypeUrl(HeaderByNumberRequestSchema), value: toBinary(HeaderByNumberRequestSchema, fromJson(HeaderByNumberRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "HeaderByNumber", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "HeaderByNumber", mode: this.mode, }); @@ -286,7 +337,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "HeaderByNumber", mode: this.mode, }); @@ -301,16 +352,20 @@ export class ClientCapability { typeUrl: getTypeUrl(RegisterLogTrackingRequestSchema), value: toBinary(RegisterLogTrackingRequestSchema, fromJson(RegisterLogTrackingRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "RegisterLogTracking", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "RegisterLogTracking", mode: this.mode, }); @@ -318,7 +373,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "RegisterLogTracking", mode: this.mode, }); @@ -333,16 +388,20 @@ export class ClientCapability { typeUrl: getTypeUrl(UnregisterLogTrackingRequestSchema), value: toBinary(UnregisterLogTrackingRequestSchema, fromJson(UnregisterLogTrackingRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "UnregisterLogTracking", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "UnregisterLogTracking", mode: this.mode, }); @@ -350,7 +409,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "UnregisterLogTracking", mode: this.mode, }); @@ -369,16 +428,20 @@ export class ClientCapability { typeUrl: getTypeUrl(WriteReportRequestSchema), value: toBinary(WriteReportRequestSchema, fromJson(WriteReportRequestSchema, input)), }; + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? `${ClientCapability.CAPABILITY_NAME}:ChainSelector:${this.chainSelector}@${ClientCapability.CAPABILITY_VERSION}` + : ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "WriteReport", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "WriteReport", mode: this.mode, }); @@ -386,7 +449,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "WriteReport", mode: this.mode, }); diff --git a/src/generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen.ts b/src/generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen.ts index d9db33f1..b57e801a 100644 --- a/src/generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen.ts +++ b/src/generated-sdk/capabilities/internal/actionandtrigger/v1/basic_sdk_gen.ts @@ -24,6 +24,8 @@ import { * * Capability ID: basic-test-action-trigger@1.0.0 * Default Mode: Mode.DON + * Capability Name: basic-test-action-trigger + * Capability Version: 1.0.0 */ export class BasicCapability { /** The capability ID for this service */ @@ -32,6 +34,10 @@ export class BasicCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "basic-test-action-trigger"; + static readonly CAPABILITY_VERSION = "1.0.0"; + + constructor( private readonly mode: Mode = BasicCapability.DEFAULT_MODE ) {} @@ -41,16 +47,17 @@ export class BasicCapability { typeUrl: getTypeUrl(InputSchema), value: toBinary(InputSchema, fromJson(InputSchema, input)), }; + const capabilityId = BasicCapability.CAPABILITY_ID; return callCapability({ - capabilityId: BasicCapability.CAPABILITY_ID, + capabilityId, method: "Action", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: BasicCapability.CAPABILITY_ID, + capabilityId, method: "Action", mode: this.mode, }); @@ -58,7 +65,7 @@ export class BasicCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: BasicCapability.CAPABILITY_ID, + capabilityId, method: "Action", mode: this.mode, }); diff --git a/src/generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen.ts b/src/generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen.ts index adea931c..d2ab2af3 100644 --- a/src/generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen.ts +++ b/src/generated-sdk/capabilities/internal/basicaction/v1/basicaction_sdk_gen.ts @@ -18,6 +18,8 @@ import { * * Capability ID: basic-test-action@1.0.0 * Default Mode: Mode.DON + * Capability Name: basic-test-action + * Capability Version: 1.0.0 */ export class BasicActionCapability { /** The capability ID for this service */ @@ -26,6 +28,10 @@ export class BasicActionCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "basic-test-action"; + static readonly CAPABILITY_VERSION = "1.0.0"; + + constructor( private readonly mode: Mode = BasicActionCapability.DEFAULT_MODE ) {} @@ -35,16 +41,17 @@ export class BasicActionCapability { typeUrl: getTypeUrl(InputsSchema), value: toBinary(InputsSchema, fromJson(InputsSchema, input)), }; + const capabilityId = BasicActionCapability.CAPABILITY_ID; return callCapability({ - capabilityId: BasicActionCapability.CAPABILITY_ID, + capabilityId, method: "PerformAction", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: BasicActionCapability.CAPABILITY_ID, + capabilityId, method: "PerformAction", mode: this.mode, }); @@ -52,7 +59,7 @@ export class BasicActionCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: BasicActionCapability.CAPABILITY_ID, + capabilityId, method: "PerformAction", mode: this.mode, }); diff --git a/src/generated-sdk/capabilities/internal/basictrigger/v1/basic_sdk_gen.ts b/src/generated-sdk/capabilities/internal/basictrigger/v1/basic_sdk_gen.ts index a5641b5c..7f8f0a58 100644 --- a/src/generated-sdk/capabilities/internal/basictrigger/v1/basic_sdk_gen.ts +++ b/src/generated-sdk/capabilities/internal/basictrigger/v1/basic_sdk_gen.ts @@ -20,6 +20,8 @@ import { * * Capability ID: basic-test-trigger@1.0.0 * Default Mode: Mode.DON + * Capability Name: basic-test-trigger + * Capability Version: 1.0.0 */ export class BasicCapability { /** The capability ID for this service */ @@ -28,6 +30,10 @@ export class BasicCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "basic-test-trigger"; + static readonly CAPABILITY_VERSION = "1.0.0"; + + constructor( private readonly mode: Mode = BasicCapability.DEFAULT_MODE ) {} diff --git a/src/generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen.ts b/src/generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen.ts index 4dcc0cd8..7d9d6344 100644 --- a/src/generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen.ts +++ b/src/generated-sdk/capabilities/internal/consensus/v1alpha/consensus_sdk_gen.ts @@ -24,6 +24,8 @@ import { * * Capability ID: consensus@1.0.0-alpha * Default Mode: Mode.DON + * Capability Name: consensus + * Capability Version: 1.0.0-alpha */ export class ConsensusCapability { /** The capability ID for this service */ @@ -32,6 +34,10 @@ export class ConsensusCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "consensus"; + static readonly CAPABILITY_VERSION = "1.0.0-alpha"; + + constructor( private readonly mode: Mode = ConsensusCapability.DEFAULT_MODE ) {} @@ -41,16 +47,17 @@ export class ConsensusCapability { typeUrl: getTypeUrl(SimpleConsensusInputsSchema), value: toBinary(SimpleConsensusInputsSchema, fromJson(SimpleConsensusInputsSchema, input)), }; + const capabilityId = ConsensusCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ConsensusCapability.CAPABILITY_ID, + capabilityId, method: "Simple", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ConsensusCapability.CAPABILITY_ID, + capabilityId, method: "Simple", mode: this.mode, }); @@ -58,7 +65,7 @@ export class ConsensusCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ConsensusCapability.CAPABILITY_ID, + capabilityId, method: "Simple", mode: this.mode, }); @@ -73,16 +80,17 @@ export class ConsensusCapability { typeUrl: getTypeUrl(ReportRequestSchema), value: toBinary(ReportRequestSchema, fromJson(ReportRequestSchema, input)), }; + const capabilityId = ConsensusCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ConsensusCapability.CAPABILITY_ID, + capabilityId, method: "Report", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ConsensusCapability.CAPABILITY_ID, + capabilityId, method: "Report", mode: this.mode, }); @@ -90,7 +98,7 @@ export class ConsensusCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ConsensusCapability.CAPABILITY_ID, + capabilityId, method: "Report", mode: this.mode, }); diff --git a/src/generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen.ts b/src/generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen.ts index 07d81bee..4e4afbf7 100644 --- a/src/generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen.ts +++ b/src/generated-sdk/capabilities/internal/nodeaction/v1/basicaction_sdk_gen.ts @@ -18,6 +18,8 @@ import { * * Capability ID: basic-test-node-action@1.0.0 * Default Mode: Mode.NODE + * Capability Name: basic-test-node-action + * Capability Version: 1.0.0 */ export class BasicActionCapability { /** The capability ID for this service */ @@ -26,6 +28,10 @@ export class BasicActionCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.NODE; + static readonly CAPABILITY_NAME = "basic-test-node-action"; + static readonly CAPABILITY_VERSION = "1.0.0"; + + constructor( private readonly mode: Mode = BasicActionCapability.DEFAULT_MODE ) {} @@ -35,16 +41,17 @@ export class BasicActionCapability { typeUrl: getTypeUrl(NodeInputsSchema), value: toBinary(NodeInputsSchema, fromJson(NodeInputsSchema, input)), }; + const capabilityId = BasicActionCapability.CAPABILITY_ID; return callCapability({ - capabilityId: BasicActionCapability.CAPABILITY_ID, + capabilityId, method: "PerformAction", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: BasicActionCapability.CAPABILITY_ID, + capabilityId, method: "PerformAction", mode: this.mode, }); @@ -52,7 +59,7 @@ export class BasicActionCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: BasicActionCapability.CAPABILITY_ID, + capabilityId, method: "PerformAction", mode: this.mode, }); diff --git a/src/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen.ts b/src/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen.ts index c79aacf8..d071503b 100644 --- a/src/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen.ts +++ b/src/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen.ts @@ -18,6 +18,8 @@ import { * * Capability ID: http-actions@1.0.0-alpha * Default Mode: Mode.NODE + * Capability Name: http-actions + * Capability Version: 1.0.0-alpha */ export class ClientCapability { /** The capability ID for this service */ @@ -26,6 +28,10 @@ export class ClientCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.NODE; + static readonly CAPABILITY_NAME = "http-actions"; + static readonly CAPABILITY_VERSION = "1.0.0-alpha"; + + constructor( private readonly mode: Mode = ClientCapability.DEFAULT_MODE ) {} @@ -35,16 +41,17 @@ export class ClientCapability { typeUrl: getTypeUrl(RequestSchema), value: toBinary(RequestSchema, fromJson(RequestSchema, input)), }; + const capabilityId = ClientCapability.CAPABILITY_ID; return callCapability({ - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "SendRequest", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "SendRequest", mode: this.mode, }); @@ -52,7 +59,7 @@ export class ClientCapability { if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ClientCapability.CAPABILITY_ID, + capabilityId, method: "SendRequest", mode: this.mode, }); diff --git a/src/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen.ts b/src/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen.ts index ee62eca6..996d55de 100644 --- a/src/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen.ts +++ b/src/generated-sdk/capabilities/networking/http/v1alpha/http_sdk_gen.ts @@ -20,6 +20,8 @@ import { * * Capability ID: http-trigger@1.0.0-alpha * Default Mode: Mode.DON + * Capability Name: http-trigger + * Capability Version: 1.0.0-alpha */ export class HTTPCapability { /** The capability ID for this service */ @@ -28,6 +30,10 @@ export class HTTPCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "http-trigger"; + static readonly CAPABILITY_VERSION = "1.0.0-alpha"; + + constructor( private readonly mode: Mode = HTTPCapability.DEFAULT_MODE ) {} diff --git a/src/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen.ts b/src/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen.ts index 52c707d2..81cc4a6b 100644 --- a/src/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen.ts +++ b/src/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen.ts @@ -22,6 +22,8 @@ import { * * Capability ID: cron-trigger@1.0.0 * Default Mode: Mode.DON + * Capability Name: cron-trigger + * Capability Version: 1.0.0 */ export class CronCapability { /** The capability ID for this service */ @@ -30,6 +32,10 @@ export class CronCapability { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = Mode.DON; + static readonly CAPABILITY_NAME = "cron-trigger"; + static readonly CAPABILITY_VERSION = "1.0.0"; + + constructor( private readonly mode: Mode = CronCapability.DEFAULT_MODE ) {} diff --git a/src/generator/generate-action.ts b/src/generator/generate-action.ts index 9fef9382..453064ab 100644 --- a/src/generator/generate-action.ts +++ b/src/generator/generate-action.ts @@ -6,29 +6,40 @@ import type { DescMethod } from "@bufbuild/protobuf"; * @param method - The method descriptor * @param methodName - The camelCase method name * @param capabilityClassName - The class name of the capability object + * @param hasChainSelector - Whether this capability supports chainSelector routing * @returns The generated action method code */ export function generateActionMethod( method: DescMethod, methodName: string, - capabilityClassName: string + capabilityClassName: string, + hasChainSelector: boolean = false ): string { + const capabilityIdLogic = hasChainSelector + ? ` + // Include chainSelector in capability ID for routing when specified + const capabilityId = this.chainSelector + ? \`\${${capabilityClassName}.CAPABILITY_NAME}:ChainSelector:\${this.chainSelector}@\${${capabilityClassName}.CAPABILITY_VERSION}\` + : ${capabilityClassName}.CAPABILITY_ID;` + : ` + const capabilityId = ${capabilityClassName}.CAPABILITY_ID;`; + return ` async ${methodName}(input: ${method.input.name}Json): Promise<${method.output.name}> { const payload = { typeUrl: getTypeUrl(${method.input.name}Schema), value: toBinary(${method.input.name}Schema, fromJson(${method.input.name}Schema, input)), - }; + };${capabilityIdLogic} return callCapability({ - capabilityId: ${capabilityClassName}.CAPABILITY_ID, + capabilityId, method: "${method.name}", mode: this.mode, payload, }).then((capabilityResponse: CapabilityResponse) => { if (capabilityResponse.response.case === "error") { throw new CapabilityError(capabilityResponse.response.value, { - capabilityId: ${capabilityClassName}.CAPABILITY_ID, + capabilityId, method: "${method.name}", mode: this.mode, }); @@ -36,7 +47,7 @@ export function generateActionMethod( if (capabilityResponse.response.case !== "payload") { throw new CapabilityError("No payload in response", { - capabilityId: ${capabilityClassName}.CAPABILITY_ID, + capabilityId, method: "${method.name}", mode: this.mode, }); diff --git a/src/generator/generate-sdk.ts b/src/generator/generate-sdk.ts index 655070ec..c93cb7d6 100644 --- a/src/generator/generate-sdk.ts +++ b/src/generator/generate-sdk.ts @@ -127,6 +127,10 @@ export function generateSdk(file: GenFile, outputDir: string) { const capabilityClassName = `${service.name}Capability`; + // Check if this capability supports chainSelector via labels + const chainSelectorLabel = capOption.labels?.ChainSelector as any; + const hasChainSelector = chainSelectorLabel?.kind?.case === "uint64Label"; + // Generate methods const methods = service.methods .map((method) => { @@ -154,7 +158,12 @@ export function generateSdk(file: GenFile, outputDir: string) { } // Generate action method - return generateActionMethod(method, methodName, capabilityClassName); + return generateActionMethod( + method, + methodName, + capabilityClassName, + hasChainSelector + ); }) .join("\n"); @@ -167,6 +176,32 @@ export function generateSdk(file: GenFile, outputDir: string) { // Determine default mode from metadata: NODE is specifically stated, DON otherwise. const defaultMode = capOption.mode === Mode.NODE ? "Mode.NODE" : "Mode.DON"; + const [capabilityName, capabilityVersion] = + capOption.capabilityId.split("@"); + + // Extract chainSelector support + let chainSelectorSupport = ""; + let constructorParams = `private readonly mode: Mode = ${service.name}Capability.DEFAULT_MODE`; + + if (hasChainSelector && capOption.labels) { + const chainSelectorLabel = capOption.labels.ChainSelector as any; + if ( + chainSelectorLabel?.kind?.case === "uint64Label" && + chainSelectorLabel?.kind?.value?.defaults + ) { + const defaults = chainSelectorLabel.kind.value.defaults; + chainSelectorSupport = ` + /** Available chain selectors */ + static readonly SUPPORTED_CHAINS = { +${Object.entries(defaults) + .map(([key, value]) => ` "${key}": ${value}n`) + .join(",\n")} + } as const;`; + + constructorParams = `${constructorParams},\n private readonly chainSelector?: bigint`; + } + } + // Add JSDoc with metadata information const classComment = ` /** @@ -174,6 +209,8 @@ export function generateSdk(file: GenFile, outputDir: string) { * * Capability ID: ${capOption.capabilityId} * Default Mode: ${defaultMode} + * Capability Name: ${capabilityName} + * Capability Version: ${capabilityVersion} */`; // Generate the complete file @@ -186,8 +223,12 @@ export class ${capabilityClassName} { /** The default execution mode for this capability */ static readonly DEFAULT_MODE = ${defaultMode}; + static readonly CAPABILITY_NAME = "${capabilityName}"; + static readonly CAPABILITY_VERSION = "${capabilityVersion}"; +${chainSelectorSupport} + constructor( - private readonly mode: Mode = ${service.name}Capability.DEFAULT_MODE + ${constructorParams} ) {} ${methods} } diff --git a/src/sdk/cre/index.ts b/src/sdk/cre/index.ts index a2c48d03..63c68f43 100644 --- a/src/sdk/cre/index.ts +++ b/src/sdk/cre/index.ts @@ -5,6 +5,7 @@ import { prepareRuntime } from "@cre/sdk/utils/prepare-runtime"; import { handler, Runner } from "@cre/sdk/workflow"; import { configHandler } from "@cre/sdk/utils/config"; import { CronCapability } from "@cre/generated-sdk/capabilities/scheduler/cron/v1/cron_sdk_gen"; +import { ClientCapability as EVMClient } from "@cre/generated-sdk/capabilities/blockchain/evm/v1alpha/client_sdk_gen"; import { ClientCapability as HTTPClient } from "@cre/generated-sdk/capabilities/networking/http/v1alpha/client_sdk_gen"; export type { Environment } from "@cre/sdk/workflow"; @@ -16,6 +17,7 @@ export const cre = { capabilities: { CronCapability, HTTPClient, + EVMClient, }, config: configHandler, handler, diff --git a/src/sdk/utils/capabilities/call-capability.ts b/src/sdk/utils/capabilities/call-capability.ts index 634a1501..8f3a7cb0 100644 --- a/src/sdk/utils/capabilities/call-capability.ts +++ b/src/sdk/utils/capabilities/call-capability.ts @@ -35,9 +35,19 @@ export function callCapability({ // - Block NODE-mode calls while currently in DON mode if (mode === Mode.DON) runtimeGuards.assertDonSafe(); if (mode === Mode.NODE) runtimeGuards.assertNodeSafe(); - const callbackId = doRequestAsync({ capabilityId, method, mode, payload }); + + const callbackId = doRequestAsync({ + capabilityId, + method, + mode, + payload, + }); return new LazyPromise(async () => { - return awaitAsyncRequest(callbackId, { capabilityId, method, mode }); + return awaitAsyncRequest(callbackId, { + capabilityId, + method, + mode, + }); }); } diff --git a/src/sdk/utils/hex-utils.ts b/src/sdk/utils/hex-utils.ts new file mode 100644 index 00000000..d22d90eb --- /dev/null +++ b/src/sdk/utils/hex-utils.ts @@ -0,0 +1,30 @@ +/** + * Hex conversion utilities for blockchain data + * + * Note: BigInt utilities are available in @cre/sdk/utils/values/value + * Use val.bigint() or jsBigIntToProtoBigInt() for BigInt conversions + */ + +/** + * Convert hex string to Uint8Array + */ +export const hexToBytes = (hexStr: string): Uint8Array => { + if (!hexStr.startsWith("0x")) { + throw new Error(`Invalid hex string: ${hexStr}`); + } + const hex = hexStr.slice(2); + const bytes = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + bytes[i / 2] = Number.parseInt(hex.substr(i, 2), 16); + } + return bytes; +}; + +/** + * Convert Uint8Array to hex string with 0x prefix + */ +export const bytesToHex = (bytes: Uint8Array): string => { + return `0x${Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join("")}`; +}; diff --git a/src/workflows/on-chain/on-chain.ts b/src/workflows/on-chain/on-chain.ts new file mode 100644 index 00000000..a3016d13 --- /dev/null +++ b/src/workflows/on-chain/on-chain.ts @@ -0,0 +1,148 @@ +import { z } from "zod"; +import { cre, type Environment } from "@cre/sdk/cre"; +import { runInNodeMode } from "@cre/sdk/runtime/run-in-node-mode"; +import { SimpleConsensusInputsSchema } from "@cre/generated/sdk/v1alpha/sdk_pb"; +import { create } from "@bufbuild/protobuf"; +import { + consensusDescriptorMedian, + observationValue, +} from "@cre/sdk/utils/values/consensus"; +import { sendResponseValue } from "@cre/sdk/utils/send-response-value"; +import { val } from "@cre/sdk/utils/values/value"; +import { encodeFunctionData, decodeFunctionResult, type Hex } from "viem"; +import { bytesToHex } from "@cre/sdk/utils/hex-utils"; + +// Storage contract ABI - we only need the 'get' function +// TODO: In production, load ABI from external file or contract metadata +// following Go SDK patterns for ABI management +const STORAGE_ABI = [ + { + inputs: [], + name: "get", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, +] as const; + +// Config struct defines the parameters that can be passed to the workflow +const configSchema = z.object({ + schedule: z.string(), + apiUrl: z.string(), + evms: z.array( + z.object({ + storageAddress: z.string(), + chainSelector: z.number(), + }) + ), +}); + +type Config = z.infer; + +// onCronTrigger is the callback function that gets executed when the cron trigger fires +const onCronTrigger = async (env: Environment): Promise => { + env.logger?.log("Hello, Calculator! Workflow triggered."); + + if (!env.config?.evms || env.config.evms.length === 0) { + throw new Error("No EVM configuration provided"); + } + + // Step 1: Fetch offchain data using consensus (from Part 2) + const offchainValue = await runInNodeMode(async () => { + const http = new cre.capabilities.HTTPClient(); + const resp = await http.sendRequest({ + url: env.config?.apiUrl, + method: "GET", + }); + + const bodyStr = new TextDecoder().decode(resp.body); + const num = Number.parseFloat(bodyStr.trim()); + + return create(SimpleConsensusInputsSchema, { + observation: observationValue(val.float64(num)), + descriptors: consensusDescriptorMedian, + }); + }); + + env.logger?.log("Successfully fetched offchain value"); + + // Get the first EVM configuration from the list + const evmConfig = env.config.evms[0]; + + // Step 2: Read onchain data using the EVM client with chainSelector + const evmClient = new cre.capabilities.EVMClient( + undefined, // use default mode + BigInt(evmConfig.chainSelector) // pass chainSelector as BigInt + ); + + // Encode the contract call data for the 'get' function + const callData = encodeFunctionData({ + abi: STORAGE_ABI, + functionName: "get", + }); + + const contractCall = await evmClient.callContract({ + call: { + from: "0x0000000000000000000000000000000000000000", // zero address for view calls + to: evmConfig.storageAddress, + data: callData, + }, + blockNumber: { + absVal: "03", // 3 for finalized block + sign: "-1", // negative + }, + }); + + // Decode the result + const decodedResult = decodeFunctionResult({ + abi: STORAGE_ABI, + functionName: "get", + data: bytesToHex(contractCall.data) as Hex, + }); + + const onchainValue = decodedResult as bigint; + env.logger?.log("Successfully read onchain value"); + + // Step 3: Combine the results - convert offchain float to bigint and add + const offchainFloat = + offchainValue.value.case === "float64Value" ? offchainValue.value.value : 0; + + const offchainBigInt = BigInt(Math.floor(offchainFloat)); + const finalResult = onchainValue + offchainBigInt; + + env.logger?.log("Final calculated result"); + + sendResponseValue( + val.mapValue({ + FinalResult: val.bigint(finalResult), + }) + ); +}; + +// InitWorkflow is the required entry point for a CRE workflow +// The runner calls this function to initialize the workflow and register its handlers +const initWorkflow = (env: Environment) => { + const cron = new cre.capabilities.CronCapability(); + + return [ + cre.handler( + // Use the schedule from our config file + cron.trigger({ schedule: env.config?.schedule }), + onCronTrigger + ), + ]; +}; + +// main is the entry point for the workflow +export async function main() { + try { + const runner = await cre.newRunner({ + configSchema: configSchema, + }); + await runner.run(initWorkflow); + } catch (error) { + console.log("error", JSON.stringify(error, null, 2)); + } +} + +main();