diff --git a/CLAUDE.md b/CLAUDE.md index efa4263f2..5817340d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,6 +33,8 @@ Three ways to construct the SDK: `constructSimpleSDK` accepts `{ axios }`, `{ fetch }`, or `{ fetcher }` for the network layer, and optionally `{ ethersProviderOrSigner | ethersV6ProviderOrSigner | viemClient | web3, account }` for signing/transacting. +`constructFullSDK` exposes namespaced methods: `sdk.delta.*`, `sdk.swap.*`, `sdk.quote.*`, etc. + ## Provider Adapters (`src/helpers/providers/`) Each file exports `constructContractCaller(provider, account)` → `ContractCallerFunctions`: @@ -43,11 +45,11 @@ Each file exports `constructContractCaller(provider, account)` → `ContractCall ## Module Structure (`src/methods/`) -- `delta/` — Core feature: Delta auction orders (see detail below) +- `delta/` — Core feature: Delta auction orders (server-side order building, route-based pricing, paginated orders; see detail below) - `swap/` — Token swap: rates, transaction building, approvals, balances - `limitOrders/` — **Deprecated.** EIP-712 signed limit orders - `nftOrders/` — **Deprecated.** EIP-712 signed NFT orders -- `quote/` — Unified quote endpoint +- `quote/` — Unified `/v2/quote` endpoint (mode-selectable Delta v2 price / market / fallback) ## Composable Constructor Pattern @@ -56,7 +58,7 @@ Every feature module exports a `constructXxx(options) => XxxFunctions` factory: - `D` selects required caller methods: `'transactCall'` | `'signTypedDataCall'` | both - Generic `` = transaction response type (e.g., `TxHash`, `ethers.ContractTransaction`) - Non-generic constructors use `any` (for API-only flows that don't return tx responses) -- All Delta constructors are combined in `constructAllDeltaOrdersHandlers` in `src/methods/delta/index.ts` +- All Delta constructors are combined in `constructAllDeltaOrdersHandlers` in `src/methods/delta/index.ts`. **Convention:** bind each `constructXxx(options)` call to a named local (`const deltaPrice = constructGetDeltaPrice(options)`), then spread those locals into the returned object — don't spread the constructor calls inline. (Matches `constructAllLimitOrdersHandlers`.) ## Key Patterns @@ -73,52 +75,82 @@ Every feature module exports a `constructXxx(options) => XxxFunctions` factory: ## Delta Module (`src/methods/delta/`) -Three order families, each with build/sign/post/preSign constructors: +Delta is the SDK's core feature: server-built, on-chain auction orders. Exposed as `sdk.delta.*` and via bare top-level exports. + +> **History.** Delta was once a **v1** with local EIP-712 order building and per-family sign functions. It was replaced (breaking) by the server-built **v2** described here — `sdk.delta.*` *is* v2, with a single set of bare top-level exports. All Delta URLs use the `/delta/v2/...` prefix. + +Order building is **server-side**: `POST /delta/v2/orders/build` returns a `BuiltDeltaOrder { toSign, orderHash }`; a single `signDeltaOrder(builtOrder)` signs every family; `post*` submits the signed order. Partner fee is passed as raw params (`partner`, `partnerFeeBps`) to the server rather than resolved locally. Reads are paginated (`PaginatedResponse`), price is route-based. + +### Order families + +All built via `POST /delta/v2/orders/build` with an `orderType` field: + +| Family | `orderType` / `onChainOrderType` | Build params | Build fn | +|--------|-------------------|-----------|----------| +| Standard | `'Order'` | `BuildDeltaOrderParams` | `buildDeltaOrder` | +| External | `'ExternalOrder'` | `BuildExternalDeltaOrderParams` (adds `handler`, `data`) | `buildExternalDeltaOrder` | +| TWAP Sell | `'TWAPOrder'` | `BuildTWAPSellDeltaOrderParams` | `buildTWAPDeltaOrder` | +| TWAP Buy | `'TWAPBuyOrder'` | `BuildTWAPBuyDeltaOrderParams` | `buildTWAPDeltaOrder` | +| Productive | `'ProductiveOrder'` | _read-only_ — no SDK builder (server-produced) | — | -| Family | `onChainOrderType` | Order type | Build input key | -|--------|-------------------|-----------|-----------------| -| Standard | `'Order'` | `DeltaAuctionOrder` | `buildDeltaOrder` | -| External | `'ExternalOrder'` | `ExternalDeltaOrder` | `buildExternalDeltaOrder` (has `handler` field instead of `bridge`) | -| TWAP Sell | `'TWAPOrder'` | `TWAPDeltaOrder` | `buildTWAPDeltaOrder` | -| TWAP Buy | `'TWAPBuyOrder'` | `TWAPBuyDeltaOrder` | `buildTWAPDeltaOrder` | +`'FillableOrder'` is also a key in `OnChainOrderMap`/`OnChainOrderType`, mapping to the same `DeltaAuctionOrder` shape as `'Order'`. It is not a separate buildable family — it's the `onChainOrderType` the server reports when a Standard order is `partiallyFillable`. Only surfaces (read-only) on the way back through the read paths. -Each family has four files: `build*`, `sign*`, `post*`, `preSign*`. High-level orchestrators (`constructSubmitDeltaOrder`, `constructSubmitExternalDeltaOrder`, `constructSubmitTWAPDeltaOrder`) in `index.ts` wrap build→sign→post. +Submit orchestrators (`constructSubmitDeltaOrder`, `constructSubmitExternalDeltaOrder`, `constructSubmitTWAPDeltaOrder`) in `index.ts` wrap build→sign→post. Productive orders surface only through the read paths (`getDeltaOrders*` / `getDeltaOrderById*`). ### Key Files -| File | Constructor | Purpose | Generic? | Pattern | -|------|-------------|---------|----------|---------| -| `index.ts` | `constructSubmitDeltaOrder`, `constructAllDeltaOrdersHandlers` | Composite: orchestrates all modules, defines `DeltaOrderHandlers` | `submitDelta`: No, `allHandlers`: `` | Composite | -| `buildDeltaOrder.ts` | `constructBuildDeltaOrder` | Build `SignableDeltaOrderData` from params (fetches contract + partner fee, then local computation) | No | API fetch + local | -| `signDeltaOrder.ts` | `constructSignDeltaOrder` | EIP-712 sign order via `signTypedDataCall` → returns signature `string` | No (`any`) | `signTypedDataCall` | -| `postDeltaOrder.ts` | `constructPostDeltaOrder` | POST signed order to API → `DeltaOrderApiResponse` | No | `fetcher` POST | -| `getDeltaPrice.ts` | `constructGetDeltaPrice` | Fetch quote/price from API. Overloaded: returns `DeltaPrice` (same-chain) or `BridgePrice` (cross-chain when `destChainId` present) | No | `fetcher` GET | -| `getDeltaOrders.ts` | `constructGetDeltaOrders` | Query orders from API: `getDeltaOrderById`, `getDeltaOrderByHash`, `getDeltaOrders` (list), `getRequiredBalanceForDeltaLimitOrders` | No | `fetcher` GET | -| `getDeltaContract.ts` | `constructGetDeltaContract` | Resolve ParaswapDelta contract address from contracts endpoint | No | `fetcher` GET | -| `approveForDelta.ts` | `constructApproveTokenForDelta` | ERC-20 `approve` with ParaswapDelta as spender (delegates to `approveTokenMethodFactory`) | `` | `transactCall` | -| `preSignDeltaOrder.ts` | `constructPreSignDeltaOrder` | On-chain `setPreSignature` + order hashing helpers (`hashDeltaOrderTypedData`, `hashDeltaOrder`, `preSignDeltaOrder`) | `` | `transactCall` | -| `cancelDeltaOrder.ts` | `constructCancelDeltaOrder` | API cancel: `signCancelLimitDeltaOrderRequest` → `postCancelLimitDeltaOrderRequest` → `cancelLimitDeltaOrders` (orchestrator) | No (`any`) | `signTypedDataCall` + `fetcher` POST | -| `deltaTokenModule.ts` | `constructDeltaTokenModule` | On-chain `cancelAndWithdrawDeltaOrder`, `withdrawDeltaNative`, `depositNativeAndPreSign`, `depositNativeAndPreSignDeltaOrder` | `` | `transactCall` | -| `getPartnerFee.ts` | `constructGetPartnerFee` | Fetch partner fee info (internally cached per partner in a `Map`) | No | `fetcher` GET | -| `getBridgeInfo.ts` | `constructGetBridgeInfo` | `getBridgeInfo` (supported routes) + `getBridgeProtocols` | No | `fetcher` GET | -| `isTokenSupportedInDelta.ts` | `constructIsTokenSupportedInDelta` | Check if a token is supported → `boolean` | No | `fetcher` GET | -| `constants.ts` | — | `DEFAULT_BRIDGE` constant (all-zero values for same-chain orders) | — | — | +| File | Constructor | Purpose | Generic? | +|------|-------------|---------|----------| +| `index.ts` | `constructAllDeltaOrdersHandlers`, `constructSubmit{Delta,External,TWAP}Order`, `constructSignDeltaOrder` | Composite: orchestrates all modules, defines `DeltaOrderHandlers`, hosts the single `signDeltaOrder` (signs any `BuiltDeltaOrder`), re-exports every leaf module | `allHandlers`: `` | +| `buildDeltaOrder.ts` | `constructBuildDeltaOrder` | POST `/v2/orders/build` → `BuiltDeltaOrder` | No | +| `buildExternalDeltaOrder.ts` | `constructBuildExternalDeltaOrder` | Same, `orderType: 'ExternalOrder'` | No | +| `buildTWAPDeltaOrder.ts` | `constructBuildTWAPDeltaOrder` | Same, `orderType: 'TWAPOrder'` / `'TWAPBuyOrder'` | No | +| `postDeltaOrder.ts` / `postExternalDeltaOrder.ts` / `postTWAPDeltaOrder.ts` | `constructPost*DeltaOrder` | POST `/v2/orders` → `DeltaAuction` | No | +| `getDeltaPrice.ts` | `constructGetDeltaPrice` | GET `/delta/v2/prices` → `DeltaPrice` (route-based: `route` + `alternatives`; cross-chain handled in-route via `destChainId`) | No | +| `getDeltaOrders.ts` | `constructGetDeltaOrders` | `getDeltaOrders` (paginated list), `getDeltaOrderById`, `getDeltaOrderByHash`, `getRequiredBalanceForDeltaOrders`. Reads return `DeltaAuction`. | No | +| `cancelDeltaOrder.ts` | `constructCancelDeltaOrder` | `signCancelDeltaOrderRequest` → `postCancelDeltaOrderRequest` → `cancelDeltaOrders` (orchestrator). POST `/v2/orders/cancel`. | No (`any`) | +| `getBridgeRoutes.ts` | `constructGetBridgeRoutes` | `getBridgeRoutes` (flat `BridgeRoute[]`) + `getBridgeProtocols` | No | +| `isTokenSupportedInDelta.ts` | `constructIsTokenSupportedInDelta` | GET `/v2/prices/is-token-supported` → `boolean` | No | +| `getAgentsList.ts` | `constructGetAgentsList` | GET `/v2/agents/list/:chainId` → `string[]` | No | +| `getDeltaContract.ts` | `constructGetDeltaContract` | Resolve ParaswapDelta contract address | No | +| `getPartnerFee.ts` | `constructGetPartnerFee` | Fetch partner fee info (cached per partner in a `Map`) | No | +| `approveForDelta.ts` | `constructApproveTokenForDelta` | ERC-20 `approve` with ParaswapDelta as spender | `` | +| `preSignDeltaOrder.ts` / `preSignExternalDeltaOrder.ts` / `preSignTWAPDeltaOrder.ts` | `constructPreSign*DeltaOrder` | On-chain `setPreSignature` + order hashing helpers (`produceDeltaOrderHash`, etc.) | `` | +| `deltaTokenModule.ts` | `constructDeltaTokenModule` | On-chain `cancelAndWithdrawDeltaOrder`, `withdrawDeltaNative`, `depositNativeAndPreSign`, `depositNativeAndPreSignDeltaOrder` | `` | + +The on-chain modules (`preSign*`, `deltaTokenModule`, `approveForDelta`) and the local EIP-712 hashing helpers (`helpers/build*OrderData`, `helpers/misc`) are retained from the original delta module — they back the on-chain flows (pre-signing, native deposit, cancel-and-withdraw) that complement server-side building. + +### Types + +- **`src/methods/delta/types.ts`** (v2 surface) — `BuiltDeltaOrder`, `DeltaPrice` (+ `DeltaRoute` / `DeltaRouteStep` / `DeltaRouteBridge` / `DeltaRouteBridgeContractParams`, `DeltaPriceToken`, `DeltaTokenAmount`, `BridgeTag`, `BridgeRoute`), `DeltaAuction` (the envelope returned by **every** read/post — `status: DeltaOrderStatus`, `input`/`output: DeltaTokenSide`, flat `transactions: DeltaTransaction`, explicit `side`; generic over `onChainOrderType`, narrowing `order` to `OnChainOrderMap[T]`), `DeltaOrderStatus`, `DeltaTokenSide`, `DeltaTransaction`. +- **`src/methods/delta/helpers/types.ts`** (shared on-chain order structs) — `DeltaAuctionOrder`, `ExternalDeltaOrder`, `TWAPDeltaOrder`, `TWAPBuyDeltaOrder`, `ProductiveDeltaOrder`, `Bridge`, `OnChainOrderMap` (Standard/Fillable/External/TWAP Sell/TWAP Buy/Productive), `OnChainOrderType`, `TWAPOnChainOrderType`, `DeltaOrderUnion`, `DeltaOrderType` (`'MARKET' | 'LIMIT'`), `DeltaAmounts*`, `UnifiedDeltaOrderData`, `OrderKind` / `SwapSideToOrderKind`. +- `PaginatedResponse` lives in `src/types.ts`. ### Delta Helpers (`src/methods/delta/helpers/`) -- `types.ts` — `DeltaAuctionOrder`, `Bridge`, `DeltaAuction`, `DeltaAuctionStatus`, `BridgeMetadata`, `BridgeStatus`, `BridgePriceInfo`, `SwapSideToOrderKind`, `OnChainOrderType`, `DeltaAuctionUnion` -- `buildDeltaOrderData.ts` — `buildDeltaSignableOrderData`, `produceDeltaOrderTypedData`, `SignableDeltaOrderData`, `BuildDeltaOrderDataInput`, `DELTA_DEFAULT_EXPIRY` -- `buildCancelDeltaOrderData.ts` — `buildCancelDeltaOrderSignableData`, `SignableCancelDeltaOrderData`, `CancelDeltaOrderData` -- `buildTWAPOrderData.ts` — `buildTWAPSignableOrderData`, `SignableTWAPOrderData`, `BuildTWAPOrderDataInput` -- `buildExternalOrderData.ts` — `buildExternalSignableOrderData`, `SignableExternalOrderData` -- `misc.ts` — `sanitizeDeltaOrderData` (strips extra fields before signing/hashing), `applySlippage`, `resolvePartnerFee` -- `orders.ts` — `OrderHelpers` namespace with `.checks` (type guards: `isDeltaOrder`, `isTWAPOrder`, `isExternalOrder`, `isOrderCrosschain`, `isExecutedAuction`, etc.) and `.getters` (`getUnifiedDeltaOrderData`, `getAuctionAmounts`, `getTwapAuctionAmounts`, `getFilledPercent`, etc.) -- `abi.ts` — shared ABI fragments +- `types.ts` — shared order-struct types (above). +- `buildDeltaOrderData.ts` / `buildExternalOrderData.ts` / `buildTWAPOrderData.ts` — local EIP-712 typed-data builders + `produceDeltaOrderTypedData`, `SignableDeltaOrderData` / `SignableExternalOrderData` / `SignableTWAPOrderData`, `DELTA_DEFAULT_EXPIRY` (consumed by `preSign*` / `deltaTokenModule`). +- `buildCancelDeltaOrderData.ts` — `SignableCancelDeltaOrderData`, `CancelDeltaOrderData`. +- `misc.ts` — `sanitizeDeltaOrderData` (strips extra fields before signing/hashing), `applySlippage`, `resolvePartnerFee`, `producePartnerAndFee`. +- `orders.ts` — single `OrderHelpers` object `{ checks, getters }` (no v1/v2 split): + - `checks`: order-struct guards (`isDeltaOrder`, `isExternalOrder`, `isTWAPOrder` / `isTWAPSellOrder` / `isTWAPBuyOrder`, `isProductiveOrder`, `isOrderCrosschain`); auction discriminants (`isDeltaAuction`, `isExternalAuction`, `isTWAPAuction` / `isTWAPSellAuction` / `isTWAPBuyAuction`, `isProductiveAuction`, `isFillableAuction`); status guards over the v2 envelope (`isCompletedAuction`, `isFailedAuction`, `isCanceledAuction`, `isExpiredAuction`, `isPendingAuction`, `isPartiallyExecutedAuction`). + - `getters`: `getUnifiedDeltaOrderData`, `getAuctionAmounts` (`{ expected, executed }`), `getAuctionTokenAddresses`, `getAuctionSrcChainId` / `getAuctionDestChainId`, `getAuctionSwapSide` (reads `auction.side`), `getTransactionAmounts`, `getFilledPercent`; plus order-level `getOrderTokenAddresses`, `getSwapSideFromDeltaOrder`, `getSwapSideFromTwapOrderType`, `getExpectedTwapSrcAmount` / `getExpectedTwapDestAmount` / `getExpectedTwapOrderAmounts`. +- `abi.ts` — shared ABI fragments. ### Core Types (`src/`) -- `types.ts` — `ConstructProviderFetchInput`, `ContractCallerFunctions`, `TxSendOverrides` +- `types.ts` — `ConstructProviderFetchInput`, `ContractCallerFunctions`, `TxSendOverrides`, `PaginatedResponse` - `helpers/misc.ts` — `ExtractAbiMethodNames` - `sdk/partial.ts` — `constructPartialSDK`, `InferWithTxResponse` type tuple +### `OnChainOrderType` note + +`'ProductiveOrder'` is part of `OnChainOrderType` and `OnChainOrderMap` — `DeltaAuction<'ProductiveOrder'>` resolves to `ProductiveDeltaOrder`. The type is wired through the public surface, but **no build/sign/post helpers** exist for productive orders (`constructSubmit*` / `construct(Build|Post)*` cover only `Order`, `ExternalOrder`, `TWAPOrder`, `TWAPBuyOrder`). Productive orders are read-only from the SDK's perspective. Sides are inferred as `SELL` (productive orders carry no `OrderKind`). + +`'FillableOrder'` is likewise a member of `OnChainOrderType`/`OnChainOrderMap`, resolving (via `DeltaAuction<'FillableOrder'>`) to the same `DeltaAuctionOrder` shape as `'Order'`. It's the `onChainOrderType` the server reports for a `partiallyFillable` Standard order — there is no separate builder. Consumers narrowing a `DeltaAuction` should treat `onChainOrderType === 'FillableOrder'` the same as `'Order'` (`isDeltaAuction(a) || isFillableAuction(a)`). + +## Quote Module (`src/methods/quote/`) + +`constructGetQuote` → `getQuote` hits **`GET /v2/quote`**. Same overloaded, `mode`-based shape as before (`mode: 'delta' | 'market' | 'all'`, with `'all'` returning Delta pricing or a market `fallbackReason`) — the only v2 change is that the delta side is the v2 `DeltaPrice` (route-based) from `methods/delta/types`, not the old v1 `DeltaPrice`/`BridgePrice`. Return types (`QuoteWithDeltaPrice`, `QuoteWithBridgePrice`, `QuoteWithDeltaPriceAndBridgePrice`, `QuoteWithMarketPrice`, `QuoteWithMarketPriceAsFallback`) and all function overloads are preserved. Exposed as `sdk.quote.getQuote`. + ## Checklist: Adding a New On-Chain Method 1. Add ABI to the module file (inline `as const`) @@ -168,4 +200,5 @@ OrderWithSig tuple: - EIP-712 domain: `{ name: 'Portikus', version: '2.0.0', chainId, verifyingContract }` - Order hash: computed via viem's `hashTypedData` in `produceDeltaOrderHash()` - ABI style: always inline `const ... as const`, never imported from external ABI files -- Cross-chain detection: `isOrderCrosschain(order)` checks `bridge.destinationChainId !== 0`; `DEFAULT_BRIDGE` has all-zero values for same-chain orders +- Cross-chain detection: `isOrderCrosschain(order)` checks `bridge.destinationChainId !== 0` +- **Commit messages**: `prefix/what was done` — `prefix` is the file, function, or component that was changed. Examples: `OrderDetails/added button`, `useDeltaPrice/migrate to v2 route fields`. Do NOT add a `Co-Authored-By: Claude` trailer. diff --git a/README.md b/README.md index 4f4005fb5..851f9ddef 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ const allowance = await sdk.getAllowance(userAddress, tokenAddress); ### Basic usage -The easiest way to make a trade is to rely on Quote method that communicates with [/quote API endpoint](https://developers.velora.xyz/api/velora-api/velora-delta-api/retrieve-delta-price-with-fallback-to-market) +The easiest way to make a trade is to rely on the Quote method that communicates with the `/v2/quote` API endpoint. With `mode: 'all'` it returns Delta v2 pricing when possible, falling back to the current market price (use `mode: 'delta'` or `mode: 'market'` to request a single source). ```typescript import axios from 'axios'; @@ -196,11 +196,12 @@ const quote = await simpleSDK.quote.getQuote({ srcDecimals: 18, destDecimals: 18, mode: 'all', // Delta quote if possible, with fallback to Market price - side: 'SELL', // Delta mode only supports side: SELL currenly + side: 'SELL', // Delta mode only supports side: SELL currently // partner: "..." // if available }); if ('delta' in quote) { + // quote.delta is the v2 DeltaPrice (route-based) const deltaPrice = quote.delta; const DeltaContract = await simpleSDK.delta.getDeltaContract(); @@ -208,58 +209,26 @@ if ('delta' in quote) { // or sign a Permit1 or Permit2 TransferFrom for DeltaContract await simpleSDK.delta.approveTokenForDelta(amount, Token1); - const slippagePercent = 0.5; - const destAmountAfterSlippage = BigInt( - // get rid of exponential notation - - +(+deltaPrice.destAmount * (1 - slippagePercent / 100)).toFixed(0) - // get rid of decimals - ).toString(10); - + // order building is server-side: pass the quoted route + side, no local amounts const deltaAuction = await simpleSDK.delta.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // or any deltaPrice.alternatives[i] + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send the output destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: Token1, - destToken: Token2, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount + slippage: 50, // 50 bps = 0.5% }); - // poll if necessary - function isExecutedDeltaAuction( - auction: Omit, - waitForCrosschain = true // only consider executed when destChain work is done - ) { - if (auction.status !== 'EXECUTED') return false; - - // crosschain Order is executed on destChain if bridgeStatus is filled - if (waitForCrosschain && auction.order.bridge.destinationChainId !== 0) { - return auction.bridgeStatus === 'filled'; - } - - return true; - } - - function fetchOrderPeriodically(auctionId: string) { + // poll if necessary — v2 status COMPLETED already accounts for any dest-chain bridge + function startStatusCheck(auctionId: string) { const intervalId = setInterval(async () => { const auction = await simpleSDK.delta.getDeltaOrderById(auctionId); - console.log('checks: ', auction); // Handle or log the fetched auction as needed - - if (isExecutedDeltaAuction(auction)) { - clearInterval(intervalId); // Stop interval if completed + if (auction.status === 'COMPLETED') { + clearInterval(intervalId); console.log('Order completed'); } }, 3000); - console.log('Order Pending'); - // Return intervalId to enable clearing the interval if needed externally - return intervalId; - } - - function startStatusCheck(auctionId: string) { - const intervalId = fetchOrderPeriodically(auctionId); - setTimeout(() => clearInterval(intervalId), 60000 * 5); // Stop after 5 minutes + setTimeout(() => clearInterval(intervalId), 60000 * 5); // stop after 5 minutes } startStatusCheck(deltaAuction.id); @@ -297,33 +266,39 @@ This way the user doesn't need to make a transaction themselve but only to sign (For **Crosschain Delta Orders** refer to a separate documentation page [DELTA.md](./docs/DELTA.md#crosschain-delta-orders) ) -After getting **deltaPrice** from **/quote** endpoint, there are additional steps to sign the Order and wait for its execution. +In v2 the Order is **built by the server** from the quoted route — you sign the returned typed data and post it. After getting **deltaPrice**, there are a few steps to sign the Order and wait for its execution. -### 1. Get deltaPrice from /quote +### 1. Get deltaPrice ```ts const amount = '1000000000000'; // wei const Token1 = '0x1234...' const Token2 = '0xabcde...' +// ... from the quote endpoint (mode: 'delta' or 'all') const quote = await simpleSDK.quote.getQuote({ - srcToken: Token1, // Native token (ETH) is only supported in mode: 'market' + srcToken: Token1, destToken: Token2, amount, userAddress: account, srcDecimals: 18, destDecimals: 18, - mode: 'delta' // or mode: 'all' + mode: 'delta', + side: 'SELL', // partner: "..." // if available -}) - -// if used mode: 'all' -if ('delta' in quote) { - const deltaPrice = quote.delta; -} - -// if used mode: 'delta' +}); const deltaPrice = quote.delta; + +// ... or straight from the Delta price endpoint +const deltaPriceDirect = await simpleSDK.delta.getDeltaPrice({ + srcToken: Token1, + destToken: Token2, + amount, + srcDecimals: 18, + destDecimals: 18, + userAddress: account, + // destChainId: 42161, // for cross-chain — bridge details land in deltaPrice.route.bridge +}); ``` @@ -345,35 +320,28 @@ const signature = await signer._signTypedData(domain, types, message); See more on accepted Permit variants in [Velora documentation](https://developers.velora.xyz/api/velora-api/velora-delta-api/build-a-delta-order-to-sign#supported-permits) -### 3. Sign and submit a Delta Order +### 3. Build, sign and post a Delta Order ```ts -// calculate acceptable destAmount -const slippagePercent = 0.5; - const destAmountAfterSlippage = ( - +deltaPrice.destAmount * - (1 - slippagePercent / 100) - ).toString(10); - -const signableOrderData = await simpleSDK.delta.buildDeltaOrder({ - deltaPrice, +// build is server-side — pass the quoted route + side and your slippage +const built = await simpleSDK.delta.buildDeltaOrder({ + route: deltaPrice.route, // or any deltaPrice.alternatives[i] + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send the output destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: Token1, - destToken: Token2, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount - // partner: "..." // if available + slippage: 50, // 50 bps = 0.5% + // partner, partnerFeeBps — forwarded to the server }); -const signature = await simpleSDK.delta.signDeltaOrder(signableOrderData); +// one signer for every order family (Order / ExternalOrder / TWAPOrder / TWAPBuyOrder) +const signature = await simpleSDK.delta.signDeltaOrder(built); const deltaAuction = await simpleSDK.delta.postDeltaOrder({ - // partner: "..." // if available - // partiallyFillabel: true, // allow the Order to be partially filled as opposed to fill-or-kill - order: signableOrderData.data, + order: built.toSign.value, signature, + // partner: "...", + // partiallyFillable: true, // allow the Order to be partially filled as opposed to fill-or-kill }); ``` @@ -383,15 +351,13 @@ As an option the `buildDeltaOrder + signDeltaOrder + postDeltaOrder` can be comb ```ts const deltaAuction = await simpleSDK.delta.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send output destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - // partiallyFillabel: true, // allow the Order to be partially filled as opposed to fill-or-kill - srcToken: Token1, - destToken: Token2, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount + // partiallyFillable: true, // allow the Order to be partially filled as opposed to fill-or-kill + slippage: 50, }); ``` @@ -399,69 +365,26 @@ This allows to simplify the flow at the expense of control over the Order signin #### 3.b adding partner fee -A portion of destToken will be collected as a partner fee if `partner` parameter is provided to `buildDeltaOrder` (and `submitDeltaOrder`). The `partnerFee` itself is `deltaPrice.partnerFee` - -To examine the default partnerFee parameters (`{partnerAddress: Address, partnerFee: number, takeSurplus: boolean}`), you can call `getPartnerFee` method. These parameters are then encoded in Order.partnerAndFee field. +A portion of destToken is collected as a partner fee when `partner` (and optionally `partnerFeeBps`) is provided to `buildDeltaOrder` / `submitDeltaOrder`. In v2 these are forwarded to the server, which encodes them into `Order.partnerAndFee`. You can inspect the default partner-fee parameters with `getPartnerFee`: ```ts const partnerFeeResponse = await simpleSDK.delta.getPartnerFee({ partner }); ``` -Alternatively, you can supply your own partnerFee parameters that will be encoded in Order.partnerAndFee field - -```ts -const signableOrderData = await simpleSDK.delta.buildDeltaOrder({ - deltaPrice, - owner: account, - // beneficiary: anotherAccount, // if need to send the output destToken to another account - // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - // partiallyFillabel: true, // allow the Order to be partially filled as opposed to fill-or-kill - srcToken: Token1, - destToken: Token2, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount - partnerAddress: '0x1234...', - partnerFee: 0.12, - takeSurplus: true, -}); -``` - ### 4. Wait for Delta Order execution ```ts -// poll if necessary -function isExecutedDeltaAuction( - auction: Omit, - waitForCrosschain = true // only consider executed when destChain work is done -) { - if (auction.status !== 'EXECUTED') return false; - - // crosschain Order is executed on destChain if bridgeStatus is filled - if (waitForCrosschain && auction.order.bridge.destinationChainId !== 0) { - return auction.bridgeStatus === 'filled'; - } - - return true; -} - -function fetchOrderPeriodically(auctionId: string) { +// poll if necessary — v2 status COMPLETED already accounts for any dest-chain bridge +function startStatusCheck(auctionId: string) { const intervalId = setInterval(async () => { const auction = await simpleSDK.delta.getDeltaOrderById(auctionId); - console.log('checks: ', auction); // Handle or log the fetched auction as needed - - if (isExecutedDeltaAuction(auction)) { - clearInterval(intervalId); // Stop interval if completed + if (auction.status === 'COMPLETED') { + clearInterval(intervalId); // stop interval once completed console.log('Order completed'); } }, 3000); - console.log('Order Pending'); - // Return intervalId to enable clearing the interval if needed externally - return intervalId; -} - -function startStatusCheck(auctionId: string) { - const intervalId = fetchOrderPeriodically(auctionId); - setTimeout(() => clearInterval(intervalId), 60000 * 5); // Stop after 5 minutes + // stop polling after 5 minutes + setTimeout(() => clearInterval(intervalId), 60000 * 5); } startStatusCheck(deltaAuction.id); @@ -475,13 +398,102 @@ For **External Delta Orders** (orders that delegate token handling to an externa ------------ +### Advanced Delta usage + +#### TWAP order + +A TWAP sell splits `totalSrcAmount` into `numSlices` equal slices executed `interval` seconds apart. Price is quoted for a single slice — the server multiplies amounts internally. + +```ts +const perSliceAmount = (BigInt(totalSrcAmount) / BigInt(numSlices)).toString(); + +const slicePrice = await simpleSDK.delta.getDeltaPrice({ + srcToken: Token1, + destToken: Token2, + amount: perSliceAmount, + srcDecimals: 18, + destDecimals: 18, + userAddress: account, +}); + +await simpleSDK.delta.approveTokenForDelta(totalSrcAmount, Token1); + +const twapAuction = await simpleSDK.delta.submitTWAPDeltaOrder({ + onChainOrderType: 'TWAPOrder', // or 'TWAPBuyOrder' + route: slicePrice.route, + owner: account, + totalSrcAmount, + numSlices, + interval: 300, // seconds (min 60) + slippage: 50, +}); +``` + +#### Partial SDK + +For bundle-savvy code, pull only the Delta constructors you need — they tree-shake cleanly. + +```ts +import { + constructPartialSDK, + constructFetchFetcher, + constructGetDeltaPrice, + constructGetDeltaOrders, + constructGetBridgeRoutes, +} from '@velora-dex/sdk'; + +const sdk = constructPartialSDK( + { chainId: 1, fetcher: constructFetchFetcher(fetch) }, + constructGetDeltaPrice, + constructGetDeltaOrders, + constructGetBridgeRoutes, +); + +const price = await sdk.getDeltaPrice({ /* ... */ }); +const orders = await sdk.getDeltaOrders({ userAddress: account, page: 1, limit: 50 }); +``` + +A complete example (standard, external handler, TWAP, and order listing) is in [examples/delta](./src/examples/delta.ts). + +#### Productive Orders (read-only) + +Alongside the four buildable families (`Order`, `ExternalOrder`, `TWAPOrder`, `TWAPBuyOrder`), the SDK surfaces a fifth `onChainOrderType` — **`ProductiveOrder`** — through the read endpoints (`sdk.delta.getDeltaOrderById`, `sdk.delta.getDeltaOrders`, etc.). Productive orders are produced and managed entirely by the server: there are deliberately **no `buildProductiveOrder`, `signProductiveOrder`, or `submitProductiveOrder` helpers**. The shape is wired through `OnChainOrderType` and `OnChainOrderMap` so that consumers iterating over an order list can type-narrow on `onChainOrderType === 'ProductiveOrder'` and read the order safely — productive orders carry no `OrderKind`, so the side is always `SELL`. + +`OnChainOrderType` additionally includes **`'FillableOrder'`**, which maps to the same shape as a Standard `'Order'` (`DeltaAuctionOrder`). It is not a separate buildable family — it's the `onChainOrderType` the server reports for a `partiallyFillable` Standard order, so treat it the same as `'Order'` when narrowing. + +#### Order helpers + +`OrderHelpers` exposes `{ checks, getters }` for auctions returned by `sdk.delta.*` read/post methods (`status: DeltaOrderStatus`, `input`/`output`, flat `transactions`, explicit `side`): + +```ts +import { OrderHelpers } from '@velora-dex/sdk'; + +const { checks, getters } = OrderHelpers; + +if (checks.isProductiveAuction(auction)) { + // read-only, no SDK builder +} else if (checks.isFillableAuction(auction) || checks.isDeltaAuction(auction)) { + // treat FillableOrder the same as a standard Order +} + +if (checks.isCompletedAuction(auction)) { + const { expected, executed } = getters.getAuctionAmounts(auction); + // `executed` — matches the API convention +} + +const unified = getters.getUnifiedDeltaOrderData(auction); +// { srcChainId, destChainId, srcToken, destToken, swapSide, filledPercent, amounts, ... } +``` + +------------ + ### Market Swap handling #### A more detailed overview of the Trade Flow, Market variant. Unlike the Delta Order, a Market swap requires the user themselves to submit a Swap transaction -### 1. Get Market priceRoute from /quote +### 1. Get Market priceRoute from /v2/quote ```ts const amount = '1000000000000'; // wei @@ -489,22 +501,17 @@ const Token1 = '0x1234...' const Token2 = '0xabcde...' const quote = await simpleSDK.quote.getQuote({ - srcToken: Token1, // Native token (ETH) is only supported in mode: 'market' + srcToken: Token1, destToken: Token2, amount, userAddress: account, srcDecimals: 18, destDecimals: 18, - mode: 'market' + mode: 'market', + side: 'SELL', // or 'BUY' // partner: "..." // if available }) -// if used mode: 'all' -if ('market' in quote) { - const priceRoute = quote.market; -} - -// if used mode: 'market' const priceRoute = quote.market; ``` diff --git a/docs/DELTA.md b/docs/DELTA.md index 4b251b13b..24a86a2b8 100644 --- a/docs/DELTA.md +++ b/docs/DELTA.md @@ -1,6 +1,7 @@ **Velora Delta** is an intent-based protocol that enables a Velora user to make gasless swaps where multiple agents compete to execute the trade at the best price possible. This way the user doesn't need to make a transaction themselves but only to sign a Delta Order. -The easiest way to make use of the Delta Order is to use the SDK following these steps: + +In Delta v2 the Order is **built by the server** from the route returned in the price response — you sign the returned typed data and post it. The easiest way to make use of the Delta Order is to use the SDK following these steps: ### 1. Construct an SDK object @@ -32,6 +33,8 @@ const deltaPrice = await deltaSDK.getDeltaPrice({ destDecimals: 6, // partner: "..." // if available }); + +// deltaPrice.route is the recommended route; deltaPrice.alternatives holds the rest ``` @@ -56,68 +59,42 @@ See more on accepted Permit variants in [Velora documentation](https://developer ### 4. Sign and submit a Delta Order -```ts -// calculate acceptable destAmount -const slippagePercent = 0.5; - const destAmountAfterSlippage = ( - +deltaPrice.destAmount * - (1 - slippagePercent / 100) - ).toString(10); +Order building is server-side: pass the quoted `route` + `side` and your acceptable `slippage` — the server computes the amounts. `submitDeltaOrder` bundles build → sign → post. +```ts const deltaAuction = await deltaSDK.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // or any deltaPrice.alternatives[i] + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount + slippage: 50, // 50 bps = 0.5% + // partner, partnerFeeBps — forwarded to the server }); ``` +For full control over signing, use `buildDeltaOrder` → `signDeltaOrder` → `postDeltaOrder` separately (see the main [README](../README.md)). + ### 5. Wait for Delta Order execution ```ts -// poll if necessary -function isExecutedDeltaAuction( - auction: Omit, - waitForCrosschain = true // only consider executed when destChain work is done -) { - if (auction.status !== 'EXECUTED') return false; - - // crosschain Order is executed on destChain if bridgeStatus is filled - if (waitForCrosschain && auction.order.bridge.destinationChainId !== 0) { - return auction.bridgeStatus === 'filled'; - } - - return true; -} - -function fetchOrderPeriodically(auctionId: string) { +// poll if necessary — v2 status COMPLETED already accounts for any dest-chain bridge +function startStatusCheck(auctionId: string) { const intervalId = setInterval(async () => { - const auction = await simpleSDK.delta.getDeltaOrderById(auctionId); - console.log('checks: ', auction); // Handle or log the fetched auction as needed - - if (isExecutedDeltaAuction(auction)) { - clearInterval(intervalId); // Stop interval if completed + const auction = await deltaSDK.getDeltaOrderById(auctionId); + if (auction.status === 'COMPLETED') { + clearInterval(intervalId); // stop interval once completed console.log('Order completed'); } }, 3000); - console.log('Order Pending'); - // Return intervalId to enable clearing the interval if needed externally - return intervalId; -} - -function startStatusCheck(auctionId: string) { - const intervalId = fetchOrderPeriodically(auctionId); - setTimeout(() => clearInterval(intervalId), 60000 * 5); // Stop after 5 minutes + // stop polling after 5 minutes + setTimeout(() => clearInterval(intervalId), 60000 * 5); } startStatusCheck(deltaAuction.id); ``` -#### A more detailed example of Delta Order usage can be found in [examples/delta](./src/examples/delta.ts) +#### A more detailed example of Delta Order usage can be found in [examples/delta](../src/examples/delta.ts) @@ -147,12 +124,15 @@ const deltaSDK = constructSimpleSDK( ### 2. Check which tokens are available on the destination chain. -A limited list of tokens are available in Across, the service facilitating crosschain bridging +A limited list of tokens are available in Across, the service facilitating crosschain bridging. ```ts -const bridgeInfo = await deltaSDK.getBridgeInfo(); +const bridgeRoutes = await deltaSDK.getBridgeRoutes(); -const tokensAvailableForBridging = bridgeInfo[SRC_CHAIN_ID]?.[DEST_CHAIN_ID] +const tokensAvailableForBridging = bridgeRoutes.find( + (route) => + route.srcChainId === SRC_CHAIN_ID && route.destChainId === DEST_CHAIN_ID +)?.tokens; ``` @@ -174,6 +154,8 @@ const deltaPrice = await deltaSDK.getDeltaPrice({ destDecimals: 6, // partner: "..." // if available }); + +// the returned deltaPrice.route already encodes the bridge step for the crosschain swap ``` @@ -198,71 +180,35 @@ See more on accepted Permit variants in [Velora documentation](https://developer ### 5. Sign and submit a Delta Order -```ts -// calculate acceptable destAmount -const slippagePercent = 0.5; -const destAmountAfterSlippage = ( - +deltaPrice.destAmount * - (1 - slippagePercent / 100) -).toString(10); +The crosschain `route` (from a `getDeltaPrice` call with `destChainId`) already carries the bridge details, so submitting is the same as a same-chain order — the server builds the cross-chain Order from the route. +```ts const deltaAuction = await deltaSDK.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // crosschain route, includes the bridge step + side: deltaPrice.side, owner: account, + // beneficiary: anotherAccount, // if need to send destToken to another account on destChain // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN_ON_DEST_CHAIN, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount - destChainId: DEST_CHAIN_ID, // required to construct a Crosschain Order - beneficiary: anotherAccount, // if need to send destToken to another account on destChain - beneficiaryType: 'EOA', // whether the beneficiary on destChain is a smart contract - // bridge, // user-constructed Bridge object for Crosschain Orders + slippage: 50, // 50 bps = 0.5% }); ``` -To construct a Crosschain Delta Order it is required to either: -* provide both `beneficiary` address and `beneficiaryType` value, so that the `Order.bridge` can be constructed automatically by the SDK. -* construct Bridge object. Refer to [documentation](https://developers.velora.xyz/api/velora-api/velora-delta-api/build-a-delta-order-to-sign#sign-an-order-cross-chain) for how to do that. - -This is necessary because Across, the service facilitating crosschain bridging, has [special logic when it comes to transferring ETH and WETH](https://docs.across.to/introduction/technical-faq#what-is-the-behavior-of-eth-weth-in-transfers). +Across, the service facilitating crosschain bridging, has [special logic when it comes to transferring ETH and WETH](https://docs.across.to/introduction/technical-faq#what-is-the-behavior-of-eth-weth-in-transfers). Refer to the [Velora documentation](https://developers.velora.xyz/api/velora-api/velora-delta-api/build-a-delta-order-to-sign#sign-an-order-cross-chain) for more on cross-chain orders. -### 5. Wait for Delta Order execution +### 6. Wait for Delta Order execution ```ts -// poll if necessary -function isExecutedDeltaAuction( - auction: Omit, - waitForCrosschain = true // only consider executed when destChain work is done -) { - if (auction.status !== 'EXECUTED') return false; - - // crosschain Order is executed on destChain if bridgeStatus is filled - if (waitForCrosschain && auction.order.bridge.destinationChainId !== 0) { - return auction.bridgeStatus === 'filled'; - } - - return true; -} - -function fetchOrderPeriodically(auctionId: string) { +// poll if necessary — v2 status COMPLETED already accounts for the dest-chain bridge +function startStatusCheck(auctionId: string) { const intervalId = setInterval(async () => { - const auction = await simpleSDK.delta.getDeltaOrderById(auctionId); - console.log('checks: ', auction); // Handle or log the fetched auction as needed - - if (isExecutedDeltaAuction(auction)) { - clearInterval(intervalId); // Stop interval if completed + const auction = await deltaSDK.getDeltaOrderById(auctionId); + if (auction.status === 'COMPLETED') { + clearInterval(intervalId); // stop interval once completed console.log('Order completed'); } }, 3000); - console.log('Order Pending'); - // Return intervalId to enable clearing the interval if needed externally - return intervalId; -} - -function startStatusCheck(auctionId: string) { - const intervalId = fetchOrderPeriodically(auctionId); - setTimeout(() => clearInterval(intervalId), 60000 * 5); // Stop after 5 minutes + // stop polling after 5 minutes + setTimeout(() => clearInterval(intervalId), 60000 * 5); } startStatusCheck(deltaAuction.id); diff --git a/docs/EXTERNAL_ORDERS.md b/docs/EXTERNAL_ORDERS.md index f9cf20cf0..8688a2315 100644 --- a/docs/EXTERNAL_ORDERS.md +++ b/docs/EXTERNAL_ORDERS.md @@ -2,6 +2,8 @@ External orders delegate token handling to an external **handler** contract, enabling complex DeFi strategies that go beyond simple token swaps. The handler manages the actual token logic (e.g. Aave flash loan + collateral/debt swap), while the Delta protocol handles the auction, settlement, and signature verification. +As with all Delta v2 orders, the Order is **built by the server** from the route returned by `getDeltaPrice` — you sign the returned typed data and post it. + ### Handler Contract The handler contract must be deployed by the integrator and implement the `IExternalProtocolHandler` interface: @@ -25,12 +27,13 @@ The `data` field carries protocol-specific parameters (e.g. an Aave operation ty | Feature | Standard Order | External Order | |---|---|---| -| `beneficiary` field | yes | no (output goes to owner) | -| `bridge` field | yes | no | | `handler` field | no | yes (required) | | `data` field | no | yes (required) | +| `bridge` field | yes | no | | Cross-chain support | yes | no | +`beneficiary` is supported by both (defaults to `owner`). + --- @@ -64,7 +67,7 @@ const deltaPrice = await deltaSDK.getDeltaPrice({ userAddress: account, srcDecimals: 6, destDecimals: 18, - side: SwapSide.SELL, + side: 'SELL', }); ``` @@ -83,41 +86,37 @@ await aUSDC.approve(AAVE_HANDLER, amount); #### Using `submitExternalDeltaOrder` (recommended) -The simplest approach — builds, signs, and posts the order in one call: +The simplest approach — builds, signs, and posts the order in one call. Pass the quoted `route` + `side` and your `slippage`; the server computes the amounts. ```ts const deltaAuction = await deltaSDK.submitExternalDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // or any deltaPrice.alternatives[i] + side: deltaPrice.side, owner: account, handler: AAVE_HANDLER, // your deployed handler contract data: '0x0000000000000000000000000000000000000000000000000000000000000000', // protocol-specific bytes - srcToken: USDC, - destToken: WETH, - srcAmount: amount, slippage: 50, // 0.5% in bps }); ``` #### Using individual steps -For more control, you can build, sign, and post separately: +For more control, you can build, sign, and post separately. A single `signDeltaOrder` signs every order family. ```ts -const signableOrderData = await deltaSDK.buildExternalDeltaOrder({ - deltaPrice, +const built = await deltaSDK.buildExternalDeltaOrder({ + route: deltaPrice.route, + side: deltaPrice.side, owner: account, handler: AAVE_HANDLER, data: '0x0000000000000000000000000000000000000000000000000000000000000000', - srcToken: USDC, - destToken: WETH, - srcAmount: amount, slippage: 50, // 0.5% in bps }); -const signature = await deltaSDK.signExternalDeltaOrder(signableOrderData); +const signature = await deltaSDK.signDeltaOrder(built); const deltaAuction = await deltaSDK.postExternalDeltaOrder({ - order: signableOrderData.data, + order: built.toSign.value, signature, }); ``` @@ -129,7 +128,7 @@ const intervalId = setInterval(async () => { const auction = await deltaSDK.getDeltaOrderById(deltaAuction.id); console.log('Status:', auction.status); - if (auction.status === 'EXECUTED' || auction.status === 'FAILED') { + if (auction.status === 'COMPLETED' || auction.status === 'FAILED') { clearInterval(intervalId); } }, 3000); @@ -141,9 +140,9 @@ const intervalId = setInterval(async () => { // fetch a specific external order const order = await deltaSDK.getDeltaOrderById(orderId); -// list external orders only -const orders = await deltaSDK.getDeltaOrders({ - owner: account, +// list external orders only (paginated — `data` holds the orders) +const { data: externalOrders } = await deltaSDK.getDeltaOrders({ + userAddress: account, onChainOrderType: 'ExternalOrder', }); ``` @@ -152,34 +151,27 @@ const orders = await deltaSDK.getDeltaOrders({ ## Specifying Amounts -There are two ways to specify amounts: +Amounts are derived server-side from the quoted `route`. Two ways to control them: -**With `slippage` (recommended)** — the SDK computes the slippage-adjusted amount from `deltaPrice` automatically: +**With `slippage` (recommended)** — the server computes the slippage-adjusted amount from the route: ```ts -// SELL: provide srcAmount + slippage → destAmount auto-computed await deltaSDK.buildExternalDeltaOrder({ + route: deltaPrice.route, + side: deltaPrice.side, // 'SELL' or 'BUY' // ... - srcAmount: amount, slippage: 50, // 0.5% in bps }); - -// BUY: provide destAmount + slippage → srcAmount auto-computed -await deltaSDK.buildExternalDeltaOrder({ - // ... - destAmount: amount, - slippage: 50, -}); ``` -**With explicit amounts** — you compute both amounts yourself: +**With an explicit `limitAmount`** — pin the limit yourself; the server uses it as the SELL `destAmount` (or BUY `srcAmount`) and `expectedAmount`: ```ts await deltaSDK.buildExternalDeltaOrder({ + route: deltaPrice.route, + side: deltaPrice.side, // ... - srcAmount: amount, - destAmount: destAmountAfterSlippage, - side: SwapSide.SELL, // or SwapSide.BUY + limitAmount: destAmountLimit, }); ``` @@ -187,31 +179,29 @@ await deltaSDK.buildExternalDeltaOrder({ ## Pre-signing External Orders -For smart contract wallets or other cases where EIP-712 signatures are not available, you can pre-sign an external order on-chain: +For smart contract wallets or other cases where EIP-712 signatures are not available, you can pre-sign an external order on-chain using the `orderHash` returned by the server build: ```ts -const signableOrderData = await deltaSDK.buildExternalDeltaOrder({ - deltaPrice, +const built = await deltaSDK.buildExternalDeltaOrder({ + route: deltaPrice.route, + side: deltaPrice.side, owner: account, handler: AAVE_HANDLER, data: '0x0000000000000000000000000000000000000000000000000000000000000000', - srcToken: USDC, - destToken: WETH, - srcAmount: amount, slippage: 50, }); -// on-chain pre-sign transaction -const tx = await deltaSDK.preSignExternalDeltaOrder(signableOrderData); +// on-chain pre-sign transaction (setPreSignature for the built order hash) +const tx = await deltaSDK.setExternalDeltaOrderPreSignature(built.orderHash); await tx.wait(); // post with empty signature const deltaAuction = await deltaSDK.postExternalDeltaOrder({ - order: signableOrderData.data, + order: built.toSign.value, signature: '0x', }); ``` --- -#### A more detailed example can be found in [examples/externalDelta](../src/examples/externalDelta.ts) +#### A more detailed example can be found in [examples/delta](../src/examples/delta.ts) diff --git a/src/examples/delta.ts b/src/examples/delta.ts index 208e4d948..3b371cfef 100644 --- a/src/examples/delta.ts +++ b/src/examples/delta.ts @@ -22,7 +22,6 @@ const contractCaller = constructEthersContractCaller( account ); -// type AdaptersFunctions & ApproveTokenFunctions const deltaSDK = constructPartialSDK( { chainId: 1, @@ -55,19 +54,18 @@ async function simpleDeltaFlow() { await tx.wait(); const deltaAuction = await deltaSDK.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // or pick from deltaPrice.alternatives + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - srcAmount: amount, - slippage: 50, // 50 bps = 0.5% slippage, destAmount auto-computed from deltaPrice + slippage: 50, // 50 bps = 0.5% slippage }); // poll if necessary startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); } + async function manualDeltaFlow() { const amount = '1000000000000'; // wei @@ -87,25 +85,112 @@ async function manualDeltaFlow() { const tx = await deltaSDK.approveTokenForDelta(amount, DAI_TOKEN); await tx.wait(); - const signableOrderData = await deltaSDK.buildDeltaOrder({ - deltaPrice, + // server-side build (returns EIP-712 typed data + orderHash) + const builtOrder = await deltaSDK.buildDeltaOrder({ + route: deltaPrice.route, // or pick from deltaPrice.alternatives + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - srcAmount: amount, - slippage: 50, // 50 bps = 0.5% slippage, destAmount auto-computed from deltaPrice + slippage: 50, // 50 bps = 0.5% slippage }); - const signature = await deltaSDK.signDeltaOrder(signableOrderData); + // one signer for every v2 order type (Order / ExternalOrder / TWAPOrder / TWAPBuyOrder) + const signature = await deltaSDK.signDeltaOrder(builtOrder); const deltaAuction = await deltaSDK.postDeltaOrder({ // partner: "..." // if available - order: signableOrderData.data, + order: builtOrder.toSign.value, signature, }); // poll if necessary startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); } + +// External orders forward execution to an integrator-provided handler contract. +// Same build/sign/post shape as standard orders, plus { handler, data }. +// See docs/EXTERNAL_ORDERS.md for handler-specific details (Aave collateral swap, etc.). +async function externalDeltaFlow() { + const amount = ethers.utils.parseUnits('1', 6).toString(); // 1 USDC + + const deltaPrice = await deltaSDK.getDeltaPrice({ + srcToken: USDC_TOKEN, + destToken: DAI_TOKEN, + amount, + userAddress: account, + srcDecimals: 6, + destDecimals: 18, + }); + + const HANDLER = '0xb4a2c36668cf8b19fe08f263e3685a5e16e82912'; // handler contract + const HANDLER_DATA = + '0x0000000000000000000000000000000000000000000000000000000000000000'; // handler-specific encoded bytes + + const builtOrder = await deltaSDK.buildExternalDeltaOrder({ + route: deltaPrice.route, + side: deltaPrice.side, + owner: account, + handler: HANDLER, + data: HANDLER_DATA, + slippage: 50, + }); + + const signature = await deltaSDK.signDeltaOrder(builtOrder); + + const deltaAuction = await deltaSDK.postExternalDeltaOrder({ + order: builtOrder.toSign.value, + signature, + }); + + startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); +} + +// TWAP sell splits a total srcAmount into N equal slices executed `interval` seconds apart. +// The price quote is fetched for a single slice; the server multiplies amounts by numSlices. +async function twapSellDeltaFlow() { + const numSlices = 4; + const totalSrcAmount = ethers.utils.parseUnits('100', 18).toString(); // 100 DAI total + const perSliceAmount = ( + BigInt(totalSrcAmount) / BigInt(numSlices) + ).toString(); + + // quote a single slice — route amounts must match floor(totalSrcAmount / numSlices) + const deltaPrice = await deltaSDK.getDeltaPrice({ + srcToken: DAI_TOKEN, + destToken: USDC_TOKEN, + amount: perSliceAmount, + userAddress: account, + srcDecimals: 18, + destDecimals: 6, + }); + + const tx = await deltaSDK.approveTokenForDelta(totalSrcAmount, DAI_TOKEN); + await tx.wait(); + + const deltaAuction = await deltaSDK.submitTWAPDeltaOrder({ + onChainOrderType: 'TWAPOrder', + route: deltaPrice.route, + owner: account, + totalSrcAmount, + numSlices, + interval: 300, // 5 minutes between slices (min 60) + slippage: 50, + }); + + startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); +} + +// Paginated list of a user's orders (returns a { data, total, page, limit, hasMore } envelope). +async function listUserOrders() { + const page1 = await deltaSDK.getDeltaOrders({ + userAddress: account, + page: 1, + limit: 50, + // status: ['ACTIVE', 'BRIDGING'], + // type: 'MARKET', + }); + + console.log('orders:', page1.data); + console.log('total:', page1.total, 'hasMore:', page1.hasMore); +} diff --git a/src/examples/externalDelta.ts b/src/examples/externalDelta.ts deleted file mode 100644 index 6144d5b4f..000000000 --- a/src/examples/externalDelta.ts +++ /dev/null @@ -1,162 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import axios from 'axios'; -import { ethers, Wallet } from 'ethersV5'; -import { - constructPartialSDK, - constructEthersContractCaller, - constructAxiosFetcher, - constructAllDeltaOrdersHandlers, - SwapSide, -} from '..'; -import { startStatusCheck } from './helpers/delta'; - -const fetcher = constructAxiosFetcher(axios); - -const provider = ethers.getDefaultProvider(1); // Ethereum -const signer = Wallet.createRandom().connect(provider); -const account = signer.address; -const contractCaller = constructEthersContractCaller( - { - ethersProviderOrSigner: signer, - EthersContract: ethers.Contract, - }, - account -); - -const deltaSDK = constructPartialSDK( - { - chainId: 1, // Ethereum - fetcher, - contractCaller, - }, - constructAllDeltaOrdersHandlers -); - -// Ethereum tokens -const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; -const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; - -// Aave external handler on Ethereum (https://etherscan.io/address/0xb4a2c36668cf8b19fe08f263e3685a5e16e82912#code) -// The handler contract is provided by the integrator and must implement IExternalProtocolHandler. -// Different handlers serve different purposes and may have different prerequisites -// (e.g. token approvals, credit delegation, position setup). -const AAVE_HANDLER = '0xb4a2c36668cf8b19fe08f263e3685a5e16e82912'; - -// Aave-specific order types passed as `data` field. -// The `data` encoding is handler-specific — each handler defines its own format. -const AaveOrderTypes = { - COLLATERAL_SWAP: - '0x0000000000000000000000000000000000000000000000000000000000000000', - DEBT_SWAP: - '0x0000000000000000000000000000000000000000000000000000000000000001', - REPAY_WITH_COLLATERAL: - '0x0000000000000000000000000000000000000000000000000000000000000002', -}; - -// Aave Collateral Swap: swap one collateral asset for another (SELL side) -// Prerequisites: user must approve the source aToken to the handler -async function collateralSwapFlow() { - const amount = ethers.utils.parseUnits('1', 6).toString(); // 1 USDC - - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: USDC, - destToken: WETH, - amount, - userAddress: account, - srcDecimals: 6, - destDecimals: 18, - side: SwapSide.SELL, - }); - - const signableOrderData = await deltaSDK.buildExternalDeltaOrder({ - deltaPrice, - owner: account, - handler: AAVE_HANDLER, - data: AaveOrderTypes.COLLATERAL_SWAP, - srcToken: USDC, - destToken: WETH, - srcAmount: amount, - slippage: 50, // 0.5% slippage in bps - }); - - const signature = await deltaSDK.signExternalDeltaOrder(signableOrderData); - - const deltaAuction = await deltaSDK.postExternalDeltaOrder({ - order: signableOrderData.data, - signature, - }); - - startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); -} - -// Aave Debt Swap: swap one debt for another (BUY side) -// Prerequisites: user must grant borrowAllowance on the source variable debt token to the handler -async function debtSwapFlow() { - const debtAmount = ethers.utils.parseUnits('1', 6).toString(); // amount of debt to swap - - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: USDC, - destToken: WETH, - amount: debtAmount, - userAddress: account, - srcDecimals: 6, - destDecimals: 18, - side: SwapSide.BUY, - }); - - const signableOrderData = await deltaSDK.buildExternalDeltaOrder({ - deltaPrice, - owner: account, - handler: AAVE_HANDLER, - data: AaveOrderTypes.DEBT_SWAP, - srcToken: USDC, - destToken: WETH, - destAmount: debtAmount, - slippage: 50, // 0.5% slippage in bps - }); - - const signature = await deltaSDK.signExternalDeltaOrder(signableOrderData); - - const deltaAuction = await deltaSDK.postExternalDeltaOrder({ - order: signableOrderData.data, - signature, - }); - - startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); -} - -// Aave Repay with Collateral: use collateral to repay debt (BUY side) -// Prerequisites: user must approve the source aToken to the handler -async function repayWithCollateralFlow() { - const collateralAmount = ethers.utils.parseUnits('1', 6).toString(); - - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: USDC, - destToken: WETH, - amount: collateralAmount, - userAddress: account, - srcDecimals: 6, - destDecimals: 18, - side: SwapSide.BUY, - }); - - const signableOrderData = await deltaSDK.buildExternalDeltaOrder({ - deltaPrice, - owner: account, - handler: AAVE_HANDLER, - data: AaveOrderTypes.REPAY_WITH_COLLATERAL, - srcToken: USDC, - destToken: WETH, - destAmount: collateralAmount, - slippage: 50, // 0.5% slippage in bps - }); - - const signature = await deltaSDK.signExternalDeltaOrder(signableOrderData); - - const deltaAuction = await deltaSDK.postExternalDeltaOrder({ - order: signableOrderData.data, - signature, - }); - - startStatusCheck(() => deltaSDK.getDeltaOrderById(deltaAuction.id)); -} diff --git a/src/examples/helpers/delta.ts b/src/examples/helpers/delta.ts index 0c1247380..9b3b2b788 100644 --- a/src/examples/helpers/delta.ts +++ b/src/examples/helpers/delta.ts @@ -1,31 +1,19 @@ import { DeltaAuction } from '../..'; -function isExecutedDeltaAuction( - auction: DeltaAuction, - waitForCrosschain = true // only consider executed when destChain work is done -) { - if (auction.status !== 'EXECUTED') return false; - - // crosschain Order is executed on destChain if bridgeStatus is filled - if ( - waitForCrosschain && - auction.onChainOrderType === 'Order' && - auction.order.bridge.destinationChainId !== 0 - ) { - return auction.bridgeStatus === 'filled'; - } - - return true; +// v2 status COMPLETED already accounts for destChain bridge settlement +// (crosschain orders sit in BRIDGING until the destChain leg is done). +function isCompletedDeltaOrder(order: DeltaAuction) { + return order.status === 'COMPLETED'; } type GetDeltaOrderFn = () => Promise; function fetchOrderPeriodically(getDeltaOrder: GetDeltaOrderFn) { const intervalId = setInterval(async () => { - const auction = await getDeltaOrder(); - console.log('checks: ', auction); // Handle or log the fetched auction as needed + const order = await getDeltaOrder(); + console.log('checks: ', order); // Handle or log the fetched order as needed - if (isExecutedDeltaAuction(auction)) { + if (isCompletedDeltaOrder(order)) { clearInterval(intervalId); // Stop interval if completed console.log('Order completed'); } @@ -37,5 +25,10 @@ function fetchOrderPeriodically(getDeltaOrder: GetDeltaOrderFn) { export function startStatusCheck(getDeltaOrder: GetDeltaOrderFn) { const intervalId = fetchOrderPeriodically(getDeltaOrder); - setTimeout(() => clearInterval(intervalId), 60000 * 5); // Stop after 5 minutes + const timeoutId = setTimeout(() => clearInterval(intervalId), 60000 * 5); // Stop after 5 minutes + + return () => { + clearInterval(intervalId); + clearTimeout(timeoutId); + }; } diff --git a/src/examples/quote.ts b/src/examples/quote.ts index d2d0a119f..c62de988e 100644 --- a/src/examples/quote.ts +++ b/src/examples/quote.ts @@ -40,74 +40,10 @@ const DAI_TOKEN = '0x6b175474e89094c44da98b954eedeac495271d0f'; const USDC_TOKEN = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; /** - * mode='delta' example + * mode='all' returns Delta pricing when possible, with Market price as a fallback. + * (mode='delta' and mode='market' request a single pricing source.) */ -async function deltaQuote() { - const amount = '1000000000000'; // wei - - const quote = await quoteSDK.getQuote({ - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - amount, - userAddress: account, - srcDecimals: 18, - destDecimals: 6, - mode: 'delta', - side: 'SELL', - // partner: "..." // if available - }); - - try { - const deltaPrice = quote.delta; - await handleDeltaQuote({ amount, deltaPrice }); - } catch (error) { - if (isFetcherError(error)) { - const data = error.response?.data; - console.log(`Delta Quote failed: ${data.errorType} - ${data.details}`); - } - } -} - -/** - * mode='market' example - */ -async function marketQuote() { - const amount = '1000000000000'; // wei - - const quote = await quoteSDK.getQuote({ - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - amount, - userAddress: account, - srcDecimals: 18, - destDecimals: 6, - mode: 'market', - side: 'SELL', - // partner: "..." // if available - }); - - const TokenTransferProxy = await quoteSDK.getSpender(); - - // or sign a Permit1 or Permit2 TransferFrom for TokenTransferProxy - const approveTxHash = quoteSDK.approveToken(amount, DAI_TOKEN); - - const txParams = await quoteSDK.buildTx({ - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - srcAmount: amount, - slippage: 250, // 2.5% - priceRoute: quote.market, - userAddress: account, - // partner: '...' // if available - }); - - const swapTx = await handleMarketQuote({ amount, priceRoute: quote.market }); -} - -/** - * mode='all' example - */ -async function allQuote() { +async function quoteExample() { const amount = '1000000000000'; // wei const quote = await quoteSDK.getQuote({ @@ -123,16 +59,14 @@ async function allQuote() { }); if ('delta' in quote) { - const deltaPrice = quote.delta; - await handleDeltaQuote({ amount, deltaPrice }); + // Delta path — quote.delta is the v2 DeltaPrice (route-based) + await handleDeltaQuote({ amount, deltaPrice: quote.delta }); } else { console.log( `Delta Quote failed: ${quote.fallbackReason.errorType} - ${quote.fallbackReason.details}` ); - const swapTx = await handleMarketQuote({ - amount, - priceRoute: quote.market, - }); + // settle against the current market price instead + await handleMarketQuote({ amount, priceRoute: quote.market }); } } @@ -151,23 +85,14 @@ async function handleDeltaQuote({ // or sign a Permit1 or Permit2 TransferFrom for DeltaContract await quoteSDK.approveTokenForDelta(amount, DAI_TOKEN); - const slippagePercent = 0.5; - const destAmountAfterSlippage = BigInt( - // get rid of exponential notation - - +(+deltaPrice.destAmount * (1 - slippagePercent / 100)).toFixed(0) - // get rid of decimals - ).toString(10); - + // v2 order building is server-side: pass the quoted route/side, no deltaPrice const deltaAuction = await quoteSDK.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // or pick from deltaPrice.alternatives + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount + slippage: 50, // 50 bps = 0.5% slippage }); // poll if necessary diff --git a/src/examples/simpleQuote.ts b/src/examples/simpleQuote.ts index 322b1ab44..b74934884 100644 --- a/src/examples/simpleQuote.ts +++ b/src/examples/simpleQuote.ts @@ -39,6 +39,7 @@ async function allQuote() { }); if ('delta' in quote) { + // Delta path — quote.delta is the v2 DeltaPrice (route-based) const deltaPrice = quote.delta; const DeltaContract = await simpleSDK.delta.getDeltaContract(); @@ -49,23 +50,14 @@ async function allQuote() { DAI_TOKEN ); - const slippagePercent = 0.5; - const destAmountAfterSlippage = BigInt( - // get rid of exponential notation - - +(+deltaPrice.destAmount * (1 - slippagePercent / 100)).toFixed(0) - // get rid of decimals - ).toString(10); - + // v2 order building is server-side: pass the quoted route/side, no deltaPrice const deltaAuction = await simpleSDK.delta.submitDeltaOrder({ - deltaPrice, + route: deltaPrice.route, // or pick from deltaPrice.alternatives + side: deltaPrice.side, owner: account, // beneficiary: anotherAccount, // if need to send destToken to another account // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: DAI_TOKEN, - destToken: USDC_TOKEN, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount + slippage: 50, // 50 bps = 0.5% slippage }); // poll if necessary diff --git a/src/index.ts b/src/index.ts index 9a000901d..d44a7222e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -135,39 +135,54 @@ import type { OptionalRate, APIVersion, ExtraFetchParams, + PaginatedResponse, } from './types'; +// ── Delta ───────────────────────────────────────────────────────────────── +// Delta is v2 (server-built orders). The previous local-build v1 surface was +// removed; every Delta symbol below refers to the v2 implementation. + +// Shared on-chain order-struct types import type { DeltaAuctionOrder, - DeltaAuction, - DeltaAuctionStatus, - DeltaAuctionTransaction, - BridgeMetadata, - BridgeStatus, Bridge, ExternalDeltaOrder, + ProductiveDeltaOrder, TWAPDeltaOrder, TWAPBuyDeltaOrder, TWAPOnChainOrderType, OnChainOrderType, + DeltaOrderType, SwapSideUnion, DeltaAmountsWithSlippage, DeltaAmountsSellSlippage, DeltaAmountsBuySlippage, DeltaAmountsExplicit, - DeltaAuctionDelta, - DeltaAuctionTWAP, - DeltaAuctionTWAPBuy, - DeltaAuctionExternal, DeltaOrderUnion, - DeltaAuctionUnion, UnifiedDeltaOrderData, } from './methods/delta/helpers/types'; +// v2 auction, price & route types +import type { + BuiltDeltaOrder, + DeltaAuction, + DeltaOrderStatus, + DeltaTokenSide, + DeltaTransaction, + DeltaPrice, + DeltaPriceToken, + DeltaTokenAmount, + BridgeTag, + DeltaRoute, + DeltaRouteStep, + DeltaRouteBridge, + DeltaRouteBridgeContractParams, + BridgeRoute, +} from './methods/delta/types'; + import { - BuildDeltaOrderDataParams, BuildDeltaOrderFunctions, + BuildDeltaOrderParams, constructBuildDeltaOrder, - SignableDeltaOrderData, } from './methods/delta/buildDeltaOrder'; import { constructPostDeltaOrder, @@ -178,7 +193,7 @@ import { import { constructSignDeltaOrder, SignDeltaOrderFunctions, -} from './methods/delta/signDeltaOrder'; +} from './methods/delta'; import { constructPreSignDeltaOrder, PreSignDeltaOrderFunctions, @@ -190,16 +205,11 @@ import { import { constructGetDeltaPrice, GetDeltaPriceFunctions, - DeltaPrice, - BridgePrice, - AvailableBridge, DeltaPriceParams, } from './methods/delta/getDeltaPrice'; import { constructGetDeltaOrders, GetDeltaOrdersFunctions, - DeltaOrderFilterByStatus, - DeltaOrderFromAPI, } from './methods/delta/getDeltaOrders'; import { ApproveTokenForDeltaFunctions, @@ -210,15 +220,19 @@ import { GetPartnerFeeFunctions, } from './methods/delta/getPartnerFee'; import { - constructGetBridgeInfo, - GetBridgeInfoFunctions, - BridgeInfo, + constructGetBridgeRoutes, + GetBridgeRoutesFunctions, BridgeProtocolResponse, -} from './methods/delta/getBridgeInfo'; +} from './methods/delta/getBridgeRoutes'; import { constructIsTokenSupportedInDelta, IsTokenSupportedInDeltaFunctions, } from './methods/delta/isTokenSupportedInDelta'; +import { + constructGetAgentsList, + GetAgentsListFunctions, + AgentList, +} from './methods/delta/getAgentsList'; import { constructBuildExternalDeltaOrder, @@ -226,10 +240,6 @@ import { BuildExternalDeltaOrderParams, } from './methods/delta/buildExternalDeltaOrder'; import type { SignableExternalOrderData } from './methods/delta/helpers/buildExternalOrderData'; -import { - constructSignExternalDeltaOrder, - SignExternalDeltaOrderFunctions, -} from './methods/delta/signExternalDeltaOrder'; import { constructPostExternalDeltaOrder, PostExternalDeltaOrderFunctions, @@ -242,16 +252,12 @@ import { import { BuildTWAPDeltaOrderParams, - BuildTWAPSellOrderParams, - BuildTWAPBuyOrderParams, + BuildTWAPSellDeltaOrderParams, + BuildTWAPBuyDeltaOrderParams, BuildTWAPDeltaOrderFunctions, constructBuildTWAPDeltaOrder, } from './methods/delta/buildTWAPDeltaOrder'; import type { SignableTWAPOrderData } from './methods/delta/helpers/buildTWAPOrderData'; -import { - constructSignTWAPDeltaOrder, - SignTWAPDeltaOrderFunctions, -} from './methods/delta/signTWAPDeltaOrder'; import { constructPostTWAPDeltaOrder, PostTWAPDeltaOrderFunctions, @@ -273,6 +279,9 @@ import { } from './methods/quote/getQuote'; import { CancelDeltaOrderFunctions, + CancelDeltaOrder, + SignCancelDeltaOrderRequest, + PostCancelDeltaOrderRequest, constructCancelDeltaOrder, } from './methods/delta/cancelDeltaOrder'; import { @@ -286,6 +295,7 @@ import { CancelDeltaOrderData, SignableCancelDeltaOrderData, } from './methods/delta/helpers/buildCancelDeltaOrderData'; +import { SignableDeltaOrderData } from './methods/delta/helpers/buildDeltaOrderData'; export { constructSwapSDK, SwapSDKMethods } from './methods/swap'; @@ -376,21 +386,20 @@ export { constructCancelDeltaOrder, constructDeltaTokenModule, constructApproveTokenForDelta, + constructGetAgentsList, // External Delta methods constructBuildExternalDeltaOrder, - constructSignExternalDeltaOrder, constructPostExternalDeltaOrder, constructPreSignExternalDeltaOrder, // TWAP Delta methods constructBuildTWAPDeltaOrder, - constructSignTWAPDeltaOrder, constructPostTWAPDeltaOrder, constructPreSignTWAPDeltaOrder, // Quote methods constructGetQuote, // different helpers constructGetPartnerFee, - constructGetBridgeInfo, + constructGetBridgeRoutes, constructIsTokenSupportedInDelta, constructEthersContractCaller, // same as constructEthersV5ContractCaller for backwards compatibility constructEthersV5ContractCaller, @@ -451,35 +460,33 @@ export type { BuildNFTOrderInput, BuildNFTOrderDataInput, NFTOrdersUserParams, - //types for Delta methods + // types for Delta methods DeltaPrice, - BridgePrice, DeltaPriceParams, + DeltaPriceToken, + DeltaTokenAmount, + DeltaRoute, + DeltaRouteStep, + DeltaRouteBridge, + DeltaRouteBridgeContractParams, + BridgeTag, + BridgeRoute, + BuiltDeltaOrder, DeltaAuctionOrder, DeltaAuction, - DeltaAuctionDelta, - DeltaAuctionTWAP, - DeltaAuctionTWAPBuy, - DeltaAuctionExternal, + DeltaOrderStatus, + DeltaTokenSide, + DeltaTransaction, DeltaOrderUnion, - DeltaAuctionUnion, UnifiedDeltaOrderData, - DeltaAuctionStatus, - DeltaAuctionTransaction, - DeltaOrderFilterByStatus, - DeltaOrderFromAPI, CancelDeltaOrderData, SignableCancelDeltaOrderData, + SignableDeltaOrderData, // bridge part of DeltaOrder - BridgeMetadata, - BridgeStatus, Bridge, - BridgeInfo, - AvailableBridge, BridgeProtocolResponse, - BuildDeltaOrderDataParams, BuildDeltaOrderFunctions, - SignableDeltaOrderData, + BuildDeltaOrderParams, DeltaOrderToPost, PostDeltaOrderFunctions, PostDeltaOrderParams, @@ -488,18 +495,27 @@ export type { GetDeltaContractFunctions, GetDeltaPriceFunctions, GetDeltaOrdersFunctions, + GetBridgeRoutesFunctions, + GetAgentsListFunctions, + AgentList, ApproveTokenForDeltaFunctions, CancelDeltaOrderFunctions, + CancelDeltaOrder, + SignCancelDeltaOrderRequest, + PostCancelDeltaOrderRequest, DeltaTokenModuleFunctions, CancelAndWithdrawDeltaOrderParams, DepositNativeAndPreSignParams, DepositNativeAndPreSignDeltaOrderParams, // External Delta types ExternalDeltaOrder, + // Productive Delta types + ProductiveDeltaOrder, TWAPDeltaOrder, TWAPBuyDeltaOrder, TWAPOnChainOrderType, OnChainOrderType, + DeltaOrderType, DeltaAmountsWithSlippage, DeltaAmountsSellSlippage, DeltaAmountsBuySlippage, @@ -507,17 +523,15 @@ export type { SignableExternalOrderData, BuildExternalDeltaOrderParams, BuildExternalDeltaOrderFunctions, - SignExternalDeltaOrderFunctions, PostExternalDeltaOrderFunctions, PostExternalDeltaOrderParams, PreSignExternalDeltaOrderFunctions, // TWAP Delta types BuildTWAPDeltaOrderParams, - BuildTWAPSellOrderParams, - BuildTWAPBuyOrderParams, + BuildTWAPSellDeltaOrderParams, + BuildTWAPBuyDeltaOrderParams, BuildTWAPDeltaOrderFunctions, SignableTWAPOrderData, - SignTWAPDeltaOrderFunctions, PostTWAPDeltaOrderFunctions, PostTWAPDeltaOrderParams, PreSignTWAPDeltaOrderFunctions, @@ -534,7 +548,6 @@ export type { ConstructProviderFetchInput, // other types GetPartnerFeeFunctions, - GetBridgeInfoFunctions, IsTokenSupportedInDeltaFunctions, Token, Address, @@ -548,6 +561,7 @@ export type { APIVersion, SwapSideUnion, ExtraFetchParams, + PaginatedResponse, }; export { SDKConfig, constructPartialSDK } from './sdk/partial'; diff --git a/src/methods/delta/buildDeltaOrder.ts b/src/methods/delta/buildDeltaOrder.ts index 04f954604..1fbe493ee 100644 --- a/src/methods/delta/buildDeltaOrder.ts +++ b/src/methods/delta/buildDeltaOrder.ts @@ -1,133 +1,86 @@ +import { API_URL } from '../../constants'; import type { ConstructFetchInput, RequestParameters } from '../../types'; -import { constructGetDeltaContract } from './getDeltaContract'; -import type { BridgePrice } from './getDeltaPrice'; -import { constructGetPartnerFee } from './getPartnerFee'; -import { - buildDeltaSignableOrderData, - type BuildDeltaOrderDataInput, - type SignableDeltaOrderData, -} from './helpers/buildDeltaOrderData'; -import type { DeltaAmountsWithSlippage } from './helpers/types'; -import { SwapSideToOrderKind } from './helpers/types'; -import { resolvePartnerFee, resolveAmounts } from './helpers/misc'; -import type { MarkOptional } from 'ts-essentials'; -export type { SignableDeltaOrderData } from './helpers/buildDeltaOrderData'; +import type { DeltaAuctionOrder } from './helpers/types'; +import type { BuiltDeltaOrder, DeltaRoute } from './types'; +export type { BuiltDeltaOrder } from './types'; -type BuildDeltaOrderDataParamsBase = { +export type BuildDeltaOrderParams = { /** @description The address of the order owner */ owner: string; - /** @description The address of the order beneficiary */ - beneficiary?: string; // beneficiary==owner if no transferTo - /** @description The address of the src token */ - srcToken: string; // lowercase - /** @description The address of the dest token. For Crosschain Order - destination token on the destination chain */ - destToken: string; // lowercase - /** @description The deadline for the order */ - deadline?: number; // seconds - /** @description The nonce of the order */ - nonce?: number | string; // can be random, can even be Date.now() - /** @description Optional permit signature for the src token https://developers.velora.xyz/api/velora-api/velora-delta-api/build-a-delta-order-to-sign#supported-permits-order#supported-permits */ - permit?: string; //can be "0x" - /** @description Partner string. */ + /** @description The address of the order beneficiary. Defaults to owner. */ + beneficiary?: string; + /** @description The deadline for the order (unix seconds) */ + deadline?: number; + /** @description The nonce of the order. Random if omitted. */ + nonce?: string; + /** @description Optional permit signature for the src token. Defaults to "0x". */ + permit?: string; + /** @description Partner string. Passed to the server to resolve partner fee details. */ partner?: string; - - /** @description Destination Chain ID for Crosschain Orders */ - destChainId?: number; - - /** @description price response received from /delta/prices (getDeltaPrice method) */ - deltaPrice: MarkOptional< - Pick< - BridgePrice, - | 'destAmount' - | 'partner' - | 'partnerFee' - | 'destToken' - | 'srcAmount' - | 'bridge' - >, - 'partner' | 'partnerFee' - >; - - /** @description partner fee in basis points (bps), 50bps=0.5% */ + /** @description Partner fee in basis points (bps), 50bps=0.5% */ partnerFeeBps?: number; - /** @description partner address */ + /** @description Partner address */ partnerAddress?: string; - /** @description take surplus */ + /** @description Take surplus flag */ partnerTakesSurplus?: boolean; - - /** @description A boolean indicating whether the surplus should be capped. True by default */ + /** @description Whether the surplus should be capped. True by default. */ capSurplus?: boolean; - /** @description Metadata for the order, hex string */ metadata?: string; + /** @description Designates the Order as partially fillable instead of fill-or-kill. Default false. */ + partiallyFillable?: boolean; + + /** @description DeltaRoute from getDeltaPrice — either price.route or any price.alternatives[i] */ + route: DeltaRoute; + /** @description Order side. SELL or BUY. */ + 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 */ + limitAmount?: string; }; -export type BuildDeltaOrderDataParams = BuildDeltaOrderDataParamsBase & - DeltaAmountsWithSlippage; - type BuildDeltaOrder = ( - buildOrderParams: BuildDeltaOrderDataParams, + buildOrderParams: BuildDeltaOrderParams, requestParams?: RequestParameters -) => Promise; +) => Promise>; export type BuildDeltaOrderFunctions = { - /** @description Build Orders to be posted to Delta API for execution */ + /** @description Build a Delta v2 order from a DeltaRoute via the server endpoint, ready to sign and post. */ buildDeltaOrder: BuildDeltaOrder; }; export const constructBuildDeltaOrder = ( options: ConstructFetchInput ): BuildDeltaOrderFunctions => { - const { chainId } = options; - - // cached internally - const { getDeltaContract } = constructGetDeltaContract(options); - // cached internally for `partner` - const { getPartnerFee } = constructGetPartnerFee(options); - - const buildDeltaOrder: BuildDeltaOrder = async (options, requestParams) => { - const ParaswapDelta = await getDeltaContract(requestParams); - if (!ParaswapDelta) { - throw new Error(`Delta is not available on chain ${chainId}`); - } - - const { partnerAddress, partnerFeeBps, partnerTakesSurplus } = - await resolvePartnerFee(options, getPartnerFee, requestParams); - - const { srcAmount, destAmount, expectedAmount, swapSide } = - resolveAmounts(options); - - const input: BuildDeltaOrderDataInput = { - owner: options.owner, - beneficiary: options.beneficiary, - srcToken: options.srcToken, - // for some cases of WETH->ETH crosschain swaps, the destToken is changed to WETH or ETH, - // this is already reflected in deltaPrice - destToken: options.deltaPrice.destToken, - srcAmount, - destAmount, - expectedAmount, - deadline: options.deadline, - nonce: options.nonce?.toString(10), - permit: options.permit, - kind: SwapSideToOrderKind[swapSide], - metadata: options.metadata, - - chainId, - paraswapDeltaAddress: ParaswapDelta, - partnerAddress, - partnerTakesSurplus, - partnerFeeBps, - - capSurplus: options.capSurplus, - - bridge: options.deltaPrice.bridge, // ZERO_BRIDGE for same-chain Orders - }; - - return buildDeltaSignableOrderData(input); - }; - - return { - buildDeltaOrder, - }; + const { apiURL = API_URL, fetcher } = options; + const buildUrl = `${apiURL}/delta/v2/orders/build` as const; + + const buildDeltaOrder: BuildDeltaOrder = async (params, requestParams) => + fetcher>({ + url: buildUrl, + method: 'POST', + data: { + side: params.side, + route: params.route, + owner: params.owner, + beneficiary: params.beneficiary, + deadline: params.deadline, + nonce: params.nonce, + permit: params.permit, + slippage: params.slippage, + limitAmount: params.limitAmount, + metadata: params.metadata, + partiallyFillable: params.partiallyFillable, + partner: params.partner, + partnerAddress: params.partnerAddress, + partnerFeeBps: params.partnerFeeBps, + partnerTakesSurplus: params.partnerTakesSurplus, + capSurplus: params.capSurplus, + orderType: 'Order', + }, + requestParams, + }); + + return { buildDeltaOrder }; }; diff --git a/src/methods/delta/buildExternalDeltaOrder.ts b/src/methods/delta/buildExternalDeltaOrder.ts index 8fc31b260..fe79e186a 100644 --- a/src/methods/delta/buildExternalDeltaOrder.ts +++ b/src/methods/delta/buildExternalDeltaOrder.ts @@ -1,124 +1,95 @@ +import { API_URL } from '../../constants'; import type { ConstructFetchInput, RequestParameters } from '../../types'; -import { constructGetDeltaContract } from './getDeltaContract'; -import type { DeltaPrice } from './getDeltaPrice'; -import { constructGetPartnerFee } from './getPartnerFee'; -import { - buildExternalOrderSignableData, - type BuildExternalOrderDataInput, - type SignableExternalOrderData, -} from './helpers/buildExternalOrderData'; -import type { DeltaAmountsWithSlippage } from './helpers/types'; -import { SwapSideToOrderKind } from './helpers/types'; -import type { MarkOptional } from 'ts-essentials'; -import { resolvePartnerFee, resolveAmounts } from './helpers/misc'; -export type { SignableExternalOrderData } from './helpers/buildExternalOrderData'; +import type { ExternalDeltaOrder } from './helpers/types'; +import type { BuiltDeltaOrder, DeltaRoute } from './types'; +export type { BuiltDeltaOrder } from './types'; -type BuildExternalDeltaOrderParamsBase = { +export type BuildExternalDeltaOrderParams = { /** @description The address of the order owner */ owner: string; /** @description The address of the external handler contract */ handler: string; /** @description Protocol-specific encoded bytes for the external handler */ data: string; - /** @description The address of the src token */ - srcToken: string; - /** @description The address of the dest token */ - destToken: string; - /** @description The deadline for the order */ + /** @description The address of the order beneficiary. Defaults to owner. */ + beneficiary?: string; + /** @description The deadline for the order (unix seconds) */ deadline?: number; - /** @description The nonce of the order */ - nonce?: number | string; - /** @description Optional permit signature for the src token */ + /** @description The nonce of the order. Random if omitted. */ + nonce?: string; + /** @description Optional permit signature for the src token. Defaults to "0x". */ permit?: string; - /** @description Partner string */ + /** @description Partner string. Passed to the server to resolve partner fee details. */ partner?: string; - /** @description partner fee in basis points (bps), 50bps=0.5% */ + /** @description Partner fee in basis points (bps), 50bps=0.5% */ partnerFeeBps?: number; - /** @description partner address */ + /** @description Partner address */ partnerAddress?: string; - /** @description take surplus */ + /** @description Take surplus flag */ partnerTakesSurplus?: boolean; - /** @description A boolean indicating whether the surplus should be capped. True by default */ + /** @description Whether the surplus should be capped. True by default. */ capSurplus?: boolean; /** @description Metadata for the order, hex string */ metadata?: string; + /** @description Designates the Order as partially fillable. Default false. */ + partiallyFillable?: boolean; - /** @description price response received from /delta/prices (getDeltaPrice method) */ - deltaPrice: MarkOptional< - Pick< - DeltaPrice, - 'destAmount' | 'partner' | 'partnerFee' | 'destToken' | 'srcAmount' - >, - 'partner' | 'partnerFee' - >; + /** @description DeltaRoute from getDeltaPrice */ + route: DeltaRoute; + /** @description Order side. SELL or BUY. */ + 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; }; -export type BuildExternalDeltaOrderParams = BuildExternalDeltaOrderParamsBase & - DeltaAmountsWithSlippage; - type BuildExternalDeltaOrder = ( buildOrderParams: BuildExternalDeltaOrderParams, requestParams?: RequestParameters -) => Promise; +) => Promise>; export type BuildExternalDeltaOrderFunctions = { - /** @description Build External Orders to be posted to Delta API for execution */ + /** @description Build a Delta v2 External Order from a DeltaRoute via the server endpoint, ready to sign and post. */ buildExternalDeltaOrder: BuildExternalDeltaOrder; }; export const constructBuildExternalDeltaOrder = ( options: ConstructFetchInput ): BuildExternalDeltaOrderFunctions => { - const { chainId } = options; - - // cached internally - const { getDeltaContract } = constructGetDeltaContract(options); - // cached internally for `partner` - const { getPartnerFee } = constructGetPartnerFee(options); + const { apiURL = API_URL, fetcher } = options; + const buildUrl = `${apiURL}/delta/v2/orders/build` as const; const buildExternalDeltaOrder: BuildExternalDeltaOrder = async ( - options, + params, requestParams - ) => { - const ParaswapDelta = await getDeltaContract(requestParams); - if (!ParaswapDelta) { - throw new Error(`Delta is not available on chain ${chainId}`); - } - - const { partnerAddress, partnerFeeBps, partnerTakesSurplus } = - await resolvePartnerFee(options, getPartnerFee, requestParams); - - const { srcAmount, destAmount, expectedAmount, swapSide } = - resolveAmounts(options); - - const input: BuildExternalOrderDataInput = { - owner: options.owner, - handler: options.handler, - srcToken: options.srcToken, - destToken: options.deltaPrice.destToken, - srcAmount, - destAmount, - expectedAmount, - deadline: options.deadline, - nonce: options.nonce?.toString(10), - permit: options.permit, - kind: SwapSideToOrderKind[swapSide], - metadata: options.metadata, - data: options.data, - - chainId, - paraswapDeltaAddress: ParaswapDelta, - partnerAddress, - partnerTakesSurplus, - partnerFeeBps, - - capSurplus: options.capSurplus, - }; - - return buildExternalOrderSignableData(input); - }; + ) => + fetcher>({ + url: buildUrl, + method: 'POST', + data: { + side: params.side, + route: params.route, + owner: params.owner, + handler: params.handler, + data: params.data, + beneficiary: params.beneficiary, + deadline: params.deadline, + nonce: params.nonce, + permit: params.permit, + slippage: params.slippage, + limitAmount: params.limitAmount, + metadata: params.metadata, + partiallyFillable: params.partiallyFillable, + partner: params.partner, + partnerAddress: params.partnerAddress, + partnerFeeBps: params.partnerFeeBps, + partnerTakesSurplus: params.partnerTakesSurplus, + capSurplus: params.capSurplus, + orderType: 'ExternalOrder', + }, + requestParams, + }); - return { - buildExternalDeltaOrder, - }; + return { buildExternalDeltaOrder }; }; diff --git a/src/methods/delta/buildTWAPDeltaOrder.ts b/src/methods/delta/buildTWAPDeltaOrder.ts index d7ed8b1a9..ac17f037a 100644 --- a/src/methods/delta/buildTWAPDeltaOrder.ts +++ b/src/methods/delta/buildTWAPDeltaOrder.ts @@ -1,187 +1,131 @@ +import { API_URL } from '../../constants'; import type { ConstructFetchInput, RequestParameters } from '../../types'; -import { DEFAULT_BRIDGE } from './constants'; -import { constructGetDeltaContract } from './getDeltaContract'; -import type { BridgePrice } from './getDeltaPrice'; -import { constructGetPartnerFee } from './getPartnerFee'; -import { - buildTWAPSignableOrderData, - TWAPOrderCommonInput, - type BuildTWAPOrderDataInput, - type SignableTWAPOrderData, -} from './helpers/buildTWAPOrderData'; -import { applySlippage, resolvePartnerFee } from './helpers/misc'; -import type { MarkOptional } from 'ts-essentials'; -export type { SignableTWAPOrderData } from './helpers/buildTWAPOrderData'; +import type { TWAPBuyDeltaOrder, TWAPDeltaOrder } from './helpers/types'; +import type { BuiltDeltaOrder, DeltaRoute } from './types'; +export type { BuiltDeltaOrder } from './types'; -type BuildTWAPDeltaOrderParamsBase = { +type BuildTWAPDeltaOrderBase = { /** @description The address of the order owner */ owner: string; - /** @description The address of the order beneficiary */ + /** @description The address of the order beneficiary. Defaults to owner. */ beneficiary?: string; - /** @description The address of the src token */ - srcToken: string; - /** @description The address of the dest token. For Crosschain Order - destination token on the destination chain */ - destToken: string; - /** @description The deadline for the order */ + /** @description The deadline for the order (unix seconds) */ deadline?: number; - /** @description The nonce of the order */ - nonce?: number | string; - /** @description Optional permit signature for the src token */ + /** @description The nonce of the order. Random if omitted. */ + nonce?: string; + /** @description Optional permit signature for the src token. Defaults to "0x". */ permit?: string; - /** @description Partner string */ + /** @description Partner string. Passed to the server to resolve partner fee details. */ partner?: string; - /** @description Destination Chain ID for Crosschain Orders */ - destChainId?: number; /** @description Seconds between slice executions (min 60) */ interval: number; /** @description Number of slices (min 2) */ numSlices: number; - /** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5% */ + /** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5%. Default 0. */ slippage?: number; - - /** @description price response received from /delta/prices (getDeltaPrice method) for a single slice */ - deltaPrice: MarkOptional< - Pick< - BridgePrice, - | 'destAmount' - | 'partner' - | 'partnerFee' - | 'destToken' - | 'srcAmount' - | 'bridge' - >, - 'partner' | 'partnerFee' - >; - - /** @description partner fee in basis points (bps), 50bps=0.5% */ + /** @description DeltaRoute from getDeltaPrice for a single slice */ + route: DeltaRoute; + /** @description Partner fee in basis points (bps) */ partnerFeeBps?: number; - /** @description partner address */ + /** @description Partner address */ partnerAddress?: string; - /** @description take surplus */ + /** @description Take surplus flag */ partnerTakesSurplus?: boolean; - /** @description A boolean indicating whether the surplus should be capped. True by default */ + /** @description Whether the surplus should be capped. True by default. */ capSurplus?: boolean; /** @description Metadata for the order, hex string */ 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; }; -export type BuildTWAPSellOrderParams = BuildTWAPDeltaOrderParamsBase & { - /** @description Must be "TWAPOrder" for sell orders */ +export type BuildTWAPSellDeltaOrderParams = BuildTWAPDeltaOrderBase & { onChainOrderType: 'TWAPOrder'; - /** @description Total source token amount across all slices */ + /** @description Total source token amount across all slices. route.origin.input.amount must equal floor(totalSrcAmount / numSlices). */ totalSrcAmount: string; }; -export type BuildTWAPBuyOrderParams = BuildTWAPDeltaOrderParamsBase & { - /** @description Must be "TWAPBuyOrder" for buy orders */ +export type BuildTWAPBuyDeltaOrderParams = BuildTWAPDeltaOrderBase & { onChainOrderType: 'TWAPBuyOrder'; - /** @description Total destination token amount to buy across all slices */ + /** @description Total destination token amount to buy across all slices. route.origin.output.amount must equal floor(totalDestAmount / numSlices). */ totalDestAmount: string; - /** @description Maximum source token amount willing to spend */ + /** @description Maximum source token amount willing to spend across all slices. */ maxSrcAmount: string; }; export type BuildTWAPDeltaOrderParams = - | BuildTWAPSellOrderParams - | BuildTWAPBuyOrderParams; + | BuildTWAPSellDeltaOrderParams + | BuildTWAPBuyDeltaOrderParams; type BuildTWAPDeltaOrder = ( buildOrderParams: BuildTWAPDeltaOrderParams, requestParams?: RequestParameters -) => Promise; +) => Promise>; export type BuildTWAPDeltaOrderFunctions = { - /** @description Build TWAP Orders (sell or buy) to be posted to Delta API for execution */ + /** @description Build a Delta v2 TWAP Order (sell or buy) from a DeltaRoute via the server endpoint, ready to sign and post. */ buildTWAPDeltaOrder: BuildTWAPDeltaOrder; }; export const constructBuildTWAPDeltaOrder = ( options: ConstructFetchInput ): BuildTWAPDeltaOrderFunctions => { - const { chainId } = options; - - const { getDeltaContract } = constructGetDeltaContract(options); - const { getPartnerFee } = constructGetPartnerFee(options); + const { apiURL = API_URL, fetcher } = options; + const buildUrl = `${apiURL}/delta/v2/orders/build` as const; const buildTWAPDeltaOrder: BuildTWAPDeltaOrder = async ( params, requestParams ) => { - const ParaswapDelta = await getDeltaContract(requestParams); - if (!ParaswapDelta) { - throw new Error(`Delta is not available on chain ${chainId}`); - } - - const { partnerAddress, partnerFeeBps, partnerTakesSurplus } = - await resolvePartnerFee(params, getPartnerFee, requestParams); - - const commonInput: TWAPOrderCommonInput = { + const commonBody = { + route: params.route, owner: params.owner, beneficiary: params.beneficiary, - srcToken: params.srcToken, - destToken: params.deltaPrice.destToken, deadline: params.deadline, - nonce: params.nonce?.toString(10), + nonce: params.nonce, permit: params.permit, + slippage: params.slippage, + limitAmount: params.limitAmount, metadata: params.metadata, + partiallyFillable: params.partiallyFillable, + partner: params.partner, + partnerAddress: params.partnerAddress, + partnerFeeBps: params.partnerFeeBps, + partnerTakesSurplus: params.partnerTakesSurplus, + capSurplus: params.capSurplus, interval: params.interval, numSlices: params.numSlices, - bridge: { - ...params.deltaPrice.bridge, - // TWAP child orders get their bridge data at execution time - protocolData: DEFAULT_BRIDGE.protocolData, - }, - - chainId, - paraswapDeltaAddress: ParaswapDelta, - partnerAddress, - partnerTakesSurplus, - partnerFeeBps, - capSurplus: params.capSurplus, }; - let input: BuildTWAPOrderDataInput; - if (params.onChainOrderType === 'TWAPOrder') { - const slippage = params.slippage ?? 0; - const destAmountPerSlice = - slippage > 0 - ? applySlippage({ - amount: params.deltaPrice.destAmount, - slippageBps: slippage, - increase: false, - }) - : params.deltaPrice.destAmount; - - input = { - ...commonInput, - onChainOrderType: 'TWAPOrder', - destAmountPerSlice, - totalSrcAmount: params.totalSrcAmount, - }; - } else { - const slippage = params.slippage ?? 0; - const maxSrcAmount = - slippage > 0 - ? applySlippage({ - amount: params.maxSrcAmount, - slippageBps: slippage, - increase: true, - }) - : params.maxSrcAmount; - - input = { - ...commonInput, - onChainOrderType: 'TWAPBuyOrder', - totalDestAmount: params.totalDestAmount, - maxSrcAmount, - }; + return fetcher>({ + url: buildUrl, + method: 'POST', + data: { + ...commonBody, + side: 'SELL', + orderType: 'TWAPOrder', + totalSrcAmount: params.totalSrcAmount, + }, + requestParams, + }); } - return buildTWAPSignableOrderData(input); + return fetcher>({ + url: buildUrl, + method: 'POST', + data: { + ...commonBody, + side: 'BUY', + orderType: 'TWAPBuyOrder', + totalDestAmount: params.totalDestAmount, + maxSrcAmount: params.maxSrcAmount, + }, + requestParams, + }); }; - return { - buildTWAPDeltaOrder, - }; + return { buildTWAPDeltaOrder }; }; diff --git a/src/methods/delta/cancelDeltaOrder.ts b/src/methods/delta/cancelDeltaOrder.ts index b35c215a6..29835be72 100644 --- a/src/methods/delta/cancelDeltaOrder.ts +++ b/src/methods/delta/cancelDeltaOrder.ts @@ -1,3 +1,4 @@ +import { API_URL } from '../../constants'; import type { ConstructProviderFetchInput, RequestParameters, @@ -5,7 +6,7 @@ import type { import { constructGetDeltaContract } from './getDeltaContract'; import { buildCancelDeltaOrderSignableData, - CancelDeltaOrderData, + type CancelDeltaOrderData, } from './helpers/buildCancelDeltaOrderData'; type SuccessResponse = { success: true }; @@ -31,10 +32,10 @@ export type CancelDeltaOrder = ( ) => Promise; export type CancelDeltaOrderFunctions = { - signCancelLimitDeltaOrderRequest: SignCancelDeltaOrderRequest; - postCancelLimitDeltaOrderRequest: PostCancelDeltaOrderRequest; - /** @description Cancel a Limit Delta order */ - cancelLimitDeltaOrders: CancelDeltaOrder; + signCancelDeltaOrderRequest: SignCancelDeltaOrderRequest; + postCancelDeltaOrderRequest: PostCancelDeltaOrderRequest; + /** @description Cancel one or more Delta orders via the v2 endpoint */ + cancelDeltaOrders: CancelDeltaOrder; }; export const constructCancelDeltaOrder = ( @@ -43,10 +44,11 @@ export const constructCancelDeltaOrder = ( 'contractCaller' | 'fetcher' | 'apiURL' | 'chainId' > ): CancelDeltaOrderFunctions => { - // cached internally + const apiURL = options.apiURL ?? API_URL; + const { getDeltaContract } = constructGetDeltaContract(options); - const signCancelLimitDeltaOrderRequest: SignCancelDeltaOrderRequest = async ( + const signCancelDeltaOrderRequest: SignCancelDeltaOrderRequest = async ( params, requestParams ) => { @@ -60,50 +62,39 @@ export const constructCancelDeltaOrder = ( paraswapDeltaAddress: ParaswapDelta, chainId: options.chainId, }); - const signature = await options.contractCaller.signTypedDataCall(typedData); - return signature; + return options.contractCaller.signTypedDataCall(typedData); }; - const postCancelLimitDeltaOrderRequest: PostCancelDeltaOrderRequest = async ( + const postCancelDeltaOrderRequest: PostCancelDeltaOrderRequest = async ( params, requestParams ) => { - const cancelUrl = `${options.apiURL}/delta/orders/cancel` as const; + const cancelUrl = `${apiURL}/delta/v2/orders/cancel` as const; - const res = await options.fetcher({ + return options.fetcher({ url: cancelUrl, method: 'POST', data: params, requestParams, }); - - return res; }; - const cancelLimitDeltaOrders: CancelDeltaOrder = async ( + const cancelDeltaOrders: CancelDeltaOrder = async ( { orderIds }, requestParams ) => { - const signature = await signCancelLimitDeltaOrderRequest( + const signature = await signCancelDeltaOrderRequest( { orderIds }, requestParams ); - const res = await postCancelLimitDeltaOrderRequest( - { - orderIds, - signature, - }, - requestParams - ); - - return res; + return postCancelDeltaOrderRequest({ orderIds, signature }, requestParams); }; return { - signCancelLimitDeltaOrderRequest, - postCancelLimitDeltaOrderRequest, - cancelLimitDeltaOrders, + signCancelDeltaOrderRequest, + postCancelDeltaOrderRequest, + cancelDeltaOrders, }; }; diff --git a/src/methods/delta/constants.ts b/src/methods/delta/constants.ts deleted file mode 100644 index bc22e0e0e..000000000 --- a/src/methods/delta/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ZERO_ADDRESS } from '../common/orders/buildOrderData'; -import { Bridge } from './helpers/types'; - -// for same-chain Orders, all 0 params -export const DEFAULT_BRIDGE = { - protocolSelector: '0x00000000', // 4 bytes - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', -} as const satisfies Bridge; diff --git a/src/methods/delta/getAgentsList.ts b/src/methods/delta/getAgentsList.ts new file mode 100644 index 000000000..a18154e99 --- /dev/null +++ b/src/methods/delta/getAgentsList.ts @@ -0,0 +1,32 @@ +import { API_URL } from '../../constants'; +import type { ConstructFetchInput, RequestParameters } from '../../types'; + +export type AgentList = string[]; + +type AgentsListResponse = AgentList; + +type GetAgentsList = (requestParams?: RequestParameters) => Promise; + +export type GetAgentsListFunctions = { + /** @description List agents available on the current chain. */ + getAgentsList: GetAgentsList; +}; + +export const constructGetAgentsList = ({ + apiURL = API_URL, + chainId, + fetcher, +}: ConstructFetchInput): GetAgentsListFunctions => { + const baseUrl = `${apiURL}/delta/v2/agents/list/${chainId}` as const; + + const getAgentsList: GetAgentsList = async (requestParams) => { + const data = await fetcher({ + url: baseUrl, + method: 'GET', + requestParams, + }); + return data; + }; + + return { getAgentsList }; +}; diff --git a/src/methods/delta/getBridgeInfo.ts b/src/methods/delta/getBridgeRoutes.ts similarity index 50% rename from src/methods/delta/getBridgeInfo.ts rename to src/methods/delta/getBridgeRoutes.ts index b3d33981f..32b2dbf47 100644 --- a/src/methods/delta/getBridgeInfo.ts +++ b/src/methods/delta/getBridgeRoutes.ts @@ -1,80 +1,78 @@ import { API_URL } from '../../constants'; import { constructSearchString } from '../../helpers/misc'; -import type { - Address, - ConstructFetchInput, - RequestParameters, -} from '../../types'; - -// srcChainId -> destChainId -> outputToken[] -// output Tokens that are supported for a srcChainId -> destChainId pair -export type BridgeInfo = Record>; -type BridgeInfoResponse = { supportedTokens: BridgeInfo }; - -type GetBridgeInfoParams = { +import type { ConstructFetchInput, RequestParameters } from '../../types'; +import type { BridgeRoute } from './types'; + +export type BridgeProtocolResponse = { + protocol: string; + displayName: string; + icon: string; +}; + +type GetBridgeRoutesParams = { /** @description Include tokens that can be swapped on destChain after bridge. Default is true. */ allowBridgeAndSwap?: boolean; /** @description Include only the specified bridges. Default is all bridges. */ bridges?: string[]; }; -type BridgeInfoQuery = { +type BridgeRoutesQuery = { allowBridgeAndSwap?: boolean; bridges?: string; }; -type GetBridgeInfo = ( - params?: GetBridgeInfoParams, +type BridgeRoutesResponse = { routes: BridgeRoute[] }; + +type GetBridgeRoutes = ( + params?: GetBridgeRoutesParams, requestParams?: RequestParameters -) => Promise; +) => Promise; -export type BridgeProtocolResponse = { - protocol: string; - displayName: string; - icon: string; -}; +type GetBridgeProtocols = ( + requestParams?: RequestParameters +) => Promise; type BridgeProtocolsResponse = { bridgeProtocols: BridgeProtocolResponse[]; }; -type GetBridgeProtocols = ( - requestParams?: RequestParameters -) => Promise; - -export type GetBridgeInfoFunctions = { - getBridgeInfo: GetBridgeInfo; +export type GetBridgeRoutesFunctions = { + /** @description Fetch supported bridge routes as a flat array (v2 replacement for bridge-info). */ + getBridgeRoutes: GetBridgeRoutes; + /** @description Fetch supported bridge protocols (unchanged from v1). */ getBridgeProtocols: GetBridgeProtocols; }; -export const constructGetBridgeInfo = ({ +export const constructGetBridgeRoutes = ({ apiURL = API_URL, fetcher, -}: ConstructFetchInput): GetBridgeInfoFunctions => { - const deltaBridgeUrl = `${apiURL}/delta/prices` as const; +}: ConstructFetchInput): GetBridgeRoutesFunctions => { + const deltaPricesUrl = `${apiURL}/delta/v2/prices` as const; - const getBridgeInfo: GetBridgeInfo = async (params = {}, requestParams) => { + const getBridgeRoutes: GetBridgeRoutes = async ( + params = {}, + requestParams + ) => { const { allowBridgeAndSwap, bridges } = params; - const bridgesString = bridges ? bridges.join(',') : undefined; - const search = constructSearchString({ + const search = constructSearchString({ allowBridgeAndSwap, - bridges: bridgesString, + bridges: bridges?.join(','), }); - const fetchURL = `${deltaBridgeUrl}/bridge-info${search}` as const; + const fetchURL = `${deltaPricesUrl}/bridge-routes${search}` as const; - const data = await fetcher({ + const data = await fetcher({ url: fetchURL, method: 'GET', requestParams, }); - return data.supportedTokens; + return data.routes; }; const getBridgeProtocols: GetBridgeProtocols = async (requestParams) => { - const fetchURL = `${deltaBridgeUrl}/bridge-protocols` as const; + const fetchURL = `${deltaPricesUrl}/bridge-protocols` as const; const data = await fetcher({ url: fetchURL, @@ -85,8 +83,5 @@ export const constructGetBridgeInfo = ({ return data.bridgeProtocols; }; - return { - getBridgeInfo, - getBridgeProtocols, - }; + return { getBridgeRoutes, getBridgeProtocols }; }; diff --git a/src/methods/delta/getDeltaOrders.ts b/src/methods/delta/getDeltaOrders.ts index 412a1362e..d5f25dd97 100644 --- a/src/methods/delta/getDeltaOrders.ts +++ b/src/methods/delta/getDeltaOrders.ts @@ -3,24 +3,11 @@ import { constructSearchString } from '../../helpers/misc'; import type { Address, ConstructFetchInput, + PaginatedResponse, RequestParameters, } from '../../types'; -import type { - DeltaAuction, - DeltaAuctionStatus, - OnChainOrderType, -} from './helpers/types'; - -/** @deprecated Use DeltaAuction directly */ -export type DeltaOrderFromAPI = DeltaAuction; - -export type DeltaOrderFilterByStatus = - | DeltaAuctionStatus - | 'INSUFFICIENT_BALANCE' - | 'INSUFFICIENT_ALLOWANCE' - | 'INVALIDATED' - | 'ACTIVE' - | 'INACTIVE'; +import type { DeltaOrderType, OnChainOrderType } from './helpers/types'; +import type { DeltaOrderStatus, DeltaAuction } from './types'; type GetDeltaOrderById = ( orderId: string, @@ -32,44 +19,32 @@ type GetDeltaOrderByHash = ( requestParams?: RequestParameters ) => Promise; -type OrdersFilter = { - /** @description Order.owner to fetch Delta Order for */ +type OrdersFilter = { + /** @description `order.owner` to fetch Delta Orders for. */ userAddress: Address; - /** @description Pagination option, page. Default 1 */ + /** @description Pagination option. Default 1. */ page?: number; - /** @description Pagination option, limit. Default 100 */ + /** @description Pagination option. Default 100, max 1000. */ limit?: number; - /** @description Filter by chainId, without this filter, orders from all chains are returned */ + /** @description Filter by chainId. Omitted = orders across all chains. */ chainId?: number[]; - /** - * @description - * Filter by any known DeltaAuctionStatus and some custom statuses: - * - **INSUFFICIENT_BALANCE** — returned as SUSPENDED from API - * - **INSUFFICIENT_ALLOWANCE** — returned as SUSPENDED from API - * - **INVALIDATED** — returned as FAILED from API - * - **ACTIVE** — All orders with NOT_STARTED, RUNNING, EXECUTING, CANCELLING or SUSPENDED statuses. - * - **INACTIVE** — All orders with EXECUTED, FAILED, EXPIRED, CANCELLED or INVALIDATED statuses. - */ - status?: DeltaOrderFilterByStatus[]; - /** @description Filter by type. MARKET, LIMIT. Orders with both types are returned if not specified */ - type?: 'MARKET' | 'LIMIT'; - /** @description Filter by on-chain order type. Order, ExternalOrder. Orders of all types are returned if not specified */ - onChainOrderType?: T; + /** @description Filter by integrator-facing status. */ + status?: DeltaOrderStatus[]; + /** @description Filter by order type. MARKET or LIMIT. */ + type?: DeltaOrderType; + /** @description Filter by on-chain order type. */ + onChainOrderType?: OnChainOrderType; }; + type OrderFiltersQuery = Omit & { chainId?: string; status?: string; }; -type GetDeltaOrders = { - ( - options: OrdersFilter & { onChainOrderType: T }, - requestParams?: RequestParameters - ): Promise[]>; - (options: OrdersFilter, requestParams?: RequestParameters): Promise< - DeltaAuction[] - >; -}; +type GetDeltaOrders = ( + options: OrdersFilter, + requestParams?: RequestParameters +) => Promise>; type GetRequiredBalanceParams = { userAddress: Address; @@ -79,13 +54,17 @@ type GetRequiredBalanceParams = { type GetRequiredBalance = ( userParams: GetRequiredBalanceParams, requestParams?: RequestParameters -) => Promise>; // token -> balance in Limit Orders +) => Promise>; // token -> required balance across open Delta orders export type GetDeltaOrdersFunctions = { + /** @description Fetch a single order by its UUID. */ getDeltaOrderById: GetDeltaOrderById; + /** @description Fetch a single order by its EIP-712 order hash. */ getDeltaOrderByHash: GetDeltaOrderByHash; + /** @description List Delta orders with the v2 pagination envelope. */ getDeltaOrders: GetDeltaOrders; - getRequiredBalanceForDeltaLimitOrders: GetRequiredBalance; + /** @description Required balance per token across the user's open Delta v2 orders. Pass `tokenAddress` to narrow the result to a single token. */ + getRequiredBalanceForDeltaOrders: GetRequiredBalance; }; export const constructGetDeltaOrders = ({ @@ -93,21 +72,18 @@ export const constructGetDeltaOrders = ({ fetcher, chainId, }: ConstructFetchInput): GetDeltaOrdersFunctions => { - const baseUrl = `${apiURL}/delta/orders` as const; + const baseUrl = `${apiURL}/delta/v2/orders` as const; const getDeltaOrderById: GetDeltaOrderById = async ( orderId, requestParams ) => { const fetchURL = `${baseUrl}/${orderId}` as const; - - const order = await fetcher({ + return fetcher({ url: fetchURL, method: 'GET', requestParams, }); - - return order; }; const getDeltaOrderByHash: GetDeltaOrderByHash = async ( @@ -115,22 +91,14 @@ export const constructGetDeltaOrders = ({ requestParams ) => { const fetchURL = `${baseUrl}/hash/${orderHash}` as const; - - const order = await fetcher({ + return fetcher({ url: fetchURL, method: 'GET', requestParams, }); - - return order; }; - const getDeltaOrders: GetDeltaOrders = async < - T extends OnChainOrderType = OnChainOrderType - >( - options: OrdersFilter, - requestParams?: RequestParameters - ) => { + const getDeltaOrders: GetDeltaOrders = async (options, requestParams) => { const chainIdString = options.chainId?.join(','); const statusString = options.status?.join(','); @@ -146,16 +114,14 @@ export const constructGetDeltaOrders = ({ const fetchURL = `${baseUrl}${search}` as const; - const orders = await fetcher[]>({ + return fetcher>({ url: fetchURL, method: 'GET', requestParams, }); - - return orders; }; - const getRequiredBalanceForDeltaLimitOrders: GetRequiredBalance = async ( + const getRequiredBalanceForDeltaOrders: GetRequiredBalance = async ( userParams, requestParams ) => { @@ -178,6 +144,6 @@ export const constructGetDeltaOrders = ({ getDeltaOrderById, getDeltaOrderByHash, getDeltaOrders, - getRequiredBalanceForDeltaLimitOrders, + getRequiredBalanceForDeltaOrders, }; }; diff --git a/src/methods/delta/getDeltaPrice.ts b/src/methods/delta/getDeltaPrice.ts index 70e69c658..6ca39cb6e 100644 --- a/src/methods/delta/getDeltaPrice.ts +++ b/src/methods/delta/getDeltaPrice.ts @@ -1,5 +1,3 @@ -import { Prettify } from 'viem'; -import { Bridge } from '../..'; import { API_URL, SwapSide } from '../../constants'; import { constructSearchString } from '../../helpers/misc'; import type { @@ -7,14 +5,14 @@ import type { EnumerateLiteral, RequestParameters, } from '../../types'; -import { BridgePriceInfo } from './helpers/types'; +import type { DeltaPrice } from './types'; type SwapSideUnion = EnumerateLiteral; export type DeltaPriceParams = { - /** @description Source Token Address. Not Native Token */ + /** @description Source Token Address */ srcToken: string; - /** @description Destination Token Address */ + /** @description Destination Token Address. For Crosschain Orders, the destination token on the destination chain */ destToken: string; /** @description srcToken amount in wei */ amount: string; @@ -25,16 +23,16 @@ export type DeltaPriceParams = { /** @description User's Wallet Address */ userAddress?: string; /** @description Beneficiary Address */ - beneficiary?: string; // beneficiary==owner if no transferTo + beneficiary?: string; /** @description Partner string. */ partner?: string; - /** @description Used together with `partner` if provided. Represented in basis points, 50bps=0.5% */ + /** @description Partner fee in basis points (bps), 50bps=0.5% */ partnerFeeBps?: number; /** @description Destination Chain ID for Crosschain Orders */ destChainId?: number; /** @description SELL or BUY, default is SELL */ side?: SwapSideUnion; - /** @description In %. It's a way to bypass the API price impact check (default = 15%) */ + /** @description In %. Bypasses the API price impact check (default = 15%) */ maxImpact?: number; maxUSDImpact?: number; @@ -52,101 +50,20 @@ type DeltaPriceQueryOptions = Omit< DeltaPriceParams, 'includeAgents' | 'excludeAgents' | 'includeBridges' | 'excludeBridges' > & { - chainId: number; // will return error from API on unsupported chains + chainId: number; includeAgents?: string; excludeAgents?: string; includeBridges?: string; excludeBridges?: string; }; -export type DeltaPrice = { - srcToken: string; - destToken: string; - srcAmount: string; - /** @description Available for BUY side */ - srcAmountBeforeFee?: string; - destAmount: string; - /** @description Available for SELL side */ - destAmountBeforeFee?: string; - /** @description amount of the final outcome token */ - receivedDestAmount: string; - receivedDestUSD: string; - /** @description Available for SELL side */ - receivedDestAmountBeforeFee?: string; - /** @description Available for SELL side */ - receivedDestUSDBeforeFee?: string; - gasCost: string; - gasCostBeforeFee: string; - gasCostUSD: string; - gasCostUSDBeforeFee: string; - srcUSD: string; - /** @description Available for BUY side */ - srcUSDBeforeFee?: string; - destUSD: string; - /** @description Available for SELL side */ - destUSDBeforeFee?: string; - partner: string; - partnerFee: number; // in % - hmac: string; - bridge: Bridge; // for single-chain DeltaPrice, it's DEFAULT_BRIDGE -}; - -type AvailableBridgePrice = Pick< - DeltaPrice, - | 'srcAmount' - | 'srcAmountBeforeFee' // Available for BUY side - | 'srcUSD' - | 'srcUSDBeforeFee' // Available for BUY side - | 'destToken' - | 'destAmount' - | 'destAmountBeforeFee' // Available for SELL side - | 'destUSD' - | 'destUSDBeforeFee' // Available for SELL side - | 'gasCostUSD' - | 'gasCost' - | 'gasCostUSDBeforeFee' - | 'gasCostBeforeFee' - | 'receivedDestAmount' - | 'receivedDestAmountBeforeFee' - | 'receivedDestUSD' - | 'receivedDestUSDBeforeFee' ->; - -export type AvailableBridge = Prettify< - AvailableBridgePrice & Pick ->; - -export type BridgePrice = Omit & { - // destAmountAfterBridge: string; // became bridgeInfo.destAmountAfterBridge - // destUSDAfterBridge: string; // became bridgeInfo.destUSDAfterBridge - // bridgeFee: string; // became bridgeInfo.fees[0].amount - // bridgeFeeUSD: string; // became bridgeInfo.fees[0].amountInUSD - // poolAddress: string; - bridge: Bridge; - bridgeInfo: BridgePriceInfo; - availableBridges: AvailableBridge[]; -}; - -type DeltaPriceResponse = { - price: DeltaPrice | BridgePrice; - deltaAddress: string; -}; - -interface GetDeltaPrice { - ( - options: DeltaPriceParams & { destChainId: number }, - requestParams?: RequestParameters - ): Promise; - ( - options: DeltaPriceParams & { destChainId?: undefined }, - requestParams?: RequestParameters - ): Promise; - (options: DeltaPriceParams, requestParams?: RequestParameters): Promise< - DeltaPrice | BridgePrice - >; -} +type GetDeltaPrice = ( + options: DeltaPriceParams, + requestParams?: RequestParameters +) => Promise; export type GetDeltaPriceFunctions = { + /** @description Fetch a v2 price quote (route-based response). */ getDeltaPrice: GetDeltaPrice; }; @@ -155,24 +72,9 @@ export const constructGetDeltaPrice = ({ chainId, fetcher, }: ConstructFetchInput): GetDeltaPriceFunctions => { - const pricesUrl = `${apiURL}/delta/prices` as const; + const pricesUrl = `${apiURL}/delta/v2/prices` as const; - async function getDeltaPrice( - options: DeltaPriceParams & { destChainId: number }, - requestParams?: RequestParameters - ): Promise; - async function getDeltaPrice( - options: DeltaPriceParams & { destChainId?: undefined }, - requestParams?: RequestParameters - ): Promise; - async function getDeltaPrice( - options: DeltaPriceParams, - requestParams?: RequestParameters - ): Promise; - async function getDeltaPrice( - options: DeltaPriceParams, - requestParams?: RequestParameters - ): Promise { + const getDeltaPrice: GetDeltaPrice = async (options, requestParams) => { const { includeAgents, excludeAgents, @@ -180,41 +82,27 @@ export const constructGetDeltaPrice = ({ excludeBridges, ...rest } = options; - const includeAgentsString = includeAgents - ? includeAgents.join(',') - : undefined; - const excludeAgentsString = excludeAgents - ? excludeAgents.join(',') - : undefined; - const includeBridgesString = includeBridges - ? includeBridges.join(',') - : undefined; - const excludeBridgesString = excludeBridges - ? excludeBridges.join(',') - : undefined; const search = constructSearchString({ ...rest, chainId, side: options.side ?? SwapSide.SELL, - includeAgents: includeAgentsString, - excludeAgents: excludeAgentsString, - includeBridges: includeBridgesString, - excludeBridges: excludeBridgesString, + includeAgents: includeAgents?.join(','), + excludeAgents: excludeAgents?.join(','), + includeBridges: includeBridges?.join(','), + excludeBridges: excludeBridges?.join(','), }); const fetchURL = `${pricesUrl}${search}` as const; - const data = await fetcher({ + const data = await fetcher({ url: fetchURL, method: 'GET', requestParams, }); - return data.price; - } - - return { - getDeltaPrice, + return data; }; + + return { getDeltaPrice }; }; diff --git a/src/methods/delta/helpers/orders.ts b/src/methods/delta/helpers/orders.ts index 6c5c45a21..7443f7750 100644 --- a/src/methods/delta/helpers/orders.ts +++ b/src/methods/delta/helpers/orders.ts @@ -1,23 +1,40 @@ import type { NonEmptyArray, Prettify } from 'ts-essentials'; import { Bridge, - DeltaAuction, DeltaAuctionOrder, - DeltaAuctionStatus, - DeltaAuctionTransaction, - DeltaAuctionTWAP, - DeltaAuctionTWAPBuy, - DeltaOrderUnion, ExternalDeltaOrder, OnChainOrderType, OrderKind, + ProductiveDeltaOrder, SwapSideUnion, TWAPBuyDeltaOrder, TWAPDeltaOrder, + DeltaOrderUnion, UnifiedDeltaOrderData, } from './types'; +import type { + DeltaAuction, + DeltaOrderStatus, + DeltaTokenSide, + DeltaTransaction, +} from '../types'; + +/** + * Delta order helpers. + * + * The on-chain order structs (`auction.order`) and the `onChainOrderType` union + * are shared across all order families, so the order-struct guards, auction + * discriminant guards, and order-level getters operate purely on those shapes. + * + * The *auction* envelope is the integrator-facing v2 shape: + * - `status` is the integrator-facing `DeltaOrderStatus` + * (PENDING/ACTIVE/COMPLETED/…), + * - amounts live on `input`/`output` (`DeltaTokenSide`) and `transactions` + * (`DeltaTransaction`), + * - `side` is carried explicitly on the auction. + */ -///// CHECKS ////// +///// CHECKS — order structs ////// /** * @description Checks whether an order is a TWAP Sell or TWAP Buy order. @@ -38,6 +55,7 @@ function isTWAPSellOrder(order: DeltaOrderUnion): order is TWAPDeltaOrder { typeof order.totalSrcAmount === 'string' ); } + /** * @description Checks whether an order is a TWAP Buy order. */ @@ -65,6 +83,18 @@ function isDeltaOrder(order: DeltaOrderUnion): order is DeltaAuctionOrder { ); } +/** + * @description Checks whether an order is a Productive Delta order + * (strategy-routed order without an explicit OrderKind). + */ +function isProductiveOrder( + order: DeltaOrderUnion +): order is ProductiveDeltaOrder { + return 'strategy' in order && typeof order.strategy === 'string'; +} + +///// CHECKS — auction discriminants ////// + /** * @description Checks whether an auction is a TWAP auction. */ @@ -110,27 +140,198 @@ function isExternalAuction(auction: { return auction.onChainOrderType === 'ExternalOrder'; } +/** + * @description Checks whether an auction is a Productive auction. + */ +function isProductiveAuction(auction: { + onChainOrderType: T; +}): auction is { onChainOrderType: 'ProductiveOrder' & T } { + return auction.onChainOrderType === 'ProductiveOrder'; +} + +/** + * @description Checks whether an auction is a Fillable auction. + * `FillableOrder` is the `onChainOrderType` the server reports for a + * `partiallyFillable` Standard order; it carries the same order struct as + * `Order`. Consumers that don't distinguish the two should treat + * `isDeltaAuction(a) || isFillableAuction(a)` as "is a standard order". + */ +function isFillableAuction>( + auction: T +): auction is T & { onChainOrderType: 'FillableOrder' } { + return auction.onChainOrderType === 'FillableOrder'; +} + +///// CHECKS — status / execution (v2 envelope) ////// + +/** + * @description Checks whether an auction is fully executed (settled on every chain). + */ +function isCompletedAuction>( + auction: T +): auction is T & { status: 'COMPLETED' } { + return auction.status === 'COMPLETED'; +} + +const failedAuctionStatuses = [ + 'FAILED', + 'EXPIRED', + 'CANCELLED', + 'REFUNDED', +] as const; + +const failedAuctionStatusesSet = new Set( + failedAuctionStatuses +); + +/** + * @description Checks whether an auction is in a terminal failure state + * (failed, expired, cancelled, or refunded). + */ +function isFailedAuction>( + auction: T +): auction is T & { status: (typeof failedAuctionStatuses)[number] } { + return failedAuctionStatusesSet.has(auction.status); +} + +/** + * @description Checks whether an auction status is cancelled. + */ +function isCanceledAuction>( + auction: T +): auction is T & { status: 'CANCELLED' } { + return auction.status === 'CANCELLED'; +} + +/** + * @description Checks whether an auction status is expired. + */ +function isExpiredAuction>( + auction: T +): auction is T & { status: 'EXPIRED' } { + return auction.status === 'EXPIRED'; +} + +const pendingAuctionStatuses = [ + 'PENDING', + 'AWAITING_SIGNATURE', + 'ACTIVE', + 'BRIDGING', +] as const; + +const pendingAuctionStatusesSet = new Set( + pendingAuctionStatuses +); + +/** + * @description Checks whether an auction is still in flight (not yet settled + * and not failed): awaiting signature, pending, actively executing, or bridging. + */ +function isPendingAuction>( + auction: T +): auction is T & { status: (typeof pendingAuctionStatuses)[number] } { + return pendingAuctionStatusesSet.has(auction.status); +} + +/** + * @description Checks whether an auction has been partially executed: + * it has at least one transaction and an overall filled percent strictly + * between 0 and 100. + */ +function isPartiallyExecutedAuction< + T extends Pick +>( + auction: T +): auction is T & { transactions: NonEmptyArray } { + if (auction.transactions.length === 0) return false; + + const filledPercent = getFilledPercent(auction); + + return filledPercent > 0 && filledPercent < 100; +} + +/** + * @description Checks whether an order includes valid cross-chain bridge details. + */ +function isOrderCrosschain( + order: T +): order is Prettify & { bridge: Bridge }> { + return ( + 'bridge' in order && !!order.bridge && order.bridge.destinationChainId !== 0 + ); +} + const checks = { + // order-struct guards isTWAPOrder, isTWAPSellOrder, isTWAPBuyOrder, isExternalOrder, isDeltaOrder, + isProductiveOrder, + isOrderCrosschain, + + // auction discriminant guards isTWAPAuction, isTWAPSellAuction, isTWAPBuyAuction, isDeltaAuction, isExternalAuction, - isOrderCrosschain, - isExecutedAuction, - isPartiallyExecutedAuction, + isProductiveAuction, + isFillableAuction, + + // status / execution guards (v2 envelope) + isCompletedAuction, isFailedAuction, isCanceledAuction, isExpiredAuction, isPendingAuction, + isPartiallyExecutedAuction, }; -///// GETTERS ////// +///// GETTERS — order structs ////// + +const OrderKindToSwapSide = { + [OrderKind.Sell]: 'SELL', + [OrderKind.Buy]: 'BUY', +} as const; + +/** + * @description Returns swap side from a Delta or External order kind. + */ +function getSwapSideFromDeltaOrder( + order: DeltaAuctionOrder | ExternalDeltaOrder +): SwapSideUnion { + return OrderKindToSwapSide[order.kind]; +} + +const TwapTypeToSwapSide = { + TWAPOrder: 'SELL', + TWAPBuyOrder: 'BUY', +} as const; + +/** + * @description Returns swap side from TWAP on-chain order type. + */ +function getSwapSideFromTwapOrderType( + onChainOrderType: 'TWAPOrder' | 'TWAPBuyOrder' +): SwapSideUnion { + return TwapTypeToSwapSide[onChainOrderType]; +} + +/** + * @description Returns source and destination token addresses for an order. + */ +function getOrderTokenAddresses(order: DeltaAuction['order']) { + const srcToken = order.srcToken; + const destToken = isOrderCrosschain(order) + ? order.bridge.outputToken + : order.destToken; + return { + srcToken, + destToken, + }; +} /** * @description Returns the expected source amount for a TWAP order. @@ -179,169 +380,88 @@ function getExpectedTwapOrderAmounts( return { srcAmount, destAmount }; } -/** - * @description Returns expected and, when available, final amounts for a TWAP auction. - */ -function getTwapAuctionAmounts( - twapAuction: - | Pick - | Pick -) { - const isExecuted = isExecutedAuction(twapAuction); +function scaleByFactor(amount: bigint, scalingFactor: number): bigint { + if (!amount) return 0n; - const expected = getExpectedTwapOrderAmounts(twapAuction.order); - if (isExecuted) { - const final = getTransactionAmounts(twapAuction.transactions); - return { - final, - expected, - minimal: expected, // TWAP orders don't have more detailed amounts - }; - } - return { - expected, - minimal: expected, // TWAP orders don't have more detailed amounts - }; -} + if (scalingFactor === 0) return amount; -const getters = { - getUnifiedDeltaOrderData, - getExpectedTwapSrcAmount, - getExpectedTwapDestAmount, - getExpectedTwapOrderAmounts, - getTwapAuctionAmounts, - getAuctionDestChainId, - getSwapSideFromDeltaOrder, - getSwapSideFromTwapOrderType, - getAuctionSwapSide, - getOrderTokenAddresses, - getTransactionAmounts, - getAuctionAmounts, - getFilledPercent, -}; + const base = 10n; -export const OrderHelpers = { - checks, - getters, -}; + return scalingFactor < 0 + ? amount / base ** BigInt(-scalingFactor) + : amount * base ** BigInt(scalingFactor); +} -// -------------------- Auction Unified Data -------------------- +///// GETTERS — auction envelope (v2) ////// /** - * @description Returns the destination chain id for the auction. + * @description Reads an amount off a v2 token side. A SELL input / BUY output + * carries an explicit `amount`; the opposite side carries + * `expectedAmount`/`executedAmount`. `prefer` chooses which to read on the + * expected/executed variant. */ -function getAuctionDestChainId({ - order, - chainId, -}: Pick) { - return isOrderCrosschain(order) ? order.bridge.destinationChainId : chainId; -} +function getTokenSideAmount( + side: DeltaTokenSide, + prefer: 'expected' | 'executed' +): string { + if ('amount' in side) return side.amount; -const OrderKindToSwapSide = { - [OrderKind.Sell]: 'SELL', - [OrderKind.Buy]: 'BUY', -} as const; + const value = + prefer === 'executed' ? side.executedAmount : side.expectedAmount; -/** - * @description Returns swap side from a Delta or External order kind. - */ -function getSwapSideFromDeltaOrder( - order: DeltaAuctionOrder | ExternalDeltaOrder -): SwapSideUnion { - return OrderKindToSwapSide[order.kind]; + return value ?? '0'; } -const TwapTypeToSwapSide = { - TWAPOrder: 'SELL', - TWAPBuyOrder: 'BUY', -} as const; - /** - * @description Returns swap side from TWAP on-chain order type. + * @description Returns the source chain id for the auction (the input side's chain). */ -function getSwapSideFromTwapOrderType( - onChainOrderType: 'TWAPOrder' | 'TWAPBuyOrder' -): SwapSideUnion { - return TwapTypeToSwapSide[onChainOrderType]; +function getAuctionSrcChainId(auction: Pick): number { + return auction.input.chainId; } /** - * @description Returns swap side for any auction type. + * @description Returns the destination chain id for the auction (the output side's chain). + * Equals the source chain id for same-chain orders. */ -function getAuctionSwapSide(auction: DeltaAuction): SwapSideUnion { - if (isTWAPAuction(auction)) { - // TWAP orders have onChainOrderType instead of kind - return getSwapSideFromTwapOrderType(auction.onChainOrderType); - } - return getSwapSideFromDeltaOrder(auction.order); +function getAuctionDestChainId(auction: Pick): number { + return auction.output.chainId; } /** - * @description Returns unified order data with normalized amounts, tokens, and side. + * @description Returns the swap side for any auction. The auction carries `side` + * directly, so no order introspection is needed. */ -function getUnifiedDeltaOrderData( - auction: DeltaAuction -): UnifiedDeltaOrderData { - const { order, chainId } = auction; - - const { srcToken, destToken } = getOrderTokenAddresses(order); - const { expected, final, minimal } = getAuctionAmounts(auction); - - const srcChainId = chainId; - const destChainId = getAuctionDestChainId({ order, chainId }); - - const swapSide = getAuctionSwapSide(auction); - - const filledPercent = getFilledPercent(auction); - - return { - srcChainId, - destChainId, - srcAmount: final?.srcAmount || expected.srcAmount, - destAmount: final?.destAmount || expected.destAmount, - amounts: { - expected, - final, - minimal, - }, - srcToken, - destToken, - swapSide, - filledPercent, - }; +function getAuctionSwapSide( + auction: Pick +): SwapSideUnion { + return auction.side; } /** - * @description Returns source and destination token addresses for an order. + * @description Returns source and destination token addresses for the auction, + * read from the input/output sides (already resolved to the dest-chain token + * for cross-chain orders). */ -function getOrderTokenAddresses(order: DeltaAuction['order']) { - const srcToken = order.srcToken; - const destToken = isOrderCrosschain(order) - ? order.bridge.outputToken - : order.destToken; +function getAuctionTokenAddresses( + auction: Pick +) { return { - srcToken, - destToken, + srcToken: auction.input.token, + destToken: auction.output.token, }; } /** - * @description Aggregates transaction amounts into total source and destination values. + * @description Aggregates transaction amounts into total spent (src) and + * received (dest) values. */ -function getTransactionAmounts(transactions: DeltaAuctionTransaction[]) { +function getTransactionAmounts(transactions: DeltaTransaction[]) { const { srcAmount, destAmount } = transactions.reduce( - (acc, { spentAmount, receivedAmount, bridgeMetadata }) => { - return { - srcAmount: acc.srcAmount + BigInt(spentAmount), - destAmount: - acc.destAmount + - BigInt(bridgeMetadata ? bridgeMetadata.outputAmount : receivedAmount), - }; - }, - { - srcAmount: 0n, - destAmount: 0n, - } + (acc, { spentAmount, receivedAmount }) => ({ + srcAmount: acc.srcAmount + BigInt(spentAmount ?? 0), + destAmount: acc.destAmount + BigInt(receivedAmount ?? 0), + }), + { srcAmount: 0n, destAmount: 0n } ); return { @@ -351,224 +471,125 @@ function getTransactionAmounts(transactions: DeltaAuctionTransaction[]) { } /** - * @description Returns expected and, when available, final amounts for an auction. + * @description Calculates the overall filled percent (0–100) from the + * per-transaction `filledPercent` values. For TWAP orders each transaction is + * a slice (0–100 of that slice), so the slice values are averaged across + * `numSlices`; for single-fill orders the values sum directly. */ -function getAuctionAmounts(auction: DeltaAuction) { - const isTwap = checks.isTWAPAuction(auction); - if (isTwap) { - return getTwapAuctionAmounts(auction); - } - - let expected = { - srcAmount: auction.order.srcAmount, - // defensive fallback in case Order shape changes or legacy Orders don't have all fields - destAmount: auction.order.expectedAmount || auction.order.destAmount, - }; - - let minimal = { - srcAmount: auction.order.srcAmount, - destAmount: auction.order.destAmount, - }; +function getFilledPercent( + auction: Pick +): number { + if (auction.transactions.length === 0) return 0; - const order = auction.order; + const total = auction.transactions.reduce( + (acc, { filledPercent }) => acc + filledPercent, + 0 + ); - if (isOrderCrosschain(order)) { - expected = { - srcAmount: expected.srcAmount, - destAmount: scaleByFactor( - BigInt(expected.destAmount), - order.bridge.scalingFactor - ).toString(), - }; - - minimal = { - srcAmount: minimal.srcAmount, - destAmount: scaleByFactor( - BigInt(minimal.destAmount), - order.bridge.scalingFactor - ).toString(), - }; + if (isTWAPOrder(auction.order) && auction.order.numSlices > 0) { + return total / auction.order.numSlices; } - const isExecuted = isExecutedAuction(auction); - if (isExecuted) { - const final = getTransactionAmounts(auction.transactions); - return { - final, - expected, - minimal, - }; - } - return { - expected, - minimal, - }; + return total; } /** - * @description Checks whether an order includes valid cross-chain bridge details. + * @description Returns the executed amount of a token side when present, + * otherwise the provided fallback (typically summed from transactions). */ -function isOrderCrosschain( - order: T - // Extract == never -): order is Prettify & { bridge: Bridge }> { - return ( - 'bridge' in order && !!order.bridge && order.bridge.destinationChainId !== 0 - ); -} - -function scaleByFactor(amount: bigint, scalingFactor: number): bigint { - if (!amount) return 0n; - - if (scalingFactor === 0) return amount; - - const base = 10n; +function getExecutedAmount(side: DeltaTokenSide, fallback: string): string { + if ('executedAmount' in side && side.executedAmount != null) { + return side.executedAmount; + } - return scalingFactor < 0 - ? amount / base ** BigInt(-scalingFactor) - : amount * base ** BigInt(scalingFactor); + return fallback; } -type ExecutedDeltaAuctionProps = { - status: 'EXECUTED'; - transactions: NonEmptyArray; -}; - /** - * @description Checks whether an auction is fully executed. + * @description Returns expected amounts and, once the auction is completed, + * executed amounts. Executed amounts prefer the `executedAmount` baked onto the + * token sides and fall back to summing transactions. */ -function isExecutedAuction< - T extends Pick ->(auction: T): auction is T & ExecutedDeltaAuctionProps { - if (auction.status !== 'EXECUTED') return false; +function getAuctionAmounts( + auction: Pick< + DeltaAuction, + 'status' | 'order' | 'input' | 'output' | 'transactions' + > +) { + const expected = { + srcAmount: getTokenSideAmount(auction.input, 'expected'), + destAmount: getTokenSideAmount(auction.output, 'expected'), + }; - if (isOrderCrosschain(auction.order)) { - const filledPercent = getFilledPercent(auction); - return filledPercent === 100; + if (!isCompletedAuction(auction)) { + return { expected }; } - return true; -} - -const failedAuctionStatuses = [ - 'FAILED', - 'EXPIRED', - 'CANCELLED', - 'REFUNDED', -] as const; - -const failedAuctionStatusesSet = new Set( - failedAuctionStatuses -); + const txAmounts = getTransactionAmounts(auction.transactions); -type FailedDeltaAuctionProps = - | { - status: (typeof failedAuctionStatuses)[number]; - } - | { - status: 'EXECUTED'; // srcChain tx succeeded - bridgeStatus: 'expired' | 'refunded'; // destChain tx failed or relayer didn't deliver - }; - -/** - * @description Checks whether an auction is failed on source or destination chain. - */ -function isFailedAuction< - T extends Pick ->(auction: T): auction is T & FailedDeltaAuctionProps { - // already failed on srcChain, whether Order is crosschain or not - if (failedAuctionStatusesSet.has(auction.status)) return true; - - // crosschain Order is executed on srcChain, but failed on destChain - if (auction.status === 'EXECUTED' && isOrderCrosschain(auction.order)) { - return ( - auction.bridgeStatus === 'expired' || auction.bridgeStatus === 'refunded' - ); - } + const executed = { + srcAmount: getExecutedAmount(auction.input, txAmounts.srcAmount), + destAmount: getExecutedAmount(auction.output, txAmounts.destAmount), + }; - return false; + return { expected, executed }; } /** - * @description Checks whether an auction status is cancelled. - */ -function isCanceledAuction>( - auction: T -): auction is T & { - status: 'CANCELLED'; -} { - return auction.status === 'CANCELLED'; -} - -/** - * @description Checks whether an auction status is expired. + * @description Returns unified order data with normalized amounts, tokens, + * chain ids, and side, built from the auction envelope. */ -function isExpiredAuction>( - auction: T -): auction is T & { - status: 'EXPIRED'; -} { - return auction.status === 'EXPIRED'; -} +function getUnifiedDeltaOrderData( + auction: DeltaAuction +): UnifiedDeltaOrderData { + const { srcToken, destToken } = getAuctionTokenAddresses(auction); + const { expected, executed } = getAuctionAmounts(auction); -const pendingAuctionStatuses = [ - 'NOT_STARTED', - 'AWAITING_PRE_SIGNATURE', - 'RUNNING', - 'EXECUTING', -] as const; + const srcChainId = getAuctionSrcChainId(auction); + const destChainId = getAuctionDestChainId(auction); + const swapSide = getAuctionSwapSide(auction); + const filledPercent = getFilledPercent(auction); -const pendingAuctionStatusesSet = new Set( - pendingAuctionStatuses -); -/** - * @description Checks whether an auction status is in pending execution states. - */ -function isPendingAuction>( - auction: T -): auction is T & { - status: (typeof pendingAuctionStatuses)[number]; -} { - return pendingAuctionStatusesSet.has(auction.status); + return { + srcChainId, + destChainId, + srcAmount: executed?.srcAmount || expected.srcAmount, + destAmount: executed?.destAmount || expected.destAmount, + amounts: { + expected, + // No separate "minimal" amount in the v2 envelope; mirrors expected. + minimal: expected, + final: executed, + }, + srcToken, + destToken, + swapSide, + filledPercent, + }; } -/** - * @description Auction can be cancelled in the middle of execution, - * or crosschain-TWAP slices may not all be bridged, - * or order can be suspended if it runs out of user balance/allowance. - * Orders in the middle of normal execution can also be considered partially executed if they have any transactions. - */ -function isPartiallyExecutedAuction< - T extends Pick ->( - auction: T -): auction is T & { transactions: NonEmptyArray } { - if (auction.transactions.length === 0) return false; - - const filledPercent = getFilledPercent(auction); +const getters = { + getUnifiedDeltaOrderData, - return filledPercent > 0 && filledPercent < 100; -} + // auction-level getters — v2 envelope shape. + getAuctionTokenAddresses, + getAuctionSrcChainId, + getAuctionDestChainId, + getAuctionSwapSide, + getTransactionAmounts, + getAuctionAmounts, + getFilledPercent, -/** - * @description Calculates filled percentage from auction transaction filled bps values. - */ -function getFilledPercent( - auction: Pick -): number { - const completeTransactions = !isOrderCrosschain(auction.order) - ? auction.transactions - : auction.transactions.filter( - (transaction) => transaction.bridgeStatus === 'filled' - ); - - const filledPercentBps = completeTransactions.reduce( - (acc, { filledPercent }) => { - return acc + filledPercent; - }, - 0 - ); + // order-level getters — order structs shared across families. + getOrderTokenAddresses, + getSwapSideFromDeltaOrder, + getSwapSideFromTwapOrderType, + getExpectedTwapSrcAmount, + getExpectedTwapDestAmount, + getExpectedTwapOrderAmounts, +}; - const filledPercent = filledPercentBps / 100; - return filledPercent; -} +export const OrderHelpers = { + checks, + getters, +}; diff --git a/src/methods/delta/helpers/types.ts b/src/methods/delta/helpers/types.ts index 9d86178a0..6ae8913f5 100644 --- a/src/methods/delta/helpers/types.ts +++ b/src/methods/delta/helpers/types.ts @@ -1,6 +1,5 @@ import type { EnumerateLiteral } from '../../../types'; import { SwapSide } from '../../../constants'; -import { Prettify } from 'ts-essentials'; export type SwapSideUnion = EnumerateLiteral; @@ -123,6 +122,41 @@ export type ExternalDeltaOrder = { data: string; }; +export type ProductiveDeltaOrder = { + /** @description The address of the order owner */ + owner: string; + /** @description The address of the order beneficiary */ + beneficiary: string; + /** @description The address of the src token */ + srcToken: string; + /** @description The address of the dest token */ + destToken: string; + /** @description The amount of src token to swap */ + srcAmount: string; + /** @description The minimum amount of dest token to receive */ + destAmount: string; + /** @description The expected amount of token to receive */ + expectedAmount: string; + /** @description The deadline for the order */ + deadline: number; + /** @description The nonce of the order */ + nonce: string; + /** @description Metadata for the order, hex string */ + metadata: string; + /** @description Encoded partner address, fee bps, and flags for the order. partnerAndFee = (partner << 96) | (partnerTakesSurplus << 8) | fee in bps (max fee is 2%) */ + partnerAndFee: string; + /** @description Optional permit signature for the src token */ + permit: string; + /** @description The strategy address. */ + strategy: string; + /** @description The number of shares to execute for this order. */ + shares: string; + /** @description Whether the order uses shares or raw amounts. */ + useShares: boolean; + /** @description The bridge input */ + bridge: Bridge; +}; + type TWAPDeltaOrderBase = { /** @description The address of the order owner */ owner: string; @@ -165,162 +199,23 @@ export type TWAPBuyDeltaOrder = TWAPDeltaOrderBase & { maxSrcAmount: string; // wei }; -export type DeltaAuctionStatus = - | 'NOT_STARTED' - | 'AWAITING_PRE_SIGNATURE' - | 'RUNNING' - | 'EXECUTING' - | 'EXECUTED' - | 'FAILED' - | 'EXPIRED' - | 'CANCELLED' - | 'CANCELLING' - | 'SUSPENDED' - | 'REFUNDED'; - -export type DeltaAuctionTransaction = { - id: string; - hash: string; - orderId: string; - bidId: string | null; - blockNumber: number; - blockHash: string; - blockTimestamp: string | null; // ISO string, null for older Orders - gasUsed: bigint; - gasPrice: bigint; - blobGasUsed: bigint; - blobGasPrice: bigint; - index: number; - status: number; - from: string; - to: string; - receivedAmount: string; - receivedAmountUSD: number; - spentAmount: string; - spentAmountUSD: number; - filledPercent: number; // in base points - protocolFee: string; - partnerFee: string; - agent: string; - auctionId: string; - - // transactgion.bridge* fields = null for single-chain orders - bridgeMetadata: BridgeMetadata | null; - bridgeStatus: BridgeStatus | null; - bridgeProtocol: string | null; - bridgeOverride: Pick | null; -}; - export type OnChainOrderMap = { Order: DeltaAuctionOrder; + FillableOrder: DeltaAuctionOrder; ExternalOrder: ExternalDeltaOrder; TWAPOrder: TWAPDeltaOrder; TWAPBuyOrder: TWAPBuyDeltaOrder; + ProductiveOrder: ProductiveDeltaOrder; }; -type BaseBridgeAuctionFields = Pick< - DeltaAuctionBase, - 'bridgeMetadata' | 'bridgeStatus' ->; - -type BridgeAuctionFiledsMap = { - Order: BaseBridgeAuctionFields; - ExternalOrder: BaseBridgeAuctionFields; - TWAPOrder: Record; - TWAPBuyOrder: Record; -}; - -type DeltaAuctionBase = { - id: string; - deltaVersion: string; // 1.0 or 2.0 currently - user: string; - status: DeltaAuctionStatus; - orderHash: string | null; // not available on old Orders only - transactions: DeltaAuctionTransaction[]; - chainId: number; - partner: string; - referrerAddress: string | null; - expiresAt: string; - createdAt: string; - updatedAt: string; - partiallyFillable: boolean; - - excludeAgents: string[] | null; - includeAgents: string[] | null; - - // bridge* fields = null for single-chain orders and all TWAP orders - bridgeMetadata: BridgeMetadata | null; - bridgeStatus: BridgeStatus | null; - - type: 'MARKET' | 'LIMIT'; -}; - -export type DeltaAuction = - T extends T - ? Prettify< - DeltaAuctionBase & { - onChainOrderType: T; - order: OnChainOrderMap[T]; - } & BridgeAuctionFiledsMap[T] - > - : never; - -export type DeltaAuctionDelta = DeltaAuction<'Order'>; -export type DeltaAuctionExternal = DeltaAuction<'ExternalOrder'>; -export type DeltaAuctionTWAP = DeltaAuction<'TWAPOrder'>; -export type DeltaAuctionTWAPBuy = DeltaAuction<'TWAPBuyOrder'>; - -export type DeltaAuctionUnion = - | DeltaAuctionDelta - | DeltaAuctionExternal - | DeltaAuctionTWAP - | DeltaAuctionTWAPBuy; - export type DeltaOrderUnion = OnChainOrderMap[keyof OnChainOrderMap]; -export type BridgeMetadata = { - /** @description The amount that user should expect to get */ - outputAmount: string; - /** @description The cross-chain deadline. If deadline passes, the bridgeStatus would be expired */ - fillDeadline?: number; // available for Across protocol - /** @description The deposit id */ - depositId?: number; // available for Across protocol - /** @description The transaction hash on the destination chain that fulfilled the order. When bridgeStatus='filled' */ - fillTx?: string; - /** @description The transaction hash on the source chain that refunded the deposit. When bridgeStatus='refunded' */ - depositRefundTxHash?: string; -}; - -// refunded is basically failed -export type BridgeStatus = 'pending' | 'filled' | 'expired' | 'refunded'; - -export type OnChainOrderType = - | 'Order' - | 'ExternalOrder' - | 'TWAPOrder' - | 'TWAPBuyOrder'; +export type OnChainOrderType = keyof OnChainOrderMap; export type TWAPOnChainOrderType = 'TWAPOrder' | 'TWAPBuyOrder'; -//// available on BridgePrice //// - -type BridgeQuoteFee = { - feeToken: string; - amount: string; - amountInSrcToken: string; - amountInUSD: string; -}; - -export type BridgePriceInfo = { - protocolName: string; - destAmountAfterBridge: string; - destUSDAfterBridge: string; - fees: BridgeQuoteFee[]; - estimatedTimeMs: number; - fastest: boolean; - bestReturn: boolean; - recommended: boolean; -}; +/** @description Order kind: MARKET (immediate) vs LIMIT (rate-pegged). */ +export type DeltaOrderType = 'MARKET' | 'LIMIT'; export type UnifiedDeltaOrderData = { /** @description amounts at the start of Order execution and after Order execution. May differ from each other */ diff --git a/src/methods/delta/index.ts b/src/methods/delta/index.ts index dd0a1452f..4b56c7ba3 100644 --- a/src/methods/delta/index.ts +++ b/src/methods/delta/index.ts @@ -1,31 +1,11 @@ import type { ConstructProviderFetchInput } from '../../types'; -import type { DeltaAuction } from './helpers/types'; -import { - BuildDeltaOrderDataParams, - BuildDeltaOrderFunctions, - constructBuildDeltaOrder, -} from './buildDeltaOrder'; -import { - constructPostDeltaOrder, - DeltaOrderToPost, - PostDeltaOrderFunctions, -} from './postDeltaOrder'; -import { - constructSignDeltaOrder, - SignDeltaOrderFunctions, -} from './signDeltaOrder'; +import type { DeltaAuction } from './types'; + +// reused v1 modules import { GetDeltaContractFunctions, constructGetDeltaContract, } from './getDeltaContract'; -import { - constructGetDeltaPrice, - GetDeltaPriceFunctions, -} from './getDeltaPrice'; -import { - constructGetDeltaOrders, - GetDeltaOrdersFunctions, -} from './getDeltaOrders'; import { constructGetPartnerFee, GetPartnerFeeFunctions, @@ -34,64 +14,122 @@ import { ApproveTokenForDeltaFunctions, constructApproveTokenForDelta, } from './approveForDelta'; -import { - constructGetBridgeInfo, - GetBridgeInfoFunctions, -} from './getBridgeInfo'; -import { - constructIsTokenSupportedInDelta, - IsTokenSupportedInDeltaFunctions, -} from './isTokenSupportedInDelta'; -import { - CancelDeltaOrderFunctions, - constructCancelDeltaOrder, -} from './cancelDeltaOrder'; import { constructPreSignDeltaOrder, PreSignDeltaOrderFunctions, } from './preSignDeltaOrder'; +import { + constructPreSignExternalDeltaOrder, + PreSignExternalDeltaOrderFunctions, +} from './preSignExternalDeltaOrder'; +import { + constructPreSignTWAPDeltaOrder, + PreSignTWAPDeltaOrderFunctions, +} from './preSignTWAPDeltaOrder'; import { DeltaTokenModuleFunctions, constructDeltaTokenModule, } from './deltaTokenModule'; + +// new v2 modules +import { + BuildDeltaOrderFunctions, + BuildDeltaOrderParams, + BuiltDeltaOrder, + constructBuildDeltaOrder, +} from './buildDeltaOrder'; import { - BuildExternalDeltaOrderParams, BuildExternalDeltaOrderFunctions, + BuildExternalDeltaOrderParams, constructBuildExternalDeltaOrder, } from './buildExternalDeltaOrder'; import { - constructSignExternalDeltaOrder, - SignExternalDeltaOrderFunctions, -} from './signExternalDeltaOrder'; -import { - constructPostExternalDeltaOrder, - PostExternalDeltaOrderFunctions, -} from './postExternalDeltaOrder'; -import { - constructPreSignExternalDeltaOrder, - PreSignExternalDeltaOrderFunctions, -} from './preSignExternalDeltaOrder'; -import { - BuildTWAPDeltaOrderParams, BuildTWAPDeltaOrderFunctions, + BuildTWAPDeltaOrderParams, constructBuildTWAPDeltaOrder, } from './buildTWAPDeltaOrder'; import { - constructSignTWAPDeltaOrder, - SignTWAPDeltaOrderFunctions, -} from './signTWAPDeltaOrder'; + constructPostDeltaOrder, + DeltaOrderToPost, + PostDeltaOrderFunctions, +} from './postDeltaOrder'; +import { + constructPostExternalDeltaOrder, + PostExternalDeltaOrderFunctions, +} from './postExternalDeltaOrder'; import { constructPostTWAPDeltaOrder, PostTWAPDeltaOrderFunctions, } from './postTWAPDeltaOrder'; import { - constructPreSignTWAPDeltaOrder, - PreSignTWAPDeltaOrderFunctions, -} from './preSignTWAPDeltaOrder'; + constructGetDeltaPrice, + GetDeltaPriceFunctions, +} from './getDeltaPrice'; +import { + constructGetDeltaOrders, + GetDeltaOrdersFunctions, +} from './getDeltaOrders'; +import { + constructGetBridgeRoutes, + GetBridgeRoutesFunctions, +} from './getBridgeRoutes'; +import { + constructIsTokenSupportedInDelta, + IsTokenSupportedInDeltaFunctions, +} from './isTokenSupportedInDelta'; +import { + CancelDeltaOrderFunctions, + constructCancelDeltaOrder, +} from './cancelDeltaOrder'; +import { + constructGetAgentsList, + GetAgentsListFunctions, +} from './getAgentsList'; + +// Re-export the public surface so consumers can reach every leaf module. +export * from './types'; +export * from './buildDeltaOrder'; +export * from './buildExternalDeltaOrder'; +export * from './buildTWAPDeltaOrder'; +export * from './postDeltaOrder'; +export * from './postExternalDeltaOrder'; +export * from './postTWAPDeltaOrder'; +export * from './getDeltaPrice'; +export * from './getDeltaOrders'; +export * from './getBridgeRoutes'; +export * from './isTokenSupportedInDelta'; +export * from './cancelDeltaOrder'; +export * from './getAgentsList'; +export { OrderHelpers } from './helpers/orders'; + +// ── Sign v2 ───────────────────────────────────────────────────────────────── + +type SignDeltaOrder = (builtOrder: BuiltDeltaOrder) => Promise; + +export type SignDeltaOrderFunctions = { + /** @description Sign a BuiltDeltaOrder (any order type) using EIP-712 typed data. */ + signDeltaOrder: SignDeltaOrder; +}; -export type SubmitDeltaOrderParams = BuildDeltaOrderDataParams & { - /** @description designates the Order as being able to be partially filled, as opposed to fill-or-kill */ - partiallyFillable?: boolean; +export const constructSignDeltaOrder = ( + options: Pick< + ConstructProviderFetchInput, + 'contractCaller' + > +): SignDeltaOrderFunctions => { + const signDeltaOrder: SignDeltaOrder = async (builtOrder) => { + return options.contractCaller.signTypedDataCall({ + types: builtOrder.toSign.types, + domain: builtOrder.toSign.domain, + data: builtOrder.toSign.value, + }); + }; + return { signDeltaOrder }; +}; + +// ── Submit orchestrators ───────────────────────────────────────────────────── + +export type SubmitDeltaOrderParams = BuildDeltaOrderParams & { /** @description Referrer address */ referrerAddress?: string; degenMode?: boolean; @@ -109,7 +147,6 @@ export const constructSubmitDeltaOrder = ( options: ConstructProviderFetchInput ): SubmitDeltaOrderFuncs => { const { buildDeltaOrder } = constructBuildDeltaOrder(options); - // in the normal submitOrderFlow preSign tx is not involved const { signDeltaOrder } = constructSignDeltaOrder(options); const { postDeltaOrder } = constructPostDeltaOrder(options); @@ -117,10 +154,10 @@ export const constructSubmitDeltaOrder = ( const orderData = await buildDeltaOrder(orderParams); const signature = await signDeltaOrder(orderData); - const response = await postDeltaOrder({ + return postDeltaOrder({ signature, partner: orderParams.partner, - order: orderData.data, + order: orderData.toSign.value, partiallyFillable: orderParams.partiallyFillable, referrerAddress: orderParams.referrerAddress, type: orderParams.type, @@ -128,17 +165,12 @@ export const constructSubmitDeltaOrder = ( excludeAgents: orderParams.excludeAgents, degenMode: orderParams.degenMode, }); - - return response; }; return { submitDeltaOrder }; }; export type SubmitExternalDeltaOrderParams = BuildExternalDeltaOrderParams & { - /** @description designates the Order as being able to be partially filled, as opposed to fill-or-kill */ - partiallyFillable?: boolean; - /** @description Referrer address */ referrerAddress?: string; } & Pick; @@ -154,36 +186,31 @@ export const constructSubmitExternalDeltaOrder = ( options: ConstructProviderFetchInput ): SubmitExternalDeltaOrderFuncs => { const { buildExternalDeltaOrder } = constructBuildExternalDeltaOrder(options); - const { signExternalDeltaOrder } = constructSignExternalDeltaOrder(options); + const { signDeltaOrder } = constructSignDeltaOrder(options); const { postExternalDeltaOrder } = constructPostExternalDeltaOrder(options); const submitExternalDeltaOrder: SubmitExternalDeltaOrder = async ( orderParams ) => { const orderData = await buildExternalDeltaOrder(orderParams); - const signature = await signExternalDeltaOrder(orderData); + const signature = await signDeltaOrder(orderData); - const response = await postExternalDeltaOrder({ + return postExternalDeltaOrder({ signature, partner: orderParams.partner, - order: orderData.data, + order: orderData.toSign.value, partiallyFillable: orderParams.partiallyFillable, referrerAddress: orderParams.referrerAddress, type: orderParams.type, includeAgents: orderParams.includeAgents, excludeAgents: orderParams.excludeAgents, }); - - return response; }; return { submitExternalDeltaOrder }; }; export type SubmitTWAPDeltaOrderParams = BuildTWAPDeltaOrderParams & { - /** @description designates the Order as being able to be partially filled, as opposed to fill-or-kill */ - partiallyFillable?: boolean; - /** @description Referrer address */ referrerAddress?: string; degenMode?: boolean; } & Pick; @@ -200,17 +227,17 @@ export const constructSubmitTWAPDeltaOrder = ( options: ConstructProviderFetchInput ): SubmitTWAPDeltaOrderFuncs => { const { buildTWAPDeltaOrder } = constructBuildTWAPDeltaOrder(options); - const { signTWAPDeltaOrder } = constructSignTWAPDeltaOrder(options); + const { signDeltaOrder } = constructSignDeltaOrder(options); const { postTWAPDeltaOrder } = constructPostTWAPDeltaOrder(options); const submitTWAPDeltaOrder: SubmitTWAPDeltaOrder = async (orderParams) => { const orderData = await buildTWAPDeltaOrder(orderParams); - const signature = await signTWAPDeltaOrder(orderData); + const signature = await signDeltaOrder(orderData); - const response = await postTWAPDeltaOrder({ + return postTWAPDeltaOrder({ signature, partner: orderParams.partner, - order: orderData.data, + order: orderData.toSign.value, onChainOrderType: orderParams.onChainOrderType, partiallyFillable: orderParams.partiallyFillable, referrerAddress: orderParams.referrerAddress, @@ -219,39 +246,38 @@ export const constructSubmitTWAPDeltaOrder = ( excludeAgents: orderParams.excludeAgents, degenMode: orderParams.degenMode, }); - - return response; }; return { submitTWAPDeltaOrder }; }; -export type DeltaOrderHandlers = SubmitDeltaOrderFuncs & - ApproveTokenForDeltaFunctions & - BuildDeltaOrderFunctions & - GetDeltaOrdersFunctions & - GetDeltaPriceFunctions & - GetDeltaContractFunctions & - GetPartnerFeeFunctions & - GetBridgeInfoFunctions & - IsTokenSupportedInDeltaFunctions & - PostDeltaOrderFunctions & - SignDeltaOrderFunctions & - PreSignDeltaOrderFunctions & - CancelDeltaOrderFunctions & - DeltaTokenModuleFunctions & +// ── Handler bundle ─────────────────────────────────────────────────────────── + +export type DeltaOrderHandlers = SubmitDeltaOrderFuncs & SubmitExternalDeltaOrderFuncs & - BuildExternalDeltaOrderFunctions & - SignExternalDeltaOrderFunctions & - PostExternalDeltaOrderFunctions & - PreSignExternalDeltaOrderFunctions & SubmitTWAPDeltaOrderFuncs & + BuildDeltaOrderFunctions & + BuildExternalDeltaOrderFunctions & BuildTWAPDeltaOrderFunctions & - SignTWAPDeltaOrderFunctions & + PostDeltaOrderFunctions & + PostExternalDeltaOrderFunctions & PostTWAPDeltaOrderFunctions & - PreSignTWAPDeltaOrderFunctions; + SignDeltaOrderFunctions & + PreSignDeltaOrderFunctions & + PreSignExternalDeltaOrderFunctions & + PreSignTWAPDeltaOrderFunctions & + GetDeltaPriceFunctions & + GetDeltaOrdersFunctions & + GetBridgeRoutesFunctions & + IsTokenSupportedInDeltaFunctions & + GetAgentsListFunctions & + GetDeltaContractFunctions & + GetPartnerFeeFunctions & + ApproveTokenForDeltaFunctions & + DeltaTokenModuleFunctions & + CancelDeltaOrderFunctions; -/** @description construct SDK with every Delta Order-related method, fetching from API and Order signing */ +/** @description Construct an SDK bundle exposing every Delta v2 method (queries, build/sign/post, on-chain helpers). */ export const constructAllDeltaOrdersHandlers = ( options: ConstructProviderFetchInput< TxResponse, @@ -263,15 +289,17 @@ export const constructAllDeltaOrdersHandlers = ( const deltaPrice = constructGetDeltaPrice(options); const partnerFee = constructGetPartnerFee(options); - const bridgeInfo = constructGetBridgeInfo(options); + const bridgeRoutes = constructGetBridgeRoutes(options); const isTokenSupportedInDelta = constructIsTokenSupportedInDelta(options); + const agentsList = constructGetAgentsList(options); const approveTokenForDelta = constructApproveTokenForDelta(options); - const deltaOrdersSubmit = constructSubmitDeltaOrder(options); + // single signer for every order family + const deltaOrdersSign = constructSignDeltaOrder(options); + const deltaOrdersSubmit = constructSubmitDeltaOrder(options); const deltaOrdersBuild = constructBuildDeltaOrder(options); - const deltaOrdersSign = constructSignDeltaOrder(options); const deltaOrdersPreSign = constructPreSignDeltaOrder(options); const deltaOrdersPost = constructPostDeltaOrder(options); @@ -281,14 +309,12 @@ export const constructAllDeltaOrdersHandlers = ( const externalDeltaOrdersSubmit = constructSubmitExternalDeltaOrder(options); const externalDeltaOrdersBuild = constructBuildExternalDeltaOrder(options); - const externalDeltaOrdersSign = constructSignExternalDeltaOrder(options); const externalDeltaOrdersPost = constructPostExternalDeltaOrder(options); const externalDeltaOrdersPreSign = constructPreSignExternalDeltaOrder(options); const twapDeltaOrdersSubmit = constructSubmitTWAPDeltaOrder(options); const twapDeltaOrdersBuild = constructBuildTWAPDeltaOrder(options); - const twapDeltaOrdersSign = constructSignTWAPDeltaOrder(options); const twapDeltaOrdersPost = constructPostTWAPDeltaOrder(options); const twapDeltaOrdersPreSign = constructPreSignTWAPDeltaOrder(options); @@ -297,24 +323,23 @@ export const constructAllDeltaOrdersHandlers = ( ...deltaOrdersContractGetter, ...deltaPrice, ...partnerFee, - ...bridgeInfo, + ...bridgeRoutes, ...isTokenSupportedInDelta, + ...agentsList, ...approveTokenForDelta, + ...deltaOrdersSign, ...deltaOrdersSubmit, ...deltaOrdersBuild, - ...deltaOrdersSign, ...deltaOrdersPreSign, ...deltaOrdersPost, ...deltaOrdersCancel, ...deltaTokenModule, ...externalDeltaOrdersSubmit, ...externalDeltaOrdersBuild, - ...externalDeltaOrdersSign, ...externalDeltaOrdersPost, ...externalDeltaOrdersPreSign, ...twapDeltaOrdersSubmit, ...twapDeltaOrdersBuild, - ...twapDeltaOrdersSign, ...twapDeltaOrdersPost, ...twapDeltaOrdersPreSign, }; diff --git a/src/methods/delta/isTokenSupportedInDelta.ts b/src/methods/delta/isTokenSupportedInDelta.ts index c0ae668ed..ee14a94df 100644 --- a/src/methods/delta/isTokenSupportedInDelta.ts +++ b/src/methods/delta/isTokenSupportedInDelta.ts @@ -6,8 +6,8 @@ import type { RequestParameters, } from '../../types'; -type TokenSupportedInDeltaResponse = { supported: boolean }; -type IsTokenSupportedInDeltaQueryOptions = { +type TokenSupportedResponse = { supported: boolean }; +type IsTokenSupportedQuery = { token: Address; chainId: number; }; @@ -26,20 +26,20 @@ export const constructIsTokenSupportedInDelta = ({ chainId, fetcher, }: ConstructFetchInput): IsTokenSupportedInDeltaFunctions => { - const bridgeInfoUrl = `${apiURL}/delta/prices/is-token-supported` as const; + const baseUrl = `${apiURL}/delta/v2/prices/is-token-supported` as const; const isTokenSupportedInDelta: IsTokenSupportedInDelta = async ( token, requestParams ) => { - const search = constructSearchString({ + const search = constructSearchString({ token, chainId, }); - const fetchURL = `${bridgeInfoUrl}/${search}` as const; + const fetchURL = `${baseUrl}/${search}` as const; - const data = await fetcher({ + const data = await fetcher({ url: fetchURL, method: 'GET', requestParams, @@ -48,7 +48,5 @@ export const constructIsTokenSupportedInDelta = ({ return data.supported; }; - return { - isTokenSupportedInDelta, - }; + return { isTokenSupportedInDelta }; }; diff --git a/src/methods/delta/postDeltaOrder.ts b/src/methods/delta/postDeltaOrder.ts index 3956a32c5..f494de4db 100644 --- a/src/methods/delta/postDeltaOrder.ts +++ b/src/methods/delta/postDeltaOrder.ts @@ -1,13 +1,10 @@ import { API_URL } from '../../constants'; import { constructSearchString } from '../../helpers/misc'; import type { ConstructFetchInput, RequestParameters } from '../../types'; -import type { - DeltaAuction, - OnChainOrderMap, - OnChainOrderType, -} from './helpers/types'; +import type { DeltaOrderType, OnChainOrderMap } from './helpers/types'; +import type { DeltaAuction } from './types'; -export type DeltaOrderToPost = { +export type DeltaOrderToPost = { /** @description Partner string */ partner?: string; /** @description Referrer address */ @@ -16,12 +13,10 @@ export type DeltaOrderToPost = { /** @description Signature of the order from order.owner address. EOA signatures must be submitted in ERC-2098 Compact Representation. */ signature: string; chainId: number; - /** @description designates the Order as being able to partially filled, as opposed to fill-or-kill */ + /** @description Designates the Order as being able to be partially filled, as opposed to fill-or-kill */ partiallyFillable?: boolean; - /** @description Type of the order. MARKET or LIMIT. Default is MARKET */ - type?: 'MARKET' | 'LIMIT'; - + type?: DeltaOrderType; includeAgents?: string[]; excludeAgents?: string[]; }; @@ -44,7 +39,7 @@ export const constructPostDeltaOrder = ({ chainId, fetcher, }: ConstructFetchInput): PostDeltaOrderFunctions => { - const postOrderUrl = `${apiURL}/delta/orders` as const; + const postOrderUrl = `${apiURL}/delta/v2/orders` as const; const postDeltaOrder: PostDeltaOrder = (_postData, requestParams) => { const { degenMode, ...postData } = _postData; @@ -53,7 +48,7 @@ export const constructPostDeltaOrder = ({ const search = constructSearchString<{ degenMode?: boolean }>({ degenMode, }); - const fetchURL = `${postOrderUrl}/${search}` as const; + const fetchURL = `${postOrderUrl}${search}` as const; return fetcher>({ url: fetchURL, diff --git a/src/methods/delta/postExternalDeltaOrder.ts b/src/methods/delta/postExternalDeltaOrder.ts index e3257d6d2..90b4455cd 100644 --- a/src/methods/delta/postExternalDeltaOrder.ts +++ b/src/methods/delta/postExternalDeltaOrder.ts @@ -1,7 +1,7 @@ import { API_URL } from '../../constants'; import type { ConstructFetchInput, RequestParameters } from '../../types'; -import type { DeltaAuction } from './helpers/types'; import type { DeltaOrderToPost } from './postDeltaOrder'; +import type { DeltaAuction } from './types'; export type PostExternalDeltaOrderParams = Omit< DeltaOrderToPost<'ExternalOrder'>, @@ -22,7 +22,7 @@ export const constructPostExternalDeltaOrder = ({ chainId, fetcher, }: ConstructFetchInput): PostExternalDeltaOrderFunctions => { - const postOrderUrl = `${apiURL}/delta/orders` as const; + const postOrderUrl = `${apiURL}/delta/v2/orders` as const; const postExternalDeltaOrder: PostExternalDeltaOrder = ( postData, diff --git a/src/methods/delta/postTWAPDeltaOrder.ts b/src/methods/delta/postTWAPDeltaOrder.ts index ec2279758..31cc4ddc2 100644 --- a/src/methods/delta/postTWAPDeltaOrder.ts +++ b/src/methods/delta/postTWAPDeltaOrder.ts @@ -1,9 +1,10 @@ -import { Prettify } from 'ts-essentials'; +import type { Prettify } from 'ts-essentials'; import { API_URL } from '../../constants'; +import { constructSearchString } from '../../helpers/misc'; import type { ConstructFetchInput, RequestParameters } from '../../types'; -import type { DeltaAuction, TWAPOnChainOrderType } from './helpers/types'; +import type { TWAPOnChainOrderType } from './helpers/types'; import type { DeltaOrderToPost } from './postDeltaOrder'; -import { constructSearchString } from '../../helpers/misc'; +import type { DeltaAuction } from './types'; export type PostTWAPDeltaOrderParams = Prettify< Omit< @@ -30,7 +31,7 @@ export const constructPostTWAPDeltaOrder = ({ chainId, fetcher, }: ConstructFetchInput): PostTWAPDeltaOrderFunctions => { - const postOrderUrl = `${apiURL}/delta/orders` as const; + const postOrderUrl = `${apiURL}/delta/v2/orders` as const; const postTWAPDeltaOrder: PostTWAPDeltaOrder = (_postData, requestParams) => { const { degenMode, ...postData } = _postData; @@ -43,7 +44,7 @@ export const constructPostTWAPDeltaOrder = ({ degenMode, }); - const fetchURL = `${postOrderUrl}/${search}` as const; + const fetchURL = `${postOrderUrl}${search}` as const; return fetcher | DeltaAuction<'TWAPBuyOrder'>>({ url: fetchURL, diff --git a/src/methods/delta/signDeltaOrder.ts b/src/methods/delta/signDeltaOrder.ts deleted file mode 100644 index cd13fea1d..000000000 --- a/src/methods/delta/signDeltaOrder.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ConstructProviderFetchInput } from '../../types'; -import { SignableDeltaOrderData } from './helpers/buildDeltaOrderData'; -import { sanitizeDeltaOrderData } from './helpers/misc'; - -type SignDeltaOrder = ( - signableOrderData: SignableDeltaOrderData -) => Promise; - -export type SignDeltaOrderFunctions = { - signDeltaOrder: SignDeltaOrder; -}; - -// returns whatever `contractCaller` returns -// to allow for better versatility -export const constructSignDeltaOrder = ( - options: Pick< - ConstructProviderFetchInput, - 'contractCaller' - > -): SignDeltaOrderFunctions => { - const signDeltaOrder: SignDeltaOrder = async (typedData) => { - // types allow to pass OrderData & extra_stuff, but tx will break like that - const typedDataOnly: SignableDeltaOrderData = { - ...typedData, - data: sanitizeDeltaOrderData(typedData.data), - }; - const signature = await options.contractCaller.signTypedDataCall( - typedDataOnly - ); - - return signature; - }; - - return { signDeltaOrder }; -}; diff --git a/src/methods/delta/signExternalDeltaOrder.ts b/src/methods/delta/signExternalDeltaOrder.ts deleted file mode 100644 index 2fc4d6e11..000000000 --- a/src/methods/delta/signExternalDeltaOrder.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ConstructProviderFetchInput } from '../../types'; -import { SignableExternalOrderData } from './helpers/buildExternalOrderData'; -import { sanitizeExternalOrderData } from './helpers/misc'; - -type SignExternalDeltaOrder = ( - signableOrderData: SignableExternalOrderData -) => Promise; - -export type SignExternalDeltaOrderFunctions = { - signExternalDeltaOrder: SignExternalDeltaOrder; -}; - -// returns whatever `contractCaller` returns -// to allow for better versatility -export const constructSignExternalDeltaOrder = ( - options: Pick< - ConstructProviderFetchInput, - 'contractCaller' - > -): SignExternalDeltaOrderFunctions => { - const signExternalDeltaOrder: SignExternalDeltaOrder = async (typedData) => { - // types allow to pass OrderData & extra_stuff, but tx will break like that - const typedDataOnly: SignableExternalOrderData = { - ...typedData, - data: sanitizeExternalOrderData(typedData.data), - }; - const signature = await options.contractCaller.signTypedDataCall( - typedDataOnly - ); - - return signature; - }; - - return { signExternalDeltaOrder }; -}; diff --git a/src/methods/delta/signTWAPDeltaOrder.ts b/src/methods/delta/signTWAPDeltaOrder.ts deleted file mode 100644 index 736491c79..000000000 --- a/src/methods/delta/signTWAPDeltaOrder.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ConstructProviderFetchInput } from '../../types'; -import { SignableTWAPOrderData } from './helpers/buildTWAPOrderData'; -import { sanitizeTWAPOrderData } from './helpers/misc'; - -type SignTWAPDeltaOrder = ( - signableOrderData: SignableTWAPOrderData -) => Promise; - -export type SignTWAPDeltaOrderFunctions = { - signTWAPDeltaOrder: SignTWAPDeltaOrder; -}; - -export const constructSignTWAPDeltaOrder = ( - options: Pick< - ConstructProviderFetchInput, - 'contractCaller' - > -): SignTWAPDeltaOrderFunctions => { - const signTWAPDeltaOrder: SignTWAPDeltaOrder = async (typedData) => { - const typedDataOnly = { - ...typedData, - data: sanitizeTWAPOrderData(typedData.data), - } as SignableTWAPOrderData; - const signature = await options.contractCaller.signTypedDataCall( - typedDataOnly - ); - - return signature; - }; - - return { signTWAPDeltaOrder }; -}; diff --git a/src/methods/delta/types.ts b/src/methods/delta/types.ts new file mode 100644 index 000000000..f172d6260 --- /dev/null +++ b/src/methods/delta/types.ts @@ -0,0 +1,202 @@ +import type { Prettify } from 'ts-essentials'; +import type { Address } from '../../types'; +import type { TypedDataField } from '../common/orders/buildOrderData'; +import type { + Bridge, + DeltaOrderType, + DeltaOrderUnion, + OnChainOrderMap, + OnChainOrderType, +} from './helpers/types'; + +/** @description Response from POST /delta/v2/orders/build — EIP-712 typed data ready to sign. */ +export type BuiltDeltaOrder = { + toSign: { + domain: { + name: string; + version: string; + chainId: number; + verifyingContract: string; + }; + /** EIP-712 type definitions keyed by type name. */ + types: Record; + /** The on-chain order struct value to sign. */ + value: T; + }; + /** EIP-712 order hash. */ + orderHash: string; +}; + +/** @description Token identity used across v2 endpoints. */ +export type DeltaPriceToken = { + chainId: number; + address: Address; +}; + +/** @description A token amount with its USD value, used uniformly for inputs, outputs, and fees. */ +export type DeltaTokenAmount = { + token: DeltaPriceToken; + /** @description Amount in wei. */ + amount: string; + /** @description USD value of the amount. */ + amountUSD: string; +}; + +/** @description Bridge tag used to mark routes as recommended / fastest / best-return. */ +export type BridgeTag = 'recommended' | 'fastest' | 'best-return'; + +/** @description Subset of the on-chain Bridge struct returned in v2 price responses. + * (the full `route` object is passed to the server when building an order). */ +export type DeltaRouteBridgeContractParams = Omit; + +/** @description Bridge details on a route. Same-chain routes carry `null` for `route.bridge`. */ +export type DeltaRouteBridge = { + /** @description Bridge protocol identifier (e.g. "Across", "Relay", "Mayan"). */ + protocol: string; + /** @description Estimated bridging time in milliseconds. */ + estimatedTimeMs: number; + /** @description Tags for this route ("recommended", "fastest", "best-return"). May be empty. */ + tags: BridgeTag[]; + /** @description Bridge contract parameters as returned by the server. */ + contractParams: DeltaRouteBridgeContractParams; +}; + +/** @description A single step of a route (origin chain or destination chain). */ +export type DeltaRouteStep = { + /** @description The token amount entering this step (before any per-step swap). */ + input: DeltaTokenAmount; + /** @description The token amount exiting this step (after any per-step swap). */ + output: DeltaTokenAmount; +}; + +/** @description A route describes how the swap flows from origin chain to destination chain. */ +export type DeltaRoute = { + /** @description Source-chain step. The on-chain Delta order's amounts come from `origin.input` and `origin.output`. */ + origin: DeltaRouteStep; + /** @description Destination-chain step. For same-chain routes this mirrors `origin`. */ + destination: DeltaRouteStep; + /** @description Bridge details. `null` for same-chain routes. */ + bridge: DeltaRouteBridge | null; + /** @description Fee breakdown for this route. */ + fees: { + /** @description Gas fee (single DeltaTokenAmount on the source chain). */ + gas: DeltaTokenAmount; + /** @description Bridge fees (empty array for same-chain). */ + bridge: DeltaTokenAmount[]; + }; +}; + +/** @description v2 price response: route-based, cross-chain first. */ +export type DeltaPrice = { + /** @description Unique request ID for tracing. */ + id: string; + /** @description Order side. */ + side: 'SELL' | 'BUY'; + /** @description Token the user trades (identity only — amounts live in route.origin.input). */ + inputToken: DeltaPriceToken; + /** @description Token the user receives (identity only — amounts live in route.destination.output). */ + outputToken: DeltaPriceToken; + /** @description Recommended route with full amounts and bridge details. */ + route: DeltaRoute; + /** @description Partner info attached to this price. */ + partner: { + name: string; + /** @description Partner fee in percent (e.g. 0.12 = 0.12%). */ + feePercent: number; + }; + /** @description Address to approve for spending the input token. */ + spender: Address; + /** @description Alternative routes (other bridges). Each is a full DeltaRoute. */ + alternatives: DeltaRoute[]; +}; + +/** @description + * A flat bridge-routes entry returned by GET /delta/v2/prices/bridge-routes (better version of v1 prices/bridge-info). + * Can be used to populate bridge selection UIs and to validate that bridge is possible for a given src/dest pair before calling GET /delta/v2/prices. + * */ +export type BridgeRoute = { + srcChainId: number; + destChainId: number; + /** @description Output tokens supported on the dest chain for this src→dest pair. */ + tokens: Address[]; +}; + +/* ------------------------------------------------------------------ */ +/* Orders v2 response shape: DeltaAuction, same generic shape as */ +/* v1's DeltaAuction but with v2 base fields. */ +/* ------------------------------------------------------------------ */ + +/** @description Integrator-facing order status returned by v2 order endpoints. */ +const DeltaOrderStatusMap = { + Pending: 'PENDING', + AwaitingSignature: 'AWAITING_SIGNATURE', + Active: 'ACTIVE', + Suspended: 'SUSPENDED', + Cancelling: 'CANCELLING', + Bridging: 'BRIDGING', + Completed: 'COMPLETED', + Failed: 'FAILED', + Expired: 'EXPIRED', + Cancelled: 'CANCELLED', + Refunded: 'REFUNDED', +} as const; + +export type DeltaOrderStatus = + (typeof DeltaOrderStatusMap)[keyof typeof DeltaOrderStatusMap]; + +/** @description Token side on an order. SELL provides an explicit `amount`; BUY provides expected/executed amounts. */ +export type DeltaTokenSide = + | { + chainId: number; + token: Address; + amount: string; + } + | { + chainId: number; + token: Address; + expectedAmount: string | null; + executedAmount: string | null; + }; + +/** @description A single transaction entry on a v2 order. */ +export type DeltaTransaction = { + originTx: string; + destinationTx: string | null; + /** @description Filled percent of the slice (0–100). */ + filledPercent: number; + spentAmount: string | null; + receivedAmount: string | null; +}; + +type DeltaAuctionBase = { + id: string; + status: DeltaOrderStatus; + side: 'SELL' | 'BUY'; + type: DeltaOrderType; + input: DeltaTokenSide; + output: DeltaTokenSide; + owner: Address; + beneficiary: Address; + orderHash: string; + partner: string; + transactions: DeltaTransaction[]; + /** @description ISO datetime string. */ + createdAt: string; + /** @description ISO datetime string. */ + updatedAt: string; + /** @description ISO datetime string. */ + expiresAt: string; +}; + +/** @description Order shape returned by GET /v2/orders, /v2/orders/:id, /v2/orders/hash/:hash. + * Generic over `onChainOrderType` like v1's `DeltaAuction`: the type distributes over the + * union so that `order` narrows to the matching family (`OnChainOrderMap[T]`). */ +export type DeltaAuction = + T extends T + ? Prettify< + DeltaAuctionBase & { + onChainOrderType: T; + order: OnChainOrderMap[T]; + } + > + : never; diff --git a/src/methods/quote/getQuote.ts b/src/methods/quote/getQuote.ts index 9299f6f80..4cac17fb0 100644 --- a/src/methods/quote/getQuote.ts +++ b/src/methods/quote/getQuote.ts @@ -1,6 +1,6 @@ import { API_URL, SwapSide } from '../../constants'; import { constructSearchString } from '../../helpers/misc'; -import type { BridgePrice, DeltaPrice } from '../delta/getDeltaPrice'; +import type { DeltaPrice } from '../delta/types'; import type { ConstructFetchInput, EnumerateLiteral, @@ -58,12 +58,12 @@ export type QuoteWithDeltaPrice = { }; export type QuoteWithBridgePrice = { - delta: BridgePrice; + delta: DeltaPrice; deltaAddress: string; }; export type QuoteWithDeltaPriceAndBridgePrice = { - delta: DeltaPrice | BridgePrice; + delta: DeltaPrice; deltaAddress: string; }; @@ -123,7 +123,7 @@ export const constructGetQuote = ({ chainId, fetcher, }: ConstructFetchInput): GetQuoteFunctions => { - const pricesUrl = `${apiURL}/quote` as const; + const pricesUrl = `${apiURL}/v2/quote` as const; function getQuote( options: QuoteParams<'delta'> & { destChainId?: undefined }, diff --git a/src/sdk/simple.ts b/src/sdk/simple.ts index 8d16a1860..5b24f859c 100644 --- a/src/sdk/simple.ts +++ b/src/sdk/simple.ts @@ -129,9 +129,9 @@ import { GetQuoteFunctions, } from '../methods/quote/getQuote'; import { - constructGetBridgeInfo, - GetBridgeInfoFunctions, -} from '../methods/delta/getBridgeInfo'; + constructGetBridgeRoutes, + GetBridgeRoutesFunctions, +} from '../methods/delta/getBridgeRoutes'; import { constructIsTokenSupportedInDelta, IsTokenSupportedInDeltaFunctions, @@ -164,7 +164,7 @@ export type DeltaFetchMethods = BuildDeltaOrderFunctions & GetDeltaPriceFunctions & GetDeltaContractFunctions & GetPartnerFeeFunctions & - GetBridgeInfoFunctions & + GetBridgeRoutesFunctions & IsTokenSupportedInDeltaFunctions & PostDeltaOrderFunctions; @@ -294,7 +294,7 @@ export function constructSimpleSDK( constructGetDeltaPrice, constructGetDeltaContract, constructGetPartnerFee, - constructGetBridgeInfo, + constructGetBridgeRoutes, constructIsTokenSupportedInDelta ); diff --git a/src/types.ts b/src/types.ts index 345f85d9f..c19201145 100644 --- a/src/types.ts +++ b/src/types.ts @@ -241,3 +241,12 @@ export interface JsonFragment { */ readonly gas?: string; } + +/** @description Standard pagination envelope returned by paginated API endpoints. */ +export type PaginatedResponse = { + data: T[]; + total: number; + page: number; + limit: number; + hasMore: boolean; +}; diff --git a/tests/__snapshots__/delta.test.ts.snap b/tests/__snapshots__/delta.test.ts.snap deleted file mode 100644 index f4cb106b0..000000000 --- a/tests/__snapshots__/delta.test.ts.snap +++ /dev/null @@ -1,517 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Delta:methods Build Delta Order 1`] = ` -{ - "data": { - "beneficiary": "0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9", - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "deadline": NaN, - "destAmount": "3147447403157656698880", - "destToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "expectedAmount": "3163263721766488892666", - "kind": 0, - "metadata": "0x", - "nonce": "dynamic_number", - "owner": "0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9", - "partnerAndFee": "512", - "permit": "0x", - "srcAmount": "1000000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "domain": { - "chainId": 1, - "name": "Portikus", - "verifyingContract": "0x0000000000bbf5c5fd284e657f01bd000933c96d", - "version": "2.0.0", - }, - "types": { - "Bridge": [ - { - "name": "protocolSelector", - "type": "bytes4", - }, - { - "name": "destinationChainId", - "type": "uint256", - }, - { - "name": "outputToken", - "type": "address", - }, - { - "name": "scalingFactor", - "type": "int8", - }, - { - "name": "protocolData", - "type": "bytes", - }, - ], - "Order": [ - { - "name": "owner", - "type": "address", - }, - { - "name": "beneficiary", - "type": "address", - }, - { - "name": "srcToken", - "type": "address", - }, - { - "name": "destToken", - "type": "address", - }, - { - "name": "srcAmount", - "type": "uint256", - }, - { - "name": "destAmount", - "type": "uint256", - }, - { - "name": "expectedAmount", - "type": "uint256", - }, - { - "name": "deadline", - "type": "uint256", - }, - { - "name": "kind", - "type": "uint8", - }, - { - "name": "nonce", - "type": "uint256", - }, - { - "name": "partnerAndFee", - "type": "uint256", - }, - { - "name": "permit", - "type": "bytes", - }, - { - "name": "metadata", - "type": "bytes", - }, - { - "name": "bridge", - "type": "Bridge", - }, - ], - }, -} -`; - -exports[`Delta:methods Get Delta Order by Id and Hash 1`] = ` -{ - "bridgeMetadata": null, - "bridgeStatus": null, - "chainId": 1, - "createdAt": "2025-04-18T14:04:43.806Z", - "deltaVersion": "2.0", - "excludeAgents": null, - "expiresAt": "2025-04-18T15:03:33.000Z", - "id": "7ec0dc82-98ad-4501-9f46-03e31e51098f", - "includeAgents": null, - "onChainOrderType": "Order", - "order": { - "beneficiary": "0x0ddc793680ff4f5793849c8c6992be1695cbe72a", - "bridge": { - "destinationChainId": 0, - "maxRelayerFee": "0", - "multiCallHandler": "0x0000000000000000000000000000000000000000", - "outputToken": "0x0000000000000000000000000000000000000000", - }, - "deadline": 1744988613, - "destAmount": "15614735", - "destToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "expectedAmount": "15693202", - "expectedDestAmount": "15693202", - "nonce": "1744985033868", - "owner": "0x0ddc793680ff4f5793849c8c6992be1695cbe72a", - "partnerAndFee": "0", - "permit": "0x0000000000000000000000000000000000000000000000000000000000000011b7130d8420811c9f39ed0393c106255bae4619e2ad0d41ca8bbf81a91b257a89dd67bca7d8a48faba04f51dd84f4e587865e7f5c9c92858663bfb4eb809e4c26", - "srcAmount": "10000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "orderHash": "0xce14e79ba90ba85c52f3e7308d00dd8978827d8d3c2249252222cc324121d02a", - "partiallyFillable": false, - "partner": "paraswap.io-local", - "referrerAddress": null, - "status": "EXECUTED", - "transactions": [ - { - "agent": "laita", - "bidId": "787bdca0-bd2e-4cfb-ad6c-9a8a84d90a76", - "blobGasPrice": 0, - "blobGasUsed": 0, - "blockHash": "0x9b66830ee3ae02a604309099227303bc3e73a672e58cffd0685a8c3f7152ff51", - "blockNumber": 22296365, - "blockTimestamp": null, - "bridgeMetadata": null, - "bridgeOverride": null, - "bridgeProtocol": null, - "bridgeStatus": null, - "filledPercent": 10000, - "from": "0x2e5ef37ade8afb712b8be858fec7389fe32857e2", - "gasPrice": 471068591, - "gasUsed": 313190, - "hash": "0x1f955f47482a8deeb5763613448a7d737ca3b3583dc9ab0c8665329ad93a2f32", - "id": "483394ee-1f2d-47f4-bc3d-e050b6645495", - "index": 73, - "orderId": "7ec0dc82-98ad-4501-9f46-03e31e51098f", - "partnerFee": "0", - "protocolFee": "26193", - "receivedAmount": "15719395", - "receivedAmountUSD": 15.72, - "spentAmount": "10000000000000000", - "spentAmountUSD": 15.9, - "status": 1, - "to": "0x0000000000bbf5c5fd284e657f01bd000933c96d", - }, - ], - "type": "MARKET", - "updatedAt": "2025-04-18T14:05:13.519Z", - "user": "0x0ddc793680ff4f5793849c8c6992be1695cbe72a", -} -`; - -exports[`Delta:methods Get Delta Orders for user 1`] = ` -[ - { - "bridgeMetadata": null, - "bridgeStatus": null, - "chainId": 1, - "createdAt": "2024-10-10T16:18:04.727Z", - "deltaVersion": "1.0", - "excludeAgents": null, - "expiresAt": "2024-10-10T17:17:47.000Z", - "id": "8515cce6-c7c6-486b-9f1e-5702f204edd6", - "includeAgents": null, - "onChainOrderType": "Order", - "order": { - "beneficiary": "0x76176c2971300217e9f48e3dd4e40591500b96ff", - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "deadline": 1728580667, - "destAmount": "11302885800000000000", - "destToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "expectedAmount": "11302885800000000000", - "nonce": 1728577074265, - "owner": "0x76176c2971300217e9f48e3dd4e40591500b96ff", - "permit": "0x00000000000000000000000076176c2971300217e9f48e3dd4e40591500b96ff00000000000000000000000036ff475499e928590659d5b8aa3a34330a583fd900000000000000000000000000000000000000000000000000000000019d278900000000000000000000000000000000000000000000000000000000670bf2ae000000000000000000000000000000000000000000000000000000000000001bf548be9f97f37f0b2ab285bba67c8fdab99c3a08b0fdc0a910267988485535945df93e27958867f1d479c7b7783e98ba586629407e44f8d5c5d4115a7298dca9", - "srcAmount": "27076489", - "srcToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - }, - "orderHash": "0x5cd9f334b7b72943588af160e3fa8a46c894f89a3ce38967079713bf38af8128", - "partiallyFillable": false, - "partner": "delta-paraswap.io-local", - "referrerAddress": null, - "status": "EXECUTED", - "transactions": [ - { - "agent": "laita", - "bidId": null, - "blobGasPrice": 0, - "blobGasUsed": 0, - "blockHash": "0x21425d0c3625d5e55061ba2fd8ce91621dbee52c7d2846b76dd897d3438a6893", - "blockNumber": 20936359, - "blockTimestamp": null, - "bridgeMetadata": null, - "bridgeOverride": null, - "bridgeProtocol": null, - "bridgeStatus": null, - "filledPercent": 10000, - "from": "0x2e5eF37Ade8afb712B8Be858fEc7389Fe32857e2", - "gasPrice": 23525770029, - "gasUsed": 597540, - "hash": "0x3e5040d187288848ca57e4423d60fd31922b6db7fe636580d96200b03a8a8d8f", - "id": "c729ff7b-ffc8-4c85-88d8-41f4f26668eb", - "index": 57, - "orderId": "8515cce6-c7c6-486b-9f1e-5702f204edd6", - "partnerFee": "0", - "protocolFee": "0", - "receivedAmount": "17225367867506356154", - "receivedAmountUSD": 17.22, - "spentAmount": "27076489", - "spentAmountUSD": 27.06, - "status": 1, - "to": "0x1D7405DF25FD2fe80390DA3A696dcFd5120cA9Ce", - }, - ], - "type": "MARKET", - "updatedAt": "2024-10-10T16:18:51.447Z", - "user": "0x76176c2971300217e9f48e3dd4e40591500b96ff", - }, - { - "bridgeMetadata": null, - "bridgeStatus": null, - "chainId": 1, - "createdAt": "2024-10-09T16:52:18.826Z", - "deltaVersion": "1.0", - "excludeAgents": null, - "expiresAt": "2024-10-09T17:52:08.000Z", - "id": "7696f983-4f0d-4bb0-b591-61957abf74de", - "includeAgents": null, - "onChainOrderType": "Order", - "order": { - "beneficiary": "0x76176c2971300217e9f48e3dd4e40591500b96ff", - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "deadline": 1728496328, - "destAmount": "736681085000000000000", - "destToken": "0xcafe001067cdef266afb7eb5a286dcfd277f3de5", - "expectedAmount": "736681085000000000000", - "nonce": 1728492729603, - "owner": "0x76176c2971300217e9f48e3dd4e40591500b96ff", - "permit": "0x", - "srcAmount": "21000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - "orderHash": "0x07b46a39e260d90f1be2575b1fffb7c905eacd0f9e36ad2f3a45f14b921b8a60", - "partiallyFillable": false, - "partner": "delta-paraswap.io-local", - "referrerAddress": null, - "status": "EXECUTED", - "transactions": [ - { - "agent": "laita", - "bidId": null, - "blobGasPrice": 0, - "blobGasUsed": 0, - "blockHash": "0x58dbd72e843c1224e8113694b8b4a37e29657f0d52634c0f3f89835a3c7a7187", - "blockNumber": 20929352, - "blockTimestamp": null, - "bridgeMetadata": null, - "bridgeOverride": null, - "bridgeProtocol": null, - "bridgeStatus": null, - "filledPercent": 10000, - "from": "0x2e5eF37Ade8afb712B8Be858fEc7389Fe32857e2", - "gasPrice": 47757958240, - "gasUsed": 224182, - "hash": "0x6d5fa34f4723283dc32496acb8b8faf4bb9e713e3a9d43152ebef7d842c59700", - "id": "0531cfdf-0732-4757-8a46-3886487eeedd", - "index": 188, - "orderId": "7696f983-4f0d-4bb0-b591-61957abf74de", - "partnerFee": "0", - "protocolFee": "0", - "receivedAmount": "1635237633557152096036", - "receivedAmountUSD": 22.77, - "spentAmount": "21000000000000000", - "spentAmountUSD": 51.08, - "status": 1, - "to": "0x1D7405DF25FD2fe80390DA3A696dcFd5120cA9Ce", - }, - ], - "type": "MARKET", - "updatedAt": "2024-10-09T16:52:37.585Z", - "user": "0x76176c2971300217e9f48e3dd4e40591500b96ff", - }, -] -`; - -exports[`Delta:methods Get Delta Price 1`] = ` -{ - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestAmountBeforeFee": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "receivedDestUSDBeforeFee": "dynamic_number", - "srcAmount": "1000000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "srcUSD": "dynamic_number", -} -`; - -exports[`Delta:methods Get Delta Price Crosschain Get Delta Price Crosschain/destToken=ETH 1`] = ` -{ - "availableBridges": [], - "bridge": { - "destinationChainId": 10, - "outputToken": "dynamic_hash", - "protocolData": "dynamic_string", - "protocolSelector": "dynamic_string", - "scalingFactor": NaN, - }, - "bridgeInfo": { - "bestReturn": true, - "destAmountAfterBridge": "dynamic_number", - "destUSDAfterBridge": "dynamic_number", - "estimatedTimeMs": NaN, - "fastest": true, - "fees": [], - "protocolName": "dynamic_string", - "recommended": true, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestAmountBeforeFee": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "receivedDestUSDBeforeFee": "dynamic_number", - "srcAmount": "1000000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "srcUSD": "dynamic_number", -} -`; - -exports[`Delta:methods Get Delta Price Crosschain Get Delta Price Crosschain/destToken=WETH 1`] = ` -{ - "availableBridges": [], - "bridge": { - "destinationChainId": 10, - "outputToken": "0x4200000000000000000000000000000000000006", - "protocolData": "dynamic_string", - "protocolSelector": "dynamic_string", - "scalingFactor": NaN, - }, - "bridgeInfo": { - "bestReturn": true, - "destAmountAfterBridge": "dynamic_number", - "destUSDAfterBridge": "dynamic_number", - "estimatedTimeMs": NaN, - "fastest": true, - "fees": [], - "protocolName": "dynamic_string", - "recommended": true, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestAmountBeforeFee": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "receivedDestUSDBeforeFee": "dynamic_number", - "srcAmount": "1000000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "srcUSD": "dynamic_number", -} -`; - -exports[`Delta:methods Get Delta Price Crosschain Get Delta Price Crosschain/destToken=random 1`] = ` -{ - "availableBridges": [], - "bridge": { - "destinationChainId": 10, - "outputToken": "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1", - "protocolData": "dynamic_string", - "protocolSelector": "dynamic_string", - "scalingFactor": NaN, - }, - "bridgeInfo": { - "bestReturn": true, - "destAmountAfterBridge": "dynamic_number", - "destUSDAfterBridge": "dynamic_number", - "estimatedTimeMs": NaN, - "fastest": true, - "fees": [], - "protocolName": "dynamic_string", - "recommended": true, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "dynamic_address", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestAmountBeforeFee": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "receivedDestUSDBeforeFee": "dynamic_number", - "srcAmount": "1000000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "srcUSD": "dynamic_number", -} -`; - -exports[`Delta:methods Submit(=build+sign+post) Delta Order 1`] = ` -{ - "beneficiary": "0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9", - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "deadline": NaN, - "destAmount": "3147447403157656698880", - "destToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "expectedAmount": "3163263721766488892666", - "kind": 0, - "metadata": "0x", - "nonce": "dynamic_number", - "owner": "0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9", - "partnerAndFee": "512", - "permit": "0x", - "srcAmount": "1000000000000000000", - "srcToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", -} -`; diff --git a/tests/delta.test.ts b/tests/delta.test.ts index bcd71e71b..7c7c792fc 100644 --- a/tests/delta.test.ts +++ b/tests/delta.test.ts @@ -1,1132 +1,1022 @@ import * as dotenv from 'dotenv'; -import Web3 from 'web3'; -import { ethers } from 'ethersV5'; -import { ethers as ethersV6 } from 'ethers'; import fetch from 'isomorphic-unfetch'; import { - constructEthersV5ContractCaller, - constructEthersV6ContractCaller, - constructFetchFetcher, constructPartialSDK, - constructWeb3ContractCaller, + constructFetchFetcher, constructGetDeltaContract, + constructGetPartnerFee, + constructAllDeltaOrdersHandlers, + constructBuildDeltaOrder, + constructBuildExternalDeltaOrder, + constructBuildTWAPDeltaOrder, + constructCancelDeltaOrder, + constructGetAgentsList, + constructGetBridgeRoutes, constructGetDeltaOrders, constructGetDeltaPrice, - constructBuildDeltaOrder, - constructApproveTokenForDelta, - constructSignDeltaOrder, - constructViemContractCaller, - constructGetPartnerFee, - SignableDeltaOrderData, - DeltaPrice, + constructIsTokenSupportedInDelta, constructPostDeltaOrder, + constructPostExternalDeltaOrder, + constructPostTWAPDeltaOrder, constructSubmitDeltaOrder, - PostDeltaOrderParams, + PaginatedResponse, FetcherFunction, - constructCancelDeltaOrder, - constructPreSignDeltaOrder, - GetDeltaContractFunctions, - constructGetBridgeInfo, - BridgeInfo, } from '../src'; -import BigNumber from 'bignumber.js'; +import type { ContractCallerFunctions, TxHash } from '../src/types'; +import type { + DeltaAuction, + DeltaPrice, + DeltaRoute, + BuiltDeltaOrder, + BridgeRoute, +} from '../src/methods/delta/types'; -import erc20abi from './abi/ERC20.json'; +dotenv.config(); -import { assert } from 'ts-essentials'; -import { HardhatProvider } from './helpers/hardhat'; -import { privateKeyToAccount } from 'viem/accounts'; -import { - Address, - createWalletClient, - custom, - Hash, - Hex, - publicActions, - verifyTypedData, -} from 'viem'; -import { hardhat } from 'viem/chains'; -import { ZERO_ADDRESS } from '../src/methods/common/orders/buildOrderData'; -import { - BridgePriceInfo, - DeltaAuctionOrder, -} from '../src/methods/delta/helpers/types'; +const PARASWAP_DELTA = '0x1111111111111111111111111111111111111111'; +const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; +const DAI = '0x6b175474e89094c44da98b954eedeac495271d0f'; +const USDC_ARB = '0xaf88d065e77c8cc2239327c5edb3a432268e5831'; +const OWNER = '0xac39b311dceb2a4b2f5d8461c1cdaf756f4f7ae9'; +const API_URL = 'https://api.test.invalid'; +const FAKE_SIGNATURE = '0x' + 'ab'.repeat(64); + +type FetchSpy = jest.Mock, Parameters>; + +function buildPriceFixture( + overrides: Partial = {} +): DeltaPrice { + const srcInput = { + token: { chainId: 1, address: WETH }, + amount: '1000000000000000000', + amountUSD: '3000', + }; + const destOutput = { + token: { chainId: 1, address: DAI }, + amount: '2950000000000000000000', + amountUSD: '2950', + }; -dotenv.config(); + const route: DeltaRoute = { + origin: { input: srcInput, output: destOutput }, + destination: { input: destOutput, output: destOutput }, + bridge: null, + fees: { + gas: { + token: { + chainId: 1, + address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + }, + amount: '500000', + amountUSD: '0.5', + }, + bridge: [], + }, + }; -jest.setTimeout(30 * 1000); + return { + id: 'price-id-1', + side: 'SELL', + inputToken: { chainId: 1, address: WETH }, + outputToken: { chainId: 1, address: DAI }, + route, + partner: { name: 'sdk-test', feePercent: 0 }, + spender: PARASWAP_DELTA, + alternatives: [], + ...overrides, + }; +} -const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; -const DAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; - -const chainId = 1; -const srcToken = WETH; -const destToken = DAI; -const srcAmount = (1 * 1e18).toString(); //The source amount multiplied by its decimals - -const TEST_MNEMONIC = - 'radar blur cabbage chef fix engine embark joy scheme fiction master release'; -//0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9 -const wallet = ethers.Wallet.fromMnemonic(TEST_MNEMONIC); -const walletV6 = ethersV6.HDNodeWallet.fromPhrase(TEST_MNEMONIC); - -const web3provider = new Web3(HardhatProvider as any); - -const ethersProvider = new ethers.providers.Web3Provider( - HardhatProvider as any -); - -const ethersV6Provider = new ethersV6.BrowserProvider(HardhatProvider); -const signerV6 = walletV6.connect(ethersV6Provider); - -const fetchFetcher = constructFetchFetcher(fetch); - -const signer = wallet.connect(ethersProvider); -const senderAddress = signer.address; - -const viemWalletClient = createWalletClient({ - // either walletClient needs to have account set at creation - // or provider must own the account (for testing can `await viemTestClient.impersonateAccount({ address: senderAddress });`) - // to be able to sign transactions - account: privateKeyToAccount(wallet.privateKey as Hex), - chain: { ...hardhat, id: chainId }, - transport: custom(HardhatProvider), -}).extend(publicActions); - -const ethersV5ContractCaller = constructEthersV5ContractCaller( - { - ethersProviderOrSigner: signer, - EthersContract: ethers.Contract, - }, - senderAddress -); - -const ethersV6ContractCaller = constructEthersV6ContractCaller( - { - ethersV6ProviderOrSigner: signerV6, - EthersV6Contract: ethersV6.Contract, - }, - senderAddress -); - -const web3ContractCaller = constructWeb3ContractCaller( - web3provider, - senderAddress -); - -const viemContractCaller = constructViemContractCaller( - viemWalletClient, - senderAddress -); - -describe('Delta:methods', () => { - const deltaSDK = constructPartialSDK( - { +function buildCrosschainRoute(): DeltaRoute { + const srcInput = { + token: { chainId: 1, address: WETH }, + amount: '1000000000000000000', + amountUSD: '3000', + }; + const srcOutputIntermediate = { + token: { chainId: 1, - fetcher: fetchFetcher, - contractCaller: ethersV5ContractCaller, - apiURL: process.env.API_URL, + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', }, - constructGetDeltaContract, - constructGetDeltaOrders, - constructGetDeltaPrice, - constructBuildDeltaOrder, - constructApproveTokenForDelta, - constructGetPartnerFee, - constructGetBridgeInfo - ); - - describe('Bridge methods', () => { - function flattenAllBridgeInfoTokens(bridgeInfo: BridgeInfo): string[] { - return Object.values(bridgeInfo).flatMap((chainMap) => - Object.values(chainMap).flat() - ); - } - test('Get Bridge Info', async () => { - const bridgeInfo = await deltaSDK.getBridgeInfo(); - expect(Object.keys(bridgeInfo)).toEqual( - // allow for more chains to be added in the future - expect.arrayContaining(['1', '10', '56', '130', '137', '8453', '42161']) - ); + amount: '2980000000', + amountUSD: '2980', + }; + const destInput = { + token: { chainId: 42161, address: USDC_ARB }, + amount: '2978000000', + amountUSD: '2978', + }; + const destOutput = { + token: { chainId: 42161, address: USDC_ARB }, + amount: '2978000000', + amountUSD: '2978', + }; - const defaultNumOfTokens = flattenAllBridgeInfoTokens(bridgeInfo).length; + return { + origin: { input: srcInput, output: srcOutputIntermediate }, + destination: { input: destInput, output: destOutput }, + bridge: { + protocol: 'Across', + tags: ['recommended', 'fastest'], + estimatedTimeMs: 90_000, + contractParams: { + protocolSelector: '0xdeadbeef', + outputToken: USDC_ARB, + scalingFactor: -12, + protocolData: '0xabcd', + }, + }, + fees: { + gas: { + token: { chainId: 1, address: WETH }, + amount: '7000000000000000', + amountUSD: '21', + }, + bridge: [ + { + token: { chainId: 42161, address: USDC_ARB }, + amount: '200000', + amountUSD: '0.2', + }, + ], + }, + }; +} - const bridgeInfoDIsallowedBridgeAndSwap = await deltaSDK.getBridgeInfo({ - allowBridgeAndSwap: false, - }); +/** Minimal BuiltDeltaOrder fixture representing a server-built order. */ +function buildBuiltOrderFixture( + value: Record = {}, + orderHash = '0xdeadbeef1234' +): BuiltDeltaOrder { + return { + toSign: { + domain: { + name: 'Portikus', + version: '2.0.0', + chainId: 1, + verifyingContract: PARASWAP_DELTA, + }, + types: { + Order: [ + { name: 'owner', type: 'address' }, + { name: 'beneficiary', type: 'address' }, + { name: 'srcToken', type: 'address' }, + { name: 'destToken', type: 'address' }, + { name: 'srcAmount', type: 'uint256' }, + { name: 'destAmount', type: 'uint256' }, + { name: 'expectedAmount', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + { name: 'kind', type: 'uint8' }, + { name: 'nonce', type: 'uint256' }, + { name: 'partnerAndFee', type: 'uint256' }, + { name: 'permit', type: 'bytes' }, + { name: 'metadata', type: 'bytes' }, + { name: 'bridge', type: 'Bridge' }, + ], + Bridge: [ + { name: 'protocolSelector', type: 'bytes4' }, + { name: 'destinationChainId', type: 'uint256' }, + { name: 'outputToken', type: 'address' }, + { name: 'scalingFactor', type: 'int8' }, + { name: 'protocolData', type: 'bytes' }, + ], + }, + value: { + owner: OWNER, + beneficiary: OWNER, + srcToken: WETH, + destToken: DAI, + srcAmount: '1000000000000000000', + destAmount: '2950000000000000000000', + expectedAmount: '2950000000000000000000', + deadline: 9999999999, + kind: 0, + nonce: '12345', + permit: '0x', + partnerAndFee: '0', + metadata: '0x', + bridge: { + protocolSelector: '0x00000000', + destinationChainId: 0, + outputToken: '0x0000000000000000000000000000000000000000', + scalingFactor: 0, + protocolData: '0x', + }, + ...value, + }, + }, + orderHash, + }; +} - expect(Object.keys(bridgeInfoDIsallowedBridgeAndSwap)).toEqual( - Object.keys(bridgeInfo) +function makeMockContractCaller(): ContractCallerFunctions { + return { + staticCall: jest.fn(async () => { + throw new Error( + 'staticCall should not be invoked in v2 fetch-only tests' ); - const disallowedNumOfTokens = flattenAllBridgeInfoTokens( - bridgeInfoDIsallowedBridgeAndSwap - ).length; + }), + transactCall: jest.fn(async () => '0xfeedface' as TxHash), + signTypedDataCall: jest.fn(async () => FAKE_SIGNATURE), + }; +} - // fewer tokens are available when bridge and swap (swap on destChain after bridge) is not allowed - expect(disallowedNumOfTokens).toBeLessThan(defaultNumOfTokens); +function makeFetcher(handler: (params: any) => any): FetchSpy { + return jest.fn(async (params) => handler(params)) as FetchSpy; +} + +describe('Delta: fetch methods', () => { + test('getDeltaPrice hits /delta/v2/prices and returns DeltaPrice', async () => { + const fixture = buildPriceFixture(); + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('GET'); + expect(url.startsWith(`${API_URL}/delta/v2/prices?`)).toBe(true); + expect(url).toContain(`srcToken=${WETH}`); + expect(url).toContain(`destToken=${DAI}`); + expect(url).toContain('amount=1000000000000000000'); + expect(url).toContain('chainId=1'); + expect(url).toContain('side=SELL'); + return fixture; }); - test('Get Bridge Protocols', async () => { - const bridgeProtocols = await deltaSDK.getBridgeProtocols(); - const expectedToInclude = [ - { - displayName: 'Across', - icon: expect.any(String), - protocol: 'Across', - }, - { - displayName: 'Relay', - icon: expect.any(String), - protocol: 'Relay', - }, - ]; - expect(bridgeProtocols).toEqual( - // allow for more bridges to be added in the future - expect.arrayContaining(expectedToInclude) - ); + const { getDeltaPrice } = constructGetDeltaPrice({ + apiURL: API_URL, + chainId: 1, + fetcher, }); - }); - test('Get Delta Price', async () => { - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: srcToken, - destToken: destToken, - amount: srcAmount, - userAddress: senderAddress, + const price = await getDeltaPrice({ + srcToken: WETH, + destToken: DAI, + amount: '1000000000000000000', srcDecimals: 18, destDecimals: 18, }); - const staticDeltaPrice: typeof deltaPrice = { - ...deltaPrice, - partnerFee: NaN, // dynamic number - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - receivedDestAmountBeforeFee: 'dynamic_number', - receivedDestUSDBeforeFee: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', - hmac: 'dynamic_string', - }; + expect(price).toEqual(fixture); + expect(price.route.origin.input.amount).toBe('1000000000000000000'); + expect(price.route.bridge).toBeNull(); + expect(fetcher).toHaveBeenCalledTimes(1); + }); - expect(staticDeltaPrice).toMatchSnapshot(); + test('getDeltaPrice passes destChainId for cross-chain', async () => { + const fixture = buildPriceFixture({ + route: buildCrosschainRoute(), + outputToken: { chainId: 42161, address: USDC_ARB }, + }); + const fetcher = makeFetcher(({ url }) => { + expect(url).toContain('destChainId=42161'); + return fixture; + }); + + const { getDeltaPrice } = constructGetDeltaPrice({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); + + const price = await getDeltaPrice({ + srcToken: WETH, + destToken: USDC_ARB, + amount: '1000000000000000000', + srcDecimals: 18, + destDecimals: 6, + destChainId: 42161, + }); + + expect(price.route.bridge?.protocol).toBe('Across'); + expect(price.route.destination.input.token.chainId).toBe(42161); }); - describe('Get Delta Price Crosschain', () => { - const destChainId = 10; - const ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; - const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; - const WETH_ON_OPTIMISM = '0x4200000000000000000000000000000000000006'; - // const DAI_TOKEN_ON_ETHEREUM = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; - const DAI_TOKEN_ON_OPTIMISM = '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1'; + test('getBridgeRoutes hits /delta/v2/prices/bridge-routes and unwraps `routes`', async () => { + const routes: BridgeRoute[] = [ + { srcChainId: 1, destChainId: 42161, tokens: [USDC_ARB] }, + { srcChainId: 1, destChainId: 10, tokens: [DAI] }, + ]; + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('GET'); + expect(url).toBe(`${API_URL}/delta/v2/prices/bridge-routes`); + return { routes }; + }); - test('Get Delta Price Crosschain/destToken=random', async () => { - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: WETH, - destToken: DAI_TOKEN_ON_OPTIMISM, - amount: srcAmount, - userAddress: senderAddress, - srcDecimals: 18, - destDecimals: 18, - destChainId, - }); - - const staticDeltaPrice: typeof deltaPrice = { - ...deltaPrice, - partnerFee: NaN, // dynamic number - destToken: 'dynamic_address', // will no longer match DAI_TOKEN_ON_ETHEREUM if bridge is Relay - bridge: { - ...deltaPrice.bridge, - protocolData: 'dynamic_string', - protocolSelector: 'dynamic_string', - scalingFactor: NaN, // dynamic number - }, - bridgeInfo: { - ...deltaPrice.bridgeInfo, - bestReturn: true, - destAmountAfterBridge: 'dynamic_number', - destUSDAfterBridge: 'dynamic_number', - estimatedTimeMs: NaN, // dynamic number - fastest: true, - fees: [], // dynamic array - protocolName: 'dynamic_string' as BridgePriceInfo['protocolName'], - recommended: true, - }, - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - receivedDestAmountBeforeFee: 'dynamic_number', - receivedDestUSDBeforeFee: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', - hmac: 'dynamic_string', - availableBridges: [], // dynamic array of bridge variants - }; - - // will no longer be true if swap is through Relay - // expect(deltaPrice.destToken).toEqual(DAI_TOKEN_ON_ETHEREUM.toLowerCase()); - expect(staticDeltaPrice).toMatchSnapshot(); - expect(deltaPrice.bridge.destinationChainId).toEqual(destChainId); - expect(deltaPrice.bridge.outputToken).toEqual( - DAI_TOKEN_ON_OPTIMISM.toLowerCase() - ); + const { getBridgeRoutes } = constructGetBridgeRoutes({ + apiURL: API_URL, + chainId: 1, + fetcher, }); - test('Get Delta Price Crosschain/destToken=WETH', async () => { - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: WETH, - destToken: WETH_ON_OPTIMISM, - amount: srcAmount, - userAddress: senderAddress, - srcDecimals: 18, - destDecimals: 18, - destChainId, - }); - - const staticDeltaPrice: typeof deltaPrice = { - ...deltaPrice, - partnerFee: NaN, // dynamic number - bridge: { - ...deltaPrice.bridge, - protocolData: 'dynamic_string', - protocolSelector: 'dynamic_string', - scalingFactor: NaN, // dynamic number - }, - bridgeInfo: { - ...deltaPrice.bridgeInfo, - bestReturn: true, - destAmountAfterBridge: 'dynamic_number', - destUSDAfterBridge: 'dynamic_number', - estimatedTimeMs: NaN, // dynamic number - fastest: true, - fees: [], // dynamic array - protocolName: 'dynamic_string' as BridgePriceInfo['protocolName'], - recommended: true, - }, - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - receivedDestAmountBeforeFee: 'dynamic_number', - receivedDestUSDBeforeFee: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', - hmac: 'dynamic_string', - availableBridges: [], // dynamic array of bridge variants - }; - - expect(staticDeltaPrice).toMatchSnapshot(); - expect(deltaPrice.bridge.destinationChainId).toEqual(destChainId); - expect(deltaPrice.bridge.outputToken).toEqual( - WETH_ON_OPTIMISM.toLowerCase() - ); + expect(await getBridgeRoutes()).toEqual(routes); + }); + + test('getBridgeRoutes passes filter params', async () => { + const fetcher = makeFetcher(({ url }) => { + expect(url).toContain('allowBridgeAndSwap=false'); + expect(url).toContain('bridges=Across%2CRelay'); + return { routes: [] }; }); - test('Get Delta Price Crosschain/destToken=ETH', async () => { - const deltaPrice = await deltaSDK.getDeltaPrice({ - srcToken: WETH, - destToken: ETH, - amount: srcAmount, - userAddress: senderAddress, - srcDecimals: 18, - destDecimals: 18, - destChainId, - }); - - const staticDeltaPrice: typeof deltaPrice = { - ...deltaPrice, - partnerFee: NaN, // dynamic number - bridge: { - ...deltaPrice.bridge, - outputToken: 'dynamic_hash', // WETH or ETH depending on bridge used - protocolData: 'dynamic_string', - protocolSelector: 'dynamic_string', - scalingFactor: NaN, // dynamic number - }, - bridgeInfo: { - ...deltaPrice.bridgeInfo, - bestReturn: true, - destAmountAfterBridge: 'dynamic_number', - destUSDAfterBridge: 'dynamic_number', - estimatedTimeMs: NaN, // dynamic number - fastest: true, - fees: [], // dynamic array - protocolName: 'dynamic_string' as BridgePriceInfo['protocolName'], - recommended: true, - }, - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - receivedDestAmountBeforeFee: 'dynamic_number', - receivedDestUSDBeforeFee: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', - hmac: 'dynamic_string', - availableBridges: [], // dynamic array of bridge variants - }; - - expect(staticDeltaPrice).toMatchSnapshot(); - expect(deltaPrice.bridge.destinationChainId).toEqual(destChainId); - // bridge.outputToken = WETH|ETH for destToken=ETH|WETH on destChain depending on bridge used; - // wrap/unwrap logic is determined by bridge.multiCallHandler presence - expect([WETH_ON_OPTIMISM.toLowerCase(), ETH.toLowerCase()]).toContain( - deltaPrice.bridge.outputToken - ); + const { getBridgeRoutes } = constructGetBridgeRoutes({ + apiURL: API_URL, + chainId: 1, + fetcher, }); + + expect( + await getBridgeRoutes({ + allowBridgeAndSwap: false, + bridges: ['Across', 'Relay'], + }) + ).toEqual([]); }); - test('Get Delta Contract', async () => { - const deltaContract = await deltaSDK.getDeltaContract(); - expect(deltaContract).toMatchInlineSnapshot( - `"0x0000000000bbf5c5fd284e657f01bd000933c96d"` - ); + test('isTokenSupportedInDelta unwraps `supported`', async () => { + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('GET'); + expect( + url.startsWith(`${API_URL}/delta/v2/prices/is-token-supported/?`) + ).toBe(true); + expect(url).toContain(`token=${WETH}`); + expect(url).toContain('chainId=1'); + return { supported: true }; + }); + + const { isTokenSupportedInDelta } = constructIsTokenSupportedInDelta({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); + + expect(await isTokenSupportedInDelta(WETH)).toBe(true); }); - test('Approve Token For Delta', async () => { - const deltaContract = await deltaSDK.getDeltaContract(); - assert(deltaContract, 'Delta contract not found'); + test('getDeltaOrders returns the pagination envelope', async () => { + const order = { id: 'auction-1' } as unknown as DeltaAuction; + const envelope: PaginatedResponse = { + data: [order], + total: 1, + page: 1, + limit: 100, + hasMore: false, + }; - const allowanceBefore = await getTokenAllowance({ - tokenAddress: DAI, - owner: senderAddress, - spender: deltaContract, + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('GET'); + expect(url.startsWith(`${API_URL}/delta/v2/orders?`)).toBe(true); + expect(url).toContain(`userAddress=${OWNER}`); + expect(url).toContain('page=2'); + expect(url).toContain('limit=10'); + return envelope; + }); + + const { getDeltaOrders } = constructGetDeltaOrders({ + apiURL: API_URL, + chainId: 1, + fetcher, }); - expect(allowanceBefore.toString()).toEqual('0'); + const result = await getDeltaOrders({ + userAddress: OWNER, + page: 2, + limit: 10, + }); - const amount = '1000000000000000000'; // 1 DAI - const tx = await deltaSDK.approveTokenForDelta(amount, DAI); - expect(tx).toBeDefined(); - await tx.wait(); + expect(result).toEqual(envelope); + expect(result.hasMore).toBe(false); + expect(result.data).toHaveLength(1); + }); - const allowanceAfter = await getTokenAllowance({ - tokenAddress: DAI, - owner: senderAddress, - spender: deltaContract, + test('getDeltaOrders by id / by hash use the v2 path', async () => { + const order = { id: 'auction-1' } as unknown as DeltaAuction; + const fetcher = makeFetcher(({ url }) => { + if (url === `${API_URL}/delta/v2/orders/auction-1`) return order; + if (url === `${API_URL}/delta/v2/orders/hash/0xhash`) return order; + throw new Error(`unexpected URL ${url}`); }); - expect(allowanceAfter.toString()).toEqual(amount); + const { getDeltaOrderById, getDeltaOrderByHash } = + constructGetDeltaOrders({ apiURL: API_URL, chainId: 1, fetcher }); + + expect(await getDeltaOrderById('auction-1')).toBe(order); + expect(await getDeltaOrderByHash('0xhash')).toBe(order); }); - test('Get Delta Orders for user', async () => { - const userWithOrders = '0x76176C2971300217E9f48E3dD4e40591500b96Ff'; + test('getRequiredBalanceForDeltaOrders hits /delta/v2/orders/fillablebalance/:chainId/:userAddress', async () => { + const required = { + [WETH]: '500000000000000000', + [DAI]: '1500000000000000000000', + }; + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('GET'); + expect(url).toBe( + `${API_URL}/delta/v2/orders/fillablebalance/1/${OWNER}` + ); + return required; + }); - const deltaOrders = await deltaSDK.getDeltaOrders({ - userAddress: userWithOrders, + const { getRequiredBalanceForDeltaOrders } = + constructGetDeltaOrders({ apiURL: API_URL, chainId: 1, fetcher }); + + expect( + await getRequiredBalanceForDeltaOrders({ userAddress: OWNER }) + ).toEqual(required); + }); + + test('getRequiredBalanceForDeltaOrders narrows the URL when tokenAddress is passed', async () => { + const fetcher = makeFetcher(({ url }) => { + expect(url).toBe( + `${API_URL}/delta/v2/orders/fillablebalance/1/${OWNER}/${WETH}` + ); + return { [WETH]: '500000000000000000' }; }); - // Orders that we know the user had in the past - const staticSliceOfPastOrders = deltaOrders.slice(-2); // first 2 orders historically - expect(staticSliceOfPastOrders).toMatchSnapshot(); + const { getRequiredBalanceForDeltaOrders } = + constructGetDeltaOrders({ apiURL: API_URL, chainId: 1, fetcher }); + + const balance = await getRequiredBalanceForDeltaOrders({ + userAddress: OWNER, + tokenAddress: WETH, + }); + expect(balance[WETH]).toBe('500000000000000000'); }); - test('Get Delta Order by Id and Hash', async () => { - const orderId = '7ec0dc82-98ad-4501-9f46-03e31e51098f'; - const deltaOrder = await deltaSDK.getDeltaOrderById(orderId); - expect(deltaOrder).toMatchSnapshot(); - expect(deltaOrder).toBeDefined(); - assert( - deltaOrder?.orderHash, - "Delta order not found or doesn't have orderHash" - ); + test('getAgentsList hits /delta/v2/agents/list/:chainId and returns agent names', async () => { + const agents = ['agent-a', 'agent-b']; + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('GET'); + expect(url).toBe(`${API_URL}/delta/v2/agents/list/42161`); + return agents; // server returns string[] directly + }); - const orderByHash = await deltaSDK.getDeltaOrderByHash( - deltaOrder.orderHash - ); - expect(orderByHash).toEqual(deltaOrder); + const { getAgentsList } = constructGetAgentsList({ + apiURL: API_URL, + chainId: 42161, + fetcher, + }); + + expect(await getAgentsList()).toEqual(agents); }); +}); - test('Get PartnerFee', async () => { - const partnerFee = await deltaSDK.getPartnerFee({ partner: 'paraswap.io' }); - expect(partnerFee).toMatchInlineSnapshot(` - { - "partnerAddress": "0xc85f5d432b7fa25287c7e0cb88139a1a4c37f565", - "partnerFee": 0.15, - "takeSurplus": false, - } - `); +describe('Delta: build (server-side via POST /v2/orders/build)', () => { + test('buildDeltaOrder POSTs to /delta/v2/orders/build with correct body', async () => { + const builtFixture = buildBuiltOrderFixture(); + let postedBody: any; + + const fetcher = makeFetcher(({ url, method, data }) => { + expect(method).toBe('POST'); + expect(url).toBe(`${API_URL}/delta/v2/orders/build`); + postedBody = data; + return builtFixture; + }); + + const { buildDeltaOrder } = constructBuildDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); + + const route = buildPriceFixture().route; + const result = await buildDeltaOrder({ + owner: OWNER, + route, + side: 'SELL', + partnerAddress: '0x0000000000000000000000000000000000000000', + }); + + expect(result).toEqual(builtFixture); + // build endpoint derives the chain from the route — no chainId in the body + expect(postedBody.chainId).toBeUndefined(); + expect(postedBody.side).toBe('SELL'); + expect(postedBody.owner).toBe(OWNER); + expect(postedBody.route).toBe(route); + expect(postedBody.slippage).toBeUndefined(); + expect(postedBody.orderType).toBe('Order'); }); - test('Build Delta Order', async () => { - const sampleDeltaPrice: DeltaPrice = { - destAmount: '3163263721766488892666', - destAmountBeforeFee: '3194635547945152526200', - receivedDestAmount: '3163263721766488892666', - destToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destUSD: '3166.4269854931', - receivedDestUSD: '3166.4269854931', - destUSDBeforeFee: '3197.8301834931', - gasCost: '347788', - gasCostBeforeFee: '124240', - gasCostUSD: '31.403198', - gasCostUSDBeforeFee: '11.218137', - partner: 'anon', - partnerFee: 0, - srcAmount: '1000000000000000000', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - srcUSD: '3191.5500000000', - hmac: '1234aeb', - bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', - }, - }; + test('buildDeltaOrder passes slippage to server for SELL', async () => { + const builtFixture = buildBuiltOrderFixture(); + let postedBody: any; - const slippagePercent = 0.5; - const destAmountAfterSlippage = decreaseBySlippage( - sampleDeltaPrice.destAmount, - slippagePercent - ); + const fetcher = makeFetcher(({ url, data }) => { + if (url === `${API_URL}/delta/v2/orders/build`) { + postedBody = data; + return builtFixture; + } + throw new Error(`unexpected ${url}`); + }); - const amount = '1000000000000000000'; // 1 DAI + const { buildDeltaOrder } = constructBuildDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); - const signableOrderData = await deltaSDK.buildDeltaOrder({ - deltaPrice: sampleDeltaPrice, - owner: senderAddress, - // beneficiary: anotherAccount, // if need to send destToken to another account - // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: WETH, - destToken: DAI, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount - partnerAddress: ZERO_ADDRESS, + await buildDeltaOrder({ + owner: OWNER, + route: buildPriceFixture().route, + side: 'SELL', + slippage: 100, + partnerAddress: '0x0000000000000000000000000000000000000000', }); - const staticSignableOrderData: typeof signableOrderData = { - ...signableOrderData, - data: { - ...signableOrderData.data, - deadline: NaN, // dynamic number - nonce: 'dynamic_number', - }, - }; - // for ZERO partnerAddress capSurplus (true) shifted (<< 9) = 512 - expect(signableOrderData.data.partnerAndFee).toEqual((1 << 9).toString()); - expect(staticSignableOrderData).toMatchSnapshot(); + // slippage is forwarded to server; server applies it to destAmount + expect(postedBody.slippage).toBe(100); + expect(postedBody.side).toBe('SELL'); }); - test('Build Delta Order with slippage (SELL)', async () => { - const sampleDeltaPrice: DeltaPrice = { - destAmount: '3163263721766488892666', - destAmountBeforeFee: '3194635547945152526200', - receivedDestAmount: '3163263721766488892666', - destToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destUSD: '3166.4269854931', - receivedDestUSD: '3166.4269854931', - destUSDBeforeFee: '3197.8301834931', - gasCost: '347788', - gasCostBeforeFee: '124240', - gasCostUSD: '31.403198', - gasCostUSDBeforeFee: '11.218137', - partner: 'anon', - partnerFee: 0, - srcAmount: '1000000000000000000', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - srcUSD: '3191.5500000000', - hmac: '1234aeb', - bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', - }, - }; + test('buildDeltaOrder passes slippage to server for BUY', async () => { + const builtFixture = buildBuiltOrderFixture(); + let postedBody: any; - const slippageBps = 50; // 50 bps = 0.5% - const BPS_BASE = BigInt(10_000); - const expectedDestAmount = ( - (BigInt(sampleDeltaPrice.destAmount) * (BPS_BASE - BigInt(slippageBps))) / - BPS_BASE - ).toString(10); + const fetcher = makeFetcher(({ url, data }) => { + if (url === `${API_URL}/delta/v2/orders/build`) { + postedBody = data; + return builtFixture; + } + throw new Error(`unexpected ${url}`); + }); - const amount = '1000000000000000000'; + const { buildDeltaOrder } = constructBuildDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); - const signableOrderData = await deltaSDK.buildDeltaOrder({ - deltaPrice: sampleDeltaPrice, - owner: senderAddress, - srcToken: WETH, - destToken: DAI, - srcAmount: amount, - slippage: slippageBps, - partnerAddress: ZERO_ADDRESS, + await buildDeltaOrder({ + owner: OWNER, + route: buildPriceFixture().route, + side: 'BUY', + slippage: 100, + partnerAddress: '0x0000000000000000000000000000000000000000', }); - expect(signableOrderData.data.srcAmount).toEqual(amount); - expect(signableOrderData.data.destAmount).toEqual(expectedDestAmount); + expect(postedBody.slippage).toBe(100); + expect(postedBody.side).toBe('BUY'); }); - test('Build Delta Order with slippage (BUY)', async () => { - const sampleDeltaPrice: DeltaPrice = { - destAmount: '3163263721766488892666', - destAmountBeforeFee: '3194635547945152526200', - receivedDestAmount: '3163263721766488892666', - destToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destUSD: '3166.4269854931', - receivedDestUSD: '3166.4269854931', - destUSDBeforeFee: '3197.8301834931', - gasCost: '347788', - gasCostBeforeFee: '124240', - gasCostUSD: '31.403198', - gasCostUSDBeforeFee: '11.218137', - partner: 'anon', - partnerFee: 0, - srcAmount: '1000000000000000000', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - srcUSD: '3191.5500000000', - hmac: '1234aeb', + test('buildDeltaOrder passes cross-chain route as-is; server injects destinationChainId', async () => { + const ccRoute = buildCrosschainRoute(); + const builtFixture = buildBuiltOrderFixture({ + srcToken: WETH, + destToken: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', + protocolSelector: '0xdeadbeef', + destinationChainId: 42161, + outputToken: USDC_ARB, + scalingFactor: -12, + protocolData: '0xabcd', }, - }; + }); + let postedBody: any; - const slippageBps = 50; // 50 bps = 0.5% - const destAmount = '3163263721766488892666'; + const fetcher = makeFetcher(({ url, data }) => { + if (url === `${API_URL}/delta/v2/orders/build`) { + postedBody = data; + return builtFixture; + } + throw new Error(`unexpected ${url}`); + }); - const BPS_BASE = BigInt(10_000); - const expectedSrcAmount = ( - (BigInt(sampleDeltaPrice.srcAmount) * (BPS_BASE + BigInt(slippageBps))) / - BPS_BASE - ).toString(10); + const { buildDeltaOrder } = constructBuildDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); - const signableOrderData = await deltaSDK.buildDeltaOrder({ - deltaPrice: sampleDeltaPrice, - owner: senderAddress, - srcToken: WETH, - destToken: DAI, - destAmount, - slippage: slippageBps, - side: 'BUY', - partnerAddress: ZERO_ADDRESS, + const result = await buildDeltaOrder({ + owner: OWNER, + route: ccRoute, + side: 'SELL', + partnerAddress: '0x0000000000000000000000000000000000000000', }); - expect(signableOrderData.data.destAmount).toEqual(destAmount); - expect(signableOrderData.data.srcAmount).toEqual(expectedSrcAmount); + // route is forwarded as-is; bridge in route.contractParams has no destinationChainId + expect(postedBody.route).toBe(ccRoute); + // the server (fixture) fills in destinationChainId + expect(result.toSign.value.bridge).toMatchObject({ + protocolSelector: '0xdeadbeef', + destinationChainId: 42161, + }); }); - let signature = ''; + test('buildExternalDeltaOrder sends orderType ExternalOrder with handler/data', async () => { + const builtFixture = buildBuiltOrderFixture(); + let postedBody: any; - test.each([ - ['ethersV5', ethersV5ContractCaller], - ['ethersV6', ethersV6ContractCaller], - ['web3', web3ContractCaller], - ['viem', viemContractCaller], - ])('sign Delta Order with %s', async (_libName, contractCaller) => { - const sdk = constructPartialSDK( - { - chainId: 1, - fetcher: fetchFetcher, - contractCaller, - apiURL: process.env.API_URL, - }, - constructSignDeltaOrder + const fetcher = makeFetcher(({ url, data }) => { + if (url === `${API_URL}/delta/v2/orders/build`) { + postedBody = data; + return builtFixture; + } + throw new Error(`unexpected ${url}`); + }); + + const { buildExternalDeltaOrder } = constructBuildExternalDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); + + const route = buildPriceFixture().route; + await buildExternalDeltaOrder({ + owner: OWNER, + handler: '0x2222222222222222222222222222222222222222', + data: '0xdeadbeef', + route, + side: 'SELL', + partnerAddress: '0x0000000000000000000000000000000000000000', + }); + + expect(postedBody.orderType).toBe('ExternalOrder'); + expect(postedBody.handler).toBe( + '0x2222222222222222222222222222222222222222' ); + expect(postedBody.data).toBe('0xdeadbeef'); + expect(postedBody.route).toBe(route); + }); - const sampleOrder: SignableDeltaOrderData = { - data: { - beneficiary: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9', - deadline: 1731328853, - destAmount: '3147447403157656698880', - destToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', - expectedAmount: '3163263721766488892666', - nonce: '1731325253703', - owner: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9', - partnerAndFee: '0', - permit: '0x', - srcAmount: '1000000000000000000', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - kind: 0, - metadata: '0x', - bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', - }, - }, - domain: { - chainId: 1, - name: 'Portikus', - verifyingContract: '0x0000000000bbf5c5fd284e657f01bd000933c96d', - version: '2.0.0', - }, - types: { - Order: [ - { - name: 'owner', - type: 'address', - }, - { - name: 'beneficiary', - type: 'address', - }, - { - name: 'srcToken', - type: 'address', - }, - { - name: 'destToken', - type: 'address', - }, - { - name: 'srcAmount', - type: 'uint256', - }, - { - name: 'destAmount', - type: 'uint256', - }, - { - name: 'expectedAmount', - type: 'uint256', - }, - { - name: 'deadline', - type: 'uint256', - }, - { - name: 'kind', - type: 'uint8', - }, - { - name: 'nonce', - type: 'uint256', - }, - { - name: 'partnerAndFee', - type: 'uint256', - }, - { - name: 'permit', - type: 'bytes', - }, - { - name: 'metadata', - type: 'bytes', - }, - { - name: 'bridge', - type: 'Bridge', - }, - ], - Bridge: [ - { - name: 'protocolSelector', - type: 'bytes4', - }, - { - name: 'destinationChainId', - type: 'uint256', - }, - { - name: 'outputToken', - type: 'address', - }, - { - name: 'scalingFactor', - type: 'int8', - }, - { - name: 'protocolData', - type: 'bytes', - }, - ], - }, - }; + test('buildTWAPDeltaOrder (sell) sends TWAPOrder body; slippage forwarded to server', async () => { + const builtFixture = buildBuiltOrderFixture(); + let postedBody: any; + + const fetcher = makeFetcher(({ url, data }) => { + if (url === `${API_URL}/delta/v2/orders/build`) { + postedBody = data; + return builtFixture; + } + throw new Error(`unexpected ${url}`); + }); + + const { buildTWAPDeltaOrder } = constructBuildTWAPDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); - const deltaOrderSignature = await sdk.signDeltaOrder(sampleOrder); - if (!signature) signature = deltaOrderSignature; - // signatures match between libraries - expect(deltaOrderSignature).toEqual(signature); + const route = buildPriceFixture().route; + await buildTWAPDeltaOrder({ + owner: OWNER, + onChainOrderType: 'TWAPOrder', + route, + totalSrcAmount: '5000000000000000000', + interval: 300, + numSlices: 5, + slippage: 50, + partnerAddress: '0x0000000000000000000000000000000000000000', + }); + + expect(postedBody.orderType).toBe('TWAPOrder'); + expect(postedBody.interval).toBe(300); + expect(postedBody.numSlices).toBe(5); + expect(postedBody.totalSrcAmount).toBe('5000000000000000000'); + expect(postedBody.slippage).toBe(50); // server applies slippage to destAmountPerSlice + expect(postedBody.side).toBe('SELL'); }); - let cancelSignature = ''; + test('buildTWAPDeltaOrder (buy) forwards slippage and maxSrcAmount to server', async () => { + const builtFixture = buildBuiltOrderFixture(); + let postedBody: any; + + const fetcher = makeFetcher(({ url, data }) => { + if (url === `${API_URL}/delta/v2/orders/build`) { + postedBody = data; + return builtFixture; + } + throw new Error(`unexpected ${url}`); + }); - const sampleOrderId = '7ec0dc82-98ad-4501-9f46-03e31e51098f'; + const { buildTWAPDeltaOrder } = constructBuildTWAPDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); - test.each([ - ['ethersV5', ethersV5ContractCaller], - ['ethersV6', ethersV6ContractCaller], - ['web3', web3ContractCaller], - ['viem', viemContractCaller], - ])( - 'sign Cancel Delta Order Request with %s', - async (_libName, contractCaller) => { - const sdk = constructPartialSDK( - { - chainId: 1, - fetcher: fetchFetcher, - contractCaller, - apiURL: process.env.API_URL, - }, - constructCancelDeltaOrder - ); + const route = buildPriceFixture().route; + await buildTWAPDeltaOrder({ + owner: OWNER, + onChainOrderType: 'TWAPBuyOrder', + route, + totalDestAmount: '5000000000000000000', + maxSrcAmount: '1000000000000000000', + interval: 300, + numSlices: 5, + slippage: 100, + partnerAddress: '0x0000000000000000000000000000000000000000', + }); - const deltaCancelSignature = await sdk.signCancelLimitDeltaOrderRequest({ - orderIds: [sampleOrderId], - }); + expect(postedBody.orderType).toBe('TWAPBuyOrder'); + expect(postedBody.side).toBe('BUY'); + expect(postedBody.maxSrcAmount).toBe('1000000000000000000'); + expect(postedBody.slippage).toBe(100); + }); +}); - const valid = await verifySignedCancelRequest({ - orderId: sampleOrderId, - signature: deltaCancelSignature, - address: senderAddress, - chainId: sdk.chainId, - }); +describe('Delta: submit (build → sign → post)', () => { + test('submitDeltaOrder posts to /delta/v2/orders with signed order', async () => { + const builtFixture = buildBuiltOrderFixture(); + let posted: any; + let postUrl = ''; - expect(valid).toBe(true); + const fetcher = makeFetcher(({ url, method, data }) => { + if (method === 'POST' && url === `${API_URL}/delta/v2/orders/build`) { + return builtFixture; + } + if (method === 'POST' && url.startsWith(`${API_URL}/delta/v2/orders`)) { + postUrl = url; + posted = data; + return { + id: 'auction-99', + order: data.order, + onChainOrderType: 'Order', + } as unknown as DeltaAuction<'Order'>; + } + throw new Error(`unexpected request ${method} ${url}`); + }); - if (!cancelSignature) cancelSignature = deltaCancelSignature; - // signatures match between libraries - expect(deltaCancelSignature).toEqual(cancelSignature); - } - ); + const { submitDeltaOrder } = constructSubmitDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + contractCaller: makeMockContractCaller(), + }); - const dummyFetcher: FetcherFunction = (params) => { - // intercept POST requests - if (params.method === 'POST') { - return params as any; - } + const response = await submitDeltaOrder({ + owner: OWNER, + route: buildPriceFixture().route, + side: 'SELL', + partnerAddress: '0x0000000000000000000000000000000000000000', + }); - return fetchFetcher(params); - }; + expect(postUrl.startsWith(`${API_URL}/delta/v2/orders`)).toBe(true); + expect(posted.signature).toBe(FAKE_SIGNATURE); + expect(posted.chainId).toBe(1); + // order comes from builtFixture.toSign.value + expect(posted.order.owner).toBe(OWNER); + expect(posted.order.srcToken).toBe(WETH); + expect(posted.order.destToken).toBe(DAI); + expect(response.id).toBe('auction-99'); + }); - const mockFetch = jest.fn(dummyFetcher); + test('postDeltaOrder forwards degenMode as a query param', async () => { + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('POST'); + expect(url).toContain('degenMode=true'); + return { id: 'x' } as unknown as DeltaAuction<'Order'>; + }); - const dummySDK = constructPartialSDK( - { + const { postDeltaOrder } = constructPostDeltaOrder({ + apiURL: API_URL, chainId: 1, - fetcher: mockFetch as FetcherFunction, - contractCaller: ethersV5ContractCaller, - apiURL: process.env.API_URL, - }, - constructPostDeltaOrder, - constructSubmitDeltaOrder - ); - - test('Post Delta Order', async () => { - const sampleOrderData: SignableDeltaOrderData['data'] = { - beneficiary: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9', - deadline: NaN, // dynamic number - destAmount: '3147447403157656698880', - destToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', - expectedAmount: '3163263721766488892666', - nonce: 'dynamic_number', - owner: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9', - partnerAndFee: '0', - permit: '0x', - srcAmount: '1000000000000000000', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - kind: 0, - metadata: '0x', - bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', - }, - }; + fetcher, + }); - const sampleSignature = '0x1234....'; + expect( + await postDeltaOrder({ + signature: FAKE_SIGNATURE, + order: {} as any, + degenMode: true, + }) + ).toEqual({ id: 'x' }); + }); - const input = { - order: sampleOrderData, - signature: sampleSignature, - }; + test('postExternalDeltaOrder sends to /delta/v2/orders', async () => { + const fetcher = makeFetcher(({ url, method }) => { + expect(method).toBe('POST'); + expect(url).toBe(`${API_URL}/delta/v2/orders`); + return { id: 'ext-1' } as unknown as DeltaAuction<'ExternalOrder'>; + }); - await dummySDK.postDeltaOrder(input); + const { postExternalDeltaOrder } = constructPostExternalDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + }); - expect(mockFetch).toHaveBeenLastCalledWith({ - data: { ...input, chainId: dummySDK.chainId }, - method: 'POST', - url: `${dummySDK.apiURL}/delta/orders/`, + expect( + await postExternalDeltaOrder({ + signature: FAKE_SIGNATURE, + order: {} as any, + }) + ).toEqual({ id: 'ext-1' }); + }); + + test('postTWAPDeltaOrder sends to /delta/v2/orders with onChainOrderType', async () => { + const fetcher = makeFetcher(({ url, method, data }) => { + expect(method).toBe('POST'); + expect(url.startsWith(`${API_URL}/delta/v2/orders`)).toBe(true); + expect(data.onChainOrderType).toBe('TWAPOrder'); + return { id: 'twap-1' } as unknown as DeltaAuction<'TWAPOrder'>; + }); + + const { postTWAPDeltaOrder } = constructPostTWAPDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, }); + + expect( + await postTWAPDeltaOrder({ + signature: FAKE_SIGNATURE, + order: {} as any, + onChainOrderType: 'TWAPOrder', + }) + ).toEqual({ id: 'twap-1' }); }); +}); - test('Submit(=build+sign+post) Delta Order', async () => { - const sampleDeltaPrice: DeltaPrice = { - destAmount: '3163263721766488892666', - destAmountBeforeFee: '3194635547945152526200', - receivedDestAmount: '3163263721766488892666', - destToken: '0x6b175474e89094c44da98b954eedeac495271d0f', - destUSD: '3166.4269854931', - receivedDestUSD: '3166.4269854931', - destUSDBeforeFee: '3197.8301834931', - gasCost: '347788', - gasCostBeforeFee: '124240', - gasCostUSD: '31.403198', - gasCostUSDBeforeFee: '11.218137', - partner: 'anon', - partnerFee: 0, - srcAmount: '1000000000000000000', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - srcUSD: '3191.5500000000', - hmac: '1234aeb', - bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', - }, - }; +describe('Delta: cancel', () => { + test('cancelDeltaOrders signs then posts to /delta/v2/orders/cancel', async () => { + let postedTo = ''; + let postedBody: any; + + const fetcher = makeFetcher(({ url, method, data }) => { + const adapter = url.includes('/adapters/contracts') + ? { + AugustusSwapper: '0x', + TokenTransferProxy: '0x', + AugustusRFQ: '0x', + Executors: {}, + ParaswapDelta: '0x1111111111111111111111111111111111111111', + } + : undefined; + if (adapter) return adapter; + if (method === 'POST') { + postedTo = url; + postedBody = data; + return { success: true }; + } + throw new Error('unexpected'); + }); - const slippagePercent = 0.5; - const destAmountAfterSlippage = decreaseBySlippage( - sampleDeltaPrice.destAmount, - slippagePercent - ); + const { cancelDeltaOrders } = constructCancelDeltaOrder({ + apiURL: API_URL, + chainId: 1, + fetcher, + contractCaller: makeMockContractCaller(), + }); - const amount = '1000000000000000000'; // 1 DAI + const result = await cancelDeltaOrders({ + orderIds: ['a', 'b'], + }); - const input = { - deltaPrice: sampleDeltaPrice, - owner: senderAddress, - // beneficiary: anotherAccount, // if need to send destToken to another account - // permit: "0x1234...", // if signed a Permit1 or Permit2 TransferFrom for DeltaContract - srcToken: WETH, - destToken: DAI, - srcAmount: amount, - destAmount: destAmountAfterSlippage, // minimum acceptable destAmount - partnerAddress: ZERO_ADDRESS, - }; + expect(result).toEqual({ success: true }); + expect(postedTo).toBe(`${API_URL}/delta/v2/orders/cancel`); + expect(postedBody.orderIds).toEqual(['a', 'b']); + expect(postedBody.signature).toBe(FAKE_SIGNATURE); + }); +}); - await dummySDK.submitDeltaOrder(input); +describe('Delta: live API contract', () => { + jest.setTimeout(30_000); - const callArgs = mockFetch.mock.lastCall?.[0]; - assert(callArgs, 'No fetch call was made'); - assert('data' in callArgs, 'No data was sent in the fetch call'); - const { order, signature } = callArgs.data as PostDeltaOrderParams; + const LIVE_API = process.env.API_URL; + const fetchFetcher = constructFetchFetcher(fetch); - expect(signature).toBeDefined(); + test('GET /delta/v2/prices (same-chain) matches DeltaPrice shape', async () => { + const { getDeltaPrice } = constructGetDeltaPrice({ + apiURL: LIVE_API, + chainId: 1, + fetcher: fetchFetcher, + }); - const staticSignedOrderData: SignableDeltaOrderData['data'] = { - ...order, - deadline: NaN, // dynamic number - nonce: 'dynamic_number', - }; + const price = await getDeltaPrice({ + srcToken: WETH, + destToken: DAI, + amount: '1000000000000000000', + srcDecimals: 18, + destDecimals: 18, + side: 'SELL', + }); - // for ZERO partnerAddress capSurplus (true) shifted (<< 9) = 512 - expect(order.partnerAndFee).toEqual((1 << 9).toString()); - expect(staticSignedOrderData).toMatchSnapshot(); + expect(price.side).toBe('SELL'); + expect(price.inputToken.chainId).toBe(1); + expect(price.outputToken.chainId).toBe(1); + expect(typeof price.id).toBe('string'); + expect(typeof price.spender).toBe('string'); + expect(price.route.origin.input.token.address.toLowerCase()).toBe(WETH); + expect(typeof price.route.origin.input.amount).toBe('string'); + expect(typeof price.route.origin.input.amountUSD).toBe('string'); + // same-chain: bridge is null + expect(price.route.bridge).toBeNull(); + // fees.gas is a single DeltaTokenAmount, fees.bridge is an array + expect(typeof price.route.fees.gas.amount).toBe('string'); + expect(Array.isArray(price.route.fees.bridge)).toBe(true); }); - describe('PreSign Delta Order', () => { - const sdk = constructPartialSDK( - { - chainId: 1, - fetcher: fetchFetcher, - contractCaller: viemContractCaller, - apiURL: process.env.API_URL, - }, - constructPreSignDeltaOrder, - constructGetDeltaContract - ); + test('GET /delta/v2/prices (cross-chain) returns DeltaRoute with bridge.contractParams (no destinationChainId)', async () => { + const { getDeltaPrice } = constructGetDeltaPrice({ + apiURL: LIVE_API, + chainId: 1, + fetcher: fetchFetcher, + }); - const sampleOrder: DeltaAuctionOrder = { - owner: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9', - beneficiary: '0xaC39b311DCEb2A4b2f5d8461c1cdaF756F4F7Ae9', - srcToken: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - destToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', - srcAmount: '1000000000000000000', - destAmount: '3147447403157656698880', - expectedAmount: '3163263721766488892666', - kind: 0, - metadata: '0x', - deadline: 1731328853, - nonce: '1731325253703', - permit: '0x', - partnerAndFee: '0', - bridge: { - protocolSelector: '0x00000000', - destinationChainId: 0, - outputToken: ZERO_ADDRESS, - scalingFactor: 0, - protocolData: '0x', - }, - }; + const price = await getDeltaPrice({ + srcToken: WETH, + destToken: USDC_ARB, + amount: '1000000000000000000', + srcDecimals: 18, + destDecimals: 6, + destChainId: 42161, + side: 'SELL', + }); - test('hash Delta Order', async () => { - const orderHash = await sdk.hashDeltaOrder(sampleOrder); - expect(orderHash).toMatchInlineSnapshot( - `"0x4e68492d838e64c329ecdba51d32cb088088445ca62b1d5c4edf5a2ab80b586d"` + expect(price.outputToken.chainId).toBe(42161); + expect(price.route.destination.input.token.chainId).toBe(42161); + expect(price.route.bridge).not.toBeNull(); + if (price.route.bridge) { + expect(typeof price.route.bridge.protocol).toBe('string'); + expect(Array.isArray(price.route.bridge.tags)).toBe(true); + expect(typeof price.route.bridge.contractParams.protocolSelector).toBe( + 'string' + ); + expect(typeof price.route.bridge.contractParams.outputToken).toBe( + 'string' + ); + // destinationChainId is NOT present on the wire — the SDK injects it at build time + expect('destinationChainId' in price.route.bridge.contractParams).toBe( + false ); + } + }); + + test('GET /delta/v2/prices/bridge-routes returns flat array', async () => { + const { getBridgeRoutes } = constructGetBridgeRoutes({ + apiURL: LIVE_API, + chainId: 1, + fetcher: fetchFetcher, }); - test('PreSign Delta Order', async () => { - const sampleOrderHash = - '0x4e68492d838e64c329ecdba51d32cb088088445ca62b1d5c4edf5a2ab80b586d'; - const txHash = await sdk.setDeltaOrderPreSignature(sampleOrderHash); - await viemWalletClient.waitForTransactionReceipt({ hash: txHash }); + const routes = await getBridgeRoutes(); + expect(Array.isArray(routes)).toBe(true); + expect(routes.length).toBeGreaterThan(0); + const first = routes[0]!; + expect(typeof first.srcChainId).toBe('number'); + expect(typeof first.destChainId).toBe('number'); + expect(Array.isArray(first.tokens)).toBe(true); + }); - const isPreSigned = await checkIfOrderHashPreSigned({ - orderHash: sampleOrderHash, - owner: senderAddress, - sdk, - }); - expect(isPreSigned).toBe(true); + test('GET /delta/v2/prices/bridge-protocols returns protocols', async () => { + const { getBridgeProtocols } = constructGetBridgeRoutes({ + apiURL: LIVE_API, + chainId: 1, + fetcher: fetchFetcher, }); + + const protocols = await getBridgeProtocols(); + expect(protocols.length).toBeGreaterThan(0); + expect(protocols.some((p) => p.protocol === 'Across')).toBe(true); }); }); -function getTokenAllowance({ - tokenAddress, - owner, - spender, -}: { - tokenAddress: string; - owner: string; - spender: string; -}): Promise { - const contract = new ethers.Contract(tokenAddress, erc20abi, signer); - return contract.allowance(owner, spender); -} - -function decreaseBySlippage(amount: string, slippagePercent: number): string { - const amountAfterSlippage = BigInt( - +(+amount * (1 - slippagePercent / 100)).toFixed(0) - ).toString(10); - - return amountAfterSlippage; -} +describe('Delta: SDK wiring', () => { + test('constructAllDeltaOrdersHandlers exposes all v2 methods', () => { + const sdk = constructAllDeltaOrdersHandlers({ + apiURL: API_URL, + chainId: 1, + fetcher: makeFetcher(() => ({})), + contractCaller: makeMockContractCaller(), + }); -type VerifySignedCancelRequestInput = { - orderId: string; - signature: string; - address: string; - chainId: number; -}; - -async function verifySignedCancelRequest({ - orderId, - signature, - address, -}: VerifySignedCancelRequestInput): Promise { - const ParaswapDelta = await constructGetDeltaContract({ - chainId, - fetcher: fetchFetcher, - }).getDeltaContract(); - - assert(ParaswapDelta, 'ParaswapDelta is not defined'); - - const valid = await verifyTypedData({ - address: address as Address, - domain: { - name: 'Portikus', - version: '2.0.0', - chainId, - verifyingContract: ParaswapDelta as Address, - }, - types: { - OrderCancellations: [{ name: 'orderIds', type: 'string[]' }], - }, - primaryType: 'OrderCancellations', - message: { - orderIds: [orderId], - }, - signature: signature as Hex, + expect(typeof sdk.getDeltaPrice).toBe('function'); + expect(typeof sdk.getDeltaOrders).toBe('function'); + expect(typeof sdk.getRequiredBalanceForDeltaOrders).toBe('function'); + expect(typeof sdk.getBridgeRoutes).toBe('function'); + expect(typeof sdk.buildDeltaOrder).toBe('function'); + expect(typeof sdk.postDeltaOrder).toBe('function'); + expect(typeof sdk.submitDeltaOrder).toBe('function'); + expect(typeof sdk.submitExternalDeltaOrder).toBe('function'); + expect(typeof sdk.submitTWAPDeltaOrder).toBe('function'); + expect(typeof sdk.cancelDeltaOrders).toBe('function'); + expect(typeof sdk.isTokenSupportedInDelta).toBe('function'); + expect(typeof sdk.getAgentsList).toBe('function'); + // reused v1 utilities + expect(typeof sdk.getDeltaContract).toBe('function'); + expect(typeof sdk.getPartnerFee).toBe('function'); + expect(typeof sdk.approveTokenForDelta).toBe('function'); + // v2 sign function (replaces v1 sign in deltaV2 namespace) + expect(typeof sdk.signDeltaOrder).toBe('function'); }); - return valid; -} + test('constructPartialSDK accepts v2 constructors individually', () => { + const fetcher = makeFetcher(() => ({})); + const sdk = constructPartialSDK( + { apiURL: API_URL, chainId: 1, fetcher }, + constructBuildDeltaOrder, + constructPostDeltaOrder, + constructGetDeltaOrders, + constructGetDeltaPrice, + constructGetDeltaContract, + constructGetPartnerFee, + constructGetBridgeRoutes, + constructIsTokenSupportedInDelta + ); -const PreSignatureModuleAbi = [ - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'bytes32', - name: 'orderHash', - type: 'bytes32', - }, - ], - name: 'isPreSigned', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes32', - name: 'orderHash', - type: 'bytes32', - }, - { - internalType: 'bool', - name: 'preSigned', - type: 'bool', - }, - ], - name: 'setPreSignature', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const; - -type CheckIfOrderHashPreSignedInput = { - orderHash: string; - owner: string; - sdk: GetDeltaContractFunctions; -}; - -async function checkIfOrderHashPreSigned({ - orderHash, - owner, - sdk, -}: CheckIfOrderHashPreSignedInput): Promise { - const ParaswapDelta = await sdk.getDeltaContract(); - - assert(ParaswapDelta, 'ParaswapDelta is not available'); - - const isPreSigned = await viemWalletClient.readContract({ - address: ParaswapDelta as Address, - abi: PreSignatureModuleAbi, - functionName: 'isPreSigned', - args: [owner as Address, orderHash as Hash], + expect(typeof sdk.getDeltaPrice).toBe('function'); + expect(typeof sdk.getBridgeRoutes).toBe('function'); + expect(typeof sdk.getDeltaOrders).toBe('function'); + expect(typeof sdk.getRequiredBalanceForDeltaOrders).toBe('function'); + expect(typeof sdk.buildDeltaOrder).toBe('function'); + expect(typeof sdk.postDeltaOrder).toBe('function'); + expect(typeof sdk.isTokenSupportedInDelta).toBe('function'); }); - return isPreSigned; -} +}); diff --git a/tests/quote.test.ts b/tests/quote.test.ts index 5e9157796..8292e861b 100644 --- a/tests/quote.test.ts +++ b/tests/quote.test.ts @@ -49,53 +49,16 @@ describe('Quote:methods', () => { const staticDeltaPrice: typeof quote.delta = { ...quote.delta, - partnerFee: NaN, // dynamic number, can change slightly depending on API config - hmac: 'dynamic_string', - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - receivedDestAmountBeforeFee: 'dynamic_number', - receivedDestUSDBeforeFee: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', + id: 'dynamic_string', + // route + alternatives carry the dynamic amounts/USD/gas fields in v2; + // stabilized wholesale so the inline snapshot below regenerates cleanly. + route: 'dynamic_route' as unknown as typeof quote.delta.route, + alternatives: + 'dynamic_alternatives' as unknown as typeof quote.delta.alternatives, + partner: { name: quote.delta.partner.name, feePercent: NaN }, }; - expect(staticDeltaPrice).toMatchInlineSnapshot(` - { - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestAmountBeforeFee": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "receivedDestUSDBeforeFee": "dynamic_number", - "srcAmount": "100000000000", - "srcToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "srcUSD": "dynamic_number", - } - `); + expect(staticDeltaPrice).toMatchInlineSnapshot(); }); test('Fail Quote for delta for small amounts', async () => { @@ -147,58 +110,16 @@ describe('Quote:methods', () => { const staticDeltaPrice: typeof quote.delta = { ...quote.delta, - partnerFee: NaN, // dynamic number - hmac: 'dynamic_string', - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', - srcAmount: 'dynamic_number', - srcAmountBeforeFee: 'dynamic_number', - srcUSDBeforeFee: 'dynamic_number', + id: 'dynamic_string', + // route + alternatives carry the dynamic amounts/USD/gas fields in v2; + // stabilized wholesale so the inline snapshot below regenerates cleanly. + route: 'dynamic_route' as unknown as typeof quote.delta.route, + alternatives: + 'dynamic_alternatives' as unknown as typeof quote.delta.alternatives, + partner: { name: quote.delta.partner.name, feePercent: NaN }, }; - // only SELL side has receivedDestAmountBeforeFee and receivedDestUSDBeforeFee - expect('receivedDestAmountBeforeFee' in quote.delta).toBeFalsy(); - expect('receivedDestUSDBeforeFee' in quote.delta).toBeFalsy(); - - expect(staticDeltaPrice).toMatchInlineSnapshot(` - { - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "srcAmount": "dynamic_number", - "srcAmountBeforeFee": "dynamic_number", - "srcToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "srcUSD": "dynamic_number", - "srcUSDBeforeFee": "dynamic_number", - } - `); + expect(staticDeltaPrice).toMatchInlineSnapshot(); }); test('Get Quote for market', async () => { @@ -258,53 +179,16 @@ describe('Quote:methods', () => { const staticDeltaPrice: typeof quote.delta = { ...quote.delta, - partnerFee: NaN, // dynamic number, can change slightly depending on API config - hmac: 'dynamic_string', - destAmount: 'dynamic_number', - destAmountBeforeFee: 'dynamic_number', - srcUSD: 'dynamic_number', - destUSD: 'dynamic_number', - destUSDBeforeFee: 'dynamic_number', - receivedDestAmount: 'dynamic_number', - receivedDestUSD: 'dynamic_number', - receivedDestAmountBeforeFee: 'dynamic_number', - receivedDestUSDBeforeFee: 'dynamic_number', - gasCost: 'dynamic_number', - gasCostBeforeFee: 'dynamic_number', - gasCostUSD: 'dynamic_number', - gasCostUSDBeforeFee: 'dynamic_number', + id: 'dynamic_string', + // route + alternatives carry the dynamic amounts/USD/gas fields in v2; + // stabilized wholesale so the inline snapshot below regenerates cleanly. + route: 'dynamic_route' as unknown as typeof quote.delta.route, + alternatives: + 'dynamic_alternatives' as unknown as typeof quote.delta.alternatives, + partner: { name: quote.delta.partner.name, feePercent: NaN }, }; - expect(staticDeltaPrice).toMatchInlineSnapshot(` - { - "bridge": { - "destinationChainId": 0, - "outputToken": "0x0000000000000000000000000000000000000000", - "protocolData": "0x", - "protocolSelector": "0x00000000", - "scalingFactor": 0, - }, - "destAmount": "dynamic_number", - "destAmountBeforeFee": "dynamic_number", - "destToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", - "destUSD": "dynamic_number", - "destUSDBeforeFee": "dynamic_number", - "gasCost": "dynamic_number", - "gasCostBeforeFee": "dynamic_number", - "gasCostUSD": "dynamic_number", - "gasCostUSDBeforeFee": "dynamic_number", - "hmac": "dynamic_string", - "partner": "anon", - "partnerFee": NaN, - "receivedDestAmount": "dynamic_number", - "receivedDestAmountBeforeFee": "dynamic_number", - "receivedDestUSD": "dynamic_number", - "receivedDestUSDBeforeFee": "dynamic_number", - "srcAmount": "100000000000", - "srcToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "srcUSD": "dynamic_number", - } - `); + expect(staticDeltaPrice).toMatchInlineSnapshot(); }); test('Get Fallback Market Quote for all', async () => {