At Gnosis, we want to make it as easy as possible for all users to participate in decentralized prediction markets. To get valuable product feedback, we think it’s essential to involve the entire community in testing our application. That’s why we came up with the idea to organize the prediction-market tournament Olympia, encouraging users to trade in prediction markets and win GNO tokens.

Management Interface Fork

We also took advantage of this opportunity to integrate new features and take several architecture decisions. The first decision we made was to fork our core product, the Gnosis Management Interface and establish a mechanism for back-merging changes, fixes, and features we had implemented in both projects—our Management Interface and Olympia.

We started by reviewing our current software development process and established a clear, structured process to be followed across all Gnosis products, starting with Olympia: Product Managers and Product Owners had to be in constant communication to check, test, and assure that shared tickets are implemented in both the Olympia and the Management interface; back-end engineers had to configure servers according to the project’s network necessities; and front-end developers had to find a way to separate code used for Olympia from the one used in the Management Interface, making it easier to merge processes.

uPort Integration

We also decided to integrate uPort as a wallet provider for managing all transactions when trading in prediction markets, including our play-money token OLY.

Uport will be used in two ways:

1) To log into the interface by scanning a QR code

2) To approve every single transaction by responding to a push notification or by scanning a QR code

During our integration process with uPort, we’ve faced a few challenges:

1. Code Integration of a new Wallet Provider

When integrating uPort, the most difficult thing was to correctly isolate uPort errors from those directly related with our app, test them on a test network (we use the ConsenSys Rinkeby network), and report them to the uPort team.

Coding-wise, we have implemented a Publisher/Subscriber system where each respective Provider can publish their changes to the parent integration class, reducing overall load and serving as a solid framework for future provider integrations.

Providers update interval



class Uport extends BaseIntegration { … constructor() { super() this.watcher = setInterval(() => { this.watch(‘balance’, this.getBalance) this.watch(‘network’, this.getNetwork) }, Uport.watcherInterval) }

Another improvement is the initialization of providers. We have decoupled initialization from a DOM Higher Order Component (HOC) and isolated it as an external middleware service. What this translates to is improved app execution time and higher failure tolerance as we are assured providers are initialized once.

2. Initialization & Session Management

In uPort’s case, we can speak about two initialization processes: the uPort-GnosisJS initialization for simpler and more robust transactions on the app, and the uPort-User initialization for implementing a proper session-management solution.

Initialization of Providers as a middleware

export default store => next => (action) => { ... if (type === ‘INIT_PROVIDERS’) {

const providerOptions = {

...

} Promise.all(map(walletIntegrations,

integration => integration.initialize(providerOptions)))

} return handledAction

}

Init 1 of 2 [uPort — Gnosisjs registration and initialization]

async initialize(opts) { super.initialize(opts) this.runProviderRegister(this, {

priority: Uport.providerPriority,

account: opts.uportDefaultAccount,

}) this.uport = await initUportConnector(Uport.USE_NOTIFICATIONS) this.web3 = await this.uport.getWeb3()

this.provider = await this.uport.getProvider()

this.network = await this.getNetwork()

this.networkId = await this.getNetworkId() this.account =

opts.uportDefaultAccount || (await this.getAccount()) return this.runProviderUpdate(this, {

available: true,

network: this.network,

networkId: this.networkId,

account: this.account,

})

.then(async () => {

if (!this.account) {

return

}

await opts.initGnosis()

opts.dispatch(fetchOlympiaUserData(this.account))

})

}

Step 2 of 2 [uPort — User, necessary for session management]

In the previous code snippet, I marked in bold:

this.uport = await initUportConnector(Uport.USE_NOTIFICATIONS)

Note that initUportConnector behaves differently based on uPort’s mode (that is, approving a transaction via QR code or via a push notification). For dealing with the connection itself, we have created a helper class called connector.js . It is within this class that you’ll find the initUportConnector method that was used previously.

// src/integrations/uport/connector.js const initUportConnector = async (useNotifications) => { const provider = useNotifications

? await import(‘./uportNotifications’)

: await import(‘./uportQr’) return provider.default(

uport,

requestCredentials,

getCredentialsFromLocalStorage) }

These methods work as a kind of “Factory Pattern” where we load the provider based on the type.

As you can probably imagine, the default method of both providers should contain the logic for knowing if the stored credential is valid or not. If it is not valid, we ask the user to log into the application.