Skip to content
This repository has been archived by the owner on Jul 7, 2021. It is now read-only.

Commit

Permalink
feat(profiles): implement CoinRepository for direct coin interactio…
Browse files Browse the repository at this point in the history
…ns (#669)
  • Loading branch information
faustbrian committed Aug 23, 2020
1 parent 0f5ae56 commit 7e5bac8
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 49 deletions.
14 changes: 9 additions & 5 deletions packages/platform-sdk-ark/src/services/client.ts
Expand Up @@ -69,7 +69,7 @@ export class ClientService implements Contracts.ClientService {
}

public async delegates(query?: Contracts.KeyValuePair): Promise<Coins.WalletDataCollection> {
const body = await this.get("delegates", query);
const body = await this.get("delegates", this.createSearchParams(query || {}));

return new Coins.WalletDataCollection(
body.data.map((wallet) => new WalletData(wallet)),
Expand All @@ -78,7 +78,7 @@ export class ClientService implements Contracts.ClientService {
}

public async votes(id: string, query?: Contracts.KeyValuePair): Promise<Coins.TransactionDataCollection> {
const body = await this.get(`wallets/${id}/votes`, query);
const body = await this.get(`wallets/${id}/votes`, this.createSearchParams(query || {}));

return Helpers.createTransactionDataCollectionWithType(
body.data,
Expand All @@ -88,7 +88,7 @@ export class ClientService implements Contracts.ClientService {
}

public async voters(id: string, query?: Contracts.KeyValuePair): Promise<Coins.WalletDataCollection> {
const body = await this.get(`delegates/${id}/voters`, query);
const body = await this.get(`delegates/${id}/voters`, this.createSearchParams(query || {}));

return new Coins.WalletDataCollection(
body.data.map((wallet) => new WalletData(wallet)),
Expand Down Expand Up @@ -141,7 +141,7 @@ export class ClientService implements Contracts.ClientService {
}

private async get(path: string, query?: Contracts.KeyValuePair): Promise<Contracts.KeyValuePair> {
const response = await this.#http.get(`${this.#peer}/${path}`, query);
const response = await this.#http.get(`${this.#peer}/${path}`, query?.searchParams);

return response.json();
}
Expand All @@ -166,7 +166,11 @@ export class ClientService implements Contracts.ClientService {
};
}

private createSearchParams(body: Contracts.ClientPagination): { body: object; searchParams: object } {
private createSearchParams(body: Contracts.ClientPagination): { body: object | null; searchParams: object | null } {
if (Object.keys(body).length <= 0) {
return { body: null, searchParams: null };
}

const result: any = {
body,
searchParams: {},
Expand Down
@@ -0,0 +1,44 @@
import "jest-extended";

import { ARK } from "@arkecosystem/platform-sdk-ark";
import { Request } from "@arkecosystem/platform-sdk-http-got";
import nock from "nock";

import { container } from "../environment/container";
import { Identifiers } from "../environment/container.models";
import { CoinRepository } from "./coin-repository";

let subject: CoinRepository;

beforeAll(() => {
nock.disableNetConnect();

nock(/.+/)
.get("/api/node/configuration")
.reply(200, require("../../test/fixtures/client/configuration.json"))
.get("/api/node/configuration/crypto")
.reply(200, require("../../test/fixtures/client/cryptoConfiguration.json"))
.get("/api/node/syncing")
.reply(200, require("../../test/fixtures/client/syncing.json"))
.get("/api/peers")
.reply(200, require("../../test/fixtures/client/peers.json"))
.get("/api/delegates?page=1")
.reply(200, require("../../test/fixtures/client/delegates-1.json"))
.get("/api/delegates?page=2")
.reply(200, require("../../test/fixtures/client/delegates-2.json"))
.persist();

container.set(Identifiers.HttpClient, new Request());
container.set(Identifiers.Coins, { ARK });
});

beforeEach(() => (subject = new CoinRepository()));

it("should sync the delegates", async () => {
expect(subject.delegates("ARK", "devnet")).toBeUndefined();

await subject.syncDelegates("ARK", "devnet");

expect(subject.delegates("ARK", "devnet")).toBeArray();
expect(subject.delegates("ARK", "devnet")).toHaveLength(200);
});
37 changes: 37 additions & 0 deletions packages/platform-sdk-profiles/src/environment/coin-repository.ts
@@ -0,0 +1,37 @@
import { Coins, Contracts } from "@arkecosystem/platform-sdk";

import { DataRepository } from "../repositories/data-repository";
import { makeCoin } from "./container.helpers";

export class CoinRepository {
readonly #dataRepository: DataRepository = new DataRepository();

public delegates(coin: string, network: string): any {
return this.#dataRepository.get(`${coin}.${network}.delegates`);
}

public async syncDelegates(coin: string, network: string): Promise<void> {
const instance: Coins.Coin = await makeCoin(coin, network);
const instanceKey = `${coin}.${network}.delegates`;

let result: Contracts.WalletData[] = [];
let hasMore = true;
// TODO: use the nextPage() method as cursor like aggregates
let cursor = 1;

while (hasMore) {
const response = await instance.client().delegates({ cursor });

result = result.concat(response.items());

hasMore = response.hasMorePages();

cursor++;
}

this.#dataRepository.set(
instanceKey,
result.map((delegate: Contracts.WalletData) => delegate.toObject()),
);
}
}
@@ -1,6 +1,7 @@
export const Identifiers = {
AppData: "Data<App>",
Coins: "Coins",
CoinRepository: "CoinRepository",
ContactRepository: "ContactRepository",
DataRepository: "DataRepository",
HttpClient: "HttpClient",
Expand Down
6 changes: 6 additions & 0 deletions packages/platform-sdk-profiles/src/environment/env.ts
Expand Up @@ -4,6 +4,7 @@ import { Validator, ValidatorSchema } from "@arkecosystem/platform-sdk-support";
import { DataRepository } from "../repositories/data-repository";
import { ProfileRepository } from "../repositories/profile-repository";
import { NetworkData } from "../wallets/network";
import { CoinRepository } from "./coin-repository";
import { container } from "./container";
import { makeCoin } from "./container.helpers";
import { Identifiers } from "./container.models";
Expand Down Expand Up @@ -66,6 +67,10 @@ export class Environment {
await storage.set("data", this.data().all());
}

public coins(): CoinRepository {
return container.get(Identifiers.CoinRepository);
}

public profiles(): ProfileRepository {
return container.get(Identifiers.ProfileRepository);
}
Expand Down Expand Up @@ -135,6 +140,7 @@ export class Environment {
container.set(Identifiers.AppData, new DataRepository());
container.set(Identifiers.HttpClient, options.httpClient);
container.set(Identifiers.ProfileRepository, new ProfileRepository());
container.set(Identifiers.CoinRepository, new CoinRepository());

container.set(Identifiers.Coins, options.coins);
}
Expand Down
Expand Up @@ -12,6 +12,7 @@ import { Profile } from "../../profiles/profile";
import { ProfileSetting } from "../../profiles/profile.models";
import { Wallet } from "../wallet";
import { DelegateMapper } from "./delegate-mapper";
import { CoinRepository } from "../../environment/coin-repository";

let wallet: Wallet;

Expand All @@ -37,6 +38,7 @@ beforeAll(() => {
});

beforeEach(async () => {
container.set(Identifiers.CoinRepository, new CoinRepository());
container.set(Identifiers.HttpClient, new Request());
container.set(Identifiers.Coins, { ARK });

Expand All @@ -52,7 +54,7 @@ it("should map the public keys to read-only wallets", async () => {
const publicKeys = delegates.map((delegate) => delegate.publicKey);
const usernames = delegates.map((delegate) => delegate.usernames);

await wallet.syncDelegates();
await container.get<CoinRepository>(Identifiers.CoinRepository).syncDelegates(wallet.coinId(), wallet.networkId());

const mappedDelegates = new DelegateMapper(wallet).map(publicKeys);

Expand Down
@@ -1,15 +1,20 @@
import { CoinRepository } from "../../environment/coin-repository";
import { container } from "../../environment/container";
import { Identifiers } from "../../environment/container.models";
import { ReadOnlyWallet } from "../read-only-wallet";
import { WalletData } from "../wallet.models";
import { ReadWriteWallet } from "../wallet.models";

export class DelegateMapper {
readonly #wallet;
readonly #wallet: ReadWriteWallet;

public constructor(wallet) {
public constructor(wallet: ReadWriteWallet) {
this.#wallet = wallet;
}

public map(publicKeys: string[]): ReadOnlyWallet[] {
const delegates: Record<string, string>[] = this.#wallet.data().get(WalletData.Delegates);
const delegates: Record<string, string>[] = container
.get<CoinRepository>(Identifiers.CoinRepository)
.delegates(this.#wallet.coinId(), this.#wallet.networkId());

return publicKeys
.map((publicKey: string) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/platform-sdk-profiles/src/wallets/wallet.models.ts
Expand Up @@ -82,6 +82,8 @@ export interface ReadWriteWallet {
isSecondSignature(): boolean;
isStarred(): boolean;
toggleStarred(): void;
coinId(): string;
networkId(): string;
manifest(): Coins.Manifest;
config(): Coins.Config;
guard(): Coins.Guard;
Expand All @@ -108,7 +110,6 @@ export interface ReadWriteWallet {
entityUpdateAggregate(): EntityUpdateAggregate;
mapDelegates(publicKeys: string[]): ReadOnlyWallet[];
syncIdentity(): Promise<void>;
syncDelegates(): Promise<void>;
syncVotes(): Promise<void>;
syncExchangeRate(): Promise<void>;
}
10 changes: 0 additions & 10 deletions packages/platform-sdk-profiles/src/wallets/wallet.test.ts
Expand Up @@ -187,7 +187,6 @@ describe.each([123, 456, 789])("%s", (slip44) => {
expect(actual.data).toEqual({
BALANCE: "55827093444556",
BROADCASTED_TRANSACTIONS: {},
DELEGATES: [],
EXCHANGE_RATE: 0,
SEQUENCE: "111932",
SIGNED_TRANSACTIONS: {},
Expand All @@ -210,12 +209,3 @@ it("should sync the exchange rate for ARK to BTC", async () => {

expect(subject.data().get(WalletData.ExchangeRate)).toBe(0.00005048);
});

it("should sync the delegates", async () => {
expect(subject.data().get(WalletData.Delegates)).toBeUndefined();

await subject.syncDelegates();

expect(subject.data().get(WalletData.Delegates)).toBeArray();
expect(subject.data().get(WalletData.Delegates)).toHaveLength(200);
});
37 changes: 9 additions & 28 deletions packages/platform-sdk-profiles/src/wallets/wallet.ts
Expand Up @@ -205,7 +205,6 @@ export class Wallet implements ReadWriteWallet {
data: {
[WalletData.Balance]: this.balance().toFixed(),
[WalletData.BroadcastedTransactions]: this.data().get(WalletData.BroadcastedTransactions, []),
[WalletData.Delegates]: this.data().get(WalletData.Delegates, []),
[WalletData.ExchangeRate]: this.data().get(WalletData.ExchangeRate, 0),
[WalletData.Sequence]: this.nonce().toFixed(),
[WalletData.SignedTransactions]: this.data().get(WalletData.SignedTransactions, []),
Expand Down Expand Up @@ -272,6 +271,15 @@ export class Wallet implements ReadWriteWallet {
* Any changes in how things need to be handled by consumers should be made in this package!
*/

public coinId(): string {
// TODO: make this non-nullable
return this.manifest().get<string>("name")!;
}

public networkId(): string {
return this.network().id;
}

public manifest(): Coins.Manifest {
return this.#coin.manifest();
}
Expand Down Expand Up @@ -425,33 +433,6 @@ export class Wallet implements ReadWriteWallet {
}
}

public async syncDelegates(): Promise<void> {
try {
let result: Contracts.WalletData[] = [];
let hasMore = true;
let page = 1;

while (hasMore) {
const response = await this.delegates({ page });

result = result.concat(response.items());

hasMore = response.hasMorePages();

page++;
}

this.data().set(
WalletData.Delegates,
result.map((delegate: Contracts.WalletData) => delegate.toObject()),
);
} catch {
if (this.data().has(WalletData.Delegates)) {
this.data().forget(WalletData.Delegates);
}
}
}

public async syncVotes(): Promise<void> {
try {
const response: Coins.TransactionDataCollection = await this.client().votes(this.address());
Expand Down

0 comments on commit 7e5bac8

Please sign in to comment.