How we use TypeScript at Vega

Edd Hannay
Vega Protocol
Published in
4 min readMay 7, 2019

--

The Vega trading interface was demoed at KR1’s KryptoNights in London earlier this year

If you’ve read How did we get here? Pt. 1, then you already have a sense of how we’ve been building Vega’s core infrastructure. For Vega’s derivatives trading UI, which we call the Vega trading interface, we’ve been using TypeScript since day one. In this post we outline some of the tools we use to improve our development experience.

The Vega trading interface is built to feel familiar, but powerful enough to support complex trading strategies on an entirely decentralised network. You can see a brief overview in our recent presentation at KR1’s KryptoNights, which will have to satisfy you until we deploy it publicly.

We chose TypeScript because of its ecosystem of tools. Those tools allow us to use the types defined in Vega’s core systems to shape the GraphQL API, which connects from the Go-based infrastructure all the way through to the front end.

For an overview of TypeScript, the benefits outlined in Slack’s ‘TypeScript at Slack’ sum up many of the great things that it offers. That post is approaching two years old, but the main takeaways still hold true — or have improved in the meantime. The recent React Podcast episode, ‘Be Super with TypeScript’ is another great introduction.

We’ll save the node and blockchain architecture for another post, but from a browser point of view, we have a simple client-server architecture. A user’s browser connects to a single network node, which exposes various APIs that allow the client to authenticate and interact with the Vega network through a few commonly used protocols. The trading interface uses GraphQL, which is the core of how consistent data types are communicated to the client.

Where data starts

The single source of truth for types in the core are a set of Protocol Buffers. Using protoc we generate the Go structs that are used throughout the core, and each API is then built in a layer on top of these. The glue between these objects and the GraphQL endpoint is GQLGen, which takes a GraphQL schema as input and generates skeleton resolvers and functions. These functions map the internal data types to the fields defined in the schema — and as a result of Go’s type system any changes, for example referencing a field that no longer exists, or a change in the data type, will be flagged up at compile time.

GraphQL and TypeScript

On the front end, we went with Apollo as our GraphQL client. Apollo’s command line tools include a command for generating TypeScript types from our GraphQL schema. Pointing apollo codegen at our codebase and a GraphQL endpoint produces a folder full of TypeScript types for all the queries we use in the system. So when we want to add a new UI component we are confident that the query shape we are using, and the data for any variables we are passing it, will yield the result we expect.

To help illustrate this, let’s run through how this looks in our codebase, from the query right through to how its resulting data is surfaced in a React component.

Here is an example query that asks for all orders for a given party. For the sake of this example, the component we are creating will only need a few of the properties from our Order type.

export const orderQuery = gql`
query order($party: String!) {
party(name: $party) {
orders {
id,
price,
size,
market {
name
}
}
}
}

After running apollo codegen:generate, the following types are created. The names for the generated interfaces will help you relate the generated types to the query, which can help readability, but editors should be set up with good TypeScript integration so you can jump directly to the definition.

export interface orderVariables {
party: string
}

export interface order_party_orders_market {
__typename: 'Market';
name: string;
}

export interface order_vega_party_orders {
__typename: 'Order';
id: string;
price: string;
size: string;
market: order_vega_party_orders_market;
}

And finally, we have type safety right up through to the UI level, which in our case is a React component.

import * as React from 'react'
import { compose } from 'react-apollo'
import { OrderListProps, withOrders } from '../hoc/with-orders'

class OrderView extends React.Component<OrderListProps> {
public render () {
const = { party, loading } = this.props.data

if (loading) {
return <div>Please wait for Vega goodness!</div>
}

return (
<ul>
{party.orders.map(order => (
<li key={order.id}>
Order for {order.size} at {order.price} in {order.market.name}
</li>
))}
</ul>
)
}
}

export const OrderList = compose(withOrders)(OrderListView)

Plugging all of this together, we have types defined in the trading core making their way over to the client, giving us consistent types throughout the system. This has many benefits, including excellent autocompletion in Visual Studio Code:

Having types checked end-to-end has been a big productivity boost. While this post has focused on a GraphQL client, similar tools are available for REST APIs (swagger-codegen) and GRPC (ts-protoc-gen), so we’re looking forward to exploring how to integrate similar approaches in future Vega API client projects.

Written by Edd & Matt.

--

--