accounts 0.8.6 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/core/Schema.d.ts +2 -0
- package/dist/core/Schema.d.ts.map +1 -1
- package/dist/core/Storage.js +1 -1
- package/dist/core/Storage.js.map +1 -1
- package/dist/core/zod/rpc.d.ts +2 -0
- package/dist/core/zod/rpc.d.ts.map +1 -1
- package/dist/core/zod/rpc.js +2 -0
- package/dist/core/zod/rpc.js.map +1 -1
- package/dist/server/internal/handlers/exchange.d.ts.map +1 -1
- package/dist/server/internal/handlers/exchange.js +5 -19
- package/dist/server/internal/handlers/exchange.js.map +1 -1
- package/dist/server/internal/handlers/relay.d.ts +20 -12
- package/dist/server/internal/handlers/relay.d.ts.map +1 -1
- package/dist/server/internal/handlers/relay.js +152 -84
- package/dist/server/internal/handlers/relay.js.map +1 -1
- package/dist/server/internal/kv.d.ts +7 -4
- package/dist/server/internal/kv.d.ts.map +1 -1
- package/dist/server/internal/kv.js +70 -11
- package/dist/server/internal/kv.js.map +1 -1
- package/dist/server/internal/tokenlist.d.ts +53 -0
- package/dist/server/internal/tokenlist.d.ts.map +1 -0
- package/dist/server/internal/tokenlist.js +38 -0
- package/dist/server/internal/tokenlist.js.map +1 -0
- package/package.json +1 -1
- package/src/core/Storage.ts +1 -1
- package/src/core/zod/rpc.ts +2 -0
- package/src/server/internal/handlers/exchange.ts +7 -31
- package/src/server/internal/handlers/relay.test.ts +61 -15
- package/src/server/internal/handlers/relay.ts +184 -94
- package/src/server/internal/kv.test.ts +129 -0
- package/src/server/internal/kv.ts +76 -10
- package/src/server/internal/tokenlist.ts +91 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Address } from 'viem';
|
|
2
|
+
import type * as Kv from '../Kv.js';
|
|
3
|
+
/** A single tokenlist entry. */
|
|
4
|
+
export type Token = {
|
|
5
|
+
/** Token address. */
|
|
6
|
+
address: Address;
|
|
7
|
+
/** Token decimals. */
|
|
8
|
+
decimals: number;
|
|
9
|
+
/** Token logo URI. */
|
|
10
|
+
logoUri?: string | undefined;
|
|
11
|
+
/** Token name. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Token symbol. */
|
|
14
|
+
symbol: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Fetches the verified tokenlist for `chainId`. Reads through `kv` so
|
|
18
|
+
* concurrent callers in the same scope share a single upstream request,
|
|
19
|
+
* and so independent handlers (e.g. `Handler.relay` + `Handler.exchange`)
|
|
20
|
+
* reuse the same KV cache key.
|
|
21
|
+
*
|
|
22
|
+
* Returns an empty list on any non-OK response — callers should fall back
|
|
23
|
+
* to chain-supplied behavior rather than treating an empty list as fatal.
|
|
24
|
+
*
|
|
25
|
+
* @param chainId - Chain id to fetch the tokenlist for.
|
|
26
|
+
* @param kv - Kv used to cache responses across requests.
|
|
27
|
+
* @param options - Options.
|
|
28
|
+
* @returns Tokens for the chain.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetch(chainId: number, kv: Kv.Kv, options?: fetch.Options): Promise<readonly Token[]>;
|
|
31
|
+
export declare namespace fetch {
|
|
32
|
+
/** Options for `fetch()`. */
|
|
33
|
+
type Options = {
|
|
34
|
+
/**
|
|
35
|
+
* Base URL of the tokenlist service.
|
|
36
|
+
* @default 'https://tokenlist.tempo.xyz'
|
|
37
|
+
*/
|
|
38
|
+
baseUrl?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* TTL in seconds for the cached response.
|
|
41
|
+
* @default 600 (10 minutes)
|
|
42
|
+
*/
|
|
43
|
+
cacheTtl?: number | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Override resolver. When provided, the cache hit path is unchanged but
|
|
46
|
+
* cache misses route through this function instead of fetching the
|
|
47
|
+
* default `tokenlist.tempo.xyz` URL. Useful for tests or for vendoring
|
|
48
|
+
* a curated list per app.
|
|
49
|
+
*/
|
|
50
|
+
resolver?: ((chainId: number) => readonly Token[] | Promise<readonly Token[]>) | undefined;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=tokenlist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenlist.d.ts","sourceRoot":"","sources":["../../../src/server/internal/tokenlist.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,KAAK,EAAE,MAAM,UAAU,CAAA;AASnC,gCAAgC;AAChC,MAAM,MAAM,KAAK,GAAG;IAClB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,oBAAoB;IACpB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,KAAK,CACzB,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,EAAE,CAAC,EAAE,EACT,OAAO,GAAE,KAAK,CAAC,OAAY,GAC1B,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAQ3B;AAED,MAAM,CAAC,OAAO,WAAW,KAAK,CAAC;IAC7B,6BAA6B;IAC7B,KAAK,OAAO,GAAG;QACb;;;WAGG;QACH,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QAC5B;;;WAGG;QACH,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;QAC7B;;;;;WAKG;QACH,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,KAAK,EAAE,GAAG,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;KAC3F,CAAA;CACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cached } from './kv.js';
|
|
2
|
+
/** Default cache TTL in seconds (10 minutes) for tokenlist responses. */
|
|
3
|
+
const defaultCacheTtl = 10 * 60;
|
|
4
|
+
/** Default base URL for the verified tokenlist service. */
|
|
5
|
+
const defaultBaseUrl = 'https://tokenlist.tempo.xyz';
|
|
6
|
+
/**
|
|
7
|
+
* Fetches the verified tokenlist for `chainId`. Reads through `kv` so
|
|
8
|
+
* concurrent callers in the same scope share a single upstream request,
|
|
9
|
+
* and so independent handlers (e.g. `Handler.relay` + `Handler.exchange`)
|
|
10
|
+
* reuse the same KV cache key.
|
|
11
|
+
*
|
|
12
|
+
* Returns an empty list on any non-OK response — callers should fall back
|
|
13
|
+
* to chain-supplied behavior rather than treating an empty list as fatal.
|
|
14
|
+
*
|
|
15
|
+
* @param chainId - Chain id to fetch the tokenlist for.
|
|
16
|
+
* @param kv - Kv used to cache responses across requests.
|
|
17
|
+
* @param options - Options.
|
|
18
|
+
* @returns Tokens for the chain.
|
|
19
|
+
*/
|
|
20
|
+
export async function fetch(chainId, kv, options = {}) {
|
|
21
|
+
const { baseUrl = defaultBaseUrl, cacheTtl = defaultCacheTtl, resolver } = options;
|
|
22
|
+
return cached(kv, `tokenlist:${chainId}`, async () => (resolver ? resolver(chainId) : fetchUncached(chainId, baseUrl)), { ttl: cacheTtl });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Default fetcher — resolves the verified tokenlist for `chainId` directly
|
|
26
|
+
* from `tokenlist.tempo.xyz`. Returns an empty list on any non-OK response.
|
|
27
|
+
*/
|
|
28
|
+
async function fetchUncached(chainId, baseUrl) {
|
|
29
|
+
const response = await globalThis.fetch(`${baseUrl}/list/${chainId}`);
|
|
30
|
+
if (!response.ok)
|
|
31
|
+
return [];
|
|
32
|
+
const data = (await response.json());
|
|
33
|
+
return data.tokens.map(({ logoURI, ...token }) => ({
|
|
34
|
+
...token,
|
|
35
|
+
logoUri: token.logoUri ?? logoURI,
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=tokenlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenlist.js","sourceRoot":"","sources":["../../../src/server/internal/tokenlist.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAEhC,yEAAyE;AACzE,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,CAAA;AAE/B,2DAA2D;AAC3D,MAAM,cAAc,GAAG,6BAA6B,CAAA;AAgBpD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,OAAe,EACf,EAAS,EACT,UAAyB,EAAE;IAE3B,MAAM,EAAE,OAAO,GAAG,cAAc,EAAE,QAAQ,GAAG,eAAe,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAClF,OAAO,MAAM,CACX,EAAE,EACF,aAAa,OAAO,EAAE,EACtB,KAAK,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAC5E,EAAE,GAAG,EAAE,QAAQ,EAAE,CAClB,CAAA;AACH,CAAC;AAyBD;;;GAGG;AACH,KAAK,UAAU,aAAa,CAAC,OAAe,EAAE,OAAe;IAC3D,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,OAAO,SAAS,OAAO,EAAE,CAAC,CAAA;IACrE,IAAI,CAAC,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,CAAA;IAC3B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAElC,CAAA;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACjD,GAAG,KAAK;QACR,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,OAAO;KAClC,CAAC,CAAC,CAAA;AACL,CAAC"}
|
package/package.json
CHANGED
package/src/core/Storage.ts
CHANGED
|
@@ -37,7 +37,7 @@ export function combine(...storages: readonly Storage[]): Storage {
|
|
|
37
37
|
return {
|
|
38
38
|
async getItem<value>(name: string) {
|
|
39
39
|
const results = await Promise.allSettled(storages.map((x) => x.getItem<value>(name)))
|
|
40
|
-
const result = results.find((x) => x.status === 'fulfilled' && x.value
|
|
40
|
+
const result = results.find((x) => x.status === 'fulfilled' && x.value != null)
|
|
41
41
|
if (result?.status !== 'fulfilled') return null
|
|
42
42
|
return result.value as value
|
|
43
43
|
},
|
package/src/core/zod/rpc.ts
CHANGED
|
@@ -551,6 +551,8 @@ export namespace wallet_send {
|
|
|
551
551
|
method: z.literal('wallet_send'),
|
|
552
552
|
params: z.optional(z.readonly(z.tuple([parameters]))),
|
|
553
553
|
returns: z.object({
|
|
554
|
+
/** Chain id the send is to. */
|
|
555
|
+
chainId: u.number(),
|
|
554
556
|
/** Receipt of the submitted send. */
|
|
555
557
|
receipt,
|
|
556
558
|
}),
|
|
@@ -19,6 +19,7 @@ import { type Handler, from } from '../../Handler.js'
|
|
|
19
19
|
import * as Kv from '../../Kv.js'
|
|
20
20
|
import * as Hono from '../hono.js'
|
|
21
21
|
import { cached } from '../kv.js'
|
|
22
|
+
import * as Tokenlist from '../tokenlist.js'
|
|
22
23
|
|
|
23
24
|
/** Default cache TTL in seconds (10 minutes). */
|
|
24
25
|
const defaultCacheTtl = 10 * 60
|
|
@@ -144,11 +145,14 @@ export function exchange<const path extends string = '/exchange'>(
|
|
|
144
145
|
kv = Kv.memory(),
|
|
145
146
|
onRequest,
|
|
146
147
|
path = '/exchange' as path,
|
|
147
|
-
resolveTokens
|
|
148
|
+
resolveTokens,
|
|
148
149
|
transports = {},
|
|
149
150
|
...rest
|
|
150
151
|
} = options
|
|
151
152
|
|
|
153
|
+
const getTokens = (chainId: number) =>
|
|
154
|
+
Tokenlist.fetch(chainId, kv, { cacheTtl, resolver: resolveTokens })
|
|
155
|
+
|
|
152
156
|
const clients = new Map<number, Client>()
|
|
153
157
|
for (const chain of chains) {
|
|
154
158
|
const transport = transports[chain.id] ?? http()
|
|
@@ -170,12 +174,7 @@ export function exchange<const path extends string = '/exchange'>(
|
|
|
170
174
|
const chain = chains.find((c) => c.id === chainId)
|
|
171
175
|
if (!chain) throw new Error(`Chain ${chainId} is not supported.`)
|
|
172
176
|
|
|
173
|
-
const tokens = await
|
|
174
|
-
kv,
|
|
175
|
-
`tokenlist:${chain.id}`,
|
|
176
|
-
async () => resolveTokens(chain.id),
|
|
177
|
-
{ ttl: cacheTtl },
|
|
178
|
-
)
|
|
177
|
+
const tokens = await getTokens(chain.id)
|
|
179
178
|
|
|
180
179
|
// Cache for `cacheTtl` and allow stale-while-revalidate for an
|
|
181
180
|
// additional full TTL window so CDNs/browsers can serve immediately
|
|
@@ -202,12 +201,7 @@ export function exchange<const path extends string = '/exchange'>(
|
|
|
202
201
|
const client = clients.get(chain.id)!
|
|
203
202
|
|
|
204
203
|
// Resolve `token` and `pairToken` to addresses + metadata in parallel.
|
|
205
|
-
const tokens = await
|
|
206
|
-
kv,
|
|
207
|
-
`tokenlist:${chain.id}`,
|
|
208
|
-
async () => resolveTokens(chain.id),
|
|
209
|
-
{ ttl: cacheTtl },
|
|
210
|
-
)
|
|
204
|
+
const tokens = await getTokens(chain.id)
|
|
211
205
|
const [tokenInfo, pairTokenInfo] = await Promise.all([
|
|
212
206
|
resolveToken(client, { kv, ref: token, tokens }),
|
|
213
207
|
resolveToken(client, { kv, ref: pairToken, tokens }),
|
|
@@ -478,21 +472,3 @@ function applySlippage(amount: bigint, slippage: number, dir: 'up' | 'down') {
|
|
|
478
472
|
function toCall(call: { data: Hex.Hex; to: Address }) {
|
|
479
473
|
return { data: call.data, to: call.to }
|
|
480
474
|
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Default `resolveTokens` implementation. Fetches the verified token list for
|
|
484
|
-
* `chainId` from `https://tokenlist.tempo.xyz/list/:chainId`. Returns an empty
|
|
485
|
-
* list on any non-OK response.
|
|
486
|
-
*/
|
|
487
|
-
async function defaultResolveTokens(chainId: number): Promise<readonly Token[]> {
|
|
488
|
-
// TODO: deprecate tokenlist.tempo.xyz when TIP-1026 enshrined.
|
|
489
|
-
const response = await fetch(`https://tokenlist.tempo.xyz/list/${chainId}`)
|
|
490
|
-
if (!response.ok) return []
|
|
491
|
-
const data = (await response.json()) as {
|
|
492
|
-
tokens: readonly (Token & { logoURI?: string })[]
|
|
493
|
-
}
|
|
494
|
-
return data.tokens.map(({ logoURI, ...token }) => ({
|
|
495
|
-
...token,
|
|
496
|
-
logoUri: token.logoUri ?? logoURI,
|
|
497
|
-
}))
|
|
498
|
-
}
|
|
@@ -14,6 +14,18 @@ const userAccount = accounts[9]!
|
|
|
14
14
|
const feePayerAccount = accounts[0]!
|
|
15
15
|
const recipient = accounts[7]!
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Tokens the relay handler probes for fee-token resolution. The default
|
|
19
|
+
* `resolveTokens` fetches `tokenlist.tempo.xyz`, which doesn't know about
|
|
20
|
+
* the localnet chain — so tests inject this list explicitly.
|
|
21
|
+
*/
|
|
22
|
+
const localnetTokens = [
|
|
23
|
+
{ address: '0x20c0000000000000000000000000000000000000', decimals: 6, name: 'pathUSD', symbol: 'pathUSD' },
|
|
24
|
+
{ address: '0x20c0000000000000000000000000000000000001', decimals: 6, name: 'alphaUSD', symbol: 'alphaUSD' },
|
|
25
|
+
{ address: '0x20c0000000000000000000000000000000000002', decimals: 6, name: 'betaUSD', symbol: 'betaUSD' },
|
|
26
|
+
{ address: '0x20c0000000000000000000000000000000000003', decimals: 6, name: 'thetaUSD', symbol: 'thetaUSD' },
|
|
27
|
+
] as const
|
|
28
|
+
|
|
17
29
|
/** Case-insensitive lookup into balanceDiffs keyed by address. */
|
|
18
30
|
function findDiffs(
|
|
19
31
|
balanceDiffs: Capabilities.FillTransactionCapabilities['balanceDiffs'],
|
|
@@ -1110,6 +1122,7 @@ describe('behavior: path A — guaranteed sponsorship (no validate)', () => {
|
|
|
1110
1122
|
relay({
|
|
1111
1123
|
chains: [chain],
|
|
1112
1124
|
features: 'all',
|
|
1125
|
+
resolveTokens: () => localnetTokens,
|
|
1113
1126
|
transports: { [chain.id]: http() },
|
|
1114
1127
|
feePayer: {
|
|
1115
1128
|
account: feePayerAccount,
|
|
@@ -1335,6 +1348,7 @@ describe('behavior: fee token resolution', () => {
|
|
|
1335
1348
|
relay({
|
|
1336
1349
|
chains: [chain],
|
|
1337
1350
|
features: 'all',
|
|
1351
|
+
resolveTokens: () => localnetTokens,
|
|
1338
1352
|
transports: { [chain.id]: http() },
|
|
1339
1353
|
}).listener,
|
|
1340
1354
|
)
|
|
@@ -1366,30 +1380,62 @@ describe('behavior: fee token resolution', () => {
|
|
|
1366
1380
|
})
|
|
1367
1381
|
|
|
1368
1382
|
test('behavior: resolves to highest-balance token from token list', async () => {
|
|
1369
|
-
//
|
|
1383
|
+
// Create a fresh TIP20 token "betaUsd" so we can test highest-balance
|
|
1384
|
+
// selection without depending on a pre-deployed second token.
|
|
1370
1385
|
const freshAccount = accounts[5]!
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
to: freshAccount.address,
|
|
1386
|
+
const rpc = getClient({ account: accounts[0]! })
|
|
1387
|
+
const { token: betaUsd } = await Actions.token.createSync(rpc, {
|
|
1388
|
+
name: 'BetaUSD',
|
|
1389
|
+
symbol: 'BetaUSD',
|
|
1390
|
+
currency: 'USD',
|
|
1391
|
+
quoteToken: addresses.alphaUsd,
|
|
1378
1392
|
})
|
|
1379
|
-
await
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1393
|
+
await sendTransactionSync(rpc, {
|
|
1394
|
+
calls: [
|
|
1395
|
+
Actions.token.grantRoles.call({
|
|
1396
|
+
token: betaUsd,
|
|
1397
|
+
role: 'issuer',
|
|
1398
|
+
to: rpc.account!.address,
|
|
1399
|
+
}),
|
|
1400
|
+
// Mint different amounts of two tokens to a fresh account. Amounts
|
|
1401
|
+
// must be large enough to cover gas, otherwise autoSwap fires.
|
|
1402
|
+
Actions.token.mint.call({
|
|
1403
|
+
token: addresses.alphaUsd,
|
|
1404
|
+
amount: parseUnits('100', 6),
|
|
1405
|
+
to: freshAccount.address,
|
|
1406
|
+
}),
|
|
1407
|
+
Actions.token.mint.call({
|
|
1408
|
+
token: betaUsd,
|
|
1409
|
+
amount: parseUnits('500', 6),
|
|
1410
|
+
to: freshAccount.address,
|
|
1411
|
+
}),
|
|
1412
|
+
],
|
|
1384
1413
|
})
|
|
1385
1414
|
|
|
1386
|
-
|
|
1415
|
+
// Spin up a relay whose tokenlist includes the freshly created betaUsd
|
|
1416
|
+
// alongside the localnet defaults so the highest-balance resolver can
|
|
1417
|
+
// see both.
|
|
1418
|
+
const customServer = await createServer(
|
|
1419
|
+
relay({
|
|
1420
|
+
chains: [chain],
|
|
1421
|
+
features: 'all',
|
|
1422
|
+
resolveTokens: () => [
|
|
1423
|
+
...localnetTokens,
|
|
1424
|
+
{ address: betaUsd, decimals: 6, name: 'BetaUSD', symbol: 'BetaUSD' },
|
|
1425
|
+
],
|
|
1426
|
+
transports: { [chain.id]: http() },
|
|
1427
|
+
}).listener,
|
|
1428
|
+
)
|
|
1429
|
+
const customClient = getClient({ transport: http(customServer.url) })
|
|
1430
|
+
|
|
1431
|
+
const { transaction } = await fillTransaction(customClient, {
|
|
1387
1432
|
account: freshAccount.address,
|
|
1388
1433
|
calls: [transferCall()],
|
|
1389
1434
|
})
|
|
1435
|
+
customServer.close()
|
|
1390
1436
|
|
|
1391
1437
|
// betaUsd has higher balance (500 > 100).
|
|
1392
|
-
expect(transaction.feeToken).toBe(betaUsd)
|
|
1438
|
+
expect(transaction.feeToken?.toLowerCase()).toBe(betaUsd.toLowerCase())
|
|
1393
1439
|
})
|
|
1394
1440
|
|
|
1395
1441
|
test('behavior: falls back to pathUSD when no preference or balances', async () => {
|