diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fa9dfa..f18bb401 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,13 @@ - Rename values of enum Topic and NnsFunction to match the backend values. - Use different request/response types for NNS Governance proposals, and different fields for `InstallCode` proposals. - The `getUtxos` parameter `filter.min_confirmations` has been renamed to `filter.minConfirmations` for consistency with the general naming conventions used in `@dfinity/ckbtc`. +- Only queries to `getUtxos` of the Bitcoin canister can be executed by external users — i.e., update calls can only be performed by the canister. This is why `getUtxos` now only supports non-certified calls and has been renamed to `getUtxosQuery`. ## Features - Provide a new utility to convert Candid `Nat` to `BigInt`. This utility is useful for interpreting the fees provided by the SNS Aggregator. - Support conversion of `InstallCode`, `StopOrStartCanister` and `UpdateCanisterSettings` actions, `SetVisibility` neuron operation, and `Neuron::visibility` attribute. -- Add function `getBalance` to `BitcoinCanister` object of package `@dfinity/ckbtc`, that implements the `bitcoin_get_balance` method of the IC Bitcoin API. +- Add function `getBalanceQuery` to `BitcoinCanister` object of package `@dfinity/ckbtc`, that implements the `bitcoin_get_balance_query` method of the IC Bitcoin API. ## Build diff --git a/packages/ckbtc/README.md b/packages/ckbtc/README.md index d6ed6c39..8f66cb77 100644 --- a/packages/ckbtc/README.md +++ b/packages/ckbtc/README.md @@ -281,8 +281,8 @@ Parameters: #### Methods - [create](#gear-create) -- [getUtxos](#gear-getutxos) -- [getBalance](#gear-getbalance) +- [getUtxosQuery](#gear-getutxosquery) +- [getBalanceQuery](#gear-getbalancequery) ##### :gear: create @@ -292,37 +292,39 @@ Parameters: [:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ckbtc/src/bitcoin.canister.ts#L18) -##### :gear: getUtxos +##### :gear: getUtxosQuery Given a `get_utxos_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns all unspent transaction outputs (UTXOs) associated with the provided address in the specified Bitcoin network based on the current view of the Bitcoin blockchain available to the Bitcoin component. -| Method | Type | -| ---------- | --------------------------------------------------------------------------- | -| `getUtxos` | `({ certified, ...params }: GetUtxosParams) => Promise` | +⚠️ Note that this method does not support certified calls because only canisters are allowed to get UTXOs via update calls. + +| Method | Type | +| --------------- | ---------------------------------------------------------------- | +| `getUtxosQuery` | `({ ...params }: GetUtxosParams) => Promise` | Parameters: - `params.network`: Tesnet or mainnet. - `params.filter`: The optional filter parameter can be used to restrict the set of returned UTXOs, either providing a minimum number of confirmations or a page reference when pagination is used for addresses with many UTXOs. - `params.address`: A Bitcoin address. -- `params.certified`: query or update call -[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ckbtc/src/bitcoin.canister.ts#L41) +[:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ckbtc/src/bitcoin.canister.ts#L42) -##### :gear: getBalance +##### :gear: getBalanceQuery Given a `get_balance_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns the current balance of this address in `Satoshi` (10^8 Satoshi = 1 Bitcoin) in the specified Bitcoin network. -| Method | Type | -| ------------ | ----------------------------------------------------------------- | -| `getBalance` | `({ certified, ...params }: GetBalanceParams) => Promise` | +⚠️ Note that this method does not support certified calls because only canisters are allowed to get Bitcoin balance via update calls. + +| Method | Type | +| ----------------- | ------------------------------------------------------ | +| `getBalanceQuery` | `({ ...params }: GetBalanceParams) => Promise` | Parameters: - `params.network`: Tesnet or mainnet. - `params.min_confirmations`: The optional filter parameter can be used to limit the set of considered UTXOs for the calculation of the balance to those with at least the provided number of confirmations in the same manner as for the `bitcoin_get_utxos` call. - `params.address`: A Bitcoin address. -- `params.certified`: query or update call [:link: Source](https://github.com/dfinity/ic-js/tree/main/packages/ckbtc/src/bitcoin.canister.ts#L64) diff --git a/packages/ckbtc/src/bitcoin.canister.spec.ts b/packages/ckbtc/src/bitcoin.canister.spec.ts index 70e380fa..1914dff0 100644 --- a/packages/ckbtc/src/bitcoin.canister.spec.ts +++ b/packages/ckbtc/src/bitcoin.canister.spec.ts @@ -13,17 +13,14 @@ import { GetBalanceParams, GetUtxosParams } from "./types/bitcoin.params"; describe("BitcoinCanister", () => { const createBitcoinCanister = ( - services: Pick< - CanisterOptions, - "serviceOverride" | "certifiedServiceOverride" - >, + services: Pick, "serviceOverride">, ): BitcoinCanister => BitcoinCanister.create({ canisterId: bitcoinCanisterIdMock, ...services, }); - describe("bitcoinGetUtxos", () => { + describe("bitcoinGetUtxosQuery", () => { const params: Omit = { network: "testnet", filter: { minConfirmations: 2 }, @@ -54,146 +51,107 @@ describe("BitcoinCanister", () => { ], }; - describe("certified", () => { - it("returns get utxos result when success", async () => { - const certifiedService = mock>(); - certifiedService.bitcoin_get_utxos.mockResolvedValue(response); + it("returns get utxos result when success", async () => { + const service = mock>(); + service.bitcoin_get_utxos_query.mockResolvedValue(response); - const service = mock>(); - - const { getUtxos } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - serviceOverride: service, - }); - - const res = await getUtxos({ - ...params, - certified: true, - }); - - expect(res).toEqual(response); - expect(certifiedService.bitcoin_get_utxos).toHaveBeenCalledWith({ - network: { testnet: null }, - filter: [{ min_confirmations: 2 }], - address: bitcoinAddressMock, - }); - expect(service.bitcoin_get_utxos_query).not.toHaveBeenCalled(); + const { getUtxosQuery } = createBitcoinCanister({ + serviceOverride: service, }); - it("call get utxos with min_confirmations", async () => { - const certifiedService = mock>(); - certifiedService.bitcoin_get_utxos.mockResolvedValue(response); + const res = await getUtxosQuery({ + ...params, + }); - const { getUtxos } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - }); + expect(res).toEqual(response); + expect(service.bitcoin_get_utxos_query).toHaveBeenCalledWith({ + network: { testnet: null }, + filter: [{ min_confirmations: 2 }], + address: bitcoinAddressMock, + }); + expect(service.bitcoin_get_utxos).not.toHaveBeenCalled(); + }); - await getUtxos({ - ...params, - certified: true, - }); + it("call get utxos with min_confirmations", async () => { + const service = mock>(); + service.bitcoin_get_utxos_query.mockResolvedValue(response); - expect(certifiedService.bitcoin_get_utxos).toHaveBeenCalledWith({ - network: { testnet: null }, - filter: [{ min_confirmations: 2 }], - address: bitcoinAddressMock, - }); + const { getUtxosQuery } = createBitcoinCanister({ + serviceOverride: service, }); - it("call get utxos with page", async () => { - const certifiedService = mock>(); - certifiedService.bitcoin_get_utxos.mockResolvedValue(response); - - const { getUtxos } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - }); + await getUtxosQuery({ + ...params, + }); - const page = [1, 2, 3]; - const pageParams: Omit = { - ...params, - filter: { - page, - }, - }; + expect(service.bitcoin_get_utxos_query).toHaveBeenCalledWith({ + network: { testnet: null }, + filter: [{ min_confirmations: 2 }], + address: bitcoinAddressMock, + }); + }); - await getUtxos({ - ...pageParams, - certified: true, - }); + it("call get utxos with page", async () => { + const service = mock>(); + service.bitcoin_get_utxos_query.mockResolvedValue(response); - expect(certifiedService.bitcoin_get_utxos).toHaveBeenCalledWith({ - network: { testnet: null }, - filter: [{ page }], - address: bitcoinAddressMock, - }); + const { getUtxosQuery } = createBitcoinCanister({ + serviceOverride: service, }); - it("throws Error", async () => { - const error = new Error("Test"); - const certifiedService = mock>(); - certifiedService.bitcoin_get_utxos.mockRejectedValue(error); - - const { getUtxos } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - }); + const page = [1, 2, 3]; + const pageParams: Omit = { + ...params, + filter: { + page, + }, + }; - const call = () => - getUtxos({ - ...params, - certified: true, - }); + await getUtxosQuery({ + ...pageParams, + }); - expect(call).rejects.toThrowError(Error); + expect(service.bitcoin_get_utxos_query).toHaveBeenCalledWith({ + network: { testnet: null }, + filter: [{ page }], + address: bitcoinAddressMock, }); }); - describe("Not certified", () => { - it("returns get utxos query result when success", async () => { - const service = mock>(); - service.bitcoin_get_utxos_query.mockResolvedValue(response); - - const certifiedService = mock>(); + it("throws Error", async () => { + const error = new Error("Test"); + const service = mock>(); + service.bitcoin_get_utxos_query.mockRejectedValue(error); - const { getUtxos } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - serviceOverride: service, - }); + const { getUtxosQuery } = createBitcoinCanister({ + serviceOverride: service, + }); - const res = await getUtxos({ + const call = () => + getUtxosQuery({ ...params, - certified: false, - }); - - expect(res).toEqual(response); - expect(service.bitcoin_get_utxos_query).toHaveBeenCalledWith({ - network: { testnet: null }, - filter: [{ min_confirmations: 2 }], - address: bitcoinAddressMock, }); - expect(certifiedService.bitcoin_get_utxos).not.toHaveBeenCalled(); - }); - it("throws Error", async () => { - const error = new Error("Test"); - const service = mock>(); - service.bitcoin_get_utxos_query.mockRejectedValue(error); + expect(call).rejects.toThrowError(Error); + }); - const { getUtxos } = await createBitcoinCanister({ - serviceOverride: service, - }); + it("should not call certified end point", async () => { + const service = mock>(); + service.bitcoin_get_utxos_query.mockResolvedValue(response); - const call = () => - getUtxos({ - ...params, - certified: false, - }); + const { getUtxosQuery } = createBitcoinCanister({ + serviceOverride: service, + }); - expect(call).rejects.toThrowError(Error); + await getUtxosQuery({ + ...params, }); + + expect(service.bitcoin_get_utxos).not.toHaveBeenCalled(); }); }); - describe("bitcoinGetBalance", () => { + describe("bitcoinGetBalanceQuery", () => { const params: Omit = { network: "testnet", minConfirmations: 2, @@ -202,94 +160,56 @@ describe("BitcoinCanister", () => { const response: satoshi = 1000n; - describe("certified", () => { - it("returns balance result when success", async () => { - const certifiedService = mock>(); - certifiedService.bitcoin_get_balance.mockResolvedValue(response); + it("returns balance result when success", async () => { + const service = mock>(); + service.bitcoin_get_balance_query.mockResolvedValue(response); - const service = mock>(); - - const { getBalance } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - serviceOverride: service, - }); - - const res = await getBalance({ - ...params, - certified: true, - }); - - expect(res).toEqual(response); - expect(certifiedService.bitcoin_get_balance).toHaveBeenCalledWith({ - network: { testnet: null }, - min_confirmations: [2], - address: bitcoinAddressMock, - }); - expect(service.bitcoin_get_balance_query).not.toHaveBeenCalled(); + const { getBalanceQuery } = createBitcoinCanister({ + serviceOverride: service, }); - it("throws Error", async () => { - const error = new Error("Test"); - const certifiedService = mock>(); - certifiedService.bitcoin_get_balance.mockRejectedValue(error); - - const { getBalance } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - }); - - const call = () => - getBalance({ - ...params, - certified: true, - }); + const res = await getBalanceQuery({ + ...params, + }); - expect(call).rejects.toThrowError(Error); + expect(res).toEqual(response); + expect(service.bitcoin_get_balance_query).toHaveBeenCalledWith({ + network: { testnet: null }, + min_confirmations: [2], + address: bitcoinAddressMock, }); }); - describe("Not certified", () => { - it("returns balance query result when success", async () => { - const service = mock>(); - service.bitcoin_get_balance_query.mockResolvedValue(response); + it("throws Error", async () => { + const error = new Error("Test"); + const service = mock>(); + service.bitcoin_get_balance_query.mockRejectedValue(error); - const certifiedService = mock>(); - - const { getBalance } = await createBitcoinCanister({ - certifiedServiceOverride: certifiedService, - serviceOverride: service, - }); + const { getBalanceQuery } = createBitcoinCanister({ + serviceOverride: service, + }); - const res = await getBalance({ + const call = () => + getBalanceQuery({ ...params, - certified: false, }); - expect(res).toEqual(response); - expect(service.bitcoin_get_balance_query).toHaveBeenCalledWith({ - network: { testnet: null }, - min_confirmations: [2], - address: bitcoinAddressMock, - }); - expect(certifiedService.bitcoin_get_balance).not.toHaveBeenCalled(); - }); + expect(call).rejects.toThrowError(Error); + }); - it("throws Error", async () => { - const error = new Error("Test"); - const service = mock>(); - service.bitcoin_get_balance_query.mockRejectedValue(error); + it("should not call certified end point", async () => { + const service = mock>(); + service.bitcoin_get_balance_query.mockResolvedValue(response); - const { getBalance } = await createBitcoinCanister({ - serviceOverride: service, - }); - - const call = () => - getBalance({ - ...params, - certified: false, - }); + const { getBalanceQuery } = createBitcoinCanister({ + serviceOverride: service, + }); - expect(call).rejects.toThrowError(Error); + await getBalanceQuery({ + ...params, }); + + expect(service.bitcoin_get_balance).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/ckbtc/src/bitcoin.canister.ts b/packages/ckbtc/src/bitcoin.canister.ts index 600e90c6..d39fa520 100644 --- a/packages/ckbtc/src/bitcoin.canister.ts +++ b/packages/ckbtc/src/bitcoin.canister.ts @@ -29,46 +29,42 @@ export class BitcoinCanister extends Canister { /** * Given a `get_utxos_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns all unspent transaction outputs (UTXOs) associated with the provided address in the specified Bitcoin network based on the current view of the Bitcoin blockchain available to the Bitcoin component. * + * ⚠️ Note that this method does not support certified calls because only canisters are allowed to get UTXOs via update calls. + * * @link https://internetcomputer.org/docs/current/references/ic-interface-spec#ic-bitcoin_get_utxos * * @param {Object} params * @param {BitcoinNetwork} params.network Tesnet or mainnet. * @param {Object} params.filter The optional filter parameter can be used to restrict the set of returned UTXOs, either providing a minimum number of confirmations or a page reference when pagination is used for addresses with many UTXOs. * @param {string} params.address A Bitcoin address. - * @param {boolean} params.certified query or update call * @returns {Promise} The UTXOs are returned sorted by block height in descending order. */ - getUtxos = ({ - certified = true, + getUtxosQuery = ({ ...params }: GetUtxosParams): Promise => { - const { bitcoin_get_utxos, bitcoin_get_utxos_query } = this.caller({ - certified, + const { bitcoin_get_utxos_query } = this.caller({ + certified: false, }); - const fn = certified ? bitcoin_get_utxos : bitcoin_get_utxos_query; - return fn(toGetUtxosParams(params)); + return bitcoin_get_utxos_query(toGetUtxosParams(params)); }; /** * Given a `get_balance_request`, which must specify a Bitcoin address and a Bitcoin network (`mainnet` or `testnet`), the function returns the current balance of this address in `Satoshi` (10^8 Satoshi = 1 Bitcoin) in the specified Bitcoin network. * + * ⚠️ Note that this method does not support certified calls because only canisters are allowed to get Bitcoin balance via update calls. + * * @link https://internetcomputer.org/docs/current/references/ic-interface-spec#ic-bitcoin_get_balance * * @param {Object} params * @param {BitcoinNetwork} params.network Tesnet or mainnet. * @param {Object} params.min_confirmations The optional filter parameter can be used to limit the set of considered UTXOs for the calculation of the balance to those with at least the provided number of confirmations in the same manner as for the `bitcoin_get_utxos` call. * @param {string} params.address A Bitcoin address. - * @param {boolean} params.certified query or update call * @returns {Promise} The balance is returned in `Satoshi` (10^8 Satoshi = 1 Bitcoin). */ - getBalance = ({ - certified = true, - ...params - }: GetBalanceParams): Promise => { - const { bitcoin_get_balance, bitcoin_get_balance_query } = this.caller({ - certified, + getBalanceQuery = ({ ...params }: GetBalanceParams): Promise => { + const { bitcoin_get_balance_query } = this.caller({ + certified: false, }); - const fn = certified ? bitcoin_get_balance : bitcoin_get_balance_query; - return fn(toGetBalanceParams(params)); + return bitcoin_get_balance_query(toGetBalanceParams(params)); }; } diff --git a/packages/ckbtc/src/types/bitcoin.params.ts b/packages/ckbtc/src/types/bitcoin.params.ts index 4e007017..0754f53f 100644 --- a/packages/ckbtc/src/types/bitcoin.params.ts +++ b/packages/ckbtc/src/types/bitcoin.params.ts @@ -13,7 +13,7 @@ const mapBitcoinNetwork = (network: BitcoinNetwork): network => export type GetUtxosParams = Omit & { network: BitcoinNetwork; filter?: { page: Uint8Array | number[] } | { minConfirmations: number }; -} & QueryParams; +} & Omit; export const toGetUtxosParams = ({ network, @@ -37,7 +37,7 @@ export type GetBalanceParams = Omit< > & { network: BitcoinNetwork; minConfirmations?: number; -} & QueryParams; +} & Omit; export const toGetBalanceParams = ({ network,