From 11206b6e0e6ddefd2679f719c116a1574efafeda Mon Sep 17 00:00:00 2001
From: Garet Halliday <ghalliday@gfxlabs.io>
Date: Wed, 21 Sep 2022 12:44:21 -0500
Subject: [PATCH] make it more better

---
 go.mod                            |  14 +-
 go.sum                            |  33 +-
 openrpc/cmd/cli.go                |  54 +-
 openrpc/eth-api.json              |   2 +-
 openrpc/generate/generate.go      | 271 ++-------
 openrpc/generate/generate_test.go |  83 ---
 openrpc/out/types.go              | 929 +++++++++++-------------------
 openrpc/parse/parse.go            | 121 ----
 openrpc/parse/parse_test.go       |  73 ---
 openrpc/parse/parsing.go          | 102 ----
 openrpc/templates/types.gotmpl    |  72 ++-
 openrpc/types/objects.go          |  79 ---
 openrpc/types/types.go            | 288 ++-------
 13 files changed, 486 insertions(+), 1635 deletions(-)
 delete mode 100644 openrpc/generate/generate_test.go
 delete mode 100644 openrpc/parse/parse_test.go
 delete mode 100644 openrpc/parse/parsing.go
 delete mode 100644 openrpc/types/objects.go

diff --git a/go.mod b/go.mod
index 66405e8..abc8a8b 100644
--- a/go.mod
+++ b/go.mod
@@ -9,31 +9,23 @@ require (
 	github.com/davecgh/go-spew v1.1.1
 	github.com/deckarep/golang-set v1.8.0
 	github.com/ethereum/go-ethereum v1.10.22
-	github.com/go-openapi/spec v0.20.7
 	github.com/gobuffalo/packr/v2 v2.8.3
-	github.com/imdario/mergo v0.3.13
+	github.com/iancoleman/strcase v0.2.0
 	github.com/json-iterator/go v1.1.12
-	github.com/test-go/testify v1.1.4
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce
 	nhooyr.io/websocket v1.8.7
-	sigs.k8s.io/yaml v1.3.0
 )
 
 require (
 	github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
 	github.com/go-ole/go-ole v1.2.1 // indirect
-	github.com/go-openapi/jsonpointer v0.19.5 // indirect
-	github.com/go-openapi/jsonreference v0.20.0 // indirect
-	github.com/go-openapi/swag v0.19.15 // indirect
 	github.com/go-stack/stack v1.8.0 // indirect
 	github.com/gobuffalo/logger v1.0.6 // indirect
 	github.com/gobuffalo/packd v1.0.1 // indirect
 	github.com/gorilla/websocket v1.4.2 // indirect
-	github.com/josharian/intern v1.0.0 // indirect
 	github.com/karrick/godirwalk v1.16.1 // indirect
 	github.com/klauspost/compress v1.10.3 // indirect
-	github.com/mailru/easyjson v0.7.6 // indirect
 	github.com/markbates/errx v1.1.0 // indirect
 	github.com/markbates/oncer v1.0.0 // indirect
 	github.com/markbates/safe v1.0.1 // indirect
@@ -41,15 +33,11 @@ require (
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
-	github.com/stretchr/testify v1.7.2 // indirect
 	github.com/tklauser/go-sysconf v0.3.5 // indirect
 	github.com/tklauser/numcpus v0.2.2 // indirect
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
 	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
 	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 523fc55..1b02745 100644
--- a/go.sum
+++ b/go.sum
@@ -67,7 +67,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -94,16 +93,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
 github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
-github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
-github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
-github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
-github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
-github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
-github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
-github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@@ -215,13 +204,11 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
+github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
-github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -239,15 +226,9 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
-github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
 github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
 github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
@@ -276,8 +257,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -319,8 +298,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
-github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
 github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
 github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
 github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
@@ -680,8 +657,6 @@ google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/l
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
@@ -692,9 +667,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -709,5 +682,3 @@ nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
-sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/openrpc/cmd/cli.go b/openrpc/cmd/cli.go
index 2805cb0..ba7c40d 100644
--- a/openrpc/cmd/cli.go
+++ b/openrpc/cmd/cli.go
@@ -1,16 +1,14 @@
 package main
 
+import "C"
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"gfx.cafe/open/jrpc/openrpc/generate"
 	"os"
 
-	"gfx.cafe/open/jrpc/openrpc/generate"
-	"gfx.cafe/open/jrpc/openrpc/parse"
 	"gfx.cafe/open/jrpc/openrpc/types"
 	"github.com/alecthomas/kong"
-	"github.com/gobuffalo/packr/v2"
 )
 
 var CLI struct {
@@ -25,28 +23,7 @@ type CompileCommand struct {
 }
 
 func (c *CompileCommand) Run() error {
-	openrpc := types.NewOpenRPCSpec1()
-	var err error
-	for _, v := range c.Methods {
-		err = openrpc.AddMethods(v)
-		if err != nil {
-			return err
-		}
-	}
-	for _, v := range c.Schemas {
-		err = openrpc.AddSchemas(v)
-		if err != nil {
-			return err
-		}
-	}
-	jzn, err := json.MarshalIndent(openrpc, "", " ")
-	if err != nil {
-		return err
-	}
-	err = os.WriteFile(c.Output, jzn, 0644)
-	if err != nil {
-		return err
-	}
+	// TODO
 	return nil
 }
 
@@ -64,31 +41,24 @@ func (c *GenerateCommand) Run() error {
 	if err != nil {
 		return err
 	}
-	parse.GetTypes(openrpc, openrpc.Objects)
-	box := packr.New("template", "./templates")
 
-	if err = generate.WriteFile(box, "server", c.Output, openrpc); err != nil {
-		return err
-	}
-	if err = generate.WriteFile(box, "types", c.Output, openrpc); err != nil {
+	if err = generate.Generate(openrpc, c.Templates, c.Output); err != nil {
 		return err
 	}
+
 	return nil
 }
 
-func readSpec(file string) (*types.OpenRPCSpec1, error) {
-	data, err := ioutil.ReadFile(file)
-	if err != nil {
-		return nil, err
-	}
-
-	spec := types.NewOpenRPCSpec1()
-	err = json.Unmarshal(data, spec)
+func readSpec(file string) (out *types.OpenRPC, err error) {
+	var data []byte
+	data, err = os.ReadFile(file)
 	if err != nil {
-		return nil, err
+		return
 	}
 
-	return spec, nil
+	out = new(types.OpenRPC)
+	err = json.Unmarshal(data, out)
+	return
 }
 
 func NewCLI() *kong.Context {
diff --git a/openrpc/eth-api.json b/openrpc/eth-api.json
index da2e5c0..06d85ef 100644
--- a/openrpc/eth-api.json
+++ b/openrpc/eth-api.json
@@ -1959,7 +1959,7 @@
    },
    "bytes65": {
     "type": "string",
-    "title": "bytes645",
+    "title": "bytes65",
     "pattern": "^0x[0-9a-f]{512}$"
    },
    "bytes8": {
diff --git a/openrpc/generate/generate.go b/openrpc/generate/generate.go
index 63e0334..c7268ed 100644
--- a/openrpc/generate/generate.go
+++ b/openrpc/generate/generate.go
@@ -2,242 +2,77 @@ package generate
 
 import (
 	"bytes"
-	"encoding/json"
 	"fmt"
+	"gfx.cafe/open/jrpc/openrpc/types"
+	"github.com/iancoleman/strcase"
 	"go/format"
 	"os"
-	"path"
 	"path/filepath"
-	"regexp"
-	"strings"
 	"text/template"
-
-	"gfx.cafe/open/jrpc/openrpc/types"
-	"gfx.cafe/open/jrpc/openrpc/util"
-	"github.com/go-openapi/spec"
-	"github.com/gobuffalo/packr/v2"
-	"github.com/imdario/mergo"
 )
 
-const (
-	params    = "Params"
-	result    = "Result"
-	goExt     = "go"
-	goTmplExt = goExt + "tmpl"
-)
-
-var ProgramName = "CHANGME"
-
-func schemaAsJSONPretty(s spec.Schema) string {
-	b, err := json.MarshalIndent(s, "", "    ")
-	if err != nil {
-		return ""
-	}
-	b = bytes.ReplaceAll(b, []byte("{"), []byte(""))
-	b = bytes.ReplaceAll(b, []byte("}"), []byte(""))
-	b = bytes.ReplaceAll(b, []byte(`"`), []byte(""))
-	b = bytes.ReplaceAll(b, []byte(`$ref: #`), []byte(""))
-
-	// Remove empty JSON $refs
-	reg := regexp.MustCompile(`^\s*\$ref.*$/mg`)
-	ss := reg.ReplaceAllString(string(b), "")
-
-	return ss
-}
-
-func maybeLookupComponentsContentDescriptor(cmpnts *types.Components, cd *types.ContentDescriptor) (rootCD *types.ContentDescriptor, err error) {
-	rootCD = cd
-	if cd == nil || cmpnts == nil {
-		return
-	}
-	if strings.Contains(cd.Schema.Ref.String(), "contentDescriptors") {
-		r := filepath.Base(cd.Schema.Ref.String())
-		rootCD = cmpnts.ContentDescriptors[r]
-		return
-	}
-	return
-}
-
-func schemaHazRef(sch spec.Schema) bool {
-	return sch.Ref.String() != ""
+var defaultTemplates = []string{
+	"./templates/types.gotmpl",
 }
 
-func derefSchemaRecurse(cts *types.Components, sch spec.Schema) spec.Schema {
-	if schemaHazRef(sch) {
-		sch = getSchemaFromRef(cts, sch.Ref)
-		sch = derefSchemaRecurse(cts, sch)
-	}
-	for i := range sch.OneOf {
-		got := derefSchemaRecurse(cts, sch.OneOf[i])
-		if err := mergo.Merge(&got, sch.OneOf[i]); err != nil {
-			panic(err.Error())
-		}
-		got.Schema = ""
-		sch.OneOf[i] = got
-	}
-	for i := range sch.AnyOf {
-		got := derefSchemaRecurse(cts, sch.AnyOf[i])
-		if err := mergo.Merge(&got, sch.AnyOf[i]); err != nil {
-			panic(err.Error())
-		}
-		got.Schema = ""
-		sch.AnyOf[i] = got
-	}
-	for i := range sch.AllOf {
-		got := derefSchemaRecurse(cts, sch.AllOf[i])
-		if err := mergo.Merge(&got, sch.AllOf[i]); err != nil {
-			panic(err.Error())
-		}
-		got.Schema = ""
-		sch.AllOf[i] = got
-	}
-	for k := range sch.Properties {
-		got := derefSchemaRecurse(cts, sch.Properties[k])
-		if err := mergo.Merge(&got, sch.Properties[k]); err != nil {
-			panic(err.Error())
-		}
-		got.Schema = ""
-		sch.Properties[k] = got
-	}
-	for k := range sch.PatternProperties {
-		got := derefSchemaRecurse(cts, sch.PatternProperties[k])
-		if err := mergo.Merge(&got, sch.PatternProperties[k]); err != nil {
-			panic(err.Error())
-		}
-		got.Schema = ""
-		sch.PatternProperties[k] = got
-	}
-	if sch.Items == nil {
-		return sch
-	}
-	if sch.Items.Len() > 1 {
-		for i := range sch.Items.Schemas {
-			got := derefSchemaRecurse(cts, sch.Items.Schemas[i])
-			if err := mergo.Merge(&got, sch.Items.Schemas[i]); err != nil {
-				panic(err.Error())
-			}
-			got.Schema = ""
-			sch.Items.Schemas[i] = got
-		}
-	} else {
-		// Is schema
-		got := derefSchemaRecurse(cts, *sch.Items.Schema)
-		if err := mergo.Merge(&got, sch.Items.Schema); err != nil {
-			panic(err.Error())
+var funcs = template.FuncMap{
+	"list": func(v ...any) []any {
+		return v
+	},
+	"camelCase": func(v string) string {
+		return strcase.ToCamel(v)
+	},
+	"goType": func(v string) string {
+		switch v {
+		case "boolean":
+			return "bool"
+		case "number":
+			return "float64"
+		case "string":
+			return "string"
+		case "null":
+			return "struct{}"
+		default:
+			panic(fmt.Sprintln("unknown go type:", v))
 		}
-		got.Schema = ""
-		sch.Items.Schema = &got
-	}
-
-	return sch
-}
-
-func getSchemaFromRef(cmpnts *types.Components, ref spec.Ref) (sch spec.Schema) {
-	if cmpnts == nil || ref.String() == "" {
-		return
-	}
-	r := filepath.Base(ref.String())
-	sch = cmpnts.Schemas[r] // Trust parser
-	return
-}
-
-func maybeMethodParams(method types.Method) string {
-	if len(method.Params) > 0 {
-		return fmt.Sprintf("%s%s", util.CamelCase(method.Name), params)
-	}
-	return ""
-}
-
-func maybeMethodResult(method types.Method) string {
-	if method.Result != nil {
-		return fmt.Sprintf("%s%s", util.CamelCase(method.Name), result)
-	}
-	return ""
-}
-
-func maybeMethodComment(method types.Method) string {
-	if comment := util.FirstOf(method.Description, method.Summary); comment != "" {
-		return fmt.Sprintf("// %s", comment)
-	}
-	return ""
+	},
+	"refName": func(v string) string {
+		return filepath.Base(v)
+	},
 }
 
-func maybeFieldComment(desc string) string {
-	if desc != "" {
-		return fmt.Sprintf("// %s", desc)
+func Generate(rpc *types.OpenRPC, templates []string, output string) error {
+	if len(templates) == 0 {
+		templates = defaultTemplates
 	}
-	return ""
-}
-
-func getProgramName() string {
-	return ProgramName
-}
 
-type object struct {
-	Name   string
-	Fields *types.FieldMap
-}
-
-func funcMap(openrpc *types.OpenRPCSpec1) template.FuncMap {
-	return template.FuncMap{
-		"programName":             getProgramName,
-		"derefSchema":             derefSchemaRecurse,
-		"schemaHasRef":            schemaHazRef,
-		"schemaAsJSONPretty":      schemaAsJSONPretty,
-		"lookupContentDescriptor": maybeLookupComponentsContentDescriptor,
-		"sanitizeBackticks":       util.SanitizeBackticks,
-		"inspect":                 util.Inpect,
-		"slice":                   util.Slice,
-		"camelCase":               util.CamelCase,
-		"lowerFirst":              util.LowerFirst,
-		"maybeMethodComment":      maybeMethodComment,
-		"maybeMethodParams":       maybeMethodParams,
-		"maybeMethodResult":       maybeMethodResult,
-		"maybeFieldComment":       maybeFieldComment,
-		"getObjects": func(om *types.ObjectMap) []object {
-			keys := om.GetKeys()
-			objects := make([]object, 0, len(keys))
-			for _, k := range keys {
-				objects = append(objects, object{Name: k, Fields: om.Get(k)})
-			}
-			return objects
-		},
-		"getFields": func(fm *types.FieldMap) []types.BasicType {
-			keys := fm.GetKeys()
-			fields := make([]types.BasicType, 0, len(keys))
-			for _, k := range keys {
-				fields = append(fields, fm.Get(k))
-			}
-			return fields
-		},
-		"indent": func(spaces int, v string) string {
-			pad := strings.Repeat(" ", spaces)
-			return pad + strings.Replace(v, "\n", "\n"+pad, -1)
-		},
-	}
-}
+	var wr bytes.Buffer
+	for _, tmpl := range templates {
+		name := filepath.Base(tmpl)
 
-func WriteFile(box *packr.Box, name, pkg string, openrpc *types.OpenRPCSpec1) error {
-	data, err := box.Find(fmt.Sprintf("%s.%s", name, goTmplExt))
-	if err != nil {
-		return err
-	}
+		t, err := template.New(name).Funcs(funcs).ParseFiles(tmpl)
+		if err != nil {
+			return err
+		}
 
-	tmp, err := template.New(name).Funcs(funcMap(openrpc)).Parse(string(data))
-	if err != nil {
-		return err
-	}
+		err = t.Execute(&wr, rpc)
+		if err != nil {
+			return err
+		}
 
-	tmpl := new(bytes.Buffer)
-	err = tmp.Execute(tmpl, openrpc)
-	if err != nil {
-		return err
-	}
+		var fmtd []byte
+		fmtd, err = format.Source(wr.Bytes())
+		if err != nil {
+			return err
+		}
+		wr.Reset()
 
-	fmtd, err := format.Source(tmpl.Bytes())
-	if err != nil {
-		return err
+		name = name[:len(name)-len(filepath.Ext(name))]
+		err = os.WriteFile(filepath.Join(output, name+".go"), fmtd, 0777)
+		if err != nil {
+			return err
+		}
 	}
 
-	return os.WriteFile(path.Join(pkg, fmt.Sprintf("%s.%s", name, goExt)), fmtd, 0644)
+	return nil
 }
diff --git a/openrpc/generate/generate_test.go b/openrpc/generate/generate_test.go
deleted file mode 100644
index c18a5ec..0000000
--- a/openrpc/generate/generate_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package generate
-
-import (
-	"bytes"
-	"encoding/json"
-	"io/ioutil"
-	"testing"
-	"text/template"
-
-	"gfx.cafe/open/jrpc/openrpc/parse"
-	"gfx.cafe/open/jrpc/openrpc/types"
-	"github.com/test-go/testify/require"
-)
-
-var exp = `package generate
-type ServiceMethodParams struct {
-// this is a desc
-Param1 string
-// this is a desc
-Param2 string
-
-Param3 string
-// this is an object
-Param4
-}
-type Param4 struct {
-// this is a foo
-Foo string
-// this is a bar
-Bar string
-}
-type ServiceMethodResult struct {
-// the requested data
-Data string
-}
-`
-
-func tmpl(t *testing.T) string {
-	data, err := ioutil.ReadFile("test.json")
-	require.NoError(t, err)
-
-	spec := types.NewOpenRPCSpec1()
-	err = json.Unmarshal(data, spec)
-	require.NoError(t, err)
-
-	parse.GetTypes(spec, spec.Objects)
-
-	data, err = ioutil.ReadFile("test.gotmpl")
-	require.NoError(t, err)
-	tmp, err := template.New("server").Funcs(funcMap(spec)).Parse(string(data))
-	require.NoError(t, err)
-
-	buf := new(bytes.Buffer)
-	err = tmp.Execute(buf, spec)
-	require.NoError(t, err)
-
-	return buf.String()
-}
-
-func TestGenerate(t *testing.T) {
-
-	t.Run("Should be same at least once, in a while", func(t *testing.T) {
-		require.Equal(t, exp, tmpl(t))
-	})
-
-	t.Skip("isaac gets fail here")
-	t.Run("Should be deterministic", func(t *testing.T) {
-		for i := 0; i < 20; i++ {
-			got := tmpl(t)
-			if exp == got {
-				t.Log("same same")
-				continue
-			}
-			t.Log("but different")
-			require.Equal(t, exp, got)
-		}
-	})
-}
-
-func TestMaybeComment(t *testing.T) {
-	require.Equal(t, "", maybeFieldComment(""))
-	require.Equal(t, "// hello, world", maybeFieldComment("hello, world"))
-}
diff --git a/openrpc/out/types.go b/openrpc/out/types.go
index 6bb0777..da062d3 100644
--- a/openrpc/out/types.go
+++ b/openrpc/out/types.go
@@ -4,663 +4,382 @@ package main
 
 type GoOpenRPCService interface {
 	// Returns an RLP-encoded header.
-	DebugGetRawHeader(*DebugGetRawHeaderParams) (*DebugGetRawHeaderResult, error)
+	DebugGetRawHeader(
+		Block BlockNumberOrTag,
+	) (HeaderRLP Bytes)
 	// Returns an RLP-encoded block.
-	DebugGetRawBlock(*DebugGetRawBlockParams) (*DebugGetRawBlockResult, error)
+	DebugGetRawBlock(
+		Block BlockNumberOrTag,
+	) (BlockRLP Bytes)
 	// Returns an array of EIP-2718 binary-encoded transactions.
-	DebugGetRawTransaction(*DebugGetRawTransactionParams) (*DebugGetRawTransactionResult, error)
+	DebugGetRawTransaction(
+		TransactionHash Hash32,
+	) (EIP2718BinaryEncodedTransaction Bytes)
 	// Returns an array of EIP-2718 binary-encoded receipts.
-	DebugGetRawReceipts(*DebugGetRawReceiptsParams) (*DebugGetRawReceiptsResult, error)
+	DebugGetRawReceipts(
+		Block BlockNumberOrTag,
+	) (Receipts []Bytes)
 	// Returns an array of recent bad blocks that the client has seen on the network.
-	DebugGetBadBlocks() (*DebugGetBadBlocksResult, error)
+	DebugGetBadBlocks() (Blocks []BadBlock)
 	// Returns information about a block by hash.
-	EthGetBlockByHash(*EthGetBlockByHashParams) (*EthGetBlockByHashResult, error)
+	EthGetBlockByHash(
+		BlockHash Hash32,
+		HydratedTransactions bool,
+	) (BlockInformation Block)
 	// Returns information about a block by number.
-	EthGetBlockByNumber(*EthGetBlockByNumberParams) (*EthGetBlockByNumberResult, error)
+	EthGetBlockByNumber(
+		Block BlockNumberOrTag,
+		HydratedTransactions bool,
+	) (BlockInformation Block)
 	// Returns the number of transactions in a block from a block matching the given block hash.
-	EthGetBlockTransactionCountByHash(*EthGetBlockTransactionCountByHashParams) (*EthGetBlockTransactionCountByHashResult, error)
+	EthGetBlockTransactionCountByHash(
+		BlockHash Hash32,
+	) (TransactionCount Uint)
 	// Returns the number of transactions in a block matching the given block number.
-	EthGetBlockTransactionCountByNumber(*EthGetBlockTransactionCountByNumberParams) (*EthGetBlockTransactionCountByNumberResult, error)
+	EthGetBlockTransactionCountByNumber(
+		Block BlockNumberOrTag,
+	) (TransactionCount Uint)
 	// Returns the number of uncles in a block from a block matching the given block hash.
-	EthGetUncleCountByBlockHash(*EthGetUncleCountByBlockHashParams) (*EthGetUncleCountByBlockHashResult, error)
+	EthGetUncleCountByBlockHash(
+		BlockHash Hash32,
+	) (UncleCount Uint)
 	// Returns the number of transactions in a block matching the given block number.
-	EthGetUncleCountByBlockNumber(*EthGetUncleCountByBlockNumberParams) (*EthGetUncleCountByBlockNumberResult, error)
+	EthGetUncleCountByBlockNumber(
+		Block BlockNumberOrTag,
+	) (UncleCount Uint)
 	// Returns the chain ID of the current network.
-	EthChainId() (*EthChainIdResult, error)
+	EthChainId() (ChainID Uint)
 	// Returns an object with data about the sync status or false.
-	EthSyncing() (*EthSyncingResult, error)
+	EthSyncing() (SyncingStatus SyncingStatus)
 	// Returns the client coinbase address.
-	EthCoinbase() (*EthCoinbaseResult, error)
+	EthCoinbase() (CoinbaseAddress Address)
 	// Returns a list of addresses owned by client.
-	EthAccounts() (*EthAccountsResult, error)
+	EthAccounts() (Accounts []Address)
 	// Returns the number of most recent block.
-	EthBlockNumber() (*EthBlockNumberResult, error)
+	EthBlockNumber() (BlockNumber Uint)
 	// Executes a new message call immediately without creating a transaction on the block chain.
-	EthCall(*EthCallParams) (*EthCallResult, error)
+	EthCall(
+		Transaction GenericTransaction,
+		Block BlockNumberOrTag,
+	) (ReturnData Bytes)
 	// Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
-	EthEstimateGas(*EthEstimateGasParams) (*EthEstimateGasResult, error)
+	EthEstimateGas(
+		Transaction GenericTransaction,
+		Block BlockNumberOrTag,
+	) (GasUsed Uint)
 	// Generates an access list for a transaction.
-	EthCreateAccessList(*EthCreateAccessListParams) (*EthCreateAccessListResult, error)
+	EthCreateAccessList(
+		Transaction GenericTransaction,
+		Block BlockNumberOrTag,
+	) (GasUsed struct {
+		AccessList AccessList
+		Error      string
+		GasUsed    Uint
+	})
 	// Returns the current price per gas in wei.
-	EthGasPrice() (*EthGasPriceResult, error)
+	EthGasPrice() (GasPrice Uint)
 	// Returns the current maxPriorityFeePerGas per gas in wei.
-	EthMaxPriorityFeePerGas() (*EthMaxPriorityFeePerGasResult, error)
-	// Returns transaction base fee per gas and effective priority fee per gas for the requested/supported block range.
-	EthFeeHistory(*EthFeeHistoryParams) (*EthFeeHistoryResult, error)
+	EthMaxPriorityFeePerGas() (MaxPriorityFeePerGas Uint)
+	// Transaction fee history
+	EthFeeHistory(
+		BlockCount Uint,
+		NewestBlock BlockNumberOrTag,
+		RewardPercentiles []float64,
+	) (FeeHistoryResult struct {
+		BaseFeePerGas []Uint
+		OldestBlock   Uint
+		Reward        [][]Uint
+	})
 	// Creates a filter object, based on filter options, to notify when the state changes (logs).
-	EthNewFilter(*EthNewFilterParams) (*EthNewFilterResult, error)
+	EthNewFilter(
+		Filter Filter,
+	) (FilterIdentifier Uint)
 	// Creates a filter in the node, to notify when a new block arrives.
-	EthNewBlockFilter() (*EthNewBlockFilterResult, error)
+	EthNewBlockFilter() (FilterIdentifier Uint)
 	// Creates a filter in the node, to notify when new pending transactions arrive.
-	EthNewPendingTransactionFilter() (*EthNewPendingTransactionFilterResult, error)
+	EthNewPendingTransactionFilter() (FilterIdentifier Uint)
 	// Uninstalls a filter with given id.
-	EthUninstallFilter(*EthUninstallFilterParams) (*EthUninstallFilterResult, error)
+	EthUninstallFilter(
+		FilterIdentifier Uint,
+	) (Success bool)
 	// Polling method for a filter, which returns an array of logs which occurred since last poll.
-	EthGetFilterChanges(*EthGetFilterChangesParams) (*EthGetFilterChangesResult, error)
+	EthGetFilterChanges(
+		FilterIdentifier Uint,
+	) (LogObjects FilterResults)
 	// Returns an array of all logs matching filter with given id.
-	EthGetFilterLogs(*EthGetFilterLogsParams) (*EthGetFilterLogsResult, error)
+	EthGetFilterLogs(
+		FilterIdentifier Uint,
+	) (LogObjects FilterResults)
 	// Returns an array of all logs matching filter with given id.
-	EthGetLogs(*EthGetLogsParams) (*EthGetLogsResult, error)
+	EthGetLogs(
+		Filter Filter,
+	) (LogObjects FilterResults)
 	// Returns whether the client is actively mining new blocks.
-	EthMining() (*EthMiningResult, error)
+	EthMining() (MiningStatus bool)
 	// Returns the number of hashes per second that the node is mining with.
-	EthHashrate() (*EthHashrateResult, error)
+	EthHashrate() (MiningStatus Uint)
 	// Returns the hash of the current block, the seedHash, and the boundary condition to be met (“target”).
-	EthGetWork() (*EthGetWorkResult, error)
+	EthGetWork() (CurrentWork []Bytes32)
 	// Used for submitting a proof-of-work solution.
-	EthSubmitWork(*EthSubmitWorkParams) (*EthSubmitWorkResult, error)
+	EthSubmitWork(
+		Nonce Bytes8,
+		Hash Bytes32,
+		Digest Bytes32,
+	) (Success bool)
 	// Used for submitting mining hashrate.
-	EthSubmitHashrate(*EthSubmitHashrateParams) (*EthSubmitHashrateResult, error)
+	EthSubmitHashrate(
+		Hashrate Bytes32,
+		Id Bytes32,
+	) (Success bool)
 	// Returns an EIP-191 signature over the provided data.
-	EthSign(*EthSignParams) (*EthSignResult, error)
+	EthSign(
+		Address Address,
+		Message Bytes,
+	) (Signature Bytes65)
 	// Returns an RLP encoded transaction signed by the specified account.
-	EthSignTransaction(*EthSignTransactionParams) (*EthSignTransactionResult, error)
+	EthSignTransaction(
+		Transaction GenericTransaction,
+	) (EncodedTransaction Bytes)
 	// Returns the balance of the account of given address.
-	EthGetBalance(*EthGetBalanceParams) (*EthGetBalanceResult, error)
+	EthGetBalance(
+		Address Address,
+		Block BlockNumberOrTag,
+	) (Balance Uint)
 	// Returns the value from a storage position at a given address.
-	EthGetStorageAt(*EthGetStorageAtParams) (*EthGetStorageAtResult, error)
+	EthGetStorageAt(
+		Address Address,
+		StorageSlot Uint256,
+		Block BlockNumberOrTag,
+	) (Value Bytes)
 	// Returns the number of transactions sent from an address.
-	EthGetTransactionCount(*EthGetTransactionCountParams) (*EthGetTransactionCountResult, error)
+	EthGetTransactionCount(
+		Address Address,
+		Block BlockNumberOrTag,
+	) (TransactionCount Uint)
 	// Returns code at a given address.
-	EthGetCode(*EthGetCodeParams) (*EthGetCodeResult, error)
+	EthGetCode(
+		Address Address,
+		Block BlockNumberOrTag,
+	) (Bytecode Bytes)
 	// Returns the merkle proof for a given account and optionally some storage keys.
-	EthGetProof(*EthGetProofParams) (*EthGetProofResult, error)
+	EthGetProof(
+		Address Address,
+		StorageKeys []Hash32,
+		Block BlockNumberOrTag,
+	) (Account AccountProof)
 	// Signs and submits a transaction.
-	EthSendTransaction(*EthSendTransactionParams) (*EthSendTransactionResult, error)
+	EthSendTransaction(
+		Transaction GenericTransaction,
+	) (TransactionHash Hash32)
 	// Submits a raw transaction.
-	EthSendRawTransaction(*EthSendRawTransactionParams) (*EthSendRawTransactionResult, error)
+	EthSendRawTransaction(
+		Transaction Bytes,
+	) (TransactionHash Hash32)
 	// Returns the information about a transaction requested by transaction hash.
-	EthGetTransactionByHash(*EthGetTransactionByHashParams) (*EthGetTransactionByHashResult, error)
+	EthGetTransactionByHash(
+		TransactionHash Hash32,
+	) (TransactionInformation TransactionInfo)
 	// Returns information about a transaction by block hash and transaction index position.
-	EthGetTransactionByBlockHashAndIndex(*EthGetTransactionByBlockHashAndIndexParams) (*EthGetTransactionByBlockHashAndIndexResult, error)
+	EthGetTransactionByBlockHashAndIndex(
+		BlockHash Hash32,
+		TransactionIndex Uint,
+	) (TransactionInformation TransactionInfo)
 	// Returns information about a transaction by block number and transaction index position.
-	EthGetTransactionByBlockNumberAndIndex(*EthGetTransactionByBlockNumberAndIndexParams) (*EthGetTransactionByBlockNumberAndIndexResult, error)
+	EthGetTransactionByBlockNumberAndIndex(
+		Block BlockNumberOrTag,
+		TransactionIndex Uint,
+	) (TransactionInformation TransactionInfo)
 	// Returns the receipt of a transaction by transaction hash.
-	EthGetTransactionReceipt(*EthGetTransactionReceiptParams) (*EthGetTransactionReceiptResult, error)
+	EthGetTransactionReceipt(
+		TransactionHash Hash32,
+	) (ReceiptInformation ReceiptInfo)
 }
-type BlockNumberOrTag struct {
-	FieldUint string `json:"uint"`
-}
-type DebugGetRawHeaderParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type DebugGetRawHeaderResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type DebugGetRawBlockParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type DebugGetRawBlockResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type DebugGetRawTransactionParams struct {
-	FieldHash32 string `json:"hash32"`
+type AccessList []AccessListEntry
+type AccessListEntry struct {
+	Address     Address
+	StorageKeys []Hash32
 }
-type DebugGetRawTransactionResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type DebugGetRawReceiptsParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type ReceiptArray struct {
-	FieldBytes string `json:"bytes"`
-}
-type DebugGetRawReceiptsResult struct {
-	FieldBytes string `json:"bytes"`
-
-	FieldReceiptArray []string `json:"receiptArray"`
+type AccountProof struct {
+	AccountProof []Bytes
+	Address      Address
+	Balance      Uint256
+	CodeHash     Hash32
+	Nonce        Uint64
+	StorageHash  Hash32
+	StorageProof []StorageProof
+}
+type BadBlock struct {
+	Block Bytes
+	Hash  Hash32
+	Rlp   Bytes
 }
 type Block struct {
-	FieldBytes string `json:"bytes"`
-}
-type Hash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type Rlp struct {
-	FieldBytes string `json:"bytes"`
-}
-type BadBlockArray struct {
-	BadBlock
-}
-type DebugGetBadBlocksResult struct {
-	BadBlock
-
-	FieldBadBlockArray []BadBlock `json:"badBlockArray"`
-}
-type EthGetBlockByHashParams struct {
-	FieldHash32 string `json:"hash32"`
-
-	FieldHydrated bool `json:"hydrated"`
-}
-type StateRoot struct {
-	FieldHash32 string `json:"hash32"`
-}
-type TransactionsRoot struct {
-	FieldHash32 string `json:"hash32"`
-}
-type GasLimit struct {
-	FieldUint string `json:"uint"`
-}
-type MixHash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type Nonce struct {
-	FieldBytes8 string `json:"bytes8"`
-
-	FieldUint string `json:"uint"`
-
-	FieldUint64 string `json:"uint64"`
-}
-type Number struct {
-	FieldUint string `json:"uint"`
-}
-type Size struct {
-	FieldUint string `json:"uint"`
-}
-type Difficulty struct {
-	FieldBytes string `json:"bytes"`
-}
-type ExtraData struct {
-	FieldBytes string `json:"bytes"`
-}
-type LogsBloom struct {
-	FieldBytes256 string `json:"bytes256"`
-}
-type Sha3Uncles struct {
-	FieldHash32 string `json:"hash32"`
-}
-type Timestamp struct {
-	FieldUint string `json:"uint"`
-}
-type BaseFeePerGas struct {
-	// An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.
-	FieldUint string `json:"uint"`
-	// An array of block base fees per gas. This includes the next block after the newest of the returned range, because this value can be derived from the newest block. Zeroes are returned for pre-EIP-1559 blocks.
-	FieldBaseFeePerGas []string `json:"baseFeePerGas"`
-}
-type GasUsed struct {
-	// The amount of gas used for this specific transaction alone.
-	FieldUint string `json:"uint"`
-}
-type Miner struct {
-	FieldAddress string `json:"address"`
-}
-type Uncles struct {
-	FieldHash32 string `json:"hash32"`
-
-	FieldUncles []string `json:"uncles"`
-}
-type ParentHash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type ReceiptsRoot struct {
-	FieldHash32 string `json:"hash32"`
-}
-type TotalDifficulty struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetBlockByHashResult struct {
-	BlockObject
-}
-type EthGetBlockByNumberParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-
-	FieldHydrated bool `json:"hydrated"`
-}
-type EthGetBlockByNumberResult struct {
-	BlockObject
-}
-type EthGetBlockTransactionCountByHashParams struct {
-	FieldHash32 string `json:"hash32"`
-}
-type EthGetBlockTransactionCountByHashResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetBlockTransactionCountByNumberParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthGetBlockTransactionCountByNumberResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetUncleCountByBlockHashParams struct {
-	FieldHash32 string `json:"hash32"`
-}
-type EthGetUncleCountByBlockHashResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetUncleCountByBlockNumberParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthGetUncleCountByBlockNumberResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthChainIdResult struct {
-	FieldUint string `json:"uint"`
-}
-type CurrentBlock struct {
-	FieldUint string `json:"uint"`
-}
-type HighestBlock struct {
-	FieldUint string `json:"uint"`
-}
-type StartingBlock struct {
-	FieldUint string `json:"uint"`
-}
-type SyncingStatus struct {
-	SyncingProgress
-}
-type EthSyncingResult struct {
-	FieldSyncingStatus SyncingProgress `json:"syncingStatus"`
-}
-type EthCoinbaseResult struct {
-	FieldAddress string `json:"address"`
-}
-type Accounts struct {
-	FieldAddress string `json:"address"`
-}
-type EthAccountsResult struct {
-	FieldAddress string `json:"address"`
-
-	FieldAccounts []string `json:"accounts"`
-}
-type EthBlockNumberResult struct {
-	FieldUint string `json:"uint"`
-}
-type Address struct {
-	FieldAddress string `json:"address"`
-}
-type StorageKeys struct {
-	FieldHash32 string `json:"hash32"`
-
-	FieldStorageKeys []string `json:"storageKeys"`
-}
-type AccessList struct {
-	// EIP-2930 access list
-	AccessListEntry
-	// EIP-2930 access list
-	FieldAccessList []AccessListEntry `json:"accessList"`
-}
-type MaxFeePerGas struct {
-	// The maximum total fee per gas the sender is willing to pay (includes the network / base fee and miner / priority fee) in wei
-	FieldUint string `json:"uint"`
-}
-type Type struct {
-	FieldByte string `json:"byte"`
-}
-type To struct {
-	// Address of the receiver or null in a contract creation transaction.
-	FieldAddress string `json:"address"`
-}
-type ChainId struct {
-	// Chain ID that this transaction is valid on.
-	FieldUint string `json:"uint"`
-}
-type From struct {
-	FieldAddress string `json:"address"`
-}
-type Gas struct {
-	FieldUint string `json:"uint"`
-}
-type GasPrice struct {
-	// The gas price willing to be paid by the sender in wei
-	FieldUint string `json:"uint"`
-}
-type Input struct {
-	FieldBytes string `json:"bytes"`
-}
-type MaxPriorityFeePerGas struct {
-	// Maximum fee per gas the sender is willing to pay to miners in wei
-	FieldUint string `json:"uint"`
-}
-type Value struct {
-	FieldUint string `json:"uint"`
-
-	FieldUint256 string `json:"uint256"`
-}
-type EthCallParams struct {
-	TransactionObjectGenericToAllTypes
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthCallResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type EthEstimateGasParams struct {
-	TransactionObjectGenericToAllTypes
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthEstimateGasResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthCreateAccessListParams struct {
-	TransactionObjectGenericToAllTypes
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type Error struct {
-	FieldError string `json:"error"`
-}
-type EthCreateAccessListResult struct {
-	AccessListResult
-}
-type EthGasPriceResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthMaxPriorityFeePerGasResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthFeeHistoryParams struct {
-	// Requested range of blocks. Clients will return less than the requested range if not all blocks are available.
-	FieldUint string `json:"uint"`
-	// Highest block of the requested range.
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-	// Floating point value between 0 and 100.
-	FieldRewardPercentile float64 `json:"rewardPercentile"`
-	// A monotonically increasing list of percentile values. For each block in the requested range, the transactions will be sorted in ascending order by effective tip per gas and the coresponding effective tip for the percentile will be determined, accounting for gas consumed.
-	FieldRewardPercentiles []float64 `json:"rewardPercentiles"`
-}
-type RewardPercentiles struct {
-	// Floating point value between 0 and 100.
-	FieldRewardPercentile float64 `json:"rewardPercentile"`
-}
-type OldestBlock struct {
-	// Lowest number block of returned range.
-	FieldUint string `json:"uint"`
+	BaseFeePerGas   Uint
+	Difficulty      Bytes
+	ExtraData       Bytes
+	GasLimit        Uint
+	GasUsed         Uint
+	LogsBloom       Bytes256
+	Miner           Address
+	MixHash         Hash32
+	Nonce           Bytes8
+	Number          Uint
+	ParentHash      Hash32
+	ReceiptsRoot    Hash32
+	Sha3Uncles      Hash32
+	Size            Uint
+	StateRoot       Hash32
+	Timestamp       Uint
+	TotalDifficulty Uint
+	Transactions    struct {
+		Option0 []Hash32
+		Option1 []TransactionSigned
+	}
+	TransactionsRoot Hash32
+	Uncles           []Hash32
 }
-type RewardPercentile struct {
-	// A given percentile sample of effective priority fees per gas from a single block in ascending order, weighted by gas used. Zeroes are returned if the block is empty.
-	FieldUint string `json:"uint"`
-}
-type Reward struct {
-	// A given percentile sample of effective priority fees per gas from a single block in ascending order, weighted by gas used. Zeroes are returned if the block is empty.
-	FieldUint string `json:"uint"`
-	// An array of effective priority fee per gas data points from a single block. All zeroes are returned if the block is empty.
-	FieldRewardPercentile []string `json:"rewardPercentile"`
-	// A two-dimensional array of effective priority fees per gas at the requested block percentiles.
-	FieldReward []array `json:"reward"`
-}
-type EthFeeHistoryResult struct {
-	// Fee history results.
-	FeeHistoryResults
-}
-type FromBlock struct {
-	FieldUint string `json:"uint"`
-}
-type ToBlock struct {
-	FieldUint string `json:"uint"`
-}
-type FilterTopicListEntry struct {
-	FieldAnyTopicMatch null `json:"anyTopicMatch"`
-}
-type FilterTopics struct {
-	FieldFilterTopicListEntry null `json:"filterTopicListEntry"`
-}
-type Topics struct {
-	FieldFilterTopicListEntry null `json:"filterTopicListEntry"`
-
-	FieldFilterTopics []FilterTopicListEntry `json:"filterTopics"`
-
-	FieldBytes32 string `json:"bytes32"`
-
-	FieldTopics []string `json:"topics"`
-}
-type EthNewFilterParams struct {
-	Filter
-}
-type EthNewFilterResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthNewBlockFilterResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthNewPendingTransactionFilterResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthUninstallFilterParams struct {
-	FieldUint string `json:"uint"`
-}
-type EthUninstallFilterResult struct {
-	FieldSuccess bool `json:"success"`
-}
-type EthGetFilterChangesParams struct {
-	FieldUint string `json:"uint"`
-}
-type NewBlockHashes struct {
-	FieldHash32 string `json:"hash32"`
+type BlockNumberOrTag struct {
+	Option0 Uint
+	Option1 BlockTag
+}
+type BlockTag string
+type Filter struct {
+	Address struct {
+		Option0 Address
+		Option1 Addresses
+	}
+	FromBlock Uint
+	ToBlock   Uint
+	Topics    FilterTopics
 }
 type FilterResults struct {
-	FieldHash32 string `json:"hash32"`
-
-	FieldNewBlockHashes []string `json:"newBlockHashes"`
-}
-type EthGetFilterChangesResult struct {
-	FieldFilterResults array `json:"filterResults"`
-}
-type EthGetFilterLogsParams struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetFilterLogsResult struct {
-	FieldFilterResults array `json:"filterResults"`
-}
-type EthGetLogsParams struct {
-	Filter
-}
-type EthGetLogsResult struct {
-	FieldFilterResults array `json:"filterResults"`
-}
-type EthMiningResult struct {
-	FieldMiningStatus bool `json:"miningStatus"`
-}
-type EthHashrateResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetWorkResult struct {
-	FieldCurrentWork []string `json:"currentWork"`
-}
-type EthSubmitWorkParams struct {
-	FieldBytes8 string `json:"bytes8"`
-
-	FieldBytes32 string `json:"bytes32"`
-}
-type EthSubmitWorkResult struct {
-	FieldSuccess bool `json:"success"`
-}
-type EthSubmitHashrateParams struct {
-	FieldBytes32 string `json:"bytes32"`
-}
-type EthSubmitHashrateResult struct {
-	FieldSuccess bool `json:"success"`
-}
-type EthSignParams struct {
-	FieldAddress string `json:"address"`
-
-	FieldBytes string `json:"bytes"`
-}
-type EthSignResult struct {
-	FieldBytes645 string `json:"bytes645"`
-}
-type EthSignTransactionParams struct {
-	TransactionObjectGenericToAllTypes
-}
-type EthSignTransactionResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type EthGetBalanceParams struct {
-	FieldAddress string `json:"address"`
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthGetBalanceResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetStorageAtParams struct {
-	FieldAddress string `json:"address"`
-
-	FieldUint256 string `json:"uint256"`
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthGetStorageAtResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type EthGetTransactionCountParams struct {
-	FieldAddress string `json:"address"`
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthGetTransactionCountResult struct {
-	FieldUint string `json:"uint"`
-}
-type EthGetCodeParams struct {
-	FieldAddress string `json:"address"`
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type EthGetCodeResult struct {
-	FieldBytes string `json:"bytes"`
-}
-type EthGetProofParams struct {
-	FieldAddress string `json:"address"`
-
-	FieldHash32 string `json:"hash32"`
-
-	FieldStorageKeys []string `json:"storageKeys"`
-
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-}
-type AccountProof struct {
-	FieldBytes string `json:"bytes"`
-
-	FieldAccountProof []string `json:"accountProof"`
-}
-type Balance struct {
-	FieldUint256 string `json:"uint256"`
-}
-type CodeHash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type StorageHash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type Key struct {
-	FieldHash32 string `json:"hash32"`
-}
-type Proof struct {
-	FieldBytes string `json:"bytes"`
-
-	FieldProof []string `json:"proof"`
+	Option0 []Hash32
+	Option1 []Hash32
+	Option2 []Log
+}
+type FilterTopic struct {
+	Option0 struct{}
+	Option1 Bytes32
+	Option2 []Bytes32
+}
+type FilterTopics []FilterTopic
+type GenericTransaction struct {
+	AccessList           AccessList
+	ChainId              Uint
+	From                 Address
+	Gas                  Uint
+	GasPrice             Uint
+	Input                Bytes
+	MaxFeePerGas         Uint
+	MaxPriorityFeePerGas Uint
+	Nonce                Uint
+	To                   Address
+	Type                 Byte
+	Value                Uint
+}
+type Log struct {
+	Address          Address
+	BlockHash        Hash32
+	BlockNumber      Uint
+	Data             Bytes
+	LogIndex         Uint
+	Removed          bool
+	Topics           []Bytes32
+	TransactionHash  Hash32
+	TransactionIndex Uint
+}
+type ReceiptInfo struct {
+	BlockHash       Hash32
+	BlockNumber     Uint
+	ContractAddress struct {
+		Option0 Address
+		Option1 struct{}
+	}
+	CumulativeGasUsed Uint
+	EffectiveGasPrice Uint
+	From              Address
+	GasUsed           Uint
+	Logs              []Log
+	LogsBloom         Bytes256
+	Root              Bytes32
+	Status            Uint
+	To                Address
+	TransactionHash   Hash32
+	TransactionIndex  Uint
 }
 type StorageProof struct {
-	FieldStorageProof []StorageProof `json:"storageProof"`
-}
-type EthGetProofResult struct {
-	AccountProof
-}
-type EthSendTransactionParams struct {
-	TransactionObjectGenericToAllTypes
-}
-type EthSendTransactionResult struct {
-	FieldHash32 string `json:"hash32"`
-}
-type EthSendRawTransactionParams struct {
-	FieldBytes string `json:"bytes"`
-}
-type EthSendRawTransactionResult struct {
-	FieldHash32 string `json:"hash32"`
-}
-type EthGetTransactionByHashParams struct {
-	FieldHash32 string `json:"hash32"`
-}
-type EthGetTransactionByHashResult struct {
-	FieldTransactionInformation object `json:"transactionInformation"`
-}
-type EthGetTransactionByBlockHashAndIndexParams struct {
-	FieldHash32 string `json:"hash32"`
-
-	FieldUint string `json:"uint"`
-}
-type EthGetTransactionByBlockHashAndIndexResult struct {
-	FieldTransactionInformation object `json:"transactionInformation"`
-}
-type EthGetTransactionByBlockNumberAndIndexParams struct {
-	FieldBlockNumberOrTag string `json:"blockNumberOrTag"`
-
-	FieldUint string `json:"uint"`
-}
-type EthGetTransactionByBlockNumberAndIndexResult struct {
-	FieldTransactionInformation object `json:"transactionInformation"`
-}
-type EthGetTransactionReceiptParams struct {
-	FieldHash32 string `json:"hash32"`
-}
-type EffectiveGasPrice struct {
-	// The actual value per gas deducted from the senders account. Before EIP-1559, this is equal to the transaction's gas price. After, it is equal to baseFeePerGas + min(maxFeePerGas - baseFeePerGas, maxPriorityFeePerGas).
-	FieldUint string `json:"uint"`
-}
-type Status struct {
-	// Either 1 (success) or 0 (failure). Only specified for transactions included after the Byzantium upgrade.
-	FieldUint string `json:"uint"`
-}
-type BlockNumber struct {
-	FieldUint string `json:"uint"`
-}
-type ContractAddress struct {
-	FieldAddress string `json:"address"`
-	// The contract address created, if the transaction was a contract creation, otherwise null.
-	FieldContractAddress string `json:"contractAddress"`
-}
-type BlockHash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type CumulativeGasUsed struct {
-	// The sum of gas used by this transaction and all preceding transactions in the same block.
-	FieldUint string `json:"uint"`
-}
-type Root struct {
-	// The post-transaction state root. Only specified for transactions included before the Byzantium upgrade.
-	FieldBytes32 string `json:"bytes32"`
-}
-type TransactionHash struct {
-	FieldHash32 string `json:"hash32"`
-}
-type TransactionIndex struct {
-	FieldUint string `json:"uint"`
-}
-type Data struct {
-	FieldBytes string `json:"bytes"`
-}
-type LogIndex struct {
-	FieldUint string `json:"uint"`
-}
-type Removed struct {
-	FieldRemoved bool `json:"removed"`
-}
-type Logs struct {
-	Log
-
-	FieldLogs []Log `json:"logs"`
-}
-type EthGetTransactionReceiptResult struct {
-	ReceiptInfo
+	Key   Hash32
+	Proof []Bytes
+	Value Uint256
 }
+type SyncingStatus struct {
+	Option0 struct {
+		CurrentBlock  Uint
+		HighestBlock  Uint
+		StartingBlock Uint
+	}
+	Option1 bool
+}
+type Transaction1559Signed struct {
+}
+type Transaction1559Unsigned struct {
+	AccessList           AccessList
+	ChainId              Uint
+	Gas                  Uint
+	Input                Bytes
+	MaxFeePerGas         Uint
+	MaxPriorityFeePerGas Uint
+	Nonce                Uint
+	To                   Address
+	Type                 Byte
+	Value                Uint
+}
+type Transaction2930Signed struct {
+}
+type Transaction2930Unsigned struct {
+	AccessList AccessList
+	ChainId    Uint
+	Gas        Uint
+	GasPrice   Uint
+	Input      Bytes
+	Nonce      Uint
+	To         Address
+	Type       Byte
+	Value      Uint
+}
+type TransactionInfo struct {
+}
+type TransactionLegacySigned struct {
+}
+type TransactionLegacyUnsigned struct {
+	ChainId  Uint
+	Gas      Uint
+	GasPrice Uint
+	Input    Bytes
+	Nonce    Uint
+	To       Address
+	Type     Byte
+	Value    Uint
+}
+type TransactionSigned struct {
+	Option0 Transaction1559Signed
+	Option1 Transaction2930Signed
+	Option2 TransactionLegacySigned
+}
+type TransactionUnsigned struct {
+	Option0 Transaction1559Unsigned
+	Option1 Transaction2930Unsigned
+	Option2 TransactionLegacyUnsigned
+}
+type Address string
+type Addresses []Address
+type Byte string
+type Bytes string
+type Bytes256 string
+type Bytes32 string
+type Bytes65 string
+type Bytes8 string
+type Hash32 string
+type Uint string
+type Uint256 string
+type Uint64 string
diff --git a/openrpc/parse/parse.go b/openrpc/parse/parse.go
index 5f43a25..fe2554d 100644
--- a/openrpc/parse/parse.go
+++ b/openrpc/parse/parse.go
@@ -1,122 +1 @@
 package parse
-
-import (
-	"fmt"
-	"path"
-	"reflect"
-
-	"gfx.cafe/open/jrpc/openrpc/types"
-	"gfx.cafe/open/jrpc/openrpc/util"
-	"github.com/go-openapi/spec"
-)
-
-const (
-	params = "Params"
-	result = "Result"
-)
-
-func persistTitleAndDesc(prev, next spec.Schema) spec.Schema {
-	next.Title = util.FirstOf(next.Title, prev.Title)
-	next.Description = util.FirstOf(next.Description, prev.Description)
-	return next
-}
-
-func persistFields(prev, next spec.Schema) spec.Schema {
-	next.Title = util.FirstOf(next.Title, prev.Title, path.Base(prev.Ref.String()))
-	next.Description = util.FirstOf(next.Description, prev.Description)
-	if next.Items == nil {
-		next.Items = prev.Items
-	}
-	return next
-}
-
-func resolveSchema(openrpc *types.OpenRPCSpec1, sch spec.Schema) spec.Schema {
-	pt := sch.Ref.GetPointer()
-	doc, _, _ := pt.Get(openrpc)
-	if s, ok := doc.(spec.Schema); ok {
-		sch = persistFields(sch, s)
-	} else if cd, ok := doc.(*types.ContentDescriptor); ok {
-		sch = persistFields(sch, cd.Schema)
-	}
-
-	if sch.Ref.GetURL() != nil {
-		return resolveSchema(openrpc, sch)
-	}
-	return sch
-}
-
-func getConcreteType(in string) string {
-	switch in {
-	case reflect.Bool.String(), "boolean":
-		return reflect.Bool.String()
-	case "number":
-		return "float64"
-	default:
-		return in
-	}
-}
-
-func getObjectType(openrpc *types.OpenRPCSpec1, sch spec.Schema) string {
-	sch = resolveSchema(openrpc, sch)
-
-	if len(sch.Properties) > 0 || len(sch.Type) < 1 {
-		return util.CamelCase(sch.Title)
-	}
-
-	return getConcreteType(sch.Type[0])
-}
-
-func dereference(openrpc *types.OpenRPCSpec1, name string, sch spec.Schema, om *types.ObjectMap) {
-	// resolve all pointers
-	sch = resolveSchema(openrpc, sch)
-
-	if len(sch.Properties) > 0 {
-		for key, value := range sch.Properties {
-			value.Title = key
-			dereference(openrpc, key, value, om)
-		}
-		om.Set(name, types.BasicType{
-			Desc: sch.Description, Name: sch.Title, Type: util.CamelCase(sch.Title)})
-		return
-	} else if len(sch.OneOf) > 0 {
-		next := sch.OneOf[0]
-		dereference(openrpc, sch.Title, next, om)
-		om.Set(name, types.BasicType{Desc: sch.Description, Name: sch.Title, Type: getObjectType(openrpc, resolveSchema(openrpc, next))})
-		return
-	} else if sch.Items != nil {
-		if sch.Items.Schema != nil {
-			dereference(openrpc, sch.Title, *sch.Items.Schema, om)
-			dereference(openrpc, name, persistTitleAndDesc(sch, *sch.Items.Schema), om)
-			om.Set(name, types.BasicType{Desc: sch.Description, Name: sch.Title, Type: fmt.Sprintf("[]%s", getObjectType(openrpc, persistTitleAndDesc(sch, *sch.Items.Schema)))})
-		} else if len(sch.Items.Schemas) > 0 {
-			om.Set(name, types.BasicType{Desc: sch.Description, Name: sch.Title, Type: "[]string"})
-		}
-		return
-	}
-	if len(sch.Type) == 0 {
-		return
-	}
-	om.Set(name, types.BasicType{Desc: sch.Description, Name: sch.Title, Type: getConcreteType(sch.Type[0])})
-	return
-}
-
-// GetTypes constructs all possible type definitions from the spec
-func GetTypes(openrpc *types.OpenRPCSpec1, om *types.ObjectMap) {
-	for _, m := range openrpc.Methods {
-		name := fmt.Sprintf("%s%s", util.CamelCase(m.Name), params)
-		for _, param := range m.Params {
-			sch := param.Schema
-			sch.Title = util.FirstOf(sch.Title, param.Name)
-			sch.Description = util.FirstOf(sch.Description, param.Description)
-			dereference(openrpc, name, sch, om)
-		}
-		if m.Result != nil {
-			name = fmt.Sprintf("%s%s", util.CamelCase(m.Name), result)
-			res := m.Result
-			sch := res.Schema
-			sch.Title = util.FirstOf(sch.Title, res.Name)
-			sch.Description = util.FirstOf(sch.Description, res.Description)
-			dereference(openrpc, name, sch, om)
-		}
-	}
-}
diff --git a/openrpc/parse/parse_test.go b/openrpc/parse/parse_test.go
deleted file mode 100644
index 6326ec1..0000000
--- a/openrpc/parse/parse_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-package parse
-
-import (
-	"encoding/json"
-	"io/ioutil"
-	"testing"
-
-	"gfx.cafe/open/jrpc/openrpc/types"
-	"github.com/test-go/testify/require"
-)
-
-func TestParse(t *testing.T) {
-	data, err := ioutil.ReadFile("testdata/eth_openrpc.json")
-	require.NoError(t, err)
-	spec := types.NewOpenRPCSpec1()
-	err = json.Unmarshal(data, spec)
-	require.NoError(t, err)
-	GetTypes(spec, spec.Objects)
-
-	result := spec.Objects.Get("GetBlockByHashResult")
-	require.NotNil(t, result)
-	require.Len(t, result.GetKeys(), 1)
-
-	result = spec.Objects.Get("EthBlockNumberResult")
-	require.NotNil(t, result)
-	require.Len(t, result.GetKeys(), 1)
-
-	result = spec.Objects.Get("BlockNumber")
-	require.NotNil(t, result)
-	require.Len(t, result.GetKeys(), 1)
-
-	result = spec.Objects.Get("EthAccountsResult")
-	require.NotNil(t, result)
-	bt := result.Get("Addresses")
-	require.Equal(t, "Addresses", bt.Name)
-	require.Equal(t, "[]string", bt.Type)
-
-	result = spec.Objects.Get("EthGetTransactionReceiptResult")
-	require.NotNil(t, result)
-	bt = result.Get("Receipt")
-	require.Equal(t, "Receipt", bt.Name)
-	require.Equal(t, "Receipt", bt.Type)
-
-	result = spec.Objects.Get("EthSyncingResult")
-	require.NotNil(t, result)
-	bt = result.Get("Syncing")
-	require.Equal(t, "Syncing", bt.Name)
-	require.Equal(t, "SyncStatus", bt.Type)
-
-	result = spec.Objects.Get("EthGetTransactionByHashResult")
-	require.NotNil(t, result)
-	bt = result.Get("Transaction")
-	require.Equal(t, "Transaction", bt.Name)
-
-	result = spec.Objects.Get("Transaction")
-	require.NotNil(t, result)
-	bt = result.Get("BlockHash")
-	require.Equal(t, "BlockHash", bt.Name)
-}
-
-func TestParseMulti(t *testing.T) {
-	sp := types.NewOpenRPCSpec1()
-	var err error
-	err = sp.AddSchemas("./testdata/execution-api/schemas")
-	require.NoError(t, err)
-	err = sp.AddMethods("./testdata/execution-api/eth")
-	require.NoError(t, err)
-	err = sp.AddMethods("./testdata/execution-api/engine")
-	require.NoError(t, err)
-	err = sp.AddMethods("./testdata/execution-api/debug")
-	require.NoError(t, err)
-	// TODO: finish this test
-}
diff --git a/openrpc/parse/parsing.go b/openrpc/parse/parsing.go
deleted file mode 100644
index 1b26d9e..0000000
--- a/openrpc/parse/parsing.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package parse
-
-import (
-	"strings"
-
-	"gfx.cafe/open/jrpc/openrpc/types"
-	"gfx.cafe/open/jrpc/openrpc/util"
-	"github.com/go-openapi/spec"
-)
-
-type GoGlobal struct {
-	Structs map[string]*GoStruct
-	Types   map[string]*GoType
-	Methods map[string]*GoMethod
-}
-
-type GoStruct struct {
-	Name        string
-	Description string
-
-	Fields map[string]string
-}
-type GoField struct {
-	Name     string
-	Type     *GoType
-	Optional bool
-
-	Description string
-}
-
-type GoType struct {
-	Name string
-	Type string
-
-	Description string
-}
-
-type GoMethod struct {
-	Input  string
-	Output string
-	Name   string
-}
-
-func (g *GoGlobal) ReadOpenRPC(s types.OpenRPCSpec1) {
-	for k, v := range s.Components.Schemas {
-		g.AddSchema(k, v)
-	}
-	for _, v := range s.Methods {
-		g.AddMethod(v.Name, v)
-	}
-}
-
-func (g *GoGlobal) AddSchema(name string, m spec.Schema) {
-	if len(m.Type) > 0 {
-		switch m.Type[0] {
-		case "string":
-		case "number":
-		case "boolean":
-		case "object":
-		case "array":
-		default:
-		}
-		return
-	}
-	if len(m.OneOf) > 0 {
-		//todo: handle
-		return
-	}
-}
-
-func (g *GoGlobal) AddMethod(name string, m types.Method) {
-	inStruct := "Params" + util.CamelCase(name)
-	outStruct := ""
-	if m.Result != nil {
-		outStruct = "Result" + util.CamelCase(name)
-	}
-	g.Methods[name] = &GoMethod{
-		Input:  inStruct,
-		Output: outStruct,
-		Name:   util.CamelCase(name),
-	}
-}
-
-func (g *GoGlobal) AddStruct(name string, m spec.Schema) {
-	s := &GoStruct{}
-	g.Structs[name] = s
-	s.Name = name
-	s.Description = m.Description
-	s.Fields = map[string]string{}
-	for k, v := range m.Properties {
-		g.AddSchema(k, v)
-		if ref := v.Ref.GetPointer(); ref != nil {
-			refstr := strings.ToTitle(ref.String())
-			s.Fields[k] = refstr
-		} else {
-			if len(v.Type) > 0 {
-				refstr := v.Type[0]
-				s.Fields[k] = refstr
-			}
-		}
-	}
-}
diff --git a/openrpc/templates/types.gotmpl b/openrpc/templates/types.gotmpl
index 045e28d..69370af 100644
--- a/openrpc/templates/types.gotmpl
+++ b/openrpc/templates/types.gotmpl
@@ -2,34 +2,54 @@
 
 package main
 
+{{define "schemaType" -}}
+    {{if not (eq .Ref "") -}}
+        {{camelCase (refName .Ref)}}
+    {{- else if eq .Type "object" -}}
+        struct {
+            {{range $name, $property := .Properties -}}
+                {{camelCase $name}} {{template "schemaType" $property}}
+            {{end -}}
+        }
+    {{- else if eq .Type "array" -}}
+        []{{template "schemaType" (index .Items 0)}}
+    {{- else if not (eq (len .OneOf) 0) -}}
+        struct{
+            {{range $idx, $v := .OneOf -}}
+                Option{{$idx}} {{template "schemaType" $v}}
+            {{end -}}
+        }
+    {{- else if not (eq (len .AnyOf) 0) -}}
+        struct{
+            {{range $idx, $v := .AnyOf -}}
+                Option{{$idx}} {{template "schemaType" $v}}
+            {{end -}}
+        }
+    {{- else if not (eq (len .AllOf) 0) -}}
+        struct{
+            {{range $idx, $v := .AllOf -}}
+                Field{{$idx}} {{template "schemaType" $v}}
+            {{end -}}
+        }
+    {{- else if not (eq (len .Enum) 0) -}}
+        string
+    {{- else -}}
+        {{goType .Type}}
+    {{- end}}
+{{- end -}}
+
 type GoOpenRPCService interface {
-    {{- range .Methods }}
-        {{- $name := .Name | camelCase }}
-        {{- $params := (maybeMethodParams .) }}
-        {{- $result := (maybeMethodResult .) }}
-        {{ maybeMethodComment . }}
-        {{$name}}
-        {{- if $params -}}
-            (*{{$params}})
-        {{- else -}}
-            ()
-        {{- end }}
-        {{- if $result }} (*{{$result}}, error)
-        {{- else }} error
-        {{- end }}
-    {{- end }}
+    {{range .Methods -}}
+        // {{.Summary}}
+        {{camelCase .Name}}(
+            {{range .Params -}}
+                {{camelCase .Name}} {{template "schemaType" .Schema}},
+            {{end -}}
+        ) ({{camelCase .Result.Name}} {{template "schemaType" .Result.Schema}})
+    {{end -}}
 }
 
-{{- range (getObjects .Objects) }}
-type {{.Name}} struct {
-    {{- range (getFields .Fields) }}
-        {{ maybeFieldComment .Desc }}
-        {{- if (eq .Name .Type) }}
-            {{ .Name }}
-        {{- else }}
-            Field{{camelCase .Name}} {{.Type}} `json:"{{lowerFirst .Name}}"`
-        {{- end }}
-    {{- end }}
-}
+{{- range $name, $component := .Components.Schemas }}
+    type {{camelCase $name}} {{template "schemaType" $component}}
 {{- end }}
 
diff --git a/openrpc/types/objects.go b/openrpc/types/objects.go
deleted file mode 100644
index 0400292..0000000
--- a/openrpc/types/objects.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package types
-
-import "gfx.cafe/open/jrpc/openrpc/util"
-
-type BasicType struct {
-	Desc string
-	Name string
-	Type string
-}
-
-type FieldMap struct {
-	fields map[string]BasicType
-	keys   []string
-}
-
-func NewFieldMap() *FieldMap {
-	return &FieldMap{
-		fields: make(map[string]BasicType, 0),
-		keys:   make([]string, 0),
-	}
-}
-
-func (fm *FieldMap) Set(key string, value BasicType) {
-	key = util.CamelCase(key)
-	value.Name = util.CamelCase(value.Name)
-	_, exists := fm.fields[key]
-	fm.fields[key] = value
-	if !exists {
-		fm.keys = append(fm.keys, key)
-	}
-}
-
-func (fm *FieldMap) Get(key string) BasicType {
-	a := fm.fields[key]
-	return a
-}
-
-func (fm *FieldMap) GetKeys() []string {
-	return fm.keys
-}
-
-type ObjectMap struct {
-	objects map[string]*FieldMap
-	keys    []string
-}
-
-func NewObjectMap() *ObjectMap {
-	return &ObjectMap{
-		objects: make(map[string]*FieldMap, 0),
-		keys:    make([]string, 0),
-	}
-}
-
-func (om *ObjectMap) Set(key string, value BasicType) {
-	if key == "" {
-		return
-	} else if value.Name == "" {
-		return
-	} else if util.CamelCase(key) == value.Type {
-		return
-	}
-	key = util.CamelCase(key)
-	_, exists := om.objects[key]
-	if !exists {
-		if om.objects[key] == nil {
-			om.objects[key] = NewFieldMap()
-		}
-		om.keys = append(om.keys, key)
-	}
-	om.objects[key].Set(value.Name, value)
-}
-
-func (om *ObjectMap) Get(key string) *FieldMap {
-	return om.objects[key]
-}
-
-func (om *ObjectMap) GetKeys() []string {
-	return om.keys
-}
diff --git a/openrpc/types/types.go b/openrpc/types/types.go
index 9227c79..f8461de 100644
--- a/openrpc/types/types.go
+++ b/openrpc/types/types.go
@@ -2,264 +2,70 @@ package types
 
 import (
 	"encoding/json"
-	"os"
-	"path"
-	"path/filepath"
-
-	"github.com/go-openapi/spec"
-	"sigs.k8s.io/yaml"
+	"fmt"
 )
 
-type Contact struct {
-	Name  string `json:"name,omitempty"`
-	URL   string `json:"url,omitempty"`
-	Email string `json:"email,omitempty"`
-}
-
-type License struct {
-	Name string `json:"name,omitempty"`
-	URL  string `json:"url,omitempty"`
-}
-
 type Info struct {
-	Title          string   `json:"title"`
-	Description    string   `json:"description,omitempty"`
-	TermsOfService string   `json:"termsOfService,omitempty"`
-	Contact        *Contact `json:"contact,omitempty"`
-	License        *License `json:"license,omitempty"`
-	Version        string   `json:"version"`
-}
-
-type ServerVariable struct {
-	Enum        []string `json:"enum,omitempty"`
-	Default     string   `json:"default,omitempty"`
-	Description string   `json:"description,omitempty"`
-}
-
-type Server struct {
-	Name        string                    `json:"name"`
-	URL         string                    `json:"url"`
-	Summary     string                    `json:"summary,omitempty"`
-	Description string                    `json:"description,omitempty"`
-	Variables   map[string]ServerVariable `json:"variables,omitempty"`
-}
-
-type ExternalDocs struct {
-	Description string `json:"description,omitempty"`
-	URL         string `json:"url,omitempty"`
-}
-
-type Tag struct {
-	Name         string        `json:"name"`
-	Summary      string        `json:"summary,omitempty"`
-	Description  string        `json:"description,omitempty"`
-	ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
-}
-
-type Content struct {
-	Name        string      `json:"name"`
-	Summary     string      `json:"summary,omitempty"`
-	Description string      `json:"description,omitempty"`
-	Required    bool        `json:"required,omitempty"`
-	Deprecated  bool        `json:"deprecated,omitempty"`
-	Schema      spec.Schema `json:"schema"`
+	Title   string `json:"title"`
+	Version string `json:"version"`
 }
 
-type ContentDescriptor struct {
-	Content
-}
-
-func (cd *ContentDescriptor) UnmarshalJSON(data []byte) error {
-	cont := new(Content)
-	err := json.Unmarshal(data, cont)
-	if err != nil {
-		return err
-	}
-	cd.Content = *cont
-
-	params := make(map[string]interface{})
-	err = json.Unmarshal(data, &params)
-	if err != nil {
-		return err
-	}
+type Items []Schema
 
-	if _, ok := params["$ref"]; ok {
-		sch := new(spec.Schema)
-		err = json.Unmarshal(data, sch)
-		if err != nil {
-			return err
+func (I *Items) UnmarshalJSON(b []byte) error {
+	switch b[0] {
+	case '{':
+		*I = []Schema{
+			{},
 		}
-		cd.Schema = *sch
+		return json.Unmarshal(b, &(*I)[0])
+	case '[':
+		return json.Unmarshal(b, (*[]Schema)(I))
+	default:
+		return fmt.Errorf("expected array or object")
 	}
-
 	return nil
-
 }
 
-// https://www.jsonrpc.org/specification#error_object
-type Error struct {
-	Code    int         `json:"code"`
-	Message string      `json:"message"`
-	Data    interface{} `json:"data"`
-}
+var _ json.Unmarshaler = (*Items)(nil)
 
-type Link struct {
-	Name        string                 `json:"name"`
-	Description string                 `json:"description"`
-	Summary     string                 `json:"summary"`
-	Method      string                 `json:"method"`
-	Params      map[string]interface{} `json:"params"`
-	Server      Server                 `json:"server"`
+type Schema struct {
+	Ref        string            `json:"$ref,omitempty"`
+	Type       string            `json:"type,omitempty"`
+	Title      string            `json:"title"`
+	Required   []string          `json:"required,omitempty"`
+	Items      Items             `json:"items,omitempty"`
+	Properties map[string]Schema `json:"properties,omitempty"`
+	OneOf      []Schema          `json:"oneOf,omitempty"`
+	AnyOf      []Schema          `json:"anyOf,omitempty"`
+	AllOf      []Schema          `json:"allOf,omitempty"`
+	Enum       []string          `json:"enum,omitempty"`
+	Pattern    string            `json:"pattern"`
 }
 
-type Example struct {
-	Name          string      `json:"name"`
-	Summary       string      `json:"summary"`
-	Description   string      `json:"description"`
-	Value         interface{} `json:"value"`
-	ExternalValue string      `json:"externalValue"`
+type Param struct {
+	Name     string `json:"name"`
+	Required bool   `json:"required"`
+	Schema   Schema `json:"schema"`
 }
 
-type ExamplePairing struct {
-	Name        string    `json:"name"`
-	Description string    `json:"description"`
-	Summary     string    `json:"summary"`
-	Params      []Example `json:"params"`
-	Result      Example   `json:"result"`
+type Result struct {
+	Name   string `json:"name"`
+	Schema Schema `json:"schema"`
 }
 
 type Method struct {
-	Name           string               `json:"name"`
-	Tags           []Tag                `json:"tags,omitempty"`
-	Summary        string               `json:"summary,omitempty"`
-	Description    string               `json:"description,omitempty"`
-	ExternalDocs   *ExternalDocs        `json:"externalDocs,omitempty"`
-	Params         []*ContentDescriptor `json:"params"`
-	Result         *ContentDescriptor   `json:"result"`
-	Deprecated     bool                 `json:"deprecated,omitempty"`
-	Servers        []Server             `json:"servers,omitempty"`
-	Errors         []Error              `json:"errors,omitempty"`
-	Links          []Link               `json:"links,omitempty"`
-	ParamStructure string               `json:"paramStructure,omitempty"`
-	Examples       []ExamplePairing     `json:"examples,omitempty"`
-}
-
-type Components struct {
-	ContentDescriptors    map[string]*ContentDescriptor `json:"contentDescriptors,omitempty"`
-	Schemas               map[string]spec.Schema        `json:"schemas,omitempty"`
-	Examples              map[string]Example            `json:"examples,omitempty"`
-	Links                 map[string]Link               `json:"links,omitempty"`
-	Errors                map[string]Error              `json:"errors,omitempty"`
-	ExamplePairingObjects map[string]ExamplePairing     `json:"examplePairingObjects,omitempty"`
-	Tags                  map[string]Tag                `json:"tags,omitempty"`
-}
-
-type OpenRPCSpec1 struct {
-	OpenRPC      string        `json:"openrpc"`
-	Info         Info          `json:"info"`
-	Servers      []Server      `json:"servers"`
-	Methods      []Method      `json:"methods"`
-	Components   Components    `json:"components"`
-	ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
-
-	Objects *ObjectMap `json:"-"`
-}
-
-func NewOpenRPCSpec1() *OpenRPCSpec1 {
-	return &OpenRPCSpec1{
-		OpenRPC: "1.0.0",
-		Info: Info{
-			Title:   "gfx.cafe/open/jrpc/openrpc",
-			Version: "0.0.0",
-		},
-		Servers: make([]Server, 0),
-		Methods: make([]Method, 0),
-
-		Objects: NewObjectMap(),
-	}
-}
-
-func (o *OpenRPCSpec1) AddSchemas(pth string) error {
-	dr, err := os.ReadDir(pth)
-	if err != nil {
-		return err
-	}
-	for _, v := range dr {
-		if v.IsDir() {
-			if err := o.AddSchemas(path.Join(pth, v.Name())); err != nil {
-				return err
-			}
-		} else {
-			if err := o.AddSchema(path.Join(pth, v.Name())); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func (o *OpenRPCSpec1) AddSchema(pth string) error {
-	schem := map[string]spec.Schema{}
-	bts, err := os.ReadFile(pth)
-	if err != nil {
-		return err
-	}
-	ext := filepath.Ext(path.Base(pth))
-	switch ext {
-	case ".json":
-		err = json.Unmarshal(bts, &schem)
-	case ".yml", ".yaml":
-		err = yaml.Unmarshal(bts, &schem)
-	}
-	if err != nil {
-		return err
-	}
-	if o.Components.Schemas == nil {
-		o.Components.Schemas = map[string]spec.Schema{}
-	}
-	for k, v := range schem {
-		o.Components.Schemas[k] = v
-	}
-	return nil
-}
-
-func (o *OpenRPCSpec1) AddMethods(pth string) error {
-	dr, err := os.ReadDir(pth)
-	if err != nil {
-		return err
-	}
-	for _, v := range dr {
-		if v.IsDir() {
-			if err := o.AddMethods(path.Join(pth, v.Name())); err != nil {
-				return err
-			}
-		} else {
-			if err := o.AddMethod(path.Join(pth, v.Name())); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func (o *OpenRPCSpec1) AddMethod(pth string) error {
-	var meth []Method
-	bts, err := os.ReadFile(pth)
-	if err != nil {
-		return err
-	}
-	switch filepath.Ext(path.Base(pth)) {
-	case ".json":
-		err = json.Unmarshal(bts, &meth)
-	case ".yml", ".yaml":
-		err = yaml.Unmarshal(bts, &meth)
-		if err != nil {
-			return err
-		}
-	}
-	if err != nil {
-		return err
-	}
-	o.Methods = append(o.Methods, meth...)
-	return nil
+	Name    string  `json:"name"`
+	Summary string  `json:"summary"`
+	Params  []Param `json:"params"`
+	Result  Result  `json:"result"`
+}
+
+type OpenRPC struct {
+	Version    string   `json:"openrpc"`
+	Info       Info     `json:"info"`
+	Methods    []Method `json:"methods"`
+	Components struct {
+		Schemas map[string]Schema `json:"schemas"`
+	} `json:"components"`
 }
-- 
GitLab