Created
July 25, 2020 20:47
-
-
Save youngkidwarrior/a4211be05c833a6ef04087696acfa846 to your computer and use it in GitHub Desktop.
State Example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { hasLoadedtokenRequestSettings } from './lib/token-request-settings' | |
import { compareDesc } from 'date-fns' | |
async function appStateReducer(state) { | |
const ready = hasLoadedtokenRequestSettings(state) | |
if (!ready) { | |
return { ...state, ready } | |
} | |
console.log('state: ', state); | |
const { requests = [], acceptedTokens = [] } = state | |
return { | |
...state, | |
acceptedTokens, | |
ready, | |
requests: requests.sort(({ date: dateLeft }, { date: dateRight }) => | |
// Sort by date descending | |
compareDesc(dateLeft, dateRight) | |
), | |
} | |
} | |
export default appStateReducer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'core-js/stable' | |
import 'regenerator-runtime/runtime' | |
import Aragon, { events } from '@aragon/api' | |
import { first } from 'rxjs/operators' | |
import tmAbi from './abi/tokenManager.json' | |
import agentAbi from './abi/Agent.json' | |
import { requestStatus } from './lib/constants' | |
import { | |
tokenDataFallback, | |
getTokenSymbol, | |
getTokenName, | |
getTokenDecimals, | |
ETHER_TOKEN_FAKE_ADDRESS, | |
} from './lib/token-utils' | |
const app = new Aragon() | |
const ETHER_DATA = { | |
decimals: 18, | |
name: 'Ether', | |
symbol: 'ETH', | |
} | |
let agentOrVaultAddress = '' | |
/* | |
* Calls `callback` exponentially, everytime `retry()` is called. | |
* Returns a promise that resolves with the callback's result if it (eventually) succeeds. | |
* | |
* Usage: | |
* | |
* retryEvery(retry => { | |
* // do something | |
* | |
* if (condition) { | |
* // retry in 1, 2, 4, 8 seconds… as long as the condition passes. | |
* retry() | |
* } | |
* }, 1000, 2) | |
* | |
*/ | |
const retryEvery = async (callback, { initialRetryTimer = 1000, increaseFactor = 3, maxRetries = 3 } = {}) => { | |
const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)) | |
let retryNum = 0 | |
const attempt = async (retryTimer = initialRetryTimer) => { | |
try { | |
return await callback() | |
} catch (err) { | |
if (retryNum === maxRetries) { | |
throw err | |
} | |
++retryNum | |
// Exponentially backoff attempts | |
const nextRetryTime = retryTimer * increaseFactor | |
console.log(`Retrying in ${nextRetryTime}s... (attempt ${retryNum} of ${maxRetries})`) | |
await sleep(nextRetryTime) | |
return attempt(nextRetryTime) | |
} | |
} | |
return attempt() | |
} | |
// Get the agent Or vault address to initialize ourselves | |
retryEvery( | |
async () => | |
(agentOrVaultAddress = await app | |
.call('agentOrVault') | |
.toPromise() | |
.catch((err) => { | |
console.error('Could not start background script execution due to the contract not loading the vault:', err) | |
throw err | |
})) | |
) | |
retryEvery(() => | |
app.call('getTokenManagers').subscribe( | |
(tokenManagers) => initialize(tokenManagers, agentOrVaultAddress), | |
(err) => { | |
console.error(`Could not start background script execution due to the contract not loading token: ${err}`) | |
} | |
) | |
) | |
async function initialize(tokenManagerAddresses, agentOrVaultAddress) { | |
const agentOrVaultContract = app.external(agentOrVaultAddress, agentAbi) | |
const network = await app | |
.network() | |
.pipe(first()) | |
.toPromise() | |
let tokens = [] | |
let tmContracts = [] | |
for (let tokenManagerAddress of tokenManagerAddresses) { | |
tmContracts.push(app.external(tokenManagerAddress, tmAbi)) | |
} | |
tokens = await app.call('getAcceptedDepositTokens').toPromise() | |
const settings = { | |
network, | |
agentOrVault: { | |
address: agentOrVaultAddress, | |
contract: agentOrVaultContract, | |
}, | |
} | |
return createStore(tmContracts, tokens, settings) | |
} | |
async function createStore(tokenManagerContracts, tokens, settings) { | |
return app.store( | |
(state, { event, returnValues, blockNumber }) => { | |
let nextState = { | |
...state, | |
} | |
switch (event) { | |
case events.ACCOUNTS_TRIGGER: | |
return updateConnectedAccount(nextState, returnValues) | |
case events.SYNC_STATUS_SYNCING: | |
return { ...nextState, isSyncing: true } | |
case events.SYNC_STATUS_SYNCED: | |
return { ...nextState, isSyncing: false } | |
case 'TokenRequestCreated': | |
return newTokenRequest(nextState, returnValues, settings, blockNumber) | |
case 'TokenRequestRefunded': | |
return requestRefunded(nextState, returnValues) | |
case 'TokenRequestFinalised': | |
return requestFinalised(nextState, returnValues) | |
case 'ReceiveERC721': | |
return nftReceived(nextState, returnValues) | |
default: | |
return state | |
} | |
}, | |
{ | |
init: initializeState(tokenManagerContracts, tokens, settings), | |
externals: [ | |
{ | |
contract: settings.agentOrVault.contract, | |
}, | |
], | |
} | |
) | |
} | |
/*********************** | |
* * | |
* Event Handlers * | |
* * | |
***********************/ | |
function initializeState(tokenManagerContracts, tokens, settings) { | |
return async (cachedState) => { | |
try { | |
const nftTokens = [] | |
const orgTokens = [] | |
for (let tokenManagerContract of tokenManagerContracts) { | |
const minimeAddress = await tokenManagerContract.token().toPromise() | |
const token = await getTokenData(minimeAddress, settings) | |
token && app.indentify(`token-request ${token.symbol}`) | |
orgTokens.push(token) | |
} | |
const acceptedTokens = await getAcceptedTokens(tokens, settings) | |
tokens.includes(ETHER_TOKEN_FAKE_ADDRESS) && | |
acceptedTokens.unshift({ | |
...ETHER_DATA, | |
address: ETHER_TOKEN_FAKE_ADDRESS, | |
}) | |
return { | |
...cachedState, | |
isSyncing: true, | |
orgTokens, | |
acceptedTokens, | |
nftTokens, | |
lastSoldBlock: 0, | |
totalSoldNFT: 0, | |
} | |
} catch (error) { | |
console.error('Error initializing state: ', error) | |
} | |
} | |
} | |
const getAcceptedTokens = async (tokens, settings) => { | |
const promises = tokens | |
.filter((token) => token != ETHER_TOKEN_FAKE_ADDRESS) | |
.map((tokenAddress) => getTokenData(tokenAddress, settings)) | |
return Promise.all(promises) | |
} | |
// const getNFTTokens = async (agentContract) => { | |
// const nftAddressesLength = await agentContract.getNFTTokensLength().toPromise() | |
// console.log('nftAddressesLength: ', nftAddressesLength) | |
// // for (let i = 0; i < nftAddressesLength; i++) { | |
// // console.log('agentContract: ', await agentContract.nftTokens(i).toPromise()) | |
// // } | |
// return nftAddressesLength | |
// } | |
async function updateConnectedAccount(state, { account }) { | |
return { | |
...state, | |
account, | |
} | |
} | |
async function newTokenRequest( | |
state, | |
{ requestId, requesterAddress, depositToken, depositAmount, requestToken, requestAmount, requestTokenId, reference }, | |
settings, | |
blockNumber | |
) { | |
try { | |
const { requests = [], nftTokens } = state | |
const { decimals: depositDecimals, name: depositName, symbol: depositSymbol } = | |
depositToken === ETHER_TOKEN_FAKE_ADDRESS ? ETHER_DATA : await getTokenData(depositToken, settings) | |
const { decimals: requestDecimals, name: requestName, symbol: requestSymbol } = | |
requestToken === ETHER_TOKEN_FAKE_ADDRESS ? ETHER_DATA : await getTokenData(requestToken, settings) | |
const nftSelected = nftTokens.includes(requestToken.value) | |
if (nftSelected) { | |
nftTokens = nftTokens.filter((nft) => nft !== requestToken) | |
} | |
const { timestamp } = await app.web3Eth('getBlock', blockNumber).toPromise() | |
return { | |
...state, | |
requests: [ | |
...requests, | |
nftTokens, | |
{ | |
requestId, | |
requesterAddress, | |
depositToken, | |
depositDecimals, | |
depositName, | |
depositSymbol, | |
depositAmount, | |
requestToken, | |
requestDecimals, | |
requestName, | |
requestSymbol, | |
requestAmount, | |
requestTokenId, | |
reference, | |
status: requestStatus.PENDING, | |
date: marshallDate(timestamp), | |
}, | |
], | |
} | |
} catch (err) { | |
console.log(err) | |
} | |
} | |
async function requestRefunded(state, { requestId }) { | |
const { requests, nftTokens, selectedRequest } = state | |
const tokenAddress = selectedRequest.requestTokenAddress | |
const nftSelected = nftTokens.includes(tokenAddress.value) | |
if (nftSelected) { | |
nftTokens = nftTokens.append(tokenAddress) | |
} | |
return { | |
...state, | |
nftTokens, | |
requests: await updateRequestStatus(requests, requestId, nextStatus), | |
} | |
} | |
async function requestFinalised(state, { requestId }) { | |
const { requests, nftTokens, selectedRequest, lastSoldBlock } = state | |
const nextStatus = requestStatus.APPROVED | |
const tokenAddress = selectedRequest.requestTokenAddress | |
const nftSelected = nftTokens.includes(tokenAddress.value) | |
if (nftSelected) { | |
lastSoldBlock = await api.web3Eth('getBlockNumber').toPromise() | |
totalSoldNFT++ | |
} | |
return { | |
...state, | |
lastSoldBlock, | |
totalSoldNFT, | |
nftTokens, | |
requests: await updateRequestStatus(requests, requestId, nextStatus), | |
} | |
} | |
async function nftReceived(state, { token, tokenId }) { | |
const { nftTokens } = state | |
nftTokens.append({ token, tokenId }) | |
return { | |
...state, | |
nftTokens, | |
} | |
} | |
/*********************** | |
* * | |
* Helpers * | |
* * | |
***********************/ | |
async function getTokenData(tokenAddress, settings) { | |
const [decimals, name, symbol] = await Promise.all([ | |
loadTokenDecimals(tokenAddress, settings), | |
loadTokenName(tokenAddress, settings), | |
loadTokenSymbol(tokenAddress, settings), | |
]) | |
return { | |
decimals, | |
name, | |
symbol, | |
address: tokenAddress, | |
} | |
} | |
async function updateRequestStatus(requests, requestId, nextStatus) { | |
const requestIndex = requests.findIndex((request) => request.requestId === requestId) | |
if (requestIndex !== -1) { | |
const nextRequests = Array.from(requests) | |
nextRequests[requestIndex] = { | |
...nextRequests[requestIndex], | |
status: nextStatus, | |
} | |
return nextRequests | |
} else { | |
console.error(`Tried to update request #${requestId} that shouldn't exist!`) | |
} | |
} | |
async function loadTokenName(tokenAddress, { network }) { | |
const fallback = tokenDataFallback(tokenAddress, 'name', network.type) || '' | |
let name | |
try { | |
name = (await getTokenName(app, tokenAddress)) || fallback | |
} catch (err) { | |
// name is optional | |
name = fallback | |
} | |
return name | |
} | |
async function loadTokenSymbol(tokenAddress, { network }) { | |
const fallback = tokenDataFallback(tokenAddress, 'symbol', network.type) || '' | |
let symbol | |
try { | |
symbol = (await getTokenSymbol(app, tokenAddress)) || fallback | |
} catch (err) { | |
// symbol is optional | |
symbol = fallback | |
} | |
return symbol | |
} | |
async function loadTokenDecimals(tokenAddress, { network }) { | |
const fallback = tokenDataFallback(tokenAddress, 'decimals', network.type) || '0' | |
let decimals | |
try { | |
decimals = (await getTokenDecimals(app, tokenAddress)) || fallback | |
} catch (err) { | |
// decimals is optional | |
decimals = fallback | |
} | |
return decimals | |
} | |
function marshallDate(date) { | |
// Represent dates as real numbers, as it's very unlikely they'll hit the limit... | |
// Adjust for js time (in ms vs s) | |
return parseInt(date, 10) * 1000 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment