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
61 changes: 60 additions & 1 deletion packages/ramps-controller/src/RampsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,54 @@ export type RampsControllerGetStateAction = ControllerGetStateAction<
RampsControllerState
>;

/**
* Sets the selected token by asset ID.
*/
export type RampsControllerSetSelectedTokenAction = {
type: `${typeof controllerName}:setSelectedToken`;
handler: (assetId?: string) => void;
};

/**
* Fetches buy quotes using the current controller context and provided inputs.
*/
export type RampsControllerGetQuotesAction = {
type: `${typeof controllerName}:getQuotes`;
handler: (options: {
region?: string;
fiat?: string;
assetId?: string;
amount: number;
walletAddress: string;
paymentMethods?: string[];
providers?: string[];
redirectUrl?: string;
action?: RampAction;
forceRefresh?: boolean;
ttl?: number;
}) => Promise<QuotesResponse>;
};

/**
* Fetches a specific order from the unified V2 API endpoint.
*/
export type RampsControllerGetOrderAction = {
type: `${typeof controllerName}:getOrder`;
handler: (
providerCode: string,
orderCode: string,
wallet: string,
) => Promise<RampsOrder>;
};

/**
* Actions that {@link RampsControllerMessenger} exposes to other consumers.
*/
export type RampsControllerActions = RampsControllerGetStateAction;
export type RampsControllerActions =
| RampsControllerGetStateAction
| RampsControllerGetOrderAction
| RampsControllerGetQuotesAction
| RampsControllerSetSelectedTokenAction;

/**
* Actions from other messengers that {@link RampsController} calls.
Expand Down Expand Up @@ -661,6 +705,21 @@ export class RampsController extends BaseController<

this.#requestCacheTTL = requestCacheTTL;
this.#requestCacheMaxSize = requestCacheMaxSize;

this.messenger.registerActionHandler(
'RampsController:setSelectedToken',
this.setSelectedToken.bind(this),
);

this.messenger.registerActionHandler(
'RampsController:getQuotes',
this.getQuotes.bind(this),
);

this.messenger.registerActionHandler(
'RampsController:getOrder',
this.getOrder.bind(this),
);
}

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/ramps-controller/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export type {
RampsControllerActions,
RampsControllerEvents,
RampsControllerGetOrderAction,
RampsControllerGetStateAction,
RampsControllerGetQuotesAction,
RampsControllerMessenger,
RampsControllerSetSelectedTokenAction,
RampsControllerState,
RampsControllerStateChangeEvent,
RampsControllerOptions,
Expand Down
1 change: 1 addition & 0 deletions packages/transaction-pay-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@metamask/messenger": "^0.3.0",
"@metamask/metamask-eth-abis": "^3.1.1",
"@metamask/network-controller": "^30.0.0",
"@metamask/ramps-controller": "^10.0.0",
"@metamask/remote-feature-flag-controller": "^4.1.0",
"@metamask/transaction-controller": "^62.19.0",
"@metamask/utils": "^11.9.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { TransactionMeta } from '@metamask/transaction-controller';
import type { Draft } from 'immer';
import { noop } from 'lodash';

import { updateFiatPayment } from './actions/update-fiat-payment';
import { updatePaymentToken } from './actions/update-payment-token';
import {
CONTROLLER_NAME,
Expand All @@ -18,12 +19,14 @@ import type {
TransactionPayControllerMessenger,
TransactionPayControllerOptions,
TransactionPayControllerState,
UpdateFiatPaymentRequest,
UpdatePaymentTokenRequest,
} from './types';
import { getStrategyOrder } from './utils/feature-flags';
import { deriveFiatAssetIdForFiatPayment } from './utils/fiat';
import { updateQuotes } from './utils/quotes';
import { updateSourceAmounts } from './utils/source-amounts';
import { pollTransactionChanges } from './utils/transaction';
import { getTransaction, pollTransactionChanges } from './utils/transaction';

const stateMetadata: StateMetadata<TransactionPayControllerState> = {
transactionData: {
Expand All @@ -47,10 +50,12 @@ export class TransactionPayController extends BaseController<

readonly #getStrategy?: (
transaction: TransactionMeta,
transactionData?: TransactionData,
) => TransactionPayStrategy;

readonly #getStrategies?: (
transaction: TransactionMeta,
transactionData?: TransactionData,
) => TransactionPayStrategy[];

constructor({
Expand Down Expand Up @@ -111,6 +116,13 @@ export class TransactionPayController extends BaseController<
});
}

updateFiatPayment(request: UpdateFiatPaymentRequest): void {
updateFiatPayment(request, {
messenger: this.messenger,
updateTransactionData: this.#updateTransactionData.bind(this),
});
}

#removeTransactionData(transactionId: string): void {
this.update((state) => {
delete state.transactionData[transactionId];
Expand All @@ -122,6 +134,7 @@ export class TransactionPayController extends BaseController<
fn: (transactionData: Draft<TransactionData>) => void,
): void {
let shouldUpdateQuotes = false;
let fiatAssetId: string | undefined;

this.update((state) => {
const { transactionData } = state;
Expand All @@ -130,13 +143,26 @@ export class TransactionPayController extends BaseController<
const originalTokens = current?.tokens;
const originalIsMaxAmount = current?.isMaxAmount;
const originalIsPostQuote = current?.isPostQuote;
const originalFiatPaymentAmount = current?.fiatPayment?.amount;
const originalFiatPaymentMethodId =
current?.fiatPayment?.selectedPaymentMethodId;

if (!current) {
transactionData[transactionId] = {
fiatPayment: {
amount: null,
quickBuyOrderId: null,
selectedPaymentMethodId: null,
},
isLoading: false,
tokens: [],
};

const transaction = getTransaction(transactionId, this.messenger);
fiatAssetId = deriveFiatAssetIdForFiatPayment(
transaction as unknown as TransactionMeta,
);

current = transactionData[transactionId];
}

Expand All @@ -150,6 +176,11 @@ export class TransactionPayController extends BaseController<
const isTokensUpdated = current.tokens !== originalTokens;
const isIsMaxUpdated = current.isMaxAmount !== originalIsMaxAmount;
const isPostQuoteUpdated = current.isPostQuote !== originalIsPostQuote;
const isFiatAmountUpdated =
current.fiatPayment?.amount !== originalFiatPaymentAmount;
const isFiatPaymentMethodUpdated =
current.fiatPayment?.selectedPaymentMethodId !==
originalFiatPaymentMethodId;

if (
isPaymentTokenUpdated ||
Expand All @@ -161,6 +192,14 @@ export class TransactionPayController extends BaseController<

shouldUpdateQuotes = true;
}

if (isFiatAmountUpdated || isFiatPaymentMethodUpdated) {
shouldUpdateQuotes = true;
}

if (fiatAssetId) {
this.messenger.call('RampsController:setSelectedToken', fiatAssetId);
}
});

if (shouldUpdateQuotes) {
Expand Down Expand Up @@ -195,14 +234,25 @@ export class TransactionPayController extends BaseController<
'TransactionPayController:updatePaymentToken',
this.updatePaymentToken.bind(this),
);

this.messenger.registerActionHandler(
'TransactionPayController:updateFiatPayment',
this.updateFiatPayment.bind(this),
);
}

#getStrategiesWithFallback(
transaction: TransactionMeta,
): TransactionPayStrategy[] {
const transactionData = transaction.id
? this.state.transactionData[transaction.id]
: undefined;

const strategyCandidates: unknown[] =
this.#getStrategies?.(transaction) ??
(this.#getStrategy ? [this.#getStrategy(transaction)] : []);
this.#getStrategies?.(transaction, transactionData) ??
(this.#getStrategy
? [this.#getStrategy(transaction, transactionData)]
: []);

const validStrategies = strategyCandidates.filter(
(strategy): strategy is TransactionPayStrategy =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createModuleLogger } from '@metamask/utils';
import { pickBy } from 'lodash';

import type { TransactionPayControllerMessenger } from '..';
import { projectLogger } from '../logger';
import type {
UpdateFiatPaymentRequest,
UpdateTransactionDataCallback,
} from '../types';
import { getTransaction } from '../utils/transaction';

const log = createModuleLogger(projectLogger, 'update-fiat-payment');

export type UpdateFiatPaymentOptions = {
messenger: TransactionPayControllerMessenger;
updateTransactionData: UpdateTransactionDataCallback;
};

/**
* Update fiat payment state for a specific transaction.
*
* @param request - Request parameters.
* @param options - Options bag.
*/
export function updateFiatPayment(
request: UpdateFiatPaymentRequest,
options: UpdateFiatPaymentOptions,
): void {
const { transactionId, selectedPaymentMethodId, amount, quickBuyOrderId } =
request;
const { messenger, updateTransactionData } = options;

const transaction = getTransaction(transactionId, messenger);

if (!transaction) {
throw new Error('Transaction not found');
}

log('Updated fiat payment', {
transactionId,
selectedPaymentMethodId,
amount,
quickBuyOrderId,
});

updateTransactionData(transactionId, (data) => {
const currentFiatPayment = data.fiatPayment ?? {
amount: null,
quickBuyOrderId: null,
selectedPaymentMethodId: null,
};

const patch = pickBy(
{
amount,
quickBuyOrderId,
selectedPaymentMethodId,
},
(value) => value !== undefined,
) as Partial<typeof currentFiatPayment>;

data.fiatPayment = {
...currentFiatPayment,
...patch,
};

// We may need to update the payment token here later
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export function updatePaymentToken(

updateTransactionData(transactionId, (data) => {
data.paymentToken = paymentToken;
data.fiatPayment = {
amount: null,
quickBuyOrderId: null,
selectedPaymentMethodId: null,
};
});
}

Expand Down
32 changes: 32 additions & 0 deletions packages/transaction-pay-controller/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TransactionType } from '@metamask/transaction-controller';
import type { Hex } from '@metamask/utils';

export const CONTROLLER_NAME = 'TransactionPayController';
Expand All @@ -14,6 +15,36 @@ export const ARBITRUM_USDC_ADDRESS =
export const POLYGON_USDCE_ADDRESS =
'0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174' as Hex;

export type TransactionPayFiatAsset = {
address: Hex;
caipAssetId: string;
chainId: Hex;
decimals: number;
};

const POLYGON_POL_FIAT_ASSET: TransactionPayFiatAsset = {
address: '0x0000000000000000000000000000000000001010',
caipAssetId: 'eip155:137/slip44:966',
chainId: CHAIN_ID_POLYGON,
decimals: 18,
};

const ARBITRUM_ETH_FIAT_ASSET: TransactionPayFiatAsset = {
address: NATIVE_TOKEN_ADDRESS,
caipAssetId: 'eip155:42161/slip44:60',
chainId: CHAIN_ID_ARBITRUM,
decimals: 18,
};

// We might use feature flags to determine these later
export const MMPAY_FIAT_ASSET_ID_BY_TX_TYPE: Partial<
Record<TransactionType, TransactionPayFiatAsset>
> = {
[TransactionType.predictDeposit]: POLYGON_POL_FIAT_ASSET,
[TransactionType.perpsDeposit]: ARBITRUM_ETH_FIAT_ASSET,
[TransactionType.perpsDepositAndOrder]: ARBITRUM_ETH_FIAT_ASSET,
};

export const STABLECOINS: Record<Hex, Hex[]> = {
// Mainnet
'0x1': [
Expand All @@ -35,6 +66,7 @@ export const STABLECOINS: Record<Hex, Hex[]> = {
export enum TransactionPayStrategy {
Bridge = 'bridge',
Relay = 'relay',
Fiat = 'fiat',
Test = 'test',
}

Expand Down
3 changes: 3 additions & 0 deletions packages/transaction-pay-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export type {
TransactionPayControllerGetStrategyAction,
TransactionPayControllerMessenger,
TransactionPayControllerOptions,
TransactionPayControllerUpdateFiatPaymentAction,
TransactionPayControllerSetTransactionConfigAction,
TransactionFiatPayment,
TransactionPayControllerState,
TransactionPayControllerStateChangeEvent,
TransactionPayControllerUpdatePaymentTokenAction,
Expand All @@ -17,6 +19,7 @@ export type {
TransactionPayRequiredToken,
TransactionPaySourceAmount,
TransactionPayTotals,
UpdateFiatPaymentRequest,
UpdatePaymentTokenRequest,
} from './types';
export { TransactionPayStrategy } from './constants';
Expand Down
Loading
Loading