f-o-a-m / purescript-web3

a purescript library for the web3 api
Apache License 2.0
127 stars 24 forks source link

Parametrizing http provider #24

Closed biern closed 6 years ago

biern commented 6 years ago

Hi! I have a use case, where I would like to parametrize the http provider URL based on a user config. if I understand the code correctly:

XertroV commented 6 years ago

I'm also wondering this.

One possibility is to have getAsyncProvider call an FFI function w/ a global variable. (yuck)

Another one is that httpProvider is String -> Eff e Provider so it might be possible to use something like an IORef.


Here's a working example. I'm not fond of the two unsafe calls, but they should be okay.

data HttpProvider

http :: Proxy HttpProvider
http = Proxy 

userSetHttpProvider :: Ref String
userSetHttpProvider = unsafePerformEff $ newRef "https://mainnet.infura.io/"

setHttpProvider :: forall e. String -> Eff (ref :: REF | e) Unit
setHttpProvider newProvider = writeRef userSetHttpProvider newProvider

getHttpProvider :: forall e. Eff (ref :: REF | e) String
getHttpProvider = readRef userSetHttpProvider

instance isAsyncHttp :: IsAsyncProvider HttpProvider where
    getAsyncProvider = unsafeCoerceWeb3 <<< Web3 <<< liftEff $ httpProvider =<< getHttpProvider

runWeb3_ :: forall e a. Web3 HttpProvider e a -> Aff (eth :: ETH | e) a
runWeb3_ = runWeb3 http
martyall commented 6 years ago

I'm more in favor of something what @XertroV is proposing. Our basic assumption at this point is that in a browser based application you would have little use to dynamically configure the provider because of metamask. As I see it, there are a few possible solutions for dynamic configuration

  1. Use a Ref
  2. the monadic context for the provider return type should change from Eff to either Aff or Web3, allowing for the possibility of reading an environment variable, reading from a Ref, querying configuration from a another server, etc.

We typically use environment variables for all of our configuration, so we haven't really run up against these kinds of problems. How does this sound?

-- Edit To better address the problem, can you describe a situation in which want to dynamically change the provider configuration which is not already handled by metamask, and where you don't have access to environment variables? It would help me understand what we're up against.

biern commented 6 years ago

Thanks @XertroV for sharing the snippet and @blinky3713 for explaining the motivation behind getAsyncProvider!

To better address the problem, can you describe a situation in which want to dynamically change the provider configuration which is not already handled by metamask, and where you don't have access to environment variables? It would help me understand what we're up against.

Well, my main use case is just to fallback to HTTPProvider in situations where metamask is not available. Right now I'm storing the provider url just in the app state, but I can easily read it from environment variable as well, so wouldn't be a problem for now. Seeing how getAsyncProvided is implemented I was wondering though how would I go about implementing a network switcher for the end user at a later point, similar to what myetherwallet.com does, but @XertroV snippet using REFs answers that question.

Currently in the app that I'm working on I'm maintaining a very limited set of bindings to Web3 and had a considerably different workflow regarding web3 initialization and usage:

So far I had no problems with this approach. I'm not sure which workflow is more correct or reliable. Just getAsyncProvider feels a bit hacky. It can only be parametrized by reading state from Eff and if I read the code correctly is initialized on each web3 call (please correct me if I'm wrong here).

I'd really like to use your library, it looks like a ton of good work. I already tried the contract typings generator and it works great!

martyall commented 6 years ago

The assumption is that you would do something like

data HttpProvider

instance asyncProviderHttp :: IsAsyncProvider HttpProvider where
   getAsyncProvider = do
     url <- {getEnv "NODE_URL", readRef nodeURL, etc}
     httpProvider url

I'm not sure that it's initialized on each web3 call, only if the web3 object isn't defined or you are changing the provider url. see https://github.com/f-o-a-m/purescript-web3/blob/master/src/Network/Ethereum/Web3/Provider.js#L14

martyall commented 6 years ago

I think that unless you need to be able to do something which is not currently possible, or if there is something wrong with the implementation of the way things are now, we're going to probably keep it like this for now. The reason why we want the type level parameterization of the provider is that it will soon be the case that metamask will offer some services that your ethereum client will not, like signing messages for example, and perhaps other things useful for state channels.