Skip to content

Registration Relayer #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bac4790
chore: Minor improvements & fixes
wottpal Oct 11, 2024
857f789
feat: Prepare for relayer implementation
wottpal Oct 11, 2024
528e512
wip(relayer): parse & validate registration request from event logs
realnimish Oct 17, 2024
d7c1323
feat(relayer): evm to wasm relayer complete
realnimish Oct 18, 2024
f3363a9
feat(relayer): store EVM wallet-client
realnimish Oct 21, 2024
0c7338e
feat(relayer): wasm to evm relayer impl complete for `FAILURE` status
realnimish Oct 22, 2024
0e993aa
feat(relayer): wasm to evm relayer impl complete for 'SUCCESS' status
realnimish Oct 22, 2024
bd72100
feat(relayer): add evm contract
realnimish Oct 23, 2024
89c5888
feat(relayer): add wasm contract
realnimish Oct 23, 2024
58d024c
feat: Various gateway improvements & general refactorings
wottpal Oct 25, 2024
aa7a7fd
fix: incorrect unlocking logic
realnimish Nov 1, 2024
8857dee
feat: implement price converter
realnimish Nov 1, 2024
54236c0
feat(relayer): set metadata alongside registration
realnimish Nov 1, 2024
59a7321
feat(relayer): add support for payment in ERC20 tokens
realnimish Nov 4, 2024
0cd0329
feat(relayer): process 1 request per invocation
realnimish Nov 4, 2024
9db3e6f
feat(relayer): response status propagation
realnimish Nov 5, 2024
29833eb
chore: improve log messages
realnimish Nov 5, 2024
f96f61d
feat(scripts): add deploy script for `RegistrationProxy`
realnimish Nov 5, 2024
28804ac
feat(scripts): add deploy script for `wasm` contract
realnimish Nov 7, 2024
501b081
feat: set admin as controller and allow funding on init
realnimish Nov 7, 2024
2c30d86
docs(relayer): add documentation
realnimish Nov 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,32 @@ It specifically targets the [AZERO.ID](https://azero.id) registry, though, it ca
| | **Testnet¹** | **Mainnet²** |
| --------------------- | -------------------------------------------- | -------------------------------------------- |
| **Resolver Contract** | `0x5cf63C14b82C6E1B95023d8D23e682d12761F56C` | `0x723f6C968609F62583504DD67307A4Ae4c9Fd886` |
| **Gateway** | https://tzero-id-gateway.nameverse.io | https://azero-id-gateway.nameverse.io |
| **ENS Domain** | `*.tzero-id.eth`, `tzero.eth` | `*.azero-id.eth` |
| **Gateway** | https://gateway.tzero.id | https://gateway.azero.id |
| **ENS Domains** | `<name>.tzero.id`³, `<name>.tzero-id.eth` | `<name>.azero.id`³, `<name>.azero-id.eth` |
| **RegistrationProxy** | TODO | TODO |
| **Wasm⁴** | TODO | TODO |

<small style="opacity: 0.5;">
<strong>¹ Testnet:</strong> Ethereum Sepolia & Aleph Zero Testnet<br/>
<strong>² Mainnet:</strong> Ethereum Mainnet & Aleph Zero Mainnet<br/>
<strong>¹</strong> Ethereum Sepolia & Aleph Zero Testnet<br/>
<strong>²</strong> Ethereum Mainnet & Aleph Zero Mainnet<br/>
<strong>³</strong> Regular ENS Domains imported via DNSSEC<br/>
<strong>⁴</strong> Deployed on substrate chain<br/>
</small>

## Packages

### [Solidity Contracts](packages/contracts)
### [Solidity Contracts](packages/contracts/README.md)

The smart contract provides a resolver stub that implement CCIP Read (EIP 3668) and ENS wildcard resolution (ENSIP 10). When queried for a name, it directs the client to query the gateway server. When called back with the gateway server response, the resolver verifies the signature was produced by an authorised signer, and returns the response to the client.

### [Gateway Server](packages/gateway)
### [Gateway & Relayer Server](packages/server/README.md)

The gateway server implements CCIP Read (EIP 3668), and answers requests by looking up the names on the registry Aleph Zero. Once a record is retrieved, it is signed using a user-provided key to assert its validity, and both record and signature are returned to the caller so they can be provided to the contract that initiated the request. It's designed to be deployed as a Cloudflare worker.
The server serves as both a EVM Registration Proxy (Relayer) and as a CCIP Read Resolver (Gateway) for ENS resolution.

### [Demo Client](packages/client)
- **Gateway**: Implements CCIP Read (EIP 3668), and answers requests by looking up the names on the registry Aleph Zero. Once a record is retrieved, it is signed using a user-provided key to assert its validity, and both record and signature are returned to the caller so they can be provided to the contract that initiated the request. It's designed to be deployed as a Cloudflare worker.
- **Relayer**: Relays registration requests from EVM chain to the substrate chain. `InitiateRequest` event is emitted when `RegistrationProxy::register()` is invoked. Its `TxHash` and optionally `reqId` is submitted to the relayer that parses and executes it on the substrate chain and then relays back the result to the EVM chain. Multiple payment options (native token, ERC20, and theoretically traditional payment as well) are supported by the relayer.

### [Demo Client](packages/client/README.md)

A simple script that resolves a given domain through the ENS protocol (using the gateway server) and verifies the response with the result from the registry contracts directly on the Aleph Zero network.

Expand All @@ -44,7 +51,7 @@ A simple script that resolves a given domain through the ENS protocol (using the
> - Install [Bun](https://bun.sh/)
> - Clone this repository

1. Run the gateway server ([packages/gateway/README.md](packages/gateway/README.md))
1. Run the gateway server ([packages/server/README.md](packages/server/README.md))
1. Use the worker url as environment variable when deploying the contracts
2. Deploy the contracts ([packages/contracts/README.md](packages/contracts/README.md))
1. Assign the new resolver to your ENS name
Expand Down
Binary file modified bun.lockb
Binary file not shown.
15 changes: 9 additions & 6 deletions packages/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,32 @@ const transport = process.env.INFURA_API_KEY
const viemClient = createPublicClient({ chain: evmChain, transport })

const evmChainName = isMainnet ? 'Ethereum Mainnet' : 'Ethereum Sepolia'
const ensDomain = isMainnet ? `${domain}-id.eth` : `${domain}-id.eth`
const ensDomain = isMainnet ? `${domain}.id` : `${domain}.id`
// const ensDomain = isMainnet ? `${domain}-id.eth` : `${domain}-id.eth`

const resolver = await viemClient.getEnsResolver({
name: normalize(ensDomain),
})
spinner.info(`[${evmChainName}] Found Resolver: ${resolver}`).start()

const gatewayUrl = isMainnet
? `https://azero-id-gateway.nameverse.io`
: `https://tzero-id-gateway.nameverse.io`
const gatewayUrl = isMainnet ? `https://gateway.azero.id` : `https://gateway.tzero.id`
spinner.info(`[${evmChainName}] Gateway URL: ${gatewayUrl}`)

spinner.start(`Fetching ENS Address on EVM via Gateway (${gatewayUrl})…`)

const startTime = performance.now()
const evmAddress = await viemClient.getEnsAddress({
name: normalize(ensDomain),
coinType: 643,
// universalResolverAddress: resolver,
universalResolverAddress: resolver,
})
const endTime = performance.now()
const duration = endTime - startTime

const evmAddressSs58 = evmAddress ? new AccountId32(evmAddress).address() : null
if (evmAddress && evmAddressSs58) {
spinner.success(
`[${evmChainName}] Resolved address of ${ensDomain}: ${evmAddressSs58} (${evmAddress})`,
`[${evmChainName}] Resolved address of ${ensDomain}: ${evmAddressSs58} (${evmAddress}) in ${duration.toFixed(0)}ms`,
)
} else {
spinner.error(`[${evmChainName}] Couldn't resolve address of ${ensDomain}`)
Expand Down
8 changes: 4 additions & 4 deletions packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@scio-labs/azero-offchain-resolver-client",
"version": "0.0.1",
"name": "@scio-labs/azero-offchain-client",
"version": "0.2.0",
"repository": "[email protected]:scio-labs/offchain-resolver-ts.git",
"author": "Scio Labs <[email protected]> (https://scio.xyz)",
"license": "MIT",
Expand All @@ -16,7 +16,7 @@
"@types/bun": "latest",
"@wagmi/cli": "^2.1.16",
"dedot": "^0.6.0",
"viem": "^2.21.22",
"yocto-spinner": "^0.1.0"
"viem": "^2.21.34",
"yocto-spinner": "^0.1.1"
}
}
18 changes: 18 additions & 0 deletions packages/contracts/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,27 @@ DEPLOYER_KEY=TODO
SIGNER_ADDR=TODO
NETWORK=sepolia
REMOTE_GATEWAY=https://tzero-id-gateway.nameverse.io/
HOLD_PERIOD=TODO

RPC_URL='wss://ws.test.azero.dev'
WASM_PRIVATE_KEY=TODO
WASM_PATH=TODO
ABI_PATH=TODO
ADMIN=TODO
REGISTRY_ADDR='5FsB91tXSEuMj6akzdPczAtmBaVKToqHmtAwSUzXh49AYzaD'
PRE_FUND_AMOUNT=0

# Mainnet
# DEPLOYER_KEY=TODO
# SIGNER_ADDR=TODO
# NETWORK=mainnet
# REMOTE_GATEWAY=https://azero-id-gateway.nameverse.io/
# HOLD_PERIOD=TODO

# RPC_URL='wss://ws.azero.dev'
# WASM_PRIVATE_KEY=TODO
# WASM_PATH=TODO
# ABI_PATH=TODO
# ADMIN=TODO
# REGISTRY_ADDR='5CTQBfBC9SfdrCDBJdfLiyW2pg9z5W6C6Es8sK313BLnFgDf'
# PRE_FUND_AMOUNT=0
20 changes: 4 additions & 16 deletions packages/contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
# ENS Offchain Gateway for Aleph Zero – Solidity Contracts
# Contracts

> **See [README.md](../../README.md) for more information.**

## Contracts

### [IExtendedResolver.sol](contracts/IExtendedResolver.sol)

This is the interface for wildcard resolution specified in ENSIP 10. In time this will likely be moved to the [@ensdomains/ens-contracts](https://github.com/ensdomains/ens-contracts) repository.

### [SignatureVerifier.sol](contracts/SignatureVerifier.sol)

This library facilitates checking signatures over CCIP read responses.

### [OffchainResolver.sol](contracts/OffchainResolver.sol)

This contract implements the offchain resolution system. Set this contract as the resolver for a name, and that name and all its subdomains that are not present in the ENS registry will be resolved via the provided gateway by supported clients.
There are two directories under the `contracts` dir - `gateway` and `relayer`. Each containing the contracts for respective purposes.

## Getting Started

Deploy smart contracts for both gateway and relayer:

```bash
# Install dependencies
bun install
Expand Down
15 changes: 15 additions & 0 deletions packages/contracts/contracts/gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# ENS Offchain Gateway for Aleph Zero – Solidity Contracts

## Contracts

### [IExtendedResolver.sol](./IExtendedResolver.sol)

This is the interface for wildcard resolution specified in ENSIP 10. In time this will likely be moved to the [@ensdomains/ens-contracts](https://github.com/ensdomains/ens-contracts) repository.

### [SignatureVerifier.sol](./SignatureVerifier.sol)

This library facilitates checking signatures over CCIP read responses.

### [OffchainResolver.sol](./OffchainResolver.sol)

This contract implements the offchain resolution system. Set this contract as the resolver for a name, and that name and all its subdomains that are not present in the ENS registry will be resolved via the provided gateway by supported clients.
15 changes: 15 additions & 0 deletions packages/contracts/contracts/relayer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Relayer contracts

## [RegistrationProxy](./evm/RegistrationProxy.sol)

It is a solidity contract and is deployed on the EVM chain.

## [Wasm](./wasm/lib.rs)

It is a wasm contract and is deployed on the substrate chain.

## Note

1. Admin is assigned as a controller by default during init (for both `RegistrationProxy` & `wasm`). The admin can assign other accounts as a controller as well by invoking the `setController()` method.

2. `wasm` contract has a payable method `fundMe()` which can be used to fund its reserve.
17 changes: 17 additions & 0 deletions packages/contracts/contracts/relayer/evm/Controllable.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
144 changes: 144 additions & 0 deletions packages/contracts/contracts/relayer/evm/RegistrationProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.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,
string[2][] metadata,
address paymentToken,
uint256 value,
uint256 ttl
);

event ResultInfo(uint256 indexed id, bool success, uint256 refundAmt);

struct Record {
// string name;
address initiator;
address paymentToken;
uint256 value;
uint256 ttl;
Status status;
}

uint256 public id;
uint256 public holdPeriod;
mapping(address => uint256) public lockedFunds;
mapping(uint256 => Record) public idToRecord;
mapping(address => bool) public whitelistedTokens;

constructor(uint256 _holdPeriod) Ownable() {
holdPeriod = _holdPeriod;
controllers[msg.sender] = true;
}

function setHoldPeriod(uint256 _holdPeriod) external onlyOwner {
holdPeriod = _holdPeriod;
}

function setWhitelistToken(address paymentToken, bool state) external onlyOwner {
whitelistedTokens[paymentToken] = state;
}

function register(
string calldata name,
string calldata recipient,
uint8 yearsToRegister,
string[2][] calldata metadata
) external payable {
_register(name, recipient, yearsToRegister, metadata, address(0), msg.value);
}

function register(
string calldata name,
string calldata recipient,
uint8 yearsToRegister,
string[2][] calldata metadata,
address paymentToken,
uint256 value
) external {
_collectPayment(msg.sender, paymentToken, value);
_register(name, recipient, yearsToRegister, metadata, paymentToken, value);
}

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;
_transferFunds(record.initiator, record.paymentToken, record.value);
lockedFunds[record.paymentToken] -= record.value;

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;
_transferFunds(record.initiator, record.paymentToken, record.value);
lockedFunds[record.paymentToken] -= record.value;

emit ResultInfo(_id, false, record.value);
}

function withdrawFunds(address beneficiary, address paymentToken, uint256 value) external onlyOwner {
uint256 maxWithdrawableBalance = _contractBalance(paymentToken) - lockedFunds[paymentToken];
require(value <= maxWithdrawableBalance, "Insufficient Balance");
_transferFunds(beneficiary, paymentToken, value);
}

function _register(
string calldata name,
string calldata recipient,
uint8 yearsToRegister,
string[2][] calldata metadata,
address paymentToken,
uint256 value
) private returns (uint256 _id) {
_id = id++;
uint256 ttl = block.timestamp + holdPeriod;

lockedFunds[paymentToken] += msg.value;
idToRecord[_id] = Record(msg.sender, paymentToken, value, ttl, Status.PENDING);

emit InitiateRequest(_id, name, recipient, yearsToRegister, metadata, paymentToken, value, ttl);
}

function _contractBalance(address paymentToken) private view returns (uint256) {
if (paymentToken == address(0)) return address(this).balance;
return IERC20(paymentToken).balanceOf(address(this));
}

function _transferFunds(address beneficiary, address paymentToken, uint256 value) private {
if (paymentToken == address(0)) {
payable(beneficiary).transfer(value);
} else {
require(IERC20(paymentToken).transfer(beneficiary, value), "erc20: transfer failed");
}
}

function _collectPayment(address from, address paymentToken, uint256 value) private {
require(whitelistedTokens[paymentToken], "given token not accepted");
require(IERC20(paymentToken).transferFrom(from, address(this), value), "erc20: payment failed");
}
}
9 changes: 9 additions & 0 deletions packages/contracts/contracts/relayer/wasm/.gitignore
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions packages/contracts/contracts/relayer/wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 = []
Loading