From a115848b232a85dd234e2df0a55c40a748437d2f Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 19 Sep 2024 13:56:55 +0530 Subject: [PATCH 01/12] feat(contracts): add solidity proxy contract for registration --- .../contracts/relayer/evm/Controllable.sol | 17 ++++ .../relayer/evm/RegistrationProxy.sol | 93 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 packages/contracts/contracts/relayer/evm/Controllable.sol create mode 100644 packages/contracts/contracts/relayer/evm/RegistrationProxy.sol diff --git a/packages/contracts/contracts/relayer/evm/Controllable.sol b/packages/contracts/contracts/relayer/evm/Controllable.sol new file mode 100644 index 0000000..8c4bd2a --- /dev/null +++ b/packages/contracts/contracts/relayer/evm/Controllable.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +abstract contract Controllable is Ownable { + mapping(address => bool) public controllers; + + modifier onlyController { + require(controllers[msg.sender]); + _; + } + + function setController(address controller, bool status) external onlyOwner { + controllers[controller] = status; + } +} diff --git a/packages/contracts/contracts/relayer/evm/RegistrationProxy.sol b/packages/contracts/contracts/relayer/evm/RegistrationProxy.sol new file mode 100644 index 0000000..d1ae040 --- /dev/null +++ b/packages/contracts/contracts/relayer/evm/RegistrationProxy.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "./Controllable.sol"; + +contract RegistrationProxy is Ownable, Controllable { + enum Status { + NON_EXISTENT, + PENDING, + SUCCESS, + FAILURE + } + + event InitiateRequest( + uint256 indexed id, + string name, + string recipient, + uint8 yearsToRegister, + uint256 value, + uint256 ttl + ); + + event ResultInfo( + uint256 indexed id, + bool success, + uint256 refundAmt + ); + + struct Record { + // string name; + address initiator; + uint256 value; + uint256 ttl; + Status status; + } + + uint256 public id; + uint256 public holdPeriod; + mapping(uint256 => Record) public idToRecord; + + constructor(uint256 _holdPeriod) Ownable() { + holdPeriod = _holdPeriod; + } + + function setHoldPeriod(uint256 _holdPeriod) external onlyOwner { + holdPeriod = _holdPeriod; + } + + function register( + string calldata name, + string calldata recipient, + uint8 yearsToRegister + ) external payable { + uint256 ttl = block.timestamp + holdPeriod; + uint256 _id = id++; + + idToRecord[_id] = Record(msg.sender, msg.value, ttl, Status.PENDING); + + emit InitiateRequest( + _id, + name, + recipient, + yearsToRegister, + msg.value, + ttl + ); + } + + function success(uint256 _id, uint256 refundAmt) external onlyController { + Record memory record = idToRecord[_id]; + require(record.status == Status.PENDING, "Invalid state"); + require(refundAmt <= record.value, "Refund exceeds received value"); + + record.status = Status.SUCCESS; + idToRecord[_id] = record; + payable(record.initiator).transfer(refundAmt); + + emit ResultInfo(_id, true, refundAmt); + } + + function failure(uint256 _id) external { + Record memory record = idToRecord[_id]; + require(record.status == Status.PENDING, "Invalid state"); + require(controllers[msg.sender] || record.ttl < block.timestamp, "Only controller can respond till TTL"); + + record.status = Status.FAILURE; + idToRecord[_id] = record; + payable(record.initiator).transfer(record.value); + + emit ResultInfo(_id, false, record.value); + } +} From 7ed7e8e012697c9237ffdc916c9686c81adbbb3e Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 3 Oct 2024 17:30:36 +0530 Subject: [PATCH 02/12] feat(contracts): add ink! proxy contract for registration --- .../contracts/relayer/wasm/.gitignore | 9 + .../contracts/relayer/wasm/Cargo.toml | 23 + .../contracts/contracts/relayer/wasm/lib.rs | 169 +++ packages/gateway/src/artefacts/wasm.json | 1140 +++++++++++++++++ 4 files changed, 1341 insertions(+) create mode 100755 packages/contracts/contracts/relayer/wasm/.gitignore create mode 100755 packages/contracts/contracts/relayer/wasm/Cargo.toml create mode 100644 packages/contracts/contracts/relayer/wasm/lib.rs create mode 100644 packages/gateway/src/artefacts/wasm.json diff --git a/packages/contracts/contracts/relayer/wasm/.gitignore b/packages/contracts/contracts/relayer/wasm/.gitignore new file mode 100755 index 0000000..8de8f87 --- /dev/null +++ b/packages/contracts/contracts/relayer/wasm/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/packages/contracts/contracts/relayer/wasm/Cargo.toml b/packages/contracts/contracts/relayer/wasm/Cargo.toml new file mode 100755 index 0000000..1562a19 --- /dev/null +++ b/packages/contracts/contracts/relayer/wasm/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "wasm" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2021" + +[dependencies] +ink = { version = "5.0.0", default-features = false } +zink = { git = "https://github.com/scio-labs/zink" } + +[dev-dependencies] +ink_e2e = { version = "5.0.0" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/packages/contracts/contracts/relayer/wasm/lib.rs b/packages/contracts/contracts/relayer/wasm/lib.rs new file mode 100644 index 0000000..1ebcaac --- /dev/null +++ b/packages/contracts/contracts/relayer/wasm/lib.rs @@ -0,0 +1,169 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[zink::coating(Ownable2Step[ + Error = Error::NotAdmin +])] +#[ink::contract] +mod registration_proxy { + use ink::env::call::{build_call, ExecutionInput, Selector}; + use ink::prelude::string::String; + use ink::storage::Mapping; + + #[ink(event)] + pub struct Success { + id: u128, + price: Balance, + } + + #[ink(storage)] + pub struct RegistrationProxy { + admin: AccountId, + /// Two-step ownership transfer AccountId + pending_admin: Option, + registry_addr: AccountId, + controllers: Mapping, + } + + #[derive(Debug)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Caller not allowed to call privileged calls. + NotAdmin, + /// Caller is not approved as controller + NotController, + /// Insufficient balance in the contract + InsufficientBalance, + /// Price exceeds the mentioned cap + TooExpensive, + /// Failed to register + RegisterFailed(u8), + /// Unable to retrieve the price + PriceFetchFailed(u8), + } + + impl RegistrationProxy { + #[ink(constructor)] + pub fn new(admin: AccountId, registry_addr: AccountId) -> Self { + Self { + admin, + pending_admin: None, + registry_addr, + controllers: Mapping::default(), + } + } + + #[ink(message, payable)] + pub fn fund_me(&mut self) -> Result<(), Error> { + Ok(()) + } + + #[ink(message)] + pub fn register( + &mut self, + id: u128, + name: String, + recipient: AccountId, + years_to_register: u8, + max_fees: Balance, + ) -> Result { + self.ensure_controller()?; + + let price = self.get_name_price(&name, recipient, years_to_register)?; + if price > max_fees { + return Err(Error::TooExpensive); + } else if price > self.env().balance() { + return Err(Error::InsufficientBalance); + } + + const REGISTER_SELECTOR: [u8; 4] = ink::selector_bytes!("register_on_behalf_of"); + let result = build_call::() + .call(self.registry_addr) + .call_v1() + .exec_input( + ExecutionInput::new(Selector::new(REGISTER_SELECTOR)) + .push_arg(name) + .push_arg(recipient) + .push_arg(years_to_register) + .push_arg::>(None) + .push_arg::>(None), + ) + .returns::>() + .transferred_value(price) + .params() + .invoke(); + + result.map_err(Error::RegisterFailed)?; + + self.env().emit_event(Success { + id, + price, + }); + + Ok(price) + } + + #[ink(message)] + pub fn set_controller(&mut self, controller: AccountId, enable: bool) -> Result<(), Error> { + self.ensure_admin().expect("Not Authorised"); + + if enable { + self.controllers.insert(controller, &()); + } else { + self.controllers.remove(controller); + } + + Ok(()) + } + + #[ink(message)] + pub fn is_controller(&self, controller: AccountId) -> bool { + self.controllers.contains(controller) + } + + #[ink(message)] + pub fn upgrade_contract(&mut self, code_hash: Hash) { + self.ensure_admin().expect("Not Authorised"); + + self.env().set_code_hash(&code_hash).unwrap_or_else(|err| { + panic!( + "Failed to `set_code_hash` to {:?} due to {:?}", + code_hash, err + ) + }); + ink::env::debug_println!("Switched code hash to {:?}.", code_hash); + } + + fn get_name_price( + &self, + name: &str, + recipient: AccountId, + years_to_register: u8, + ) -> Result { + const GET_NAME_PRICE_SELECTOR: [u8; 4] = ink::selector_bytes!("get_name_price"); + + let result = build_call::() + .call(self.registry_addr) + .call_v1() + .exec_input( + ExecutionInput::new(Selector::new(GET_NAME_PRICE_SELECTOR)) + .push_arg(name) + .push_arg(recipient) + .push_arg(years_to_register) + .push_arg::>(None), + ) + .returns::), u8>>() + .params() + .invoke(); + + let (base_price, premium, _, _) = result.map_err(Error::PriceFetchFailed)?; + let price = base_price.checked_add(premium).expect("Overflow"); + + Ok(price) + } + + fn ensure_controller(&self) -> Result<(), Error> { + let caller = self.env().caller(); + self.controllers.get(caller).ok_or(Error::NotController) + } + } +} diff --git a/packages/gateway/src/artefacts/wasm.json b/packages/gateway/src/artefacts/wasm.json new file mode 100644 index 0000000..fb7b0ae --- /dev/null +++ b/packages/gateway/src/artefacts/wasm.json @@ -0,0 +1,1140 @@ +{ + "source": { + "hash": "0x19af38a9ba0bb74c28516eb1c25947eb6d88ad579f1f7ebc983084fd0e6b7762", + "language": "ink! 5.0.0", + "compiler": "rustc 1.81.0", + "build_info": { + "build_mode": "Release", + "cargo_contract_version": "4.1.1", + "rust_toolchain": "stable-aarch64-apple-darwin", + "wasm_opt_settings": { + "keep_debug_symbols": false, + "optimization_passes": "Z" + } + } + }, + "contract": { + "name": "wasm", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "image": null, + "spec": { + "constructors": [ + { + "args": [ + { + "label": "admin", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "registry_addr", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + } + ], + "default": false, + "docs": [], + "label": "new", + "payable": false, + "returnType": { + "displayName": [ + "ink_primitives", + "ConstructorResult" + ], + "type": 10 + }, + "selector": "0x9bae9d5e" + } + ], + "docs": [], + "environment": { + "accountId": { + "displayName": [ + "AccountId" + ], + "type": 0 + }, + "balance": { + "displayName": [ + "Balance" + ], + "type": 15 + }, + "blockNumber": { + "displayName": [ + "BlockNumber" + ], + "type": 25 + }, + "chainExtension": { + "displayName": [ + "ChainExtension" + ], + "type": 26 + }, + "hash": { + "displayName": [ + "Hash" + ], + "type": 21 + }, + "maxEventTopics": 4, + "staticBufferSize": 16384, + "timestamp": { + "displayName": [ + "Timestamp" + ], + "type": 24 + } + }, + "events": [ + { + "args": [ + { + "docs": [], + "indexed": false, + "label": "id", + "type": { + "displayName": [ + "u128" + ], + "type": 15 + } + }, + { + "docs": [], + "indexed": false, + "label": "price", + "type": { + "displayName": [ + "Balance" + ], + "type": 15 + } + } + ], + "docs": [], + "label": "Success", + "module_path": "wasm::registration_proxy", + "signature_topic": "0x096bb2d50ce5aa43ab85a5681d5e8498e69e9fba99c0e016e6dfe3a2236e62ab" + } + ], + "lang_error": { + "displayName": [ + "ink", + "LangError" + ], + "type": 11 + }, + "messages": [ + { + "args": [], + "default": false, + "docs": [], + "label": "fund_me", + "mutates": true, + "payable": true, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x0848523c" + }, + { + "args": [ + { + "label": "id", + "type": { + "displayName": [ + "u128" + ], + "type": 15 + } + }, + { + "label": "name", + "type": { + "displayName": [ + "String" + ], + "type": 16 + } + }, + { + "label": "recipient", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "years_to_register", + "type": { + "displayName": [ + "u8" + ], + "type": 2 + } + }, + { + "label": "max_fees", + "type": { + "displayName": [ + "Balance" + ], + "type": 15 + } + } + ], + "default": false, + "docs": [], + "label": "register", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 17 + }, + "selector": "0x229b553f" + }, + { + "args": [ + { + "label": "controller", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + }, + { + "label": "enable", + "type": { + "displayName": [ + "bool" + ], + "type": 19 + } + } + ], + "default": false, + "docs": [], + "label": "set_controller", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0xc5e161ea" + }, + { + "args": [ + { + "label": "controller", + "type": { + "displayName": [ + "AccountId" + ], + "type": 0 + } + } + ], + "default": false, + "docs": [], + "label": "is_controller", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 20 + }, + "selector": "0x493a701c" + }, + { + "args": [ + { + "label": "code_hash", + "type": { + "displayName": [ + "Hash" + ], + "type": 21 + } + } + ], + "default": false, + "docs": [], + "label": "upgrade_contract", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 10 + }, + "selector": "0x1345543d" + }, + { + "args": [], + "default": false, + "docs": [], + "label": "get_admin", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 22 + }, + "selector": "0x57b8a8a7" + }, + { + "args": [], + "default": false, + "docs": [], + "label": "get_pending_admin", + "mutates": false, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 23 + }, + "selector": "0xbcd31d76" + }, + { + "args": [ + { + "label": "account", + "type": { + "displayName": [ + "Option" + ], + "type": 9 + } + } + ], + "default": false, + "docs": [], + "label": "transfer_ownership", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0x107e33ea" + }, + { + "args": [], + "default": false, + "docs": [], + "label": "accept_ownership", + "mutates": true, + "payable": false, + "returnType": { + "displayName": [ + "ink", + "MessageResult" + ], + "type": 12 + }, + "selector": "0xb55be9f0" + } + ] + }, + "storage": { + "root": { + "layout": { + "struct": { + "fields": [ + { + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + }, + "name": "admin" + }, + { + "layout": { + "enum": { + "dispatchKey": "0x00000000", + "name": "Option", + "variants": { + "0": { + "fields": [], + "name": "None" + }, + "1": { + "fields": [ + { + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + }, + "name": "0" + } + ], + "name": "Some" + } + } + } + }, + "name": "pending_admin" + }, + { + "layout": { + "leaf": { + "key": "0x00000000", + "ty": 0 + } + }, + "name": "registry_addr" + }, + { + "layout": { + "root": { + "layout": { + "leaf": { + "key": "0x09621f32", + "ty": 3 + } + }, + "root_key": "0x09621f32", + "ty": 4 + } + }, + "name": "controllers" + } + ], + "name": "RegistrationProxy" + } + }, + "root_key": "0x00000000", + "ty": 8 + } + }, + "types": [ + { + "id": 0, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 1, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "AccountId" + ] + } + }, + { + "id": 1, + "type": { + "def": { + "array": { + "len": 32, + "type": 2 + } + } + } + }, + { + "id": 2, + "type": { + "def": { + "primitive": "u8" + } + } + }, + { + "id": 3, + "type": { + "def": { + "tuple": [] + } + } + }, + { + "id": 4, + "type": { + "def": { + "composite": {} + }, + "params": [ + { + "name": "K", + "type": 0 + }, + { + "name": "V", + "type": 3 + }, + { + "name": "KeyType", + "type": 5 + } + ], + "path": [ + "ink_storage", + "lazy", + "mapping", + "Mapping" + ] + } + }, + { + "id": 5, + "type": { + "def": { + "composite": {} + }, + "params": [ + { + "name": "L", + "type": 6 + }, + { + "name": "R", + "type": 7 + } + ], + "path": [ + "ink_storage_traits", + "impls", + "ResolverKey" + ] + } + }, + { + "id": 6, + "type": { + "def": { + "composite": {} + }, + "path": [ + "ink_storage_traits", + "impls", + "AutoKey" + ] + } + }, + { + "id": 7, + "type": { + "def": { + "composite": {} + }, + "params": [ + { + "name": "ParentKey", + "type": 3 + } + ], + "path": [ + "ink_storage_traits", + "impls", + "ManualKey" + ] + } + }, + { + "id": 8, + "type": { + "def": { + "composite": { + "fields": [ + { + "name": "admin", + "type": 0, + "typeName": ",>>::Type" + }, + { + "name": "pending_admin", + "type": 9, + "typeName": " as::ink::storage::traits::AutoStorableHint<::\nink::storage::traits::ManualKey<801277377u32, ()>,>>::Type" + }, + { + "name": "registry_addr", + "type": 0, + "typeName": ",>>::Type" + }, + { + "name": "controllers", + "type": 4, + "typeName": " as::ink::storage::traits::AutoStorableHint\n<::ink::storage::traits::ManualKey<840917513u32, ()>,>>::Type" + } + ] + } + }, + "path": [ + "wasm", + "registration_proxy", + "RegistrationProxy" + ] + } + }, + { + "id": 9, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "None" + }, + { + "fields": [ + { + "type": 0 + } + ], + "index": 1, + "name": "Some" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 0 + } + ], + "path": [ + "Option" + ] + } + }, + { + "id": 10, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 11, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 1, + "name": "CouldNotReadInput" + } + ] + } + }, + "path": [ + "ink_primitives", + "LangError" + ] + } + }, + { + "id": 12, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 13 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 13 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 13, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 3 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 14 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 3 + }, + { + "name": "E", + "type": 14 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 14, + "type": { + "def": { + "variant": { + "variants": [ + { + "index": 0, + "name": "NotAdmin" + }, + { + "index": 1, + "name": "NotController" + }, + { + "index": 2, + "name": "InsufficientBalance" + }, + { + "index": 3, + "name": "TooExpensive" + }, + { + "fields": [ + { + "type": 2, + "typeName": "u8" + } + ], + "index": 4, + "name": "RegisterFailed" + }, + { + "fields": [ + { + "type": 2, + "typeName": "u8" + } + ], + "index": 5, + "name": "PriceFetchFailed" + } + ] + } + }, + "path": [ + "wasm", + "registration_proxy", + "Error" + ] + } + }, + { + "id": 15, + "type": { + "def": { + "primitive": "u128" + } + } + }, + { + "id": 16, + "type": { + "def": { + "primitive": "str" + } + } + }, + { + "id": 17, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 18 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 18 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 18, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 15 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 14 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 15 + }, + { + "name": "E", + "type": 14 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 19, + "type": { + "def": { + "primitive": "bool" + } + } + }, + { + "id": 20, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 19 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 19 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 21, + "type": { + "def": { + "composite": { + "fields": [ + { + "type": 1, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_primitives", + "types", + "Hash" + ] + } + }, + { + "id": 22, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 0 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 0 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 23, + "type": { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "type": 9 + } + ], + "index": 0, + "name": "Ok" + }, + { + "fields": [ + { + "type": 11 + } + ], + "index": 1, + "name": "Err" + } + ] + } + }, + "params": [ + { + "name": "T", + "type": 9 + }, + { + "name": "E", + "type": 11 + } + ], + "path": [ + "Result" + ] + } + }, + { + "id": 24, + "type": { + "def": { + "primitive": "u64" + } + } + }, + { + "id": 25, + "type": { + "def": { + "primitive": "u32" + } + } + }, + { + "id": 26, + "type": { + "def": { + "variant": {} + }, + "path": [ + "ink_env", + "types", + "NoChainExtension" + ] + } + } + ], + "version": 5 +} \ No newline at end of file From 7fe1361430a4d8f31120d90f8e6d05554984fe23 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 3 Oct 2024 19:53:34 +0530 Subject: [PATCH 03/12] chore: add artefact of solidity contract proxy --- packages/gateway/src/artefacts/evm.json | 319 ++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 packages/gateway/src/artefacts/evm.json diff --git a/packages/gateway/src/artefacts/evm.json b/packages/gateway/src/artefacts/evm.json new file mode 100644 index 0000000..bc4600c --- /dev/null +++ b/packages/gateway/src/artefacts/evm.json @@ -0,0 +1,319 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_holdPeriod", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "recipient", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "yearsToRegister", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ttl", + "type": "uint256" + } + ], + "name": "InitiateRequest", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "refundAmt", + "type": "uint256" + } + ], + "name": "ResultInfo", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "controllers", + "outputs": [ + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + } + ], + "name": "failure", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "holdPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "id", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "idToRecord", + "outputs": [ + { + "internalType": "address", + "name": "initiator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "ttl", + "type": "uint256" + }, + { + "internalType": "enum RegistrationProxy.Status", + "name": "status", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "recipient", + "type": "string" + }, + { + "internalType": "uint8", + "name": "yearsToRegister", + "type": "uint8" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_holdPeriod", + "type": "uint256" + } + ], + "name": "setHoldPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "refundAmt", + "type": "uint256" + } + ], + "name": "success", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file From 502ff61c168f6cb68d17d79fc2112dffbffa3045 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 3 Oct 2024 19:58:38 +0530 Subject: [PATCH 04/12] chore: shift `azns_registry` abi to artefacts dir --- .../gateway/src/{metadata.json => artefacts/azns_registry.json} | 0 packages/gateway/src/azero-id.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/gateway/src/{metadata.json => artefacts/azns_registry.json} (100%) diff --git a/packages/gateway/src/metadata.json b/packages/gateway/src/artefacts/azns_registry.json similarity index 100% rename from packages/gateway/src/metadata.json rename to packages/gateway/src/artefacts/azns_registry.json diff --git a/packages/gateway/src/azero-id.ts b/packages/gateway/src/azero-id.ts index c86ab27..8f4bcf1 100644 --- a/packages/gateway/src/azero-id.ts +++ b/packages/gateway/src/azero-id.ts @@ -1,4 +1,4 @@ -import abi from './metadata.json'; +import abi from './artefacts/azns_registry.json'; import { ApiPromise, WsProvider } from '@polkadot/api'; import { Database } from './server'; import { ContractPromise } from '@polkadot/api-contract'; From abe8fcd4b9b81f99bf8d5fcd294907fb7fb3148b Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 3 Oct 2024 20:29:21 +0530 Subject: [PATCH 05/12] refactor: shift `GasLimit` interface to utils --- packages/gateway/src/azero-id.ts | 6 +----- packages/gateway/src/index.ts | 3 ++- packages/gateway/src/utils.ts | 5 +++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/gateway/src/azero-id.ts b/packages/gateway/src/azero-id.ts index 8f4bcf1..5d2c876 100644 --- a/packages/gateway/src/azero-id.ts +++ b/packages/gateway/src/azero-id.ts @@ -6,16 +6,12 @@ import type { WeightV2 } from '@polkadot/types/interfaces'; import { getCoderByCoinType } from "@ensdomains/address-encoder"; import { createDotAddressDecoder } from '@ensdomains/address-encoder/utils' import { hexlify } from 'ethers/lib/utils'; +import { GasLimit } from './utils'; const AZERO_COIN_TYPE = 643; const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; const EMPTY_CONTENT_HASH = '0x'; -export interface GasLimit { - refTime: number, - proofSize: number, -} - export class AzeroId implements Database { ttl: number; tldToContract: Map; diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index 8aad458..4b08ac6 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -2,7 +2,8 @@ import { makeApp } from './server'; import { Command } from 'commander'; import { readFileSync } from 'fs'; import { ethers } from 'ethers'; -import { AzeroId, GasLimit } from './azero-id'; +import { AzeroId } from './azero-id'; +import { GasLimit } from './utils'; import supportedTLDs from './supported-tlds.json'; const program = new Command(); diff --git a/packages/gateway/src/utils.ts b/packages/gateway/src/utils.ts index 228afbf..6c98660 100644 --- a/packages/gateway/src/utils.ts +++ b/packages/gateway/src/utils.ts @@ -1 +1,6 @@ export const ETH_COIN_TYPE = 60; + +export interface GasLimit { + refTime: number, + proofSize: number, +} From 639aee74534d01af4ca053b47b21bd5302ca6f48 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 3 Oct 2024 20:36:30 +0530 Subject: [PATCH 06/12] wip: evm to wasm relayer connection complete --- packages/gateway/src/index.ts | 24 +++- packages/gateway/src/registration-relayer.ts | 111 +++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 packages/gateway/src/registration-relayer.ts diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index 4b08ac6..c36ee7f 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -4,6 +4,8 @@ import { readFileSync } from 'fs'; import { ethers } from 'ethers'; import { AzeroId } from './azero-id'; import { GasLimit } from './utils'; +import { Relayer } from './registration-relayer'; +import { Keyring } from '@polkadot/keyring'; import supportedTLDs from './supported-tlds.json'; const program = new Command(); @@ -28,7 +30,7 @@ const signer = new ethers.utils.SigningKey(privateKey); // TODO: make it configurable const defaultGasLimit: GasLimit = { - refTime: 10_000_000_000, + refTime: 100_000_000_000, proofSize: 1_000_000, }; @@ -43,4 +45,24 @@ AzeroId.init( const app = makeApp(signer, '/', db); console.log(`Serving on port ${options.port} with signing address ${address}`); app.listen(parseInt(options.port)); +}).then(async () => { + // TODO: make it configurable + const evmProviderURL = "https://ethereum-sepolia.publicnode.com"; + const evmRelayerAddr = "0x2BaD727319377af238a7F6D166494118Ca9D0497"; + const wasmRelayerAddr = "5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P";; + const wasmProviderURL = options.providerUrl; + const keyring = new Keyring({ type: 'sr25519' }); + const seed = "0xd5836897dc77e6c87e5cc268abaaa9c661bcf19aea9f0f50a1e149d21ce31eb7"; // public seed + const wasmSigner = keyring.createFromUri(seed); + + const relayer = await Relayer.init( + evmProviderURL, + evmRelayerAddr, + wasmProviderURL, + wasmRelayerAddr, + wasmSigner, + defaultGasLimit + ); + + relayer.start(); }); diff --git a/packages/gateway/src/registration-relayer.ts b/packages/gateway/src/registration-relayer.ts new file mode 100644 index 0000000..8e764f9 --- /dev/null +++ b/packages/gateway/src/registration-relayer.ts @@ -0,0 +1,111 @@ +import { ethers } from 'ethers'; +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { ContractPromise } from '@polkadot/api-contract'; +import { KeyringPair } from '@polkadot/keyring/types'; +import type { WeightV2 } from '@polkadot/types/interfaces'; +import evmABI from './artefacts/evm.json'; +import wasmABI from './artefacts/wasm.json'; +import { GasLimit } from './utils'; + +export class Relayer { + evm: ethers.Contract; + wasm: ContractPromise; + wasmSigner: KeyringPair; + wasmGasLimit: WeightV2; + + constructor( + evm: ethers.Contract, + wasm: ContractPromise, + wasmSigner: KeyringPair, + wasmGasLimit: WeightV2 + ) { + this.evm = evm; + this.wasm = wasm; + this.wasmSigner = wasmSigner; + this.wasmGasLimit = wasmGasLimit + } + + static async init( + evmProviderURL: string, + evmAddr: string, + wasmProviderURL: string, + wasmAddr: string, + wasmSigner: KeyringPair, + wasmGasLimit: GasLimit + ): Promise { + const evmProvider = ethers.getDefaultProvider(evmProviderURL); + const evm = new ethers.Contract(evmAddr, evmABI, evmProvider); + + const wsProvider = new WsProvider(wasmProviderURL); + const api = await ApiPromise.create({ provider: wsProvider }); + const wasm = new ContractPromise(api, wasmABI, wasmAddr); + const weightV2 = api.registry.createType('WeightV2', wasmGasLimit) as WeightV2; + + return new Relayer(evm, wasm, wasmSigner, weightV2); + } + + start() { + this.evm.on("InitiateRequest", (id, name, recipient, yearsToRegister, value, ttl) => { + console.log("New request:", Number(id), name, ttl); + // TODO: Check ttl is valid + this.executeRequest(Number(id), name, recipient, Number(yearsToRegister), value); + }) + } + + async executeRequest( + id: number, + name: string, + recipient: string, + yearsToRegister: number, + maxFeesInEVM: number + ) { + const maxFeesInWASM = this.valueEVM2WASM(maxFeesInEVM); + + // TODO: first dry-run to save Tx which would fail + + await this.wasm.tx.register( + { + gasLimit: this.wasmGasLimit + }, + id, + name, + recipient, + yearsToRegister, + maxFeesInWASM, + ) + .signAndSend(this.wasmSigner, ({ events = [], status }) => { + if (status.isFinalized) { + let successEventRecord = events.find((eventRecord) => { + const isContractEvent = this.wasm.api.events.contracts.ContractEmitted.is; + const verifyEventEmitter = (addr: any) => eventRecord.event.data.at(0)?.eq(addr); + + const successEventExists = (eventRecord: any, id: number) => { + const decoded = this.wasm.abi.decodeEvent(eventRecord); + const emittedID = Number(decoded.args[0]); + return decoded.event.identifier === "wasm::registration_proxy::Success" && + emittedID === id; + }; + + return isContractEvent(eventRecord.event) && + verifyEventEmitter(this.wasm.address) && + successEventExists(eventRecord, id); + }); + + if (successEventRecord === undefined) { + // Failure + console.log("Failed to register") + } else { + // Success + const decoded = this.wasm.abi.decodeEvent(successEventRecord); + const priceInWASM = Number(decoded.args[1]); + console.log("Registered successfully with price:", priceInWASM); + } + } + }); + } + + private valueEVM2WASM(valueInEVM: number) { + // TODO: set value converter properly + return valueInEVM + 10000000000000; + } +} \ No newline at end of file From bb829a92eb1153d2012e8b838c51d56ceda1514b Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Fri, 4 Oct 2024 11:05:56 +0530 Subject: [PATCH 07/12] ci: only build on node 18+ --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dccda4a..a65db8d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node: ['16.x', '18.x'] + node: ['18.x'] os: [ubuntu-latest] steps: From 639fe94fac64ff20ff14870e0be391875403e820 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Fri, 4 Oct 2024 20:58:08 +0530 Subject: [PATCH 08/12] style: run lint --- packages/gateway/src/azero-id.ts | 76 +++---- packages/gateway/src/index.ts | 69 ++++--- packages/gateway/src/registration-relayer.ts | 203 ++++++++++--------- packages/gateway/src/utils.ts | 4 +- 4 files changed, 195 insertions(+), 157 deletions(-) diff --git a/packages/gateway/src/azero-id.ts b/packages/gateway/src/azero-id.ts index 5d2c876..697fecb 100644 --- a/packages/gateway/src/azero-id.ts +++ b/packages/gateway/src/azero-id.ts @@ -2,9 +2,9 @@ import abi from './artefacts/azns_registry.json'; import { ApiPromise, WsProvider } from '@polkadot/api'; import { Database } from './server'; import { ContractPromise } from '@polkadot/api-contract'; -import type { WeightV2 } from '@polkadot/types/interfaces'; -import { getCoderByCoinType } from "@ensdomains/address-encoder"; -import { createDotAddressDecoder } from '@ensdomains/address-encoder/utils' +import { WeightV2 } from '@polkadot/types/interfaces'; +import { getCoderByCoinType } from '@ensdomains/address-encoder'; +import { createDotAddressDecoder } from '@ensdomains/address-encoder/utils'; import { hexlify } from 'ethers/lib/utils'; import { GasLimit } from './utils'; @@ -17,51 +17,59 @@ export class AzeroId implements Database { tldToContract: Map; maxGasLimit: WeightV2; - constructor(ttl: number, tldToContract: Map, maxGasLimit: WeightV2) { + constructor( + ttl: number, + tldToContract: Map, + maxGasLimit: WeightV2 + ) { this.ttl = ttl; this.tldToContract = tldToContract; this.maxGasLimit = maxGasLimit; } - static async init(ttl: number, providerURL: string, tldToContractAddress: Map, gasLimit: GasLimit) { + static async init( + ttl: number, + providerURL: string, + tldToContractAddress: Map, + gasLimit: GasLimit + ) { const wsProvider = new WsProvider(providerURL); const api = await ApiPromise.create({ provider: wsProvider }); - + const tldToContract = new Map(); tldToContractAddress.forEach((addr, tld) => { - tldToContract.set(tld, new ContractPromise(api, abi, addr)) - }) + tldToContract.set(tld, new ContractPromise(api, abi, addr)); + }); - const maxGasLimit = api.registry.createType('WeightV2', gasLimit) as WeightV2; + const maxGasLimit = api.registry.createType( + 'WeightV2', + gasLimit + ) as WeightV2; - return new AzeroId( - ttl, - tldToContract, - maxGasLimit, - ); + return new AzeroId(ttl, tldToContract, maxGasLimit); } async addr(name: string, coinType: number) { coinType = Number(coinType); // convert BigNumber to number - console.log("addr", name, coinType); - + console.log('addr', name, coinType); + let value; - if (coinType == AZERO_COIN_TYPE) { + if (coinType === AZERO_COIN_TYPE) { value = await this.fetchA0ResolverAddress(name); } else { - let alias = AzeroId.getAlias(""+coinType); + let alias = AzeroId.getAlias('' + coinType); if (alias !== undefined) { - const serviceKey = "address." + alias; + const serviceKey = 'address.' + alias; value = await this.fetchRecord(name, serviceKey); } if (value === undefined) { - const serviceKey = "address." + coinType; + const serviceKey = 'address.' + coinType; value = await this.fetchRecord(name, serviceKey); } } if (value === undefined) { - value = coinType == 60? ZERO_ADDRESS:'0x'; + value = coinType === 60 ? ZERO_ADDRESS : '0x'; } else { value = AzeroId.encodeAddress(value, coinType); } @@ -70,36 +78,36 @@ export class AzeroId implements Database { } async text(name: string, key: string) { - console.log("text", name, key); - const value = await this.fetchRecord(name, key) || ''; + console.log('text', name, key); + const value = (await this.fetchRecord(name, key)) || ''; return { value, ttl: this.ttl }; } contenthash(name: string) { - console.log("contenthash", name); + console.log('contenthash', name); return { contenthash: EMPTY_CONTENT_HASH, ttl: this.ttl }; } private async fetchRecord(domain: string, key: string) { - let {name, contract} = this.processName(domain); + let { name, contract } = this.processName(domain); const resp: any = await contract.query.getRecord( '', { - gasLimit: this.maxGasLimit + gasLimit: this.maxGasLimit, }, name, key ); - + return resp.output?.toHuman().Ok.Ok; } private async fetchA0ResolverAddress(domain: string) { - let {name, contract} = this.processName(domain); + let { name, contract } = this.processName(domain); const resp: any = await contract.query.getAddress( '', { - gasLimit: this.maxGasLimit + gasLimit: this.maxGasLimit, }, name ); @@ -109,7 +117,7 @@ export class AzeroId implements Database { private processName(domain: string) { const labels = domain.split('.'); - console.log("Labels:", labels); + console.log('Labels:', labels); const name = labels.shift() || ''; const tld = labels.join('.'); @@ -119,7 +127,7 @@ export class AzeroId implements Database { throw new Error(`TLD (.${tld}) not supported`); } - return {name, contract}; + return { name, contract }; } static getAlias(coinType: string) { @@ -136,10 +144,10 @@ export class AzeroId implements Database { static encodeAddress(addr: string, coinType: number) { const isEvmCoinType = (c: number) => { - return c == 60 || (c & 0x80000000)!=0 - } + return c === 60 || (c & 0x80000000) !== 0; + }; - if (coinType == AZERO_COIN_TYPE) { + if (coinType === AZERO_COIN_TYPE) { const azeroCoder = createDotAddressDecoder(42); return hexlify(azeroCoder(addr)); } diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index c36ee7f..6c70fa5 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -14,7 +14,11 @@ program '-k --private-key ', 'Private key to sign responses with. Prefix with @ to read from a file' ) - .option('--provider-url ', 'Provider URL of the substrate chain', 'wss://ws.test.azero.dev') + .option( + '--provider-url ', + 'Provider URL of the substrate chain', + 'wss://ws.test.azero.dev' + ) .option('-t --ttl ', 'TTL for signatures', '300') .option('-p --port ', 'Port number to serve on', '8080'); program.parse(process.argv); @@ -34,35 +38,42 @@ const defaultGasLimit: GasLimit = { proofSize: 1_000_000, }; -const tldToContractAddress = new Map(Object.entries(supportedTLDs)); +const tldToContractAddress = new Map( + Object.entries(supportedTLDs) +); AzeroId.init( - parseInt(options.ttl), - options.providerUrl, - tldToContractAddress, - defaultGasLimit, -).then(db => { - const app = makeApp(signer, '/', db); - console.log(`Serving on port ${options.port} with signing address ${address}`); - app.listen(parseInt(options.port)); -}).then(async () => { - // TODO: make it configurable - const evmProviderURL = "https://ethereum-sepolia.publicnode.com"; - const evmRelayerAddr = "0x2BaD727319377af238a7F6D166494118Ca9D0497"; - const wasmRelayerAddr = "5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P";; - const wasmProviderURL = options.providerUrl; - const keyring = new Keyring({ type: 'sr25519' }); - const seed = "0xd5836897dc77e6c87e5cc268abaaa9c661bcf19aea9f0f50a1e149d21ce31eb7"; // public seed - const wasmSigner = keyring.createFromUri(seed); + parseInt(options.ttl), + options.providerUrl, + tldToContractAddress, + defaultGasLimit +) + .then(db => { + const app = makeApp(signer, '/', db); + console.log( + `Serving on port ${options.port} with signing address ${address}` + ); + app.listen(parseInt(options.port)); + }) + .then(async () => { + // TODO: make it configurable + const evmProviderURL = 'https://ethereum-sepolia.publicnode.com'; + const evmRelayerAddr = '0x2BaD727319377af238a7F6D166494118Ca9D0497'; + const wasmRelayerAddr = '5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P'; + const wasmProviderURL = options.providerUrl; + const keyring = new Keyring({ type: 'sr25519' }); + const seed = + '0xd5836897dc77e6c87e5cc268abaaa9c661bcf19aea9f0f50a1e149d21ce31eb7'; // public seed + const wasmSigner = keyring.createFromUri(seed); - const relayer = await Relayer.init( - evmProviderURL, - evmRelayerAddr, - wasmProviderURL, - wasmRelayerAddr, - wasmSigner, - defaultGasLimit - ); + const relayer = await Relayer.init( + evmProviderURL, + evmRelayerAddr, + wasmProviderURL, + wasmRelayerAddr, + wasmSigner, + defaultGasLimit + ); - relayer.start(); -}); + relayer.start(); + }); diff --git a/packages/gateway/src/registration-relayer.ts b/packages/gateway/src/registration-relayer.ts index 8e764f9..afa7a66 100644 --- a/packages/gateway/src/registration-relayer.ts +++ b/packages/gateway/src/registration-relayer.ts @@ -2,110 +2,129 @@ import { ethers } from 'ethers'; import { ApiPromise, WsProvider } from '@polkadot/api'; import { ContractPromise } from '@polkadot/api-contract'; import { KeyringPair } from '@polkadot/keyring/types'; -import type { WeightV2 } from '@polkadot/types/interfaces'; +import { WeightV2 } from '@polkadot/types/interfaces'; import evmABI from './artefacts/evm.json'; import wasmABI from './artefacts/wasm.json'; import { GasLimit } from './utils'; export class Relayer { - evm: ethers.Contract; - wasm: ContractPromise; - wasmSigner: KeyringPair; - wasmGasLimit: WeightV2; + evm: ethers.Contract; + wasm: ContractPromise; + wasmSigner: KeyringPair; + wasmGasLimit: WeightV2; - constructor( - evm: ethers.Contract, - wasm: ContractPromise, - wasmSigner: KeyringPair, - wasmGasLimit: WeightV2 - ) { - this.evm = evm; - this.wasm = wasm; - this.wasmSigner = wasmSigner; - this.wasmGasLimit = wasmGasLimit - } + constructor( + evm: ethers.Contract, + wasm: ContractPromise, + wasmSigner: KeyringPair, + wasmGasLimit: WeightV2 + ) { + this.evm = evm; + this.wasm = wasm; + this.wasmSigner = wasmSigner; + this.wasmGasLimit = wasmGasLimit; + } - static async init( - evmProviderURL: string, - evmAddr: string, - wasmProviderURL: string, - wasmAddr: string, - wasmSigner: KeyringPair, - wasmGasLimit: GasLimit - ): Promise { - const evmProvider = ethers.getDefaultProvider(evmProviderURL); - const evm = new ethers.Contract(evmAddr, evmABI, evmProvider); + static async init( + evmProviderURL: string, + evmAddr: string, + wasmProviderURL: string, + wasmAddr: string, + wasmSigner: KeyringPair, + wasmGasLimit: GasLimit + ): Promise { + const evmProvider = ethers.getDefaultProvider(evmProviderURL); + const evm = new ethers.Contract(evmAddr, evmABI, evmProvider); - const wsProvider = new WsProvider(wasmProviderURL); - const api = await ApiPromise.create({ provider: wsProvider }); - const wasm = new ContractPromise(api, wasmABI, wasmAddr); - const weightV2 = api.registry.createType('WeightV2', wasmGasLimit) as WeightV2; + const wsProvider = new WsProvider(wasmProviderURL); + const api = await ApiPromise.create({ provider: wsProvider }); + const wasm = new ContractPromise(api, wasmABI, wasmAddr); + const weightV2 = api.registry.createType( + 'WeightV2', + wasmGasLimit + ) as WeightV2; - return new Relayer(evm, wasm, wasmSigner, weightV2); - } + return new Relayer(evm, wasm, wasmSigner, weightV2); + } - start() { - this.evm.on("InitiateRequest", (id, name, recipient, yearsToRegister, value, ttl) => { - console.log("New request:", Number(id), name, ttl); - // TODO: Check ttl is valid - this.executeRequest(Number(id), name, recipient, Number(yearsToRegister), value); - }) - } + start() { + this.evm.on( + 'InitiateRequest', + (id, name, recipient, yearsToRegister, value, ttl) => { + console.log('New request:', Number(id), name, ttl); + // TODO: Check ttl is valid + this.executeRequest( + Number(id), + name, + recipient, + Number(yearsToRegister), + value + ); + } + ); + } - async executeRequest( - id: number, - name: string, - recipient: string, - yearsToRegister: number, - maxFeesInEVM: number - ) { - const maxFeesInWASM = this.valueEVM2WASM(maxFeesInEVM); + async executeRequest( + id: number, + name: string, + recipient: string, + yearsToRegister: number, + maxFeesInEVM: number + ) { + const maxFeesInWASM = this.valueEVM2WASM(maxFeesInEVM); - // TODO: first dry-run to save Tx which would fail - - await this.wasm.tx.register( - { - gasLimit: this.wasmGasLimit - }, - id, - name, - recipient, - yearsToRegister, - maxFeesInWASM, - ) - .signAndSend(this.wasmSigner, ({ events = [], status }) => { - if (status.isFinalized) { - let successEventRecord = events.find((eventRecord) => { - const isContractEvent = this.wasm.api.events.contracts.ContractEmitted.is; - const verifyEventEmitter = (addr: any) => eventRecord.event.data.at(0)?.eq(addr); - - const successEventExists = (eventRecord: any, id: number) => { - const decoded = this.wasm.abi.decodeEvent(eventRecord); - const emittedID = Number(decoded.args[0]); - return decoded.event.identifier === "wasm::registration_proxy::Success" && - emittedID === id; - }; + // TODO: first dry-run to save Tx which would fail - return isContractEvent(eventRecord.event) && - verifyEventEmitter(this.wasm.address) && - successEventExists(eventRecord, id); - }); + await this.wasm.tx + .register( + { + gasLimit: this.wasmGasLimit, + }, + id, + name, + recipient, + yearsToRegister, + maxFeesInWASM + ) + .signAndSend(this.wasmSigner, ({ events = [], status }) => { + if (status.isFinalized) { + let successEventRecord = events.find(eventRecord => { + const isContractEvent = this.wasm.api.events.contracts + .ContractEmitted.is; + const verifyEventEmitter = (addr: any) => + eventRecord.event.data.at(0)?.eq(addr); - if (successEventRecord === undefined) { - // Failure - console.log("Failed to register") - } else { - // Success - const decoded = this.wasm.abi.decodeEvent(successEventRecord); - const priceInWASM = Number(decoded.args[1]); - console.log("Registered successfully with price:", priceInWASM); - } - } - }); - } + const successEventExists = (eventRecord: any, id: number) => { + const decoded = this.wasm.abi.decodeEvent(eventRecord); + const emittedID = Number(decoded.args[0]); + return ( + decoded.event.identifier === + 'wasm::registration_proxy::Success' && emittedID === id + ); + }; - private valueEVM2WASM(valueInEVM: number) { - // TODO: set value converter properly - return valueInEVM + 10000000000000; - } -} \ No newline at end of file + return ( + isContractEvent(eventRecord.event) && + verifyEventEmitter(this.wasm.address) && + successEventExists(eventRecord, id) + ); + }); + + if (successEventRecord === undefined) { + // Failure + console.log('Failed to register'); + } else { + // Success + const decoded = this.wasm.abi.decodeEvent(successEventRecord); + const priceInWASM = Number(decoded.args[1]); + console.log('Registered successfully with price:', priceInWASM); + } + } + }); + } + + private valueEVM2WASM(valueInEVM: number) { + // TODO: set value converter properly + return valueInEVM + 10000000000000; + } +} diff --git a/packages/gateway/src/utils.ts b/packages/gateway/src/utils.ts index 6c98660..ba8ead9 100644 --- a/packages/gateway/src/utils.ts +++ b/packages/gateway/src/utils.ts @@ -1,6 +1,6 @@ export const ETH_COIN_TYPE = 60; export interface GasLimit { - refTime: number, - proofSize: number, + refTime: number; + proofSize: number; } From acec213b9901cb0bec8b10f0466f3573dc3946c1 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Wed, 9 Oct 2024 01:03:40 +0530 Subject: [PATCH 09/12] feat: wasm to evm relayer complete --- packages/gateway/src/index.ts | 4 +- packages/gateway/src/registration-relayer.ts | 63 ++++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index 6c70fa5..c639aa2 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -58,6 +58,8 @@ AzeroId.init( .then(async () => { // TODO: make it configurable const evmProviderURL = 'https://ethereum-sepolia.publicnode.com'; + const evmProvider = ethers.getDefaultProvider(evmProviderURL); + const evmSigner = new ethers.Wallet(signer, evmProvider); // Should a separate signer be used? const evmRelayerAddr = '0x2BaD727319377af238a7F6D166494118Ca9D0497'; const wasmRelayerAddr = '5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P'; const wasmProviderURL = options.providerUrl; @@ -67,7 +69,7 @@ AzeroId.init( const wasmSigner = keyring.createFromUri(seed); const relayer = await Relayer.init( - evmProviderURL, + evmSigner, evmRelayerAddr, wasmProviderURL, wasmRelayerAddr, diff --git a/packages/gateway/src/registration-relayer.ts b/packages/gateway/src/registration-relayer.ts index afa7a66..0b41ca9 100644 --- a/packages/gateway/src/registration-relayer.ts +++ b/packages/gateway/src/registration-relayer.ts @@ -1,4 +1,4 @@ -import { ethers } from 'ethers'; +import { ContractReceipt, ContractTransaction, ethers } from 'ethers'; import { ApiPromise, WsProvider } from '@polkadot/api'; import { ContractPromise } from '@polkadot/api-contract'; import { KeyringPair } from '@polkadot/keyring/types'; @@ -26,15 +26,14 @@ export class Relayer { } static async init( - evmProviderURL: string, + evmSigner: ethers.Signer, evmAddr: string, wasmProviderURL: string, wasmAddr: string, wasmSigner: KeyringPair, wasmGasLimit: GasLimit ): Promise { - const evmProvider = ethers.getDefaultProvider(evmProviderURL); - const evm = new ethers.Contract(evmAddr, evmABI, evmProvider); + const evm = new ethers.Contract(evmAddr, evmABI, evmSigner); const wsProvider = new WsProvider(wasmProviderURL); const api = await ApiPromise.create({ provider: wsProvider }); @@ -113,18 +112,74 @@ export class Relayer { if (successEventRecord === undefined) { // Failure console.log('Failed to register'); + this.failure(id); } else { // Success const decoded = this.wasm.abi.decodeEvent(successEventRecord); const priceInWASM = Number(decoded.args[1]); console.log('Registered successfully with price:', priceInWASM); + + const refundInWASM = maxFeesInWASM - priceInWASM; + const refundInEVM = this.valueWASM2EVM(refundInWASM); + this.success(id, refundInEVM); } } }); } + private async failure(id: number) { + const tx: ContractTransaction = await this.evm.failure(id); + const receipt: ContractReceipt = await tx.wait(); + + const relaySuccess = receipt.events?.some(eve => + isResultInfoEvent(eve, this.evm.address, id, false) + ); + + if (relaySuccess) { + console.log('Failure status relayed back successfully'); + } else { + console.log('Failure status was NOT relayed back'); + } + } + + private async success(id: number, refundInEVM: number) { + const tx: ContractTransaction = await this.evm.success(id, refundInEVM); + const receipt: ContractReceipt = await tx.wait(); + + const relaySuccess = receipt.events?.some(eve => + isResultInfoEvent(eve, this.evm.address, id, true) + ); + + if (relaySuccess) { + console.log('Success status relayed back successfully'); + } else { + // ALERT: RELAYER FAILURE + throw new Error('FAILURE: Success status could not be relayed back'); + } + } + private valueEVM2WASM(valueInEVM: number) { // TODO: set value converter properly return valueInEVM + 10000000000000; } + + private valueWASM2EVM(valueInEVM: number) { + // TODO: set value converter properly + if (valueInEVM <= 0) return 0; + return 0; + } +} + +function isResultInfoEvent( + eve: ethers.Event, + addr: string, + id: number, + success: boolean +): boolean { + return ( + eve.address === addr && + eve.event === 'ResultInfo' && + Number(eve.args?.at(0)) === id && + eve.args?.at(1) === success + ); } From afbd2250fa456b9f4936ea1e6bd43356e13fca11 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Wed, 9 Oct 2024 01:48:08 +0530 Subject: [PATCH 10/12] feat: check TTL before accepting request --- packages/gateway/src/index.ts | 4 +- packages/gateway/src/registration-relayer.ts | 43 ++++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index c639aa2..e09906c 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -57,6 +57,7 @@ AzeroId.init( }) .then(async () => { // TODO: make it configurable + const bufferDuration = 10; const evmProviderURL = 'https://ethereum-sepolia.publicnode.com'; const evmProvider = ethers.getDefaultProvider(evmProviderURL); const evmSigner = new ethers.Wallet(signer, evmProvider); // Should a separate signer be used? @@ -74,7 +75,8 @@ AzeroId.init( wasmProviderURL, wasmRelayerAddr, wasmSigner, - defaultGasLimit + defaultGasLimit, + bufferDuration ); relayer.start(); diff --git a/packages/gateway/src/registration-relayer.ts b/packages/gateway/src/registration-relayer.ts index 0b41ca9..f017441 100644 --- a/packages/gateway/src/registration-relayer.ts +++ b/packages/gateway/src/registration-relayer.ts @@ -12,17 +12,20 @@ export class Relayer { wasm: ContractPromise; wasmSigner: KeyringPair; wasmGasLimit: WeightV2; + bufferDuration: number; constructor( evm: ethers.Contract, wasm: ContractPromise, wasmSigner: KeyringPair, - wasmGasLimit: WeightV2 + wasmGasLimit: WeightV2, + bufferDuration: number // In minutes ) { this.evm = evm; this.wasm = wasm; this.wasmSigner = wasmSigner; this.wasmGasLimit = wasmGasLimit; + this.bufferDuration = bufferDuration * 60 * 1000; // converted to milliseconds } static async init( @@ -31,7 +34,8 @@ export class Relayer { wasmProviderURL: string, wasmAddr: string, wasmSigner: KeyringPair, - wasmGasLimit: GasLimit + wasmGasLimit: GasLimit, + bufferDuration: number // In minutes ): Promise { const evm = new ethers.Contract(evmAddr, evmABI, evmSigner); @@ -43,26 +47,41 @@ export class Relayer { wasmGasLimit ) as WeightV2; - return new Relayer(evm, wasm, wasmSigner, weightV2); + return new Relayer(evm, wasm, wasmSigner, weightV2, bufferDuration); } start() { this.evm.on( 'InitiateRequest', (id, name, recipient, yearsToRegister, value, ttl) => { - console.log('New request:', Number(id), name, ttl); - // TODO: Check ttl is valid - this.executeRequest( - Number(id), - name, - recipient, - Number(yearsToRegister), - value - ); + console.log('New request:', Number(id), name); + + if (this.isTTLInRange(Number(ttl))) { + this.executeRequest( + Number(id), + name, + recipient, + Number(yearsToRegister), + value + ); + } else { + // Ignore the request + console.log( + 'Request %d skipped as its expiry-time falls short', + Number(id) + ); + } } ); } + /// @dev ttl is expected to be in seconds + isTTLInRange(ttl: number) { + ttl = ttl * 1000; // convert seconds to milliseconds + const currentTimestamp = Date.now(); + return currentTimestamp + this.bufferDuration <= ttl; + } + async executeRequest( id: number, name: string, From 94117a790784dc6b269c12d54466f992bbcf95a8 Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Thu, 10 Oct 2024 19:32:18 +0530 Subject: [PATCH 11/12] refactor: clean the init code --- packages/gateway/src/index.ts | 133 ++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 47 deletions(-) diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index e09906c..bb5f9a2 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -6,6 +6,7 @@ import { AzeroId } from './azero-id'; import { GasLimit } from './utils'; import { Relayer } from './registration-relayer'; import { Keyring } from '@polkadot/keyring'; +import { KeyringPair } from '@polkadot/keyring/types'; import supportedTLDs from './supported-tlds.json'; const program = new Command(); @@ -29,55 +30,93 @@ if (privateKey.startsWith('@')) { readFileSync(privateKey.slice(1), { encoding: 'utf-8' }) ); } -const address = ethers.utils.computeAddress(privateKey); -const signer = new ethers.utils.SigningKey(privateKey); -// TODO: make it configurable -const defaultGasLimit: GasLimit = { - refTime: 100_000_000_000, - proofSize: 1_000_000, -}; +async function gateway( + port: number, + ttl: number, + wasmProviderURL: string, + tldToContractAddress: Map, + wasmGasLimit: GasLimit, + evmSigningKey: ethers.utils.SigningKey +) { + const db = await AzeroId.init( + ttl, + wasmProviderURL, + tldToContractAddress, + wasmGasLimit + ); + + const app = makeApp(evmSigningKey, '/', db); + const address = ethers.utils.computeAddress(evmSigningKey.privateKey); + console.log(`Serving on port ${port} with signing address ${address}`); + app.listen(port); +} + +async function relayer( + evmSigner: ethers.Signer, + evmRelayerAddr: string, + wasmProviderURL: string, + wasmRelayerAddr: string, + wasmSigner: KeyringPair, + wasmGasLimit: GasLimit, + bufferDurationInMinutes: number +) { + const relayer = await Relayer.init( + evmSigner, + evmRelayerAddr, + wasmProviderURL, + wasmRelayerAddr, + wasmSigner, + wasmGasLimit, + bufferDurationInMinutes + ); + + relayer.start(); +} -const tldToContractAddress = new Map( - Object.entries(supportedTLDs) -); +async function main() { + const port = parseInt(options.port); + const ttl = parseInt(options.ttl); + const wasmProviderURL = options.providerUrl; + const tldToContractAddress = new Map( + Object.entries(supportedTLDs) + ); + const wasmGasLimit: GasLimit = { + refTime: 100_000_000_000, + proofSize: 1_000_000, + }; + const evmGatewaySigningKey = new ethers.utils.SigningKey(privateKey); + const evmRelayerSigningKey = new ethers.utils.SigningKey(privateKey); + const evmProviderURL = 'https://ethereum-sepolia.publicnode.com'; + const evmProvider = ethers.getDefaultProvider(evmProviderURL); + const evmSigner = new ethers.Wallet(evmRelayerSigningKey, evmProvider); + const evmRelayerAddr = '0x2BaD727319377af238a7F6D166494118Ca9D0497'; + const wasmRelayerAddr = '5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P'; + const wasmPrivateKey = + '0xd5836897dc77e6c87e5cc268abaaa9c661bcf19aea9f0f50a1e149d21ce31eb7'; // public seed + const wasmSigner = new Keyring({ type: 'sr25519' }).createFromUri( + wasmPrivateKey + ); + const bufferDurationInMinutes = 10; -AzeroId.init( - parseInt(options.ttl), - options.providerUrl, - tldToContractAddress, - defaultGasLimit -) - .then(db => { - const app = makeApp(signer, '/', db); - console.log( - `Serving on port ${options.port} with signing address ${address}` - ); - app.listen(parseInt(options.port)); - }) - .then(async () => { - // TODO: make it configurable - const bufferDuration = 10; - const evmProviderURL = 'https://ethereum-sepolia.publicnode.com'; - const evmProvider = ethers.getDefaultProvider(evmProviderURL); - const evmSigner = new ethers.Wallet(signer, evmProvider); // Should a separate signer be used? - const evmRelayerAddr = '0x2BaD727319377af238a7F6D166494118Ca9D0497'; - const wasmRelayerAddr = '5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P'; - const wasmProviderURL = options.providerUrl; - const keyring = new Keyring({ type: 'sr25519' }); - const seed = - '0xd5836897dc77e6c87e5cc268abaaa9c661bcf19aea9f0f50a1e149d21ce31eb7'; // public seed - const wasmSigner = keyring.createFromUri(seed); + await gateway( + port, + ttl, + wasmProviderURL, + tldToContractAddress, + wasmGasLimit, + evmGatewaySigningKey + ); - const relayer = await Relayer.init( - evmSigner, - evmRelayerAddr, - wasmProviderURL, - wasmRelayerAddr, - wasmSigner, - defaultGasLimit, - bufferDuration - ); + await relayer( + evmSigner, + evmRelayerAddr, + wasmProviderURL, + wasmRelayerAddr, + wasmSigner, + wasmGasLimit, + bufferDurationInMinutes + ); +} - relayer.start(); - }); +main(); From 0de78660f5ac8823aaa188606398d9c33413829c Mon Sep 17 00:00:00 2001 From: Nimish Agrawal Date: Fri, 11 Oct 2024 15:46:08 +0530 Subject: [PATCH 12/12] feat: use dotenv for runtime arguments --- packages/gateway/.env.example | 10 ++++++ packages/gateway/src/index.ts | 62 ++++++++++++++--------------------- 2 files changed, 35 insertions(+), 37 deletions(-) create mode 100644 packages/gateway/.env.example diff --git a/packages/gateway/.env.example b/packages/gateway/.env.example new file mode 100644 index 0000000..c6cacd4 --- /dev/null +++ b/packages/gateway/.env.example @@ -0,0 +1,10 @@ +PORT=8080 +TTL=300 +WASM_PROVIDER_URL='wss://ws.test.azero.dev' +EVM_PROVIDER_URL='https://ethereum-sepolia.publicnode.com' +EVM_RELAYER_CONTRACT='0x2BaD727319377af238a7F6D166494118Ca9D0497' +WASM_RELAYER_CONTRACT='5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P' +BUFFER_DURATION_IN_MIN= 10 +GATEWAY_SIGNER_KEY= +EVM_PRIVATE_KEY= +WASM_PRIVATE_KEY= diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index bb5f9a2..002caab 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -1,35 +1,15 @@ import { makeApp } from './server'; -import { Command } from 'commander'; -import { readFileSync } from 'fs'; import { ethers } from 'ethers'; import { AzeroId } from './azero-id'; import { GasLimit } from './utils'; import { Relayer } from './registration-relayer'; import { Keyring } from '@polkadot/keyring'; import { KeyringPair } from '@polkadot/keyring/types'; +import { waitReady } from '@polkadot/wasm-crypto'; import supportedTLDs from './supported-tlds.json'; +import dotenv from 'dotenv'; -const program = new Command(); -program - .requiredOption( - '-k --private-key ', - 'Private key to sign responses with. Prefix with @ to read from a file' - ) - .option( - '--provider-url ', - 'Provider URL of the substrate chain', - 'wss://ws.test.azero.dev' - ) - .option('-t --ttl ', 'TTL for signatures', '300') - .option('-p --port ', 'Port number to serve on', '8080'); -program.parse(process.argv); -const options = program.opts(); -let privateKey = options.privateKey; -if (privateKey.startsWith('@')) { - privateKey = ethers.utils.arrayify( - readFileSync(privateKey.slice(1), { encoding: 'utf-8' }) - ); -} +dotenv.config(); async function gateway( port: number, @@ -75,9 +55,10 @@ async function relayer( } async function main() { - const port = parseInt(options.port); - const ttl = parseInt(options.ttl); - const wasmProviderURL = options.providerUrl; + const port = parseInt(process.env.PORT || '8080'); + const ttl = parseInt(process.env.TTL || '300'); + const wasmProviderURL = + process.env.WASM_PROVIDER_URL || 'wss://ws.test.azero.dev'; const tldToContractAddress = new Map( Object.entries(supportedTLDs) ); @@ -85,19 +66,26 @@ async function main() { refTime: 100_000_000_000, proofSize: 1_000_000, }; - const evmGatewaySigningKey = new ethers.utils.SigningKey(privateKey); - const evmRelayerSigningKey = new ethers.utils.SigningKey(privateKey); - const evmProviderURL = 'https://ethereum-sepolia.publicnode.com'; - const evmProvider = ethers.getDefaultProvider(evmProviderURL); + const evmGatewaySigningKey = new ethers.utils.SigningKey( + process.env.GATEWAY_SIGNER_KEY || '' + ); + const evmRelayerSigningKey = new ethers.utils.SigningKey( + process.env.EVM_PRIVATE_KEY || '' + ); + const evmProvider = ethers.getDefaultProvider( + process.env.EVM_PROVIDER_URL || 'https://ethereum-sepolia.publicnode.com' + ); const evmSigner = new ethers.Wallet(evmRelayerSigningKey, evmProvider); - const evmRelayerAddr = '0x2BaD727319377af238a7F6D166494118Ca9D0497'; - const wasmRelayerAddr = '5GNDka5xV9y9nsES2gqYQponJ8vJaAmoJjMUvrqGqPF65q7P'; - const wasmPrivateKey = - '0xd5836897dc77e6c87e5cc268abaaa9c661bcf19aea9f0f50a1e149d21ce31eb7'; // public seed - const wasmSigner = new Keyring({ type: 'sr25519' }).createFromUri( - wasmPrivateKey + const evmRelayerAddr = process.env.EVM_RELAYER_CONTRACT || ''; + const wasmRelayerAddr = process.env.WASM_RELAYER_CONTRACT || ''; + const wasmSigner = await waitReady().then(() => + new Keyring({ type: 'sr25519' }).createFromUri( + process.env.WASM_PRIVATE_KEY || '' + ) + ); + const bufferDurationInMinutes = parseInt( + process.env.BUFFER_DURATION_IN_MIN || '10' ); - const bufferDurationInMinutes = 10; await gateway( port,