add lock order support

This commit is contained in:
TheJuze
2024-01-18 17:45:32 +03:00
parent 12cbd5a87f
commit 72e1e9464e
10 changed files with 205 additions and 58 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@orionprotocol/sdk",
"version": "0.20.42-rc3",
"version": "0.20.42-rc4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@orionprotocol/sdk",
"version": "0.20.42-rc3",
"version": "0.20.42-rc4",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@orionprotocol/sdk",
"version": "0.20.42-rc3",
"version": "0.20.42-rc4",
"description": "Orion Protocol SDK",
"main": "./lib/index.cjs",
"module": "./lib/index.js",

View File

@@ -4,7 +4,7 @@ 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';
@@ -37,7 +37,6 @@ type PoolSwap = {
export type Swap = AggregatorOrder | PoolSwap;
const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory: string }): singleSwap is SingleSwap => {
return isValidFactory(singleSwap.factory);
}

View File

@@ -14,6 +14,8 @@ const hashOrder = (order: Order) => ethers.solidityPackedKeccak256(
'uint64',
'uint64',
'uint64',
'uint64',
'uint64',
'uint8',
],
[
@@ -24,10 +26,12 @@ const hashOrder = (order: Order) => ethers.solidityPackedKeccak256(
order.quoteAsset,
order.matcherFeeAsset,
order.amount,
order.targetChainId,
order.price,
order.matcherFee,
order.nonce,
order.expiration,
order.secretHash,
order.buySide === 1 ? '0x01' : '0x00',
],
);

View File

@@ -0,0 +1,71 @@
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
import { ethers } from 'ethers';
import ORDER_TYPES from '../constants/orderTypes.js';
import type { LockOrder, SignedLockOrder, SupportedChainId } from '../types.js';
import getDomainData from './getDomainData.js';
import generateSecret from '../utils/generateSecret';
const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days
type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner;
export type LockOrderProps = {
userAddress: string // адрес юзера который хочет сделать лок
senderAddress: string // broker
asset: string
amount: ethers.BigNumberish
targetChainId: ethers.BigNumberish
sign: string // подпись юзера
signer: ethers.Signer
chainId: SupportedChainId
}
export const signLockOrder = async ({
userAddress,
senderAddress,
sign,
amount,
targetChainId,
asset,
chainId,
signer
}: LockOrderProps) => {
const nonce = Date.now();
const expiration = nonce + DEFAULT_EXPIRATION;
const secret = generateSecret();
const secretHash = ethers.keccak256(secret);
const order: LockOrder = {
user: userAddress,
sender: senderAddress,
expiration,
asset,
amount,
targetChainId,
secretHash,
sign
};
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const typedDataSigner = signer as SignerWithTypedDataSign;
const signature = await typedDataSigner.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: SignedLockOrder = {
...order,
signature: fixedSignature,
secret
};
return signedOrder;
};

View File

@@ -1,12 +1,13 @@
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 { INTERNAL_PROTOCOL_PRECISION } from '../constants';
import ORDER_TYPES from '../constants/orderTypes.js';
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 generateSecret from '../utils/generateSecret';
const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days
@@ -24,9 +25,12 @@ export const signOrder = async (
serviceFeeAssetAddr: string,
signer: ethers.Signer,
chainId: SupportedChainId,
targetChainId?: ethers.BigNumberish,
) => {
const nonce = Date.now();
const expiration = nonce + DEFAULT_EXPIRATION;
const secret = generateSecret();
const secretHash = ethers.keccak256(secret);
const order: Order = {
senderAddress,
@@ -51,6 +55,8 @@ export const signOrder = async (
)),
nonce,
expiration,
secretHash,
targetChainId,
buySide: side === 'BUY' ? 1 : 0,
};
@@ -73,6 +79,7 @@ export const signOrder = async (
...order,
id: hashOrder(order),
signature: fixedSignature,
secret
};
return signedOrder;
};

View File

@@ -8,12 +8,17 @@ import errorSchema from './schemas/errorSchema.js';
import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema.js';
import { AggregatorWS } from './ws';
import { atomicSwapHistorySchema } from './schemas/atomicSwapHistorySchema.js';
import type { BasicAuthCredentials, SignedCancelOrderRequest, SignedOrder } from '../../types.js';
import type {
BasicAuthCredentials,
NetworkShortName,
SignedCancelOrderRequest,
SignedLockOrder,
SignedOrder
} 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';
@@ -55,6 +60,7 @@ class Aggregator {
this.getTradeProfits = this.getTradeProfits.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);
@@ -236,6 +242,39 @@ class Aggregator {
);
};
placeLockOrder = (
signedLockOrder: SignedLockOrder,
rawExchangeRestrictions?: string | undefined,
) => {
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
...this.basicAuthHeaders,
};
const url = new URL(`${this.apiUrl}/api/v1/cross-chain`);
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({ ...signedLockOrder, rawExchangeRestrictions }),
},
errorSchema,
);
};
cancelOrder = (signedCancelOrderRequest: SignedCancelOrderRequest) => fetchWithValidation(
`${this.apiUrl}/api/v1/order`,
cancelOrderSchema,
@@ -291,7 +330,7 @@ class Aggregator {
);
};
getCrossChainAssetsByNetwork = (sourceChain: Uppercase<typeof networkCodes[number]>) => {
getCrossChainAssetsByNetwork = (sourceChain: NetworkShortName) => {
const url = new URL(`${this.apiUrl}/api/v1/cross-chain/assets`);
url.searchParams.append('sourceChain', sourceChain);
@@ -354,7 +393,7 @@ class Aggregator {
*/
placeAtomicSwap = (
secretHash: string,
sourceNetworkCode: Uppercase<typeof networkCodes[number]>,
sourceNetworkCode: NetworkShortName,
) => fetchWithValidation(
`${this.apiUrl}/api/v1/atomic-swap`,
placeAtomicSwapSchema,
@@ -376,6 +415,7 @@ 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
* @param limit
* @returns Fetch promise
*/
getHistoryAtomicSwaps = (sender: string, limit = 1000) => {

View File

@@ -82,22 +82,22 @@ type SwapInfoSubscription = {
type AddressUpdateUpdate = {
kind: 'update'
balances: Partial<
Record<
string,
Balance
Record<
string,
Balance
>
>
>
order?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema> | undefined
}
type AddressUpdateInitial = {
kind: 'initial'
balances: Partial<
Record<
string,
Balance
Record<
string,
Balance
>
>
>
orders?: Array<z.infer<typeof fullOrderSchema>> | undefined // The field is not defined if the user has no orders
}
@@ -122,22 +122,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);
@@ -201,6 +201,7 @@ class AggregatorWS {
}
private messageQueue: BufferLike[] = [];
private sendWsMessage(message: BufferLike) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(message);
@@ -228,6 +229,7 @@ class AggregatorWS {
}
private hearbeatIntervalId: ReturnType<typeof setInterval> | undefined;
private setupHeartbeat() {
const heartbeat = () => {
if (this.isAlive) {
@@ -328,11 +330,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) {

View File

@@ -3,8 +3,10 @@ import type factories from './constants/factories.js';
import type { BigNumber } from 'bignumber.js';
import type subOrderStatuses from './constants/subOrderStatuses.js';
import type positionStatuses from './constants/positionStatuses.js';
import type { knownEnvs } from './config/schemas/index.js';
import type { knownEnvs } from './config/schemas';
import type getHistory from './Orion/bridge/getHistory.js';
import type { ethers } from 'ethers';
import type { networkCodes } from './constants';
export type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
@@ -47,14 +49,33 @@ export type Order = {
matcherFee: number // uint64
nonce: number // uint64
expiration: number // uint64
secretHash: string // uint64
buySide: 0 | 1 // uint8, 1=buy, 0=sell
targetChainId: ethers.BigNumberish | undefined // uint64
}
export type SignedOrder = {
id: string // hash of Order (it's not part of order structure in smart-contract)
export type LockOrder = {
user: string // address // адрес юзера который хочет сделать лок
sender: string // address // broker
expiration: ethers.BigNumberish // uint64
asset: string // address(?)
amount: ethers.BigNumberish // uint64
targetChainId: ethers.BigNumberish // uint64
secretHash: string // uint64
sign: string // uint64 // подпись юзера
}
type SignedOrderAdditionalProps = {
signature: string // bytes
secret: string
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 SignedLockOrder = SignedOrderAdditionalProps & LockOrder
export type CancelOrderRequest = {
id: number | string
@@ -99,6 +120,8 @@ export enum SupportedChainId {
// BROKEN = '0',
}
export type NetworkShortName = Uppercase<typeof networkCodes[number]>;
const balanceTypes = ['exchange', 'wallet'] as const;
export type Source = typeof balanceTypes[number];
@@ -278,22 +301,22 @@ export type EnvConfig = {
analyticsAPI: string
referralAPI: string
networks: Partial<
Record<
SupportedChainId,
VerboseUnitConfig
Record<
SupportedChainId,
VerboseUnitConfig
>
>
>
}
export type AggregatedAssets = Partial<
Record<
string,
Partial<
Record<SupportedChainId, {
address: string
}>
Record<
string,
Partial<
Record<SupportedChainId, {
address: string
}>
>
>
>
>;
>;
export type RedeemOrder = {
sender: string
@@ -433,9 +456,9 @@ type BridgeHistory = Awaited<ReturnType<typeof getHistory>>;
type BridgeHistoryItem = NonNullable<BridgeHistory[string]>;
export type AtomicSwap = Partial<
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
> & Partial<
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
> & {
sourceChainId: SupportedChainId
targetChainId: SupportedChainId

View File

@@ -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<typeof networkCodes[number]> => networkCodes
const isUppercasedNetworkCode = (value: string): value is NetworkShortName => networkCodes
.map(toUpperCase).some((networkCode) => networkCode === value);
export default isUppercasedNetworkCode;