Skip to content
Merged
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
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@velora-dex/sdk",
"version": "10.4.0",
"version": "11.0.0",
"main": "dist/index.js",
"module": "dist/sdk.esm.js",
"typings": "dist/index.d.ts",
Expand Down Expand Up @@ -65,7 +65,7 @@
"@types/node": "^22.8.5",
"@wagmi/connectors": "^8.0.13",
"@wagmi/core": "^3.4.11",
"axios": "^1.13.2",
"axios": "^1.20.0",
"bignumber.js": "^9.1.2",
"dotenv": "^16.4.5",
"dts-cli": "^2.0.5",
Expand All @@ -77,11 +77,11 @@
"isomorphic-unfetch": "^4.0.2",
"size-limit": "^12.1.0",
"tslib": "^2.8.1",
"typedoc": "^0.26.11",
"typedoc-plugin-markdown": "^4.2.10",
"typedoc-plugin-missing-exports": "^3.0.0",
"typedoc-plugin-replace-text": "^4.0.0",
"typescript": "^5.6.3",
"typedoc": "^0.28.20",
"typedoc-plugin-markdown": "^4.13.0",
"typedoc-plugin-missing-exports": "^4.1.4",
"typedoc-plugin-replace-text": "^4.2.0",
"typescript": "^5.9.3",
"web3": "^4.14.0",
"web3-eth-contract": "4.7.0"
},
Expand Down
885 changes: 420 additions & 465 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions src/methods/delta/buildDeltaOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { API_URL } from '../../constants';
import type { ConstructFetchInput, RequestParameters } from '../../types';
import type { DeltaAuctionOrder } from './helpers/types';
import type { BuiltDeltaOrder, DeltaRoute } from './types';
import { toOrderLimitAmount } from './helpers/limitAmount';
export type { BuiltDeltaOrder } from './types';

export type BuildDeltaOrderParams = {
Expand Down Expand Up @@ -36,7 +37,17 @@ export type BuildDeltaOrderParams = {
side: 'SELL' | 'BUY';
/** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5%. Default 0. */
slippage?: number;
/** @description If passed, the server will use this as SELL destAmount (as BUY srcAmount) and expectedAmount */
/**
* @description If passed, the server will use this as SELL destAmount (as BUY srcAmount) and expectedAmount.
*
* Units depend on `side`:
* - **SELL** — destination-token wei, the same units as `route.destination.output.amount`.
* Over a bridge route the on-chain Order carries destAmount scaled by
* `route.bridge.contractParams.scalingFactor`, and the SDK applies that scaling for you
* (rounding up, so the minimum you receive never lands below what you asked for).
* - **BUY** — origin src-token wei, the same units as `route.origin.input.amount`. It caps
* what you spend, is always an origin-chain amount, and is never bridge-scaled.
*/
limitAmount?: string;
};

Expand Down Expand Up @@ -69,7 +80,7 @@ export const constructBuildDeltaOrder = (
nonce: params.nonce,
permit: params.permit,
slippage: params.slippage,
limitAmount: params.limitAmount,
limitAmount: toOrderLimitAmount(params),
metadata: params.metadata,
partiallyFillable: params.partiallyFillable,
partner: params.partner,
Expand Down
5 changes: 2 additions & 3 deletions src/methods/delta/buildExternalDeltaOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export type BuildExternalDeltaOrderParams = {
side: 'SELL' | 'BUY';
/** @description Slippage in basis points (bps). Default 0. */
slippage?: number;
/** @description If passed, the server will use this as SELL destAmount (as BUY srcAmount) and expectedAmount */
limitAmount?: string;
// No `limitAmount` here: the build endpoint declares it only on the `Order`
// variant of its (strict) request schema, so sending it is rejected.
};

type BuildExternalDeltaOrder = (
Expand Down Expand Up @@ -78,7 +78,6 @@ export const constructBuildExternalDeltaOrder = (
nonce: params.nonce,
permit: params.permit,
slippage: params.slippage,
limitAmount: params.limitAmount,
metadata: params.metadata,
partiallyFillable: params.partiallyFillable,
partner: params.partner,
Expand Down
6 changes: 3 additions & 3 deletions src/methods/delta/buildTWAPDeltaOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ type BuildTWAPDeltaOrderBase = {
metadata?: string;
/** @description Designates the Order as partially fillable. Default false. */
partiallyFillable?: boolean;
/** @description If passed, the server will use this as SELL destAmount (as BUY srcAmount) and expectedAmount for each slice */
limitAmount?: string;
// No `limitAmount` here: the build endpoint declares it only on the `Order`
// variant of its (strict) request schema, so sending it is rejected. TWAP
// amounts are set by `totalSrcAmount` / `totalDestAmount` + `maxSrcAmount`.
};

export type BuildTWAPSellDeltaOrderParams = BuildTWAPDeltaOrderBase & {
Expand Down Expand Up @@ -87,7 +88,6 @@ export const constructBuildTWAPDeltaOrder = (
nonce: params.nonce,
permit: params.permit,
slippage: params.slippage,
limitAmount: params.limitAmount,
metadata: params.metadata,
partiallyFillable: params.partiallyFillable,
partner: params.partner,
Expand Down
61 changes: 61 additions & 0 deletions src/methods/delta/helpers/limitAmount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { DeltaRoute } from '../types';
import type { SwapSideUnion } from './types';

type ToOrderLimitAmountParams = {
/**
* @description The caller-supplied limit amount. On SELL, destination-token wei; on BUY,
* origin src-token wei. See `toOrderLimitAmount`.
*/
limitAmount?: string;
/** @description The route the order is built from. */
route: DeltaRoute;
/** @description Order side. */
side: SwapSideUnion;
};

/**
* @description Converts a caller-supplied limit amount into the units the on-chain Order
* carries.
*
* On a SELL the caller passes destination-token units (those of
* `route.destination.output.amount`), but on a bridge route the Order's `destAmount` lives
* in *bridge units*: the destination amount scaled by `bridge.contractParams.scalingFactor`,
* the `int8` the settlement contract applies when it scales the amount back up on the
* destination chain. Passing a destination-token amount straight through as `limitAmount`
* therefore sets a limit that is wrong by `10 ** scalingFactor`.
*
* Returned unchanged for same-chain routes (`route.bridge === null`) and for BUY, where
* `limitAmount` bounds `srcAmount` — origin src-token units, always an origin-chain amount,
* never scaled. Which conversion applies is fully determined by the `route` and `side` the
* caller already passes to the builders, so there is no unit flag to get wrong.
*/
export function toOrderLimitAmount({
limitAmount,
route,
side,
}: ToOrderLimitAmountParams): string | undefined {
const bridge = route.bridge;

if (limitAmount === undefined || side !== 'SELL' || bridge === null) {
return limitAmount;
}

return scaleToBridgeUnits(limitAmount, bridge.contractParams.scalingFactor);
}

function scaleToBridgeUnits(amount: string, scalingFactor: number): string {
if (scalingFactor === 0) return amount;

const value = BigInt(amount);

if (scalingFactor < 0) {
return (value * 10n ** BigInt(-scalingFactor)).toString();
}

const divisor = 10n ** BigInt(scalingFactor);

// A SELL limitAmount is a *minimum* to receive, and Order units are integers:
// round up, so that scaling back at settlement never lands below the amount
// the user asked for.
return ((value + divisor - 1n) / divisor).toString();
}
46 changes: 40 additions & 6 deletions src/methods/delta/helpers/orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,15 +453,47 @@ function getAuctionTokenAddresses(
};
}

/**
* @description Picks the received amount denominated in the auction's `output`
* token. On a crosschain order that token lives on the destination chain, so the
* destination leg is read; otherwise the origin leg is. Falls back to the legacy
* `receivedAmount`, which reports whichever leg matches the order and so is
* denominated in the same token either way.
*/
function getReceivedAmountOnOutputSide(
tx: DeltaTransaction,
crosschain: boolean
): string | null {
const onOutputLeg = crosschain
? tx.destinationReceivedAmount
: tx.originReceivedAmount;

return onOutputLeg ?? tx.receivedAmount;
}

/**
* @description Aggregates transaction amounts into total spent (src) and
* received (dest) values.
*/
function getTransactionAmounts(transactions: DeltaTransaction[]) {
*
* `destAmount` is denominated in the auction's `output` token, so pass
* `crosschain` to read the leg that token sits on — mirroring how the server
* derives `output.executedAmount`. Omit it and the legacy `receivedAmount` is
* summed, whose leg depends on the order.
*/
function getTransactionAmounts(
transactions: DeltaTransaction[],
options?: { crosschain: boolean }
) {
const { srcAmount, destAmount } = transactions.reduce(
(acc, { spentAmount, receivedAmount }) => ({
srcAmount: acc.srcAmount + BigInt(spentAmount ?? 0),
destAmount: acc.destAmount + BigInt(receivedAmount ?? 0),
(acc, tx) => ({
srcAmount: acc.srcAmount + BigInt(tx.spentAmount ?? 0),
destAmount:
acc.destAmount +
BigInt(
(options
? getReceivedAmountOnOutputSide(tx, options.crosschain)
: tx.receivedAmount) ?? 0
),
}),
{ srcAmount: 0n, destAmount: 0n }
);
Expand Down Expand Up @@ -549,7 +581,9 @@ function getAuctionAmounts(
return { expected, minimal };
}

const txAmounts = getTransactionAmounts(auction.transactions);
const txAmounts = getTransactionAmounts(auction.transactions, {
crosschain: isOrderCrosschain(order),
});

const executed = {
srcAmount: getExecutedAmount(auction.input, txAmounts.srcAmount),
Expand Down
26 changes: 25 additions & 1 deletion src/methods/delta/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,33 @@ export type DeltaTransaction = {
destinationTx: string | null;
/** @description Filled percent of the slice (0–100). */
filledPercent: number;
/**
* @description `order.srcToken` taken from the owner on the origin chain by
* `originTx` — the same leg, chain and token decimals as `originReceivedAmount`.
*/
spentAmount: string | null;
/**
* @description Which leg this reports depends on the order: the destination leg
* for bridge fills, the origin leg otherwise. Unlike `destinationReceivedAmount`
* it is not gated on the bridge having filled, so on a bridge order it can carry
* a destination amount the provider recorded while the leg was still in flight.
* Kept as-is for existing integrators — prefer the explicit
* `originReceivedAmount` / `destinationReceivedAmount` pair.
*/
receivedAmount: string | null;
/** @description ISO datetime string. */
/**
* @description `order.destToken` delivered on the origin chain by `originTx` —
* the same leg, chain and token decimals as `spentAmount`.
*/
originReceivedAmount: string | null;
/**
* @description `bridge.outputToken` delivered on `bridge.destinationChainId`,
* already scaled by `bridge.scalingFactor`. `null` on same-chain fills, which
* have no destination leg, and on bridge fills whose destination leg is still
* in flight.
*/
destinationReceivedAmount: string | null;
/** @description ISO datetime string of origin Tx. */
timestamp: string | null;
};

Expand Down
115 changes: 115 additions & 0 deletions tests/auctionAmounts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { OrderHelpers } from '../src';
import type { DeltaAuction, DeltaTransaction } from '../src/methods/delta/types';

const { getAuctionAmounts, getTransactionAmounts } = OrderHelpers.getters;

const USDC_MAINNET = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
const USDC_ARBITRUM = '0xaf88d065e77c8cc2239327c5edb3a432268e5831';

// Deliberately different so a test can tell which leg was summed. The fixtures
// below let the legacy `receivedAmount` disagree with the explicit pair, which
// a real payload would not: the point is to pin *which field is authoritative*,
// not to reproduce a server response.
const ORIGIN_LEG = '1000000000000000000';
const DESTINATION_LEG = '999000';

function tx(overrides: Partial<DeltaTransaction> = {}): DeltaTransaction {
return {
originTx: '0xorigin',
destinationTx: '0xdestination',
filledPercent: 100,
spentAmount: '5000000',
receivedAmount: ORIGIN_LEG,
originReceivedAmount: ORIGIN_LEG,
destinationReceivedAmount: DESTINATION_LEG,
timestamp: null,
...overrides,
};
}

describe('getTransactionAmounts leg selection', () => {
test('reads the destination leg when crosschain', () => {
expect(
getTransactionAmounts([tx()], { crosschain: true }).destAmount
).toEqual(DESTINATION_LEG);
});

test('reads the origin leg when same-chain', () => {
expect(
getTransactionAmounts([tx({ destinationReceivedAmount: null })], {
crosschain: false,
}).destAmount
).toEqual(ORIGIN_LEG);
});

test('sums across transactions without mixing legs', () => {
expect(
getTransactionAmounts([tx(), tx()], { crosschain: true }).destAmount
).toEqual((BigInt(DESTINATION_LEG) * 2n).toString());
});

test('falls back to receivedAmount when the specific leg is absent', () => {
expect(
getTransactionAmounts([tx({ destinationReceivedAmount: null })], {
crosschain: true,
}).destAmount
).toEqual(ORIGIN_LEG);
});

test('sums the legacy receivedAmount when no leg is given', () => {
expect(getTransactionAmounts([tx()]).destAmount).toEqual(ORIGIN_LEG);
});

test('always sums spentAmount for srcAmount', () => {
expect(
getTransactionAmounts([tx(), tx()], { crosschain: true }).srcAmount
).toEqual('10000000');
});
});

function auction(crosschain: boolean): DeltaAuction {
return {
status: 'COMPLETED',
side: 'SELL',
transactions: [tx()],
input: { chainId: 1, token: USDC_MAINNET, amount: '5000000' },
output: {
chainId: crosschain ? 42161 : 1,
token: crosschain ? USDC_ARBITRUM : USDC_MAINNET,
expectedAmount: DESTINATION_LEG,
minAmount: DESTINATION_LEG,
// null so the sum over transactions is what gets reported
executedAmount: null,
},
order: {
kind: 0,
srcAmount: '5000000',
destAmount: DESTINATION_LEG,
bridge: { destinationChainId: crosschain ? 42161 : 0 },
},
} as unknown as DeltaAuction;
}

describe('getAuctionAmounts executed destAmount', () => {
test('reports the destination leg for a bridge order', () => {
expect(getAuctionAmounts(auction(true)).executed?.destAmount).toEqual(
DESTINATION_LEG
);
});

test('reports the origin leg for a same-chain order', () => {
expect(getAuctionAmounts(auction(false)).executed?.destAmount).toEqual(
ORIGIN_LEG
);
});

test('prefers the executedAmount baked onto the output side', () => {
const base = auction(true);
const amounts = getAuctionAmounts({
...base,
output: { ...base.output, executedAmount: '424242' } as never,
});

expect(amounts.executed?.destAmount).toEqual('424242');
});
});
Loading
Loading