diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..5655115c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,107 @@ +# Velora SDK — Architecture Guide + +TypeScript SDK for the Paraswap/Velora Delta protocol (on-chain order-based DEX). + +## Composable Constructor Pattern + +Every feature module exports a `constructXxx(options) => XxxFunctions` factory: +- `options` is `ConstructProviderFetchInput` or a `Pick` of it +- `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` + +## Key Patterns + +### On-Chain Transaction (reference: `src/methods/delta/preSignDeltaOrder.ts`) +1. Define minimal ABI inline as `const XxxAbi = [...] as const` +2. Extract method names: `type AvailableMethods = ExtractAbiMethodNames` +3. Constructor takes `ConstructProviderFetchInput` +4. Get contract address: `const { getDeltaContract } = constructGetDeltaContract(options)` +5. Call contract: `options.contractCaller.transactCall({ address, abi, contractMethod, args, overrides })` + +### API Call (reference: `src/methods/delta/cancelDeltaOrder.ts` sign/post methods) +1. Sign EIP-712 typed data via `options.contractCaller.signTypedDataCall(typedData)` +2. POST to API via `options.fetcher({ url, method, data })` + +## Key File Locations + +### Delta Module (`src/methods/delta/`) +| 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) | — | — | + +### Delta Helpers (`src/methods/delta/helpers/`) +- `types.ts` — `DeltaAuctionOrder`, `Bridge`, `DeltaAuction`, `DeltaAuctionStatus`, `BridgeMetadata`, `BridgeStatus`, `BridgePriceInfo`, `SwapSideToOrderKind` +- `buildDeltaOrderData.ts` — `buildDeltaSignableOrderData`, `produceDeltaOrderTypedData`, `SignableDeltaOrderData`, `BuildDeltaOrderDataInput`, `DELTA_DEFAULT_EXPIRY` +- `buildCancelDeltaOrderData.ts` — `buildCancelDeltaOrderSignableData`, `SignableCancelDeltaOrderData`, `CancelDeltaOrderData` +- `misc.ts` — `sanitizeDeltaOrderData` (strips extra fields from order data before signing/hashing) + +### Core Types (`src/`) +- `types.ts` — `ConstructProviderFetchInput`, `ContractCallerFunctions`, `TxSendOverrides` +- `helpers/misc.ts` — `ExtractAbiMethodNames` +- `sdk/partial.ts` — `constructPartialSDK`, `InferWithTxResponse` type tuple + +## Checklist: Adding a New On-Chain Method + +1. Add ABI to the module file (inline `as const`) +2. Add `type AvailableMethods = ExtractAbiMethodNames` +3. Make constructor generic `` if not already +4. Add `'transactCall'` to the `ConstructProviderFetchInput` `Pick` +5. Implement function using `options.contractCaller.transactCall({...})` +6. Export function type + param types +7. Update `DeltaOrderHandlers` in `src/methods/delta/index.ts` (make generic if was non-generic) +8. Add to `InferWithTxResponse` tuple in `src/sdk/partial.ts` (import + add to array) +9. Export new types from `src/index.ts` +10. Run `npx tsc --noEmit` to verify + +## Solidity ABI: Order Struct Mapping + +When writing inline ABI for functions that take the `Order` struct: + +``` +Order tuple (maps to DeltaAuctionOrder in TS): + owner → address + beneficiary → address + srcToken → address + destToken → address + srcAmount → uint256 + destAmount → uint256 + expectedAmount → uint256 + kind → uint8 (enum OrderKind) + metadata → bytes32 + deadline → uint256 + nonce → uint256 + permit → bytes + partnerAndFee → uint256 + bridge → tuple (Bridge) + protocolSelector → bytes4 + destinationChainId → uint64 + outputToken → address + scalingFactor → uint32 + protocolData → bytes + +OrderWithSig tuple: + order → tuple (Order) + signature → bytes +``` + +## Key Conventions +- Contract address is always resolved via `constructGetDeltaContract(options).getDeltaContract()` +- 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 diff --git a/package.json b/package.json index 09b9d1cb..2d9ede63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@velora-dex/sdk", - "version": "9.3.3", + "version": "9.3.4-dev.4", "main": "dist/index.js", "module": "dist/sdk.esm.js", "typings": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 12ad1e25..d9b56e88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -216,6 +216,13 @@ import { CancelDeltaOrderFunctions, constructCancelDeltaOrder, } from './methods/delta/cancelDeltaOrder'; +import { + DeltaTokenModuleFunctions, + CancelAndWithdrawDeltaOrderParams, + constructDeltaTokenModule, + DepositNativeAndPreSignParams, + DepositNativeAndPreSignDeltaOrderParams, +} from './methods/delta/deltaTokenModule'; export { constructSwapSDK, SwapSDKMethods } from './methods/swap'; @@ -300,6 +307,7 @@ export { constructGetDeltaPrice, constructGetDeltaOrders, constructCancelDeltaOrder, + constructDeltaTokenModule, constructApproveTokenForDelta, // Quote methods constructGetQuote, @@ -395,6 +403,10 @@ export type { GetDeltaOrdersFunctions, ApproveTokenForDeltaFunctions, CancelDeltaOrderFunctions, + DeltaTokenModuleFunctions, + CancelAndWithdrawDeltaOrderParams, + DepositNativeAndPreSignParams, + DepositNativeAndPreSignDeltaOrderParams, // types for Quote methods GetQuoteFunctions, QuoteParams, diff --git a/src/methods/delta/constants.ts b/src/methods/delta/constants.ts new file mode 100644 index 00000000..bc22e0e0 --- /dev/null +++ b/src/methods/delta/constants.ts @@ -0,0 +1,11 @@ +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/deltaTokenModule.ts b/src/methods/delta/deltaTokenModule.ts new file mode 100644 index 00000000..3f0f78b4 --- /dev/null +++ b/src/methods/delta/deltaTokenModule.ts @@ -0,0 +1,367 @@ +import type { + ConstructProviderFetchInput, + RequestParameters, + TxSendOverrides, +} from '../../types'; +import { constructGetDeltaContract } from './getDeltaContract'; +import { sanitizeDeltaOrderData } from './helpers/misc'; +import { SignableDeltaOrderData } from './helpers/buildDeltaOrderData'; +import { produceDeltaOrderHash } from './preSignDeltaOrder'; +import type { ExtractAbiMethodNames } from '../../helpers/misc'; +import type { DeltaAuctionOrder } from './helpers/types'; +import { DEFAULT_BRIDGE } from './constants'; + +export type CancelAndWithdrawDeltaOrderParams = { + order: DeltaAuctionOrder; + signature: string; + /** @description A boolean indicating whether the order is a fillable order. False by default */ + isFillable?: boolean; +}; + +export type CancelAndWithdrawDeltaOrder = ( + params: CancelAndWithdrawDeltaOrderParams, + overrides?: TxSendOverrides, + requestParams?: RequestParameters +) => Promise; + +export type WithdrawDeltaNative = ( + amount: string, + overrides?: TxSendOverrides, + requestParams?: RequestParameters +) => Promise; + +export type DepositNativeAndPreSignParams = { + orderHash: string; + depositAmount: string; +}; + +export type DepositNativeAndPreSign = ( + params: DepositNativeAndPreSignParams, + overrides?: Omit, // value is set internally based on depositAmount + requestParams?: RequestParameters +) => Promise; + +export type DepositNativeAndPreSignDeltaOrderParams = Pick< + DepositNativeAndPreSignParams, + 'depositAmount' +> & { + signableOrderData: SignableDeltaOrderData; +}; + +export type DepositNativeAndPreSignDeltaOrder = ( + params: DepositNativeAndPreSignDeltaOrderParams, + overrides?: Omit, // value is set internally based on depositAmount + requestParams?: RequestParameters +) => Promise; + +export type DeltaTokenModuleFunctions = { + /** @description Cancel an order on-chain and withdraw native ETH back to the owner */ + cancelAndWithdrawDeltaOrder: CancelAndWithdrawDeltaOrder; + /** @description Withdraw Delta Wrapped Native tokens as native ETH */ + withdrawDeltaNative: WithdrawDeltaNative; + /** @description Deposit native ETH and pre-sign a Delta order */ + depositNativeAndPreSign: DepositNativeAndPreSign; + /** @description Deposit native ETH and pre-sign a Delta order from signable order data */ + depositNativeAndPreSignDeltaOrder: DepositNativeAndPreSignDeltaOrder; +}; + +const DeltaTokenModuleAbi = [ + { + type: 'function', + name: 'cancelAndWithdraw', + inputs: [ + { + name: 'orderWithSig', + type: 'tuple', + internalType: 'struct OrderWithSig', + components: [ + { + name: 'order', + type: 'tuple', + internalType: 'struct Order', + components: [ + { + name: 'owner', + type: 'address', + internalType: 'address', + }, + { + name: 'beneficiary', + type: 'address', + internalType: 'address', + }, + { + name: 'srcToken', + type: 'address', + internalType: 'address', + }, + { + name: 'destToken', + type: 'address', + internalType: 'address', + }, + { + name: 'srcAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'destAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'expectedAmount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'deadline', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'kind', + type: 'uint8', + internalType: 'enum OrderKind', + }, + { + name: 'nonce', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'partnerAndFee', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'permit', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'metadata', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'bridge', + type: 'tuple', + internalType: 'struct Bridge', + components: [ + { + name: 'protocolSelector', + type: 'bytes4', + internalType: 'bytes4', + }, + { + name: 'destinationChainId', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'outputToken', + type: 'address', + internalType: 'address', + }, + { + name: 'scalingFactor', + type: 'int8', + internalType: 'int8', + }, + { + name: 'protocolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + ], + }, + { + name: 'signature', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'bridgeOverride', + type: 'tuple', + internalType: 'struct BridgeOverride', + components: [ + { + name: 'protocolSelector', + type: 'bytes4', + internalType: 'bytes4', + }, + { + name: 'protocolData', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'cosignature', + type: 'bytes', + internalType: 'bytes', + }, + ], + }, + { + name: 'isFillable', + type: 'bool', + internalType: 'bool', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdrawNative', + inputs: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'depositNativeAndPreSign', + inputs: [ + { + name: 'orderHash', + type: 'bytes32', + internalType: 'bytes32', + }, + ], + outputs: [], + stateMutability: 'payable', + }, +] as const; + +type AvailableMethods = ExtractAbiMethodNames; + +// returns whatever `contractCaller` returns +// to allow for better versatility +export const constructDeltaTokenModule = ( + options: Pick< + ConstructProviderFetchInput, + 'contractCaller' | 'fetcher' | 'apiURL' | 'chainId' + > +): DeltaTokenModuleFunctions => { + // cached internally + const { getDeltaContract } = constructGetDeltaContract(options); + + const cancelAndWithdrawDeltaOrder: CancelAndWithdrawDeltaOrder = async ( + { order, signature, isFillable = false }, + overrides = {}, + requestParams + ) => { + const ParaswapDelta = await getDeltaContract(requestParams); + if (!ParaswapDelta) { + throw new Error(`Delta is not available on chain ${options.chainId}`); + } + + const orderWithSig = { + order, + signature, + // bridgeOverride and cosignature are not used by the contract, + // can always provide defaults + bridgeOverride: { + protocolData: DEFAULT_BRIDGE.protocolData, + protocolSelector: DEFAULT_BRIDGE.protocolSelector, + }, + cosignature: '0x', + }; + + const res = await options.contractCaller.transactCall({ + address: ParaswapDelta, + abi: DeltaTokenModuleAbi, + contractMethod: 'cancelAndWithdraw', + args: [orderWithSig, isFillable], + overrides, + }); + + return res; + }; + + const withdrawDeltaNative: WithdrawDeltaNative = async ( + amount, + overrides = {}, + requestParams + ) => { + const ParaswapDelta = await getDeltaContract(requestParams); + if (!ParaswapDelta) { + throw new Error(`Delta is not available on chain ${options.chainId}`); + } + + const res = await options.contractCaller.transactCall({ + address: ParaswapDelta, + abi: DeltaTokenModuleAbi, + contractMethod: 'withdrawNative', + args: [amount], + overrides, + }); + + return res; + }; + + const depositNativeAndPreSign: DepositNativeAndPreSign = async ( + { orderHash, depositAmount }, + overrides = {}, + requestParams + ) => { + const ParaswapDelta = await getDeltaContract(requestParams); + if (!ParaswapDelta) { + throw new Error(`Delta is not available on chain ${options.chainId}`); + } + + const res = await options.contractCaller.transactCall({ + address: ParaswapDelta, + abi: DeltaTokenModuleAbi, + contractMethod: 'depositNativeAndPreSign', + args: [orderHash], + overrides: { + ...overrides, + value: depositAmount, + }, + }); + + return res; + }; + + const depositNativeAndPreSignDeltaOrder: DepositNativeAndPreSignDeltaOrder< + T + > = async ( + { signableOrderData, depositAmount }, + overrides = {}, + requestParams + ) => { + // types allow to pass OrderData & extra_stuff, but tx will break like that + const typedDataOnly: SignableDeltaOrderData = { + ...signableOrderData, + data: sanitizeDeltaOrderData(signableOrderData.data), + }; + + const orderHash = produceDeltaOrderHash(typedDataOnly); + const res = await depositNativeAndPreSign( + { orderHash, depositAmount }, + overrides, + requestParams + ); + return res; + }; + + return { + cancelAndWithdrawDeltaOrder, + withdrawDeltaNative, + depositNativeAndPreSign, + depositNativeAndPreSignDeltaOrder, + }; +}; diff --git a/src/methods/delta/getDeltaPrice.ts b/src/methods/delta/getDeltaPrice.ts index d39ddc5a..2d8327dd 100644 --- a/src/methods/delta/getDeltaPrice.ts +++ b/src/methods/delta/getDeltaPrice.ts @@ -7,7 +7,6 @@ import type { EnumerateLiteral, RequestParameters, } from '../../types'; -import { ZERO_ADDRESS } from '../common/orders/buildOrderData'; import { BridgePriceInfo } from './helpers/types'; type SwapSideUnion = EnumerateLiteral; @@ -56,15 +55,6 @@ type DeltaPriceQueryOptions = Omit< excludeBridges?: string; }; -// 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; - export type DeltaPrice = { srcToken: string; destToken: string; diff --git a/src/methods/delta/index.ts b/src/methods/delta/index.ts index 80ce147a..e18ebc63 100644 --- a/src/methods/delta/index.ts +++ b/src/methods/delta/index.ts @@ -50,6 +50,10 @@ import { constructPreSignDeltaOrder, PreSignDeltaOrderFunctions, } from './preSignDeltaOrder'; +import { + DeltaTokenModuleFunctions, + constructDeltaTokenModule, +} from './deltaTokenModule'; export type SubmitDeltaOrderParams = BuildDeltaOrderDataParams & { /** @description designates the Order as being able to be partially filled, as opposed to fill-or-kill */ @@ -107,7 +111,8 @@ export type DeltaOrderHandlers = SubmitDeltaOrderFuncs & PostDeltaOrderFunctions & SignDeltaOrderFunctions & PreSignDeltaOrderFunctions & - CancelDeltaOrderFunctions; + CancelDeltaOrderFunctions & + DeltaTokenModuleFunctions; /** @description construct SDK with every Delta Order-related method, fetching from API and Order signing */ export const constructAllDeltaOrdersHandlers = ( @@ -135,6 +140,8 @@ export const constructAllDeltaOrdersHandlers = ( const deltaOrdersCancel = constructCancelDeltaOrder(options); + const deltaTokenModule = constructDeltaTokenModule(options); + return { ...deltaOrdersGetters, ...deltaOrdersContractGetter, @@ -149,5 +156,6 @@ export const constructAllDeltaOrdersHandlers = ( ...deltaOrdersPreSign, ...deltaOrdersPost, ...deltaOrdersCancel, + ...deltaTokenModule, }; }; diff --git a/src/sdk/partial.ts b/src/sdk/partial.ts index 07b60aca..437e95dd 100644 --- a/src/sdk/partial.ts +++ b/src/sdk/partial.ts @@ -13,6 +13,7 @@ import type { ApproveTokenForNFTOrderFunctions } from '../methods/nftOrders/appr import type { FillOrderDirectlyFunctions } from '../methods/limitOrders/fillOrderDirectly'; import type { ApproveTokenForDeltaFunctions } from '../methods/delta/approveForDelta'; import type { PreSignDeltaOrderFunctions } from '../methods/delta/preSignDeltaOrder'; +import type { DeltaTokenModuleFunctions } from '../methods/delta/deltaTokenModule'; import { API_URL, DEFAULT_VERSION } from '../constants'; export type SDKConfig = ConstructProviderFetchInput< @@ -52,7 +53,8 @@ type InferWithTxResponse< CancelNFTOrderFunctions, ApproveTokenForNFTOrderFunctions, ApproveTokenForDeltaFunctions, - PreSignDeltaOrderFunctions + PreSignDeltaOrderFunctions, + DeltaTokenModuleFunctions ] // then merge IntersectionOfReturns with them recursively >