Skip to content

Commit 6952fd3

Browse files
authored
Merge pull request #1 from TradeOnDESK/feat/LD-1806-desk-plugin
[main][feat] LD-1806: DESK Plugin
2 parents b5acce7 + 7bc0b6e commit 6952fd3

16 files changed

Lines changed: 5601 additions & 0 deletions

plugins/deskPlugin/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# DESK Exchange Plugin Configuration
2+
3+
DESK_EXCHANGE_PRIVATE_KEY=
4+
5+
DESK_EXCHANGE_NETWORK=

plugins/deskPlugin/.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#dependencies
2+
node_modules/
3+
4+
#env
5+
.env
6+
7+
#build
8+
dist/
9+
10+
#IDE
11+
.vscode/
12+
.idea/
13+
14+
#test
15+
coverage/

plugins/deskPlugin/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Desk Plugin
2+
3+
A plugin for interacting with a trading desk/exchange, providing functionality for perpetual trading, account management, and order handling.
4+
5+
## Features
6+
7+
- Account summary retrieval
8+
- Perpetual trading execution
9+
- Order management (placing and canceling orders)
10+
11+
## Installation
12+
13+
```bash
14+
pnpm install @virtuals-protocol/desk-plugin
15+
```
16+
17+
## Configuration
18+
19+
The plugin requires the following environment variables:
20+
21+
- `DESK_EXCHANGE_PRIVATE_KEY`: Your private key for authentication
22+
- `DESK_EXCHANGE_NETWORK`: Network to connect to (`mainnet` or `testnet`)
23+
24+
## Usage
25+
26+
### Initializing the Plugin
27+
28+
```typescript
29+
import DeskPlugin from '@virtuals-protocol/desk-plugin'
30+
31+
const deskPlugin = new DeskPlugin({
32+
credentials: {
33+
network: 'testnet', // or 'mainnet'
34+
privateKey: 'YOUR_PRIVATE_KEY'
35+
}
36+
})
37+
```
38+
39+
### Available Functions
40+
41+
#### Get Account Summary
42+
43+
Retrieves a comprehensive summary of your account, including positions, orders, and collaterals.
44+
45+
```typescript
46+
const summary = await deskPlugin.getAccountSummary.executable({}, logger)
47+
```
48+
49+
#### Place Perpetual Trade
50+
51+
Execute a perpetual trade with specified parameters.
52+
53+
```typescript
54+
const tradeRequest = {
55+
amount: '1', // Desired amount
56+
price: '10000', // Market price
57+
side: 'Long', // 'Long' or 'Short'
58+
symbol: 'BTC' // Trading pair symbol (without 'USD')
59+
}
60+
61+
const trade = await deskPlugin.perpTrade.executable(tradeRequest, logger)
62+
```
63+
64+
#### Cancel Orders
65+
66+
Cancel all open orders for the account.
67+
68+
```typescript
69+
const cancelResult = await deskPlugin.cancelOrders.executable({}, logger)
70+
```
71+
72+
## Response Format
73+
74+
All functions return an `ExecutableGameFunctionResponse` with the following structure:
75+
76+
```typescript
77+
{
78+
status: ExecutableGameFunctionStatus;
79+
feedback: string;
80+
}
81+
```
82+
83+
## Testing
84+
85+
To run the tests:
86+
87+
```bash
88+
pnpm test
89+
```
90+
91+
Make sure to set up the required environment variables before running tests.
92+
93+
## License
94+
95+
[License Type] - See LICENSE file for details
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { beforeAll, describe, expect, it, jest } from '@jest/globals'
2+
import { ExecutableGameFunctionResponse, ExecutableGameFunctionStatus, GameWorker } from '@virtuals-protocol/game'
3+
4+
import { GAME_WORKER_FUNCTIONS, SUCCESS_MESSAGES } from '../src/constants'
5+
import DeskPlugin from '../src/deskPlugin'
6+
import { logger } from './log'
7+
8+
describe('DeskPlugin', () => {
9+
const DESK_EXCHANGE_PRIVATE_KEY: string = process.env.DESK_EXCHANGE_PRIVATE_KEY || ''
10+
const DESK_EXCHANGE_NETWORK: 'mainnet' | 'testnet' = process.env.DESK_EXCHANGE_NETWORK as 'mainnet' | 'testnet'
11+
let deskPlugin: DeskPlugin
12+
let _logger: (msg: string) => void
13+
14+
// setup
15+
beforeAll(async () => {
16+
// clear caches
17+
jest.resetModules()
18+
// init desk plugin
19+
deskPlugin = new DeskPlugin({
20+
credentials: {
21+
network: DESK_EXCHANGE_NETWORK,
22+
privateKey: DESK_EXCHANGE_PRIVATE_KEY,
23+
},
24+
})
25+
// apply logger
26+
_logger = logger
27+
})
28+
29+
// get worker
30+
describe('getWorker', () => {
31+
it('should return a GameWorker with default functions', () => {
32+
const worker: GameWorker = deskPlugin.getWorker()
33+
expect(worker).toBeDefined()
34+
expect(worker.functions).toHaveLength(GAME_WORKER_FUNCTIONS.length)
35+
expect(worker.functions.map((f: GameWorker['functions'][0]) => f.name)).toEqual(GAME_WORKER_FUNCTIONS)
36+
})
37+
})
38+
39+
// get account summary
40+
describe('getAccountSummary', () => {
41+
// success case
42+
it('should return account summary', async () => {
43+
const result: ExecutableGameFunctionResponse = await deskPlugin.getAccountSummary.executable({}, _logger)
44+
expect(result).toBeDefined()
45+
expect(result.status).toBe(ExecutableGameFunctionStatus.Done)
46+
expect(result.feedback).toContain(SUCCESS_MESSAGES.ACCOUNT_SUMMARY)
47+
expect(result.feedback).toContain(SUCCESS_MESSAGES.YOUR_POSITIONS)
48+
expect(result.feedback).toContain(SUCCESS_MESSAGES.YOUR_ORDERS)
49+
expect(result.feedback).toContain(SUCCESS_MESSAGES.YOUR_COLLATERALS)
50+
})
51+
})
52+
53+
// perp trade
54+
describe('perpTrade', () => {
55+
// success case (either found or not found)
56+
it('should successfully place order', async () => {
57+
const request: Partial<{ amount: string; price: string; side: string; symbol: string }> = {
58+
// NOTE: desire amount
59+
amount: 'YOUR_DESIRE_AMOUNT',
60+
// NOTE: check up price on market info
61+
price: 'YOUR_DESIRE_PRICE',
62+
// NOTE: side, ex:`Long` or `Short`
63+
side: 'YOUR_DESIRE_SIDE',
64+
// NOTE: symbol without `USD`, ex: `BTC`
65+
symbol: 'YOUR_DESIRE_SYMBOL',
66+
}
67+
const result: ExecutableGameFunctionResponse = await deskPlugin.perpTrade.executable(request, _logger)
68+
expect(result).toBeDefined()
69+
expect(result.status).toBe(ExecutableGameFunctionStatus.Done)
70+
expect(result.feedback).toContain(SUCCESS_MESSAGES.PERP_TRADE)
71+
})
72+
})
73+
74+
// cancel orders
75+
describe('cancelOrders', () => {
76+
// success case (either found or not found any open orders)
77+
it('should successfully cancel open orders', async () => {
78+
const result: ExecutableGameFunctionResponse = await deskPlugin.cancelOrders.executable({}, _logger)
79+
expect(result).toBeDefined()
80+
expect(result.status).toBe(ExecutableGameFunctionStatus.Done)
81+
expect(result.feedback).toContain(SUCCESS_MESSAGES.ORDER_CANCELLED)
82+
})
83+
})
84+
})

plugins/deskPlugin/__test__/log.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { log } from 'console'
2+
3+
export const logger = (msg: string): void => {
4+
log(msg)
5+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import js from '@eslint/js'
2+
import configPrettier from 'eslint-config-prettier'
3+
import importPlugin from 'eslint-plugin-import'
4+
import prettierPlugin from 'eslint-plugin-prettier'
5+
import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort'
6+
import tseslint from 'typescript-eslint'
7+
8+
export default tseslint.config(
9+
{ ignores: ['dist', 'node_modules', 'coverage'] },
10+
{
11+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
12+
files: ['**/*.ts'],
13+
languageOptions: {
14+
ecmaVersion: 2020,
15+
},
16+
plugins: {
17+
import: importPlugin,
18+
configPrettier: configPrettier,
19+
prettier: prettierPlugin,
20+
'simple-import-sort': simpleImportSortPlugin,
21+
},
22+
rules: {
23+
'no-console': 1,
24+
'prettier/prettier': [
25+
'error',
26+
{
27+
printWidth: 120,
28+
semi: false,
29+
singleQuote: true,
30+
tabWidth: 2,
31+
trailingComma: 'all',
32+
},
33+
],
34+
'@typescript-eslint/no-explicit-any': 'off',
35+
'@typescript-eslint/no-inferrable-types': 'off',
36+
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
37+
'@typescript-eslint/typedef': [
38+
1,
39+
{
40+
arrayDestructuring: true,
41+
arrowParameter: true,
42+
memberVariableDeclaration: true,
43+
objectDestructuring: true,
44+
parameter: true,
45+
propertyDeclaration: true,
46+
variableDeclaration: true,
47+
variableDeclarationIgnoreFunction: true,
48+
},
49+
],
50+
'simple-import-sort/imports': 'error',
51+
'simple-import-sort/exports': 'error',
52+
'import/first': 'error',
53+
'import/newline-after-import': 'error',
54+
'import/no-duplicates': 'error',
55+
},
56+
},
57+
)

plugins/deskPlugin/jest.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
setupFiles: ['<rootDir>/jest.setup.js'],
5+
}

plugins/deskPlugin/jest.setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('dotenv').config({ path: '.env' });

plugins/deskPlugin/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@game-node/desk-exchange-plugin",
3+
"version": "1.0.0",
4+
"description": "DESK Exchange Plugin for ...",
5+
"main": "dist/index.js",
6+
"dependencies": {
7+
"@virtuals-protocol/game": "^0.1.7"
8+
},
9+
"devDependencies": {
10+
"@desk-exchange/typescript-sdk": "1.0.3",
11+
"@eslint/js": "9.15.0",
12+
"@jest/globals": "29.7.0",
13+
"@types/jest": "29.5.14",
14+
"@types/node": "20.0.0",
15+
"eslint": "9.15.0",
16+
"eslint-config-prettier": "9.1.0",
17+
"eslint-plugin-import": "2.31.0",
18+
"eslint-plugin-prettier": "5.2.1",
19+
"eslint-plugin-simple-import-sort": "12.1.1",
20+
"jest": "29.7.0",
21+
"ts-jest": "29.2.5",
22+
"tsup": "8.3.5",
23+
"typescript-eslint": "8.16.0"
24+
},
25+
"scripts": {
26+
"build": "tsup --format esm --dts",
27+
"dev": "tsup --watch --format esm --dts",
28+
"lint": "eslint . --config eslint.config.mjs",
29+
"test": "jest --coverage"
30+
}
31+
}

0 commit comments

Comments
 (0)