From 75a3ff49309de7c20fffeed84d4efb73c99b74be Mon Sep 17 00:00:00 2001 From: David de Kloet <122978264+dskloetd@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:45:08 +0200 Subject: [PATCH] Add ICRC-1 version of stakeNeuron method (#418) # Motivation We want use ICRC-1 instead of generating account identifiers in the client. When staking a neuron we first have to transfer the stake to a subaccount of the governance canister. We want to use an ICRC-1 transfer to do this. # Changes Add `stakeNeuronIcrc1` which is a copy of `stakeNeuron` with minimal changes to use `IcrcLedgerCanister` instead of `LedgerCanister`. The plan is for this to replace `stakeNeuron` once it's been properly tested in nns-dapp. # Tests Add a copy of all the `stakeNeuron` tests and apply minimal changes to make them apply to the new `stakeNeuronIcrc1` method. Tested manually by installing the local package in a local nns-dapp and using the new method to create a neuron. I tested increasing stake, setting dissolve delay and voting with this new neuron. --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- packages/nns/README.md | 71 +++++----- packages/nns/src/governance.canister.spec.ts | 128 +++++++++++++++++++ packages/nns/src/governance.canister.ts | 80 +++++++++++- 3 files changed, 247 insertions(+), 32 deletions(-) diff --git a/packages/nns/README.md b/packages/nns/README.md index f51e346f..3d576c24 100644 --- a/packages/nns/README.md +++ b/packages/nns/README.md @@ -312,7 +312,7 @@ Returns the index of the block containing the tx if it was successful. ### :factory: GovernanceCanister -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L107) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L108) #### Methods @@ -322,6 +322,7 @@ Returns the index of the block containing the tx if it was successful. - [getLastestRewardEvent](#gear-getlastestrewardevent) - [listProposals](#gear-listproposals) - [stakeNeuron](#gear-stakeneuron) +- [stakeNeuronIcrc1](#gear-stakeneuronicrc1) - [increaseDissolveDelay](#gear-increasedissolvedelay) - [setDissolveDelay](#gear-setdissolvedelay) - [startDissolving](#gear-startdissolving) @@ -353,7 +354,7 @@ Returns the index of the block containing the tx if it was successful. | -------- | ------------------------------------------------------------- | | `create` | `(options?: GovernanceCanisterOptions) => GovernanceCanister` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L122) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L123) ##### :gear: listNeurons @@ -368,7 +369,7 @@ it is fetched using a query call. | ------------- | ----------------------------------------------------------------------------------------------------- | | `listNeurons` | `({ certified, neuronIds, }: { certified: boolean; neuronIds?: bigint[]; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L154) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L155) ##### :gear: listKnownNeurons @@ -382,7 +383,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#L186) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L187) ##### :gear: getLastestRewardEvent @@ -395,7 +396,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#L209) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L210) ##### :gear: listProposals @@ -414,7 +415,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#L225) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L226) ##### :gear: stakeNeuron @@ -422,7 +423,15 @@ Parameters: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stakeNeuron` | `({ stake, principal, fromSubAccount, ledgerCanister, createdAt, fee, }: { stake: bigint; principal: Principal; fromSubAccount?: number[]; ledgerCanister: LedgerCanister; createdAt?: bigint; fee?: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L245) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L246) + +##### :gear: stakeNeuronIcrc1 + +| Method | Type | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `stakeNeuronIcrc1` | `({ stake, principal, fromSubAccount, icrcLedgerCanister, createdAt, fee, }: { stake: bigint; principal: Principal; fromSubAccount?: number[]; icrcLedgerCanister: IcrcLedgerCanister; createdAt?: bigint; fee?: bigint; }) => Promise` | + +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L310) ##### :gear: increaseDissolveDelay @@ -432,7 +441,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#L306) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L375) ##### :gear: setDissolveDelay @@ -443,7 +452,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#L338) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L407) ##### :gear: startDissolving @@ -453,7 +462,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#L361) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L430) ##### :gear: stopDissolving @@ -463,7 +472,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#L378) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L447) ##### :gear: joinCommunityFund @@ -473,7 +482,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#L395) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L464) ##### :gear: autoStakeMaturity @@ -488,7 +497,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#L417) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L486) ##### :gear: leaveCommunityFund @@ -498,7 +507,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#L431) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L500) ##### :gear: setNodeProviderAccount @@ -509,7 +518,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#L448) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L517) ##### :gear: mergeNeurons @@ -519,7 +528,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#L468) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L537) ##### :gear: simulateMergeNeurons @@ -529,7 +538,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#L485) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L554) ##### :gear: splitNeuron @@ -539,7 +548,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#L530) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L599) ##### :gear: getProposal @@ -552,7 +561,7 @@ it is fetched using a query call. | ------------- | ----------------------------------------------------------------------------------------------------- | | `getProposal` | `({ proposalId, certified, }: { proposalId: bigint; certified?: boolean; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L570) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L639) ##### :gear: makeProposal @@ -562,7 +571,7 @@ Create new proposal | -------------- | ------------------------------------------------- | | `makeProposal` | `(request: MakeProposalRequest) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L587) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L656) ##### :gear: registerVote @@ -572,7 +581,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#L602) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L671) ##### :gear: setFollowees @@ -582,7 +591,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#L624) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L693) ##### :gear: disburse @@ -592,7 +601,7 @@ Disburse neuron on Account | ---------- | --------------------------------------------------------------------------------------------------------------------- | | `disburse` | `({ neuronId, toAccountId, amount, }: { neuronId: bigint; toAccountId?: string; amount?: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L639) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L708) ##### :gear: mergeMaturity @@ -602,7 +611,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#L678) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L747) ##### :gear: stakeMaturity @@ -617,7 +626,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#L711) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L780) ##### :gear: spawnNeuron @@ -627,7 +636,7 @@ Merge Maturity of a neuron | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `spawnNeuron` | `({ neuronId, percentageToSpawn, newController, nonce, }: { neuronId: bigint; percentageToSpawn?: number; newController?: Principal; nonce?: bigint; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L733) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L802) ##### :gear: addHotkey @@ -637,7 +646,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#L787) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L856) ##### :gear: removeHotkey @@ -647,7 +656,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#L811) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L880) ##### :gear: claimOrRefreshNeuronFromAccount @@ -657,7 +666,7 @@ Gets the NeuronID of a newly created neuron. | --------------------------------- | --------------------------------------------------------------------------------------- | | `claimOrRefreshNeuronFromAccount` | `({ memo, controller, }: { memo: bigint; controller?: Principal; }) => Promise` | -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L832) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L901) ##### :gear: claimOrRefreshNeuron @@ -668,7 +677,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#L863) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L932) ##### :gear: getNeuron @@ -678,7 +687,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#L905) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/nns/src/governance.canister.ts#L983) ### :factory: ICP diff --git a/packages/nns/src/governance.canister.spec.ts b/packages/nns/src/governance.canister.spec.ts index 00c0d309..1ed9df40 100644 --- a/packages/nns/src/governance.canister.spec.ts +++ b/packages/nns/src/governance.canister.spec.ts @@ -6,6 +6,7 @@ import { type Agent, type RequestId, } from "@dfinity/agent"; +import type { IcrcLedgerCanister } from "@dfinity/ledger"; import { ManageNeuronResponse as PbManageNeuronResponse, NeuronId as PbNeuronId, @@ -291,6 +292,133 @@ describe("GovernanceCanister", () => { }); }); + describe("GovernanceCanister.stakeNeuronIcrc1", () => { + it("creates new neuron successfully", async () => { + const neuronId = BigInt(10); + const serviceResponse: ManageNeuronResponse = { + command: [ + { ClaimOrRefresh: { refreshed_neuron_id: [{ id: neuronId }] } }, + ], + }; + const service = mock>(); + service.manage_neuron.mockResolvedValue(serviceResponse); + + const mockLedger = mock(); + mockLedger.transfer.mockImplementation( + jest.fn().mockResolvedValue(BigInt(1)), + ); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + }); + const response = await governance.stakeNeuronIcrc1({ + stake: BigInt(100_000_000), + principal: new AnonymousIdentity().getPrincipal(), + icrcLedgerCanister: mockLedger, + }); + + expect(mockLedger.transfer).toBeCalled(); + expect(service.manage_neuron).toBeCalled(); + expect(response).toEqual(neuronId); + }); + + it("stakeNeuron passes fee to the ledger transfer", async () => { + const neuronId = BigInt(10); + const serviceResponse: ManageNeuronResponse = { + command: [ + { ClaimOrRefresh: { refreshed_neuron_id: [{ id: neuronId }] } }, + ], + }; + const service = mock>(); + service.manage_neuron.mockResolvedValue(serviceResponse); + + const mockLedger = mock(); + mockLedger.transfer.mockImplementation( + jest.fn().mockResolvedValue(BigInt(1)), + ); + const fee = BigInt(10_000); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + }); + const response = await governance.stakeNeuronIcrc1({ + stake: BigInt(100_000_000), + principal: new AnonymousIdentity().getPrincipal(), + icrcLedgerCanister: mockLedger, + fee, + }); + + expect(mockLedger.transfer).toBeCalledWith( + expect.objectContaining({ fee }), + ); + }); + + it("creates new neuron from subaccount successfully", async () => { + const neuronId = BigInt(10); + const serviceResponse: ManageNeuronResponse = { + command: [ + { ClaimOrRefresh: { refreshed_neuron_id: [{ id: neuronId }] } }, + ], + }; + const service = mock>(); + service.manage_neuron.mockResolvedValue(serviceResponse); + + const mockLedger = mock(); + mockLedger.transfer.mockImplementation( + jest.fn().mockResolvedValue(BigInt(1)), + ); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + }); + const response = await governance.stakeNeuronIcrc1({ + stake: BigInt(100_000_000), + principal: new AnonymousIdentity().getPrincipal(), + icrcLedgerCanister: mockLedger, + fromSubAccount: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 1, + ], + }); + + expect(mockLedger.transfer).toBeCalled(); + expect(service.manage_neuron).toBeCalled(); + expect(response).toEqual(neuronId); + }); + + it("returns insufficient amount errors", async () => { + const neuronId = BigInt(1); + const clainNeuronResponse: ClaimOrRefreshNeuronFromAccountResponse = { + result: [{ NeuronId: { id: neuronId } }], + }; + const service = mock>(); + service.claim_or_refresh_neuron_from_account.mockResolvedValue( + clainNeuronResponse, + ); + + const mockLedger = mock(); + mockLedger.transfer.mockImplementation(jest.fn()); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + }); + + const call = async () => + await governance.stakeNeuronIcrc1({ + stake: BigInt(10_000_000), + principal: new AnonymousIdentity().getPrincipal(), + icrcLedgerCanister: mockLedger, + }); + + expect(mockLedger.transfer).not.toBeCalled(); + expect(service.claim_or_refresh_neuron_from_account).not.toBeCalled(); + + await expect(call).rejects.toThrow( + new InsufficientAmountError(BigInt(10_000_000)), + ); + }); + }); + describe("GovernanceCanister.listNeurons", () => { it("list user neurons", async () => { const service = mock>(); diff --git a/packages/nns/src/governance.canister.ts b/packages/nns/src/governance.canister.ts index 9b76d85b..9d4c8928 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 type { IcrcLedgerCanister } from "@dfinity/ledger"; import type { ManageNeuron as PbManageNeuron } from "@dfinity/nns-proto"; import type { Principal } from "@dfinity/principal"; import { @@ -298,6 +299,74 @@ export class GovernanceCanister { return neuronId; }; + // TODO: Rename to and replace `stakeNeuron` once `stakeNeuronIcrc1` is tested + // in NNS dapp. + /** + * @throws {@link InsufficientAmountError} + * @throws {@link StakeNeuronTransferError} + * @throws {@link CouldNotClaimNeuronError} + * @throws {@link TransferError} + */ + public stakeNeuronIcrc1 = async ({ + stake, + principal, + fromSubAccount, + icrcLedgerCanister, + createdAt, + fee, + }: { + stake: bigint; + principal: Principal; + fromSubAccount?: number[]; + icrcLedgerCanister: IcrcLedgerCanister; + // Used for the TransferRequest parameters. + // Check the TransferRequest type for more information. + createdAt?: bigint; + fee?: E8s; + }): Promise => { + if (stake < E8S_PER_TOKEN) { + throw new InsufficientAmountError(stake); + } + + const nonceBytes = new Uint8Array(randomBytes(8)); + const nonce = uint8ArrayToBigInt(nonceBytes); + const toSubAccount = this.getNeuronStakeSubAccountBytes( + nonceBytes, + principal, + ); + + const from_subaccount = fromSubAccount && Uint8Array.from(fromSubAccount); + + // Send amount to the ledger. + await icrcLedgerCanister.transfer({ + memo: nonceBytes, + amount: stake, + from_subaccount, + to: { + owner: this.canisterId, + subaccount: [toSubAccount], + }, + created_at_time: createdAt, + fee, + }); + + // Notify the governance of the transaction so that the neuron is created. + const neuronId: NeuronId | undefined = + await this.claimOrRefreshNeuronFromAccount({ + controller: principal, + memo: nonce, + }); + + // Typescript was complaining with `neuronId || new NeuronNotFound()`: + // "Type 'undefined' is not assignable to type 'bigint | StakeNeuronError | TransferError'" + // hence the explicit check. + if (isNullish(neuronId)) { + throw new CouldNotClaimNeuronError(); + } + + return neuronId; + }; + /** * Increases dissolve delay of a neuron * @@ -882,6 +951,15 @@ export class GovernanceCanister { nonce: Uint8Array, principal: Principal, ): SubAccount => { + return SubAccount.fromBytes( + this.getNeuronStakeSubAccountBytes(nonce, principal), + ) as SubAccount; + }; + + private getNeuronStakeSubAccountBytes = ( + nonce: Uint8Array, + principal: Principal, + ): Uint8Array => { const padding = asciiStringToByteArray("neuron-stake"); const shaObj = sha256.create(); shaObj.update( @@ -892,7 +970,7 @@ export class GovernanceCanister { ...nonce, ]), ); - return SubAccount.fromBytes(shaObj.digest()) as SubAccount; + return shaObj.digest(); }; private getGovernanceService(certified: boolean): GovernanceService {