Skip to content

Commit

Permalink
Implement the ability to register using the endpoint of the validator
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelKim20 committed Sep 13, 2023
1 parent b007d72 commit 9c781f9
Show file tree
Hide file tree
Showing 14 changed files with 651 additions and 151 deletions.
4 changes: 4 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,17 @@
"devDependencies": {
"@ethersproject/experimental": "^5.7.0",
"@size-limit/preset-small-lib": "^7.0.8",
"@types/cors": "^2.8.8",
"@types/express": "^4.17.8",
"@types/node-cron": "^3.0.1",
"bigint-buffer": "^1.1.5",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-validator": "^6.14.0",
"ganache": "^7.9.1",
"glob": "^8.0.3",
"husky": "^7.0.4",
"node-cron": "^3.0.0",
"size-limit": "^7.0.8",
"solc": "0.4.17",
"tsdx": "^0.14.1",
Expand Down
6 changes: 6 additions & 0 deletions packages/client/src/client-common/interfaces/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import { Signer } from "@ethersproject/abstract-signer";
import { Contract, ContractInterface } from "@ethersproject/contracts";
import { JsonRpcProvider } from "@ethersproject/providers";
import { GenericRecord } from "./common";
import { UnfetchResponse } from "unfetch";

export interface IClientWeb3Core {
useSigner: (signer: Signer) => void;
Expand All @@ -14,6 +16,10 @@ export interface IClientWeb3Core {
ensureOnline: () => Promise<void>;
attachContract: <T>(address: string, abi: ContractInterface) => Contract & T;
getLinkCollectionAddress: () => string;
isRelayUp: () => Promise<boolean>;
assignValidatorEndpoint: () => Promise<void>;
get: (path: string, data?: GenericRecord) => Promise<UnfetchResponse>;
post: (path: string, data?: GenericRecord) => Promise<UnfetchResponse>;
}

export interface IClientCore {
Expand Down
46 changes: 45 additions & 1 deletion packages/client/src/client-common/modules/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { JsonRpcProvider } from "@ethersproject/providers";
import { Contract, ContractInterface } from "@ethersproject/contracts";
import { Signer } from "@ethersproject/abstract-signer";
import { IClientWeb3Core } from "../interfaces/core";
import { NoLinkCollection } from "del-sdk-common";
import { NoLinkCollection, NoProviderError, NoSignerError } from "del-sdk-common";
import { GenericRecord, IHttpConfig } from "../interfaces/common";
import { LinkCollection__factory } from "del-osx-lib";
import { NoValidator } from "../../utils/errors";
import { Network } from "../../utils/network";
import { UnfetchResponse } from "unfetch";

const linkCollectionAddressMap = new Map<Web3Module, string>();
const providersMap = new Map<Web3Module, JsonRpcProvider[]>();
Expand All @@ -28,6 +33,10 @@ export class Web3Module implements IClientWeb3Core {
linkCollectionAddressMap.set(this, context.linkCollectionAddress);
}

this.config = {
url: new URL("http://localhost"),
headers: {},
};
Object.freeze(Web3Module.prototype);
Object.freeze(this);
}
Expand Down Expand Up @@ -155,4 +164,39 @@ export class Web3Module implements IClientWeb3Core {
}
return this.linkCollectionAddress;
}
public config: IHttpConfig;

public async assignValidatorEndpoint(): Promise<void> {
const signer = this.getConnectedSigner();
if (!signer) {
throw new NoSignerError();
} else if (!signer.provider) {
throw new NoProviderError();
}

const contract = LinkCollection__factory.connect(this.getLinkCollectionAddress(), signer);
const validators = await contract.getValidators();
if (validators.length === 0) {
throw new NoValidator();
}
const idx = Math.floor(Math.random() * validators.length);
this.config.url = new URL(validators[idx].endpoint);
}

public async isRelayUp(): Promise<boolean> {
try {
const res = await this.get("/");
return (res.status === 200 && (await res.json())) === "OK";
} catch {
return false;
}
}

public async get(path: string, data?: GenericRecord): Promise<UnfetchResponse> {
return Network.get(this.config, path, data);
}

public async post(path: string, data?: GenericRecord): Promise<UnfetchResponse> {
return Network.post(this.config, path, data);
}
}
24 changes: 21 additions & 3 deletions packages/client/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import { BigNumber } from "ethers";
export interface IClientMethods extends IClientCore {
addRequest: (email: string) => AsyncGenerator<AddRequestValue>;
toAddress: (email: string) => Promise<string>;
toEmail: (wallet: string) => Promise<string>;
nonceOf: (wallet: string) => Promise<BigNumber>;
toEmail: (address: string) => Promise<string>;
nonceOf: (address: string) => Promise<BigNumber>;
getValidators: () => Promise<ValidatorInfoValue[]>;
isRelayUp: () => Promise<boolean>;
assignValidatorEndpoint: () => Promise<void>;
register: (email: string) => AsyncGenerator<RegisterValue>;
getRegisterStatus: (id: string) => Promise<number>;
}

export interface IClient {
Expand Down Expand Up @@ -44,10 +48,24 @@ export type AddRequestValue =
email: string;
id: string;
emailHash: string;
wallet: string;
address: string;
};

export enum AddRequestSteps {
ADDING = "adding",
DONE = "done",
}

export type RegisterValue =
| { key: RegisterSteps.DOING; requestId: string; email: string; address: string }
| {
key: RegisterSteps.DONE;
requestId: string;
email: string;
address: string;
};

export enum RegisterSteps {
DOING = "doing",
DONE = "done",
}
149 changes: 124 additions & 25 deletions packages/client/src/internal/client/methods.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
import { LinkCollection__factory } from "del-osx-lib";
import { NoProviderError, NoSignerError } from "del-sdk-common";

import { AddRequestSteps, AddRequestValue, IClientMethods, ValidatorInfoValue } from "../../interfaces";
import {
AddRequestSteps,
AddRequestValue,
IClientMethods,
RegisterSteps,
RegisterValue,
ValidatorInfoValue,
} from "../../interfaces";

import { ClientCore, Context } from "../../client-common";
import { ContractUtils } from "../../utils/ContractUtils";
import { BigNumber } from "ethers";
import { Contract } from "@ethersproject/contracts";
import {
AlreadyRegisteredAddress,
AlreadyRegisteredEmail,
FailedParameterValidation,
NotValidSignature,
ServerError,
UnknownError,
} from "../../utils/errors";

import { handleNetworkError } from "../../utils/network/ErrorTypes";

/**
* Methods module the SDK Generic Client
*/
export class ClientMethods extends ClientCore implements IClientMethods {
constructor(context: Context) {
super(context);

Object.freeze(ClientMethods.prototype);
Object.freeze(this);
}

public async assignValidatorEndpoint(): Promise<void> {
await this.web3.assignValidatorEndpoint();
}

public async isRelayUp(): Promise<boolean> {
return await this.web3.isRelayUp();
}
/**
* Add a request
*
* @param {email} email
* @return {*} {AsyncGenerator<null>}
* @return {*} {AsyncGenerator<AddRequestValue>}
* @memberof ClientMethods
*/
public async *addRequest(email: string): AsyncGenerator<AddRequestValue> {
Expand Down Expand Up @@ -65,11 +90,18 @@ export class ClientMethods extends ClientCore implements IClientMethods {
id: events[0].args[0],
email: email,
emailHash: events[0].args[1],
wallet: events[0].args[2],
address: events[0].args[2],
};
}

public async toAddress(email: string): Promise<string> {
/**
* Register email & address
*
* @param {email} email
* @return {*} {AsyncGenerator<RegisterValue>}
* @memberof ClientMethods
*/
public async *register(email: string): AsyncGenerator<RegisterValue> {
const signer = this.web3.getConnectedSigner();
if (!signer) {
throw new NoSignerError();
Expand All @@ -78,32 +110,90 @@ export class ClientMethods extends ClientCore implements IClientMethods {
}

const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), signer);
const address = await signer.getAddress();
const nonce = await contract.nonceOf(address);
const signature = await ContractUtils.signRequestData(signer, email, nonce);
const res = await this.web3.post("request", {
email,
address,
signature,
});

if (!res.ok) {
throw handleNetworkError(res);
}

const response = await res.json();
if (response.code === 200) {
} else if (response.code === 400) {
throw new FailedParameterValidation();
} else if (response.code === 401) {
throw new NotValidSignature();
} else if (response.code === 402) {
throw new AlreadyRegisteredEmail();
} else if (response.code === 403) {
throw new AlreadyRegisteredAddress();
} else if (response.code === 500) {
throw new ServerError();
} else {
throw new UnknownError();
}

yield {
key: RegisterSteps.DOING,
requestId: response.data.requestId,
email,
address,
};

const start = ContractUtils.getTimeStamp();
let done = false;
while (!done) {
const status = await this.getRegisterStatus(response.data.requestId);
if (status !== 0 || ContractUtils.getTimeStamp() - start > 60) {
done = true;
} else {
await ContractUtils.delay(3000);
}
}

yield {
key: RegisterSteps.DONE,
requestId: response.data.requestId,
email,
address,
};
}

public async toAddress(email: string): Promise<string> {
const provider = this.web3.getProvider();
if (!provider) {
throw new NoProviderError();
}

const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), provider);

return await contract.toAddress(email);
}

public async toEmail(wallet: string): Promise<string> {
const signer = this.web3.getConnectedSigner();
if (!signer) {
throw new NoSignerError();
} else if (!signer.provider) {
public async toEmail(address: string): Promise<string> {
const provider = this.web3.getProvider();
if (!provider) {
throw new NoProviderError();
}

const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), signer);
const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), provider);

return await contract.toEmail(wallet);
return await contract.toEmail(address);
}

public async nonceOf(wallet: string): Promise<BigNumber> {
const signer = this.web3.getConnectedSigner();
if (!signer) {
throw new NoSignerError();
} else if (!signer.provider) {
const provider = this.web3.getProvider();
if (!provider) {
throw new NoProviderError();
}

const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), signer);
const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), provider);

return await contract.nonceOf(wallet);
}
Expand All @@ -122,22 +212,31 @@ export class ClientMethods extends ClientCore implements IClientMethods {
}

public async getValidators(): Promise<ValidatorInfoValue[]> {
const signer = this.web3.getConnectedSigner();
if (!signer) {
throw new NoSignerError();
} else if (!signer.provider) {
const provider = this.web3.getProvider();
if (!provider) {
throw new NoProviderError();
}

const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), signer);
const validators = await contract.getValidators()
const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), provider);
const validators = await contract.getValidators();
return validators.map(m => {
return {
address: m.validator,
index: m.index.toNumber(),
endpoint: m.endpoint,
status: m.status
}
})
status: m.status,
};
});
}

public async getRegisterStatus(id: string): Promise<number> {
const provider = this.web3.getProvider();
if (!provider) {
throw new NoProviderError();
}

const contract = LinkCollection__factory.connect(this.web3.getLinkCollectionAddress(), provider);
const res = await contract.getRequestItem(id);
return res.status;
}
}
10 changes: 10 additions & 0 deletions packages/client/src/utils/ContractUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ export class ContractUtils {
return "0x" + data.toString("hex");
}

public static getTimeStamp(): number {
return Math.floor(new Date().getTime() / 1000);
}

public static delay(interval: number): Promise<void> {
return new Promise<void>((resolve, _) => {
setTimeout(resolve, interval);
});
}

public static getRequestId(emailHash: string, address: string, nonce: BigNumberish): string {
const encodedResult = ethers.utils.defaultAbiCoder.encode(
["bytes32", "address", "uint256", "bytes32"],
Expand Down
Loading

0 comments on commit 9c781f9

Please sign in to comment.