New docs (#20)

* docs - wip

* add note to write about your-own-token-scenario

* add reminder to myself to write about backend implementation in security risks

* docs: finish writing README, CLI and example integrations

* add endpoints documentation
This commit is contained in:
Seva Zhidkov 2022-09-09 19:02:03 +03:00 committed by GitHub
parent f06b842982
commit 630a8d6afc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 366 additions and 21 deletions

100
README.md
View File

@ -8,7 +8,9 @@
## What is Octane?
Octane is a gasless transaction relayer for Solana.
Octane is a gasless transaction relayer for Solana. Octane accepts transactions via an HTTP API, signs them if they satisfy certain conditions and broadcasts to the network.
It's designed for anyone to be able to run for free on Vercel as a collection of serverless Node.js API functions.
Transaction fees on Solana are very inexpensive, but users still need SOL to pay for them, and they often don't know (or forget) this.
@ -18,12 +20,22 @@ Sometimes users stake all their SOL or swap it for tokens or mint an NFT and don
Sometimes a merchant or dApp would like to pay for certain transactions on behalf of their users.
Octane is designed for anyone to be able to run for free on Vercel as a collection of serverless Node.js API functions.
Octane exists to solve these scenarios.
![Overview of Octane architecture](overview.png)
## How does it work?
Octane provides an API that lets users pay for transactions with SPL token transfers instead of native SOL.
It leverages unique properties of Solana:
1) Transaction can have multiple signers, one of whom is the transaction fee payer. There is no single "msg.sender".
2) Transaction can have multiple instructions interacting with different programs that are executed atomically. If one instruction fail, whole transactions fails.
3) Each instruction refers to accounts it touches as writable or readable. It allows to validate transactions before signing.
A user creates a transaction that contains an instruction for a small token transfer to Octane, along with whatever else their transaction is supposed to do.
The user partially signs the transaction, authorizing it to make the transfer, and so it can't be modified by Octane or MITM attacks.
@ -40,30 +52,28 @@ Octane operates trustlessly and is designed to be easily run by anyone in an adv
It uses ratelimiting, transaction validation, and transaction simulation to mitigate DoS, spam, draining, and other attacks.
_However..._
However, there are some risks associated with running an Octane node:
---
1) Token-to-SOL price spread. Octane is configured to accept specific amounts of SPL tokens for paying fixed transaction fees in SOL. Since the token price relative to SOL can change, Octane could end up in a state where it loses money on every transaction.
2) Draining possibilities due to Octane software bugs. Octane signs user-generated transactions with fee payer's keypair after confirming a transaction transfers fee and does not try to modify fee payer's accounts. However, if implemented checks are insufficient due to a bug, an attacker could run transaction without paying the fee or modify fee payer's accounts.
🚨 **Octane is untested alpha software!** 🚨
---
Please don't run Octane on Mainnet Beta yet!
It may contain bugs and vulnerabilities.
It doesn't completely prevent spam.
It doesn't prevent simulation bypass attacks.
It doesn't use CAPTCHAs (but it could).
Please do help us build, test, and make it better.
Follow these recommendations to minimize risks:
1. Run Octane on a new separate keypair, not used in governance or within contracts as an authority.
2. Set SPL token price for transactions with a premium relative to their real cost in SOL.
3. Don't hold more SOL on the keypair than needed for 3-4 hours of spending on transaction fees for your load expectations. It could be as low as 0.2-1 SOL.
4. Every hour automatically received swap tokens to SOL (Octane provides a CLI for that).
5. Regularly check that prices and liquidity of SPL tokens allow your profitably pay for transaction fees in SOL.
6. If your Octane node makes profit, regularly withdraw that profit to another keypair.
7. When using Octane as a library in your backend, make sure to:
1. Never return fee payer's signature of an unconfirmed transaction to a user. You must submit transaction to the network from the backend.
2. Implement duplicated transaction checks, limits per user and general rate limits
## What does Octane want?
Octane wants to make Solana easier to use by abstracting away some complexity that leads to user confusion and error.
Octane wants to enable SOL-less wallets for new users in crypto, allowing them to operate only in stablecoins.
Octane wants to become integrated with wallets, support multiple tokens with different fees, and perform atomic swaps to pay for transactions or get SOL.
Octane wants to be customizable for decentralized applications that want to sponsor their users transactions.
@ -74,4 +84,54 @@ Octane wants to be secure, well-tested, well-documented, and easy to use.
## How do I use it?
You can get started by following the steps in [SETUP](SETUP.md).
1. You can use Octane as a server application to deploy on a platform like Render or Vercel.
2. You can use someone else's node. This way you don't have to support your own server and manage funds on fee payer account. However, you'll be limited by SPL tokens they offer at their price per signature.
3. You can integrate Octane into your backend by using it as a Node.js library.
### Common integration scenarios
1. **For wallets**: let your users pay transaction fees and associated account initialization fees with liquid SPL tokens.
2. **For wallets**: let your users convert liquid SPL tokens to SOL without paying any fees in SOL.
3. **For wallets and dapps**: convert tokens to SOL when a user doesn't have enough SOL to execute a transaction.
4. **For dapps and ecosystems**: fully sponsor transactions for authorized users.
5. **For dapps with tokens**: let your users pay transaction fees in your token.
### Setting up your own node
You can get started by following the steps in [SETUP](SETUP.md). You'll need to support a fee payer account and manage prices in `config.json`
### Using Octane from a client app
If you already have set up an Octane node, or you are using a public endpoint from someone else, it's the time to integrate the node with your client code. Follow [this guide](docs/example-integration.md) to run your first transaction through Octane. You can also view all Octane endpoints and their parameters on [this page](docs/endpoints.md).
### Managing fee payer account
If you host your own Octane node, you'll need to manage fee payer account. Octane provides a handy CLI for that: it allows to swap tokens to SOL, create accounts and generate config. Go through [this guide](docs/CLI.md) to learn more.
## Is there an example app that uses Octane?
[octane-demo](https://github.com/sevazhidkov/octane-demo) is an open source app that showcases various types of gasless transactions using Octane.
## How is the code structured?
Octane is built as a monorepo with multiple packages using [Lerna](https://lerna.js.org/).
[Core](https://github.com/solana-labs/octane/tree/master/packages/core) package provides reusable functions that process and sign transactions ("actions").
[Server](https://github.com/solana-labs/octane/tree/master/packages/package) package is a Next.js app that runs the server using API functions. It's also responsible for parsing `config.json` and managing web-specific security tools (CORS and rate limits).
If you want to create a new action (for example, swap on a new protocol), you'll need to add it as an action in Core and add a new endpoint in Server that calls new action.
Octane utilizes cache for some rate limiting, but generally should be stateless.
## How to contribute?
Octane is a great way to start contributing in the Solana ecosystem. Since Octane is just an HTTP server written on Typescript, you don't have know Rust or learn how to build programs on Solana.
Some ideas for your first PRs:
* More tests with various scenarios focused on Octane security
* Docs, guides and example code for new people to get started with Octane
* Add support for more exchange protocols
* Build apps for consumers on top of Octane
Also, you can run your own Octane node and promote it among developers.

View File

@ -1,4 +1,4 @@
# Set up Octane ⛽
# Set up your own Octane node ⛽
This guide assumes you have:
@ -18,6 +18,8 @@ Fork the project to create your own deployment of Octane.
![Github fork repo](setup/1_github_fork_repo.png)
If you want to keep your fork private, follow [this guide](https://gist.github.com/0xjac/85097472043b697ab57ba1b1c7530274).
## 2. Clone your fork
From your fork, copy the git link.

86
docs/CLI.md Normal file
View File

@ -0,0 +1,86 @@
# Command-line tools
You can launch command-line tools from your Octane server directory. You can learn how to set it up in [SETUP](SETUP.md).
## Generating config with popular tokens
`yarn run cli generate-config-with-popular-tokens `
Generates JSON to put in `config.json` as token fees for a specific endpoint. It loads popular tokens from Jupiter aggregator, calculates fees according to arguments and selects accounts to receive fees.
Config should be generated for each endpoint separately — in most cases, with different arguments.
Endpoints like `transfer` and `buildWhirlpoolSwap` require less SOL, so margins could be higher. Endpoint `createAssociatedTokenAccount`, that charges fee payer for a rent exemption payment on a new token account, must include `--include-account-fees` and may use lower margins, since the original price would be much higher.
```
{
"rpcUrl": "https://api.mainnet-beta.solana.com",
"maxSignatures": 2,
"lamportsPerSignature": 5000,
"corsOrigin": true,
"endpoints": {
"transfer": {
"tokens": [
// [ Result #1, yarn run cli generate-config-with-popular-tokens ]
]
},
"buildWhirlpoolSwap": {
"tokens": [
// [ Result #2, yarn run cli generate-config-with-popular-tokens --n 5 ]
]
}
"createAssociatedTokenAccount": {
"tokens": [
// [ Result #3, yarn run cli generate-config-with-popular-tokens --include-account-fees ]
]
}
}
}
````
| Argument | Description | Default value |
|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| -t, --tokens-from-top <number> | Tokens from the top of Jupiter aggregator to include | 10 |
| -m, --margin <number> | Part of total user-paid fee that fee payers takes as a surplus to transaction costs. From 0 to 1. For example, 0.5 would mean that user pays 2x the SOL signature fee. | 0.9 |
| -a, --include-account-fees | Includes cost creating a associated token account in each fee pre-margin. Use this flag when filling out the endpoints.createAssociatedAccount part of config. | |
## Generating a config entry
`yarn run cli generate-config-entry`
Generates a config entry for a single provided token. All rules of `generate-config-with-popular-tokens` apply here as well.
| Argument | Description | Default value |
|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| -m, --margin <number> | Part of total user-paid fee that fee payers takes as a surplus to transaction costs. From 0 to 1. For example, 0.5 would mean that user pays 2x the SOL signature fee. | 0.9 |
| -a, --include-account-fees | Includes cost creating a associated token account in each fee pre-margin. Use this flag when filling out the endpoints.createAssociatedAccount part of config. | |
## Swapping tokens
`yarn run cli swap-tokens-to-sol`
Swaps tokens on accounts in `config.json` to SOL using Jupiter aggregator HTTP API.
It's recommended to run this command automatically every few hours to make sure fee payer always has enough SOL.
| Argument | Description | Default value |
|--------------------------|------------------------------------------------------|---------------|
| -d, --dry-run | Do not execute swaps, just calculate routes | |
| -t, --threshold <number> | Minimum value of tokens to exchange, in SOL lamports | 100000000 |
## Create accounts
`yarn run cli create-accounts`
Creates associated token accounts for tokens specified in `config.json`. Octane will need these accounts to receive fee payments
from users.
| Argument | Description | Default value |
|--------------------------|------------------------------------------------------------|---------------|
| -d, --dry-run | Do not create accounts, just output what should be created | |

134
docs/endpoints.md Normal file
View File

@ -0,0 +1,134 @@
# Endpoints
### Get node configuration
`GET /api/`
Load Octane node configuration. Config is used to correctly set token fee and fee payer in the created transaction.
No parameters.
Example response:
```json
{
"feePayer": "AmnCNDKh74yWiyAtA3gn6tBBdyU7qzdxYkeXhZRqMNZm",
"rpcUrl": "https://api.mainnet-beta.solana.com",
"maxSignatures": 2,
"lamportsPerSignature": 5000,
"corsOrigin": true,
"endpoints": {
"transfer": {
"tokens": [
{
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"account": "Ar9LjjzJoAhqVQ5xjtAqRhRLtanzYPT72bBv2MZ8ggA1",
"decimals": 6,
"fee": 1553
}
]
},
"createAssociatedTokenAccount": {
"tokens": [
{
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"account": "Ar9LjjzJoAhqVQ5xjtAqRhRLtanzYPT72bBv2MZ8ggA1",
"decimals": 6,
"fee": 127014
}
]
},
"whirlpoolsSwap": {
"tokens": [
{
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"account": "Ar9LjjzJoAhqVQ5xjtAqRhRLtanzYPT72bBv2MZ8ggA1",
"decimals": 6,
"fee": 1553
}
]
}
}
}
```
[Source code](https://github.com/solana-labs/octane/blob/master/packages/server/pages/api/index.ts)
### Create a new associated token account
`POST /api/createAssociatedTokenAccount`
The transaction should transfer a fee to Octane as first instruction and create new associated token account as second instruction.
| Parameter | Type | Description |
|-------------|--------|-------------------------------------------------------------|
| transaction | String | Base58-encoded serialized transaction with user's signature |
Example response:
```json
{
"status": "ok",
"signature": "LSYkHUMUuPCmnScxtbqPrBiy8Eiw28NHvwRzbRRix2v8jon8RKMNYkqxH23E9Mabks985AKeR5293ekQzLoTGBT"
}
```
[Source code](https://github.com/solana-labs/octane/blob/master/packages/server/pages/api/createAssociatedTokenAccount.ts)
### Submit an arbitrary transaction
`POST /api/transfer`
Submit an arbitrary transaction. First instruction should transfer a fee to Octane. Transaction can't create new accounts: use `/createAssociatedTokenAccount` for Token Program and just-in-time swaps for other programs.
| Parameter | Type | Description |
|-------------|--------|-------------------------------------------------------------|
| transaction | String | Base58-encoded serialized transaction with user's signature |
Example response:
```json
{
"status": "ok",
"signature": "LSYkHUMUuPCmnScxtbqPrBiy8Eiw28NHvwRzbRRix2v8jon8RKMNYkqxH23E9Mabks985AKeR5293ekQzLoTGBT"
}
```
[Source code](https://github.com/solana-labs/octane/blob/master/packages/server/pages/api/transfer.ts)
### Build a Whirlpools swap transaction
`POST /api/buildWhirlpoolsSwap`
Creates a transaction that allows to exchange an SPL token to SOL without having any SOL. It also returns `messageToken`: it needs to be passed to `/sendWhirlpoolsSwap` when submitting the signed transaction.
| Parameter | Type | Description |
|-------------------|--------|----------------------------------------------------------|
| user | String | Base58-encoded public key of user who wants to make swap |
| sourceMint | String | Base58-encoded mint of source SPL token |
| amount | Number | Amount of source token to swap, in decimals notation |
| slippingTolerance | Number | Slipping tolerance for swap |
Example response:
```json
{
"status": "ok",
"transaction": "[...]",
"messageToken": "[...]",
"quote": {}
}
```
[Source code](https://github.com/solana-labs/octane/blob/master/packages/server/pages/api/buildWhirlpoolsSwap.ts)
### Send a Whirlpools swap transaction
`POST /api/sendWhirlpoolsSwap`
Sends a swap transaction after it was signed by the user.
| Parameter | Type | Description |
|--------------|--------|-------------------------------------------------------------|
| transaction | String | Base58-encoded serialized transaction with user's signature |
| messageToken | String | Message token from `/buildWhirlpoolsSwap` result |
[Source code](https://github.com/solana-labs/octane/blob/master/packages/server/pages/api/sendWhirlpoolsSwap.ts)

View File

@ -0,0 +1,63 @@
# Example integration
This guide will walk you though integrating with an existing Octane node to send a gasless transfer.
## Load Octane config
First, let's load Octane node configuration. We need to know how many tokens we should transfer to pay for their transaction and which account will be the fee payer:
```javascript
const response = (await axios.get('https://octane-devnet.breakroom.show/api', {
headers: {'Accept': 'application/json'}
})).data;
const feePayer = new PublicKey(response.feePayer);
// First token in the list is USDC
const mint = new PublicKey(response.endpoints.transfer.tokens[0].mint);
const simpleTransactionFee = response.endpoints.transfer.tokens[0].fee;
```
## Prepare transaction
Before we can proceed, we'll need some additional information about the user:
```javascript
// `publicKey` should be loaded from wallet adapter
const userTokenAccount = await getAssociatedTokenAddress(mint, publicKey);
// Let's say we want to send a gasless transfer to this public key
const targetOwner = new PublicKey('EmMC1F6X25qsnXVzNzKUyqFWfLF2GsVJW55fGJRm9feY');
const targetAccount = await getAssociatedTokenAddress(mint, targetOwner);
```
Now, we're ready to create the transaction with two instructions:
1. Send token fee to Octane's account
2. Send token transfer to any public key (but this transaction could be anything else)
We also should set feePayer and recentBlockhash, and then sign the transaction using the end user wallet.
```javascript
const transaction = new Transaction();
transaction.add(createTransferInstruction(userTokenAccount, tokenAccount.address, userPublicKey, simpleTransactionFee));
transaction.add(createTransferInstruction(userTokenAccount, targetAccount, userPublicKey, 100));
transaction.feePayer = feePayer;
transaction.recentBlockhash = (await connection.getRecentBlockhash()).blockhash;
await signTransaction(transaction);
```
## Submit transaction
Now, we have the transaction with end user's signature. However, the transaction lacks signature of fee payer.
We need to call an Octane HTTP endpoint to get transaction signed and submitted to the network:
```javascript
const octaneResponse = (await axios.post('https://octane-mainnet-beta.breakroom.show/api/transfer', {
transaction: base58.encode(transaction.serialize({requireAllSignatures: false})),
})).data;
console.log(octaneResponse);
```
It's done!

BIN
overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB