diff --git a/ADMIN.md b/ADMIN.md index 125e85a5f..3a513745b 100644 --- a/ADMIN.md +++ b/ADMIN.md @@ -42,7 +42,7 @@ Add an empty test file `index.spec.ts` to make sure the command will not fail be ```javaScript import * as lib from "./index"; -describe("rosetta-client", () => { +describe("my-lib", () => { it("is implemented", () => { expect(lib).toEqual({}); }); diff --git a/package-lock.json b/package-lock.json index 5c0c5bf52..60f792d81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "packages/sns", "packages/cmc", "packages/ckbtc", - "packages/rosetta-client", "packages/ic-management" ], "devDependencies": { @@ -718,10 +717,6 @@ "@noble/hashes": "^1.3.1" } }, - "node_modules/@dfinity/rosetta-client": { - "resolved": "packages/rosetta-client", - "link": true - }, "node_modules/@dfinity/sns": { "resolved": "packages/sns", "link": true @@ -7225,11 +7220,6 @@ "@types/google-protobuf": "^3.15.6" } }, - "packages/rosetta-client": { - "name": "@dfinity/rosetta-client", - "version": "0.0.1", - "license": "Apache-2.0" - }, "packages/sns": { "name": "@dfinity/sns", "version": "0.0.23", @@ -7787,9 +7777,6 @@ "@noble/hashes": "^1.3.1" } }, - "@dfinity/rosetta-client": { - "version": "file:packages/rosetta-client" - }, "@dfinity/sns": { "version": "file:packages/sns", "requires": { diff --git a/package.json b/package.json index d7b33bc8d..93e293dfe 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "packages/sns", "packages/cmc", "packages/ckbtc", - "packages/rosetta-client", "packages/ic-management" ], "scripts": { @@ -21,7 +20,7 @@ "protoc": "bash ./scripts/update_proto.sh", "test": "jest", "test-all": "npm ci && npm run build --workspace=packages/utils && npm run build --workspace=packages/ledger && npm run test --workspaces", - "docs": "node scripts/docs.js && prettier --write packages/nns/README.md packages/sns/README.md packages/cmc/README.md packages/utils/README.md packages/ledger/README.md packages/ckbtc/README.md packages/rosetta-client/README.md packages/ic-management/README.md", + "docs": "node scripts/docs.js && prettier --write packages/nns/README.md packages/sns/README.md packages/cmc/README.md packages/utils/README.md packages/ledger/README.md packages/ckbtc/README.md packages/ic-management/README.md", "build": "npm run build --workspaces", "size": "size-limit --json", "update:agent": "./scripts/update-agent" @@ -133,12 +132,6 @@ "@dfinity/principal" ] }, - { - "name": "@dfinity/rosetta-client", - "path": "./packages/rosetta-client/dist/index.js", - "limit": "1 kB", - "ignore": [] - }, { "name": "@dfinity/ic-management", "path": "./packages/ic-management/dist/index.js", diff --git a/packages/nns/README.md b/packages/nns/README.md index f51e346f0..3d576c249 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 7b264a67c..1ed9df40b 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, @@ -82,7 +83,6 @@ describe("GovernanceCanister", () => { jest.clearAllMocks(); }); - // TODO: Take out tests from "listKnownNeurons" describe describe("GovernanceCanister.listKnownNeurons", () => { it("populates all KnownNeuron fields correctly", async () => { const response: ListKnownNeuronsResponse = { @@ -163,7 +163,9 @@ describe("GovernanceCanister", () => { expect(res.map((n) => Number(n.id))).toEqual([100, 200, 300, 400]); }); + }); + describe("GovernanceCanister.stakeNeuron", () => { it("creates new neuron successfully", async () => { const neuronId = BigInt(10); const serviceResponse: ManageNeuronResponse = { @@ -288,52 +290,181 @@ describe("GovernanceCanister", () => { new InsufficientAmountError(BigInt(10_000_000)), ); }); + }); + + 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); - describe("listNeurons", () => { - it("list user neurons", async () => { - const service = mock>(); - service.list_neurons.mockResolvedValue(mockListNeuronsResponse); + const mockLedger = mock(); + mockLedger.transfer.mockImplementation( + jest.fn().mockResolvedValue(BigInt(1)), + ); - const governance = GovernanceCanister.create({ - certifiedServiceOverride: service, - serviceOverride: service, - }); - const neurons = await governance.listNeurons({ - certified: true, - }); - expect(service.list_neurons).toBeCalled(); - expect(neurons.length).toBe(1); + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + }); + const response = await governance.stakeNeuronIcrc1({ + stake: BigInt(100_000_000), + principal: new AnonymousIdentity().getPrincipal(), + icrcLedgerCanister: mockLedger, }); - it("list Hardware Wallet neurons", async () => { - const agent = mock(); - agent.call.mockResolvedValue(agentCallSuccessfulResponse); + expect(mockLedger.transfer).toBeCalled(); + expect(service.manage_neuron).toBeCalled(); + expect(response).toEqual(neuronId); + }); - const governance = GovernanceCanister.create({ - agent, - hardwareWallet: true, - }); - await governance.listNeurons({ certified: true }); - expect(agent.call).toBeCalled(); - expect(spyPollForResponse).toBeCalled(); + 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, + ); - it("should not support query neurons with hardware wallet (only update calls)", async () => { - const agent = mock(); - agent.call.mockResolvedValue(agentCallSuccessfulResponse); + const mockLedger = mock(); + mockLedger.transfer.mockImplementation(jest.fn()); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + }); - const governance = GovernanceCanister.create({ - agent, - hardwareWallet: true, + const call = async () => + await governance.stakeNeuronIcrc1({ + stake: BigInt(10_000_000), + principal: new AnonymousIdentity().getPrincipal(), + icrcLedgerCanister: mockLedger, }); - const call = async () => - await governance.listNeurons({ certified: false }); + expect(mockLedger.transfer).not.toBeCalled(); + expect(service.claim_or_refresh_neuron_from_account).not.toBeCalled(); - await expect(call).rejects.toThrow(new FeatureNotSupportedError()); + await expect(call).rejects.toThrow( + new InsufficientAmountError(BigInt(10_000_000)), + ); + }); + }); + + describe("GovernanceCanister.listNeurons", () => { + it("list user neurons", async () => { + const service = mock>(); + service.list_neurons.mockResolvedValue(mockListNeuronsResponse); + + const governance = GovernanceCanister.create({ + certifiedServiceOverride: service, + serviceOverride: service, + }); + const neurons = await governance.listNeurons({ + certified: true, }); + expect(service.list_neurons).toBeCalled(); + expect(neurons.length).toBe(1); }); + it("list Hardware Wallet neurons", async () => { + const agent = mock(); + agent.call.mockResolvedValue(agentCallSuccessfulResponse); + + const governance = GovernanceCanister.create({ + agent, + hardwareWallet: true, + }); + await governance.listNeurons({ certified: true }); + expect(agent.call).toBeCalled(); + expect(spyPollForResponse).toBeCalled(); + }); + + it("should not support query neurons with hardware wallet (only update calls)", async () => { + const agent = mock(); + agent.call.mockResolvedValue(agentCallSuccessfulResponse); + + const governance = GovernanceCanister.create({ + agent, + hardwareWallet: true, + }); + + const call = async () => + await governance.listNeurons({ certified: false }); + + await expect(call).rejects.toThrow(new FeatureNotSupportedError()); + }); + }); + + describe("GovernanceCanister.registerVote", () => { it("registers vote successfully", async () => { const serviceResponse: ManageNeuronResponse = { command: [{ RegisterVote: {} }], @@ -392,7 +523,9 @@ describe("GovernanceCanister", () => { new GovernanceError(unexpectedGovernanceError), ); }); + }); + describe("GovernanceCanister.getNeuron", () => { it("should fetch and convert a neuron", async () => { const service = mock>(); const governance = GovernanceCanister.create({ diff --git a/packages/nns/src/governance.canister.ts b/packages/nns/src/governance.canister.ts index 9b76d85bf..9d4c89289 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 { diff --git a/packages/rosetta-client/LICENSE b/packages/rosetta-client/LICENSE deleted file mode 100644 index 4874b73b8..000000000 --- a/packages/rosetta-client/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 DFINITY LLC. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/rosetta-client/README.md b/packages/rosetta-client/README.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/rosetta-client/esbuild.mjs b/packages/rosetta-client/esbuild.mjs deleted file mode 100644 index f3cda319f..000000000 --- a/packages/rosetta-client/esbuild.mjs +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -import { build } from "../../scripts/esbuild.mjs"; - -build(); diff --git a/packages/rosetta-client/jest.config.js b/packages/rosetta-client/jest.config.js deleted file mode 100644 index e75ede62b..000000000 --- a/packages/rosetta-client/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const rootJestConfig = require("../../jest.config"); - -module.exports = { - ...rootJestConfig, - setupFiles: [`../../test-setup.ts`], - modulePathIgnorePatterns: ["./dist"], -}; diff --git a/packages/rosetta-client/package.json b/packages/rosetta-client/package.json deleted file mode 100644 index 6590c25cd..000000000 --- a/packages/rosetta-client/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@dfinity/rosetta-client", - "version": "0.0.1", - "description": "JavaScript library to interact with the Rosetta client of the IC", - "license": "Apache-2.0", - "main": "dist/cjs/index.cjs.js", - "module": "dist/esm/index.js", - "types": "dist/types/index.d.ts", - "files": [ - "dist", - "README.md", - "LICENSE" - ], - "scripts": { - "rmdir": "node ../../scripts/rmdir.mjs", - "ts-declaration": "tsc --emitDeclarationOnly --outDir dist/types", - "build": "npm run rmdir && mkdir -p dist && node esbuild.mjs && npm run ts-declaration", - "prepack": "npm run build", - "test": "jest" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/dfinity/ic-js.git", - "directory": "packages/rosetta-client" - }, - "bugs": { - "url": "https://github.com/dfinity/ic-js/issues" - }, - "keywords": [ - "internet computer", - "internet-computer", - "rosetta", - "icp" - ], - "homepage": "https://github.com/dfinity/ic-js#readme" -} diff --git a/packages/rosetta-client/src/index.spec.ts b/packages/rosetta-client/src/index.spec.ts deleted file mode 100644 index 3b73e418f..000000000 --- a/packages/rosetta-client/src/index.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as lib from "./index"; - -describe("rosetta-client", () => { - it("is implemented", () => { - expect(lib).toEqual({}); - }); -}); diff --git a/packages/rosetta-client/src/index.ts b/packages/rosetta-client/src/index.ts deleted file mode 100644 index cb0ff5c3b..000000000 --- a/packages/rosetta-client/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/packages/rosetta-client/tsconfig.json b/packages/rosetta-client/tsconfig.json deleted file mode 100644 index 52d43eaaa..000000000 --- a/packages/rosetta-client/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src/**/*"] -} diff --git a/scripts/docs.js b/scripts/docs.js index af681bdb7..400cdbcae 100644 --- a/scripts/docs.js +++ b/scripts/docs.js @@ -38,8 +38,6 @@ const ckBTCInputFiles = [ "./packages/ckbtc/src/utils/btc.utils.ts", ]; -const rosettaInputFiles = ["./packages/rosetta-client/src/index.ts"]; - const icMgmtInputFiles = [ "./packages/ic-management/src/ic-management.canister.ts", ]; @@ -94,13 +92,6 @@ generateDocumentation({ buildOptions: { ...buildOptions, explore: true }, }); -generateDocumentation({ - inputFiles: rosettaInputFiles, - outputFile: "./packages/rosetta-client/README.md", - markdownOptions, - buildOptions, -}); - generateDocumentation({ inputFiles: icMgmtInputFiles, outputFile: "./packages/ic-management/README.md",