Skip to content

fix(passport): undefined fee options #2664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 15, 2025
Merged
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
20 changes: 18 additions & 2 deletions packages/passport/sdk/src/zkEvm/relayerClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('relayerClient', () => {
});
});

it('throws HTTP error for non-ok response', async () => {
it('throws error from JSON response when response contains error field', async () => {
const to = '0xd64b0d2d72bb1b3f18046b8a7fc6c9ee6bccd287';
const data = '0x123';

Expand All @@ -84,7 +84,23 @@ describe('relayerClient', () => {
});

await expect(relayerClient.ethSendTransaction(to, data)).rejects.toThrow(
'Relayer HTTP error: 401. Content: "{"error":"invalid_token"}"',
'invalid_token',
);
});

it('throws HTTP error for non-ok response without error field', async () => {
const to = '0xd64b0d2d72bb1b3f18046b8a7fc6c9ee6bccd287';
const data = '0x123';

(global.fetch as jest.Mock).mockResolvedValue({
ok: false,
status: 500,
statusText: 'Internal Server Error',
text: () => Promise.resolve('{"result":"some_result"}'),
});

await expect(relayerClient.ethSendTransaction(to, data)).rejects.toThrow(
'Relayer HTTP error: 500. Content: "{"result":"some_result"}"',
);
});

Expand Down
12 changes: 8 additions & 4 deletions packages/passport/sdk/src/zkEvm/relayerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type ImGetFeeOptionsRequest = {
};

type ImGetFeeOptionsResponse = JsonRpc & {
result: FeeOption[]
result: FeeOption[] | undefined
};

// ImSign types
Expand Down Expand Up @@ -126,13 +126,13 @@ export class RelayerClient {
body: JSON.stringify(body),
});

const responseText = await response.text();

if (!response.ok) {
const responseText = await response.text();
const preview = RelayerClient.getResponsePreview(responseText);
throw new Error(`Relayer HTTP error: ${response.status}. Content: "${preview}"`);
}

const responseText = await response.text();
let jsonResponse;
try {
jsonResponse = JSON.parse(responseText);
Expand All @@ -141,6 +141,10 @@ export class RelayerClient {
throw new Error(`Relayer JSON parse error: ${parseError instanceof Error ? parseError.message : 'Unknown error'}. Content: "${preview}"`);
}

if (jsonResponse.error) {
throw new Error(jsonResponse.error);
}

return jsonResponse;
}

Expand All @@ -167,7 +171,7 @@ export class RelayerClient {
return result;
}

public async imGetFeeOptions(userAddress: string, data: BytesLike): Promise<FeeOption[]> {
public async imGetFeeOptions(userAddress: string, data: BytesLike): Promise<FeeOption[] | undefined> {
const { chainId } = await this.rpcProvider.getNetwork();
const payload: ImGetFeeOptionsRequest = {
method: 'im_getFeeOptions',
Expand Down
42 changes: 42 additions & 0 deletions packages/passport/sdk/src/zkEvm/transactionHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,48 @@ describe('transactionHelpers', () => {
flow,
})).rejects.toThrow('Transaction send failed');
});

it('throws an error when imGetFeeOptions returns undefined', async () => {
(relayerClient.imGetFeeOptions as jest.Mock).mockResolvedValue(undefined);

await expect(prepareAndSignTransaction({
transactionRequest,
ethSigner,
rpcProvider,
guardianClient,
relayerClient,
zkEvmAddress,
flow,
})).rejects.toThrow('Invalid fee options received from relayer');
});

it('throws an error when imGetFeeOptions returns null', async () => {
(relayerClient.imGetFeeOptions as jest.Mock).mockResolvedValue(null);

await expect(prepareAndSignTransaction({
transactionRequest,
ethSigner,
rpcProvider,
guardianClient,
relayerClient,
zkEvmAddress,
flow,
})).rejects.toThrow('Invalid fee options received from relayer');
});

it('throws an error when imGetFeeOptions returns a non-array', async () => {
(relayerClient.imGetFeeOptions as jest.Mock).mockResolvedValue({ invalid: 'response' });

await expect(prepareAndSignTransaction({
transactionRequest,
ethSigner,
rpcProvider,
guardianClient,
relayerClient,
zkEvmAddress,
flow,
})).rejects.toThrow('Invalid fee options received from relayer');
});
});

describe('prepareAndSignEjectionTransaction', () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/passport/sdk/src/zkEvm/transactionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const getFeeOption = async (
transactions,
);

if (!feeOptions || !Array.isArray(feeOptions)) {
throw new Error('Invalid fee options received from relayer');
}

const imxFeeOption = feeOptions.find(
(feeOption) => feeOption.tokenSymbol === 'IMX',
);
Expand Down