Skip to content
Merged
4 changes: 4 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions app/_locales/en_GB/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added app/images/variational-referral.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions app/scripts/controllers/preferences-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,7 @@ describe('preferences controller', () => {
"asterdex": {},
"gmx": {},
"hyperliquid": {},
"variational": {},
},
"securityAlertsEnabled": true,
"snapRegistryList": {},
Expand Down Expand Up @@ -899,6 +900,7 @@ describe('preferences controller', () => {
"asterdex": {},
"gmx": {},
"hyperliquid": {},
"variational": {},
},
"securityAlertsEnabled": true,
"showSidePanelMigrationToast": false,
Expand Down Expand Up @@ -984,6 +986,7 @@ describe('preferences controller', () => {
"asterdex": {},
"gmx": {},
"hyperliquid": {},
"variational": {},
},
"securityAlertsEnabled": true,
"showSidePanelMigrationToast": false,
Expand Down Expand Up @@ -1120,6 +1123,7 @@ describe('preferences controller', () => {
},
[DefiReferralPartner.GMX]: {},
[DefiReferralPartner.AsterDEX]: {},
[DefiReferralPartner.Variational]: {},
},
},
});
Expand All @@ -1144,6 +1148,7 @@ describe('preferences controller', () => {
},
[DefiReferralPartner.GMX]: {},
[DefiReferralPartner.AsterDEX]: {},
[DefiReferralPartner.Variational]: {},
},
},
});
Expand Down Expand Up @@ -1185,6 +1190,7 @@ describe('preferences controller', () => {
},
[DefiReferralPartner.GMX]: {},
[DefiReferralPartner.AsterDEX]: {},
[DefiReferralPartner.Variational]: {},
},
},
});
Expand All @@ -1208,6 +1214,7 @@ describe('preferences controller', () => {
},
[DefiReferralPartner.GMX]: {},
[DefiReferralPartner.AsterDEX]: {},
[DefiReferralPartner.Variational]: {},
},
},
});
Expand All @@ -1232,6 +1239,9 @@ describe('preferences controller', () => {
expect(
controller.state.referrals[DefiReferralPartner.AsterDEX],
).toStrictEqual({});
expect(
controller.state.referrals[DefiReferralPartner.Variational],
).toStrictEqual({});
});

it('deep merges referrals state to add new partners while preserving existing data', () => {
Expand Down
1 change: 1 addition & 0 deletions app/scripts/controllers/preferences-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export const getDefaultPreferencesControllerState =
[DefiReferralPartner.AsterDEX]: {},
[DefiReferralPartner.GMX]: {},
[DefiReferralPartner.Hyperliquid]: {},
[DefiReferralPartner.Variational]: {},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function getLegacyBackgroundApiServiceMessenger(
'NetworkController:findNetworkClientIdByChainId',
'NetworkController:getNetworkClientById',
'NetworkController:getNetworkConfigurationByNetworkClientId',
'SelectedNetworkController:getNetworkClientIdForDomain',
'NetworkController:getSelectedNetworkClient',
'NetworkController:addNetwork',
'NetworkController:setActiveNetwork',
Expand Down
85 changes: 85 additions & 0 deletions app/scripts/services/legacy-background-api-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6259,6 +6259,7 @@ describe('LegacyBackgroundApiService', () => {
describe('DeFi referral', () => {
const HYPERLIQUID = DEFI_REFERRAL_PARTNERS[DefiReferralPartner.Hyperliquid];
const GMX = DEFI_REFERRAL_PARTNERS[DefiReferralPartner.GMX];
const VARIATIONAL = DEFI_REFERRAL_PARTNERS[DefiReferralPartner.Variational];
const mockTabId = 140;
const mockPermittedAccount = '0x123';
const mockPermittedAccounts = [mockPermittedAccount, '0x456'];
Expand Down Expand Up @@ -6366,6 +6367,26 @@ describe('LegacyBackgroundApiService', () => {
);
}

/**
* Registers the network handlers used by the requiredChainId gate.
*
* @param rootMessenger - The root messenger to register handlers on.
* @param chainId - The active chain ID for the partner's domain.
*/
function registerPartnerNetwork(
rootMessenger: RootMessenger,
chainId: string,
) {
rootMessenger.registerActionHandler(
'SelectedNetworkController:getNetworkClientIdForDomain',
jest.fn().mockReturnValue('network-client-id'),
);
rootMessenger.registerActionHandler(
'NetworkController:getNetworkConfigurationByNetworkClientId',
jest.fn().mockReturnValue({ chainId }),
);
}

beforeEach(() => {
jest.mocked(trackEvent).mockClear();
jest.mocked(checkGmxHasReferralCode).mockReset().mockResolvedValue(false);
Expand Down Expand Up @@ -7057,6 +7078,69 @@ describe('LegacyBackgroundApiService', () => {
);
});
});

describe('requiredChainId gate', () => {
it('does not proceed when the active chain does not match requiredChainId', async () => {
const getPermittedAccounts = jest
.fn()
.mockResolvedValue(mockPermittedAccounts);
await withService(
{ options: { getPermittedAccounts } },
async ({ rootMessenger }) => {
const handlers = registerReferralHandlers(rootMessenger, {
featureFlags: { [DefiReferralPartner.Variational]: true },
referrals: { [DefiReferralPartner.Variational]: {} },
});
registerPartnerNetwork(rootMessenger, '0x1');

await rootMessenger.call(
'LegacyBackgroundApiService:handleDefiReferral',
VARIATIONAL,
mockTabId,
ReferralTriggerType.NewConnection,
);

expect(handlers.add).not.toHaveBeenCalled();
expect(handlers.addReferralPassedAccount).not.toHaveBeenCalled();
},
);
});

it('proceeds when the active chain matches requiredChainId', async () => {
const getPermittedAccounts = jest
.fn()
.mockResolvedValue(mockPermittedAccounts);
await withService(
{ options: { getPermittedAccounts } },
async ({ rootMessenger }) => {
const handlers = registerReferralHandlers(rootMessenger, {
featureFlags: { [DefiReferralPartner.Variational]: true },
referrals: { [DefiReferralPartner.Variational]: {} },
});
registerPartnerNetwork(rootMessenger, '0xa4b1');

await rootMessenger.call(
'LegacyBackgroundApiService:handleDefiReferral',
VARIATIONAL,
mockTabId,
ReferralTriggerType.NewConnection,
);

expect(handlers.add).toHaveBeenCalledWith({
origin: VARIATIONAL.origin,
type: VARIATIONAL.approvalType,
requestData: {
learnMoreUrl: VARIATIONAL.learnMoreUrl,
partnerId: DefiReferralPartner.Variational,
partnerName: VARIATIONAL.name,
selectedAddress: mockPermittedAccount,
},
shouldShowRequest: true,
});
},
);
});
});
});

describe('handleDefiReferralOnPermittedAccountsAdded', () => {
Expand Down Expand Up @@ -7329,6 +7413,7 @@ function getMessenger(
'NetworkController:findNetworkClientIdByChainId',
'NetworkController:getNetworkClientById',
'NetworkController:getNetworkConfigurationByNetworkClientId',
'SelectedNetworkController:getNetworkClientIdForDomain',
'NetworkController:getSelectedNetworkClient',
'NetworkController:addNetwork',
'NetworkController:setActiveNetwork',
Expand Down
21 changes: 21 additions & 0 deletions app/scripts/services/legacy-background-api-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
NetworkEnablementControllerState,
NetworkEnablementControllerStateChangeEvent,
} from '@metamask/network-enablement-controller';
import { SelectedNetworkControllerGetNetworkClientIdForDomainAction } from '@metamask/selected-network-controller';
import {
add0x,
bytesToHex,
Expand Down Expand Up @@ -678,6 +679,7 @@ type AllowedActions =
| SeedlessOnboardingControllerSubmitPasswordAction
| SeedlessOnboardingControllerSyncLatestGlobalPasswordAction
| SeedlessOnboardingControllerUpdateBackupMetadataStateAction
| SelectedNetworkControllerGetNetworkClientIdForDomainAction
| GetSignatureState
| ShieldControllerClearStateAction
| ShieldControllerStartAction
Expand Down Expand Up @@ -4598,6 +4600,25 @@ export class LegacyBackgroundApiService {
return;
}

// If the partner requires a specific chain and user's chain doesn't match,
// return early to avoid the referral code potentially not being applied.
// Don't write any account status so that the prompt can show on the next
// trigger (NewConnection or OnNavigateConnectedTab) once the user has switched chain
if (partner.requiredChainId) {
const networkClientId = this.#messenger.call(
'SelectedNetworkController:getNetworkClientIdForDomain',
partner.origin,
);
const networkConfig = this.#messenger.call(
'NetworkController:getNetworkConfigurationByNetworkClientId',
networkClientId,
);
const currentChainId = networkConfig?.chainId;
if (currentChainId !== partner.requiredChainId) {
return;
}
}
Comment thread
ameliejyc marked this conversation as resolved.

const { activePermittedAddressOverride } = options;
const activePermittedAccount =
(activePermittedAddressOverride &&
Expand Down
3 changes: 3 additions & 0 deletions shared/constants/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export const MESSAGE_TYPE = {
HYPERLIQUID_REFERRAL_CONSENT: 'hyperliquid_referral_consent',
ASTERDEX_REFERRAL_CONSENT: 'asterdex_referral_consent',
GMX_REFERRAL_CONSENT: 'gmx_referral_consent',
VARIATIONAL_REFERRAL_CONSENT: 'variational_referral_consent',
} as const;

export type MessageType = (typeof MESSAGE_TYPE)[keyof typeof MESSAGE_TYPE];
Expand All @@ -176,6 +177,8 @@ export const HYPERLIQUID_APPROVAL_TYPE =
MESSAGE_TYPE.HYPERLIQUID_REFERRAL_CONSENT;
export const ASTERDEX_APPROVAL_TYPE = MESSAGE_TYPE.ASTERDEX_REFERRAL_CONSENT;
export const GMX_APPROVAL_TYPE = MESSAGE_TYPE.GMX_REFERRAL_CONSENT;
export const VARIATIONAL_APPROVAL_TYPE =
MESSAGE_TYPE.VARIATIONAL_REFERRAL_CONSENT;

export const SNAP_MANAGE_ACCOUNTS_CONFIRMATION_TYPES = {
confirmAccountCreation: 'snap_manageAccounts:confirmAccountCreation',
Expand Down
17 changes: 17 additions & 0 deletions shared/constants/defi-referrals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum DefiReferralPartner {
Hyperliquid = 'hyperliquid',
GMX = 'gmx',
AsterDEX = 'asterdex',
Variational = 'variational',
}

/**
Expand Down Expand Up @@ -32,6 +33,12 @@ export type DefiReferralPartnerConfig = {
approvalType: string;
/** Connection flow prior to showing the referral screen */
connectionFlow: ConnectionFlow;
/**
* When set, the referral prompt is suppressed if the user's active chain
* doesn't match this hex chain ID. This is used in cases where the partner's
* code application logic only works when on the required chain (e.g. Variational)
*/
requiredChainId?: string;
};

/**
Expand Down Expand Up @@ -70,6 +77,16 @@ export const DEFI_REFERRAL_PARTNERS: Record<
approvalType: 'asterdex_referral_consent',
connectionFlow: 'permissions_then_signature',
},
[DefiReferralPartner.Variational]: {
id: DefiReferralPartner.Variational,
name: 'Variational',
origin: 'https://omni.variational.io',
referralUrl: 'https://omni.variational.io?ref=OMNIMETAMASK',
learnMoreUrl: 'https://docs.variational.io/omni/rewards/referrals',
approvalType: 'variational_referral_consent',
connectionFlow: 'permissions',
requiredChainId: '0xa4b1', // Arbitrum
},
};

/**
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/fixtures/default-fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,8 @@
"referrals": {
"asterdex": {},
"gmx": {},
"hyperliquid": {}
"hyperliquid": {},
"variational": {}
},
"securityAlertsEnabled": true,
"showSidePanelMigrationToast": false,
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/fixtures/onboarding-fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -2046,7 +2046,8 @@
"referrals": {
"asterdex": {},
"gmx": {},
"hyperliquid": {}
"hyperliquid": {},
"variational": {}
},
"securityAlertsEnabled": true,
"showSidePanelMigrationToast": false,
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/tests/settings/state-logs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1298,7 +1298,8 @@
"referrals": {
"asterdex": {},
"gmx": {},
"hyperliquid": {}
"hyperliquid": {},
"variational": {}
},
"region": "string",
"requests": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
HYPERLIQUID_APPROVAL_TYPE,
GMX_APPROVAL_TYPE,
ASTERDEX_APPROVAL_TYPE,
VARIATIONAL_APPROVAL_TYPE,
} from '../../../../../shared/constants/app';
import {
DEFI_REFERRAL_PARTNERS,
Expand All @@ -15,6 +16,8 @@ const HYPERLIQUID_CONFIG =
DEFI_REFERRAL_PARTNERS[DefiReferralPartner.Hyperliquid];
const GMX_CONFIG = DEFI_REFERRAL_PARTNERS[DefiReferralPartner.GMX];
const ASTERDEX_CONFIG = DEFI_REFERRAL_PARTNERS[DefiReferralPartner.AsterDEX];
const VARIATIONAL_CONFIG =
DEFI_REFERRAL_PARTNERS[DefiReferralPartner.Variational];

const STATE_MOCK_DEFAULT = {
metamask: {
Expand All @@ -23,6 +26,7 @@ const STATE_MOCK_DEFAULT = {
[DefiReferralPartner.Hyperliquid]: {},
[DefiReferralPartner.GMX]: {},
[DefiReferralPartner.AsterDEX]: {},
[DefiReferralPartner.Variational]: {},
},
},
},
Expand Down Expand Up @@ -103,3 +107,22 @@ export const AsterdexStory = (args: { selectedAddress: string }) => {
};

AsterdexStory.storyName = 'Asterdex';

export const VariationalStory = (args: { selectedAddress: string }) => {
return (
<PendingApproval
type={VARIATIONAL_APPROVAL_TYPE}
requestData={{
selectedAddress: args.selectedAddress,
partnerId: VARIATIONAL_CONFIG.id,
partnerName: VARIATIONAL_CONFIG.name,
learnMoreUrl: VARIATIONAL_CONFIG.learnMoreUrl,
}}
state={STATE_MOCK_DEFAULT}
>
<ConfirmationPage />
</PendingApproval>
);
};

VariationalStory.storyName = 'Variational';
Loading
Loading