Repository Public
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