Skip to content
Open
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
5 changes: 5 additions & 0 deletions packages/bridge-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Define `QuoteResponse` v2 and `validateQuoteResponse` ([#9724](https://github.com/MetaMask/core/pull/9724))
- Export `BridgeAsset` and `validateBridgeAsset` used by QuoteResponse v2, and token endpoints ([#9724](https://github.com/MetaMask/core/pull/9724))

### Changed

- Bump `@metamask/assets-controllers` from `^110.0.0` to `^110.0.1` ([#9693](https://github.com/MetaMask/core/pull/9693), [#9706](https://github.com/MetaMask/core/pull/9706))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ exports[`BridgeController BatchSell (multiple quote requests) SSE fetch quotes s
},
{
"destChainId": "137",
"destTokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"destTokenAddress": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
"destWalletAddress": "SolanaWalletAddres1234",
"insufficientBal": false,
"resetApproval": false,
"slippage": 0.5,
"srcChainId": "10",
"srcTokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"srcTokenAddress": "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
"srcTokenAmount": "1000000000000000000",
"walletAddress": "0x30E8ccaD5A980BDF30447f8c2C48e70989D9d294",
},
Expand Down
11 changes: 10 additions & 1 deletion packages/bridge-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export type {
NonEvmFees,
GasMultiplierByChainId,
FeatureFlagResponse,
BridgeAsset,
GenericQuoteRequest,
BatchSellTradesResponse,
GaslessProperties,
Expand Down Expand Up @@ -118,6 +117,7 @@ export type {
QuoteResponseV1 as QuoteResponse,
QuoteResponseV1,
} from './validators/quote-response-v1.js';
export { validateQuoteResponse } from './validators/quote-response.js';
export type { Quote } from './validators/quote.js';
export { FeeType, DiscountType } from './validators/quote.js';
export { ActionTypes } from './validators/step.js';
Expand All @@ -127,9 +127,18 @@ export {
} from './validators/quote-stream-complete.js';
export { BatchSellTransactionType } from './validators/batch-sell.js';
export { TokenFeatureType } from './validators/token-feature.js';
export type {
BridgeAsset,
BridgeAssetV2,
MinimalAsset,
} from './validators/bridge-asset.js';
export {
BridgeAssetSchema,
validateBridgeAsset,
validateBridgeAssetV2,
MinimalAssetSchema,
BridgeAssetV2Schema,
BridgeAssetSecurityDataType,
} from './validators/bridge-asset.js';
export { FeatureId } from './validators/feature-flags.js';

Expand Down
7 changes: 0 additions & 7 deletions packages/bridge-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import type { BridgeController } from './bridge-controller.js';
import type { BRIDGE_CONTROLLER_NAME } from './constants/bridge.js';
import type { SimulatedGasFeeLimitsSchema } from './validators/batch-sell.js';
import type { BatchSellTradesResponseSchema } from './validators/batch-sell.js';
import type { BridgeAssetSchema } from './validators/bridge-asset.js';
import type {
ChainConfigurationSchema,
ChainRankingSchema,
Expand Down Expand Up @@ -99,12 +98,6 @@ export enum SortOrder {
ETA_ASC = 'time_descending',
}

/**
* This is the interface for the asset object returned by the bridge-api
* This type is used in the QuoteResponse and in the fetchBridgeTokens response
*/
export type BridgeAsset = Infer<typeof BridgeAssetSchema>;

/**
* This is the interface for the token object used in the extension client
* In addition to the {@link BridgeAsset} fields, it includes balance information
Expand Down
7 changes: 2 additions & 5 deletions packages/bridge-controller/src/utils/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@ import {
SYMBOL_TO_SLIP44_MAP,
} from '../constants/tokens.js';
import type { SupportedSwapsNativeCurrencySymbols } from '../constants/tokens.js';
import type {
BridgeAsset,
BridgeControllerState,
GenericQuoteRequest,
} from '../types.js';
import type { BridgeControllerState, GenericQuoteRequest } from '../types.js';
import { ChainId } from '../types.js';
import type { BridgeAsset } from '../validators/bridge-asset.js';
import type { QuoteResponseV1 } from '../validators/quote-response-v1.js';
import type { TxData } from '../validators/trade.js';
import {
Expand Down
3 changes: 0 additions & 3 deletions packages/bridge-controller/src/utils/caip-formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ export const formatChainIdToCaip = (
return TrxScope.Mainnet;
}
if (isStellarChainId(chainId)) {
if (chainId === XlmScope.Testnet) {
return XlmScope.Testnet;
}
return XlmScope.Pubnet;
}
return toEvmCaipChainId(numberToHex(Number(chainId)));
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge-controller/src/utils/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import type {
FetchFunction,
GenericQuoteRequest,
QuoteRequest,
BridgeAsset,
TokenFeature,
QuoteStreamCompleteData,
BatchSellTradesRequest,
BatchSellTradesResponse,
} from '../types.js';
import { validateBatchSellTradesResponse } from '../validators/batch-sell.js';
import type { BridgeAsset } from '../validators/bridge-asset.js';
import { validateBridgeAsset } from '../validators/bridge-asset.js';
import type { FeatureId } from '../validators/feature-flags.js';
import type { QuoteResponseV1 } from '../validators/quote-response-v1.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import type {
ExchangeRate,
NonEvmFees,
DeepPartial,
BridgeAsset,
} from '../../types.js';
import type { BridgeAsset } from '../../validators/bridge-asset.js';
import { FloatStringSchema } from '../../validators/number.js';
import type { QuoteResponseV1 as QuoteResponse } from '../../validators/quote-response-v1.js';
import { TxData } from '../../validators/trade.js';
Expand Down
33 changes: 33 additions & 0 deletions packages/bridge-controller/src/validators/amount-and-asset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
type,
optional,
nullable,
union,
string,
Infer,
} from '@metamask/superstruct';

import { BridgeAssetV2Schema } from './bridge-asset.js';
import { NumberStringSchema, FloatStringSchema } from './number.js';

export const AmountsAndAssetSchema = type({
/*
* The atomic amount of the asset
* @example "1000000000000000000"
*/
amount: NumberStringSchema,
/*
* The normalized amount of the asset
* @example "1.5"
*/
normalizedAmount: optional(FloatStringSchema),
asset: BridgeAssetV2Schema,
// TODO remove string and fix usd in backend
usd: optional(nullable(union([FloatStringSchema, string()]))),
/*
* The value of the asset in the currency, calculated based on usd value
* @example "0.15"
*/
valueInCurrency: optional(nullable(FloatStringSchema)),
});
export type AmountsAndAsset = Infer<typeof AmountsAndAssetSchema>;
92 changes: 92 additions & 0 deletions packages/bridge-controller/src/validators/bridge-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,81 @@ import {
optional,
nullable,
is,
intersection,
enums,
array,
boolean,
} from '@metamask/superstruct';
import type { Infer } from '@metamask/superstruct';
import { CaipAssetTypeStruct } from '@metamask/utils';

export const ChainIdSchema = number();

export const MinimalAssetSchema = type({
/**
* Case-sensitive for non-EVM chains, case-insensitive for EVM chains
*/
assetId: CaipAssetTypeStruct,
/**
* The symbol of token object
*/
symbol: string(),
/**
* The name for the network
*/
name: string(),
decimals: number(),
});

export type MinimalAsset = Infer<typeof MinimalAssetSchema>;

export enum BridgeAssetSecurityDataType {
INFO = 'Info',
BENIGN = 'Benign',
VERIFIED = 'Verified',
WARNING = 'Warning',
SPAM = 'Spam',
MALICIOUS = 'Malicious',
}

const BridgeAssetSecurityData = type({
isVerified: optional(boolean()),
securityData: optional(
type({
type: enums(Object.values(BridgeAssetSecurityDataType)),
metadata: optional(
type({
features: array(
type({
featureId: string(),
type: enums(Object.values(BridgeAssetSecurityDataType)),
description: string(),
}),
),
}),
),
}),
),
});

export const BridgeAssetV2Schema = intersection([
MinimalAssetSchema,
BridgeAssetSecurityData,
type({
/**
* URL for token icon
*/
iconUrl: nullable(optional(string())),
noFee: optional(
type({
isDestination: nullable(optional(boolean())),
isSource: nullable(optional(boolean())),
}),
),
}),
]);
export type BridgeAssetV2 = Infer<typeof BridgeAssetV2Schema>;

export const BridgeAssetSchema = type({
/**
* The chainId of the token
Expand Down Expand Up @@ -43,8 +112,31 @@ export const BridgeAssetSchema = type({
iconUrl: optional(nullable(string())),
});

/**
* This is the interface for the asset object returned by the bridge-api
* This type is used in the QuoteResponse and in the fetchBridgeTokens response
*
* @deprecated Avoid introducing new code that uses this function. Use BridgeAssetV2 instead
*/
export type BridgeAsset = Infer<typeof BridgeAssetSchema>;

/**
* Validates a token object from the bridge-api
*
* @deprecated Avoid introducing new code that uses this function. Use BridgeAssetV2 instead
*
* @param data - The data to validate
* @returns Whether the data satisfies the {@link BridgeAssetSchema}
*/
export const validateBridgeAsset = (
data: unknown,
): data is Infer<typeof BridgeAssetSchema> => {
return is(data, BridgeAssetSchema);
};

/* istanbul ignore next */
export const validateBridgeAssetV2 = (
data: unknown,
): data is Infer<typeof BridgeAssetV2Schema> => {
return is(data, BridgeAssetV2Schema);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { KnownCaipNamespace } from '@metamask/utils';

import { mockBridgeQuotesErc20Erc20V2Migration } from '../../tests/mock-quotes-erc20-erc20-migration-v2.js';
import { validateQuoteResponse } from './quote-response.js';

describe('quote-response-v2', () => {
describe('validateQuoteResponse', () => {
it('should return a validation error for an invalid quote response', () => {
const quoteResponse = {
quote: {
requestId: '123',
},
};

expect(() =>
validateQuoteResponse(quoteResponse),
).toThrowErrorMatchingInlineSnapshot(
`"At path: quote.src -- Expected an object, but received: undefined"`,
);
});

it('should validate a valid quote response', () => {
const quoteResponse = mockBridgeQuotesErc20Erc20V2Migration[0];
expect(validateQuoteResponse(quoteResponse)).toBe(true);
});

it('should return false when quote namespace is not supported', () => {
const quoteResponse = mockBridgeQuotesErc20Erc20V2Migration[0];
quoteResponse.quote.src.asset.assetId = `${KnownCaipNamespace.Wallet}:123/token:test`;
expect(validateQuoteResponse(quoteResponse)).toBe(false);
});
});
});
Loading