diff --git a/package.json b/package.json index c7032a8..f0811b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.22.0-rc12", + "version": "0.22.0-rc13", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Unit/Exchange/swapLimit.ts b/src/Unit/Exchange/swapLimit.ts index 26b3049..32c773b 100644 --- a/src/Unit/Exchange/swapLimit.ts +++ b/src/Unit/Exchange/swapLimit.ts @@ -9,10 +9,10 @@ import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT -} from '../../constants/index.js'; +} from '../../constants'; import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js'; import { calculateFeeInFeeAsset, denormalizeNumber, normalizeNumber } from '../../utils/index.js'; -import { signOrder } from '../../crypt/index.js'; +import { signOrder } from '../../crypt'; import type orderSchema from '../../services/Aggregator/schemas/orderSchema.js'; import type { z } from 'zod'; import { simpleFetch } from 'simple-typed-fetch'; diff --git a/src/Unit/Exchange/swapMarket.ts b/src/Unit/Exchange/swapMarket.ts index a5c971b..2cbc3e5 100644 --- a/src/Unit/Exchange/swapMarket.ts +++ b/src/Unit/Exchange/swapMarket.ts @@ -11,7 +11,7 @@ import { } from '../../constants'; import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js'; import { calculateFeeInFeeAsset, denormalizeNumber, normalizeNumber } from '../../utils/index.js'; -import { signOrder } from '../../crypt/index.js'; +import { signOrder } from '../../crypt'; import type orderSchema from '../../services/Aggregator/schemas/orderSchema.js'; import type { z } from 'zod'; import type { SwapLimitParams } from './swapLimit.js'; diff --git a/src/constants/index.ts b/src/constants/index.ts index efe47f0..835464e 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,11 +1,11 @@ export { default as cancelOrderTypes } from './cancelOrderTypes.js'; export { default as orderStatuses } from './orderStatuses.js'; -export { default as orderTypes } from './orderTypes.js'; export { default as subOrderStatuses } from './subOrderStatuses.js'; export { default as networkCodes } from './networkCodes.js'; export { default as exchanges } from './exchanges.js'; export { default as exchangesMap } from './exchangesMap.js'; +export * from './orderTypes'; export * from './chains.js'; export * from './precisions.js'; export * from './gasLimits.js'; diff --git a/src/constants/orderTypes.ts b/src/constants/orderTypes.ts deleted file mode 100644 index 699190f..0000000 --- a/src/constants/orderTypes.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const ORDER_TYPES = { - Order: [ - { name: 'senderAddress', type: 'address' }, - { name: 'matcherAddress', type: 'address' }, - { name: 'baseAsset', type: 'address' }, - { name: 'quoteAsset', type: 'address' }, - { name: 'matcherFeeAsset', type: 'address' }, - { name: 'amount', type: 'uint64' }, - { name: 'price', type: 'uint64' }, - { name: 'matcherFee', type: 'uint64' }, - { name: 'nonce', type: 'uint64' }, - { name: 'expiration', type: 'uint64' }, - { name: 'buySide', type: 'uint8' }, - ], - CrossChainOrder: [ - { name: 'limitOrder', type: 'Order' }, - { name: 'chainId', type: 'uint24' }, - { name: 'secretHash', type: 'bytes32' }, - { name: 'lockOrderExpiration', type: 'uint64' }, - ] -} - -export default ORDER_TYPES; diff --git a/src/constants/orderTypes/crossChainOrderTypes.ts b/src/constants/orderTypes/crossChainOrderTypes.ts new file mode 100644 index 0000000..2c7a1c8 --- /dev/null +++ b/src/constants/orderTypes/crossChainOrderTypes.ts @@ -0,0 +1,11 @@ +import {ORDER_TYPE} from './orderTypes'; + +export const CROSS_CHAIN_ORDER_TYPES = { + Order: ORDER_TYPE, + CrossChainOrder: [ + {name: 'limitOrder', type: 'Order'}, + {name: 'chainId', type: 'uint24'}, + {name: 'secretHash', type: 'bytes32'}, + {name: 'lockOrderExpiration', type: 'uint64'}, + ] +} diff --git a/src/constants/orderTypes/index.ts b/src/constants/orderTypes/index.ts new file mode 100644 index 0000000..8be58c4 --- /dev/null +++ b/src/constants/orderTypes/index.ts @@ -0,0 +1,3 @@ +export * from './crossChainOrderTypes'; +export * from './lockOrderTypes'; +export { ORDER_TYPES } from './orderTypes'; diff --git a/src/constants/lockOrderTypes.ts b/src/constants/orderTypes/lockOrderTypes.ts similarity index 100% rename from src/constants/lockOrderTypes.ts rename to src/constants/orderTypes/lockOrderTypes.ts diff --git a/src/constants/orderTypes/orderTypes.ts b/src/constants/orderTypes/orderTypes.ts new file mode 100644 index 0000000..c265a2d --- /dev/null +++ b/src/constants/orderTypes/orderTypes.ts @@ -0,0 +1,27 @@ +export const ORDER_TYPE = [ + { name: 'senderAddress', type: 'address' }, + { name: 'matcherAddress', type: 'address' }, + { name: 'baseAsset', type: 'address' }, + { name: 'quoteAsset', type: 'address' }, + { name: 'matcherFeeAsset', type: 'address' }, + { name: 'amount', type: 'uint64' }, + { name: 'price', type: 'uint64' }, + { name: 'matcherFee', type: 'uint64' }, + { name: 'nonce', type: 'uint64' }, + { name: 'expiration', type: 'uint64' }, + { name: 'buySide', type: 'uint8' }, +]; + +export const ORDER_TYPES = { + Order: ORDER_TYPE +} + +export const CROSS_CHAIN_ORDER_TYPES = { + Order: ORDER_TYPE, + CrossChainOrder: [ + { name: 'limitOrder', type: 'Order' }, + { name: 'chainId', type: 'uint24' }, + { name: 'secretHash', type: 'bytes32' }, + { name: 'lockOrderExpiration', type: 'uint64' }, + ] +} diff --git a/src/crypt/getDomainData.ts b/src/crypt/getDomainData.ts index d3d1445..ecd361b 100644 --- a/src/crypt/getDomainData.ts +++ b/src/crypt/getDomainData.ts @@ -1,5 +1,5 @@ import type { SupportedChainId } from '../types.js'; -import eip712DomainData from '../config/eip712DomainData.json' assert { type: 'json' }; +import eip712DomainData from '../config/eip712DomainData.json' assert {type: 'json'}; import eip712DomainSchema from '../config/schemas/eip712DomainSchema.js'; const EIP712Domain = eip712DomainSchema.parse(eip712DomainData); @@ -19,7 +19,7 @@ function removeUndefined(obj: Record) { */ const getDomainData = (chainId: SupportedChainId) => ({ ...removeUndefined(EIP712Domain), - chainId: Number(chainId), // check if it broke + chainId: Number(chainId), }); export default getDomainData; diff --git a/src/crypt/hashOrder.ts b/src/crypt/hashOrders/hashCrossChainOrder.ts similarity index 89% rename from src/crypt/hashOrder.ts rename to src/crypt/hashOrders/hashCrossChainOrder.ts index 98a1365..ac074ae 100644 --- a/src/crypt/hashOrder.ts +++ b/src/crypt/hashOrders/hashCrossChainOrder.ts @@ -1,12 +1,12 @@ import { ethers, keccak256 } from 'ethers'; -import type { SupportedChainId, SignedOrder } from '../types.js'; +import type { SupportedChainId, CrossChainOrder } from '../../types'; const ORDER_TYPEHASH = '0xb5132db62dfceb466f2f8aee7a039db36a99772e5a9771d28388a5f9baad7c54'; const CROSS_CHAIN_ORDER_TYPEHASH = '0xc4666edeecc42a94cf6b87f39e1ca967792e6d738224365e54d7d06ec632b05c'; -export function getOrderHash(order: Omit, chainId: SupportedChainId) { +export function getOrderHash(order: CrossChainOrder, chainId: SupportedChainId) { const abiCoder = ethers.AbiCoder.defaultAbiCoder(); // Generate the orderParamsHash diff --git a/src/crypt/hashLockOrder.ts b/src/crypt/hashOrders/hashLockOrder.ts similarity index 83% rename from src/crypt/hashLockOrder.ts rename to src/crypt/hashOrders/hashLockOrder.ts index 71173f8..c3084eb 100644 --- a/src/crypt/hashLockOrder.ts +++ b/src/crypt/hashOrders/hashLockOrder.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import type { LockOrder } from '../types.js'; +import type { LockOrder } from '../../types'; export const hashLockOrder = (order: LockOrder) => ethers.solidityPackedKeccak256( [ @@ -10,11 +10,9 @@ export const hashLockOrder = (order: LockOrder) => ethers.solidityPackedKeccak25 'uint64', 'uint64', 'uint64', - 'string' ], [ '0x03', - order.user, order.sender, order.expiration, order.asset, diff --git a/src/crypt/hashOrders/hashOrder.ts b/src/crypt/hashOrders/hashOrder.ts new file mode 100644 index 0000000..c8abe14 --- /dev/null +++ b/src/crypt/hashOrders/hashOrder.ts @@ -0,0 +1,54 @@ +import { ethers, keccak256 } from 'ethers'; +import type { Order } from '../../types'; + +const ORDER_TYPEHASH = '0xb5132db62dfceb466f2f8aee7a039db36a99772e5a9771d28388a5f9baad7c54' + +export default function getOrderHash(order: Order) { + const abiCoder = ethers.AbiCoder.defaultAbiCoder() + + const { + senderAddress, + matcherAddress, + baseAsset, + quoteAsset, + matcherFeeAsset, + amount, + price, + matcherFee, + nonce, + expiration, + buySide + } = order + const orderBytes = abiCoder.encode( + [ + 'bytes32', + 'address', + 'address', + 'address', + 'address', + 'address', + 'uint64', + 'uint64', + 'uint64', + 'uint64', + 'uint64', + 'uint8' + ], + [ + ORDER_TYPEHASH, + senderAddress, + matcherAddress, + baseAsset, + quoteAsset, + matcherFeeAsset, + amount, + price, + matcherFee, + nonce, + expiration, + buySide + ] + ) + + return keccak256(orderBytes) +} diff --git a/src/crypt/hashOrders/index.ts b/src/crypt/hashOrders/index.ts new file mode 100644 index 0000000..fe8b399 --- /dev/null +++ b/src/crypt/hashOrders/index.ts @@ -0,0 +1,3 @@ +export * from './hashCrossChainOrder'; +export * from './hashOrder'; +export * from './hashLockOrder'; diff --git a/src/crypt/index.ts b/src/crypt/index.ts index 4bf9085..2d0ac7e 100644 --- a/src/crypt/index.ts +++ b/src/crypt/index.ts @@ -1,3 +1,4 @@ export { default as signCancelOrder } from './signCancelOrder.js'; export { signOrder, type SignOrderProps } from './signOrder.js'; +export { signCrossChainOrder, type SignCrossChainOrderProps } from './signCrossChainOrder.js'; export { signLockOrder, type SignLockOrderProps } from './signLockOrder.js'; diff --git a/src/crypt/signCrossChainOrder.ts b/src/crypt/signCrossChainOrder.ts new file mode 100644 index 0000000..ce2f649 --- /dev/null +++ b/src/crypt/signCrossChainOrder.ts @@ -0,0 +1,111 @@ +import { BigNumber } from 'bignumber.js'; +import { ethers, keccak256 } from 'ethers'; +import { INTERNAL_PROTOCOL_PRECISION } from '../constants'; +import { CROSS_CHAIN_ORDER_TYPES } from '../constants/orderTypes/orderTypes'; +import type { Order, SignedCrossChainOrder, SupportedChainId } from '../types.js'; +import normalizeNumber from '../utils/normalizeNumber.js'; +import getDomainData from './getDomainData.js'; +import generateSecret from '../utils/generateSecret'; +import { getOrderHash } from './hashOrders'; + +const DAY = 24 * 60 * 60 * 1000; +const LOCK_ORDER_EXPIRATION = 4 * DAY; +const DEFAULT_EXPIRATION = 29 * DAY; + +export type SignCrossChainOrderProps = { + baseAssetAddress: string + quoteAssetAddress: string + side: 'BUY' | 'SELL' + price: BigNumber.Value + amount: BigNumber.Value + matcherFee: BigNumber.Value + senderAddress: string + matcherAddress: string + serviceFeeAssetAddress: string + signer: ethers.Signer + chainId: SupportedChainId + targetChainId?: SupportedChainId +} + +export const signCrossChainOrder = async ({ + amount, + signer, + side, + baseAssetAddress, + quoteAssetAddress, + serviceFeeAssetAddress, + matcherFee, + matcherAddress, + senderAddress, + targetChainId, + chainId, + price +}: SignCrossChainOrderProps): Promise => { + const nonce = Date.now(); + const expiration = nonce + DEFAULT_EXPIRATION; + const lockOrderExpiration = nonce + LOCK_ORDER_EXPIRATION; + + const order: Order = { + senderAddress, + matcherAddress, + baseAsset: baseAssetAddress, + quoteAsset: quoteAssetAddress, + matcherFeeAsset: serviceFeeAssetAddress, + amount: Number(normalizeNumber( + amount, + INTERNAL_PROTOCOL_PRECISION, + BigNumber.ROUND_FLOOR, + )), + price: Number(normalizeNumber( + price, + INTERNAL_PROTOCOL_PRECISION, + BigNumber.ROUND_FLOOR, + )), + matcherFee: Number(normalizeNumber( + matcherFee, + INTERNAL_PROTOCOL_PRECISION, + BigNumber.ROUND_CEIL, // ROUND_CEIL because we don't want get "not enough fee" error + )), + nonce, + expiration, + buySide: side === 'BUY' ? 1 : 0 + }; + + const secret = generateSecret(); + const secretHash = keccak256(secret); + + const crossChainOrder = { + limitOrder: order, + chainId: Number(chainId), + secretHash, + lockOrderExpiration + } + + const signature = await signer.signTypedData( + getDomainData(chainId), + CROSS_CHAIN_ORDER_TYPES, + crossChainOrder + ); + + // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 + // "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1" + const fixedSignature = ethers.Signature.from(signature).serialized; + + // if (!fixedSignature) throw new Error("Can't sign order"); + const signedOrderWithoutId: Omit = { + ...order, + signature: fixedSignature, + secret, + secretHash, + targetChainId: Number(targetChainId), + lockOrderExpiration + } + const orderHash = getOrderHash(signedOrderWithoutId, chainId); + + const signedCrossChainOrder: SignedCrossChainOrder = { + ...signedOrderWithoutId, + id: orderHash + }; + + return signedCrossChainOrder; +}; diff --git a/src/crypt/signLockOrder.ts b/src/crypt/signLockOrder.ts index 6d75bf4..3ffb34d 100644 --- a/src/crypt/signLockOrder.ts +++ b/src/crypt/signLockOrder.ts @@ -4,8 +4,7 @@ import getDomainData from './getDomainData.js'; import generateSecret from '../utils/generateSecret'; import { BigNumber } from 'bignumber.js'; import normalizeNumber from '../utils/normalizeNumber'; -import { INTERNAL_PROTOCOL_PRECISION } from '../constants'; -import { LOCK_ORDER_TYPES } from '../constants/lockOrderTypes'; +import { INTERNAL_PROTOCOL_PRECISION, LOCK_ORDER_TYPES } from '../constants'; const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days @@ -41,6 +40,7 @@ export const signLockOrder = async ({ BigNumber.ROUND_FLOOR, )), targetChainId: Number(targetChainId), + secret, secretHash }; diff --git a/src/crypt/signOrder.ts b/src/crypt/signOrder.ts index 7b1ca80..e772620 100644 --- a/src/crypt/signOrder.ts +++ b/src/crypt/signOrder.ts @@ -1,16 +1,12 @@ import { BigNumber } from 'bignumber.js'; -import { ethers, keccak256 } from 'ethers'; -import { INTERNAL_PROTOCOL_PRECISION } from '../constants'; -import ORDER_TYPES from '../constants/orderTypes.js'; +import { ethers } from 'ethers'; +import { INTERNAL_PROTOCOL_PRECISION, ORDER_TYPES } from '../constants'; import type { Order, SignedOrder, SupportedChainId } from '../types.js'; import normalizeNumber from '../utils/normalizeNumber.js'; import getDomainData from './getDomainData.js'; -import generateSecret from '../utils/generateSecret'; -import { getOrderHash } from './hashOrder'; +import hashOrder from './hashOrders/hashOrder'; -const DAY = 24 * 60 * 60 * 1000; -const LOCK_ORDER_EXPIRATION = 4 * DAY; -const DEFAULT_EXPIRATION = 29 * DAY; +const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days export type SignOrderProps = { baseAssetAddress: string @@ -24,28 +20,23 @@ export type SignOrderProps = { serviceFeeAssetAddress: string signer: ethers.Signer chainId: SupportedChainId - targetChainId?: SupportedChainId } export const signOrder = async ({ - amount, - signer, - side, + senderAddress, + serviceFeeAssetAddress, baseAssetAddress, quoteAssetAddress, - serviceFeeAssetAddress, matcherFee, matcherAddress, - senderAddress, - targetChainId, chainId, + signer, + side, + amount, price -}: SignOrderProps): Promise => { +}: SignOrderProps) => { const nonce = Date.now(); const expiration = nonce + DEFAULT_EXPIRATION; - const lockOrderExpiration = nonce + LOCK_ORDER_EXPIRATION; - - const isCrossChain = targetChainId === undefined || targetChainId !== chainId; const order: Order = { senderAddress, @@ -70,29 +61,13 @@ export const signOrder = async ({ )), nonce, expiration, - ...(isCrossChain - ? { - targetChainId: Number(targetChainId) - } - : {}), - buySide: side === 'BUY' ? 1 : 0 + buySide: side === 'BUY' ? 1 : 0, }; - const secret = generateSecret(); - const secretHash = keccak256(secret); - - const crossChainOrder = { - limitOrder: order, - chainId: Number(chainId), - secretHash, - lockOrderExpiration - } - - // TODO: change what to show const signature = await signer.signTypedData( getDomainData(chainId), ORDER_TYPES, - crossChainOrder + order, ); // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 @@ -100,25 +75,11 @@ export const signOrder = async ({ const fixedSignature = ethers.Signature.from(signature).serialized; // if (!fixedSignature) throw new Error("Can't sign order"); - const signedOrderWithoutId: Omit = { - ...order, - signature: fixedSignature, - secret, - secretHash, - ...(isCrossChain - ? { - targetChainId: Number(targetChainId) - } - : {}), - lockOrderExpiration - } - const orderHash = getOrderHash(signedOrderWithoutId, chainId); const signedOrder: SignedOrder = { - ...signedOrderWithoutId, - id: orderHash + ...order, + id: hashOrder(order), + signature: fixedSignature, }; return signedOrder; }; - -export default signOrder; diff --git a/src/services/Aggregator/index.ts b/src/services/Aggregator/index.ts index 29edd71..bf72163 100644 --- a/src/services/Aggregator/index.ts +++ b/src/services/Aggregator/index.ts @@ -14,7 +14,8 @@ import type { NetworkShortName, SignedLockOrder, SignedCancelOrderRequest, - SignedOrder + SignedOrder, + SignedCrossChainOrder } from '../../types.js'; import { pairConfigSchema, aggregatedOrderbookSchema, @@ -211,7 +212,7 @@ class Aggregator { ); placeOrder = ( - signedOrder: SignedOrder, + signedOrder: SignedOrder | SignedCrossChainOrder, isCreateInternalOrder: boolean, isReversedOrder?: boolean, partnerId?: string, diff --git a/src/types.ts b/src/types.ts index a47116d..5c6189c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -50,7 +50,9 @@ export type Order = { expiration: number // uint64 buySide: 0 | 1 // uint8, 1=buy, 0=sell } -export type CrossOrder = Order & { + +export type CrossChainOrder = Order & { + secret: string secretHash: string // bytes32 targetChainId: number // uint24 lockOrderExpiration: number // uint64 @@ -62,18 +64,20 @@ export type LockOrder = { asset: string // address(?) amount: number // uint64 targetChainId: number // uint64 + secret: string // bytes32 secretHash: string // bytes32 } type SignedOrderAdditionalProps = { signature: string // bytes - secret?: string - secretHash?: string // bytes32 needWithdraw?: boolean // bool (not supported yet by smart-contract) - lockOrderExpiration?: number } -export type SignedOrder = SignedOrderAdditionalProps & (Order | CrossOrder) & { +export type SignedOrder = SignedOrderAdditionalProps & Order & { + id: string // hash of Order (it's not part of order structure in smart-contract) +} + +export type SignedCrossChainOrder = SignedOrderAdditionalProps & CrossChainOrder & { id: string // hash of Order (it's not part of order structure in smart-contract) }