Skip to content

Development support mnemonics in registrar client #9

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

Merged
merged 4 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 22 additions & 9 deletions packages/registrar_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,37 @@ This package provides a client for interacting with the TFGrid v4 Node Registrar

## Getting Started

To initialize the Registrar Client, you need to provide the base url of registrar and Base64-encoded, 64-byte raw Ed25519 private key (nacl format).
To initialize the Registrar Client, you need to provide the base URL of the registrar and either your mnemonic phrase or a 64-character hex seed. You also need to specify the keypair type.

To generate a 64-byte ed25519 private key, you can use tweetnacl library to generate key:
The supported keypair types are:

- `ed25519`
- `sr25519` (default)

Here's how to initialize the client:

```typescript
import nacl from "tweetnacl";
import base64 from "base64-js";
const client = new RegistrarClient({
baseUrl: "https://registrar.dev4.grid.tf/v1",
mnemonicOrSeed: "your_mnemonic_or_seed",
keypairType: "sr25519", // Optional, defaults to "sr25519"
});
```

const keyPair = nacl.sign.keyPair();
const privateKey = base64.fromByteArray(keyPair.secretKey);
To generate 64 character hex seed:

console.log("Your 64-byte ed25519 private key:", privateKey);
```bash
openssl rand -hex 32
```

Here is an example:

```typescript
const client = new RegistrarClient({ baseURl: "https://registrar.dev4.grid.tf/v1", privateKey: your_private_key });
const client = new RegistrarClient({
baseURl: "https://registrar.dev4.grid.tf/v1",
mnemonicOrSeed: "your_mnemonic_or_seed",
keypairType: "ed21559"
});
```

To be able to create a farm you need to have a Stellar wallet and provide your Stellar address. For more details on how to create a Stellar wallet and generate a Stellar address, please refer to the [Stellar Account Viewer](https://www.stellar.org/account-viewer/#!/) or the [Stellar Documentation](https://developers.stellar.org/docs/tutorials/create-account/).
Expand All @@ -62,7 +75,7 @@ To be able to create a farm you need to have a Stellar wallet and provide your S
Here is an example of how to use the Registrar Client:

```typescript
const client = new RegistrarClient({ baseUrl: URl, privateKey: your_private_key });
const client = new RegistrarClient({ baseUrl: URl, mnemonicOrSeed: your_mnemonic_or_seed, keypairType: "ed21559" });

// Example: Create an account
const accountRequest: CreateAccountRequest = {
Expand Down
2 changes: 2 additions & 0 deletions packages/registrar_client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
"typescript": "^5.7.3"
},
"dependencies": {
"@polkadot/keyring": "^13.4.4",
"@stellar/stellar-base": "^12.1.1",
"@types/jest": "^29.5.14",
"bip39": "^3.1.0",
"dotenv": "^16.4.7",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
Expand Down
4 changes: 2 additions & 2 deletions packages/registrar_client/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ yarn install

## Getting Started

- Set base URL, your private key, and Stellar address (if needed) in `scripts/config.json`.
- Set base URL, your mnemonic or seed, keypair type and Stellar address (if needed) in `scripts/config.json`.

## Usage

To run any of the scripts, use the following command format:

```bash
yarn ts-node --project tsconfig-node.json scripts/<script_name>.ts
yarn ts-node --project tsconfig-node.json scripts/<script_name>.ts
```
2 changes: 1 addition & 1 deletion packages/registrar_client/scripts/config.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"baseUrl": "http://localhost:8080/v1", "privateKey": "", "stellarAddress": ""}
{"baseUrl": "https://registrar.dev4.grid.tf/v1", "mnemonicOrSeed": "", "stellarAddress": "", "keypairType": "ed25519"}
3 changes: 2 additions & 1 deletion packages/registrar_client/scripts/create_account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { log } from "console";
import { RegistrarClient } from "../src/";
import config from "./config.json";
import { KeypairType } from "@polkadot/util-crypto/types";

async function createAccount(client: RegistrarClient) {
const account = await client.accounts.createAccount({});
Expand All @@ -10,7 +11,7 @@ async function createAccount(client: RegistrarClient) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed, keypairType: config.keypairType as KeypairType });
await createAccount(client);
}

Expand Down
9 changes: 5 additions & 4 deletions packages/registrar_client/scripts/create_farm.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { log } from "console";
import { Account, RegistrarClient } from "../src/";
import config from "./config.json";
import * as tweetnacl from "tweetnacl";
import * as base64 from "base64-js";
import { deriveKeyPair } from "../src/utils";
import { KeypairType } from "@polkadot/util-crypto/types";

async function getAccount(client: RegistrarClient): Promise<Account> {
const publicKey = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(config.privateKey)).publicKey;
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(publicKey));
const keyPair = await deriveKeyPair(config.mnemonicOrSeed, config.keypairType as KeypairType);
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(keyPair.publicKey));
log("================= Getting Account =================");
log(account);
log("================= Getting Account =================");
Expand All @@ -29,7 +30,7 @@ async function getFarm(client: RegistrarClient, farmID: number) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
const account = await getAccount(client);
const twinID = account.twin_id;
const farmID = await createFarm(client, twinID, config.stellarAddress);
Expand Down
9 changes: 5 additions & 4 deletions packages/registrar_client/scripts/create_node.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { log } from "console";
import { Account, RegistrarClient, NodeRegistrationRequest } from "../src/";
import config from "./config.json";
import * as tweetnacl from "tweetnacl";
import * as base64 from "base64-js";
import { deriveKeyPair } from "../src/utils";
import { KeypairType } from "@polkadot/util-crypto/types";

async function getAccount(client: RegistrarClient): Promise<Account> {
const publicKey = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(config.privateKey)).publicKey;
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(publicKey));
const keyPair = await deriveKeyPair(config.mnemonicOrSeed, config.keypairType as KeypairType);
const account = await client.accounts.getAccountByPublicKey(base64.fromByteArray(keyPair.publicKey));
log("================= Getting Account =================");
log(account);
log("================= Getting Account =================");
Expand All @@ -29,7 +30,7 @@ async function getNode(client: RegistrarClient, nodeID: number) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed, keypairType: config.keypairType as KeypairType });
const account = await getAccount(client);
const twinID = account.twin_id;
const node: NodeRegistrationRequest = {
Expand Down
2 changes: 1 addition & 1 deletion packages/registrar_client/scripts/list_farms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async function listFarms(client: RegistrarClient, filter: NodesFilter) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
await listFarms(client, {});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/registrar_client/scripts/list_nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async function listNodes(client: RegistrarClient, filter: NodesFilter) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed });
const filter: NodesFilter = {
farm_id: 70,
};
Expand Down
3 changes: 2 additions & 1 deletion packages/registrar_client/scripts/update_account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { log } from "console";
import { RegistrarClient, UpdateAccountRequest } from "../src/";
import config from "./config.json";
import { KeypairType } from "@polkadot/util-crypto/types";

async function updateAccount(client: RegistrarClient, twinID: number, update: UpdateAccountRequest) {
const account = await client.accounts.updateAccount(twinID, update);
Expand All @@ -17,7 +18,7 @@ async function getAccount(client: RegistrarClient, twinID: number) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed, keypairType: config.keypairType as KeypairType });
const update: UpdateAccountRequest = {
relays: ["relay1", "relay2"],
rmb_enc_key: "rmb_enc_key",
Expand Down
3 changes: 2 additions & 1 deletion packages/registrar_client/scripts/update_farm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { log } from "console";
import { RegistrarClient } from "../src/";
import config from "./config.json";
import { KeypairType } from "@polkadot/util-crypto/types";

async function updateFarm(client: RegistrarClient, twinID: number, farmID: number, farmName: string) {
const farm = await client.farms.updateFarm(farmID, twinID, farmName, config.stellarAddress);
Expand All @@ -17,7 +18,7 @@ async function getFarm(client: RegistrarClient, farmID: number) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed, keypairType: config.keypairType as KeypairType });
const twinID = 143;
const farmID = 46;
const farmName = "testfarm";
Expand Down
3 changes: 2 additions & 1 deletion packages/registrar_client/scripts/update_node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { log } from "console";
import { UpdateNodeRequest, RegistrarClient } from "../src/";
import config from "./config.json";
import { KeypairType } from "@polkadot/util-crypto/types";

async function updateNode(client: RegistrarClient, twinID: number, nodeID: number, update: UpdateNodeRequest) {
const account = await client.nodes.updateNode(nodeID, twinID, update);
Expand All @@ -17,7 +18,7 @@ async function getNode(client: RegistrarClient, nodeID: number) {
}

async function main() {
const client = new RegistrarClient({ baseURL: config.baseUrl, privateKey: config.privateKey });
const client = new RegistrarClient({ baseURL: config.baseUrl, mnemonicOrSeed: config.mnemonicOrSeed, keypairType: config.keypairType as KeypairType });
const update: UpdateNodeRequest = {
farm_id: 46,
interfaces: [
Expand Down
39 changes: 31 additions & 8 deletions packages/registrar_client/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Accounts } from "../modules/accounts";
import { Farms } from "../modules/farms";
import { Nodes } from "../modules/nodes";
import { Zos } from "../modules/zos";

import { validateMnemonic } from "bip39";
import { KeypairType } from "@polkadot/util-crypto/types";
import { SUPPORTED_KEYPAIR_TYPES } from "../utils";
export abstract class BaseClient {
private client: AxiosInstance;

Expand Down Expand Up @@ -35,25 +37,46 @@ export abstract class BaseClient {
}
interface Config {
baseURL: string;
privateKey: string;
mnemonicOrSeed: string;
keypairType?: KeypairType;
}

export class RegistrarClient extends BaseClient {
public readonly privateKey: string;
public readonly mnemonicOrSeed: string;
public readonly keypairType: KeypairType;
accounts: Accounts;
farms: Farms;
nodes: Nodes;
zos: Zos;

constructor({ baseURL, privateKey }: Config) {
_validateSeed(seed: string): string {
if (!seed.startsWith("0x")) {
seed = `0x${seed}`;
}
if (!seed.match(/^0x[a-fA-F0-9]{64}$/)) {
return "";
}
return seed;
}

constructor({ baseURL, mnemonicOrSeed, keypairType = "sr25519" }: Config) {
if (!SUPPORTED_KEYPAIR_TYPES.includes(keypairType)) {
throw new Error(`Unsupported keypair type: ${keypairType}`);
}
if (!baseURL) {
throw new Error("Base URL is required");
}
if (!privateKey) {
throw new Error("Private key is required");
}
super(baseURL);
this.privateKey = privateKey;

if (!validateMnemonic(mnemonicOrSeed)) {
mnemonicOrSeed = this._validateSeed(mnemonicOrSeed);
}
if (!mnemonicOrSeed) {
throw new Error("Invalid mnemonic or seed");
}

this.mnemonicOrSeed = mnemonicOrSeed;
this.keypairType = keypairType;
this.accounts = new Accounts(this);
this.farms = new Farms(this);
this.nodes = new Nodes(this);
Expand Down
10 changes: 2 additions & 8 deletions packages/registrar_client/src/modules/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { RegistrarClient } from "../client/client";
import { Account, CreateAccountRequest, UpdateAccountRequest } from "../types/account";
import * as tweetnacl from "tweetnacl";
import * as base64 from "base64-js";
import { createSignatureWithPublicKey, createAuthHeader } from "../utils";

export class Accounts {
Expand All @@ -14,11 +12,7 @@ export class Accounts {
}

async createAccount(request: Partial<CreateAccountRequest>): Promise<Account | null> {
const privateKey = this.client.privateKey;
const keyPair = tweetnacl.sign.keyPair.fromSecretKey(base64.toByteArray(privateKey));

const publicKey = base64.fromByteArray(keyPair.publicKey);
const { signature, timestamp } = createSignatureWithPublicKey(publicKey, privateKey);
const { signature, publicKey, timestamp } = await createSignatureWithPublicKey(this.client.mnemonicOrSeed, this.client.keypairType);

request.public_key = publicKey;
request.signature = signature;
Expand Down Expand Up @@ -59,7 +53,7 @@ export class Accounts {

async updateAccount(twinID: number, body: UpdateAccountRequest): Promise<any> {
try {
const headers = createAuthHeader(twinID, this.client.privateKey);
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed, this.client.keypairType);

const data = await this.client.patch<any>(`${this.accountUri}/${twinID}`, body, { headers });
return data;
Expand Down
4 changes: 2 additions & 2 deletions packages/registrar_client/src/modules/farms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export class Farms {
}

const farm = { farm_name: farmName, dedicated, twin_id: twinID, stellar_address: stellarAddress };
const headers = createAuthHeader(twinID, this.client.privateKey);
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed, this.client.keypairType);
try {
const data = await this.client.post<FarmCreationResponse>(`${this.farmUri}/`, farm, { headers });
return data;
Expand Down Expand Up @@ -94,7 +94,7 @@ export class Farms {
throw new Error("Invalid stellar address");
}

const headers = createAuthHeader(twinID, this.client.privateKey);
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed, this.client.keypairType);
const farm : FarmUpdateRequest = { farm_name: name, stellar_address: stellarAddress };
try {
const data = await this.client.patch<any>(`${this.farmUri}/${farmID}`,farm , { headers });
Expand Down
6 changes: 3 additions & 3 deletions packages/registrar_client/src/modules/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class Nodes {

async registerNode(node: NodeRegistrationRequest): Promise<NodeRegistrationResponse> {
this._validateNodeData(node);
const headers = createAuthHeader(node.twin_id, this.client.privateKey);
const headers = await createAuthHeader(node.twin_id, this.client.mnemonicOrSeed, this.client.keypairType);
try {
const data = await this.client.post<NodeRegistrationResponse>(`${this.nodeUri}/`, node, { headers });
return data;
Expand Down Expand Up @@ -50,7 +50,7 @@ export class Nodes {

async updateNode(nodeID: number, twinID: number, node: UpdateNodeRequest): Promise<any> {
this._validateNodeData(node);
const headers = createAuthHeader(twinID, this.client.privateKey);
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed, this.client.keypairType);
try {
const data = await this.client.patch<Node>(`${this.nodeUri}/${nodeID}`, node, { headers });
return data;
Expand All @@ -60,7 +60,7 @@ export class Nodes {
}

async reportNodeUptime(nodeID: number, twinID: number, uptime: UptimeReportRequest): Promise<any> {
const headers = createAuthHeader(twinID, this.client.privateKey);
const headers = await createAuthHeader(twinID, this.client.mnemonicOrSeed, this.client.keypairType);
try {
const data = await this.client.post<any>(`${this.nodeUri}/${nodeID}/uptime`, uptime, { headers });
return data;
Expand Down
Loading