Browse Source

Version 0.2.0

master v0.2.0
Ezerous 4 years ago
parent
commit
9d212ecb3b
  1. 2
      package.json
  2. 8
      src/Drizzle.js
  3. 2
      src/accountBalances/accountBalancesMiddleware.js
  4. 2
      src/accountBalances/accountBalancesSaga.js
  5. 6
      src/accounts/accountsMiddleware.js
  6. 2
      src/blocks/blocksSaga.js
  7. 1
      src/contractStateUtils.js
  8. 1
      src/contracts/constants.js
  9. 11
      src/contracts/contractsReducer.js
  10. 18
      src/contracts/contractsSaga.js
  11. 9
      src/drizzle-middleware.js
  12. 20
      src/drizzleStatus/drizzleStatusSaga.js
  13. 2
      src/generateStore.js
  14. 40
      src/index.js
  15. 2
      src/mergeOptions.js
  16. 10
      src/web3/web3Actions.js
  17. 23
      src/web3/web3Middleware.js
  18. 5
      src/web3/web3Reducer.js
  19. 17
      src/web3/web3Saga.js

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "@ezerous/drizzle", "name": "@ezerous/drizzle",
"version": "0.1.0", "version": "0.2.0",
"description": "A reactive data-store for web3 and smart contracts.", "description": "A reactive data-store for web3 and smart contracts.",
"license": "MIT", "license": "MIT",
"author": "Ezerous <ezerous@gmail.com>", "author": "Ezerous <ezerous@gmail.com>",

8
src/Drizzle.js

@ -6,7 +6,7 @@ import * as ContractActions from './contracts/constants'
import * as DrizzleActions from './drizzleStatus/drizzleActions' import * as DrizzleActions from './drizzleStatus/drizzleActions'
// Load as promise so that async Drizzle initialization can still resolve // Load as promise so that async Drizzle initialization can still resolve
var isEnvReadyPromise = new Promise((resolve, reject) => { const isEnvReadyPromise = new Promise((resolve) => {
const hasNavigator = typeof navigator !== 'undefined' const hasNavigator = typeof navigator !== 'undefined'
const hasWindow = typeof window !== 'undefined' const hasWindow = typeof window !== 'undefined'
const hasDocument = typeof document !== 'undefined' const hasDocument = typeof document !== 'undefined'
@ -81,12 +81,6 @@ class Drizzle {
events events
) )
if (this.contracts[drizzleContract.contractName]) {
throw new Error(
`Contract already exists: ${drizzleContract.contractName}`
)
}
this.store.dispatch({ type: ContractActions.CONTRACT_INITIALIZING, contractConfig }) this.store.dispatch({ type: ContractActions.CONTRACT_INITIALIZING, contractConfig })
this.contracts[drizzleContract.contractName] = drizzleContract this.contracts[drizzleContract.contractName] = drizzleContract

2
src/accountBalances/accountBalancesMiddleware.js

@ -1,4 +1,4 @@
import { WEB3_INITIALIZED } from '../web3/constants' import { WEB3_INITIALIZED } from '../web3/web3Actions'
import { accountBalancesFetching } from './accountBalancesActions' import { accountBalancesFetching } from './accountBalancesActions'
import { ACCOUNTS_FETCHED } from '../accounts/accountsActions' import { ACCOUNTS_FETCHED } from '../accounts/accountsActions'

2
src/accountBalances/accountBalancesSaga.js

@ -1,7 +1,7 @@
import { call, put, select, takeLatest } from 'redux-saga/effects' import { call, put, select, takeLatest } from 'redux-saga/effects'
import { import {
ACCOUNT_BALANCES_FAILED,
ACCOUNT_BALANCE_FETCHED, ACCOUNT_BALANCE_FETCHED,
ACCOUNT_BALANCES_FAILED,
ACCOUNT_BALANCES_FETCHED, ACCOUNT_BALANCES_FETCHED,
ACCOUNT_BALANCES_FETCHING ACCOUNT_BALANCES_FETCHING
} from './accountBalancesActions' } from './accountBalancesActions'

6
src/accounts/accountsMiddleware.js

@ -1,14 +1,14 @@
import { WEB3_INITIALIZED } from '../web3/constants' import { WEB3_INITIALIZED } from '../web3/web3Actions'
import { accountsFetched, accountsListening } from './accountsActions' import { accountsFetched, accountsListening } from './accountsActions'
export const accountsMiddleware = () => store => next => action => { export const accountsMiddleware = web3 => store => next => action => {
const { type } = action const { type } = action
if (type === WEB3_INITIALIZED) { if (type === WEB3_INITIALIZED) {
if(!window.ethereum) if(!window.ethereum)
console.warn('No Metamask detected, not subscribed to account changes!') console.warn('No Metamask detected, not subscribed to account changes!')
else { else {
const { web3 } = action; web3 = action.web3;
window.ethereum.on('accountsChanged', accounts => { window.ethereum.on('accountsChanged', accounts => {
// For some reason accounts here are returned with lowercase letters, so we need to patch them // For some reason accounts here are returned with lowercase letters, so we need to patch them
let patchedAccounts = Array.from(accounts); let patchedAccounts = Array.from(accounts);

2
src/blocks/blocksSaga.js

@ -1,5 +1,5 @@
import { END, eventChannel } from 'redux-saga' import { END, eventChannel } from 'redux-saga'
import { call, put, take, takeEvery, takeLatest, all } from 'redux-saga/effects' import { all, call, put, take, takeEvery, takeLatest } from 'redux-saga/effects'
import * as BlocksActions from './blockActions' import * as BlocksActions from './blockActions'
/* /*

1
src/contractStateUtils.js

@ -22,6 +22,7 @@ export const generateContractInitialState = contractConfig => {
return { return {
initialized: false, initialized: false,
synced: false, synced: false,
deployed: true,
...objectOfConstants ...objectOfConstants
} }
} }

1
src/contracts/constants.js

@ -4,6 +4,7 @@ export const EVENT_ERROR = 'EVENT_ERROR'
export const LISTEN_FOR_EVENT = 'LISTEN_FOR_EVENT' export const LISTEN_FOR_EVENT = 'LISTEN_FOR_EVENT'
export const CONTRACT_INITIALIZING = 'CONTRACT_INITIALIZING' export const CONTRACT_INITIALIZING = 'CONTRACT_INITIALIZING'
export const CONTRACT_INITIALIZED = 'CONTRACT_INITIALIZED' export const CONTRACT_INITIALIZED = 'CONTRACT_INITIALIZED'
export const CONTRACT_NOT_DEPLOYED = 'CONTRACT_NOT_DEPLOYED'
export const GOT_CONTRACT_VAR = 'GOT_CONTRACT_VAR' export const GOT_CONTRACT_VAR = 'GOT_CONTRACT_VAR'
export const DELETE_CONTRACT = 'DELETE_CONTRACT' export const DELETE_CONTRACT = 'DELETE_CONTRACT'
export const CONTRACT_SYNCING = 'CONTRACT_SYNCING' export const CONTRACT_SYNCING = 'CONTRACT_SYNCING'

11
src/contracts/contractsReducer.js

@ -29,6 +29,17 @@ const contractsReducer = (state = initialState, action) => {
} }
} }
// Contract not found on the current network
if (action.type === ContractActions.CONTRACT_NOT_DEPLOYED) {
return {
...state,
[action.name]: {
...state[action.name],
deployed: false
}
}
}
if (action.type === ContractActions.DELETE_CONTRACT) { if (action.type === ContractActions.DELETE_CONTRACT) {
const { [action.contractName]: omitted, ...rest } = state const { [action.contractName]: omitted, ...rest } = state
return rest return rest

18
src/contracts/contractsSaga.js

@ -199,7 +199,7 @@ function * callCallContractFn ({
} catch (error) { } catch (error) {
console.error(error) console.error(error)
var errorArgs = { const errorArgs = {
name: contract.contractName, name: contract.contractName,
variable: contract.abi[fnIndex].name, variable: contract.abi[fnIndex].name,
argsHash: argsHash, argsHash: argsHash,
@ -230,8 +230,8 @@ function * callSyncContract (action) {
delete contractFnsState.events delete contractFnsState.events
// Iterate over functions and hashes // Iterate over functions and hashes
for (var fnName in contractFnsState) { for (let fnName in contractFnsState) {
for (var argsHash in contractFnsState[fnName]) { for (let argsHash in contractFnsState[fnName]) {
const fnIndex = contractFnsState[fnName][argsHash].fnIndex const fnIndex = contractFnsState[fnName][argsHash].fnIndex
const args = contractFnsState[fnName][argsHash].args const args = contractFnsState[fnName][argsHash].args
@ -271,6 +271,18 @@ function isSendOrCallOptions (options) {
return false return false
} }
export function * isContractDeployed ({ web3, contractConfig }) {
const networkId = yield call(web3.eth.net.getId);
if(contractConfig.networks[networkId]){
const contractAddress = contractConfig.networks[networkId].address;
const fetchedByteCode = yield call(web3.eth.getCode, contractAddress);
if(fetchedByteCode === contractConfig.deployedBytecode)
return true;
}
return false;
}
function * contractsSaga () { function * contractsSaga () {
yield takeEvery(ContractActions.SEND_CONTRACT_TX, callSendContractTx) yield takeEvery(ContractActions.SEND_CONTRACT_TX, callSendContractTx)
yield takeEvery(ContractActions.CALL_CONTRACT_FN, callCallContractFn) yield takeEvery(ContractActions.CALL_CONTRACT_FN, callCallContractFn)

9
src/drizzle-middleware.js

@ -1,6 +1,7 @@
import * as DrizzleActions from './drizzleStatus/drizzleActions' import * as DrizzleActions from './drizzleStatus/drizzleActions'
import * as ContractActions from './contracts/constants' import * as ContractActions from './contracts/constants'
import { ACCOUNTS_FETCHED } from './accounts/accountsActions' import { ACCOUNTS_FETCHED } from './accounts/accountsActions'
import { NETWORK_ID_CHANGED } from './web3/web3Actions'
export const drizzleMiddleware = drizzleInstance => store => next => action => { export const drizzleMiddleware = drizzleInstance => store => next => action => {
const { type } = action const { type } = action
@ -9,6 +10,14 @@ export const drizzleMiddleware = drizzleInstance => store => next => action => {
drizzleInstance = action.drizzle drizzleInstance = action.drizzle
} }
if (type === NETWORK_ID_CHANGED) {
store.dispatch({
type: DrizzleActions.DRIZZLE_INITIALIZING,
drizzle: drizzleInstance,
options: drizzleInstance.options
})
}
if ( if (
type === ACCOUNTS_FETCHED && type === ACCOUNTS_FETCHED &&
drizzleInstance && drizzleInstance &&

20
src/drizzleStatus/drizzleStatusSaga.js

@ -1,13 +1,15 @@
import { call, put, takeLatest } from 'redux-saga/effects' import { call, put, takeLatest } from 'redux-saga/effects'
// Initialization Functions // Initialization Functions
import { initializeWeb3, getNetworkId } from '../web3/web3Saga' import { getNetworkId, initializeWeb3 } from '../web3/web3Saga'
import { getAccounts } from '../accounts/accountsSaga' import { getAccounts } from '../accounts/accountsSaga'
import { getAccountBalances } from '../accountBalances/accountBalancesSaga' import { getAccountBalances } from '../accountBalances/accountBalancesSaga'
import * as DrizzleActions from './drizzleActions' import * as DrizzleActions from './drizzleActions'
import * as BlocksActions from '../blocks/blockActions' import * as BlocksActions from '../blocks/blockActions'
import { NETWORK_IDS, NETWORK_MISMATCH } from '../web3/constants' import { NETWORK_IDS, NETWORK_MISMATCH } from '../web3/web3Actions'
import { CONTRACT_NOT_DEPLOYED } from '../contracts/constants'
import { isContractDeployed } from '../contracts/contractsSaga'
export function * initializeDrizzle (action) { export function * initializeDrizzle (action) {
try { try {
@ -35,15 +37,19 @@ export function * initializeDrizzle (action) {
yield call(getAccountBalances, { web3 }) yield call(getAccountBalances, { web3 })
// Instantiate contracts passed through via options. // Instantiate contracts passed through via options.
for (var i = 0; i < options.contracts.length; i++) { for (let i = 0; i < options.contracts.length; i++) {
var contractConfig = options.contracts[i] const contractConfig = options.contracts[i]
var events = [] let events = []
var contractName = contractConfig.contractName const contractName = contractConfig.contractName;
if (contractName in options.events) { if (contractName in options.events) {
events = options.events[contractName] events = options.events[contractName]
} }
if(!(yield call(isContractDeployed, { web3, contractConfig }))){
yield put({ type: CONTRACT_NOT_DEPLOYED, name: contractName })
throw `Contract ${contractName} not deployed on this network`
}
yield call([drizzle, drizzle.addContract], contractConfig, events) yield call([drizzle, drizzle.addContract], contractConfig, events)
} }

2
src/generateStore.js

@ -1,5 +1,5 @@
import { all, fork } from 'redux-saga/effects' import { all, fork } from 'redux-saga/effects'
import { createStore, applyMiddleware, compose, combineReducers } from 'redux' import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
import createSagaMiddleware from 'redux-saga' import createSagaMiddleware from 'redux-saga'
import drizzleSagas from './rootSaga' import drizzleSagas from './rootSaga'
import drizzleReducers from './reducer' import drizzleReducers from './reducer'

40
src/index.js

@ -7,53 +7,27 @@ import * as EventActions from './contracts/constants'
import * as AccountActions from './accounts/accountsActions' import * as AccountActions from './accounts/accountsActions'
// Reducers // Reducers
import accountsReducer from './accounts/accountsReducer' import drizzleReducers from './reducer'
import accountBalancesReducer from './accountBalances/accountBalancesReducer'
import blocksReducer from './blocks/blocksReducer'
import contractsReducer from './contracts/contractsReducer'
import drizzleStatusReducer from './drizzleStatus/drizzleStatusReducer'
import transactionsReducer from './transactions/transactionsReducer'
import transactionStackReducer from './transactions/transactionStackReducer'
import web3Reducer from './web3/web3Reducer'
// Middleware // Middleware
import drizzleMiddleware from './drizzle-middleware' import drizzleMiddleware from './drizzle-middleware'
import accountsMiddleware from './accounts/accountsMiddleware' import accountsMiddleware from './accounts/accountsMiddleware'
import accountBalancesMiddleware from './accountBalances/accountBalancesMiddleware' import accountBalancesMiddleware from './accountBalances/accountBalancesMiddleware'
import web3Middleware from './web3/web3Middleware'
// Sagas // Sagas
import accountBalancesSaga from './accountBalances/accountBalancesSaga' import drizzleSagas from './rootSaga'
import blocksSaga from './blocks/blocksSaga'
import contractsSaga from './contracts/contractsSaga'
import drizzleStatusSaga from './drizzleStatus/drizzleStatusSaga'
const drizzleReducers = {
accounts: accountsReducer,
accountBalances: accountBalancesReducer,
contracts: contractsReducer,
currentBlock: blocksReducer,
drizzleStatus: drizzleStatusReducer,
transactions: transactionsReducer,
transactionStack: transactionStackReducer,
web3: web3Reducer
}
const drizzleMiddlewares = [ const drizzleMiddlewares = [
drizzleMiddleware, drizzleMiddleware,
accountsMiddleware, accountsMiddleware,
accountBalancesMiddleware accountBalancesMiddleware,
] web3Middleware
const drizzleSagas = [
accountBalancesSaga,
blocksSaga,
contractsSaga,
drizzleStatusSaga
] ]
const drizzleActions = { const drizzleActions = {
AccountActions, account: AccountActions,
EventActions event: EventActions
} }
export { export {

2
src/mergeOptions.js

@ -1,5 +1,5 @@
const merge = require('deepmerge'); const merge = require('deepmerge');
import isPlainObject from 'is-plain-object'; import isPlainObject from 'is-plain-object'
export default function (defaultOptions, newOptions) { export default function (defaultOptions, newOptions) {
return merge(defaultOptions, newOptions, { return merge(defaultOptions, newOptions, {

10
src/web3/constants.js → src/web3/web3Actions.js

@ -3,7 +3,9 @@ export const WEB3_INITIALIZED = 'WEB3_INITIALIZED'
export const WEB3_FAILED = 'WEB3_FAILED' export const WEB3_FAILED = 'WEB3_FAILED'
export const WEB3_USER_DENIED = 'WEB3_USER_DENIED' export const WEB3_USER_DENIED = 'WEB3_USER_DENIED'
export const NETWORK_ID_FETCHING = 'NETWORK_ID_FETCHING'
export const NETWORK_ID_FETCHED = 'NETWORK_ID_FETCHED' export const NETWORK_ID_FETCHED = 'NETWORK_ID_FETCHED'
export const NETWORK_ID_CHANGED = 'NETWORK_ID_CHANGED'
export const NETWORK_ID_FAILED = 'NETWORK_ID_FAILED' export const NETWORK_ID_FAILED = 'NETWORK_ID_FAILED'
export const NETWORK_MISMATCH = 'NETWORK_MISMATCH' export const NETWORK_MISMATCH = 'NETWORK_MISMATCH'
@ -15,3 +17,11 @@ export const NETWORK_IDS = {
kovan: 42, kovan: 42,
ganache: 5777 ganache: 5777
} }
export function networkIdChanged (web3, networkId) {
return {
type: NETWORK_ID_CHANGED,
web3,
networkId
}
}

23
src/web3/web3Middleware.js

@ -0,0 +1,23 @@
import { networkIdChanged, WEB3_INITIALIZED } from './web3Actions'
export const web3Middleware = web3 => store => next => action => {
const { type } = action
if (type === WEB3_INITIALIZED) {
if(!window.ethereum)
console.warn('No Metamask detected, not subscribed to network changes!')
else {
web3 = action.web3;
window.ethereum.on('networkChanged', (networkId) => {
// Warning: 'networkChanged' is deprecated (EIP-1193)
const storedNetworkId = store.getState().web3.networkId;
if(storedNetworkId && networkId !== storedNetworkId)
store.dispatch(networkIdChanged(web3, networkId));
});
}
}
return next(action)
}
const initializedMiddleware = web3Middleware(undefined)
export default initializedMiddleware

5
src/web3/web3Reducer.js

@ -1,4 +1,4 @@
import * as Action from './constants' import * as Action from './web3Actions'
const initialState = { const initialState = {
status: '' status: ''
@ -33,7 +33,8 @@ const web3Reducer = (state = initialState, action) => {
} }
} }
if (action.type === Action.NETWORK_ID_FETCHED) { if (action.type === Action.NETWORK_ID_FETCHED
|| action.type === Action.NETWORK_ID_CHANGED) {
return { return {
...state, ...state,
networkId: action.networkId networkId: action.networkId

17
src/web3/web3Saga.js

@ -1,12 +1,11 @@
import { call, put } from 'redux-saga/effects' import { call, put } from 'redux-saga/effects'
import * as Action from './constants' import * as Action from './web3Actions'
const Web3 = require('web3'); const Web3 = require('web3');
/* /*
* Initialization * Initialization
*/ */
export function * initializeWeb3 (options) { export function * initializeWeb3 (options) {
try { try {
let web3 = {} let web3 = {}
@ -35,7 +34,6 @@ export function * initializeWeb3 (options) {
} catch (error) { } catch (error) {
console.error(error) console.error(error)
yield put({ type: Action.WEB3_FAILED }) yield put({ type: Action.WEB3_FAILED })
return
} }
} else if (typeof window.web3 !== 'undefined') { } else if (typeof window.web3 !== 'undefined') {
// Checking if Web3 has been injected by the browser (Mist/MetaMask) // Checking if Web3 has been injected by the browser (Mist/MetaMask)
@ -71,20 +69,19 @@ export function * initializeWeb3 (options) {
} }
/* /*
* Network ID * Fetch Network ID
*/ */
export function * getNetworkId ({ web3 }) { export function * getNetworkId ({ web3 }) {
try { try {
const networkId = yield call(web3.eth.net.getId) const networkId = yield call(web3.eth.net.getId);
yield put({ type: Action.NETWORK_ID_FETCHED, networkId }) yield put({ type: Action.NETWORK_ID_FETCHED, networkId });
return networkId return networkId
} catch (error) { } catch (error) {
yield put({ type: Action.NETWORK_ID_FAILED, error }) yield put({ type: Action.NETWORK_ID_FAILED, error });
console.error('Error fetching network ID:') console.error('Error fetching network ID:');
console.error(error) console.error(error);
} }
} }

Loading…
Cancel
Save