diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d56caed..ec56e650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ## Fix - `updateNeuron` to not change the neuron subaccount. +- `list_neurons` to use old `ListNeurons` type for hardware wallet compatibility. # 2024.06.11-1630Z diff --git a/packages/nns/README.md b/packages/nns/README.md index 76626c63..dbb4cae2 100644 --- a/packages/nns/README.md +++ b/packages/nns/README.md @@ -155,7 +155,7 @@ Parameters: ### :factory: GovernanceCanister -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L91) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L93) #### Methods @@ -197,7 +197,7 @@ Parameters: | -------- | ------------------------------------------------------------- | | `create` | `(options?: GovernanceCanisterOptions) => GovernanceCanister` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L104) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L108) ##### :gear: listNeurons @@ -214,7 +214,7 @@ The backend treats `includeEmptyNeurons` as true if absent. | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `listNeurons` | `({ certified, neuronIds, includeEmptyNeurons, }: { certified: boolean; neuronIds?: bigint[] or undefined; includeEmptyNeurons?: boolean or undefined; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L131) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L148) ##### :gear: listKnownNeurons @@ -228,7 +228,7 @@ it is fetched using a query call. | ------------------ | ------------------------------------------------- | | `listKnownNeurons` | `(certified?: boolean) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L156) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L181) ##### :gear: getLastestRewardEvent @@ -241,7 +241,7 @@ it's fetched using a query call. | ----------------------- | ----------------------------------------------- | | `getLastestRewardEvent` | `(certified?: boolean) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L178) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L203) ##### :gear: listProposals @@ -260,7 +260,7 @@ Parameters: - `request`: the options to list the proposals (limit number of results, topics to search for, etc.) - `certified`: query or update calls -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L194) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L219) ##### :gear: stakeNeuron @@ -268,7 +268,7 @@ Parameters: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stakeNeuron` | `({ stake, principal, fromSubAccount, ledgerCanister, createdAt, fee, }: { stake: bigint; principal: Principal; fromSubAccount?: number[] or undefined; ledgerCanister: LedgerCanister; createdAt?: bigint or undefined; fee?: bigint or undefined; }) => Promise<...>` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L213) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L238) ##### :gear: stakeNeuronIcrc1 @@ -276,7 +276,7 @@ Parameters: | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stakeNeuronIcrc1` | `({ stake, principal, fromSubAccount, ledgerCanister, createdAt, fee, }: { stake: bigint; principal: Principal; fromSubAccount?: Uint8Array or undefined; ledgerCanister: LedgerCanister; createdAt?: bigint or undefined; fee?: bigint or undefined; }) => Promise<...>` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L279) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L304) ##### :gear: increaseDissolveDelay @@ -286,7 +286,7 @@ Increases dissolve delay of a neuron | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `increaseDissolveDelay` | `({ neuronId, additionalDissolveDelaySeconds, }: { neuronId: bigint; additionalDissolveDelaySeconds: number; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L344) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L369) ##### :gear: setDissolveDelay @@ -297,7 +297,7 @@ The new date is now + dissolveDelaySeconds. | ------------------ | ------------------------------------------------------------------------------------------------------------- | | `setDissolveDelay` | `({ neuronId, dissolveDelaySeconds, }: { neuronId: bigint; dissolveDelaySeconds: number; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L370) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L395) ##### :gear: startDissolving @@ -307,7 +307,7 @@ Start dissolving process of a neuron | ----------------- | ------------------------------------- | | `startDissolving` | `(neuronId: bigint) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L393) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L418) ##### :gear: stopDissolving @@ -317,7 +317,7 @@ Stop dissolving process of a neuron | ---------------- | ------------------------------------- | | `stopDissolving` | `(neuronId: bigint) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L407) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L432) ##### :gear: joinCommunityFund @@ -327,7 +327,7 @@ Neuron joins the community fund | ------------------- | ------------------------------------- | | `joinCommunityFund` | `(neuronId: bigint) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L421) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L446) ##### :gear: autoStakeMaturity @@ -342,7 +342,7 @@ Parameters: - `neuronId`: The id of the neuron for which to request a change of the auto stake feature - `autoStake`: `true` to enable the auto-stake maturity for this neuron, `false` to turn it off -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L439) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L464) ##### :gear: leaveCommunityFund @@ -352,7 +352,7 @@ Neuron leaves the community fund | -------------------- | ------------------------------------- | | `leaveCommunityFund` | `(neuronId: bigint) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L454) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L479) ##### :gear: setNodeProviderAccount @@ -363,7 +363,7 @@ Where the reward is paid to. | ------------------------ | ---------------------------------------------- | | `setNodeProviderAccount` | `(accountIdentifier: string) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L471) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L496) ##### :gear: mergeNeurons @@ -373,7 +373,7 @@ Merge two neurons | -------------- | --------------------------------------------------------------------------------- | | `mergeNeurons` | `(request: { sourceNeuronId: bigint; targetNeuronId: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L491) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L516) ##### :gear: simulateMergeNeurons @@ -383,7 +383,7 @@ Simulate merging two neurons | ---------------------- | --------------------------------------------------------------------------------------- | | `simulateMergeNeurons` | `(request: { sourceNeuronId: bigint; targetNeuronId: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L508) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L533) ##### :gear: splitNeuron @@ -393,7 +393,7 @@ Splits a neuron creating a new one | ------------- | ----------------------------------------------------------------------------------- | | `splitNeuron` | `({ neuronId, amount, }: { neuronId: bigint; amount: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L553) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L578) ##### :gear: getProposal @@ -406,7 +406,7 @@ it is fetched using a query call. | ------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `getProposal` | `({ proposalId, certified, }: { proposalId: bigint; certified?: boolean or undefined; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L593) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L618) ##### :gear: makeProposal @@ -416,7 +416,7 @@ Create new proposal | -------------- | ---------------------------------------------------------------- | | `makeProposal` | `(request: MakeProposalRequest) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L611) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L636) ##### :gear: registerVote @@ -426,7 +426,7 @@ Registers vote for a proposal from the neuron passed. | -------------- | ----------------------------------------------------------------------------------------------------------- | | `registerVote` | `({ neuronId, vote, proposalId, }: { neuronId: bigint; vote: Vote; proposalId: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L632) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L657) ##### :gear: setFollowees @@ -436,7 +436,7 @@ Edit neuron followees per topic | -------------- | ------------------------------------------------- | | `setFollowees` | `(followRequest: FollowRequest) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L654) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L679) ##### :gear: disburse @@ -446,7 +446,7 @@ Disburse neuron on Account | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `disburse` | `({ neuronId, toAccountId, amount, }: { neuronId: bigint; toAccountId?: string or undefined; amount?: bigint or undefined; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L669) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L694) ##### :gear: mergeMaturity @@ -456,7 +456,7 @@ Merge Maturity of a neuron | --------------- | ------------------------------------------------------------------------------------------------------- | | `mergeMaturity` | `({ neuronId, percentageToMerge, }: { neuronId: bigint; percentageToMerge: number; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L705) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L730) ##### :gear: stakeMaturity @@ -471,7 +471,7 @@ Parameters: - `neuronId`: The id of the neuron for which to stake the maturity - `percentageToStake`: Optional. Percentage of the current maturity to stake. If not provided, all of the neuron's current maturity will be staked. -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L734) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L759) ##### :gear: spawnNeuron @@ -481,7 +481,7 @@ Merge Maturity of a neuron | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `spawnNeuron` | `({ neuronId, percentageToSpawn, newController, nonce, }: { neuronId: bigint; percentageToSpawn?: number or undefined; newController?: Principal or undefined; nonce?: bigint or undefined; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L756) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L781) ##### :gear: addHotkey @@ -491,7 +491,7 @@ Add hotkey to neuron | ----------- | ------------------------------------------------------------------------------------------ | | `addHotkey` | `({ neuronId, principal, }: { neuronId: bigint; principal: Principal; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L803) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L828) ##### :gear: removeHotkey @@ -501,7 +501,7 @@ Remove hotkey to neuron | -------------- | ------------------------------------------------------------------------------------------ | | `removeHotkey` | `({ neuronId, principal, }: { neuronId: bigint; principal: Principal; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L823) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L848) ##### :gear: claimOrRefreshNeuronFromAccount @@ -511,7 +511,7 @@ Gets the NeuronID of a newly created neuron. | --------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `claimOrRefreshNeuronFromAccount` | `({ memo, controller, }: { memo: bigint; controller?: Principal or undefined; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L841) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L866) ##### :gear: claimOrRefreshNeuron @@ -522,7 +522,7 @@ Uses query call only. | ---------------------- | ------------------------------------------------------------------------ | | `claimOrRefreshNeuron` | `(request: ClaimOrRefreshNeuronRequest) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L872) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L897) ##### :gear: getNeuron @@ -532,7 +532,7 @@ Return the data of the neuron provided as id. | ----------- | ----------------------------------------------------------------------------------------------------------- | | `getNeuron` | `({ certified, neuronId, }: { certified: boolean; neuronId: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L923) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L948) ### :factory: SnsWasmCanister diff --git a/packages/nns/candid/old_list_neurons_service.certified.idl.d.ts b/packages/nns/candid/old_list_neurons_service.certified.idl.d.ts new file mode 100644 index 00000000..8e1474b8 --- /dev/null +++ b/packages/nns/candid/old_list_neurons_service.certified.idl.d.ts @@ -0,0 +1,2 @@ +import type { IDL } from "@dfinity/candid"; +export const idlFactory: IDL.InterfaceFactory; diff --git a/packages/nns/candid/old_list_neurons_service.certified.idl.js b/packages/nns/candid/old_list_neurons_service.certified.idl.js new file mode 100644 index 00000000..19b46737 --- /dev/null +++ b/packages/nns/candid/old_list_neurons_service.certified.idl.js @@ -0,0 +1,78 @@ +// This file was created manually by taking governance.certified.idl.js and +// removing everything that isn't needed for `list_neurons` and then removing +// `include_empty_neurons_readable_by_caller` from `ListNeurons`. +// The Ledger hardware wallet doesn't support the +// `include_empty_neurons_readable_by_caller` field, even when it's not set, so +// we use this service for compatibility with the hardware wallet. +export const idlFactory = ({ IDL }) => { + const NeuronId = IDL.Record({ id: IDL.Nat64 }); + const Followees = IDL.Record({ followees: IDL.Vec(NeuronId) }); + const KnownNeuronData = IDL.Record({ + name: IDL.Text, + description: IDL.Opt(IDL.Text), + }); + const NeuronStakeTransfer = IDL.Record({ + to_subaccount: IDL.Vec(IDL.Nat8), + neuron_stake_e8s: IDL.Nat64, + from: IDL.Opt(IDL.Principal), + memo: IDL.Nat64, + from_subaccount: IDL.Vec(IDL.Nat8), + transfer_timestamp: IDL.Nat64, + block_height: IDL.Nat64, + }); + const BallotInfo = IDL.Record({ + vote: IDL.Int32, + proposal_id: IDL.Opt(NeuronId), + }); + const DissolveState = IDL.Variant({ + DissolveDelaySeconds: IDL.Nat64, + WhenDissolvedTimestampSeconds: IDL.Nat64, + }); + const Neuron = IDL.Record({ + id: IDL.Opt(NeuronId), + staked_maturity_e8s_equivalent: IDL.Opt(IDL.Nat64), + controller: IDL.Opt(IDL.Principal), + recent_ballots: IDL.Vec(BallotInfo), + kyc_verified: IDL.Bool, + neuron_type: IDL.Opt(IDL.Int32), + not_for_profit: IDL.Bool, + maturity_e8s_equivalent: IDL.Nat64, + cached_neuron_stake_e8s: IDL.Nat64, + created_timestamp_seconds: IDL.Nat64, + auto_stake_maturity: IDL.Opt(IDL.Bool), + aging_since_timestamp_seconds: IDL.Nat64, + hot_keys: IDL.Vec(IDL.Principal), + account: IDL.Vec(IDL.Nat8), + joined_community_fund_timestamp_seconds: IDL.Opt(IDL.Nat64), + dissolve_state: IDL.Opt(DissolveState), + followees: IDL.Vec(IDL.Tuple(IDL.Int32, Followees)), + neuron_fees_e8s: IDL.Nat64, + transfer: IDL.Opt(NeuronStakeTransfer), + known_neuron_data: IDL.Opt(KnownNeuronData), + spawn_at_timestamp_seconds: IDL.Opt(IDL.Nat64), + }); + const NeuronInfo = IDL.Record({ + dissolve_delay_seconds: IDL.Nat64, + recent_ballots: IDL.Vec(BallotInfo), + neuron_type: IDL.Opt(IDL.Int32), + created_timestamp_seconds: IDL.Nat64, + state: IDL.Int32, + stake_e8s: IDL.Nat64, + joined_community_fund_timestamp_seconds: IDL.Opt(IDL.Nat64), + retrieved_at_timestamp_seconds: IDL.Nat64, + known_neuron_data: IDL.Opt(KnownNeuronData), + voting_power: IDL.Nat64, + age_seconds: IDL.Nat64, + }); + const ListNeurons = IDL.Record({ + neuron_ids: IDL.Vec(IDL.Nat64), + include_neurons_readable_by_caller: IDL.Bool, + }); + const ListNeuronsResponse = IDL.Record({ + neuron_infos: IDL.Vec(IDL.Tuple(IDL.Nat64, NeuronInfo)), + full_neurons: IDL.Vec(Neuron), + }); + return IDL.Service({ + list_neurons: IDL.Func([ListNeurons], [ListNeuronsResponse], []), + }); +}; diff --git a/packages/nns/src/governance.canister.spec.ts b/packages/nns/src/governance.canister.spec.ts index 04bff7e6..8a452b69 100644 --- a/packages/nns/src/governance.canister.spec.ts +++ b/packages/nns/src/governance.canister.spec.ts @@ -425,31 +425,39 @@ describe("GovernanceCanister", () => { describe("GovernanceCanister.listNeurons", () => { it("list user neurons", async () => { const service = mock>(); - service.list_neurons.mockResolvedValue(mockListNeuronsResponse); + const certifiedService = mock>(); + const oldService = mock>(); + certifiedService.list_neurons.mockResolvedValue(mockListNeuronsResponse); const governance = GovernanceCanister.create({ - certifiedServiceOverride: service, + certifiedServiceOverride: certifiedService, serviceOverride: service, + oldListNeuronsServiceOverride: oldService, }); const neurons = await governance.listNeurons({ certified: true, + includeEmptyNeurons: true, }); - expect(service.list_neurons).toBeCalledWith({ + expect(certifiedService.list_neurons).toBeCalledWith({ neuron_ids: new BigUint64Array(), include_neurons_readable_by_caller: true, - include_empty_neurons_readable_by_caller: [], + include_empty_neurons_readable_by_caller: [true], }); - expect(service.list_neurons).toBeCalledTimes(1); + expect(certifiedService.list_neurons).toBeCalledTimes(1); expect(neurons.length).toBe(1); + expect(service.list_neurons).not.toBeCalled(); + expect(oldService.list_neurons).not.toBeCalled(); }); it("list user neurons excluding empty neurons", async () => { const service = mock>(); + const oldService = mock>(); service.list_neurons.mockResolvedValue(mockListNeuronsResponse); const governance = GovernanceCanister.create({ certifiedServiceOverride: service, serviceOverride: service, + oldListNeuronsServiceOverride: oldService, }); const neurons = await governance.listNeurons({ certified: true, @@ -463,6 +471,55 @@ describe("GovernanceCanister", () => { expect(service.list_neurons).toBeCalledTimes(1); expect(neurons.length).toBe(1); }); + + it("should use old service when not excluding empty neurons", async () => { + const service = mock>(); + const oldService = mock>(); + oldService.list_neurons.mockResolvedValue(mockListNeuronsResponse); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + serviceOverride: service, + oldListNeuronsServiceOverride: oldService, + }); + const neurons = await governance.listNeurons({ + certified: true, + }); + expect(oldService.list_neurons).toBeCalledWith({ + neuron_ids: new BigUint64Array(), + include_neurons_readable_by_caller: true, + // The field is present in the argument but ignored by the old service. + include_empty_neurons_readable_by_caller: [], + }); + expect(oldService.list_neurons).toBeCalledTimes(1); + expect(neurons.length).toBe(1); + expect(service.list_neurons).not.toBeCalled(); + }); + + it("should not use old service when not certified", async () => { + const certifiedService = mock>(); + const service = mock>(); + const oldService = mock>(); + service.list_neurons.mockResolvedValue(mockListNeuronsResponse); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: certifiedService, + serviceOverride: service, + oldListNeuronsServiceOverride: oldService, + }); + const neurons = await governance.listNeurons({ + certified: false, + }); + expect(service.list_neurons).toBeCalledWith({ + neuron_ids: new BigUint64Array(), + include_neurons_readable_by_caller: true, + include_empty_neurons_readable_by_caller: [], + }); + expect(service.list_neurons).toBeCalledTimes(1); + expect(neurons.length).toBe(1); + expect(oldService.list_neurons).not.toBeCalled(); + expect(certifiedService.list_neurons).not.toBeCalled(); + }); }); describe("GovernanceCanister.listProposals", () => { @@ -613,6 +670,7 @@ describe("GovernanceCanister", () => { const governance = GovernanceCanister.create({ certifiedServiceOverride: service, serviceOverride: service, + oldListNeuronsServiceOverride: service, }); service.list_neurons.mockResolvedValue( diff --git a/packages/nns/src/governance.canister.ts b/packages/nns/src/governance.canister.ts index adfd1ed1..38ebd63f 100644 --- a/packages/nns/src/governance.canister.ts +++ b/packages/nns/src/governance.canister.ts @@ -1,4 +1,5 @@ import type { ActorSubclass, Agent } from "@dfinity/agent"; +import { Actor } from "@dfinity/agent"; import type { LedgerCanister } from "@dfinity/ledger-icp"; import { AccountIdentifier, @@ -30,6 +31,7 @@ import type { } from "../candid/governance"; import { idlFactory as certifiedIdlFactory } from "../candid/governance.certified.idl"; import { idlFactory } from "../candid/governance.idl"; +import { idlFactory as oldListNeuronsCertifiedIdlFactory } from "../candid/old_list_neurons_service.certified.idl"; import { fromClaimOrRefreshNeuronRequest, fromListNeurons, @@ -93,11 +95,13 @@ export class GovernanceCanister { private readonly canisterId: Principal, private readonly service: ActorSubclass, private readonly certifiedService: ActorSubclass, + private readonly oldListNeuronsCertifiedService: ActorSubclass, private readonly agent: Agent, ) { this.canisterId = canisterId; this.service = service; this.certifiedService = certifiedService; + this.oldListNeuronsCertifiedService = oldListNeuronsCertifiedService; this.agent = agent; } @@ -115,7 +119,20 @@ export class GovernanceCanister { certifiedIdlFactory, }); - return new GovernanceCanister(canisterId, service, certifiedService, agent); + const oldListNeuronsCertifiedService = + options.oldListNeuronsServiceOverride ?? + Actor.createActor(oldListNeuronsCertifiedIdlFactory, { + agent, + canisterId, + }); + + return new GovernanceCanister( + canisterId, + service, + certifiedService, + oldListNeuronsCertifiedService, + agent, + ); } /** @@ -138,8 +155,16 @@ export class GovernanceCanister { includeEmptyNeurons?: boolean; }): Promise => { const rawRequest = fromListNeurons({ neuronIds, includeEmptyNeurons }); - const raw_response = - await this.getGovernanceService(certified).list_neurons(rawRequest); + // The Ledger app version 2.4.9 does not support + // include_empty_neurons_readable_by_caller, even when the field is absent, + // so we use the old service (which does not have this field) if possible, + // in case the call will be signed by the Ledger device. We only have a + // certified version of the old service. + const useOldMethod = isNullish(includeEmptyNeurons) && certified; + const service = useOldMethod + ? this.oldListNeuronsCertifiedService + : this.getGovernanceService(certified); + const raw_response = await service.list_neurons(rawRequest); return toArrayOfNeuronInfo({ response: raw_response, canisterId: this.canisterId, diff --git a/packages/nns/src/types/governance.options.ts b/packages/nns/src/types/governance.options.ts index 6aa2e56a..e9bf42d0 100644 --- a/packages/nns/src/types/governance.options.ts +++ b/packages/nns/src/types/governance.options.ts @@ -1,3 +1,4 @@ +import type { ActorSubclass } from "@dfinity/agent"; import type { CanisterOptions } from "@dfinity/utils"; import type { _SERVICE as GovernanceService } from "../../candid/governance"; @@ -6,4 +7,5 @@ export interface GovernanceCanisterOptions // Ledger IC App needs requests built with Protobuf. // This flag ensures that the methods use Protobuf. hardwareWallet?: boolean; + oldListNeuronsServiceOverride?: ActorSubclass; }