about summary refs log tree commit diff
path: root/tools/monzo_ynab
diff options
context:
space:
mode:
Diffstat (limited to 'tools/monzo_ynab')
-rw-r--r--tools/monzo_ynab/.envrc8
-rw-r--r--tools/monzo_ynab/.gitignore3
-rw-r--r--tools/monzo_ynab/README.md41
-rw-r--r--tools/monzo_ynab/auth.go101
-rw-r--r--tools/monzo_ynab/job.nix12
-rw-r--r--tools/monzo_ynab/main.go43
-rw-r--r--tools/monzo_ynab/monzo/client.go52
-rw-r--r--tools/monzo_ynab/monzo/serde.go82
-rw-r--r--tools/monzo_ynab/requests.txt80
-rw-r--r--tools/monzo_ynab/shell.nix10
-rw-r--r--tools/monzo_ynab/tokens.go283
-rw-r--r--tools/monzo_ynab/tokens.nix23
-rw-r--r--tools/monzo_ynab/ynab/client.go24
-rw-r--r--tools/monzo_ynab/ynab/serde.go52
14 files changed, 0 insertions, 814 deletions
diff --git a/tools/monzo_ynab/.envrc b/tools/monzo_ynab/.envrc
deleted file mode 100644
index f368d0b7e813..000000000000
--- a/tools/monzo_ynab/.envrc
+++ /dev/null
@@ -1,8 +0,0 @@
-source_up
-use_nix
-export monzo_client_id="$(jq -j '.monzo | .clientId' < ~/briefcase/secrets.json)"
-export monzo_client_secret="$(jq -j '.monzo | .clientSecret' < ~/briefcase/secrets.json)"
-export ynab_personal_access_token="$(jq -j '.ynab | .personalAccessToken' < ~/briefcase/secrets.json)"
-export ynab_account_id="$(jq -j '.ynab | .accountId' < ~/briefcase/secrets.json)"
-export ynab_budget_id="$(jq -j '.ynab | .budgetId' < ~/briefcase/secrets.json)"
-export store_path="$(pwd)"
diff --git a/tools/monzo_ynab/.gitignore b/tools/monzo_ynab/.gitignore
deleted file mode 100644
index e92078303bec..000000000000
--- a/tools/monzo_ynab/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/ynab/fixture.json
-/monzo/fixture.json
-/kv.json
diff --git a/tools/monzo_ynab/README.md b/tools/monzo_ynab/README.md
deleted file mode 100644
index 4ccbb35d8c5d..000000000000
--- a/tools/monzo_ynab/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# monzo_ynab
-
-Exporting Monzo transactions to my YouNeedABudget.com (i.e. YNAB) account. YNAB
-unfortunately doesn't currently offer an Monzo integration. As a workaround and
-a practical excuse to learn Go, I decided to write one myself.
-
-This job is going to run N times per 24 hours. Monzo offers webhooks for
-reacting to certain types of events. I don't expect I'll need realtime data for
-my YNAB integration. That may change, however, so it's worth noting.
-
-## Installation
-
-Like many other packages in this repository, `monzo_ynab` is packaged using
-Nix. To install and use, you have two options:
-
-You can install using `nix-build` and then run the resulting
-`./result/bin/monzo_ynab`.
-
-```shell
-> nix-build . && ./result/bin/monzo_ynab
-```
-
-Or you can install using `nix-env` if you'd like to create the `monzo_ynab`
-symlink.
-
-```shell
-> nix-env -f ~/briefcase/monzo_ynab -i
-```
-
-## Deployment
-
-While this project is currently not deployed, my plan is to host it on Google
-Cloud and run it as a Cloud Run application. What I don't yet know is whether or
-not this is feasible or a good idea. One complication that I foresee is that the
-OAuth 2.0 login flow requires a web browser until the access token and refresh
-tokens are acquired. I'm unsure how to workaround this at the moment.
-
-For more information about the general packaging and deployment strategies I'm
-currently using, refer to the [deployments][deploy] writeup.
-
-[deploy]: ../deploy/README.md
diff --git a/tools/monzo_ynab/auth.go b/tools/monzo_ynab/auth.go
deleted file mode 100644
index b66bacb10687..000000000000
--- a/tools/monzo_ynab/auth.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package auth
-
-////////////////////////////////////////////////////////////////////////////////
-// Dependencies
-////////////////////////////////////////////////////////////////////////////////
-
-import (
-	"encoding/json"
-	"fmt"
-	"log"
-	"net/http"
-	"net/url"
-	"os"
-	"os/exec"
-	"utils"
-)
-
-////////////////////////////////////////////////////////////////////////////////
-// Constants
-////////////////////////////////////////////////////////////////////////////////
-
-var (
-	BROWSER      = os.Getenv("BROWSER")
-	REDIRECT_URI = "http://localhost:8080/authorization-code"
-)
-
-////////////////////////////////////////////////////////////////////////////////
-// Types
-////////////////////////////////////////////////////////////////////////////////
-
-// This is the response returned from Monzo when we exchange our authorization
-// code for an access token. While Monzo returns additional fields, I'm only
-// interested in AccessToken and RefreshToken.
-type accessTokenResponse struct {
-	AccessToken  string `json:"access_token"`
-	RefreshToken string `json:"refresh_token"`
-	ExpiresIn    int    `json:"expires_in"`
-}
-
-type Tokens struct {
-	AccessToken  string
-	RefreshToken string
-	ExpiresIn    int
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Functions
-////////////////////////////////////////////////////////////////////////////////
-
-// Returns the access token and refresh tokens for the Monzo API.
-func GetTokensFromAuthCode(authCode string, clientID string, clientSecret string) *Tokens {
-	res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{
-		"grant_type":    {"authorization_code"},
-		"client_id":     {clientID},
-		"client_secret": {clientSecret},
-		"redirect_uri":  {REDIRECT_URI},
-		"code":          {authCode},
-	})
-	utils.FailOn(err)
-	defer res.Body.Close()
-	payload := &accessTokenResponse{}
-	json.NewDecoder(res.Body).Decode(payload)
-
-	return &Tokens{payload.AccessToken, payload.RefreshToken, payload.ExpiresIn}
-}
-
-// Open a web browser to allow the user to authorize this application. Return
-// the authorization code sent from Monzo.
-func GetAuthCode(clientID string) string {
-	// TODO(wpcarro): Consider generating a random string for the state when the
-	// application starts instead of hardcoding it here.
-	state := "xyz123"
-	url := fmt.Sprintf(
-		"https://auth.monzo.com/?client_id=%s&redirect_uri=%s&response_type=code&state=%s",
-		clientID, REDIRECT_URI, state)
-	exec.Command(BROWSER, url).Start()
-
-	authCode := make(chan string)
-	go func() {
-		log.Fatal(http.ListenAndServe(":8080",
-			http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-				// 1. Get authorization code from Monzo.
-				if req.URL.Path == "/authorization-code" {
-					params := req.URL.Query()
-					reqState := params["state"][0]
-					code := params["code"][0]
-
-					if reqState != state {
-						log.Fatalf("Value for state returned by Monzo does not equal our state. %s != %s", reqState, state)
-					}
-					authCode <- code
-
-					fmt.Fprintf(w, "Authorized!")
-				} else {
-					log.Printf("Unhandled request: %v\n", *req)
-				}
-			})))
-	}()
-	result := <-authCode
-	return result
-}
diff --git a/tools/monzo_ynab/job.nix b/tools/monzo_ynab/job.nix
deleted file mode 100644
index 1e10751012e2..000000000000
--- a/tools/monzo_ynab/job.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{ depot, briefcase, ... }:
-
-depot.buildGo.program {
-  name = "job";
-  srcs = [
-    ./main.go
-  ];
-  deps = with briefcase.gopkgs; [
-    kv
-    utils
-  ];
-}
diff --git a/tools/monzo_ynab/main.go b/tools/monzo_ynab/main.go
deleted file mode 100644
index 06f1944eab70..000000000000
--- a/tools/monzo_ynab/main.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// Exporting Monzo transactions to my YouNeedABudget.com (i.e. YNAB)
-// account. YNAB unfortunately doesn't currently offer an Monzo integration. As
-// a workaround and a practical excuse to learn Go, I decided to write one
-// myself.
-//
-// This job is going to run N times per 24 hours. Monzo offers webhooks for
-// reacting to certain types of events. I don't expect I'll need realtime data
-// for my YNAB integration. That may change, however, so it's worth noting.
-
-package main
-
-import (
-	"fmt"
-)
-
-var (
-	ynabAccountID = os.Getenv("ynab_account_id")
-)
-
-////////////////////////////////////////////////////////////////////////////////
-// Business Logic
-////////////////////////////////////////////////////////////////////////////////
-
-// Convert a Monzo transaction struct, `tx`, into a YNAB transaction struct.
-func toYnab(tx monzoSerde.Transaction) ynabSerde.Transaction {
-	return ynabSerde.Transaction{
-		Id: tx.Id,
-		Date: tx.Created,
-		Amount: tx.Amount,
-		Memo: tx.Notes,
-		AccountId: ynabAccountID,
-	}
-}
-
-func main() {
-	txs := monzo.TransactionsLast24Hours()
-	var ynabTxs []ynabSerde.Transaction{}
-	for tx := range txs {
-		append(ynabTxs, toYnab(tx))
-	}
-	ynab.PostTransactions(ynabTxs)
-	os.Exit(0)
-}
diff --git a/tools/monzo_ynab/monzo/client.go b/tools/monzo_ynab/monzo/client.go
deleted file mode 100644
index 8c6c41e29f40..000000000000
--- a/tools/monzo_ynab/monzo/client.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package monzoClient
-
-import (
-	"fmt"
-	"log"
-	"monzoSerde"
-	"net/http"
-	"net/url"
-	"strings"
-	"time"
-	"tokens"
-	"utils"
-)
-
-const (
-	accountID = "pizza"
-)
-
-type Client struct{}
-
-// Ensure that the token server is running and return a new instance of a Client
-// struct.
-func Create() *Client {
-	tokens.StartServer()
-	time.Sleep(time.Second * 1)
-	return &Client{}
-}
-
-// Returns a slice of transactions from the last 24 hours.
-func (c *Client) Transactions24Hours() []monzoSerde.Transaction {
-	token := tokens.AccessToken()
-	form := url.Values{"account_id": {accountID}}
-	client := http.Client{}
-	req, _ := http.NewRequest("POST", "https://api.monzo.com/transactions",
-		strings.NewReader(form.Encode()))
-	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
-	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-	req.Header.Add("User-Agent", "monzo-ynab")
-	res, err := client.Do(req)
-
-	utils.DebugRequest(req)
-	utils.DebugResponse(res)
-
-	if err != nil {
-		utils.DebugRequest(req)
-		utils.DebugResponse(res)
-		log.Fatal(err)
-	}
-	defer res.Body.Close()
-
-	return []monzoSerde.Transaction{}
-}
diff --git a/tools/monzo_ynab/monzo/serde.go b/tools/monzo_ynab/monzo/serde.go
deleted file mode 100644
index a38585eca632..000000000000
--- a/tools/monzo_ynab/monzo/serde.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// This package hosts the serialization and deserialization logic for all of the
-// data types with which our application interacts from the Monzo API.
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"time"
-)
-
-type TxMetadata struct {
-	FasterPayment string `json:"faster_payment"`
-	FpsPaymentId  string `json:"fps_payment_id"`
-	Insertion     string `json:"insertion"`
-	Notes         string `json:"notes"`
-	Trn           string `json:"trn"`
-}
-
-type TxCounterparty struct {
-	AccountNumber string `json:"account_number"`
-	Name          string `json:"name"`
-	SortCode      string `json:"sort_code"`
-	UserId        string `json:"user_id"`
-}
-
-type Transaction struct {
-	Id                         string    `json:"id"`
-	Created                    time.Time `json:"created"`
-	Description                string    `json:"description"`
-	Amount                     int       `json:"amount"`
-	Currency                   string    `json:"currency"`
-	Notes                      string    `json:"notes"`
-	Metadata                   TxMetadata
-	AccountBalance             int            `json:"account_balance"`
-	International              interface{}    `json:"international"`
-	Category                   string         `json:"category"`
-	IsLoad                     bool           `json:"is_load"`
-	Settled                    time.Time      `json:"settled"`
-	LocalAmount                int            `json:"local_amount"`
-	LocalCurrency              string         `json:"local_currency"`
-	Updated                    time.Time      `json:"updated"`
-	AccountId                  string         `json:"account_id"`
-	UserId                     string         `json:"user_id"`
-	Counterparty               TxCounterparty `json:"counterparty"`
-	Scheme                     string         `json:"scheme"`
-	DedupeId                   string         `json:"dedupe_id"`
-	Originator                 bool           `json:"originator"`
-	IncludeInSpending          bool           `json:"include_in_spending"`
-	CanBeExcludedFromBreakdown bool           `json:"can_be_excluded_from_breakdown"`
-	CanBeMadeSubscription      bool           `json:"can_be_made_subscription"`
-	CanSplitTheBill            bool           `json:"can_split_the_bill"`
-	CanAddToTab                bool           `json:"can_add_to_tab"`
-	AmountIsPending            bool           `json:"amount_is_pending"`
-	// Fees interface{} `json:"fees"`
-	// Merchant interface `json:"merchant"`
-	// Labels interface{} `json:"labels"`
-	// Attachments interface{} `json:"attachments"`
-	// Categories interface{} `json:"categories"`
-}
-
-// Attempts to encode a Monzo transaction struct into a string.
-func serializeTx(tx *Transaction) (string, error) {
-	x, err := json.Marshal(tx)
-	return string(x), err
-}
-
-// Attempts to parse a string encoding a transaction presumably sent from a
-// Monzo server.
-func deserializeTx(x string) (*Transaction, error) {
-	target := &Transaction{}
-	err := json.Unmarshal([]byte(x), target)
-	return target, err
-}
-
-func main() {
-	b, _ := ioutil.ReadFile("./fixture.json")
-	tx := string(b)
-	target, _ := deserializeTx(tx)
-	out, _ := serializeTx(target)
-	fmt.Println(out)
-}
diff --git a/tools/monzo_ynab/requests.txt b/tools/monzo_ynab/requests.txt
deleted file mode 100644
index 2da17c0b326a..000000000000
--- a/tools/monzo_ynab/requests.txt
+++ /dev/null
@@ -1,80 +0,0 @@
-################################################################################
-# YNAB
-################################################################################
-:ynab = https://api.youneedabudget.com/v1
-:ynab-access-token := (getenv "ynab_personal_access_token")
-:ynab-budget-id := (getenv "ynab_budget_id")
-:ynab-account-id := (getenv "ynab_account_id")
-
-# Test
-GET :ynab/budgets
-Authorization: Bearer :ynab-access-token
-
-# List transactions
-GET :ynab/budgets/:ynab-budget-id/transactions
-Authorization: Bearer :ynab-access-token
-
-# Post transactions
-POST :ynab/budgets/:ynab-budget-id/transactions
-Authorization: Bearer :ynab-access-token
-Content-Type: application/json
-{
-  "transactions": [
-    {
-      "account_id": ":ynab-account-id",
-      "date": "2019-12-30",
-      "amount": 10000,
-      "payee_name": "Richard Stallman",
-      "memo": "Not so free software after all...",
-      "cleared": "cleared",
-      "approved": true,
-      "flag_color": "red",
-      "import_id": "xyz-123"
-    }
-  ]
-}
-
-################################################################################
-# Monzo
-################################################################################
-:monzo = https://api.monzo.com
-:monzo-access-token := (getenv "monzo_cached_access_token")
-:monzo-refresh-token := (getenv "monzo_cached_refresh_token")
-:monzo-client-id := (getenv "monzo_client_id")
-:monzo-client-secret := (getenv "monzo_client_secret")
-:monzo-account-id := (getenv "monzo_account_id")
-
-# List transactions
-GET :monzo/transactions
-Authorization: Bearer :monzo-access-token
-account_id==:monzo-account-id
-
-# Refresh access token
-# According from the docs, the access token expires in 6 hours.
-POST :monzo/oauth2/token
-Content-Type: application/x-www-form-urlencoded
-Authorization: Bearer :monzo-access-token
-grant_type=refresh_token&client_id=:monzo-client-id&client_secret=:monzo-client-secret&refresh_token=:monzo-refresh-token
-
-################################################################################
-# Tokens server
-################################################################################
-:tokens = http://localhost:4242
-
-# Get tokens
-GET :tokens/tokens
-
-# Get application state for debugging purposes
-GET :tokens/state
-
-# Force refresh tokens
-POST :tokens/refresh-tokens
-
-# Set tokens
-POST :tokens/set-tokens
-Content-Type: application/json
-{
-  "access_token": "access-token",
-  "refresh_token": "refresh-token",
-  "expires_in": 120
-}
diff --git a/tools/monzo_ynab/shell.nix b/tools/monzo_ynab/shell.nix
deleted file mode 100644
index 910d7c1829e2..000000000000
--- a/tools/monzo_ynab/shell.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-let
-  briefcase = import <briefcase> {};
-  pkgs = briefcase.third_party.pkgs;
-in pkgs.mkShell {
-  buildInputs = [
-    pkgs.go
-    pkgs.goimports
-    pkgs.godef
-  ];
-}
diff --git a/tools/monzo_ynab/tokens.go b/tools/monzo_ynab/tokens.go
deleted file mode 100644
index 4be967ccb803..000000000000
--- a/tools/monzo_ynab/tokens.go
+++ /dev/null
@@ -1,283 +0,0 @@
-// Creating a Tokens server to manage my access and refresh tokens. Keeping this
-// as a separate server allows me to develop and use the access tokens without
-// going through client authorization.
-package main
-
-////////////////////////////////////////////////////////////////////////////////
-// Dependencies
-////////////////////////////////////////////////////////////////////////////////
-
-import (
-	"auth"
-	"encoding/json"
-	"fmt"
-	"io"
-	"kv"
-	"log"
-	"net/http"
-	"net/url"
-	"os"
-	"os/signal"
-	"syscall"
-	"time"
-	"utils"
-)
-
-////////////////////////////////////////////////////////////////////////////////
-// Types
-////////////////////////////////////////////////////////////////////////////////
-
-// This is the response from Monzo's API after we request an access token
-// refresh.
-type refreshTokenResponse struct {
-	AccessToken  string `json:"access_token"`
-	RefreshToken string `json:"refresh_token"`
-	ClientId     string `json:"client_id"`
-	ExpiresIn    int    `json:"expires_in"`
-}
-
-// This is the shape of the request from clients wishing to set state of the
-// server.
-type setTokensRequest struct {
-	AccessToken  string `json:"access_token"`
-	RefreshToken string `json:"refresh_token"`
-	ExpiresIn    int    `json:"expires_in"`
-}
-
-// This is our application state.
-type state struct {
-	accessToken  string `json:"access_token"`
-	refreshToken string `json:"refresh_token"`
-}
-
-type readMsg struct {
-	sender chan state
-}
-
-type writeMsg struct {
-	state  state
-	sender chan bool
-}
-
-type channels struct {
-	reads  chan readMsg
-	writes chan writeMsg
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Top-level Definitions
-////////////////////////////////////////////////////////////////////////////////
-
-var chans = &channels{
-	reads:  make(chan readMsg),
-	writes: make(chan writeMsg),
-}
-
-var (
-	monzoClientId     = os.Getenv("monzo_client_id")
-	monzoClientSecret = os.Getenv("monzo_client_secret")
-	storePath         = os.Getenv("store_path")
-)
-
-////////////////////////////////////////////////////////////////////////////////
-// Utils
-////////////////////////////////////////////////////////////////////////////////
-
-// Print the access and refresh tokens for debugging.
-func logTokens(access string, refresh string) {
-	log.Printf("Access: %s\n", access)
-	log.Printf("Refresh: %s\n", refresh)
-}
-
-func (state *state) String() string {
-	return fmt.Sprintf("state{\n\taccessToken: \"%s\",\n\trefreshToken: \"%s\"\n}\n", state.accessToken, state.refreshToken)
-}
-
-// Schedule a token refresh for `expiresIn` seconds using the provided
-// `refreshToken`. This will update the application state with the access token
-// and schedule an additional token refresh for the newly acquired tokens.
-func scheduleTokenRefresh(expiresIn int, refreshToken string) {
-	duration := time.Second * time.Duration(expiresIn)
-	timestamp := time.Now().Local().Add(duration)
-	// TODO(wpcarro): Consider adding a more human readable version that will
-	// log the number of hours, minutes, etc. until the next refresh.
-	log.Printf("Scheduling token refresh for %v\n", timestamp)
-	time.Sleep(duration)
-	log.Println("Refreshing tokens now...")
-	accessToken, refreshToken := refreshTokens(refreshToken)
-	log.Println("Successfully refreshed tokens.")
-	logTokens(accessToken, refreshToken)
-	setState(accessToken, refreshToken)
-}
-
-// Exchange existing credentials for a new access token and `refreshToken`. Also
-// schedule the next refresh. This function returns the newly acquired access
-// token and refresh token.
-func refreshTokens(refreshToken string) (string, string) {
-	// TODO(wpcarro): Support retries with exponential backoff.
-	res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{
-		"grant_type":    {"refresh_token"},
-		"client_id":     {monzoClientId},
-		"client_secret": {monzoClientSecret},
-		"refresh_token": {refreshToken},
-	})
-	if res.StatusCode != http.StatusOK {
-		// TODO(wpcarro): Considering panicking here.
-		utils.DebugResponse(res)
-	}
-	if err != nil {
-		utils.DebugResponse(res)
-		log.Fatal("The request to Monzo to refresh our access token failed.", err)
-	}
-	defer res.Body.Close()
-	payload := &refreshTokenResponse{}
-	err = json.NewDecoder(res.Body).Decode(payload)
-	if err != nil {
-		log.Fatal("Could not decode the JSON response from Monzo.", err)
-	}
-
-	go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken)
-
-	// Interestingly, JSON decoding into the refreshTokenResponse can success
-	// even if the decoder doesn't populate any of the fields in the
-	// refreshTokenResponse struct. From what I read, it isn't possible to make
-	// these fields as required using an annotation, so this guard must suffice
-	// for now.
-	if payload.AccessToken == "" || payload.RefreshToken == "" {
-		log.Fatal("JSON parsed correctly but failed to populate token fields.")
-	}
-
-	return payload.AccessToken, payload.RefreshToken
-}
-
-func persistTokens(access string, refresh string) {
-	log.Println("Persisting tokens...")
-	kv.Set(storePath, "monzoAccessToken", access)
-	kv.Set(storePath, "monzoRefreshToken", refresh)
-	log.Println("Successfully persisted tokens.")
-}
-
-// Listen for SIGINT and SIGTERM signals. When received, persist the access and
-// refresh tokens and shutdown the server.
-func handleInterrupts() {
-	// Gracefully handle interruptions.
-	sigs := make(chan os.Signal, 1)
-	done := make(chan bool)
-
-	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
-
-	go func() {
-		sig := <-sigs
-		log.Printf("Received signal to shutdown. %v\n", sig)
-		state := getState()
-		persistTokens(state.accessToken, state.refreshToken)
-		done <- true
-	}()
-
-	<-done
-	log.Println("Exiting...")
-	os.Exit(0)
-}
-
-// Set `accessToken` and `refreshToken` on application state.
-func setState(accessToken string, refreshToken string) {
-	msg := writeMsg{state{accessToken, refreshToken}, make(chan bool)}
-	chans.writes <- msg
-	<-msg.sender
-}
-
-// Return our application state.
-func getState() state {
-	msg := readMsg{make(chan state)}
-	chans.reads <- msg
-	return <-msg.sender
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Main
-////////////////////////////////////////////////////////////////////////////////
-
-func main() {
-	// Manage application state.
-	go func() {
-		state := &state{}
-		for {
-			select {
-			case msg := <-chans.reads:
-				log.Println("Reading from state...")
-				log.Println(state)
-				msg.sender <- *state
-			case msg := <-chans.writes:
-				log.Println("Writing to state.")
-				log.Printf("Old: %s\n", state)
-				*state = msg.state
-				log.Printf("New: %s\n", state)
-				// As an attempt to maintain consistency between application
-				// state and persisted state, everytime we write to the
-				// application state, we will write to the store.
-				persistTokens(state.accessToken, state.refreshToken)
-				msg.sender <- true
-			}
-		}
-	}()
-
-	// Retrieve cached tokens from store.
-	accessToken := fmt.Sprintf("%v", kv.Get(storePath, "monzoAccessToken"))
-	refreshToken := fmt.Sprintf("%v", kv.Get(storePath, "monzoRefreshToken"))
-
-	log.Println("Attempting to retrieve cached credentials...")
-	logTokens(accessToken, refreshToken)
-
-	if accessToken == "" || refreshToken == "" {
-		log.Println("Cached credentials are absent. Authorizing client...")
-		authCode := auth.GetAuthCode(monzoClientId)
-		tokens := auth.GetTokensFromAuthCode(authCode, monzoClientId, monzoClientSecret)
-		setState(tokens.AccessToken, tokens.RefreshToken)
-		go scheduleTokenRefresh(tokens.ExpiresIn, tokens.RefreshToken)
-	} else {
-		setState(accessToken, refreshToken)
-		// If we have tokens, they may be expiring soon. We don't know because
-		// we aren't storing the expiration timestamp in the state or in the
-		// store. Until we have that information, and to be safe, let's refresh
-		// the tokens.
-		go scheduleTokenRefresh(0, refreshToken)
-	}
-
-	// Gracefully handle shutdowns.
-	go handleInterrupts()
-
-	// Listen to inbound requests.
-	fmt.Println("Listening on http://localhost:4242 ...")
-	log.Fatal(http.ListenAndServe(":4242",
-		http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-			if req.URL.Path == "/refresh-tokens" && req.Method == "POST" {
-				state := getState()
-				go scheduleTokenRefresh(0, state.refreshToken)
-				fmt.Fprintf(w, "Done.")
-			} else if req.URL.Path == "/set-tokens" && req.Method == "POST" {
-				// Parse
-				payload := &setTokensRequest{}
-				err := json.NewDecoder(req.Body).Decode(payload)
-				if err != nil {
-					log.Fatal("Could not decode the user's JSON request.", err)
-				}
-
-				// Update application state
-				setState(payload.AccessToken, payload.RefreshToken)
-
-				// Refresh tokens
-				go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken)
-
-				// Ack
-				fmt.Fprintf(w, "Done.")
-			} else if req.URL.Path == "/state" && req.Method == "GET" {
-				// TODO(wpcarro): Ensure that this returns serialized state.
-				w.Header().Set("Content-type", "application/json")
-				state := getState()
-				payload, _ := json.Marshal(state)
-				io.WriteString(w, string(payload))
-			} else {
-				log.Printf("Unhandled request: %v\n", *req)
-			}
-		})))
-}
diff --git a/tools/monzo_ynab/tokens.nix b/tools/monzo_ynab/tokens.nix
deleted file mode 100644
index 97de09d741e9..000000000000
--- a/tools/monzo_ynab/tokens.nix
+++ /dev/null
@@ -1,23 +0,0 @@
-{ depot, briefcase, ... }:
-
-let
-  auth = depot.buildGo.package {
-    name = "auth";
-    srcs = [
-      ./auth.go
-    ];
-    deps = with briefcase.gopkgs; [
-      utils
-    ];
-  };
-in depot.buildGo.program {
-  name = "token-server";
-  srcs = [
-    ./tokens.go
-  ];
-  deps = with briefcase.gopkgs; [
-    kv
-    utils
-    auth
-  ];
-}
diff --git a/tools/monzo_ynab/ynab/client.go b/tools/monzo_ynab/ynab/client.go
deleted file mode 100644
index 0492b9071adc..000000000000
--- a/tools/monzo_ynab/ynab/client.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package client
-
-import (
-	"serde"
-)
-
-// See requests.txt for more details.
-func PostTransactions(accountID string, txs []serde.Transaction{}) error {
-	return map[string]string{
-		"transactions": [
-			{
-				"account_id": accountID,
-					"date": "2019-12-30",
-					"amount": 10000,
-					"payee_name": "Richard Stallman",
-					"memo": "Not so free software after all...",
-					"cleared": "cleared",
-					"approved": true,
-					"flag_color": "red",
-					"import_id": "xyz-123"
-			}
-		]
-	}
-}
diff --git a/tools/monzo_ynab/ynab/serde.go b/tools/monzo_ynab/ynab/serde.go
deleted file mode 100644
index 53dd33e83637..000000000000
--- a/tools/monzo_ynab/ynab/serde.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// This package hosts the serialization and deserialization logic for all of the
-// data types with which our application interacts from the YNAB API.
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"time"
-)
-
-type Transaction struct {
-	Id           string    `json:"id"`
-	Date         time.Time `json:"date"`
-	Amount       int       `json:"amount"`
-	Memo         string    `json:"memo"`
-	Cleared      string    `json:"cleared"`
-	Approved     bool      `json:"approved"`
-	FlagColor    string    `json:"flag_color"`
-	AccountId    string    `json:"account_id"`
-	AccountName  string    `json:"account_name"`
-	PayeeId      string    `json:"payeed_id"`
-	PayeeName    string    `json:"payee_name"`
-	CategoryId   string    `json:"category_id"`
-	CategoryName string    `json:"category_name"`
-	Deleted      bool      `json:"deleted"`
-	// TransferAccountId interface{} `json:"transfer_account_id"`
-	// TransferTransactionId interface{} `json:"transfer_transaction_id"`
-	// MatchedTransactionId interface{} `json:"matched_transaction_id"`
-	// ImportId interface{} `json:"import_id"`
-	// Subtransactions interface{} `json:"subtransactions"`
-}
-
-// Attempts to encode a YNAB transaction into a string.
-func serializeTx(tx *Transaction) (string, error) {
-	x, err := json.Marshal(tx)
-	return string(x), err
-}
-
-// Attempts to parse a string encoding a transaction presumably sent from a
-// YNAB server.
-func deserializeTx(x string) (*Transaction, error) {
-	target := &Transaction{}
-	err := json.Unmarshal([]byte(x), target)
-	return target, err
-}
-
-func main() {
-	target, _ := deserializeTx(tx)
-	out, _ := serializeTx(target)
-	fmt.Println(out)
-	fmt.Println(ynabOut)
-}