Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/frontend/src/btc/services/wallet-connect.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@ export const signPsbt = ({
return { success: false };
}

const indicesToSign = nonNullish(signInputs)
? signInputs.map(({ index }) => index).filter((index): index is number => nonNullish(index))
: undefined;

if (nonNullish(indicesToSign) && indicesToSign.length === 0) {
toastsError({ msg: { text: get(i18n).wallet_connect.error.unknown_parameter } });
await listener.rejectRequest({ topic, id, error: UNEXPECTED_ERROR });
return { success: false };
}

const network = bitcoinJsNetwork(request.params.chainId);

modalNext();
Expand Down Expand Up @@ -442,13 +452,9 @@ export const signPsbt = ({

// Reown's `signInputs` selects which inputs to sign; if absent, sign every input that is
// ours. Each must be a P2WPKH input owned by this wallet — anything else is rejected.
const indicesToSign = nonNullish(signInputs)
? signInputs
.map(({ index }) => index)
.filter((index): index is number => nonNullish(index))
: parsed.data.inputs.map((_, index) => index);
const inputIndicesToSign = indicesToSign ?? parsed.data.inputs.map((_, index) => index);

for (const index of indicesToSign) {
for (const index of inputIndicesToSign) {
const input = parsed.data.inputs[index];

if (isNullish(input?.witnessUtxo)) {
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/sol/services/wallet-connect.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ export const sign = ({
toastsError({
msg: { text: get(i18n).wallet_connect.error.wallet_not_initialized }
});
await listener.rejectRequest({ topic, id, error: UNEXPECTED_ERROR });
return { success: false };
}

Expand Down Expand Up @@ -317,6 +318,7 @@ export const sign = ({
: undefined;

if (isNullish(signature)) {
await listener.rejectRequest({ topic, id, error: UNEXPECTED_ERROR });
return { success: false };
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { decodePsbt, getAccountAddresses } from '$btc/services/wallet-connect.services';
import { decodePsbt, getAccountAddresses, signPsbt } from '$btc/services/wallet-connect.services';
import type { OptionBtcAddress } from '$btc/types/address';
import { BIP122_CHAINS } from '$env/bip122-chains.env';
import { BTC_MAINNET_NETWORK_ID, BTC_TESTNET_NETWORK_ID } from '$env/networks/networks.btc.env';
Expand Down Expand Up @@ -224,6 +224,8 @@ describe('btc wallet-connect.services', () => {

const buildRequest = (params: Record<string, unknown>): WalletKitTypes.SessionRequest =>
({
id: 123,
topic: 'test-topic',
params: {
chainId: testnetChainId,
request: { method: 'signPsbt', params }
Expand Down Expand Up @@ -310,4 +312,57 @@ describe('btc wallet-connect.services', () => {

expect(() => decodePsbt({ request, address: walletAddress })).toThrow();
});

describe('signPsbt', () => {
const mockListener = {
pair: vi.fn(),
approveSession: vi.fn(),
rejectSession: vi.fn(),
attachHandlers: vi.fn(),
detachHandlers: vi.fn(),
rejectRequest: vi.fn(),
getActiveSessions: vi.fn(),
approveRequest: vi.fn(),
disconnect: vi.fn()
} as WalletConnectListener;

let spyToastsError: MockInstance;

beforeEach(() => {
vi.clearAllMocks();

spyToastsError = vi.spyOn(toastsStore, 'toastsError');
});

it('rejects when signInputs selects no inputs', async () => {
const request = buildRequest({ psbt: buildPsbt(), signInputs: [] });
const modalNext = vi.fn();
const progress = vi.fn();

const result = await signPsbt({
address: walletAddress,
modalNext,
progress,
identity: mockIdentity,
request,
listener: mockListener
});

expect(result).toEqual({ success: false });

expect(mockListener.rejectRequest).toHaveBeenCalledExactlyOnceWith({
id: request.id,
topic: request.topic,
error: UNEXPECTED_ERROR
});
expect(mockListener.approveRequest).not.toHaveBeenCalled();

expect(modalNext).not.toHaveBeenCalled();
expect(progress).not.toHaveBeenCalled();

expect(spyToastsError).toHaveBeenCalledWith({
msg: { text: en.wallet_connect.error.unknown_parameter }
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ describe('wallet-connect.services', () => {
expect(spyToastsError).toHaveBeenCalledWith({
msg: { text: en.wallet_connect.error.wallet_not_initialized }
});

expect(mockListener.rejectRequest).toHaveBeenCalledExactlyOnceWith({
topic: mockRequest.topic,
id: mockRequest.id,
error: UNEXPECTED_ERROR
});
});

it('should return success with amount and destination when signing is successful', async () => {
Expand Down Expand Up @@ -438,6 +444,12 @@ describe('wallet-connect.services', () => {
expect(spyToastsError).toHaveBeenCalledWith({
msg: { text: en.wallet_connect.error.wallet_not_initialized }
});

expect(mockListener.rejectRequest).toHaveBeenCalledExactlyOnceWith({
topic: mockRequest.topic,
id: mockRequest.id,
error: UNEXPECTED_ERROR
});
});

it('should return success with amount and destination when signing is successful', async () => {
Expand Down Expand Up @@ -673,6 +685,29 @@ describe('wallet-connect.services', () => {

expect(console.warn).not.toHaveBeenCalled();
});

it('should reject the request when sending cannot produce a signature', async () => {
vi.mocked(isTransactionMessageWithBlockhashLifetime).mockReturnValueOnce(false);

const result = await sign(mockParams);

expect(result).toEqual({ success: false });

expect(mockParams.modalNext).toHaveBeenCalledOnce();

expect(mockParams.progress).toHaveBeenCalledExactlyOnceWith(ProgressStepsSendSol.SIGN);

expect(mockListener.approveRequest).not.toHaveBeenCalled();
expect(mockListener.rejectRequest).toHaveBeenCalledExactlyOnceWith({
topic: mockRequest.topic,
id: mockRequest.id,
error: UNEXPECTED_ERROR
});

expect(console.warn).toHaveBeenCalledExactlyOnceWith(
'WalletConnect Solana transaction does not have blockhash lifetime, cannot be sent'
);
});
});

describe('with other WalletConnect methods', () => {
Expand Down Expand Up @@ -703,7 +738,11 @@ describe('wallet-connect.services', () => {

expect(mockListener.approveRequest).not.toHaveBeenCalled();

expect(mockListener.rejectRequest).not.toHaveBeenCalled();
expect(mockListener.rejectRequest).toHaveBeenCalledExactlyOnceWith({
topic: mockParamsOther.request.topic,
id: mockParamsOther.request.id,
error: UNEXPECTED_ERROR
});

expect(spyToastsShow).not.toHaveBeenCalled();
expect(spyToastsError).not.toHaveBeenCalled();
Expand Down
Loading