diff --git a/package-lock.json b/package-lock.json index 6d926606..fda9bd7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "@orionprotocol/sdk", - "version": "0.22.5", + "version": "0.23.0-rc7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@orionprotocol/sdk", - "version": "0.22.5", + "version": "0.23.0-rc7", "hasInstallScript": true, "license": "ISC", "dependencies": { "@babel/runtime": "^7.21.0", - "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@orionprotocol/contracts": "^1.23.9", + "@orionprotocol/contracts": "1.23.9", "@types/lodash.clonedeep": "^4.5.9", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", @@ -27,7 +26,7 @@ "merge-anything": "^5.1.7", "neverthrow": "^6.0.0", "patch-package": "^8.0.0", - "simple-typed-fetch": "0.2.3", + "simple-typed-fetch": "0.2.5", "stream-browserify": "^3.0.0", "tiny-invariant": "^1.3.1", "ts-is-present": "^1.2.2", @@ -2424,7 +2423,10 @@ "node_modules/@orionprotocol/contracts": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.23.9.tgz", - "integrity": "sha512-tx21XokSK8kBYmuzfYrXoDattirm6yaG3dslrOKZTHgGP1wC6c6SbQF69pVCCAnPNGvQCt4lw0I/8fxm9Cx23Q==" + "integrity": "sha512-tx21XokSK8kBYmuzfYrXoDattirm6yaG3dslrOKZTHgGP1wC6c6SbQF69pVCCAnPNGvQCt4lw0I/8fxm9Cx23Q==", + "workspaces": [ + "packages/*" + ] }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -10399,9 +10401,9 @@ "dev": true }, "node_modules/simple-typed-fetch": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/simple-typed-fetch/-/simple-typed-fetch-0.2.3.tgz", - "integrity": "sha512-EXP2mVVsVf4A3+5QGevs8789ztnT6FozsYyMrIrUYhfqtX2V+X9xETHeGXffmgv7YQ0p+GrW7N+5x+b+pBW59Q==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/simple-typed-fetch/-/simple-typed-fetch-0.2.5.tgz", + "integrity": "sha512-T/KKUHKOZgaYVp3dbjE1wEK5cAGmG5N7FNAzP6ZGqLDhzBsRm3Gpt8bO/kowV6bt8duXDWiapYFQibW/8iHp6Q==", "dependencies": { "isomorphic-unfetch": "^4.0.2", "neverthrow": "^6.0.0", @@ -19336,9 +19338,9 @@ "dev": true }, "simple-typed-fetch": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/simple-typed-fetch/-/simple-typed-fetch-0.2.3.tgz", - "integrity": "sha512-EXP2mVVsVf4A3+5QGevs8789ztnT6FozsYyMrIrUYhfqtX2V+X9xETHeGXffmgv7YQ0p+GrW7N+5x+b+pBW59Q==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/simple-typed-fetch/-/simple-typed-fetch-0.2.5.tgz", + "integrity": "sha512-T/KKUHKOZgaYVp3dbjE1wEK5cAGmG5N7FNAzP6ZGqLDhzBsRm3Gpt8bO/kowV6bt8duXDWiapYFQibW/8iHp6Q==", "requires": { "isomorphic-unfetch": "^4.0.2", "neverthrow": "^6.0.0", diff --git a/package.json b/package.json index bd57f9f0..e1efb235 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.22.14", + "version": "0.23.1-rc0", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", @@ -86,9 +86,8 @@ }, "dependencies": { "@babel/runtime": "^7.21.0", - "@ethersproject/abstract-signer": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@orionprotocol/contracts": "^1.23.9", + "@orionprotocol/contracts": "1.23.9", "@types/lodash.clonedeep": "^4.5.9", "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", @@ -102,7 +101,7 @@ "merge-anything": "^5.1.7", "neverthrow": "^6.0.0", "patch-package": "^8.0.0", - "simple-typed-fetch": "0.2.3", + "simple-typed-fetch": "0.2.5", "stream-browserify": "^3.0.0", "tiny-invariant": "^1.3.1", "ts-is-present": "^1.2.2", diff --git a/src/Orion/bridge/swap.ts b/src/Orion/bridge/swap.ts index 4bb83528..b38074a0 100644 --- a/src/Orion/bridge/swap.ts +++ b/src/Orion/bridge/swap.ts @@ -10,7 +10,7 @@ import { LOCKATOMIC_GAS_LIMIT, REDEEMATOMIC_GAS_LIMIT, WITHDRAW_GAS_LIMIT -} from '../../constants/index.js'; +} from '../../constants'; import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js'; import { denormalizeNumber, generateSecret, normalizeNumber, toUpperCase } from '../../utils/index.js'; import type { SupportedChainId } from '../../types.js'; diff --git a/src/Unit/Exchange/swapLimit.ts b/src/Unit/Exchange/swapLimit.ts index 19ae3008..ae19a0e1 100644 --- a/src/Unit/Exchange/swapLimit.ts +++ b/src/Unit/Exchange/swapLimit.ts @@ -5,10 +5,14 @@ import getBalances from '../../utils/getBalances.js'; import BalanceGuard from '../../BalanceGuard.js'; import getAvailableSources from '../../utils/getAvailableFundsSources.js'; import type Unit from '../index.js'; -import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants/index.js'; +import { + INTERNAL_PROTOCOL_PRECISION, + NATIVE_CURRENCY_PRECISION, + SWAP_THROUGH_ORION_POOL_GAS_LIMIT +} 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'; @@ -212,7 +216,7 @@ export default async function swapLimit({ if (options.developer.route === 'pool' && !priceIsBTEMP) { throw new Error( 'CONFLICT: Pool execution is not available for this swap.' + - ' Price is worse than market price. Please unset "route" option or set it to "aggregator"' + ' Price is worse than market price. Please unset "route" option or set it to "aggregator"' ); } route = options.developer.route; @@ -221,17 +225,17 @@ export default async function swapLimit({ if (!priceIsBTEMP) { throw new Error( 'CONFLICT: Pool execution is not available for this swap.' + - ' Price is worse than market price. Please disable "poolOnly" option' + ' Price is worse than market price. Please disable "poolOnly" option' ); } options.logger?.('Swap is through pool (because "poolOnly" option is true)'); route = 'pool'; } else if ( poolExchangesList.length > 0 && - swapExchanges.length === 1 && - firstSwapExchange !== undefined && - poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) && - priceIsBTEMP + swapExchanges.length === 1 && + firstSwapExchange !== undefined && + poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) && + priceIsBTEMP ) { options?.logger?.(`Swap is through pool [via ${firstSwapExchange}] (detected by "exchanges" field)`); route = 'pool'; @@ -439,19 +443,19 @@ export default async function swapLimit({ await balanceGuard.check(options?.autoApprove); - const signedOrder = await signOrder( + const signedOrder = await signOrder({ baseAssetAddress, quoteAssetAddress, - swapInfo.orderInfo.side, - safePriceWithAppliedPrecision.toString(), - swapInfo.orderInfo.amount, - totalFeeInFeeAsset, - walletAddress, + side: swapInfo.orderInfo.side, + price: safePriceWithAppliedPrecision.toString(), + amount: swapInfo.orderInfo.amount, + matcherFee: totalFeeInFeeAsset, + senderAddress: walletAddress, matcherAddress, - feeAssetAddress, + serviceFeeAssetAddress: feeAssetAddress, signer, chainId, - ); + }); const orderIsOk = await exchangeContract.validateOrder(signedOrder); if (!orderIsOk) throw new Error('Order is not valid'); diff --git a/src/Unit/Exchange/swapMarket.ts b/src/Unit/Exchange/swapMarket.ts index 934e2bb6..71d18d13 100644 --- a/src/Unit/Exchange/swapMarket.ts +++ b/src/Unit/Exchange/swapMarket.ts @@ -4,10 +4,14 @@ import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index. import getBalances from '../../utils/getBalances.js'; import BalanceGuard from '../../BalanceGuard.js'; import getAvailableSources from '../../utils/getAvailableFundsSources.js'; -import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants/index.js'; +import { + INTERNAL_PROTOCOL_PRECISION, + NATIVE_CURRENCY_PRECISION, + SWAP_THROUGH_ORION_POOL_GAS_LIMIT +} 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'; @@ -178,9 +182,9 @@ export default async function swapMarket({ route = 'pool'; } else if ( poolExchangesList.length > 0 && - swapExchanges.length === 1 && - firstSwapExchange !== undefined && - poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) + swapExchanges.length === 1 && + firstSwapExchange !== undefined && + poolExchangesList.some((poolExchange) => poolExchange === firstSwapExchange) ) { options?.logger?.(`Swap is through pool [via ${firstSwapExchange}] (detected by "exchanges" field)`); route = 'pool'; @@ -398,19 +402,19 @@ export default async function swapMarket({ await balanceGuard.check(options?.autoApprove); - const signedOrder = await signOrder( + const signedOrder = await signOrder({ baseAssetAddress, quoteAssetAddress, - swapInfo.orderInfo.side, - safePriceWithAppliedPrecision.toString(), - swapInfo.orderInfo.amount, - totalFeeInFeeAsset, - walletAddress, + side: swapInfo.orderInfo.side, + price: safePriceWithAppliedPrecision.toString(), + amount: swapInfo.orderInfo.amount, + matcherFee: totalFeeInFeeAsset, + senderAddress: walletAddress, matcherAddress, - feeAssetAddress, + serviceFeeAssetAddress: feeAssetAddress, signer, chainId, - ); + }); const orderIsOk = await exchangeContract.validateOrder(signedOrder); if (!orderIsOk) throw new Error('Order is not valid'); diff --git a/src/constants/index.ts b/src/constants/index.ts index efe47f0e..835464e7 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/orderStatuses.ts b/src/constants/orderStatuses.ts index ce853a70..2cfb894a 100644 --- a/src/constants/orderStatuses.ts +++ b/src/constants/orderStatuses.ts @@ -3,6 +3,8 @@ import subOrderStatuses from './subOrderStatuses.js'; const orderStatuses = [ ...subOrderStatuses, 'ROUTING', // order got sub orders, but not all of them have status ACCEPTED + 'TRANSFER', // TX_PENDING > TRANSFER > SETTLED + 'REFUNDED' // TX_PENDING > TRANSFER > CANCELED > REFUNDED ] as const; export default orderStatuses; diff --git a/src/constants/orderTypes.ts b/src/constants/orderTypes.ts deleted file mode 100644 index cf80f602..00000000 --- a/src/constants/orderTypes.ts +++ /dev/null @@ -1,17 +0,0 @@ -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' }, - ], -}; - -export default ORDER_TYPES; diff --git a/src/constants/orderTypes/crossChainOrderTypes.ts b/src/constants/orderTypes/crossChainOrderTypes.ts new file mode 100644 index 00000000..2c7a1c86 --- /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 00000000..8be58c46 --- /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/orderTypes/lockOrderTypes.ts b/src/constants/orderTypes/lockOrderTypes.ts new file mode 100644 index 00000000..2d009181 --- /dev/null +++ b/src/constants/orderTypes/lockOrderTypes.ts @@ -0,0 +1,10 @@ +export const LOCK_ORDER_TYPES = { + LockOrder: [ + { name: 'sender', type: 'address' }, + { name: 'expiration', type: 'uint64' }, + { name: 'asset', type: 'address' }, + { name: 'amount', type: 'uint64' }, + { name: 'targetChainId', type: 'uint24' }, + { name: 'secretHash', type: 'bytes32' }, + ], +}; diff --git a/src/constants/orderTypes/orderTypes.ts b/src/constants/orderTypes/orderTypes.ts new file mode 100644 index 00000000..1751c276 --- /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: 'targetChainId', type: 'uint24' }, + { name: 'secretHash', type: 'bytes32' }, + { name: 'lockOrderExpiration', type: 'uint64' }, + ] +} diff --git a/src/crypt/getDomainData.ts b/src/crypt/getDomainData.ts index 2b077428..ecd361be 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, + chainId: Number(chainId), }); export default getDomainData; diff --git a/src/crypt/hashOrder.ts b/src/crypt/hashOrder.ts deleted file mode 100644 index c62f97d0..00000000 --- a/src/crypt/hashOrder.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ethers, keccak256 } from 'ethers'; -import type { Order } from '../types.js'; - -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) -} \ No newline at end of file diff --git a/src/crypt/hashOrders/hashCrossChainOrder.ts b/src/crypt/hashOrders/hashCrossChainOrder.ts new file mode 100644 index 00000000..e089f087 --- /dev/null +++ b/src/crypt/hashOrders/hashCrossChainOrder.ts @@ -0,0 +1,59 @@ +import { ethers, keccak256 } from 'ethers'; +import type { SupportedChainId, CrossChainOrder } from '../../types'; + +const ORDER_TYPEHASH = + '0xb5132db62dfceb466f2f8aee7a039db36a99772e5a9771d28388a5f9baad7c54'; +const CROSS_CHAIN_ORDER_TYPEHASH = + '0xb0edab98a08b4f5ce4f349d7cb1622bde999112acf1ac4a30cc9f394bd7809a6'; + +export function getOrderHash(order: CrossChainOrder, targetChainId: SupportedChainId) { + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + + // Generate the orderParamsHash + const limitOrderHash = keccak256( + abiCoder.encode( + [ + 'bytes32', + 'address', + 'address', + 'address', + 'address', + 'address', + 'uint64', + 'uint64', + 'uint64', + 'uint64', + 'uint64', + 'uint8', + ], + [ + ORDER_TYPEHASH, + order.senderAddress, + order.matcherAddress, + order.baseAsset, + order.quoteAsset, + order.matcherFeeAsset, + order.amount, + order.price, + order.matcherFee, + order.nonce, + order.expiration, + order.buySide, + ] + ) + ); + const orderHash = keccak256( + abiCoder.encode( + ['bytes32', 'bytes32', 'uint24', 'bytes32', 'uint64'], + [ + CROSS_CHAIN_ORDER_TYPEHASH, + limitOrderHash, + Number(targetChainId), + order.secretHash, + order.lockOrderExpiration, + ] + ) + ); + + return orderHash +} diff --git a/src/crypt/hashOrders/hashLockOrder.ts b/src/crypt/hashOrders/hashLockOrder.ts new file mode 100644 index 00000000..c3084eb1 --- /dev/null +++ b/src/crypt/hashOrders/hashLockOrder.ts @@ -0,0 +1,23 @@ +import { ethers } from 'ethers'; +import type { LockOrder } from '../../types'; + +export const hashLockOrder = (order: LockOrder) => ethers.solidityPackedKeccak256( + [ + 'uint8', + 'address', + 'uint64', + 'string', + 'uint64', + 'uint64', + 'uint64', + ], + [ + '0x03', + order.sender, + order.expiration, + order.asset, + order.amount, + order.targetChainId, + order.secretHash + ], +); diff --git a/src/crypt/hashOrders/hashOrder.ts b/src/crypt/hashOrders/hashOrder.ts new file mode 100644 index 00000000..c8abe14f --- /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 00000000..fe8b3994 --- /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 d09530af..2d0ac7e2 100644 --- a/src/crypt/index.ts +++ b/src/crypt/index.ts @@ -1,2 +1,4 @@ export { default as signCancelOrder } from './signCancelOrder.js'; -export { default as signOrder } from './signOrder.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/signCancelOrder.ts b/src/crypt/signCancelOrder.ts index f9bd3293..51fda908 100644 --- a/src/crypt/signCancelOrder.ts +++ b/src/crypt/signCancelOrder.ts @@ -1,11 +1,8 @@ -import type { TypedDataSigner } from '@ethersproject/abstract-signer'; -import { ethers } from 'ethers'; +import type { ethers } from 'ethers'; import CANCEL_ORDER_TYPES from '../constants/cancelOrderTypes.js'; import type { CancelOrderRequest, SignedCancelOrderRequest, SupportedChainId } from '../types.js'; import getDomainData from './getDomainData.js'; -type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; - const signCancelOrder = async ( senderAddress: string, id: string, @@ -16,24 +13,16 @@ const signCancelOrder = async ( id, senderAddress, }; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const typedDataSigner = signer as SignerWithTypedDataSign; - const signature = await typedDataSigner.signTypedData( + const signature = await signer.signTypedData( getDomainData(chainId), CANCEL_ORDER_TYPES, cancelOrderRequest, ); - // 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 cancel"); - const signedCancelOrderReqeust: SignedCancelOrderRequest = { ...cancelOrderRequest, - signature: fixedSignature, + signature, }; return signedCancelOrderReqeust; }; diff --git a/src/crypt/signCrossChainOrder.ts b/src/crypt/signCrossChainOrder.ts new file mode 100644 index 00000000..57571af2 --- /dev/null +++ b/src/crypt/signCrossChainOrder.ts @@ -0,0 +1,107 @@ +import { BigNumber } from 'bignumber.js'; +import type { ethers } from 'ethers'; +import { 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, + targetChainId: Number(targetChainId), + secretHash, + lockOrderExpiration + } + + const signature = await signer.signTypedData( + getDomainData(chainId), + CROSS_CHAIN_ORDER_TYPES, + crossChainOrder + ); + + const signedOrderWithoutId: Omit = { + ...order, + signature, + secret, + secretHash, + targetChainId: Number(targetChainId), + lockOrderExpiration + } + const orderHash = getOrderHash(signedOrderWithoutId, targetChainId); + + const signedCrossChainOrder: SignedCrossChainOrder = { + ...signedOrderWithoutId, + id: orderHash + }; + + return signedCrossChainOrder; +}; diff --git a/src/crypt/signLockOrder.ts b/src/crypt/signLockOrder.ts new file mode 100644 index 00000000..2eae4dfb --- /dev/null +++ b/src/crypt/signLockOrder.ts @@ -0,0 +1,61 @@ +import { ethers } from 'ethers'; +import type { SupportedChainId, LockOrder, SignedLockOrder } from '../types.js'; +import getDomainData from './getDomainData.js'; +import generateSecret from '../utils/generateSecret'; +import { BigNumber } from 'bignumber.js'; +import normalizeNumber from '../utils/normalizeNumber'; +import { INTERNAL_PROTOCOL_PRECISION, LOCK_ORDER_TYPES } from '../constants'; + +const DEFAULT_EXPIRATION = 4 * 24 * 60 * 60 * 1000; // 4 days + +export type SignLockOrderProps = { + senderAddress: string // user + asset: string + amount: BigNumber.Value + signer: ethers.Signer + chainId: SupportedChainId + targetChainId: SupportedChainId +} + +export const signLockOrder = async ({ + senderAddress, + amount, + chainId, + targetChainId, + asset, + signer, +}: SignLockOrderProps) => { + const nonce = Date.now(); + const expiration = nonce + DEFAULT_EXPIRATION; + const secret = generateSecret(); + const secretHash = ethers.keccak256(secret); + + const order: LockOrder = { + sender: senderAddress, + expiration, + asset, + amount: Number(normalizeNumber( + amount, + INTERNAL_PROTOCOL_PRECISION, + BigNumber.ROUND_FLOOR, + )), + targetChainId: Number(targetChainId), + secret, + secretHash + }; + + const signature = await signer.signTypedData( + getDomainData(chainId), + LOCK_ORDER_TYPES, + order, + ); + + const signedOrder: SignedLockOrder = { + ...order, + signature, + secret, + secretHash + }; + + return signedOrder; +}; diff --git a/src/crypt/signOrder.ts b/src/crypt/signOrder.ts index fca41a73..4623991a 100644 --- a/src/crypt/signOrder.ts +++ b/src/crypt/signOrder.ts @@ -1,39 +1,49 @@ -import type { TypedDataSigner } from '@ethersproject/abstract-signer'; import { BigNumber } from 'bignumber.js'; -import { ethers } from 'ethers'; -import { INTERNAL_PROTOCOL_PRECISION } from '../constants/index.js'; -import ORDER_TYPES from '../constants/orderTypes.js'; +import type { 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 hashOrder from './hashOrder.js'; +import hashOrder from './hashOrders/hashOrder'; const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days -type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; +export type SignOrderProps = { + 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 +} -export const signOrder = async ( - baseAssetAddr: string, - quoteAssetAddr: string, - side: 'BUY' | 'SELL', - price: BigNumber.Value, - amount: BigNumber.Value, - matcherFee: BigNumber.Value, - senderAddress: string, - matcherAddress: string, - serviceFeeAssetAddr: string, - signer: ethers.Signer, - chainId: SupportedChainId, -) => { +export const signOrder = async ({ + senderAddress, + serviceFeeAssetAddress, + baseAssetAddress, + quoteAssetAddress, + matcherFee, + matcherAddress, + chainId, + signer, + side, + amount, + price +}: SignOrderProps) => { const nonce = Date.now(); const expiration = nonce + DEFAULT_EXPIRATION; const order: Order = { senderAddress, matcherAddress, - baseAsset: baseAssetAddr, - quoteAsset: quoteAssetAddr, - matcherFeeAsset: serviceFeeAssetAddr, + baseAsset: baseAssetAddress, + quoteAsset: quoteAssetAddress, + matcherFeeAsset: serviceFeeAssetAddress, amount: Number(normalizeNumber( amount, INTERNAL_PROTOCOL_PRECISION, @@ -54,27 +64,16 @@ export const signOrder = async ( buySide: side === 'BUY' ? 1 : 0, }; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const typedDataSigner = signer as SignerWithTypedDataSign; - - const signature = await typedDataSigner.signTypedData( + const signature = await signer.signTypedData( getDomainData(chainId), ORDER_TYPES, order, ); - // 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 signedOrder: SignedOrder = { ...order, id: hashOrder(order), - signature: fixedSignature, + signature, }; return signedOrder; }; - -export default signOrder; diff --git a/src/services/Aggregator/index.ts b/src/services/Aggregator/index.ts index 876fc26b..f1a05166 100644 --- a/src/services/Aggregator/index.ts +++ b/src/services/Aggregator/index.ts @@ -6,14 +6,21 @@ import cancelOrderSchema from './schemas/cancelOrderSchema.js'; import orderBenefitsSchema from './schemas/orderBenefitsSchema.js'; import errorSchema from './schemas/errorSchema.js'; import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema.js'; -import { AggregatorWS } from './ws/index.js'; +import { AggregatorWS } from './ws'; import { atomicSwapHistorySchema } from './schemas/atomicSwapHistorySchema.js'; -import type { BasicAuthCredentials, OrderSource, SignedCancelOrderRequest, SignedOrder } from '../../types.js'; +import type { + BasicAuthCredentials, + OrderSource, + NetworkShortName, + SignedLockOrder, + SignedCancelOrderRequest, + SignedOrder, + SignedCrossChainOrder +} from '../../types.js'; import { pairConfigSchema, aggregatedOrderbookSchema, exchangeOrderbookSchema, poolReservesSchema, } from './schemas/index.js'; -import type networkCodes from '../../constants/networkCodes.js'; import toUpperCase from '../../utils/toUpperCase.js'; import httpToWS from '../../utils/httpToWS.js'; import { ethers } from 'ethers'; @@ -60,10 +67,12 @@ class Aggregator { this.getPairConfigs = this.getPairConfigs.bind(this); this.getPairsList = this.getPairsList.bind(this); this.getSwapInfo = this.getSwapInfo.bind(this); + this.getCrossChainAssetsByNetwork = this.getCrossChainAssetsByNetwork.bind(this); this.getTradeProfits = this.getTradeProfits.bind(this); this.getStableCoins = this.getStableCoins.bind(this); this.placeAtomicSwap = this.placeAtomicSwap.bind(this); this.placeOrder = this.placeOrder.bind(this); + this.placeLockOrder = this.placeLockOrder.bind(this); this.cancelOrder = this.cancelOrder.bind(this); this.checkWhitelisted = this.checkWhitelisted.bind(this); this.getLockedBalance = this.getLockedBalance.bind(this); @@ -203,7 +212,7 @@ class Aggregator { ); placeOrder = ( - signedOrder: SignedOrder, + signedOrder: SignedOrder | SignedCrossChainOrder, isCreateInternalOrder: boolean, isReversedOrder?: boolean, partnerId?: string, @@ -244,6 +253,49 @@ class Aggregator { ); }; + placeLockOrder = ( + signedLockOrder: SignedLockOrder, + ) => { + const headers = { + 'Content-Type': 'application/json', + Accept: 'application/json', + ...this.basicAuthHeaders, + }; + + const url = new URL(`${this.apiUrl}/api/v1/cross-chain`); + + const body = { + secretHash: signedLockOrder.secretHash, + sender: signedLockOrder.sender, + expiration: signedLockOrder.expiration, + asset: signedLockOrder.asset, + amount: signedLockOrder.amount, + targetChainId: Number(signedLockOrder.targetChainId), + sign: signedLockOrder.signature, + secret: signedLockOrder.secret, + } + + return fetchWithValidation( + url.toString(), + z.object({ + orderId: z.string(), + placementRequests: z.array( + z.object({ + amount: z.number(), + brokerAddress: z.string(), + exchange: z.string(), + }), + ).optional(), + }), + { + headers, + method: 'POST', + body: JSON.stringify(body), + }, + errorSchema, + ); + }; + cancelOrder = (signedCancelOrderRequest: SignedCancelOrderRequest) => fetchWithValidation( `${this.apiUrl}/api/v1/order`, cancelOrderSchema, @@ -300,6 +352,18 @@ class Aggregator { ); }; + getCrossChainAssetsByNetwork = (sourceChain: NetworkShortName) => { + const url = new URL(`${this.apiUrl}/api/v1/cross-chain/assets`); + url.searchParams.append('sourceChain', sourceChain); + + return fetchWithValidation( + url.toString(), + z.array(z.string()), + { headers: this.basicAuthHeaders }, + errorSchema, + ) + } + getPrices = (assetPair: string, includePools: boolean) => { const url = new URL(`${this.apiUrl}/api/v1/prices/`); url.searchParams.append('assetPair', assetPair); @@ -354,14 +418,14 @@ class Aggregator { }; /** - * Placing atomic swap. Placement must take place on the target chain. - * @param secretHash Secret hash - * @param sourceNetworkCode uppercase, e.g. BSC, ETH - * @returns Fetch promise - */ + * Placing atomic swap. Placement must take place on the target chain. + * @param secretHash Secret hash + * @param sourceNetworkCode uppercase, e.g. BSC, ETH + * @returns Fetch promise + */ placeAtomicSwap = ( secretHash: string, - sourceNetworkCode: Uppercase, + sourceNetworkCode: NetworkShortName, ) => fetchWithValidation( `${this.apiUrl}/api/v1/atomic-swap`, placeAtomicSwapSchema, @@ -381,10 +445,11 @@ class Aggregator { ); /** - * Get placed atomic swaps. Each atomic swap received from this list has a target chain corresponding to this Aggregator - * @param sender Sender address - * @returns Fetch promise - */ + * Get placed atomic swaps. Each atomic swap received from this list has a target chain corresponding to this Aggregator + * @param sender Sender address + * @param limit + * @returns Fetch promise + */ getHistoryAtomicSwaps = (sender: string, limit = 1000) => { const url = new URL(`${this.apiUrl}/api/v1/atomic-swap/history/all`); url.searchParams.append('sender', sender); @@ -466,7 +531,7 @@ class Aggregator { const signatureHeaders = this.generateHeaders(data, method, path, timestamp, apiKey, secretKey); const compiledHeaders = { ...headers, ...signatureHeaders.headers, }; const body = JSON.stringify(data) - ; + ; const res = pmmOrderSchema.parse({}); @@ -486,7 +551,9 @@ class Aggregator { const errorParseResult = errorSchema.safeParse(json); - if (!errorParseResult.success) { throw Error(`Unrecognized answer from aggregator: ${json}`); } + if (!errorParseResult.success) { + throw Error(`Unrecognized answer from aggregator: ${json}`); + } throw Error(errorParseResult.data.error.reason); } @@ -502,6 +569,7 @@ class Aggregator { return res; } } + export * as schemas from './schemas/index.js'; export * as ws from './ws/index.js'; export { Aggregator }; diff --git a/src/services/Aggregator/schemas/swapInfoSchema.ts b/src/services/Aggregator/schemas/swapInfoSchema.ts index 46a663f2..8de1e8db 100644 --- a/src/services/Aggregator/schemas/swapInfoSchema.ts +++ b/src/services/Aggregator/schemas/swapInfoSchema.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import uppercasedNetworkCodes from '../../../constants/uppercasedNetworkCodes'; const orderInfoSchema = z.object({ assetPair: z.string().toUpperCase(), @@ -52,6 +53,8 @@ const swapInfoBase = z.object({ d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage }).optional(), autoSlippage: z.number().optional(), + sourceChain: z.enum(uppercasedNetworkCodes).optional(), + targetChain: z.enum(uppercasedNetworkCodes).optional(), }); const swapInfoByAmountIn = swapInfoBase.extend({ diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index 84434584..58852652 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -43,6 +43,8 @@ type SwapInfoSubscriptionPayload = { es?: string // exchange list of all cex or all pools (ORION_POOL, UNISWAP, PANCAKESWAP etc) e?: boolean // is amount IN? Value `false` means a = amount OUT, `true` if omitted is?: boolean // instant settlement + sc?: string // CS sourceChain + tc?: string // CS targetChain } type BrokerTradableAtomicSwapBalanceSubscription = { @@ -83,22 +85,22 @@ type SwapInfoSubscription = { type AddressUpdateUpdate = { kind: 'update' balances: Partial< - Record< - string, - Balance + Record< + string, + Balance + > > - > order?: z.infer | z.infer | undefined } type AddressUpdateInitial = { kind: 'initial' balances: Partial< - Record< - string, - Balance + Record< + string, + Balance + > > - > orders?: Array> | undefined // The field is not defined if the user has no orders } @@ -123,22 +125,22 @@ const exclusiveSubscriptions = [ ] as const; type BufferLike = - | string - | Buffer - | DataView - | number - | ArrayBufferView - | Uint8Array - | ArrayBuffer - | SharedArrayBuffer - | readonly unknown[] - | readonly number[] - | { valueOf: () => ArrayBuffer } - | { valueOf: () => SharedArrayBuffer } - | { valueOf: () => Uint8Array } - | { valueOf: () => readonly number[] } - | { valueOf: () => string } - | { [Symbol.toPrimitive]: (hint: string) => string }; + | string + | Buffer + | DataView + | number + | ArrayBufferView + | Uint8Array + | ArrayBuffer + | SharedArrayBuffer + | readonly unknown[] + | readonly number[] + | { valueOf: () => ArrayBuffer } + | { valueOf: () => SharedArrayBuffer } + | { valueOf: () => Uint8Array } + | { valueOf: () => readonly number[] } + | { valueOf: () => string } + | { [Symbol.toPrimitive]: (hint: string) => string }; const isSubType = (subType: string): subType is keyof Subscription => Object.values(SubscriptionType).some((t) => t === subType); @@ -203,6 +205,7 @@ class AggregatorWS { } private messageQueue: BufferLike[] = []; + private sendWsMessage(message: BufferLike) { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send(message); @@ -230,6 +233,7 @@ class AggregatorWS { } private hearbeatIntervalId: ReturnType | undefined; + private setupHeartbeat() { const heartbeat = () => { if (this.isAlive) { @@ -336,11 +340,11 @@ class AggregatorWS { } /** - * Returns newest subscription id for given id. Subscription id can be changed during resubscription. - * This function ensure that old subscription id will be replaced with newest one. - * @param id Id of subscription - * @returns Newest subscription id - */ + * Returns newest subscription id for given id. Subscription id can be changed during resubscription. + * This function ensure that old subscription id will be replaced with newest one. + * @param id Id of subscription + * @returns Newest subscription id + */ getNewestSubscriptionId(id: string): string { const newId = this.subIdReplacements[id]; if (newId !== undefined && newId !== id) { diff --git a/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts b/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts index 1e3e8d99..048e9c08 100644 --- a/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts +++ b/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts @@ -37,6 +37,8 @@ export const orderUpdateSchema = z.object({ t: z.number(), // update time C: z.string().optional(), // trigger condition E: z.enum(executionTypes).optional(), + bf: z.number().optional(), + bF: z.string().optional(), c: subOrderSchema.array(), }) .transform((val) => ({ @@ -49,6 +51,8 @@ export const orderUpdateSchema = z.object({ status: o.S, liquidated: o.l, executionType: o.E, + bridgeFee: o.bf, + bridgeFeeAsset: o.bF, triggerCondition: o.C, subOrders: o.c.map((so) => ({ pair: so.P, @@ -74,6 +78,8 @@ export const fullOrderSchema = z.object({ p: z.number(), // price F: z.string().toUpperCase(), // fee asset f: z.number(), // fee + bf: z.number().optional(), + bF: z.string().optional(), l: z.boolean().optional(), // is liquidation order L: z.number().optional(), // stop limit price, o: z.boolean(), // internal only @@ -84,6 +90,10 @@ export const fullOrderSchema = z.object({ E: z.enum(executionTypes).optional(), C: z.string().optional(), // trigger condition ro: z.boolean().optional(), // is reversed order + sc: z.string().optional(), // source chain + tc: z.string().optional(), // target chain + ir: z.boolean().optional(), // is refundable + sh: z.string().optional(), // secret hash }).transform((val) => ({ ...val, k: 'full' as const, @@ -93,6 +103,8 @@ export const fullOrderSchema = z.object({ settledAmount: o.A, feeAsset: o.F, fee: o.f, + bridgeFee: o.bf, + bridgeFeeAsset: o.bF, liquidated: o.l, stopPrice: o.L, status: o.S, @@ -105,6 +117,10 @@ export const fullOrderSchema = z.object({ executionType: o.E, triggerCondition: o.C, isReversedOrder: o.ro, + sourceChain: o.sc, + targetChain: o.tc, + isRefundable: o.ir, + secretHash: o.sh, subOrders: o.c.map((so) => ({ pair: so.P, exchange: so.e, diff --git a/src/services/BlockchainService/index.ts b/src/services/BlockchainService/index.ts index ceacbcbe..ee37b28b 100644 --- a/src/services/BlockchainService/index.ts +++ b/src/services/BlockchainService/index.ts @@ -19,7 +19,8 @@ import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/ import { makePartial } from '../../utils'; import type { networkCodes } from '../../constants/index.js'; import { fetchWithValidation } from 'simple-typed-fetch'; -import type { BasicAuthCredentials } from '../../types.js'; +import { type BasicAuthCredentials, SupportedChainId } from '../../types.js'; +import errorSchema from '../Aggregator/schemas/errorSchema'; type IAdminAuthHeaders = { auth: string @@ -65,6 +66,12 @@ type PlatformFees = { fromWidget?: string | undefined } +type MinLockAmount = { + assetIn: string + assetOut: string + targetChainId: SupportedChainId +} + class BlockchainService { private readonly apiUrl: string; @@ -115,6 +122,8 @@ class BlockchainService { this.claimOrder = this.claimOrder.bind(this); this.getGasLimits = this.getGasLimits.bind(this); this.getExchangeContractWalletBalance = this.getExchangeContractWalletBalance.bind(this); + this.getAtomicSwapFee = this.getAtomicSwapFee.bind(this); + this.getMinLockAmountForCCS = this.getMinLockAmountForCCS.bind(this); } get basicAuthHeaders() { @@ -491,6 +500,17 @@ class BlockchainService { }, ); + /** + * Get atomic swap fee in current chain + * @returns Fee in percents + */ + getAtomicSwapFee = () => fetchWithValidation( + `${this.apiUrl}/api/atomic/swap-fee`, + z.string(), + { headers: this.basicAuthHeaders }, + errorSchema, + ); + getGasLimits = () => fetchWithValidation( `${this.apiUrl}/api/baseLimits`, z.record(z.number()), @@ -502,6 +522,12 @@ class BlockchainService { z.record(z.string()), { headers: this.basicAuthHeaders } ); + + getMinLockAmountForCCS = ({ assetIn, assetOut, targetChainId }: MinLockAmount) => fetchWithValidation( + `${this.apiUrl}/api/atomic/min-lock-amount?assetIn=${assetIn}&assetOut=${assetOut}&targetChainId=${targetChainId}`, + z.number(), + { headers: this.basicAuthHeaders } + ); } export * as schemas from './schemas/index.js'; diff --git a/src/types.ts b/src/types.ts index 602f5932..5a74999e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -51,11 +51,37 @@ export type Order = { buySide: 0 | 1 // uint8, 1=buy, 0=sell } -export type SignedOrder = { - id: string // hash of Order (it's not part of order structure in smart-contract) +export type CrossChainOrder = Order & { + secret: string + secretHash: string // bytes32 + targetChainId: number // uint24 + lockOrderExpiration: number // uint64 +} + +export type LockOrder = { + sender: string // user address + expiration: number // uint64 + asset: string // address(?) + amount: number // uint64 + targetChainId: number // uint64 + secret: string // bytes32 + secretHash: string // bytes32 +} + +type SignedOrderAdditionalProps = { signature: string // bytes needWithdraw?: boolean // bool (not supported yet by smart-contract) -} & Order +} + +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) +} + +export type SignedLockOrder = SignedOrderAdditionalProps & LockOrder export type CancelOrderRequest = { id: number | string @@ -102,6 +128,8 @@ export enum SupportedChainId { // BROKEN = '0', } +export type NetworkShortName = Uppercase; + const balanceTypes = ['exchange', 'wallet'] as const; export type Source = typeof balanceTypes[number]; diff --git a/src/utils/generateSecret.ts b/src/utils/generateSecret.ts index 7caabca5..68dc7dc6 100644 --- a/src/utils/generateSecret.ts +++ b/src/utils/generateSecret.ts @@ -1,4 +1,5 @@ import { ethers } from 'ethers'; + class XorShift128Plus { private x: number; private y: number; diff --git a/src/utils/isUppercasedNetworkCode.ts b/src/utils/isUppercasedNetworkCode.ts index 978f1cd5..2cf20554 100644 --- a/src/utils/isUppercasedNetworkCode.ts +++ b/src/utils/isUppercasedNetworkCode.ts @@ -1,7 +1,8 @@ import { networkCodes } from '../constants/index.js'; import toUpperCase from './toUpperCase.js'; +import type { NetworkShortName } from '../types'; -const isUppercasedNetworkCode = (value: string): value is Uppercase => networkCodes +const isUppercasedNetworkCode = (value: string): value is NetworkShortName => networkCodes .map(toUpperCase).some((networkCode) => networkCode === value); export default isUppercasedNetworkCode; diff --git a/tsconfig.json b/tsconfig.json index 89852030..1e9094b0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,7 @@ "noUnusedLocals": true /* Enable error reporting when a local variables aren't read. */, "noUnusedParameters": true /* Raise an error when a function parameter isn't read */, "noUncheckedIndexedAccess": true, - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ }, "ts-node": { // Tell ts-node CLI to install the --loader automatically, explained below