From 0b60bb1454af1428b876aa1724bf1fbc8177e522 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Fri, 13 Oct 2023 10:32:38 +0100 Subject: [PATCH] Get nervous system data from the Registry (#4575) --- .../src/components/home/AccessGateIcon.svelte | 3 +- .../home/AccessGateParameters.svelte | 3 +- .../components/home/VisibilityControl.svelte | 2 +- .../home/proposals/ProposalContent.svelte | 2 +- frontend/app/src/utils/access.ts | 26 +++++---- .../src/services/openchatAgent.ts | 4 +- .../src/services/registry/candid/idl.d.ts | 9 +-- .../src/services/registry/mappers.ts | 43 ++++++++------ frontend/openchat-client/src/openchat.ts | 58 +++++++++---------- frontend/openchat-shared/src/domain/crypto.ts | 13 ++++- .../src/domain/registry/index.ts | 35 ++--------- 11 files changed, 95 insertions(+), 103 deletions(-) diff --git a/frontend/app/src/components/home/AccessGateIcon.svelte b/frontend/app/src/components/home/AccessGateIcon.svelte index f51954384f..58e9c6a431 100644 --- a/frontend/app/src/components/home/AccessGateIcon.svelte +++ b/frontend/app/src/components/home/AccessGateIcon.svelte @@ -15,8 +15,7 @@ const dispatch = createEventDispatcher(); $: params = formatParams(gate); - $: cryptoLookup = client.cryptoLookup; - $: tokenDetails = client.getTokenDetailsForSnsAccessGate(gate, $cryptoLookup); + $: tokenDetails = client.getTokenDetailsForSnsAccessGate(gate); function formatParams(gate: AccessGate): string { const parts = []; diff --git a/frontend/app/src/components/home/AccessGateParameters.svelte b/frontend/app/src/components/home/AccessGateParameters.svelte index fb230ccfb4..35440b626a 100644 --- a/frontend/app/src/components/home/AccessGateParameters.svelte +++ b/frontend/app/src/components/home/AccessGateParameters.svelte @@ -11,8 +11,7 @@ const client = getContext("client"); export let gate: SNSAccessGate | CredentialGate; - $: cryptoLookup = client.cryptoLookup; - $: tokenDetails = client.getTokenDetailsForSnsAccessGate(gate, $cryptoLookup); + $: tokenDetails = client.getTokenDetailsForSnsAccessGate(gate); {#if gate.kind === "credential_gate"} diff --git a/frontend/app/src/components/home/VisibilityControl.svelte b/frontend/app/src/components/home/VisibilityControl.svelte index 0802eae4de..24592cab69 100644 --- a/frontend/app/src/components/home/VisibilityControl.svelte +++ b/frontend/app/src/components/home/VisibilityControl.svelte @@ -83,7 +83,7 @@ } function snsHolderParams(gate: SNSAccessGate): InterpolationValues { - const tokenDetails = client.getTokenDetailsForSnsAccessGate(gate, $cryptoLookup); + const tokenDetails = client.getTokenDetailsForSnsAccessGate(gate); return tokenDetails ? { token: tokenDetails.symbol } : undefined; } diff --git a/frontend/app/src/components/home/proposals/ProposalContent.svelte b/frontend/app/src/components/home/proposals/ProposalContent.svelte index f1c4e3ff34..8e7fd6fb6b 100644 --- a/frontend/app/src/components/home/proposals/ProposalContent.svelte +++ b/frontend/app/src/components/home/proposals/ProposalContent.svelte @@ -46,7 +46,7 @@ let showPayload = false; $: tokenDetails = client.getTokenByGovernanceCanister(content.governanceCanisterId); - $: rootCanister = tokenDetails.rootCanister ?? ""; + $: rootCanister = tokenDetails.nervousSystem?.rootCanisterId ?? ""; $: proposalTopicsStore = client.proposalTopicsStore; $: isNns = content.proposal.kind === "nns"; $: voteStatus = diff --git a/frontend/app/src/utils/access.ts b/frontend/app/src/utils/access.ts index 59374f668c..88d7f88d7f 100644 --- a/frontend/app/src/utils/access.ts +++ b/frontend/app/src/utils/access.ts @@ -10,16 +10,22 @@ export type GateBinding = { }; function getSnsGateBindings(cryptoLookup: Record): GateBinding[] { - return Object.values(cryptoLookup).map((v) => { - return { - label: "access.snsHolder", - gate: { kind: "sns_gate", governanceCanister: v.governanceCanister! }, - key: "sns_gate", - enabled: true, - cssClass: v.symbol.toLowerCase(), - labelParams: { token: v.symbol }, - }; - }); + return Object.values(cryptoLookup).reduce((gates, next) => { + if (next.nervousSystem !== undefined) { + gates.push({ + label: "access.snsHolder", + gate: { + kind: "sns_gate", + governanceCanister: next.nervousSystem.governanceCanisterId, + }, + key: "sns_gate", + enabled: true, + cssClass: next.symbol.toLowerCase(), + labelParams: { token: next.symbol }, + }); + } + return gates; + }, [] as GateBinding[]); } const noGate: GateBinding = { diff --git a/frontend/openchat-agent/src/services/openchatAgent.ts b/frontend/openchat-agent/src/services/openchatAgent.ts index 9eee28263e..8aed2f9c24 100644 --- a/frontend/openchat-agent/src/services/openchatAgent.ts +++ b/frontend/openchat-agent/src/services/openchatAgent.ts @@ -2481,12 +2481,12 @@ export class OpenChatAgent extends EventTarget { const updates = await this._registryClient.updates(current?.lastUpdated); - if (updates.kind === "success" && updates.tokenDetails !== undefined) { + if (updates.kind === "success") { const updated = { lastUpdated: updates.lastUpdated, tokenDetails: distinctBy( [...updates.tokenDetails, ...(current?.tokenDetails ?? [])], - (t) => t.ledgerCanisterId, + (t) => t.ledger, ), nervousSystemDetails: distinctBy( [...updates.nervousSystemDetails, ...(current?.nervousSystemDetails ?? [])], diff --git a/frontend/openchat-agent/src/services/registry/candid/idl.d.ts b/frontend/openchat-agent/src/services/registry/candid/idl.d.ts index f6db75c930..93c0622897 100644 --- a/frontend/openchat-agent/src/services/registry/candid/idl.d.ts +++ b/frontend/openchat-agent/src/services/registry/candid/idl.d.ts @@ -1,13 +1,10 @@ import type { IDL } from "@dfinity/candid"; -import { - TokenDetails, - UpdatesResponse, - _SERVICE -} from "./types"; +import { NervousSystemSummary, TokenDetails, UpdatesResponse, _SERVICE } from "./types"; export { + NervousSystemSummary as ApiNervousSystemSummary, TokenDetails as ApiTokenDetails, UpdatesResponse as ApiUpdatesResponse, - _SERVICE as RegistryService + _SERVICE as RegistryService, }; export const idlFactory: IDL.InterfaceFactory; diff --git a/frontend/openchat-agent/src/services/registry/mappers.ts b/frontend/openchat-agent/src/services/registry/mappers.ts index 2b8b1a42ed..4360280cd1 100644 --- a/frontend/openchat-agent/src/services/registry/mappers.ts +++ b/frontend/openchat-agent/src/services/registry/mappers.ts @@ -1,5 +1,9 @@ -import type { ApiTokenDetails, ApiUpdatesResponse } from "./candid/idl"; -import type { RegistryUpdatesResponse, TokenDetails } from "openchat-shared"; +import type { ApiNervousSystemSummary, ApiTokenDetails, ApiUpdatesResponse } from "./candid/idl"; +import type { + NervousSystemSummary, + RegistryUpdatesResponse, + RegistryTokenDetails, +} from "openchat-shared"; import { optional } from "../../utils/mapping"; import { UnsupportedValueError } from "openchat-shared"; @@ -8,15 +12,11 @@ export function updatesResponse(candid: ApiUpdatesResponse): RegistryUpdatesResp return { kind: "success", lastUpdated: candid.Success.last_updated, - tokenDetails: optional(candid.Success.token_details, (t) => t.map(tokenDetails)) ?? [], - nervousSystemDetails: candid.Success.nervous_system_details.map((ns) => ({ - rootCanisterId: ns.root_canister_id.toString(), - governanceCanisterId: ns.governance_canister_id.toString(), - ledgerCanisterId: ns.ledger_canister_id.toString(), - isNns: ns.is_nns, - proposalRejectionFee: ns.proposal_rejection_fee, - submittingProposalsEnabled: ns.submitting_proposals_enabled, - })), + tokenDetails: + optional(candid.Success.token_details, (tokens) => + tokens.map((t) => tokenDetails(t)), + ) ?? [], + nervousSystemDetails: candid.Success.nervous_system_details.map(nervousSystemSummary), }; } if ("SuccessNoUpdates" in candid) { @@ -27,18 +27,14 @@ export function updatesResponse(candid: ApiUpdatesResponse): RegistryUpdatesResp throw new UnsupportedValueError("Unexpected ApiUpdatesResponse type received", candid); } -function tokenDetails(candid: ApiTokenDetails): TokenDetails { +function tokenDetails(candid: ApiTokenDetails): RegistryTokenDetails { return { - ledgerCanisterId: candid.ledger_canister_id.toString(), + ledger: candid.ledger_canister_id.toString(), name: candid.name, symbol: candid.symbol, decimals: candid.decimals, - fee: candid.fee, + transferFee: candid.fee, logo: candid.logo, - nervousSystem: optional(candid.nervous_system, (ns) => ({ - root: ns.root.toString(), - governance: ns.governance.toString(), - })), infoUrl: candid.info_url, howToBuyUrl: candid.how_to_buy_url, transactionUrlFormat: candid.transaction_url_format, @@ -46,3 +42,14 @@ function tokenDetails(candid: ApiTokenDetails): TokenDetails { lastUpdated: candid.last_updated, }; } + +function nervousSystemSummary(candid: ApiNervousSystemSummary): NervousSystemSummary { + return { + rootCanisterId: candid.root_canister_id.toString(), + governanceCanisterId: candid.governance_canister_id.toString(), + ledgerCanisterId: candid.ledger_canister_id.toString(), + isNns: candid.is_nns, + proposalRejectionFee: candid.proposal_rejection_fee, + submittingProposalsEnabled: candid.submitting_proposals_enabled, + }; +} diff --git a/frontend/openchat-client/src/openchat.ts b/frontend/openchat-client/src/openchat.ts index acd4b0472b..da430abc1f 100644 --- a/frontend/openchat-client/src/openchat.ts +++ b/frontend/openchat-client/src/openchat.ts @@ -185,7 +185,7 @@ import { } from "./utils/date"; import formatFileSize from "./utils/fileSize"; import { calculateMediaDimensions } from "./utils/layout"; -import { findLast, groupBy, groupWhile, keepMax, toRecord2 } from "./utils/list"; +import { findLast, groupBy, groupWhile, keepMax, toRecord, toRecord2 } from "./utils/list"; import { audioRecordingMimeType, containsSocialVideoLink, @@ -2281,14 +2281,9 @@ export class OpenChat extends OpenChatAgentWorker { return false; } - getTokenDetailsForSnsAccessGate( - gate: AccessGate, - cryptoLookup: Record, - ): CryptocurrencyDetails | undefined { + getTokenDetailsForSnsAccessGate(gate: AccessGate): CryptocurrencyDetails | undefined { if (gate.kind !== "sns_gate") return undefined; - return Object.values(cryptoLookup).find( - (td) => td.governanceCanister === gate.governanceCanister, - ); + return this.tryGetTokenDetailsByGovernanceCanister(gate.governanceCanister); } getMinDissolveDelayDays(gate: AccessGate): number | undefined { @@ -3989,9 +3984,7 @@ export class OpenChat extends OpenChatAgentWorker { } getTokenByGovernanceCanister(governanceCanister: string): CryptocurrencyDetails { - const tokenDetails = Object.values(get(cryptoLookup)).find( - (t) => t.governanceCanister === governanceCanister, - ); + const tokenDetails = this.tryGetTokenDetailsByGovernanceCanister(governanceCanister); if (tokenDetails === undefined) { throw new Error(`Unknown governance canister: ${governanceCanister}`); } else { @@ -4917,32 +4910,37 @@ export class OpenChat extends OpenChatAgentWorker { kind: "updateRegistry", }); + const nervousSystemLookup = toRecord( + registry.nervousSystemDetails, + (ns) => ns.ledgerCanisterId, + ); + cryptoLookup.set( - toRecord2( - registry.tokenDetails, - (t) => t.ledgerCanisterId, - (t) => ({ - name: t.name, - symbol: t.symbol, - ledger: t.ledgerCanisterId, - decimals: t.decimals, - transferFee: t.fee, - logo: t.logo, - howToBuyUrl: t.howToBuyUrl, - infoUrl: t.infoUrl, - transactionUrlFormat: t.transactionUrlFormat, - rootCanister: t.nervousSystem?.root, - governanceCanister: t.nervousSystem?.governance, - lastUpdated: t.lastUpdated, - }), + registry.tokenDetails.reduce( + (results, next) => { + results[next.ledger] = { + ...next, + nervousSystem: nervousSystemLookup[next.ledger], + }; + return results; + }, + {} as Record, ), ); } private getSnsLogo(governanceCanisterId: string): string | undefined { + return this.tryGetTokenDetailsByGovernanceCanister(governanceCanisterId)?.logo; + } + + private tryGetTokenDetailsByGovernanceCanister( + governanceCanisterId: string, + ): CryptocurrencyDetails | undefined { return Object.values(get(cryptoLookup)).find( - (t) => t.governanceCanister === governanceCanisterId, - )?.logo; + (t) => + t.nervousSystem !== undefined && + t.nervousSystem.governanceCanisterId === governanceCanisterId, + ); } // the key might be a username or it might be a user group name diff --git a/frontend/openchat-shared/src/domain/crypto.ts b/frontend/openchat-shared/src/domain/crypto.ts index 341edc913f..5cea5fb58b 100644 --- a/frontend/openchat-shared/src/domain/crypto.ts +++ b/frontend/openchat-shared/src/domain/crypto.ts @@ -21,11 +21,20 @@ export type CryptocurrencyDetails = { howToBuyUrl: string; infoUrl: string; transactionUrlFormat: string; - governanceCanister: string | undefined; - rootCanister: string | undefined; + nervousSystem: NervousSystemSummary | undefined; + added: bigint; lastUpdated: bigint; }; +export type NervousSystemSummary = { + rootCanisterId: string; + governanceCanisterId: string; + ledgerCanisterId: string; + isNns: boolean; + proposalRejectionFee: bigint; + submittingProposalsEnabled: boolean; +}; + // approximate dollar exchange rates - until we come up with something better const dollarToICP = 0.34; diff --git a/frontend/openchat-shared/src/domain/registry/index.ts b/frontend/openchat-shared/src/domain/registry/index.ts index 2955738874..d5109cd2a1 100644 --- a/frontend/openchat-shared/src/domain/registry/index.ts +++ b/frontend/openchat-shared/src/domain/registry/index.ts @@ -1,47 +1,24 @@ +import type { CryptocurrencyDetails, NervousSystemSummary } from "../crypto"; + export type RegistryUpdatesResponse = | RegistryUpdatesResponseSuccess | RegistryUpdatesResponseSuccessNoUpdates; export type RegistryValue = { lastUpdated: bigint; - tokenDetails: TokenDetails[]; + tokenDetails: RegistryTokenDetails[]; nervousSystemDetails: NervousSystemSummary[]; }; export type RegistryUpdatesResponseSuccess = { kind: "success"; lastUpdated: bigint; - tokenDetails: TokenDetails[]; + tokenDetails: RegistryTokenDetails[]; nervousSystemDetails: NervousSystemSummary[]; }; +export type RegistryTokenDetails = Omit; + export type RegistryUpdatesResponseSuccessNoUpdates = { kind: "success_no_updates"; }; - -export type TokenDetails = { - ledgerCanisterId: string; - name: string; - symbol: string; - decimals: number; - fee: bigint; - logo: string; - nervousSystem?: { - root: string; - governance: string; - }; - infoUrl: string; - howToBuyUrl: string; - transactionUrlFormat: string; - added: bigint; - lastUpdated: bigint; -}; - -export type NervousSystemSummary = { - rootCanisterId: string; - governanceCanisterId: string; - ledgerCanisterId: string; - isNns: boolean; - proposalRejectionFee: bigint; - submittingProposalsEnabled: boolean; -};