Skip to content
This repository was archived by the owner on Jun 24, 2022. It is now read-only.

Orders state #42

Merged
merged 53 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
eda6633
copy state/index to custom
Velenir Nov 24, 2020
0fcb46f
create sample of Operator actions
Velenir Nov 24, 2020
35ab633
create sample of Operator reducer
Velenir Nov 24, 2020
8ea3215
add operator reducer to state
Velenir Nov 24, 2020
de9377d
create sample of Operator hooks
Velenir Nov 24, 2020
1f29e2c
changeto absolute import { AppDispatch, AppState } from 'state/'
Velenir Nov 24, 2020
e4f42f7
change to absolute import store from 'state/'
Velenir Nov 24, 2020
d5ccbf8
minor organisational changes
W3stside Nov 24, 2020
246a118
from 'state/' -> from 'state'
Velenir Nov 27, 2020
9099002
special var for UNISWAP_REDUCERS
Velenir Nov 27, 2020
f6f7712
simplify types
Velenir Nov 27, 2020
60069b2
fix import error
W3stside Nov 27, 2020
98d426e
create some actions for Orders
Velenir Nov 30, 2020
1410bc3
create some reducers for Orders
Velenir Nov 30, 2020
9eb3b0c
hook up Orders reducer
Velenir Nov 30, 2020
fd1e82f
create some hooks for Orders
Velenir Nov 30, 2020
23f5352
differentiate orders by chainId
Velenir Dec 1, 2020
6ca7521
add clearOrders action
Velenir Dec 1, 2020
290aecd
expand Orders types
Velenir Dec 1, 2020
0065fdf
fix orders/hooks
Velenir Dec 1, 2020
287e8dd
add useClearOrders hook
Velenir Dec 1, 2020
1b0405f
wrap hook function returns in useCallback
Velenir Dec 1, 2020
bab547f
scaffold orders/updater
Velenir Dec 1, 2020
9330108
don't use const enum
Velenir Dec 1, 2020
5532ed8
fix build
Velenir Dec 1, 2020
f2c05c5
remove unnecessary stuff
Velenir Dec 2, 2020
c40beb1
setup mock event watcher
Velenir Dec 2, 2020
1a17133
extend types
Velenir Dec 2, 2020
56f87ff
use mock EventUpdater
Velenir Dec 2, 2020
8b67146
change type names
Velenir Dec 2, 2020
57a0e67
try different decoding methods
Velenir Dec 3, 2020
083ea80
improve event watcher
Velenir Dec 3, 2020
6a79936
better types
Velenir Dec 4, 2020
4cf9741
Merge branch 'develop' into orders_state
Velenir Dec 4, 2020
346336b
normalize decoder interface
Velenir Dec 7, 2020
f941419
differentiate Orders by status
Velenir Dec 7, 2020
59f08c8
fullfillOrder action + reducer
Velenir Dec 7, 2020
22e6d9e
hooks for different Orders by state
Velenir Dec 7, 2020
2467583
useFulfillOrder hook
Velenir Dec 7, 2020
0f9575e
persist state.orders in localStorage
Velenir Dec 7, 2020
4de95db
separate pending and fulfilled orders more
Velenir Dec 8, 2020
68a9572
prefill optional state for convenience
Velenir Dec 8, 2020
a003616
rename action to explicitly say pendingOrder
Velenir Dec 8, 2020
593af23
refactorhooks fornew state shape
Velenir Dec 8, 2020
632ebf9
rename some types
Velenir Dec 8, 2020
b338784
fix build
Velenir Dec 8, 2020
2b2a02f
remove updater for now
Velenir Dec 8, 2020
5212179
move isTruthy to utils
Velenir Dec 8, 2020
eb7c9dc
remove Updater reference
Velenir Dec 8, 2020
f64d603
OrderKind enum in line with api
Velenir Dec 9, 2020
5ae21f2
more comments for types
Velenir Dec 9, 2020
21b7fdb
OrderStatus enum values as strings,for readability when serialized
Velenir Dec 9, 2020
6d97ce9
add Order.summary prop
Velenir Dec 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/custom/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import burn from '@src/state/burn/reducer'
import multicall from '@src/state/multicall/reducer'
// CUSTOM REDUCERS
import operator from './operator/reducer'
import orders from './orders/reducer'

const UNISWAP_REDUCERS = {
application,
Expand All @@ -25,12 +26,13 @@ const UNISWAP_REDUCERS = {
lists
}

const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists', 'orders']

const store = configureStore({
reducer: {
...UNISWAP_REDUCERS,
operator
operator,
orders
},
middleware: [...getDefaultMiddleware({ thunk: false }), save({ states: PERSISTED_KEYS })],
preloadedState: load({ states: PERSISTED_KEYS })
Expand Down
57 changes: 57 additions & 0 deletions src/custom/state/orders/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createAction } from '@reduxjs/toolkit'
import { ChainId } from '@uniswap/sdk'

enum OrderKind {
SELL = 'sell',
BUY = 'buy'
}

// posted to /api/v1/orders on Order creation
// serializable, so no BigNumbers
export interface OrderCreation {
sellToken: string // address, without '0x' prefix
buyToken: string // address, without '0x' prefix
sellAmount: string // in atoms
buyAmount: string // in atoms
validTo: number // unix timestamp, seconds, use new Date(validTo * 1000)
appData: number // arbitrary identifier sent along with the order
tip: string // in atoms
orderType: OrderKind
partiallyFillable: boolean
signature: string // 65 bytes encoded as hex without `0x` prefix. v + r + s from the spec
}

export enum OrderStatus {
PENDING = 'pending',
FULFILLED = 'fulfilled'
}

// used internally by dapp
export interface Order extends OrderCreation {
id: OrderID // it is special :), Unique identifier for the order: 56 bytes encoded as hex without 0x
owner: string // address, without '0x' prefix
status: OrderStatus
fulfillmentTime?: string
creationTime: string
summary: string // for dapp use only, readable by user
}

// gotten from querying /api/v1/orders
export interface OrderFromApi extends OrderCreation {
creationTime: string // Creation time of the order. Encoded as ISO 8601 UTC
owner: string // address, without '0x' prefix
}

/**
* Unique identifier for the order, calculated by keccak256(orderDigest, ownerAddress, validTo),
where orderDigest = keccak256(orderStruct). bytes32.
*/
export type OrderID = string

export const addPendingOrder = createAction<{ id: OrderID; chainId: ChainId; order: Order }>('order/addPendingOrder')
export const removeOrder = createAction<{ id: OrderID; chainId: ChainId }>('order/removeOrder')
// fulfillmentTime from event timestamp
export const fulfillOrder = createAction<{ id: OrderID; chainId: ChainId; fulfillmentTime: string }>(
'order/fulfillOrder'
)
export const clearOrders = createAction<{ chainId: ChainId }>('order/clearOrders')
97 changes: 97 additions & 0 deletions src/custom/state/orders/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { ChainId } from '@uniswap/sdk'
import { useCallback, useMemo } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { AppDispatch, AppState } from 'state'
import { addPendingOrder, removeOrder, clearOrders, fulfillOrder, Order, OrderID } from './actions'
import { OrdersState, PartialOrdersMap } from './reducer'
import { isTruthy } from 'utils/misc'

interface AddPendingOrderParams extends GetRemoveOrderParams {
order: Order
}

interface FulfillOrderParams extends GetRemoveOrderParams {
fulfillmentTime: string
}
interface GetRemoveOrderParams {
id: OrderID
chainId: ChainId
}

interface ClearOrdersParams {
chainId: ChainId
}

type GetOrdersParams = Pick<GetRemoveOrderParams, 'chainId'>

type AddOrderCallback = (addOrderParams: AddPendingOrderParams) => void
type RemoveOrderCallback = (clearOrderParams: GetRemoveOrderParams) => void
type FulfillOrderCallback = (fulfillOrderParams: FulfillOrderParams) => void
type ClearOrdersCallback = (clearOrdersParams: ClearOrdersParams) => void

export const useOrder = ({ id, chainId }: GetRemoveOrderParams): Order | undefined => {
const state = useSelector<AppState, OrdersState[ChainId]>(state => state.orders[chainId])

return state?.fulfilled[id]?.order || state?.pending[id]?.order
}

export const useOrders = ({ chainId }: GetOrdersParams): Order[] => {
const state = useSelector<AppState, OrdersState[ChainId]>(state => state.orders?.[chainId])

return useMemo(() => {
if (!state) return []

const allOrders = Object.values(state.fulfilled)
.concat(Object.values(state.pending))
.map(orderObject => orderObject?.order)
.filter(isTruthy)
return allOrders
}, [state])
}

export const usePendingOrders = ({ chainId }: GetOrdersParams): Order[] => {
const state = useSelector<AppState, PartialOrdersMap | undefined>(state => state.orders?.[chainId]?.pending)

return useMemo(() => {
if (!state) return []

const allOrders = Object.values(state)
.map(orderObject => orderObject?.order)
.filter(isTruthy)
return allOrders
}, [state])
}

export const useFulfilledOrders = ({ chainId }: GetOrdersParams): Order[] => {
const state = useSelector<AppState, PartialOrdersMap | undefined>(state => state.orders?.[chainId]?.fulfilled)

return useMemo(() => {
if (!state) return []

const allOrders = Object.values(state)
.map(orderObject => orderObject?.order)
.filter(isTruthy)
return allOrders
}, [state])
}

export const useAddPendingOrder = (): AddOrderCallback => {
const dispatch = useDispatch<AppDispatch>()
return useCallback((addOrderParams: AddPendingOrderParams) => dispatch(addPendingOrder(addOrderParams)), [dispatch])
}

export const useFulfillOrder = (): FulfillOrderCallback => {
const dispatch = useDispatch<AppDispatch>()
return useCallback((fulfillOrderParams: FulfillOrderParams) => dispatch(fulfillOrder(fulfillOrderParams)), [dispatch])
}

export const useRemoveOrder = (): RemoveOrderCallback => {
const dispatch = useDispatch<AppDispatch>()
return useCallback((removeOrderParams: GetRemoveOrderParams) => dispatch(removeOrder(removeOrderParams)), [dispatch])
}

export const useClearOrders = (): ClearOrdersCallback => {
const dispatch = useDispatch<AppDispatch>()
return useCallback((clearOrdersParams: ClearOrdersParams) => dispatch(clearOrders(clearOrdersParams)), [dispatch])
}
93 changes: 93 additions & 0 deletions src/custom/state/orders/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { createReducer, PayloadAction } from '@reduxjs/toolkit'
import { ChainId } from '@uniswap/sdk'
import { addPendingOrder, removeOrder, Order, OrderID, clearOrders, fulfillOrder, OrderStatus } from './actions'

export interface OrderObject {
id: OrderID
order: Order
}

// {order uuid => OrderObject} mapping
type OrdersMap = Record<OrderID, OrderObject>
export type PartialOrdersMap = Partial<OrdersMap>

export type OrdersState = {
readonly [chainId in ChainId]?: {
pending: PartialOrdersMap
fulfilled: PartialOrdersMap
}
}

interface PrefillStateRequired {
chainId: ChainId
}

type Writable<T> = {
-readonly [K in keyof T]: T[K]
}

// makes sure there's always an object at state[chainId], state[chainId].pending | .fulfilled
function prefillState(
state: Writable<OrdersState>,
{ payload: { chainId } }: PayloadAction<PrefillStateRequired>
): asserts state is Required<OrdersState> {
// asserts that state[chainId].pending | .fulfilled is ok to access
const stateAtChainId = state[chainId]

if (!stateAtChainId) {
state[chainId] = {
pending: {},
fulfilled: {}
}
return
}

if (!stateAtChainId.pending) {
stateAtChainId.pending = {}
}

if (!stateAtChainId.fulfilled) {
stateAtChainId.fulfilled = {}
}
}

const initialState: OrdersState = {}

export default createReducer(initialState, builder =>
builder
.addCase(addPendingOrder, (state, action) => {
prefillState(state, action)
const { order, id, chainId } = action.payload

state[chainId].pending[id] = { order, id }
})
.addCase(removeOrder, (state, action) => {
prefillState(state, action)
const { id, chainId } = action.payload
delete state[chainId].pending[id]
delete state[chainId].fulfilled[id]
})
.addCase(fulfillOrder, (state, action) => {
prefillState(state, action)
const { id, chainId, fulfillmentTime } = action.payload

const orderObject = state[chainId].pending[id]

if (orderObject) {
delete state[chainId].pending[id]

orderObject.order.status = OrderStatus.FULFILLED
orderObject.order.fulfillmentTime = fulfillmentTime

state[chainId].fulfilled[id] = orderObject
}
})
.addCase(clearOrders, (state, action) => {
const { chainId } = action.payload

state[chainId] = {
pending: {},
fulfilled: {}
}
})
)
1 change: 1 addition & 0 deletions src/custom/utils/misc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isTruthy = <T>(value: T | null | undefined | false): value is T => !!value