From 970153fbaa6278767a3f07f8239864013315d28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20W=C5=82odek?= Date: Thu, 13 Feb 2025 09:56:03 +0100 Subject: [PATCH] feat(wallet): remove auto-collateral management logic --- packages/wallet/src/cip30.ts | 87 +--------- .../test/integration/cip30mapping.test.ts | 154 +----------------- 2 files changed, 7 insertions(+), 234 deletions(-) diff --git a/packages/wallet/src/cip30.ts b/packages/wallet/src/cip30.ts index 6cb1a66329f..dd7e6fcde09 100644 --- a/packages/wallet/src/cip30.ts +++ b/packages/wallet/src/cip30.ts @@ -35,8 +35,7 @@ export type Cip30WalletDependencies = { export enum Cip30ConfirmationCallbackType { SignData = 'sign_data', SignTx = 'sign_tx', - SubmitTx = 'submit_tx', - GetCollateral = 'get_collateral' + SubmitTx = 'submit_tx' } export type SignDataCallbackParams = { @@ -59,18 +58,6 @@ export type SubmitTxCallbackParams = { data: Cardano.Tx; }; -// Optional callback -export type GetCollateralCallbackParams = { - sender: MessageSender; - type: Cip30ConfirmationCallbackType.GetCollateral; - data: { - amount: Cardano.Lovelace; - utxos: Cardano.Utxo[]; - }; -}; - -type GetCollateralCallback = (args: GetCollateralCallbackParams) => Promise; - export type SignConfirmationOk = { cancel$: Observable }; export type SignConfirmationResult = SignConfirmationOk | false; @@ -81,7 +68,6 @@ export type CallbackConfirmation = { signData: (args: SignDataCallbackParams) => Promise; signTx: (args: SignTxCallbackParams) => Promise; submitTx: (args: SubmitTxCallbackParams) => Promise; - getCollateral?: GetCollateralCallback; }; const firstValueFromTimed = (observable$: Observable, timeoutAfter: Milliseconds) => @@ -194,9 +180,6 @@ const selectUtxo = async (wallet: ObservableWallet, filterAmount: Cardano.Value, ? dumbSelection(await firstValueFrom(wallet.utxo.available$), filterAmount) : await walletSelection(filterAmount, wallet); -/** Returns an array of UTxOs that do not contain assets */ -const getUtxosWithoutAssets = (utxos: Cardano.Utxo[]): Cardano.Utxo[] => utxos.filter((utxo) => !utxo[1].value.assets); - const getFilterAsBigNum = (amount: Cbor): bigint => { const reader = new Serialization.CborReader(HexBlob(amount)); @@ -224,46 +207,6 @@ const getFilterAmount = (amount: Cbor): bigint => { } }; -/** - * getCollateralCallback - * - * @param sender The sender of the request - * @param amount ADA collateral required in lovelaces - * @param availableUtxos available UTxOs - * @param callback Callback to execute to attempt setting new collateral - * @param logger The logger instance - * @returns Promise or null - */ -const getCollateralCallback = async ( - sender: MessageSender, - amount: Cardano.Lovelace, - availableUtxos: Cardano.Utxo[], - callback: GetCollateralCallback, - logger: Logger -) => { - if (availableUtxos.length === 0) return null; - const availableUtxosWithoutAssets = getUtxosWithoutAssets(availableUtxos); - try { - // Send the amount and filtered available UTxOs to the callback - // Client can then choose to mark a UTxO set as unspendable - const newCollateral = await callback({ - data: { - amount, - utxos: availableUtxosWithoutAssets - }, - sender, - type: Cip30ConfirmationCallbackType.GetCollateral - }); - return newCollateral.map((core) => Serialization.TransactionUnspentOutput.fromCore(core).toCbor()); - } catch (error) { - logger.error(error); - if (error instanceof ApiError) { - throw error; - } - throw new ApiError(APIErrorCode.InternalError, formatUnknownError(error)); - } -}; - const getSortedUtxos = async (observableUtxos: Observable): Promise => { const utxos = await firstValueFrom(observableUtxos); return utxos.sort(compareUtxos); @@ -333,31 +276,19 @@ const baseCip30WalletApi = ( throw new ApiError(APIErrorCode.InternalError, formatUnknownError(error)); } }, - // eslint-disable-next-line max-statements, sonarjs/cognitive-complexity,complexity getCollateral: async ( - { sender }: SenderContext, + _: SenderContext, { amount = new Serialization.Value(MAX_COLLATERAL_AMOUNT).toCbor() }: { amount?: Cbor } = {} ): Promise< Cbor[] | null - // eslint-disable-next-line sonarjs/cognitive-complexity, max-statements + // eslint-disable-next-line sonarjs/cognitive-complexity > => { logger.debug('getting collateral'); const wallet = await firstValueFrom(wallet$); await waitForWalletStateSettle(wallet); let unspendables = await getSortedUtxos(wallet.utxo.unspendable$); - const available = await getSortedUtxos(wallet.utxo.available$); // No available unspendable UTxO if (unspendables.length === 0) { - if (available.length > 0 && !!confirmationCallback.getCollateral) { - // available UTxOs could be set as collateral based on user preference - return await getCollateralCallback( - sender, - getFilterAmount(amount), - available, - confirmationCallback.getCollateral, - logger - ); - } return null; } @@ -377,18 +308,6 @@ const baseCip30WalletApi = ( if (totalCoins >= filterAmount) break; } if (totalCoins < filterAmount) { - // if no collateral available by amount in unspendables, return callback if provided to set unspendables and return in the callback - - if (available.length > 0 && !!confirmationCallback.getCollateral) { - return await getCollateralCallback( - sender, - filterAmount, - available, - confirmationCallback.getCollateral, - logger - ); - } - throw new ApiError(APIErrorCode.Refused, 'not enough coins in configured collateral UTxOs'); } unspendables = utxos; diff --git a/packages/wallet/test/integration/cip30mapping.test.ts b/packages/wallet/test/integration/cip30mapping.test.ts index a70b88bde9c..370f4cf913a 100644 --- a/packages/wallet/test/integration/cip30mapping.test.ts +++ b/packages/wallet/test/integration/cip30mapping.test.ts @@ -18,7 +18,7 @@ import { import { AddressType, Bip32Account, GroupedAddress, KeyRole, util } from '@cardano-sdk/key-management'; import { AssetId, mockProviders as mocks } from '@cardano-sdk/util-dev'; import { BaseWallet, ObservableWallet, cip30, createPersonalWallet } from '../../src'; -import { CallbackConfirmation, GetCollateralCallbackParams } from '../../src/cip30'; +import { CallbackConfirmation } from '../../src/cip30'; import { Cardano, OutsideOfValidityIntervalData, @@ -45,14 +45,10 @@ const { mockRewardsProvider, mockTxSubmitProvider, utxo: mockUtxo, - utxosWithLowCoins, - utxosWithLowCoinsAndMixedAssets, - sortedUtxosWithLowCoins, - impureUtxos + utxosWithLowCoins } = mocks; type TestProviders = Required>; -const mockCollateralCallback = jest.fn().mockResolvedValue([mockUtxo[3]]); const createMockGenericCallback = (result: T) => jest.fn().mockResolvedValue(result); const foreignTx = Serialization.TxCBOR( '84a70081825820dce442e983f3f5cd5b2644bc57f749075390f1fbae9ab55bf454342959c885db00018182583900d161d64eef0eeb59f9124f520f8c8f3b717ed04198d54c8b17e604aea63c153fb3ea8a4ea4f165574ea91173756de0bf30222ca0e95a649a1a0082607b021a0016360509a1581cb77934706fa311b6568d1070c2d23f092324b35ad623aa571a0e3726a14e4d6573685f476966745f43617264200b5820d8175f3b1276a48939a6ccee220a7f81b6422167317ba3ff6325cba1fb6ccbe70d818258208d68748457cd0f1a8596f41fd2125a415315897d2da4a4b94335829cee7198ae001281825820dce442e983f3f5cd5b2644bc57f749075390f1fbae9ab55bf454342959c885db00a2068259016b590168010000333232323232323223223222253330083232533300d3010002132533300b3370e6eb4c034009200113371e0020122940dd718058008b180700099299980499b8748008c028dd50008a5eb7bdb1804dd5980718059baa001323300100132330010013756601e602060206020602060186ea8c03cc030dd50019129998070008a5eb7bdb1804c8c8c8c94ccc03ccdc8a45000021533300f3371e91010000210031005133013337606ea4008dd3000998030030019bab3010003375c601c0046024004602000244a66601a002298103d87a8000132323232533300e337220140042a66601c66e3c0280084cdd2a4000660246e980052f5c02980103d87a80001330060060033756601e0066eb8c034008c044008c03c00452613656375c0026eb80055cd2ab9d5573caae7d5d02ba157449810f4e4d6573685f476966745f43617264004c011e581cb77934706fa311b6568d1070c2d23f092324b35ad623aa571a0e3726000159023c59023901000033323232323232322322232323225333009323232533300c3007300d3754002264646464a666026602c00426464a666024601a60266ea803854ccc048c034c04cdd5191980080080311299980b8008a60103d87a80001323253330163375e603660306ea800804c4cdd2a40006603400497ae0133004004001301b002301900115333012300c00113371e00402029405854ccc048cdc3800a4002266e3c0080405281bad3013002375c60220022c602800264a66601e601260206ea800452f5bded8c026eacc050c044dd500099191980080099198008009bab3016301730173017301700522533301500114bd6f7b630099191919299980b19b91488100002153330163371e9101000021003100513301a337606ea4008dd3000998030030019bab3017003375c602a0046032004602e00244a666028002298103d87a800013232323253330153372200e0042a66602a66e3c01c0084cdd2a4000660326e980052f5c02980103d87a80001330060060033756602c0066eb8c050008c060008c058004dd7180998081baa00337586024002601c6ea800858c040c044008c03c004c02cdd50008a4c26cac64a66601060060022a66601660146ea8010526161533300830020011533300b300a37540082930b0b18041baa003370e90011b8748000dd7000ab9a5573aaae7955cfaba05742ae8930010f4e4d6573685f476966745f43617264004c012bd8799fd8799f58203159a6f2ae24c5bfbed947fe0ecfe936f088c8d265484e6979cacb607d33c811ff05ff0001058284000040821a006acfc01ab2d05e00840100d87a80821a006acfc01ab2d05e00f5f6' @@ -61,7 +57,6 @@ const foreignTx = Serialization.TxCBOR( const createWalletAndApiWithStores = async ( unspendableUtxos: Cardano.Utxo[], providers?: TestProviders, - getCollateralCallback?: (args: GetCollateralCallbackParams) => Promise, settle = true, availableUtxos?: Cardano.Utxo[] ) => { @@ -79,8 +74,7 @@ const createWalletAndApiWithStores = async ( const confirmationCallback = { signData: createMockGenericCallback({ cancel$: NEVER }), signTx: createMockGenericCallback({ cancel$: NEVER }), - submitTx: createMockGenericCallback(true), - ...(!!getCollateralCallback && { getCollateral: getCollateralCallback }) + submitTx: createMockGenericCallback(true) }; wallet.governance.getPubDRepKey = jest.fn(wallet.governance.getPubDRepKey); @@ -123,12 +117,7 @@ describe('cip30', () => { }) ); // CREATE A WALLET - ({ wallet, api, confirmationCallback } = await createWalletAndApiWithStores( - [mockUtxo[2]], - providers, - undefined, - false - )); + ({ wallet, api, confirmationCallback } = await createWalletAndApiWithStores([mockUtxo[2]], providers, false)); }); afterEach(() => { @@ -302,26 +291,6 @@ describe('cip30', () => { let wallet4: BaseWallet; let api4: WithSenderContext; - // Wallet 5 - let wallet5: BaseWallet; - let api5: WithSenderContext; - - // Wallet 6 - let wallet6: BaseWallet; - let api6: WithSenderContext; - - // Wallet 7 - let wallet7: BaseWallet; - let api7: WithSenderContext; - - // Wallet 8 - let wallet8: BaseWallet; - let api8: WithSenderContext; - - // Wallet 9 - let wallet9: BaseWallet; - let api9: WithSenderContext; - beforeAll(async () => { // CREATE A WALLET WITH LOW COINS UTxOs ({ wallet: wallet2, api: api2 } = await createWalletAndApiWithStores(utxosWithLowCoins)); @@ -331,51 +300,6 @@ describe('cip30', () => { // CREATE A WALLET WITH UTxOS WITH ASSETS ({ wallet: wallet4, api: api4 } = await createWalletAndApiWithStores([mockUtxo[1], mockUtxo[2]])); - - // CREATE WALLET WITH CALLBACK FOR GET COLLATERAL (UNSPENDABLES DOES NOT FULFILL AMOUNT, AVAILABLE UTxOs WITH MIXED ASSETS) - ({ wallet: wallet5, api: api5 } = await createWalletAndApiWithStores( - utxosWithLowCoins, - providers, - mockCollateralCallback, - true, - utxosWithLowCoinsAndMixedAssets - )); - - // CREATE WALLET WITH CALLBACK FOR GET COLLATERAL (NO UNSPENDABLES, AVAILABLE UTxOs WITH MIXED ASSETS) - ({ wallet: wallet6, api: api6 } = await createWalletAndApiWithStores( - [], - providers, - mockCollateralCallback, - true, - utxosWithLowCoinsAndMixedAssets - )); - - // WALLET WITH CALLBACK FOR GET COLLATERAL (UNSPENDABLES DOES NOT FULFILL AMOUNT, NO AVAILABLE UTxOS) - ({ wallet: wallet7, api: api7 } = await createWalletAndApiWithStores( - utxosWithLowCoins, - providers, - mockCollateralCallback, - true, - [] - )); - - // WALLET WITH CALLBACK FOR GET COLLATERAL (BRAND NEW WALLET, NO UTXOS) - ({ wallet: wallet8, api: api8 } = await createWalletAndApiWithStores( - [], - providers, - mockCollateralCallback, - true, - [] - )); - - // WALLET WITH CALLBACK FOR GET COLLATERAL (ONLY IMPURE UTXOs) - ({ wallet: wallet9, api: api9 } = await createWalletAndApiWithStores( - [], - providers, - mockCollateralCallback, - true, - impureUtxos - )); }); afterAll(() => { @@ -384,81 +308,11 @@ describe('cip30', () => { wallet4.shutdown(); }); - beforeEach(() => { - mockCollateralCallback.mockClear(); - }); - test('can handle serialization errors', async () => { // YYYY is invalid hex that will throw at serialization await expect(api.getCollateral(context, { amount: 'YYYY' })).rejects.toThrowError(ApiError); }); - it('executes collateral callback if provided and unspendable UTxOs do not meet amount required', async () => { - const collateral = await api5.getCollateral(context); - expect(mockCollateralCallback).toHaveBeenCalledWith({ - data: { - amount: 5_000_000n, - utxos: sortedUtxosWithLowCoins - }, - sender: { - url: 'https://lace.io' - }, - type: 'get_collateral' - }); - - expect(collateral).toEqual([Serialization.TransactionUnspentOutput.fromCore(mockUtxo[3]).toCbor()]); - wallet5.shutdown(); - }); - - it('executes collateral callback if provided and no unspendable UTxOs are available', async () => { - const collateral = await api6.getCollateral(context); - expect(mockCollateralCallback).toHaveBeenCalledWith({ - data: { - amount: 5_000_000n, - utxos: sortedUtxosWithLowCoins - }, - sender: { - url: 'https://lace.io' - }, - type: 'get_collateral' - }); - - expect(collateral).toEqual([Serialization.TransactionUnspentOutput.fromCore(mockUtxo[3]).toCbor()]); - wallet6.shutdown(); - }); - - it('does not execute collateral callback if provided with no available UTxOs', async () => { - await expect(api7.getCollateral(context)).rejects.toThrow(ApiError); - expect(mockCollateralCallback).not.toHaveBeenCalled(); - wallet7.shutdown(); - }); - - it('does not execute collateral callback and returns null if brand new wallet (no UTXOS)', async () => { - await expect(api8.getCollateral(context)).resolves.toBeNull(); - expect(mockCollateralCallback).not.toHaveBeenCalled(); - wallet8.shutdown(); - }); - - it('does executes collateral callback with empty array if wallet has only impure UTXOS', async () => { - await expect(api9.getCollateral(context)).resolves.not.toBeNull(); - expect(mockCollateralCallback).toHaveBeenCalledWith({ - data: { - amount: 5_000_000n, - utxos: [] - }, - sender: { - url: 'https://lace.io' - }, - type: 'get_collateral' - }); - wallet9.shutdown(); - }); - - it('does not execute collateral callback if not provided', async () => { - await expect(api2.getCollateral(context)).rejects.toThrow(ApiError); - expect(mockCollateralCallback).not.toHaveBeenCalled(); - }); - test('accepts amount as tagged integer', async () => { await expect(api.getCollateral(context, { amount: 'c2434c4b40' })).resolves.not.toThrow(); });