Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,21 @@ export type PredictControllerPrepareWithdrawAction = {
handler: PredictController['prepareWithdraw'];
};

export type PredictControllerBeforePublishAction = {
type: `PredictController:beforePublish`;
handler: PredictController['beforePublish'];
};

export type PredictControllerBeforeSignAction = {
type: `PredictController:beforeSign`;
handler: PredictController['beforeSign'];
};

export type PredictControllerPublishAction = {
type: `PredictController:publish`;
handler: PredictController['publish'];
};

export type PredictControllerClearWithdrawTransactionAction = {
type: `PredictController:clearWithdrawTransaction`;
handler: PredictController['clearWithdrawTransaction'];
Expand Down Expand Up @@ -300,5 +310,7 @@ export type PredictControllerMethodActions =
| PredictControllerGetAccountStateAction
| PredictControllerGetBalanceAction
| PredictControllerPrepareWithdrawAction
| PredictControllerBeforePublishAction
| PredictControllerBeforeSignAction
| PredictControllerPublishAction
| PredictControllerClearWithdrawTransactionAction;
34 changes: 34 additions & 0 deletions app/components/UI/Predict/controllers/PredictController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6139,6 +6139,40 @@ describe('PredictController', () => {
});
});

describe('beforePublish', () => {
it('passes through by default', async () => {
await withController(async ({ controller }) => {
const result = await controller.beforePublish({
transactionMeta: {
id: 'tx-1',
txParams: {
from: MOCK_ADDRESS,
},
} as TransactionMeta,
});

expect(result).toBe(true);
});
});
});

describe('publish', () => {
it('passes through by default', async () => {
await withController(async ({ controller }) => {
const result = await controller.publish({
transactionMeta: {
id: 'tx-1',
txParams: {
from: MOCK_ADDRESS,
},
} as TransactionMeta,
});

expect(result).toEqual({ transactionHash: undefined });
});
});
});

describe('beforeSign', () => {
const mockTransactionMeta = {
id: 'tx-1',
Expand Down
16 changes: 16 additions & 0 deletions app/components/UI/Predict/controllers/PredictController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ export interface PredictControllerOptions {
}

const MESSENGER_EXPOSED_METHODS = [
'beforePublish',
'beforeSign',
'claimWithConfirmation',
'clearActiveOrder',
Expand All @@ -370,6 +371,7 @@ const MESSENGER_EXPOSED_METHODS = [
'onPlaceOrderSuccess',
'placeOrder',
'prepareWithdraw',
'publish',
'previewOrder',
'refreshEligibility',
'selectPaymentToken',
Expand Down Expand Up @@ -2619,6 +2621,12 @@ export class PredictController extends BaseController<
}
}

public async beforePublish(_request: {
transactionMeta: TransactionMeta;
}): Promise<boolean> {
return true;
}

public async beforeSign(request: {
transactionMeta: TransactionMeta;
}): Promise<
Expand Down Expand Up @@ -2722,6 +2730,12 @@ export class PredictController extends BaseController<
};
}

public async publish(_request: {
transactionMeta: TransactionMeta;
}): Promise<{ transactionHash?: string; isIntentComplete?: boolean }> {
return { transactionHash: undefined };
}

public clearWithdrawTransaction(): void {
this.update((state) => {
state.withdrawTransaction = null;
Expand All @@ -2730,6 +2744,7 @@ export class PredictController extends BaseController<
}

export type {
PredictControllerBeforePublishAction,
PredictControllerBeforeSignAction,
PredictControllerClaimWithConfirmationAction,
PredictControllerClearActiveOrderAction,
Expand All @@ -2754,6 +2769,7 @@ export type {
PredictControllerPlaceOrderAction,
PredictControllerPrepareWithdrawAction,
PredictControllerPreviewOrderAction,
PredictControllerPublishAction,
PredictControllerRefreshEligibilityAction,
PredictControllerSelectPaymentTokenAction,
PredictControllerSetSelectedPaymentTokenAction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,20 @@ const MOCK_TRANSACTION_META = {
* with the default mock.
* @returns A mock NetworkController.
*/
type ControllerMock = NetworkController & {
beforePublish: jest.Mock;
beforeSign: jest.Mock;
publish: jest.Mock;
};

function buildControllerMock(
partialMock?: Partial<NetworkController>,
): NetworkController {
const defaultControllerMocks = {};
partialMock?: Partial<ControllerMock>,
): ControllerMock {
const defaultControllerMocks = {
beforePublish: jest.fn().mockResolvedValue(true),
beforeSign: jest.fn(),
publish: jest.fn().mockResolvedValue({ transactionHash: undefined }),
};

// @ts-expect-error Incomplete mock, just includes properties used by code-under-test.
return {
Expand Down Expand Up @@ -180,9 +190,11 @@ describe('Transaction Controller Init', () => {
): TransactionControllerOptions[T] {
const requestMock = buildInitRequestMock(initRequestProperties);

requestMock.getMessengerClient.mockReturnValue(
buildControllerMock(dependencyProperties),
);
if (!initRequestProperties.getMessengerClient) {
requestMock.getMessengerClient.mockReturnValue(
buildControllerMock(dependencyProperties),
);
}

TransactionControllerInit(requestMock);

Expand Down Expand Up @@ -320,6 +332,29 @@ describe('Transaction Controller Init', () => {
expect(optionFn?.()).toBe(false);
});

describe('beforePublish hook', () => {
it('delegates to PredictController beforePublish', async () => {
const predictControllerMock = buildControllerMock();
const hooks = testConstructorOption(
'hooks',
{},
{
getMessengerClient: jest.fn((controllerName: string) =>
controllerName === 'PredictController'
? predictControllerMock
: buildControllerMock(),
),
},
);

await hooks?.beforePublish?.(MOCK_TRANSACTION_META);

expect(predictControllerMock.beforePublish).toHaveBeenCalledWith({
transactionMeta: MOCK_TRANSACTION_META,
});
});
});

describe('publish hook', () => {
it('calls submitSmartTransactionHook', async () => {
const hooks = testConstructorOption('hooks');
Expand Down Expand Up @@ -347,6 +382,107 @@ describe('Transaction Controller Init', () => {
expect(payHookMock).toHaveBeenCalledTimes(1);
});

it('calls Predict publish before pay and smart transaction hooks', async () => {
const predictControllerMock = buildControllerMock();
const hooks = testConstructorOption(
'hooks',
{},
{
getMessengerClient: jest.fn((controllerName: string) =>
controllerName === 'PredictController'
? predictControllerMock
: buildControllerMock(),
),
},
);

await hooks?.publish?.(MOCK_TRANSACTION_META);

expect(predictControllerMock.publish).toHaveBeenCalledWith({
transactionMeta: MOCK_TRANSACTION_META,
});
expect(payHookMock).toHaveBeenCalledTimes(1);
expect(
(predictControllerMock.publish as jest.Mock).mock
.invocationCallOrder[0],
).toBeLessThan(payHookMock.mock.invocationCallOrder[0]);
expect(
(predictControllerMock.publish as jest.Mock).mock
.invocationCallOrder[0],
).toBeLessThan(
submitSmartTransactionHookMock.mock.invocationCallOrder[0],
);
});

it('short-circuits publish when Predict returns a transaction hash', async () => {
const predictControllerMock = buildControllerMock({
publish: jest.fn().mockResolvedValue({ transactionHash: '0xpredict' }),
} as unknown as Partial<NetworkController>);
const hooks = testConstructorOption(
'hooks',
{},
{
getMessengerClient: jest.fn((controllerName: string) =>
controllerName === 'PredictController'
? predictControllerMock
: buildControllerMock(),
),
},
);

const result = await hooks?.publish?.(MOCK_TRANSACTION_META);

expect(result).toEqual({ transactionHash: '0xpredict' });
expect(payHookMock).not.toHaveBeenCalled();
expect(submitSmartTransactionHookMock).not.toHaveBeenCalled();
});

it('marks the latest transaction intent complete when Predict publish completes an intent', async () => {
const predictControllerMock = buildControllerMock({
publish: jest.fn().mockResolvedValue({
transactionHash: '0xpredict',
isIntentComplete: true,
}),
} as unknown as Partial<NetworkController>);
const getTransactionByIdMock = jest.requireMock(
'../../../../util/transactions',
).getTransactionById;
getTransactionByIdMock.mockReturnValue({ ...MOCK_TRANSACTION_META });
const hooks = testConstructorOption(
'hooks',
{},
{
getMessengerClient: jest.fn((controllerName: string) =>
controllerName === 'PredictController'
? predictControllerMock
: buildControllerMock(),
),
},
);

const result = await hooks?.publish?.(MOCK_TRANSACTION_META);

const transactionControllerInstance = transactionControllerClassMock.mock
.instances[0] as unknown as {
updateTransaction: jest.Mock;
};

expect(result).toEqual({ transactionHash: '0xpredict' });
expect(getTransactionByIdMock).toHaveBeenCalledWith(
MOCK_TRANSACTION_META.id,
transactionControllerInstance,
);
expect(
transactionControllerInstance.updateTransaction,
).toHaveBeenCalledWith(
expect.objectContaining({
id: MOCK_TRANSACTION_META.id,
isIntentComplete: true,
}),
'Predict claim relayer intent complete',
);
});

it('passes isSmartTransaction returning false to pay hook when stxDisabled is true', async () => {
selectMetaMaskPayFlagsMock.mockReturnValue({
attemptsMax: 2,
Expand Down
Loading
Loading