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

Commit

Permalink
Orders state (#42)
Browse files Browse the repository at this point in the history
* copy state/index to custom

* create sample of Operator actions

* create sample of Operator reducer

* add operator reducer to state

* create sample of Operator hooks

* changeto absolute import { AppDispatch, AppState } from 'state/'

* change to absolute import store from 'state/'

* minor organisational changes

* from 'state/' -> from 'state'

* special var for UNISWAP_REDUCERS

* simplify types

* fix import error

* create some actions for Orders

* create some reducers for Orders

* hook up Orders reducer

* create some hooks for Orders

* differentiate orders by chainId

* add clearOrders action

* expand Orders types

* fix orders/hooks

* add useClearOrders hook

* wrap hook function returns in useCallback

* scaffold orders/updater

* don't use const enum

* fix build

* remove unnecessary stuff

* setup mock event watcher

* extend types

* use mock EventUpdater

* change type names

* try different decoding methods

* improve event watcher

* better types

* normalize decoder interface

* differentiate Orders by status

* fullfillOrder action + reducer

* hooks for different Orders by state

* useFulfillOrder hook

* persist state.orders in localStorage

* separate pending and fulfilled orders more

* prefill optional state for convenience

* rename action to explicitly say pendingOrder

* refactorhooks fornew state shape

* rename some types

* fix build

* remove updater for now

* move isTruthy to utils

* remove Updater reference

* OrderKind enum in line with api

* more comments for types

* OrderStatus enum values as strings,for readability when serialized

* add Order.summary prop

Co-authored-by: David Sato <[email protected]>
  • Loading branch information
Velenir and W3stside authored Dec 9, 2020
1 parent cf6389d commit bfb6384
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 2 deletions.
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

0 comments on commit bfb6384

Please sign in to comment.