Skip to content

Commit e936314

Browse files
committed
document
1 parent 6056740 commit e936314

File tree

9 files changed

+148
-71
lines changed

9 files changed

+148
-71
lines changed

README.md

Lines changed: 126 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,135 @@
11
# Offchain
22

3-
Like crosschain, but offchain.
3+
Offchain is a server that consolidates API keys in a central, secure location, and presents
4+
a universal API that can be used to manage funds. This is intended for making withdrawals,
5+
sub-account transfers, and deposits. Trading is out of scope.
46

5-
## Addresses
7+
Being able to withdraw funds directly from an exchange is a very sensitive operation. If you
8+
withdraw to the wrong address, those funds could be lost forever. This necessitates a separate
9+
application to take 'custody' of withdrawal-permissioned API keys.
610

7-
For our testing, we want addresses to add the following "crosschain" addresses
8-
to exchange allowlists. If there is a universal address option, we should use that.
11+
### Features
912

10-
API keys should either be read only, or have full read + trade + withdraw permissions.
13+
- Simple configuration
14+
- Single binary
15+
- Rich exchange support and incredibly lightweight framework to easily add more
16+
- Strong authentication using ed25519 based [http-signatures](https://datatracker.ietf.org/doc/html/rfc9421).
17+
- Stateless
18+
- Universal asset symbology (based on Cordial Systems asset registry)
19+
- Can store exchange API keys in popular secret managers (vault, gcp, aws, etc).
20+
- Ability exchange exchange operations on CLI.
21+
- [Stable OpenAPI API](https://cordialapis.stoplight.io/docs/Exchange/2gnp0107q21eh-exchange)
1122

23+
# Usage
24+
25+
First, configure API keys for supported exchanges.
26+
27+
```yaml
28+
# config.yaml
29+
offchain:
30+
# setup API keys for the exchanges
31+
exchanges:
32+
binance:
33+
# load from your favorite secret manager
34+
api_key: "gcp:your_gcp_project,API_KEY_NAME"
35+
secret_key: "gcp:your_gcp_project,API_SECRET_NAME"
36+
# include any sub-accounts
37+
subaccounts:
38+
- id: "offchain1@example.com"
39+
api_key: "gcp:your_gcp_project,SUB1_API_KEY_NAME"
40+
secret_key: "gcp:your_gcp_project,SUB1_API_KEY_SECRET_NAME"
41+
# ...
42+
```
43+
44+
API keys can be loaded from env, file, or from you favorite secret manager (see `oc secret --help`).
45+
46+
Second, generate a ed25519 key to authenticate to the `offchain` server. [HTTP Signatures](https://datatracker.ietf.org/doc/html/rfc9421)
47+
are used.
48+
49+
You can generate a client-side key like so (private key will be written to disk).
50+
51+
```bash
52+
oc keys generate mykey
53+
# client-keys/mykey
54+
# e7a205bbe21184f4f6cd72e7ba659566d96b8aea00e49b9967328b0109f9c706
55+
```
56+
57+
Now update your server configuration again with the public key.
58+
59+
```yaml
60+
offchain:
61+
server:
62+
public_keys:
63+
- id: "mykey"
64+
key: "abf9649d7a0a7534cde49f12de47effd601e60a2258e51b5a257af9ef78e901f"
1265
```
13-
SOL 8Rub84DA2L6BH2FVKzVBxD7jp1i6o1BtrCunm51hQcg2
14-
EVM 0x3fa26f0dc74cfa8a56cb9d5f94245524d0888277
15-
APTOS 0x5249a0f1ccb427e6595343ef001ec18765fd325beb70fbea0a9c25807167e60d
66+
67+
Start the server.
68+
69+
```bash
70+
oc start --config ./config.yaml -v
1671
```
1772

18-
Ideally tokens like USDC and USDT should be able to be used on the addresses above.
73+
Now make requests against it.
74+
75+
```bash
76+
# Lookup main account balances
77+
oc api balances --exchange binance --sign-with mykey
78+
79+
# Lookup balance of a sub-account
80+
oc api balances --subaccount offchain1@example.com --exchange binance --sign-with mykey
81+
82+
# Make sub-account transfer from main to sub-account
83+
oc api --exchange binance transfer --to offchain1@example.com --symbol USDC --amount 3 --sign-with mykey
84+
# From sub-account to main
85+
oc api --exchange binance transfer --from offchain1@example.com --symbol USDC --amount 2 --sign-with mykey
86+
87+
# Make a withdrawal from main account
88+
oc api --exchange binance withdraw --to "<your-solana-address>" --network SOL --symbol USDC --sign-with mykey
89+
90+
# Look at withdrawal history
91+
oc api --exchange binance history --sign-with mykey
92+
```
93+
94+
# Policy
95+
96+
To further enhance the security, policies should be built on top of `offchain`. For example, you should check that
97+
a withdrawal address is approved before signing a request to `offchain`.
98+
99+
[Cordial Treasury](https://cordialsystems.com/) integrates with `offchain` and includes rich Transfer policies. It is default deny,
100+
and allows you to easily build allow-lists, notional transfer limits, and approval/quorom conditions.
101+
102+
![](./docs/treasury-example.png)
103+
104+
Cordial Treasury will enforce policies, then sign requests to offchain using an MPC key. Both Cordial Treasury and offchain are non-custodial, self-hosted products.
105+
106+
# Supported Exchanges
107+
108+
Exchange clients are lightweight, pure go implementations. It's very easy to add support for new exchanges.
109+
110+
- Backpack
111+
- Binance
112+
- Binance US
113+
- Bybit
114+
- Okx
115+
116+
# API Reference
117+
118+
See the [API reference](https://cordialapis.stoplight.io/docs/Exchange/2gnp0107q21eh-exchange).
119+
120+
For http-authorization, you can look at or use the simple [http-signature package](./pkg/httpsignature/).
121+
122+
# Crosschain
123+
124+
We maintain a similar library, [crosschain](https://github.com/cordialsys/crosschain). It's like offchain,
125+
but solely for blockchains. You can locally generate addresses and make transfers on many different blockchains (as well as manage staking).
126+
127+
You could use both `oc` and `xc` to fully rebalance and/or take custody of your portfolio across all different exchanges
128+
and blockchain networks.
129+
130+
# Roadmap
131+
132+
### Universal asset symbols
133+
134+
We are still building out the universal asset integration. If you have interest in this, reach out to us at https://cordialsystems.com/contact
135+
and we can escalate.

cmd/oc/exchange/cmd.go

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,8 @@ func printJson(data interface{}) {
2929

3030
type contextKey string
3131

32-
// const exchangeConfigKey contextKey = "exchange_config"
33-
34-
// const exchangeAccountSecretsKey contextKey = "exchange_account_secrets"
35-
// const configContextKey contextKey = "config"
3632
const exchangeClientKey contextKey = "exchange_client"
3733

38-
// func unwrapExchangeConfig(ctx context.Context) *oc.ExchangeConfig {
39-
// return ctx.Value(exchangeConfigKey).(*oc.ExchangeConfig)
40-
// }
41-
42-
// func unwrapAccountSecrets(ctx context.Context) *oc.Account {
43-
// return ctx.Value(exchangeAccountSecretsKey).(*oc.Account)
44-
// }
45-
46-
// func unwrapAccountConfig(ctx context.Context) (*oc.ExchangeConfig, *oc.Account) {
47-
// exchangeConfig := unwrapExchangeConfig(ctx)
48-
// secrets := unwrapAccountSecrets(ctx)
49-
// return exchangeConfig, secrets
50-
// }
51-
5234
func unwrapClient(ctx context.Context) loader.Client {
5335
return ctx.Value(exchangeClientKey).(loader.Client)
5436
}
@@ -197,7 +179,7 @@ func NewExchangeCmd() *cobra.Command {
197179
cmd := &cobra.Command{
198180
Use: "exchange",
199181
Short: "Execute command directly using exchange APIs and local secrets configuration",
200-
Aliases: []string{"e", "ex"},
182+
Aliases: []string{"x", "ex"},
201183
SilenceUsage: true,
202184
PersistentPreRunE: exchangePreRun,
203185
}

cmd/oc/exchange/list_subaccounts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ func NewListSubaccountsCmd() *cobra.Command {
1010
SilenceUsage: true,
1111
Use: "subaccounts",
1212
Short: "List all configured subaccounts on an exchange",
13+
Aliases: []string{"sub-accounts"},
1314

1415
RunE: func(cmd *cobra.Command, args []string) error {
1516
cli := unwrapClient(cmd.Context())

cmd/oc/secret.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ import (
1010

1111
func NewSecretCmd() *cobra.Command {
1212
multi := false
13+
help := ""
14+
for _, t := range secret.Types {
15+
help += fmt.Sprintf("%s: %s\n", t, t.Usage())
16+
}
1317
cmd := &cobra.Command{
1418
SilenceUsage: true,
1519
Use: "secret <reference>",
1620
Short: "Print out a secret given a reference",
21+
Long: help,
1722
Args: cobra.ExactArgs(1),
1823
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
1924
return nil

docs/treasury-example.png

45.9 KB
Loading

examples/example-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
offchain:
2+
# set httpsignature public keys for the endpoints
3+
public_keys:
4+
- id: "key1"
5+
key: "abf9649d7a0a7534cde49f12de47effd601e60a2258e51b5a257af9ef78e901f"
6+
# optionally, set bearer tokens for the read endpoints (not relevant for write endpoints)
7+
bearer_tokens:
8+
- id: "token1"
9+
token: "raw:1234567890"
10+
11+
# setup API keys for the exchanges
212
exchanges:
313
okx:
414
# load from env

pkg/httpsignature/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Http Signature
2+
3+
This implements both signing and verification for HTTP signatures in go.
4+
It is standalone and can be used as a library.

server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func New(ocConf *oc.Config, args ServerArgs) *Server {
8080

8181
// read subaccount if used in header or query
8282
subaccount := c.Get("sub-account")
83-
if subaccount != "" {
83+
if subaccount == "" {
8484
subaccount = c.Query("sub-account")
8585
}
8686
c.Locals("sub-account", subaccount)

subaccount-notes.md

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)