-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathrelayFixedSpread.ts
More file actions
275 lines (248 loc) · 8.2 KB
/
Copy pathrelayFixedSpread.ts
File metadata and controls
275 lines (248 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import { Hex, isStrictHexString } from '@metamask/utils';
/**
* Types + helpers for the `confirmations_relay_fixed_spread` remote feature flag.
*/
export interface RelayFixedSpreadRoute {
sourceChain: Hex;
sourceToken: Hex;
targetChain: Hex;
targetToken: Hex;
}
export interface RelayFixedSpreadConfig {
routes: RelayFixedSpreadRoute[];
}
export const EMPTY_RELAY_FIXED_SPREAD_CONFIG: RelayFixedSpreadConfig = {
routes: [],
};
const EXPECTED_FORMAT =
'Expected format: {"chains":{"eth":"0x1"},"tokens":{"musd":"0x..."},"routes":[["eth","musd","eth","musd"]]}';
const isStringRecord = (value: unknown): value is Record<string, unknown> =>
!!value && typeof value === 'object' && !Array.isArray(value);
const buildAliasMap = (raw: unknown): Map<string, Hex> | null => {
if (!isStringRecord(raw)) return null;
const map = new Map<string, Hex>();
for (const [alias, value] of Object.entries(raw)) {
if (!isStrictHexString(value)) continue;
map.set(alias, value.toLowerCase() as Hex);
}
return map;
};
interface ResolvedRouteBase {
srcTokenAlias: string;
dstTokenAlias: string;
sourceChain: Hex;
sourceToken: Hex;
targetChain: Hex;
targetToken: Hex;
}
const resolveRouteBase = (
tuple: unknown,
chains: Map<string, Hex>,
tokens: Map<string, Hex>,
): ResolvedRouteBase | null => {
if (!Array.isArray(tuple) || tuple.length !== 4) return null;
const [srcChainAlias, srcTokenAlias, dstChainAlias, dstTokenAlias] = tuple;
if (
typeof srcChainAlias !== 'string' ||
typeof srcTokenAlias !== 'string' ||
typeof dstChainAlias !== 'string' ||
typeof dstTokenAlias !== 'string'
) {
return null;
}
const sourceChain = chains.get(srcChainAlias);
const sourceToken = tokens.get(srcTokenAlias);
const targetChain = chains.get(dstChainAlias);
const targetToken = tokens.get(dstTokenAlias);
if (!sourceChain || !sourceToken || !targetChain || !targetToken) {
return null;
}
return {
srcTokenAlias,
dstTokenAlias,
sourceChain,
sourceToken,
targetChain,
targetToken,
};
};
const resolveRoute = (
tuple: unknown,
chains: Map<string, Hex>,
tokens: Map<string, Hex>,
): RelayFixedSpreadRoute | null => {
const base = resolveRouteBase(tuple, chains, tokens);
if (!base) return null;
return {
sourceChain: base.sourceChain,
sourceToken: base.sourceToken,
targetChain: base.targetChain,
targetToken: base.targetToken,
};
};
const tryJsonParse = (raw: string): unknown => {
try {
return JSON.parse(raw);
} catch {
return null;
}
};
const extractRoutes = (value: unknown): RelayFixedSpreadRoute[] | null => {
if (!isStringRecord(value)) return null;
const chains = buildAliasMap(value.chains);
const tokens = buildAliasMap(value.tokens);
const routes = value.routes;
if (!chains || !tokens || !Array.isArray(routes)) return null;
return routes
.map((tuple) => resolveRoute(tuple, chains, tokens))
.filter((route): route is RelayFixedSpreadRoute => route !== null);
};
/**
* Parses a `confirmations_relay_fixed_spread` remote flag value into a
* normalised, lowercased {@link RelayFixedSpreadConfig}. Invalid entries are
* silently dropped; a fully invalid payload yields {@link EMPTY_RELAY_FIXED_SPREAD_CONFIG}.
*
* Empty/missing payloads are the desired "feature off" state and produce no
* warning. Local override during development is done via the existing
* `OVERRIDE_REMOTE_FEATURE_FLAGS` mechanism rather than a dedicated env var.
*/
export const getRelayFixedSpreadFromConfig = (
remoteValue: unknown,
remoteFlagName: string,
): RelayFixedSpreadConfig => {
if (remoteValue === undefined || remoteValue === null || remoteValue === '') {
return EMPTY_RELAY_FIXED_SPREAD_CONFIG;
}
const parsed =
typeof remoteValue === 'string' ? tryJsonParse(remoteValue) : remoteValue;
if (parsed === null) {
console.warn(`Failed to parse remote ${remoteFlagName}: invalid JSON.`);
return EMPTY_RELAY_FIXED_SPREAD_CONFIG;
}
const routes = extractRoutes(parsed);
if (routes === null) {
console.warn(
`Remote ${remoteFlagName} produced invalid structure. ${EXPECTED_FORMAT}`,
);
return EMPTY_RELAY_FIXED_SPREAD_CONFIG;
}
return { routes };
};
/**
* Like {@link RelayFixedSpreadRoute} but preserves the original token alias keys
* (e.g. `eth_usdc`) alongside the resolved addresses. Consumers that need the
* human-facing token symbols (which {@link getRelayFixedSpreadFromConfig}
* discards) use this shape.
*/
export interface RelayFixedSpreadAliasRoute {
sourceChain: Hex;
sourceTokenAlias: string;
sourceToken: Hex;
targetChain: Hex;
targetTokenAlias: string;
targetToken: Hex;
}
const resolveRouteWithSymbols = (
tuple: unknown,
chains: Map<string, Hex>,
tokens: Map<string, Hex>,
): RelayFixedSpreadAliasRoute | null => {
const base = resolveRouteBase(tuple, chains, tokens);
if (!base) return null;
return {
sourceChain: base.sourceChain,
sourceTokenAlias: base.srcTokenAlias,
sourceToken: base.sourceToken,
targetChain: base.targetChain,
targetTokenAlias: base.dstTokenAlias,
targetToken: base.targetToken,
};
};
/**
* Parses a `confirmations_relay_fixed_spread` remote flag value into a list of
* routes that retain their token alias keys. Unlike
* {@link getRelayFixedSpreadFromConfig}, the alias (and therefore the token
* symbol) is preserved for consumers that render token symbols. Invalid entries
* are dropped; an invalid/empty payload yields an empty array.
*/
export const getRelayFixedSpreadRoutesWithSymbols = (
remoteValue: unknown,
remoteFlagName: string,
): RelayFixedSpreadAliasRoute[] => {
if (remoteValue === undefined || remoteValue === null || remoteValue === '') {
return [];
}
const parsed =
typeof remoteValue === 'string' ? tryJsonParse(remoteValue) : remoteValue;
if (!isStringRecord(parsed)) {
console.warn(`Failed to parse remote ${remoteFlagName}: invalid JSON.`);
return [];
}
const chains = buildAliasMap(parsed.chains);
const tokens = buildAliasMap(parsed.tokens);
const routes = parsed.routes;
if (!chains || !tokens || !Array.isArray(routes)) {
console.warn(
`Remote ${remoteFlagName} produced invalid structure. ${EXPECTED_FORMAT}`,
);
return [];
}
return routes
.map((tuple) => resolveRouteWithSymbols(tuple, chains, tokens))
.filter((route): route is RelayFixedSpreadAliasRoute => route !== null);
};
const addressesEqual = (
a: string | undefined,
b: string | undefined,
): boolean => !!a && !!b && a.toLowerCase() === b.toLowerCase();
export interface RouteEndpoint {
chainId: string;
address: string;
}
/**
* Returns true when at least one route in {@link config} has a source matching
* `(chainId, address)`. Used by the "no fee" tag when the consumer does not
* yet have a directional target.
*/
export const isSubsidizedSource = (
config: RelayFixedSpreadConfig,
source: RouteEndpoint,
): boolean =>
config.routes.some(
(route) =>
addressesEqual(route.sourceChain, source.chainId) &&
addressesEqual(route.sourceToken, source.address),
);
/**
* Returns true when {@link config} contains an exact `(source → target)` route.
* Reserved for callsites that know the destination of the transaction being
* built; not yet wired in to the MM Pay picker.
*/
/**
* Returns true when the given token appears in any route as either source or
* target. Used to decide whether a pay token is a stablecoin for prefill
* percentage logic.
*/
export const isRouteToken = (
config: RelayFixedSpreadConfig,
endpoint: RouteEndpoint,
): boolean =>
config.routes.some(
(route) =>
(addressesEqual(route.sourceChain, endpoint.chainId) &&
addressesEqual(route.sourceToken, endpoint.address)) ||
(addressesEqual(route.targetChain, endpoint.chainId) &&
addressesEqual(route.targetToken, endpoint.address)),
);
export const isSubsidizedRoute = (
config: RelayFixedSpreadConfig,
source: RouteEndpoint,
target: RouteEndpoint,
): boolean =>
config.routes.some(
(route) =>
addressesEqual(route.sourceChain, source.chainId) &&
addressesEqual(route.sourceToken, source.address) &&
addressesEqual(route.targetChain, target.chainId) &&
addressesEqual(route.targetToken, target.address),
);