SPB Git

Repository Public

debridge.test.ts 23.5 KB 252 lines typescript

1// deBridge (DLN) adapter against real, unmodified answers of GET https://dln.debridge.finance/v1.0/dln/order/create-tx2// recorded on 2026-11-10 (fixtures/debridge/*.json) with public addresses. Nothing in those answers was signed or submitted.3import { readFileSync } from 'node:fs';4import { describe, expect, it } from 'vitest';5import { NETWORKS } from '@spw/core';6import { checkQuote, DEFAULT_POLICY, httpClient, ProviderError, ProviderRuntime, type AssetRef, type ProviderContext, type SwapQuoteRequest } from '../src/index.ts';7import { createProviders } from '../src/providers/index.ts';8import { createDebridgeProvider, DLN_API, DLN_CHAINS } from '../src/providers/debridge.ts';910const fx = (name: string): any => JSON.parse(readFileSync(new URL(`./fixtures/debridge/${name}.json`, import.meta.url), 'utf8'));11const tok = (network: string, symbol: string): AssetRef => ({ network, asset: NETWORKS[network]!.tokens.find((t) => t.symbol === symbol)!.id });12const nat = (network: string): AssetRef => ({ network, asset: 'native' });13const EVM = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', SOL = '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', TRON = 'TV6MuMXfmLbBqPZvBHdwFsDnQeVfnmiuSi';14const DLN_SOURCE = '0xeF4fB24aD0916217251F553c0596F8Edc630EB66', DLN_DESTINATION = '0xE7351Fd770A37282b91D153Ee690B63579D6dd7f', ROUTER = '0x663DC15D3C1aC63ff12E45Ab68FeA3F0a883C251';15const NOW = 1_794_000_000_000;1617const CASES: Record<string, SwapQuoteRequest> = {18  'base-usdc-sol-usdc': { from: tok('base', 'USDC'), to: tok('solana', 'USDC'), amountIn: 25_000_000n, sender: EVM, recipient: SOL, slippageBps: 50 },19  'eth-link-sol-sol': { from: tok('ethereum', 'LINK'), to: nat('solana'), amountIn: 3_000_000_000_000_000_000n, sender: EVM, recipient: SOL, slippageBps: 50 },20  'eth-eth-sol-sol': { from: nat('ethereum'), to: nat('solana'), amountIn: 12_000_000_000_000_000n, sender: EVM, recipient: SOL, slippageBps: 50 },21  'sol-usdc-arb-usdc': { from: tok('solana', 'USDC'), to: tok('arbitrum', 'USDC'), amountIn: 25_000_000n, sender: SOL, recipient: EVM, slippageBps: 50 },22  'arb-usdc-tron-usdt': { from: tok('arbitrum', 'USDC'), to: tok('tron', 'USDT'), amountIn: 30_000_000n, sender: EVM, recipient: TRON, slippageBps: 50 },23  'arb-usdc-tron-trx': { from: tok('arbitrum', 'USDC'), to: nat('tron'), amountIn: 30_000_000n, sender: EVM, recipient: TRON, slippageBps: 50 },24  'tron-usdt-eth-usdt': { from: tok('tron', 'USDT'), to: tok('ethereum', 'USDT'), amountIn: 30_000_000n, sender: TRON, recipient: EVM, slippageBps: 50 },25  'base-usdc-hyper-usdc': { from: tok('base', 'USDC'), to: tok('hyperevm', 'USDC'), amountIn: 25_000_000n, sender: EVM, recipient: EVM, slippageBps: 50 },26  'tron-trx-base-usdc': { from: nat('tron'), to: tok('base', 'USDC'), amountIn: 100_000_000n, sender: TRON, recipient: EVM, slippageBps: 50 },27  'hyper-hype-base-usdc': { from: nat('hyperevm'), to: tok('base', 'USDC'), amountIn: 500_000_000_000_000_000n, sender: EVM, recipient: EVM, slippageBps: 50 },28  'polygon-pol-bsc-bnb': { from: nat('polygon'), to: nat('bsc'), amountIn: 100_000_000_000_000_000_000n, sender: EVM, recipient: EVM, slippageBps: 50 },29};30const dln = createDebridgeProvider();31function ctxOf(payload: unknown, seen: { url?: string } = {}): ProviderContext {32  return { signal: new AbortController().signal, now: () => NOW,33    http: { postJson: async () => { throw new Error('DLN estimates are requested with GET'); }, getJson: async (url) => { seen.url = url; return payload; } } };34}35const viaFetch = (status: number, body: string): ProviderContext => ({ signal: new AbortController().signal, now: () => NOW, http: httpClient('debridge', (async () => new Response(body, { status })) as typeof fetch) });36const run = async (name: string, payload: unknown = fx(name), req: SwapQuoteRequest = CASES[name]!) => (await dln.quote(req, ctxOf(payload)));37const one = async (name: string) => { const q = await run(name); expect(q).toHaveLength(1); expect(checkQuote(q[0]!, CASES[name]!, NOW)).toBeNull(); return q[0]!; };38const tamper = (name: string, f: (d: any) => void) => { const d = fx(name); f(d); return d; };39const schemaError = (p: Promise<unknown>, re?: RegExp) => expect(p).rejects.toSatisfy((e: unknown) => e instanceof ProviderError && e.provider === 'debridge' && e.code === 'schema' && (!re || re.test(e.message)));4041describe('debridge: capabilities', () => {42  it('declares the wallet networks DLN serves, with deBridge chain ids, and is registered', () => {43    const c = dln.capabilities();44    expect(c.networks.sort()).toEqual(['arbitrum', 'avalanche', 'base', 'bsc', 'ethereum', 'hyperevm', 'optimism', 'polygon', 'solana', 'tron']);45    expect(c).toMatchObject({ crossChain: true, needsKey: false, configured: true, kinds: ['bridge'] });46    // deBridge has its own ids for Solana, HyperEVM (not 999) and TRON (not 728126428)47    expect(DLN_CHAINS.solana!.chainId).toBe(7565164); expect(DLN_CHAINS.hyperevm!.chainId).toBe(100000022); expect(DLN_CHAINS.tron!.chainId).toBe(100000026);48    for (const net of ['ethereum', 'optimism', 'bsc', 'polygon', 'base', 'arbitrum', 'avalanche']) expect(String(DLN_CHAINS[net]!.chainId)).toBe(NETWORKS[net]!.chainRef);49    expect(createProviders().map((p) => p.id)).toContain('debridge');50  });51  it('filters pairs locally: cross-chain only, registry assets only', () => {52    expect(dln.supportsPair(tok('base', 'USDC'), tok('solana', 'USDC'))).toBe(true);53    expect(dln.supportsPair(tok('tron', 'USDT'), nat('hyperevm'))).toBe(true);54    expect(dln.supportsPair(nat('arbitrum'), tok('arbitrum', 'USDC'))).toBe(false); // DLN has no same-chain swap (recorded: error-same-chain)55    expect(fx('error-same-chain').errorId).toBe('SAME_SOURCE_AND_DESTINATION_CHAINS');56    for (const n of ['bitcoin', 'sui', 'litecoin', 'stellar', 'xrpl', 'aptos', 'cosmos', 'near', 'ton']) expect(dln.supportsPair(nat('base'), nat(n))).toBe(false);57    expect(dln.supportsPair(nat('base'), { network: 'solana', asset: 'So11111111111111111111111111111111111111112' })).toBe(false);58  });59});6061describe('debridge: request', () => {62  it('asks for a market order with the wallet addresses as recipient and as both order authorities', async () => {63    const seen: { url?: string } = {};64    await dln.quote(CASES['base-usdc-sol-usdc']!, ctxOf(fx('base-usdc-sol-usdc'), seen));65    const u = new URL(seen.url!);66    expect(u.origin + u.pathname).toBe(`${DLN_API}/dln/order/create-tx`); expect(DLN_API).toBe('https://dln.debridge.finance/v1.0');67    expect(Object.fromEntries(u.searchParams)).toEqual({68      srcChainId: '8453', srcChainTokenIn: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', srcChainTokenInAmount: '25000000',69      dstChainId: '7565164', dstChainTokenOut: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', dstChainTokenOutAmount: 'auto', prependOperatingExpenses: 'false',70      dstChainTokenOutRecipient: SOL, senderAddress: EVM, srcChainOrderAuthorityAddress: EVM, dstChainOrderAuthorityAddress: SOL,71    });72  });73  it('uses the chain-coin ids DLN expects per family and large amounts without loss', async () => {74    const seen: { url?: string } = {};75    await dln.quote(CASES['polygon-pol-bsc-bnb']!, ctxOf(fx('polygon-pol-bsc-bnb'), seen));76    let p = new URL(seen.url!).searchParams;77    expect([p.get('srcChainId'), p.get('srcChainTokenIn'), p.get('srcChainTokenInAmount'), p.get('dstChainId')]).toEqual(['137', '0x0000000000000000000000000000000000000000', '100000000000000000000', '56']);78    await dln.quote(CASES['arb-usdc-tron-trx']!, ctxOf(fx('arb-usdc-tron-trx'), seen));79    p = new URL(seen.url!).searchParams;80    expect([p.get('dstChainId'), p.get('dstChainTokenOut'), p.get('dstChainTokenOutRecipient')]).toEqual(['100000026', 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb', TRON]);81    await dln.quote(CASES['eth-eth-sol-sol']!, ctxOf(fx('eth-eth-sol-sol'), seen));82    expect(new URL(seen.url!).searchParams.get('dstChainTokenOut')).toBe('11111111111111111111111111111111');83    await dln.quote(CASES['base-usdc-hyper-usdc']!, ctxOf(fx('base-usdc-hyper-usdc'), seen));84    expect(new URL(seen.url!).searchParams.get('dstChainId')).toBe('100000022');85  });86});8788describe('debridge: field mapping', () => {89  it('EVM -> Solana, USDC Base -> USDC Solana: fixed output, every fee component, contracts, flags', async () => {90    const q = await one('base-usdc-sol-usdc'), req = CASES['base-usdc-sol-usdc']!, raw = fx('base-usdc-sol-usdc');91    expect(q.id).toBe(`debridge:${raw.orderId}`); expect(q.provider).toBe('debridge');92    // the take amount is written in the order: the recipient gets exactly this or the order is not filled93    expect(q.amountOut).toBe(24_242_915n); expect(q.minAmountOut).toBe(24_242_915n);94    expect(q.fees).toEqual([95      { kind: 'protocol', asset: nat('base'), amount: 1_000_000_000_000_000n, includedInOutput: false, estimated: false }, // flat fee 0.001 ETH, paid on top96      { kind: 'network', asset: nat('base'), amount: 4_369_714_560_000n, includedInOutput: false, estimated: true }, // gas of the order transaction97      { kind: 'protocol', asset: tok('base', 'USDC'), amount: 10_000n, includedInOutput: true, estimated: false }, // 4 bps of the input98      { kind: 'provider', asset: tok('solana', 'USDC'), amount: 9_996n, includedInOutput: true, estimated: false }, // taker margin, 4 bps99      { kind: 'bridge', asset: tok('solana', 'USDC'), amount: 737_089n, includedInOutput: true, estimated: true }, // solver's operating expenses100    ]);101    expect(25_000_000n - 10_000n - 9_996n - 737_089n).toBe(q.amountOut); // the three deducted components account for the whole difference102    expect(q.estimatedSeconds).toBe(1); expect(q.priceImpactBps).toBe(303); // usdPriceImpact -3.02834 %103    expect(q.expiresAt).toBe(NOW + 30_000); expect(q.firmness).toBe('firm'); expect(q.atomic).toBe(false); expect(q.refundable).toBe(true);104    expect(q.executability.class).toBe('opaque'); expect((q.executability as { reason: string }).reason).toMatch(/DLN API builds/);105    expect(q.steps).toEqual([{ kind: 'bridge', provider: 'debridge', from: req.from, to: req.to, via: ['DLN'], contracts: [106      { network: 'base', address: DLN_SOURCE, role: 'router' }, { network: 'base', address: DLN_SOURCE, role: 'spender' }, { network: 'base', address: DLN_SOURCE, role: 'vault' },107      { network: 'solana', address: 'dst5MGcFPoBeREFAA5E3tU5ij8m5uVYwkzkSAbsLbNo', role: 'program' }] }]);108    expect((q.raw as { orderId: string }).orderId).toBe(raw.orderId);109    expect(JSON.stringify(q.raw)).not.toMatch(/userPoints/); // validated fields only110  });111  it('a token that is not a reserve asset goes through DeBridgeRouter with a pre-order swap and a destination swap', async () => {112    const q = await one('eth-link-sol-sol');113    expect(q.amountOut).toBe(361_149_576n); expect(q.minAmountOut).toBe(q.amountOut); // not maxTheoreticalAmount (363326262): slippage reserves are already taken114    expect(q.steps[0]!.via).toEqual(['pre-order swap', 'DLN', 'destination swap']);115    expect(q.steps[0]!.contracts.slice(0, 3)).toEqual([{ network: 'ethereum', address: ROUTER, role: 'router' }, { network: 'ethereum', address: ROUTER, role: 'spender' }, { network: 'ethereum', address: DLN_SOURCE, role: 'vault' }]);116    expect(q.fees.map((f) => [f.kind, f.asset, f.amount, f.includedInOutput])).toEqual([117      ['protocol', nat('ethereum'), 1_000_000_000_000_000n, false], ['network', nat('ethereum'), 906_615_166_565_682n, false],118      ['protocol', tok('ethereum', 'USDC'), 12_325n, true], ['provider', tok('solana', 'USDC'), 12_320n, true], ['bridge', tok('solana', 'USDC'), 743_910n, true]]);119    expect(q.priceImpactBps).toBe(308);120  });121  it('a native input is sent as value together with the flat fee; no spender', async () => {122    const q = await one('eth-eth-sol-sol');123    expect(BigInt(fx('eth-eth-sol-sol').tx.value)).toBe(12_000_000_000_000_000n + 1_000_000_000_000_000n);124    expect(q.amountOut).toBe(284_608_568n); expect(q.steps[0]!.contracts.some((c) => c.role === 'spender')).toBe(false);125    expect(q.fees[0]).toEqual({ kind: 'protocol', asset: nat('ethereum'), amount: 1_000_000_000_000_000n, includedInOutput: false, estimated: false });126    const pol = await one('polygon-pol-bsc-bnb');127    expect(pol.amountOut).toBe(12_238_319_658_602_409n); expect(pol.fees[0]!.amount).toBe(500_000_000_000_000_000n); // flat fee on Polygon: 0.5 POL128    // taker margin and operating expenses are charged in USDC on BNB Chain (Binance-Peg USDC, in the registry since v5): reported as fee items; the raw answer is kept too129    expect(pol.fees.map((f) => [f.kind, f.asset])).toEqual([['protocol', nat('polygon')], ['network', nat('polygon')], ['protocol', tok('polygon', 'USDC')], ['provider', tok('bsc', 'USDC')], ['bridge', tok('bsc', 'USDC')]]);130    expect(JSON.stringify(pol.raw)).toMatch(/EstimatedOperatingExpenses/);131  });132  it('Solana source: flat fee and network cost in lamports, programs instead of contracts', async () => {133    const q = await one('sol-usdc-arb-usdc');134    expect(q.amountOut).toBe(24_201_767n); expect(q.estimatedSeconds).toBe(9);135    expect(q.fees.slice(0, 2)).toEqual([136      { kind: 'protocol', asset: nat('solana'), amount: 15_000_000n, includedInOutput: false, estimated: false }, // 0.015 SOL137      { kind: 'network', asset: nat('solana'), amount: 24_162_360n, includedInOutput: false, estimated: true }]); // fee + priority fee + account rents138    expect(q.steps[0]!.contracts).toEqual([{ network: 'solana', address: 'src5qyZHqTqecJV4aY6Cb6zDZLMDzrDKKezs22MPHr4', role: 'program' }, { network: 'arbitrum', address: DLN_DESTINATION, role: 'bridge' }]);139  });140  it('TRON in both directions and HyperEVM', async () => {141    const from = await one('tron-usdt-eth-usdt');142    expect(from.amountOut).toBe(27_939_980n); expect(from.estimatedSeconds).toBe(15);143    expect(from.fees[0]).toEqual({ kind: 'protocol', asset: nat('tron'), amount: 4_000_000n, includedInOutput: false, estimated: false }); // 4 TRX144    expect(from.fees[1]).toEqual({ kind: 'network', asset: nat('tron'), amount: 35_750_000n, includedInOutput: false, estimated: true });145    // the API names the TRON contract in 0x form; the quote reports the pinned base58 DlnSource it corresponds to146    expect(fx('tron-usdt-eth-usdt').tx.to).toBe('0xE6f924E3C42350684aF70F798c3cA2533A4c5Bd0');147    expect(from.steps[0]!.contracts[0]).toEqual({ network: 'tron', address: 'TX2Ut1reF59i2WPzsYVoMfA25EkUkavnd5', role: 'router' });148    expect(from.steps[0]!.via).toEqual(['DLN', 'destination swap']);149    const to = await one('arb-usdc-tron-usdt');150    expect(to.amountOut).toBe(23_951_643n); expect(to.priceImpactBps).toBe(2024); // 6 USDT of operating expenses on a 30 USDC order151    expect(to.fees[4]).toEqual({ kind: 'bridge', asset: tok('tron', 'USDT'), amount: 6_006_369n, includedInOutput: true, estimated: true });152    expect(to.steps[0]!.contracts.at(-1)).toEqual({ network: 'tron', address: 'TXCbCdoHjg28g36X5jnWTP88mRzx54RqXp', role: 'bridge' });153    const trx = await one('arb-usdc-tron-trx');154    expect(trx.amountOut).toBe(25_708_107n); expect(trx.steps[0]!.to).toEqual(nat('tron')); expect(trx.priceImpactBps).toBe(7272);155    // native TRX input: TRON's DeBridgeRouter (0x form in the answer), value = 100 TRX + the 4 TRX flat fee156    const trxIn = await one('tron-trx-base-usdc');157    expect(BigInt(fx('tron-trx-base-usdc').tx.value)).toBe(104_000_000n);158    expect(trxIn.steps[0]!.contracts.slice(0, 2)).toEqual([{ network: 'tron', address: 'TRsqznGXF7mSYhRxU49L3HY13QW1v9yBZa', role: 'router' }, { network: 'tron', address: 'TX2Ut1reF59i2WPzsYVoMfA25EkUkavnd5', role: 'vault' }]);159    expect(trxIn.amountOut).toBe(29_667_770n); expect(trxIn.steps[0]!.via).toEqual(['pre-order swap', 'DLN']);160    // native HYPE input: flat fee 0.05 HYPE161    const hypeIn = await one('hyper-hype-base-usdc');162    expect(hypeIn.fees[0]).toEqual({ kind: 'protocol', asset: nat('hyperevm'), amount: 50_000_000_000_000_000n, includedInOutput: false, estimated: false });163    expect(hypeIn.amountOut).toBe(35_483_289n);164    const hype = await one('base-usdc-hyper-usdc');165    expect(hype.amountOut).toBe(24_546_646n); expect(hype.steps[0]!.contracts.at(-1)).toEqual({ network: 'hyperevm', address: DLN_DESTINATION, role: 'bridge' });166  });167  it('the requested slippage does not change a DLN quote (the output is fixed, there is no slippage parameter)', async () => {168    const seen: { url?: string } = {};169    const a = await dln.quote({ ...CASES['eth-link-sol-sol']!, slippageBps: 10 }, ctxOf(fx('eth-link-sol-sol'), seen));170    expect(a[0]!.minAmountOut).toBe(361_149_576n); expect(seen.url).not.toMatch(/slippage/i);171  });172});173174describe('debridge: no route and provider errors', () => {175  it('"amount too small" (HTTP 400, ERROR_LOW_GIVE_AMOUNT) is no route', async () => {176    const body = JSON.stringify(fx('no-route-low-amount'));177    expect(JSON.parse(body).errorId).toBe('ERROR_LOW_GIVE_AMOUNT');178    expect(await dln.quote({ ...CASES['base-usdc-sol-usdc']!, amountIn: 1000n }, viaFetch(400, body))).toEqual([]);179  });180  it('other provider errors throw', async () => {181    const req = CASES['base-usdc-sol-usdc']!;182    await expect(dln.quote(req, viaFetch(400, JSON.stringify(fx('error-not-a-token'))))).rejects.toMatchObject({ code: 'http' });183    await expect(dln.quote(req, viaFetch(400, JSON.stringify(fx('error-same-chain'))))).rejects.toMatchObject({ code: 'http' });184    await expect(dln.quote(req, viaFetch(500, '{"errorId":"INTERNAL_SERVER_ERROR"}'))).rejects.toMatchObject({ code: 'http' });185    await expect(dln.quote(req, viaFetch(429, 'Too Many Requests'))).rejects.toMatchObject({ code: 'rate_limited' });186    await expect(dln.quote(req, viaFetch(200, 'gateway timeout'))).rejects.toMatchObject({ code: 'bad_response' });187  });188  it('does not call the API for a pair it cannot serve', async () => {189    const http = { getJson: async () => { throw new Error('called'); }, postJson: async () => { throw new Error('called'); } };190    expect(await dln.quote({ ...CASES['base-usdc-sol-usdc']!, to: nat('bitcoin') }, { signal: new AbortController().signal, now: () => NOW, http })).toEqual([]);191    expect(await dln.quote({ ...CASES['base-usdc-sol-usdc']!, to: nat('base') }, { signal: new AbortController().signal, now: () => NOW, http })).toEqual([]);192  });193  it('reports a timeout when the deadline aborts the request', async () => {194    const slow = (async (_u: unknown, init?: RequestInit) => new Promise<Response>((_res, rej) => init!.signal!.addEventListener('abort', () => rej(Object.assign(new Error('aborted'), { name: 'AbortError' }))))) as typeof fetch;195    const rt = new ProviderRuntime(dln, { ...DEFAULT_POLICY, timeoutMs: 30 }, httpClient('debridge', slow));196    await expect(rt.quote(CASES['base-usdc-sol-usdc']!)).rejects.toMatchObject({ code: 'timeout' });197    expect(rt.stats().timeouts).toBe(1);198  });199});200201describe('debridge: malformed and hostile answers are rejected', () => {202  const name = 'base-usdc-sol-usdc';203  it('rejects answers that are not the documented shape', async () => {204    await schemaError(run(name, null)); await schemaError(run(name, {})); await schemaError(run(name, []));205    await schemaError(run(name, fx('estimate-without-addresses'))); // an estimate with no order and no transaction is not what was asked206    for (const k of ['estimation', 'tx', 'order', 'orderId', 'fixFee']) await schemaError(run(name, tamper(name, (d) => { delete d[k]; })), new RegExp(k));207    await schemaError(run(name, tamper(name, (d) => { delete d.estimation.dstChainTokenOut.amount; })));208    await schemaError(run(name, tamper(name, (d) => { delete d.order.approximateFulfillmentDelay; })));209    await schemaError(run(name, tamper(name, (d) => { d.tx.data = 'b930370100'; })));210    await schemaError(run(name, tamper(name, (d) => { delete d.estimation.costsDetails[2].payload; })), /EstimatedOperatingExpenses/);211  });212  it('rejects amounts that are not plain base-unit integers', async () => {213    for (const v of ['24242915.5', '-24242915', '2.4e7', '0x171eb23', '', 24242915, null]) {214      await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.amount = v; })));215      await schemaError(run(name, tamper(name, (d) => { d.fixFee = v; })));216      await schemaError(run(name, tamper(name, (d) => { d.estimation.costsDetails[0].payload.feeAmount = v; })));217      await schemaError(run(name, tamper(name, (d) => { d.estimatedTransactionFee.total = v; })));218    }219    await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.amount = '0'; d.estimation.dstChainTokenOut.recommendedAmount = '0'; })), /output amount/);220    await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.amount = '25000000'; })), /recommended/); // more than a solver would fill221  });222  it('rejects an estimate for another token, chain, decimals or input amount', async () => {223    await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.address = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'; })), /another pair/);224    await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.address = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1x'; })), /another pair/);225    await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.chainId = 42161; })), /another pair/);226    await schemaError(run(name, tamper(name, (d) => { d.estimation.srcChainTokenIn.address = '0x0000000000000000000000000000000000000000'; })), /another pair/);227    await schemaError(run(name, tamper(name, (d) => { d.estimation.dstChainTokenOut.decimals = 9; })), /decimals/);228    await schemaError(run(name, tamper(name, (d) => { d.estimation.srcChainTokenIn.amount = '25736992'; d.estimation.srcChainTokenIn.mutatedWithOperatingExpense = true; })), /input amount/);229    await schemaError(run(name, fx(name), { ...CASES[name]!, amountIn: 26_000_000n }), /input amount/);230  });231  it('rejects an order for another recipient, another target contract or another value', async () => {232    await schemaError(run(name, fx(name), { ...CASES[name]!, recipient: 'DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy' }), /recipient/);233    await schemaError(run('sol-usdc-arb-usdc', fx('sol-usdc-arb-usdc'), { ...CASES['sol-usdc-arb-usdc']!, recipient: '0x000000000000000000000000000000000000dEaD' }), /recipient/);234    await schemaError(run('arb-usdc-tron-usdt', fx('arb-usdc-tron-usdt'), { ...CASES['arb-usdc-tron-usdt']!, recipient: 'TXCbCdoHjg28g36X5jnWTP88mRzx54RqXp' }), /recipient/);235    await expect(run(name, fx(name), { ...CASES[name]!, recipient: 'not-an-address' })).rejects.toMatchObject({ code: 'bad_response' });236    await schemaError(run(name, tamper(name, (d) => { d.tx.to = '0x000000000000000000000000000000000000dEaD'; })), /pinned DLN contract/);237    await schemaError(run(name, tamper(name, (d) => { delete d.tx.to; })), /pinned DLN contract/);238    await schemaError(run('tron-usdt-eth-usdt', tamper('tron-usdt-eth-usdt', (d) => { d.tx.to = DLN_SOURCE; })), /pinned DLN contract/); // the EVM DlnSource is not TRON's239    await schemaError(run(name, tamper(name, (d) => { d.tx.value = '2000000000000000'; })), /value/);240    await schemaError(run('eth-eth-sol-sol', tamper('eth-eth-sol-sol', (d) => { d.tx.value = d.fixFee; })), /value/); // the native input must be in the value241    await schemaError(run('sol-usdc-arb-usdc', tamper('sol-usdc-arb-usdc', (d) => { d.tx.to = DLN_SOURCE; })), /Solana/);242    // checksum-case differences on EVM addresses are not differences243    expect(await run('sol-usdc-arb-usdc', fx('sol-usdc-arb-usdc'), { ...CASES['sol-usdc-arb-usdc']!, recipient: EVM.toLowerCase() })).toHaveLength(1);244  });245  it('a price impact outside the plausible range is dropped by the aggregator check', async () => {246    const q = await run(name, tamper(name, (d) => { d.usdPriceImpact = -150; }));247    expect(checkQuote(q[0]!, CASES[name]!, NOW)).toBe('invalid price impact');248    const none = await run(name, tamper(name, (d) => { delete d.usdPriceImpact; }));249    expect(none[0]!.priceImpactBps).toBeNull();250  });251});252
Files main
Menu

Theme