Skip to content

Commit

Permalink
poll receipts data, get address from public key #9
Browse files Browse the repository at this point in the history
  • Loading branch information
ukorvl committed May 27, 2024
1 parent 8629ea1 commit 6b970c3
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 66 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-pants-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nilfoundation/niljs": patch
---

Add simple polling implementation, allow to run publish workflow only manually, extend walletClient from publicClient
4 changes: 0 additions & 4 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
name: Release

on:
push:
branches:
- main
- master
workflow_dispatch:

concurrency:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"node": ">=18.0.0"
},
"type": "module",
"main": "dist/niljs.cjs.cjs",
"main": "dist/niljs.cjs",
"module": "dist/niljs.esm.js",
"files": [
"dist"
Expand All @@ -34,7 +34,8 @@
"lint": "biome check .",
"biome:fix": "biome check --apply .",
"changeset": "changeset",
"changeset:publish": "npm run build && changeset publish"
"changeset:publish": "changeset publish",
"git-hooks:update": "git config core.hooksPath .git/hooks/ && rm -rf .git/hooks && npx --no-install simple-git-hooks"
},
"license": "MIT",
"peerDependencies": {
Expand Down
47 changes: 42 additions & 5 deletions src/clients/PublicClient.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { IReceipt } from "../index.js";
import { BaseClient } from "./BaseClient.js";
import type { IPublicClientConfig } from "./types/ClientConfigs.js";

Expand Down Expand Up @@ -205,11 +206,47 @@ class PublicClient extends BaseClient {

return res.result;
}

/**
* getMessageReceiptByHash returns the message receipt by the hash.
* @param shardId - The shard id.
* @param hash - The hash.
* @returns The message receipt.
*/
public async getMessageReceiptByHash(
shardId: number,
hash: Uint8Array,
): Promise<IReceipt> {
const res = await this.rpcClient.request({
method: "eth_getMessageReceipt",
params: [shardId, hash],
});

return res.result;
}

/**
* sendRawMessage sends a raw message to the network.
* @param message - The message to send.
* @returns The hash of the message.
* @example
* import { PublicClient } from '@nilfoundation/niljs';
*
* const client = new PublicClient({
* endpoint: 'http://127.0.0.1:8529'
* })
*
* const message = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
* const hash = await client.sendRawMessage(message);
*/
public async sendRawMessage(message: Uint8Array): Promise<Uint8Array> {
const res = await this.rpcClient.request({
method: "eth_sendRawMessage",
params: [message],
});

return res.hash;
}
}

export { PublicClient };

// this client is subject to change a lot
// we need to add more methods to interact with the network
// and we need to know shard id before executing the request
// we won't have a single source of data for all shards so we need to know shard id.
55 changes: 18 additions & 37 deletions src/clients/WalletClient.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import invariant from "tiny-invariant";
import { messageToSsz, signedMessageToSsz } from "../encoding/toSsz.js";
import { type IReceipt, getShardIdFromAddress } from "../index.js";
import type { ISigner } from "../signers/index.js";
import type { IMessage } from "../types/IMessage.js";
import type { IReceipt } from "../types/IReceipt.js";
import { assertIsValidMessage } from "../utils/assert.js";
import { BaseClient } from "./BaseClient.js";
import { startPollingUntilCondition } from "../utils/polling.js";
import { PublicClient } from "./PublicClient.js";
import type { IWalletClientConfig } from "./types/ClientConfigs.js";
import type { ISendMessageOptions } from "./types/ISendMessageOptions.js";
import type { ISignMessageOptions } from "./types/ISignMessageOptions.js";
Expand All @@ -15,13 +16,15 @@ import type { ISignMessageOptions } from "./types/ISignMessageOptions.js";
* Wallet client alllows to use api that require signing data and private key usage.
* @example
* import { WalletClient } from '@nilfoundation/niljs';
* import { LocalKeySigner } from '@nilfoundation/niljs';
*
* const client = new WalletClient({
* endpoint: 'http://127.0.0.1:8529'
* signer: new LocalKeySigner({ privateKey: "xxx" })
* })
*/
class WalletClient extends BaseClient {
private signer?: ISigner;
class WalletClient extends PublicClient {
private signer: ISigner;

constructor(config: IWalletClientConfig) {
super(config);
Expand Down Expand Up @@ -88,29 +91,6 @@ class WalletClient extends BaseClient {
});
}

/**
* sendRawMessage sends a raw message to the network.
* @param message - The message to send.
* @returns The hash of the message.
* @example
* import { WalletClient } from '@nilfoundation/niljs';
*
* const client = new WalletClient({
* endpoint: 'http://127.0.0.1:8529'
* })
*
* const message = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
* const hash = await client.sendRawMessage(message);
*/
public async sendRawMessage(message: Uint8Array): Promise<Uint8Array> {
const res = await this.rpcClient.request({
method: "eth_sendRawMessage",
params: [message],
});

return res.hash;
}

/**
* deployContract deploys a contract to the network.
* @param contract - The contract to deploy.
Expand All @@ -127,19 +107,20 @@ class WalletClient extends BaseClient {
*/
public async deployContract(contract: Uint8Array): Promise<Uint8Array> {
const hash = await this.sendRawMessage(contract);
// there will be a method to get receipt by hash, but it is not implemented yet
// it will use kinda polling to get receipt asap
// mocking it for now:
const receipt: Partial<IReceipt> = {
success: true,
};
const address = this.signer.getAddress();
const shardId = getShardIdFromAddress(address);

// in the future we want to use subscribe method to get the receipt
// for now it is simple short polling
const receipt = await startPollingUntilCondition<IReceipt>(
async () => await this.getMessageReceiptByHash(shardId, hash),
(receipt) => receipt !== undefined,
1000,
);

// ! compiling smart contract to the bytecode shall not be included in this library
// it can be done by hardhat
invariant(
receipt.success,
"Contract deployment failed. Please check the contract bytecode.",
);
invariant(receipt?.success, "Contract deployment failed.");

return hash;
}
Expand Down
2 changes: 1 addition & 1 deletion src/clients/types/ClientConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type IWalletClientConfig = IClientBaseConfig & {
* signer: signer
* })
*/
signer?: ISigner;
signer: ISigner;
};

export type { IClientBaseConfig, IPublicClientConfig, IWalletClientConfig };
36 changes: 29 additions & 7 deletions src/signers/LocalKeySigner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import type { Hex } from "@noble/curves/abstract/utils";
import { secp256k1 } from "@noble/curves/secp256k1";
import { toHex } from "../encoding/toHex.js";
import { assertIsHexString, assertIsValidPrivateKey } from "../utils/assert.js";
import { getPublicKey } from "./publicKey.js";
import {
assertIsAddress,
assertIsHexString,
assertIsValidPrivateKey,
} from "../utils/assert.js";
import { getAddressFromPublicKey, getPublicKey } from "./publicKey.js";
import type { IAddress } from "./types/IAddress.js";
import type { ILocalKeySignerConfig } from "./types/ILocalKeySignerConfig.js";
import type { ISignature } from "./types/ISignature.js";
import type { ISigner } from "./types/ISigner.js";
Expand All @@ -17,17 +23,14 @@ import type { ISigner } from "./types/ISigner.js";
* const signer = new LocalKeySigner({ privateKey });
*/
class LocalKeySigner implements ISigner {
private publicKey;
private privateKey;
private publicKey?: Hex = undefined;
private address?: IAddress = undefined;

constructor(config: ILocalKeySignerConfig) {
const { privateKey } = config;
assertIsValidPrivateKey(privateKey);

const publicKey = getPublicKey(privateKey);
assertIsHexString(publicKey);

this.publicKey = publicKey;
this.privateKey = privateKey;
}

Expand All @@ -44,8 +47,27 @@ class LocalKeySigner implements ISigner {
}

public getPublicKey() {
if (this.publicKey) {
return this.publicKey;
}

const publicKey = getPublicKey(this.privateKey);
assertIsHexString(publicKey);

this.publicKey = publicKey;
return this.publicKey;
}

public getAddress() {
if (this.address) {
return this.address;
}

this.address = getAddressFromPublicKey(this.getPublicKey());
assertIsAddress(this.address);

return this.address;
}
}

export { LocalKeySigner };
20 changes: 19 additions & 1 deletion src/signers/publicKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import {
type ISignature,
addHexPrefix,
removeHexPrefix,
toBytes,
toHex,
} from "../index.js";
import { keccak_256 } from "../utils/keccak256.js";
import type { IAddress } from "./types/IAddress.js";
import type { IPrivateKey } from "./types/IPrivateKey.js";

/**
Expand All @@ -31,4 +34,19 @@ const recoverPublicKey = (
//
};

export { getPublicKey, generatePrivateKey, recoverPublicKey };
/**
* Returns the address from the public key.
* @param publicKey - Public key in hex format
* @returns Address in hex format
*/
const getAddressFromPublicKey = (publicKey: Hex): IAddress => {
const bytes = keccak_256(toBytes(removeHexPrefix(publicKey)));
return toHex(bytes) as IAddress;
};

export {
getPublicKey,
generatePrivateKey,
recoverPublicKey,
getAddressFromPublicKey,
};
6 changes: 6 additions & 0 deletions src/signers/types/IAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* Address type represents an address in hexadecimal format.
*/
type IAddress = `0x${string}`;

export type { IAddress };
8 changes: 8 additions & 0 deletions src/signers/types/ISigner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Hex } from "@noble/curves/abstract/utils";
import type { IAddress } from "./IAddress.js";
import type { ISignature } from "./ISignature.js";

/**
Expand All @@ -21,6 +22,13 @@ abstract class ISigner {
* const publicKey = signer.getPublicKey();
*/
abstract getPublicKey(): Hex;
/**
* Returns the address.
* @returns The address.
* @example
* const address = signer.getAddress();
*/
abstract getAddress(): IAddress;
}

export { ISigner };
2 changes: 1 addition & 1 deletion src/utils/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const assertIsValidPrivateKey = (
message?: string,
): void => {
invariant(
isHexString(privateKey) && privateKey.length === 64,
isHexString(privateKey) && privateKey.length === 32 * 2 + 2,
message ?? `Expected a valid private key, but got ${privateKey}`,
);
};
Expand Down
16 changes: 12 additions & 4 deletions src/utils/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ const HEX_REGEX = /^[0-9a-fA-F]+$/;
* Otherwise, it returns false.
* @param value - The value to check.
*/
const isHexString = (value: Hex): boolean => {
return typeof value === "string" && HEX_REGEX.test(value);
const isHexString = (value: Hex): value is Hex => {
return (
typeof value === "string" &&
value.startsWith("0x") &&
HEX_REGEX.test(removeHexPrefix(value))
);
};

/**
* Remove a hex prefix from a hex string.
* @param hex hex-string
* @returns format: base16-string
*/
const removeHexPrefix = (hex: string): string => {
return hex.replace(/^0x/i, "");
const removeHexPrefix = (hex: Hex): string => {
if (typeof hex !== "string") {
throw new Error(`Expected a hex string but got ${hex}`);
}

return hex.startsWith("0x") ? hex.slice(2) : hex;
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./assert.js";
export * from "./hex.js";
export * from "./message.js";
export * from "./keccak256.js";
13 changes: 13 additions & 0 deletions src/utils/keccak256.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Hex } from "@noble/curves/abstract/utils";
import { keccak_256 as keccak_256Module } from "@noble/hashes/sha3";

/**
* Returns the keccak-256 hash of the data. It is used in the Nil blockchain.
* @param data - The data to hash.
* @returns The keccak-256 hash.
*/
const keccak_256 = (data: Hex) => {
return keccak_256Module(data);
};

export { keccak_256 };
Loading

0 comments on commit 6b970c3

Please sign in to comment.