diff --git a/src/examples/delta.ts b/src/examples/delta.ts index 3f2577f4..208e4d94 100644 --- a/src/examples/delta.ts +++ b/src/examples/delta.ts @@ -14,10 +14,13 @@ const fetcher = constructAxiosFetcher(axios); const provider = ethers.getDefaultProvider(1); const signer = Wallet.createRandom().connect(provider); const account = signer.address; -const contractCaller = constructEthersContractCaller({ - ethersProviderOrSigner: provider, - EthersContract: ethers.Contract, -}); +const contractCaller = constructEthersContractCaller( + { + ethersProviderOrSigner: provider, + EthersContract: ethers.Contract, + }, + account +); // type AdaptersFunctions & ApproveTokenFunctions const deltaSDK = constructPartialSDK( diff --git a/src/examples/externalDelta.ts b/src/examples/externalDelta.ts new file mode 100644 index 00000000..c88afdde --- /dev/null +++ b/src/examples/externalDelta.ts @@ -0,0 +1,155 @@ +/* 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(8453); // Base +const signer = Wallet.createRandom().connect(provider); +const account = signer.address; +const contractCaller = constructEthersContractCaller( + { + ethersProviderOrSigner: signer, + EthersContract: ethers.Contract, + }, + account +); + +const deltaSDK = constructPartialSDK( + { + chainId: 8453, // Base + fetcher, + contractCaller, + }, + constructAllDeltaOrdersHandlers +); + +// Base tokens +const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913'; +const WETH = '0x4200000000000000000000000000000000000006'; + +// Aave external handler on Base +const AAVE_HANDLER = '0xe4c9d68f134b6d2380d124233002535ba786d5a1'; + +// Aave order types passed as `data` field +const AaveOrderTypes = { + COLLATERAL_SWAP: + '0x0000000000000000000000000000000000000000000000000000000000000000', + DEBT_SWAP: + '0x0000000000000000000000000000000000000000000000000000000000000001', + REPAY_WITH_COLLATERAL: + '0x0000000000000000000000000000000000000000000000000000000000000002', +}; + +// Aave Collateral Swap: swap one collateral asset for another (SELL side) +async function collateralSwapFlow() { + const amount = (10 ** 6).toString(); // 1 USDC in wei + + 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: 500, // 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) +async function debtSwapFlow() { + const debtAmount = (10 ** 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: 500, // 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) +async function repayWithCollateralFlow() { + const collateralAmount = (10 ** 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: 500, // 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 a2972df1..73806b27 100644 --- a/src/examples/helpers/delta.ts +++ b/src/examples/helpers/delta.ts @@ -1,13 +1,17 @@ -import { DeltaAuction, DeltaOrderFromAPI } from '../..'; +import { DeltaOrderFromAPI } from '../..'; function isExecutedDeltaAuction( - auction: Omit, + auction: DeltaOrderFromAPI, 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) { + if ( + waitForCrosschain && + 'bridge' in auction.order && + auction.order.bridge.destinationChainId !== 0 + ) { return auction.bridgeStatus === 'filled'; } diff --git a/src/index.ts b/src/index.ts index d9b56e88..b27a86f0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -144,13 +144,14 @@ import type { BridgeMetadata, BridgeStatus, Bridge, + ExternalDeltaOrder, + SwapSideUnion, } from './methods/delta/helpers/types'; import { BuildDeltaOrderDataParams, BuildDeltaOrderFunctions, constructBuildDeltaOrder, SignableDeltaOrderData, - SwapSideUnion, } from './methods/delta/buildDeltaOrder'; import { constructPostDeltaOrder, @@ -203,6 +204,27 @@ import { IsTokenSupportedInDeltaFunctions, } from './methods/delta/isTokenSupportedInDelta'; +import { + constructBuildExternalDeltaOrder, + BuildExternalDeltaOrderFunctions, + BuildExternalDeltaOrderParams, +} from './methods/delta/buildExternalDeltaOrder'; +import type { SignableExternalOrderData } from './methods/delta/helpers/buildExternalOrderData'; +import { + constructSignExternalDeltaOrder, + SignExternalDeltaOrderFunctions, +} from './methods/delta/signExternalDeltaOrder'; +import { + constructPostExternalDeltaOrder, + ExternalDeltaOrderApiResponse, + PostExternalDeltaOrderFunctions, + PostExternalDeltaOrderParams, +} from './methods/delta/postExternalDeltaOrder'; +import { + constructPreSignExternalDeltaOrder, + PreSignExternalDeltaOrderFunctions, +} from './methods/delta/preSignExternalDeltaOrder'; + import { constructGetQuote, GetQuoteFunctions, @@ -235,8 +257,10 @@ export { export { constructAllDeltaOrdersHandlers, constructSubmitDeltaOrder, + constructSubmitExternalDeltaOrder, DeltaOrderHandlers, SubmitDeltaOrderParams, + SubmitExternalDeltaOrderParams, } from './methods/delta'; export type { @@ -309,6 +333,11 @@ export { constructCancelDeltaOrder, constructDeltaTokenModule, constructApproveTokenForDelta, + // External Delta methods + constructBuildExternalDeltaOrder, + constructSignExternalDeltaOrder, + constructPostExternalDeltaOrder, + constructPreSignExternalDeltaOrder, // Quote methods constructGetQuote, // different helpers @@ -407,6 +436,16 @@ export type { CancelAndWithdrawDeltaOrderParams, DepositNativeAndPreSignParams, DepositNativeAndPreSignDeltaOrderParams, + // External Delta types + ExternalDeltaOrder, + SignableExternalOrderData, + BuildExternalDeltaOrderParams, + BuildExternalDeltaOrderFunctions, + SignExternalDeltaOrderFunctions, + ExternalDeltaOrderApiResponse, + PostExternalDeltaOrderFunctions, + PostExternalDeltaOrderParams, + PreSignExternalDeltaOrderFunctions, // types for Quote methods GetQuoteFunctions, QuoteParams, diff --git a/src/methods/delta/buildDeltaOrder.ts b/src/methods/delta/buildDeltaOrder.ts index 0261cea2..c84479cf 100644 --- a/src/methods/delta/buildDeltaOrder.ts +++ b/src/methods/delta/buildDeltaOrder.ts @@ -1,8 +1,4 @@ -import type { - ConstructFetchInput, - EnumerateLiteral, - RequestParameters, -} from '../../types'; +import type { ConstructFetchInput, RequestParameters } from '../../types'; import { constructGetDeltaContract } from './getDeltaContract'; import type { BridgePrice } from './getDeltaPrice'; import { constructGetPartnerFee } from './getPartnerFee'; @@ -11,14 +7,12 @@ import { type BuildDeltaOrderDataInput, type SignableDeltaOrderData, } from './helpers/buildDeltaOrderData'; +import type { AmountsWithSlippage } from './helpers/types'; import { SwapSideToOrderKind } from './helpers/types'; -import { SwapSide } from '../../constants'; -import { assert, type MarkOptional } from 'ts-essentials'; -import { ZERO_ADDRESS } from '../common/orders/buildOrderData'; +import { resolvePartnerFee, resolveAmounts } from './helpers/misc'; +import type { MarkOptional } from 'ts-essentials'; export type { SignableDeltaOrderData } from './helpers/buildDeltaOrderData'; -export type SwapSideUnion = EnumerateLiteral; - type BuildDeltaOrderDataParamsBase = { /** @description The address of the order owner */ owner: string; @@ -68,39 +62,8 @@ type BuildDeltaOrderDataParamsBase = { metadata?: string; }; -/** @description SELL with slippage: srcAmount provided, destAmount auto-computed from deltaPrice.destAmount */ -type DeltaAmountsSellSlippage = { - /** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5% */ - slippage: number; - /** @description The amount of src token to swap */ - srcAmount: string; - destAmount?: never; - /** @description The side of the order */ - side?: 'SELL'; -}; -/** @description BUY with slippage: destAmount provided, srcAmount auto-computed from deltaPrice.srcAmount */ -type DeltaAmountsBuySlippage = { - /** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5% */ - slippage: number; - /** @description The minimum amount of dest token to receive */ - destAmount: string; - srcAmount?: never; - /** @description The side of the order */ - side?: 'BUY'; -}; -/** @description Explicit amounts, no slippage (backward-compatible) */ -type DeltaAmountsExplicit = { - slippage?: never; - /** @description The amount of src token to swap */ - srcAmount: string; - /** @description The minimum amount of dest token to receive */ - destAmount: string; - /** @description The side of the order. Default is SELL */ - side?: SwapSideUnion; -}; - export type BuildDeltaOrderDataParams = BuildDeltaOrderDataParamsBase & - (DeltaAmountsSellSlippage | DeltaAmountsBuySlippage | DeltaAmountsExplicit); + AmountsWithSlippage; type BuildDeltaOrder = ( buildOrderParams: BuildDeltaOrderDataParams, @@ -128,83 +91,11 @@ export const constructBuildDeltaOrder = ( throw new Error(`Delta is not available on chain ${chainId}`); } - ////// Partner logic ////// - - // externally supplied partner fee data takes precedence - let partnerAddress = options.partnerAddress; - let partnerFeeBps = - options.partnerFeeBps ?? - (options.deltaPrice.partnerFee - ? options.deltaPrice.partnerFee * 100 - : undefined); - let partnerTakesSurplus = options.partnerTakesSurplus; - - // if fee given, takeSurplus is ignored - const feeOrTakeSurplusSupplied = - partnerFeeBps !== undefined || partnerTakesSurplus !== undefined; - - if (partnerAddress === undefined || feeOrTakeSurplusSupplied) { - const partner = options.partner || options.deltaPrice.partner; - if (!partner) { - // if no partner given in options or deltaPrice, default partnerAddress to zero, - // unless supplied explicitly - partnerAddress = partnerAddress ?? ZERO_ADDRESS; - } else { - const partnerFeeResponse = await getPartnerFee( - { partner }, - requestParams - ); - - partnerAddress = partnerAddress ?? partnerFeeResponse.partnerAddress; - // deltaPrice.partnerFee and partnerFeeResponse.partnerFee should be the same, but give priority to externally provided - partnerFeeBps = partnerFeeBps ?? partnerFeeResponse.partnerFee; - partnerTakesSurplus = - partnerTakesSurplus ?? partnerFeeResponse.takeSurplus; - } - } - - partnerFeeBps = partnerFeeBps ?? 0; - partnerTakesSurplus = partnerTakesSurplus ?? false; - - // Resolve srcAmount, destAmount and side. - // When slippage is used, side is inferred from which amount is present. - let srcAmount: string; - let destAmount: string; - - const swapSide: SwapSideUnion = - options.slippage !== undefined - ? options.srcAmount !== undefined - ? SwapSide.SELL - : SwapSide.BUY - : options.side ?? SwapSide.SELL; - - if (options.slippage !== undefined) { - if (options.srcAmount !== undefined) { - // SELL with slippage: destAmount auto-computed - srcAmount = options.srcAmount; - destAmount = applySlippage({ - amount: options.deltaPrice.destAmount, - slippageBps: options.slippage, - increase: false, - }); - } else { - // BUY with slippage: srcAmount auto-computed - destAmount = options.destAmount; - srcAmount = applySlippage({ - amount: options.deltaPrice.srcAmount, - slippageBps: options.slippage, - increase: true, - }); - } - } else { - srcAmount = options.srcAmount; - destAmount = options.destAmount; - } + const { partnerAddress, partnerFeeBps, partnerTakesSurplus } = + await resolvePartnerFee(options, getPartnerFee, requestParams); - const expectedAmount = - swapSide === SwapSide.SELL - ? options.deltaPrice.destAmount - : options.deltaPrice.srcAmount; + const { srcAmount, destAmount, expectedAmount, swapSide } = + resolveAmounts(options); const input: BuildDeltaOrderDataInput = { owner: options.owner, @@ -240,28 +131,3 @@ export const constructBuildDeltaOrder = ( buildDeltaOrder, }; }; - -type ApplySlippageInput = { - amount: string; - slippageBps: number; - increase: boolean; -}; - -function applySlippage({ - amount, - slippageBps, - increase, -}: ApplySlippageInput): string { - assert( - Number.isInteger(slippageBps) && slippageBps >= 0 && slippageBps <= 10_000, - 'slippageBps must be an integer between 0 and 10_000' - ); - - const BPS_BASE = 10_000n; - const amt = BigInt(amount); - const bps = BigInt(slippageBps); - - return increase - ? ((amt * (BPS_BASE + bps)) / BPS_BASE).toString(10) - : ((amt * (BPS_BASE - bps)) / BPS_BASE).toString(10); -} diff --git a/src/methods/delta/buildExternalDeltaOrder.ts b/src/methods/delta/buildExternalDeltaOrder.ts new file mode 100644 index 00000000..693571b6 --- /dev/null +++ b/src/methods/delta/buildExternalDeltaOrder.ts @@ -0,0 +1,124 @@ +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 { AmountsWithSlippage } 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'; + +type BuildExternalDeltaOrderParamsBase = { + /** @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 */ + deadline?: number; + /** @description The nonce of the order */ + nonce?: number | string; + /** @description Optional permit signature for the src token */ + permit?: string; + /** @description Partner string */ + partner?: string; + /** @description partner fee in basis points (bps), 50bps=0.5% */ + partnerFeeBps?: number; + /** @description partner address */ + partnerAddress?: string; + /** @description take surplus */ + partnerTakesSurplus?: boolean; + /** @description A boolean indicating whether the surplus should be capped. True by default */ + capSurplus?: boolean; + /** @description Metadata for the order, hex string */ + metadata?: string; + + /** @description price response received from /delta/prices (getDeltaPrice method) */ + deltaPrice: MarkOptional< + Pick< + DeltaPrice, + 'destAmount' | 'partner' | 'partnerFee' | 'destToken' | 'srcAmount' + >, + 'partner' | 'partnerFee' + >; +}; + +export type BuildExternalDeltaOrderParams = BuildExternalDeltaOrderParamsBase & + AmountsWithSlippage; + +type BuildExternalDeltaOrder = ( + buildOrderParams: BuildExternalDeltaOrderParams, + requestParams?: RequestParameters +) => Promise; + +export type BuildExternalDeltaOrderFunctions = { + /** @description Build External Orders to be posted to Delta API for execution */ + 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 buildExternalDeltaOrder: BuildExternalDeltaOrder = 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: 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); + }; + + return { + buildExternalDeltaOrder, + }; +}; diff --git a/src/methods/delta/getDeltaOrders.ts b/src/methods/delta/getDeltaOrders.ts index 3c9c4146..9bb73d3b 100644 --- a/src/methods/delta/getDeltaOrders.ts +++ b/src/methods/delta/getDeltaOrders.ts @@ -5,9 +5,16 @@ import type { ConstructFetchInput, RequestParameters, } from '../../types'; -import type { DeltaAuction, DeltaAuctionStatus } from './helpers/types'; - -export type DeltaOrderFromAPI = Omit; +import type { + DeltaAuction, + DeltaAuctionOrder, + DeltaAuctionStatus, + ExternalDeltaOrder, +} from './helpers/types'; + +export type DeltaOrderFromAPI = Omit & { + order: DeltaAuctionOrder | ExternalDeltaOrder; +}; export type DeltaOrderFilterByStatus = | DeltaAuctionStatus diff --git a/src/methods/delta/helpers/abi.ts b/src/methods/delta/helpers/abi.ts new file mode 100644 index 00000000..6b310891 --- /dev/null +++ b/src/methods/delta/helpers/abi.ts @@ -0,0 +1,20 @@ +export const PreSignatureModuleAbi = [ + { + inputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + internalType: 'bool', + name: 'preSigned', + type: 'bool', + }, + ], + name: 'setPreSignature', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const; diff --git a/src/methods/delta/helpers/buildDeltaOrderData.ts b/src/methods/delta/helpers/buildDeltaOrderData.ts index c8bfb267..3913c3e7 100644 --- a/src/methods/delta/helpers/buildDeltaOrderData.ts +++ b/src/methods/delta/helpers/buildDeltaOrderData.ts @@ -1,6 +1,7 @@ import { MarkOptional } from 'ts-essentials'; -import { Domain, ZERO_ADDRESS } from '../../common/orders/buildOrderData'; +import { Domain } from '../../common/orders/buildOrderData'; import { Bridge, DeltaAuctionOrder } from './types'; +import { DELTA_DEFAULT_EXPIRY, producePartnerAndFee } from './misc'; // Order(address owner,address beneficiary,address srcToken,address destToken,uint256 srcAmount,uint256 destAmount,uint256 deadline,uint256 nonce,bytes permit, bridge Bridge)"; const SWAP_ORDER_EIP_712_TYPES = { @@ -64,7 +65,7 @@ export function produceDeltaOrderTypedData({ chainId, paraswapDeltaAddress, }: SignDeltaOrderInput): SignableDeltaOrderData { - const typedData = { + return { types: { Order: SWAP_ORDER_EIP_712_TYPES.Order, Bridge: SWAP_ORDER_EIP_712_TYPES.Bridge, @@ -77,8 +78,6 @@ export function produceDeltaOrderTypedData({ }, data: orderInput, }; - - return typedData; } export type DeltaOrderDataInput = MarkOptional< @@ -99,9 +98,6 @@ export type BuildDeltaOrderDataInput = MarkOptional< bridge: Bridge; }; -// default deadline = 1 hour for now (may be changed later) -export const DELTA_DEFAULT_EXPIRY = 60 * 60; // seconds - export function buildDeltaSignableOrderData({ owner, beneficiary = owner, @@ -157,31 +153,3 @@ export function buildDeltaSignableOrderData({ paraswapDeltaAddress, }); } - -type ProducePartnerAndFeeInput = { - partnerFeeBps: number; - partnerAddress: string; - partnerTakesSurplus: boolean; - capSurplus: boolean; -}; - -// fee and address are encoded together -function producePartnerAndFee({ - partnerFeeBps, - partnerAddress, - partnerTakesSurplus, - capSurplus, -}: ProducePartnerAndFeeInput): string { - const capSurplusShifted = BigInt(capSurplus) << BigInt(9); - if (partnerAddress === ZERO_ADDRESS) { - return capSurplusShifted.toString(10); - } else { - const partnerAndFee = - (BigInt(partnerAddress) << BigInt(96)) | - BigInt(partnerFeeBps.toFixed(0)) | - (BigInt(partnerTakesSurplus) << BigInt(8)) | - capSurplusShifted; - - return partnerAndFee.toString(10); - } -} diff --git a/src/methods/delta/helpers/buildExternalOrderData.ts b/src/methods/delta/helpers/buildExternalOrderData.ts new file mode 100644 index 00000000..75d70a32 --- /dev/null +++ b/src/methods/delta/helpers/buildExternalOrderData.ts @@ -0,0 +1,129 @@ +import { MarkOptional } from 'ts-essentials'; +import { Domain } from '../../common/orders/buildOrderData'; +import { ExternalDeltaOrder } from './types'; +import { DELTA_DEFAULT_EXPIRY, producePartnerAndFee } from './misc'; + +const EXTERNAL_ORDER_EIP_712_TYPES = { + ExternalOrder: [ + { name: 'owner', type: 'address' }, + { name: 'handler', 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: 'data', type: 'bytes' }, + ], +}; + +export type SignableExternalOrderData = { + types: { + ExternalOrder: typeof EXTERNAL_ORDER_EIP_712_TYPES.ExternalOrder; + }; + domain: Domain; + data: ExternalDeltaOrder; +}; + +type SignExternalOrderInput = { + orderInput: ExternalDeltaOrder; + paraswapDeltaAddress: string; + chainId: number; +}; + +export function produceExternalOrderTypedData({ + orderInput, + chainId, + paraswapDeltaAddress, +}: SignExternalOrderInput): SignableExternalOrderData { + return { + types: { + ExternalOrder: EXTERNAL_ORDER_EIP_712_TYPES.ExternalOrder, + }, + domain: { + name: 'Portikus', + version: '2.0.0', + chainId, + verifyingContract: paraswapDeltaAddress, + }, + data: orderInput, + }; +} + +export type ExternalOrderDataInput = MarkOptional< + Omit, + 'deadline' | 'nonce' | 'permit' +>; + +export type BuildExternalOrderDataInput = MarkOptional< + ExternalOrderDataInput, + 'metadata' +> & { + partnerAddress: string; + paraswapDeltaAddress: string; + partnerFeeBps: number; + partnerTakesSurplus?: boolean; + capSurplus?: boolean; + chainId: number; +}; + +export function buildExternalOrderSignableData({ + owner, + handler, + + srcToken, + destToken, + srcAmount, + destAmount, + expectedAmount, + + deadline = Math.floor(Date.now() / 1000 + DELTA_DEFAULT_EXPIRY), + nonce = Date.now().toString(10), + + permit = '0x', + + kind, + metadata = '0x', + data, + + partnerAddress, + partnerFeeBps, + partnerTakesSurplus = false, + capSurplus = true, + + chainId, + paraswapDeltaAddress, +}: BuildExternalOrderDataInput): SignableExternalOrderData { + const orderInput: ExternalDeltaOrder = { + owner, + handler, + srcToken, + destToken, + srcAmount, + destAmount, + expectedAmount, + deadline, + nonce, + permit, + partnerAndFee: producePartnerAndFee({ + partnerFeeBps, + partnerAddress, + partnerTakesSurplus, + capSurplus, + }), + kind, + metadata, + data, + }; + + return produceExternalOrderTypedData({ + orderInput, + chainId, + paraswapDeltaAddress, + }); +} diff --git a/src/methods/delta/helpers/misc.ts b/src/methods/delta/helpers/misc.ts index 472bb7f4..34adaf5f 100644 --- a/src/methods/delta/helpers/misc.ts +++ b/src/methods/delta/helpers/misc.ts @@ -1,4 +1,179 @@ +import { ZERO_ADDRESS } from '../../common/orders/buildOrderData'; import type { SignableDeltaOrderData } from './buildDeltaOrderData'; +import type { SignableExternalOrderData } from './buildExternalOrderData'; +import type { GetPartnerFeeFunctions } from '../getPartnerFee'; +import type { RequestParameters } from '../../../types'; +import type { AmountsWithSlippage, SwapSideUnion } from './types'; +import { SwapSide } from '../../../constants'; +import { assert } from 'ts-essentials'; + +// default deadline = 1 hour for now (may be changed later) +export const DELTA_DEFAULT_EXPIRY = 60 * 60; // seconds + +type ProducePartnerAndFeeInput = { + partnerFeeBps: number; + partnerAddress: string; + partnerTakesSurplus: boolean; + capSurplus: boolean; +}; + +// fee and address are encoded together +export function producePartnerAndFee({ + partnerFeeBps, + partnerAddress, + partnerTakesSurplus, + capSurplus, +}: ProducePartnerAndFeeInput): string { + const capSurplusShifted = BigInt(capSurplus) << BigInt(9); + if (partnerAddress === ZERO_ADDRESS) { + return capSurplusShifted.toString(10); + } else { + const partnerAndFee = + (BigInt(partnerAddress) << BigInt(96)) | + BigInt(partnerFeeBps.toFixed(0)) | + (BigInt(partnerTakesSurplus) << BigInt(8)) | + capSurplusShifted; + + return partnerAndFee.toString(10); + } +} + +type ApplySlippageInput = { + amount: string; + slippageBps: number; + increase: boolean; +}; + +function applySlippage({ + amount, + slippageBps, + increase, +}: ApplySlippageInput): string { + assert( + Number.isInteger(slippageBps) && slippageBps >= 0 && slippageBps <= 10_000, + 'slippageBps must be an integer between 0 and 10_000' + ); + + const BPS_BASE = 10_000n; + const amt = BigInt(amount); + const bps = BigInt(slippageBps); + + return increase + ? ((amt * (BPS_BASE + bps)) / BPS_BASE).toString(10) + : ((amt * (BPS_BASE - bps)) / BPS_BASE).toString(10); +} + +export type ResolvePartnerFeeInput = { + partnerAddress?: string; + partnerFeeBps?: number; + partnerTakesSurplus?: boolean; + partner?: string; + deltaPrice: { partner?: string; partnerFee?: number }; +}; + +export type ResolvedPartnerFee = { + partnerAddress: string; + partnerFeeBps: number; + partnerTakesSurplus: boolean; +}; + +export async function resolvePartnerFee( + options: ResolvePartnerFeeInput, + getPartnerFee: GetPartnerFeeFunctions['getPartnerFee'], + requestParams?: RequestParameters +): Promise { + // externally supplied partner fee data takes precedence + let partnerAddress = options.partnerAddress; + let partnerFeeBps = + options.partnerFeeBps ?? + (options.deltaPrice.partnerFee + ? options.deltaPrice.partnerFee * 100 + : undefined); + let partnerTakesSurplus = options.partnerTakesSurplus; + + // if fee given, takeSurplus is ignored + const feeOrTakeSurplusSupplied = + partnerFeeBps !== undefined || partnerTakesSurplus !== undefined; + + if (partnerAddress === undefined || feeOrTakeSurplusSupplied) { + const partner = options.partner || options.deltaPrice.partner; + if (!partner) { + // if no partner given in options or deltaPrice, default partnerAddress to zero, + // unless supplied explicitly + partnerAddress = partnerAddress ?? ZERO_ADDRESS; + } else { + const partnerFeeResponse = await getPartnerFee( + { partner }, + requestParams + ); + + partnerAddress = partnerAddress ?? partnerFeeResponse.partnerAddress; + // deltaPrice.partnerFee and partnerFeeResponse.partnerFee should be the same, but give priority to externally provided + partnerFeeBps = partnerFeeBps ?? partnerFeeResponse.partnerFee; + partnerTakesSurplus = + partnerTakesSurplus ?? partnerFeeResponse.takeSurplus; + } + } + + return { + partnerAddress: partnerAddress!, + partnerFeeBps: partnerFeeBps ?? 0, + partnerTakesSurplus: partnerTakesSurplus ?? false, + }; +} + +export type ResolveAmountsInput = AmountsWithSlippage & { + deltaPrice: { destAmount: string; srcAmount: string }; +}; + +export type ResolvedAmounts = { + srcAmount: string; + destAmount: string; + expectedAmount: string; + swapSide: SwapSideUnion; +}; + +export function resolveAmounts(options: ResolveAmountsInput): ResolvedAmounts { + let srcAmount: string; + let destAmount: string; + + const swapSide: SwapSideUnion = + options.slippage !== undefined + ? options.srcAmount !== undefined + ? SwapSide.SELL + : SwapSide.BUY + : options.side ?? SwapSide.SELL; + + if (options.slippage !== undefined) { + if (options.srcAmount !== undefined) { + // SELL with slippage: destAmount auto-computed + srcAmount = options.srcAmount; + destAmount = applySlippage({ + amount: options.deltaPrice.destAmount, + slippageBps: options.slippage, + increase: false, + }); + } else { + // BUY with slippage: srcAmount auto-computed + destAmount = options.destAmount; + srcAmount = applySlippage({ + amount: options.deltaPrice.srcAmount, + slippageBps: options.slippage, + increase: true, + }); + } + } else { + srcAmount = options.srcAmount; + destAmount = options.destAmount; + } + + const expectedAmount = + swapSide === SwapSide.SELL + ? options.deltaPrice.destAmount + : options.deltaPrice.srcAmount; + + return { srcAmount, destAmount, expectedAmount, swapSide }; +} export function sanitizeDeltaOrderData({ owner, @@ -34,3 +209,38 @@ export function sanitizeDeltaOrderData({ metadata, }; } + +export function sanitizeExternalOrderData({ + owner, + handler, + srcToken, + destToken, + srcAmount, + destAmount, + expectedAmount, + deadline, + nonce, + permit, + partnerAndFee, + kind, + metadata, + data, +}: SignableExternalOrderData['data'] & + Record): SignableExternalOrderData['data'] { + return { + owner, + handler, + srcToken, + destToken, + srcAmount, + destAmount, + expectedAmount, + deadline, + nonce, + permit, + partnerAndFee, + kind, + metadata, + data, + }; +} diff --git a/src/methods/delta/helpers/types.ts b/src/methods/delta/helpers/types.ts index 1feeffbd..3a2e1d12 100644 --- a/src/methods/delta/helpers/types.ts +++ b/src/methods/delta/helpers/types.ts @@ -1,5 +1,44 @@ +import type { EnumerateLiteral } from '../../../types'; import { SwapSide } from '../../../constants'; +export type SwapSideUnion = EnumerateLiteral; + +/** @description SELL with slippage: srcAmount provided, destAmount auto-computed from deltaPrice.destAmount */ +export type AmountsSellSlippage = { + /** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5% */ + slippage: number; + /** @description The amount of src token to swap */ + srcAmount: string; + destAmount?: never; + /** @description The side of the order */ + side?: 'SELL'; +}; +/** @description BUY with slippage: destAmount provided, srcAmount auto-computed from deltaPrice.srcAmount */ +export type AmountsBuySlippage = { + /** @description Slippage in basis points (bps). 10000 = 100%, 50 = 0.5% */ + slippage: number; + /** @description The minimum amount of dest token to receive */ + destAmount: string; + srcAmount?: never; + /** @description The side of the order */ + side?: 'BUY'; +}; +/** @description Explicit amounts, no slippage (backward-compatible) */ +export type AmountsExplicit = { + slippage?: never; + /** @description The amount of src token to swap */ + srcAmount: string; + /** @description The minimum amount of dest token to receive */ + destAmount: string; + /** @description The side of the order. Default is SELL */ + side?: SwapSideUnion; +}; + +export type AmountsWithSlippage = + | AmountsSellSlippage + | AmountsBuySlippage + | AmountsExplicit; + enum OrderKind { Sell = 0, Buy = 1, @@ -52,6 +91,37 @@ export type Bridge = { protocolData: string; // Hex string }; +export type ExternalDeltaOrder = { + /** @description The address of the order owner */ + owner: string; + /** @description The address of the external handler contract */ + handler: 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 kind of the order */ + kind: OrderKind; + /** @description Metadata for the order, hex string */ + metadata: string; + /** @description The deadline for the order */ + deadline: number; + /** @description The nonce of the order */ + nonce: string; + /** @description Optional permit signature for the src token */ + permit: string; + /** @description Encoded partner address, fee bps, and flags for the order */ + partnerAndFee: string; + /** @description Protocol-specific encoded bytes for the external handler */ + data: string; +}; + export type DeltaAuctionStatus = | 'NOT_STARTED' | 'AWAITING_PRE_SIGNATURE' diff --git a/src/methods/delta/index.ts b/src/methods/delta/index.ts index e18ebc63..5bc75be2 100644 --- a/src/methods/delta/index.ts +++ b/src/methods/delta/index.ts @@ -54,6 +54,25 @@ import { DeltaTokenModuleFunctions, constructDeltaTokenModule, } from './deltaTokenModule'; +import { + BuildExternalDeltaOrderParams, + BuildExternalDeltaOrderFunctions, + constructBuildExternalDeltaOrder, +} from './buildExternalDeltaOrder'; +import { + constructSignExternalDeltaOrder, + SignExternalDeltaOrderFunctions, +} from './signExternalDeltaOrder'; +import { + constructPostExternalDeltaOrder, + ExternalDeltaOrderApiResponse, + PostExternalDeltaOrderFunctions, + ExternalDeltaOrderToPost, +} from './postExternalDeltaOrder'; +import { + constructPreSignExternalDeltaOrder, + PreSignExternalDeltaOrderFunctions, +} from './preSignExternalDeltaOrder'; export type SubmitDeltaOrderParams = BuildDeltaOrderDataParams & { /** @description designates the Order as being able to be partially filled, as opposed to fill-or-kill */ @@ -99,6 +118,51 @@ export const constructSubmitDeltaOrder = ( 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; + +type SubmitExternalDeltaOrder = ( + orderParams: SubmitExternalDeltaOrderParams +) => Promise; + +export type SubmitExternalDeltaOrderFuncs = { + submitExternalDeltaOrder: SubmitExternalDeltaOrder; +}; + +export const constructSubmitExternalDeltaOrder = ( + options: ConstructProviderFetchInput +): SubmitExternalDeltaOrderFuncs => { + const { buildExternalDeltaOrder } = constructBuildExternalDeltaOrder(options); + const { signExternalDeltaOrder } = constructSignExternalDeltaOrder(options); + const { postExternalDeltaOrder } = constructPostExternalDeltaOrder(options); + + const submitExternalDeltaOrder: SubmitExternalDeltaOrder = async ( + orderParams + ) => { + const orderData = await buildExternalDeltaOrder(orderParams); + const signature = await signExternalDeltaOrder(orderData); + + const response = await postExternalDeltaOrder({ + signature, + partner: orderParams.partner, + order: orderData.data, + partiallyFillable: orderParams.partiallyFillable, + referrerAddress: orderParams.referrerAddress, + type: orderParams.type, + includeAgents: orderParams.includeAgents, + excludeAgents: orderParams.excludeAgents, + }); + + return response; + }; + + return { submitExternalDeltaOrder }; +}; + export type DeltaOrderHandlers = SubmitDeltaOrderFuncs & ApproveTokenForDeltaFunctions & BuildDeltaOrderFunctions & @@ -112,7 +176,12 @@ export type DeltaOrderHandlers = SubmitDeltaOrderFuncs & SignDeltaOrderFunctions & PreSignDeltaOrderFunctions & CancelDeltaOrderFunctions & - DeltaTokenModuleFunctions; + DeltaTokenModuleFunctions & + SubmitExternalDeltaOrderFuncs & + BuildExternalDeltaOrderFunctions & + SignExternalDeltaOrderFunctions & + PostExternalDeltaOrderFunctions & + PreSignExternalDeltaOrderFunctions; /** @description construct SDK with every Delta Order-related method, fetching from API and Order signing */ export const constructAllDeltaOrdersHandlers = ( @@ -142,6 +211,13 @@ export const constructAllDeltaOrdersHandlers = ( const deltaTokenModule = constructDeltaTokenModule(options); + const externalDeltaOrdersSubmit = constructSubmitExternalDeltaOrder(options); + const externalDeltaOrdersBuild = constructBuildExternalDeltaOrder(options); + const externalDeltaOrdersSign = constructSignExternalDeltaOrder(options); + const externalDeltaOrdersPost = constructPostExternalDeltaOrder(options); + const externalDeltaOrdersPreSign = + constructPreSignExternalDeltaOrder(options); + return { ...deltaOrdersGetters, ...deltaOrdersContractGetter, @@ -157,5 +233,10 @@ export const constructAllDeltaOrdersHandlers = ( ...deltaOrdersPost, ...deltaOrdersCancel, ...deltaTokenModule, + ...externalDeltaOrdersSubmit, + ...externalDeltaOrdersBuild, + ...externalDeltaOrdersSign, + ...externalDeltaOrdersPost, + ...externalDeltaOrdersPreSign, }; }; diff --git a/src/methods/delta/postExternalDeltaOrder.ts b/src/methods/delta/postExternalDeltaOrder.ts new file mode 100644 index 00000000..5635d522 --- /dev/null +++ b/src/methods/delta/postExternalDeltaOrder.ts @@ -0,0 +1,70 @@ +import { API_URL } from '../../constants'; +import type { ConstructFetchInput, RequestParameters } from '../../types'; +import { DeltaAuction, ExternalDeltaOrder } from './helpers/types'; + +export type ExternalDeltaOrderApiResponse = Omit< + DeltaAuction, + 'transactions' | 'order' +> & { + order: ExternalDeltaOrder; + orderVersion: string; + deltaGasOverhead: number; + type: 'MARKET' | 'LIMIT'; +}; + +export type ExternalDeltaOrderToPost = { + /** @description Partner string */ + partner?: string; + /** @description Referrer address */ + referrerAddress?: string; + order: ExternalDeltaOrder; + /** @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 */ + partiallyFillable?: boolean; + + /** @description Type of the order. MARKET or LIMIT. Default is MARKET */ + type?: 'MARKET' | 'LIMIT'; + + includeAgents?: string[]; + excludeAgents?: string[]; +}; + +export type PostExternalDeltaOrderParams = Omit< + ExternalDeltaOrderToPost, + 'chainId' +>; + +type PostExternalDeltaOrder = ( + postData: PostExternalDeltaOrderParams, + requestParams?: RequestParameters +) => Promise; + +export type PostExternalDeltaOrderFunctions = { + postExternalDeltaOrder: PostExternalDeltaOrder; +}; + +export const constructPostExternalDeltaOrder = ({ + apiURL = API_URL, + chainId, + fetcher, +}: ConstructFetchInput): PostExternalDeltaOrderFunctions => { + const postOrderUrl = `${apiURL}/delta/orders` as const; + + const postExternalDeltaOrder: PostExternalDeltaOrder = ( + postData, + requestParams + ) => { + const deltaOrderToPost: ExternalDeltaOrderToPost = { ...postData, chainId }; + + return fetcher({ + url: postOrderUrl, + method: 'POST', + data: deltaOrderToPost, + requestParams, + }); + }; + + return { postExternalDeltaOrder }; +}; diff --git a/src/methods/delta/preSignDeltaOrder.ts b/src/methods/delta/preSignDeltaOrder.ts index 8109ea9c..ab511dbe 100644 --- a/src/methods/delta/preSignDeltaOrder.ts +++ b/src/methods/delta/preSignDeltaOrder.ts @@ -9,6 +9,7 @@ import { SignableDeltaOrderData, } from './helpers/buildDeltaOrderData'; import { sanitizeDeltaOrderData } from './helpers/misc'; +import { PreSignatureModuleAbi } from './helpers/abi'; import type { ExtractAbiMethodNames } from '../../helpers/misc'; import { findPrimaryType } from '../../helpers/providers/helpers'; import { constructGetDeltaContract } from './getDeltaContract'; @@ -42,27 +43,6 @@ export type PreSignDeltaOrderFunctions = { preSignDeltaOrder: PreSignDeltaOrder; }; -const PreSignatureModuleAbi = [ - { - inputs: [ - { - internalType: 'bytes32', - name: 'orderHash', - type: 'bytes32', - }, - { - internalType: 'bool', - name: 'preSigned', - type: 'bool', - }, - ], - name: 'setPreSignature', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const; - type AvailableMethods = ExtractAbiMethodNames; // returns whatever `contractCaller` returns diff --git a/src/methods/delta/preSignExternalDeltaOrder.ts b/src/methods/delta/preSignExternalDeltaOrder.ts new file mode 100644 index 00000000..4095aa2a --- /dev/null +++ b/src/methods/delta/preSignExternalDeltaOrder.ts @@ -0,0 +1,141 @@ +import { hashTypedData } from 'viem/utils'; +import type { + ConstructProviderFetchInput, + RequestParameters, + TxSendOverrides, +} from '../../types'; +import { + produceExternalOrderTypedData, + SignableExternalOrderData, +} from './helpers/buildExternalOrderData'; +import { sanitizeExternalOrderData } from './helpers/misc'; +import { PreSignatureModuleAbi } from './helpers/abi'; +import type { ExtractAbiMethodNames } from '../../helpers/misc'; +import { findPrimaryType } from '../../helpers/providers/helpers'; +import { constructGetDeltaContract } from './getDeltaContract'; +import type { ExternalDeltaOrder } from './helpers/types'; + +type HashExternalDeltaOrderTypedData = ( + signableOrderData: SignableExternalOrderData +) => string; + +type HashExternalDeltaOrder = ( + orderData: ExternalDeltaOrder, + requestParams?: RequestParameters +) => Promise; + +export type SetExternalDeltaOrderPreSignature = ( + orderHash: string, + overrides?: TxSendOverrides, + requestParams?: RequestParameters +) => Promise; + +export type PreSignExternalDeltaOrder = ( + signableOrderData: SignableExternalOrderData, + overrides?: TxSendOverrides, + requestParams?: RequestParameters +) => Promise; + +export type PreSignExternalDeltaOrderFunctions = { + hashExternalDeltaOrderTypedData: HashExternalDeltaOrderTypedData; + hashExternalDeltaOrder: HashExternalDeltaOrder; + setExternalDeltaOrderPreSignature: SetExternalDeltaOrderPreSignature; + preSignExternalDeltaOrder: PreSignExternalDeltaOrder; +}; + +type AvailableMethods = ExtractAbiMethodNames; + +// returns whatever `contractCaller` returns +// to allow for better versatility +export const constructPreSignExternalDeltaOrder = ( + options: ConstructProviderFetchInput +): PreSignExternalDeltaOrderFunctions => { + const hashExternalDeltaOrderTypedData: HashExternalDeltaOrderTypedData = ( + typedData + ) => { + // types allow to pass OrderData & extra_stuff, but tx will break like that + const typedDataOnly: SignableExternalOrderData = { + ...typedData, + data: sanitizeExternalOrderData(typedData.data), + }; + + const orderHash = produceExternalOrderHash(typedDataOnly); + + return orderHash; + }; + // cached internally + const { getDeltaContract } = constructGetDeltaContract(options); + + const hashExternalDeltaOrder: HashExternalDeltaOrder = async ( + orderData, + requestParams + ) => { + const ParaswapDelta = await getDeltaContract(requestParams); + if (!ParaswapDelta) { + throw new Error(`Delta is not available on chain ${options.chainId}`); + } + + const typedData = produceExternalOrderTypedData({ + orderInput: orderData, + chainId: options.chainId, + paraswapDeltaAddress: ParaswapDelta, + }); + return hashExternalDeltaOrderTypedData(typedData); + }; + + const setExternalDeltaOrderPreSignature: SetExternalDeltaOrderPreSignature< + T + > = async (orderHash, 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: PreSignatureModuleAbi, + contractMethod: 'setPreSignature', + args: [orderHash, true], + overrides, + }); + + return res; + }; + + const preSignExternalDeltaOrder: PreSignExternalDeltaOrder = async ( + signableOrderData, + overrides = {}, + requestParams + ) => { + const orderHash = hashExternalDeltaOrderTypedData(signableOrderData); + const res = await setExternalDeltaOrderPreSignature( + orderHash, + overrides, + requestParams + ); + return res; + }; + + return { + hashExternalDeltaOrderTypedData, + hashExternalDeltaOrder, + setExternalDeltaOrderPreSignature, + preSignExternalDeltaOrder, + }; +}; + +export function produceExternalOrderHash( + typedData: SignableExternalOrderData +): string { + return hashTypedData({ + domain: { + name: typedData.domain.name, + version: typedData.domain.version, + chainId: typedData.domain.chainId, + verifyingContract: typedData.domain.verifyingContract as `0x${string}`, + }, + types: typedData.types, + primaryType: findPrimaryType(typedData.types), + message: typedData.data, + }); +} diff --git a/src/methods/delta/signExternalDeltaOrder.ts b/src/methods/delta/signExternalDeltaOrder.ts new file mode 100644 index 00000000..2fc4d6e1 --- /dev/null +++ b/src/methods/delta/signExternalDeltaOrder.ts @@ -0,0 +1,35 @@ +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/sdk/partial.ts b/src/sdk/partial.ts index 437e95dd..29e6f7ba 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 { PreSignExternalDeltaOrderFunctions } from '../methods/delta/preSignExternalDeltaOrder'; import type { DeltaTokenModuleFunctions } from '../methods/delta/deltaTokenModule'; import { API_URL, DEFAULT_VERSION } from '../constants'; @@ -54,6 +55,7 @@ type InferWithTxResponse< ApproveTokenForNFTOrderFunctions, ApproveTokenForDeltaFunctions, PreSignDeltaOrderFunctions, + PreSignExternalDeltaOrderFunctions, DeltaTokenModuleFunctions ] // then merge IntersectionOfReturns with them recursively