FOAM
Kristoffer Josefsson
type systems : more than int ≠ bool
compiler-assistance : e.g. metaprogramming
Kristoffer Josefsson
Kristoffer Josefsson
a functional alternative to truffle
buy :: TransactionOptions MinorUnit -> { _saleId :: UIntN S256 } -> Web3 HexString buy x0 r = uncurryFields r $ buy' x0 where buy' :: TransactionOptions MinorUnit -> (Tagged (SProxy "_saleId") (UIntN S256)) -> Web3 HexString buy' y0 y1 = sendTx y0 ((tagged $ Tuple1 y1) :: BuyFn)
it "can buy signal tokens" \{ signalForSale } -> do
let txOpts = defaultTransactionOptions # _from ?~ account2
# _gas ?~ embed 8000000
signal = unwrap signalForSale
acc2BuyAction = SignalMarket.buy
(txOpts # _to ?~ signalMarket
# _value ?~ convert (mkValue one :: Value Ether))
{ _saleId: signal.saleId }
acc2BuyFilter = eventFilter (Proxy :: Proxy SignalMarket.SignalSold)
signalMarket
SignalMarket.SignalSold purchase <- monitorUntil provider logger
acc2BuyAction acc2BuyFilter
liftAff do
purchase.tokenId `shouldEqual` signal.tokenId
purchase.price `shouldEqual` originalPrice
monitor :: IndexerM ()
monitor = do
cfg <- ask
let contracts = indexerCfgContracts cfg
(window, _) = indexerMultiFilterOpts cfg
signalMarketReceipt = contractsSignalMarket contracts
(smSignalForSaleF, smSignalForSaleH) <- makeFilterHandlerPair
signalMarketReceipt SignalMarket.signalMarketSignalForSaleH
(smSignalSoldF, smSignalSoldH) <- makeFilterHandlerPair
signalMarketReceipt SignalMarket.signalMarketSignalSoldH
let filters = ftTransferF
:? smSignalForSaleF
:? smSignalSoldF
:? NilFilters
handlers = ftTransferH
:& smSignalForSaleH
:& smSignalSoldH
:& RNil
runWeb3 $ multiEventManyNoFilter' filters window handlers
signalMarketSignalSoldH
:: ( MonadPG m
, MonadThrow m )
=> Event Contract.SignalSold
-> m ()
signalMarketSignalSoldH Event{eventEventID, eventData} = case eventData of
Contract.SignalSold{..} -> do
insert Sold.signalSoldTable $ Sold.SignalSold
{ Sold.saleID = signalSoldSaleId_ ^. _SaleID
, Sold.tokenID = signalSoldTokenId_ ^. _TokenID
, Sold.price = signalSoldPrice_ ^. _Value
, Sold.soldFrom = signalSoldOwner_ ^. _EthAddress
, Sold.soldTo = signalSoldNewOwner_ ^. _EthAddress }
let updateSaleStatus a = a { ForSale.saleStatus = constant SSComplete }
isActiveTokenID a = ForSale.saleID a
.== constant (signalSoldSaleId_ ^. _SaleID)
_ :: ForSale.SignalForSale <- update ForSale.signalForSaleTable
updateSaleStatus isActiveTokenID
pure ()
defines API types
type SignalMarketAPI = "signal_market"
:> ( GetSignalMarketSignalForSale
:<|> GetSignalMarketSignalSold
:<|> GetSignalMarketHistory )
type GetSignalMarketSignalForSale = "for_sale"
:> QueryParams "sale_id" SaleID
:> QueryParams "token_id" TokenID
:> QueryParam "sale_status" SaleStatus
:> QueryParams "seller" EthAddress
:> QueryParam "limit" Int
:> QueryParam "offset" Int
:> QueryParam "ordering" BlockNumberOrdering
:> Get '[JSON] [WithMetadata SignalMarketSignalForSale.SignalForSale]
getSignalMarketSignalForSaleH
:: [SaleID] -> [TokenID] -> Maybe SaleStatus -> [EthAddress]
-> Maybe Int -> Maybe Int -> Maybe BlockNumberOrdering
-> AppHandler [WithMetadata ForSale.SignalForSale]
getSignalMarketSignalForSaleH {..} = do
let withLimitAndOffset = maybe Cat.id withCursor
(Cursor <$> mlimit <*> moffset)
saleIdFilter = case saleID of
[] -> Cat.id
xs -> O.keepWhen (\a -> fmap O.constant xs `O.in_` ForSale.saleID a)
usingOrdering = withOrdering (fromMaybe DESC mord)
(RawChange.blockNumber . snd) (RawChange.logIndex . snd)
as <- runDB $ \conn -> O.runQuery conn $
withLimitAndOffset $ usingOrdering $ withMetadata ForSale.eventID $
signalMarketSignalForSaleQ >>> saleIdFilter
pure . flip map as $ \(t, rc) -> WithMetadata t rc
frontend application
renderSignal {..} = let (Signal s) = state.signal in classy R.div "Signal" [ renderBaseSignal addLink state.signal , case user of ... ]
case user of
UserGuest -> maybeHtml s.sale \{price} ->
R.div_ [R.text $ "ON SALE FOR ", renderToken price]
type ConnectedState = { userAddress :: Address
, provider :: Provider
, contracts :: Contracts
}
data User =
UserGuest
| UserConnected ConnectedState
case user of UserConnected con@{userAddress} | userAddress == s.owner -> case s.sale of Just {id, price} -> txOrElse state.tx $ React.fragment [ R.span_ [ R.text $ "ON SALE FOR " , renderToken price, R.text " " ] , R.button { onClick: capture_ $ txSend (Tx.UnSell id) con (\newTxSt -> updateState _ {tx = Just newTxSt}) , children: [ R.text "UnList" ] } ]
chanterelle.json
{
"name": "sample-nft-project",
"version": "0.0.1",
"source-dir": "dapp/contracts",
"modules": [ "SimpleStorage"
, "SignalMarket"
, "FoamToken"
, "SignalToken"
],
"dependencies": [ "openzeppelin-solidity" ],
"purescript-generator": {
"output-path": "dapp/src",
"module-prefix": "Contracts"
},
"solc-version": "0.5.11",
"solc-evm-version": "byzantium"
}
data Route
= Signals
| Signal SignalId
data Signal = Signal
{ id :: SignalId
, stake :: Token FOAM
, owner :: Address
, geohash :: Geohash
, radius :: Radius
, sale :: Maybe
{ id :: SaleId
, price :: Token ETHER
}
}
data SignalActivity
= ListedForSale
{ owner :: Address
, saleId :: SaleId
, price :: Token ETHER
}
| UnlistedFromSale
{ owner :: Address
, saleId :: SaleId
}
| Sold
{ owner :: Address
, saleId :: SaleId
, buyer :: Address
, price :: Token ETHER
}
data SignalDetails = SignalDetails
{ signal :: Signal
, activity :: Array SignalActivity
}
data Event
= SignalForSale SignalMarket.SignalForSale
| SignalUnlisted SignalMarket.SignalUnlisted
| SignalSold SignalMarket.SignalSold
| TrackedToken SignalToken.TrackedToken
data Tx
= UnSell SaleId
| Sell SignalId (Token ETHER)
| Buy SaleId (Token ETHER)
data Status
= Submitting
| SubmittingFailed Web3Error
| MiningStart HexString
| MiningFailed TransactionReceipt
| MiningFinished HexString
data Web3Error
= Rpc
{ code :: Int
, message :: String
}
| RemoteError String
| ParserError String
| NullError
type Progress =
{ current :: Status
, finished :: Array HexString
, total :: Int
}
data ProviderConnectivity
= Connected
{ userAddress :: Address
}
| NotConnected
{ userAddress :: Maybe Address
, currentNetwork :: NetworkId
}
data ProviderState
= NotInjected
| Injected
{ loading :: Boolean
}
| Rejected
| Enabled
{ connectivity :: ProviderConnectivity
, provider :: Provider
, contracts :: Contracts
}
type ConnectedState =
{ userAddress :: Address
, provider :: Provider
, contracts :: Contracts
}
data User
= UserGuest
| UserConnected ConnectedState
Artwork Detail Page
Ex:
Why GraphQL?
Postgraphile