├── docs ├── _config.yml └── .gitignore ├── packages ├── swap │ ├── .eslintignore │ ├── jest.config.js │ ├── tsconfig.json │ ├── .eslintrc │ ├── README.md │ ├── src │ │ ├── utils.ts │ │ ├── swap-markets.ts │ │ └── idl.ts │ ├── package.json │ └── examples │ │ └── swap.js ├── openbook │ ├── .eslintignore │ ├── jest.config.js │ ├── tsconfig.json │ ├── src │ │ ├── tokens_and_markets.ts │ │ ├── instructions.test.js │ │ ├── index.ts │ │ ├── fees.ts │ │ ├── market.test.js │ │ ├── markets.json │ │ ├── error.ts │ │ ├── slab.test.js │ │ ├── slab.ts │ │ ├── layout.js │ │ ├── queue.ts │ │ ├── token-instructions.js │ │ ├── market-proxy │ │ │ ├── middleware.ts │ │ │ └── index.ts │ │ └── instructions.js │ ├── .eslintrc │ ├── package.json │ └── README.md ├── token │ ├── src │ │ ├── index.ts │ │ ├── state.ts │ │ ├── metadata.ts │ │ └── instructions.ts │ ├── README.md │ ├── tsconfig.json │ └── package.json ├── common │ ├── test │ │ └── example.spec.ts │ ├── tsconfig.json │ ├── package.json │ └── src │ │ ├── simulate-transaction.ts │ │ ├── token.ts │ │ ├── connection.ts │ │ ├── provider.ts │ │ └── index.ts ├── associated-token │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts ├── tokens │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── testnet.json │ │ ├── devnet.json │ │ └── mainnet-beta.json │ ├── rollup │ │ ├── rollup.config.ts │ │ └── rollup.config.browser.ts │ ├── package.json │ └── package-lock.json └── borsh │ ├── tsconfig.experimental.json │ ├── tsconfig.json │ ├── package.json │ └── src │ └── index.ts ├── .travis.yml ├── .gitignore ├── .editorconfig ├── lerna.json ├── tsconfig.json ├── .eslintrc ├── package.json ├── README.md ├── types └── buffer-layout │ └── index.d.ts └── LICENSE /docs/_config.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - "_*_.html" 3 | - "_*_.*.html" 4 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /associated-token 2 | /borsh 3 | /pool 4 | /token 5 | /stake-ui 6 | -------------------------------------------------------------------------------- /packages/swap/.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /packages/openbook/.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 12 3 | dist: bionic 4 | cache: yarn 5 | before_script: 6 | - yarn build 7 | -------------------------------------------------------------------------------- /packages/swap/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-ts', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/openbook/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest/presets/js-with-ts', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /packages/token/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './instructions'; 2 | export * from './state'; 3 | export * as metadata from './metadata'; 4 | -------------------------------------------------------------------------------- /packages/common/test/example.spec.ts: -------------------------------------------------------------------------------- 1 | describe('Example test suite', () => { 2 | it('Works', async () => { 3 | // no-op 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /packages/token/README.md: -------------------------------------------------------------------------------- 1 | Utilities for interacting with SPL Tokens. 2 | 3 | [API Reference](https://project-serum.github.io/serum-ts/token/modules/_index_.html) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | dist/ 4 | lib/ 5 | deploy/ 6 | docs/lockup-ui/ 7 | .DS_Store 8 | *~ 9 | .idea 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | private/ -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /packages/associated-token/README.md: -------------------------------------------------------------------------------- 1 | JavaScript library to interact with SPL Associated Token Accounts. 2 | 3 | [API Reference](https://project-serum.github.io/serum-ts/associated-token/modules/_index_.html) 4 | -------------------------------------------------------------------------------- /packages/tokens/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "./src/*.json"], 4 | "compilerOptions": { 5 | "outDir": "dist/lib", 6 | "resolveJsonModule": true, 7 | "composite": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "3.4.3", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "packages": ["packages/*"], 6 | "version": "independent", 7 | "publishConfig": { 8 | "access": "public", 9 | "registry": "https://registry.npmjs.org" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/tokens/src/index.ts: -------------------------------------------------------------------------------- 1 | import MAINNET_TOKENS from './mainnet-beta.json'; 2 | import DEVNET_TOKENS from './devnet.json'; 3 | import TESTNET_TOKENS from './testnet.json'; 4 | 5 | export const TOKENS = { 6 | mainnet: MAINNET_TOKENS, 7 | devnet: DEVNET_TOKENS, 8 | testnet: TESTNET_TOKENS, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/openbook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node12/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noImplicitAny": false, 10 | "resolveJsonModule": true 11 | }, 12 | "include": ["./src/**/*"], 13 | "exclude": ["./src/**/*.test.js", "node_modules", "**/node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/borsh/tsconfig.experimental.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node12/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noImplicitAny": false, 10 | "resolveJsonModule": true 11 | }, 12 | "include": ["./src/**/*"], 13 | "exclude": ["./src/**/*.test.js", "node_modules", "**/node_modules"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/swap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node12/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./lib", 5 | "allowJs": true, 6 | "checkJs": true, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "noImplicitAny": false, 10 | "resolveJsonModule": true, 11 | "sourceMap": true 12 | }, 13 | "include": ["./src/**/*"], 14 | "exclude": ["./src/**/*.test.js", "node_modules", "**/node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/openbook/src/tokens_and_markets.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | 3 | export const PROGRAM_LAYOUT_VERSIONS = { 4 | '4ckmDgGdxQoPDLUkDT3vHgSAkzA3QRdNq5ywwY4sUSJn': 1, 5 | BJ3jrUzddfuSrZHXSCxMUUQsjKEyLmuuyZebkcaFp2fg: 1, 6 | EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o: 2, 7 | srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX: 3, 8 | }; 9 | 10 | export function getLayoutVersion(programId: PublicKey) { 11 | return PROGRAM_LAYOUT_VERSIONS[programId.toString()] || 3; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es6", 5 | "module": "es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": true, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "noImplicitAny": true, 14 | "typeRoots": ["types/", "node_modules/@types"] 15 | }, 16 | "include": ["src/**/*"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/tokens/src/testnet.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "tokenSymbol": "SOL", 4 | "mintAddress": "So11111111111111111111111111111111111111112", 5 | "tokenName": "Solana", 6 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png" 7 | }, 8 | { 9 | "tokenSymbol": "ABC", 10 | "mintAddress": "D4fdoY5d2Bn1Cmjqy6J6shRHjcs7QNuBPzwEzTLrf7jm", 11 | "tokenName": "ABC Test", 12 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/bitcoin/info/logo.png" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /packages/borsh/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | 6 | "outDir": "./dist/lib", 7 | "rootDir": "./src", 8 | 9 | "composite": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | 14 | "strict": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "typeRoots": ["../../types/", "../../node_modules/@types"] 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["src/**/*.test.ts", "**/node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/token/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | 6 | "outDir": "./dist/lib", 7 | "rootDir": "./src", 8 | 9 | "composite": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | 14 | "strict": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "typeRoots": ["../../types/", "../../node_modules/@types"] 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["src/**/*.test.ts", "**/node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/associated-token/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | 6 | "outDir": "./dist/lib", 7 | "rootDir": "./src", 8 | 9 | "composite": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | 14 | "strict": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "typeRoots": ["../../types/", "../../node_modules/@types"] 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["src/**/*.test.ts", "**/node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2019", 5 | 6 | "outDir": "./dist/lib", 7 | "rootDir": "./src", 8 | 9 | "composite": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "sourceMap": true, 13 | 14 | "strict": true, 15 | "esModuleInterop": true, 16 | "skipLibCheck": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "typeRoots": ["../../types/", "../../node_modules/@types", "../../types"] 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["src/**/*.test.ts", "**/node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "env": { 11 | "node": true, 12 | "jest": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "no-constant-condition": ["error", { "checkLoops": false }], 20 | "no-empty-function": "off", 21 | "@typescript-eslint/ban-ts-comment": "off", 22 | "@typescript-eslint/no-explicit-any": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/swap/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "env": { 11 | "node": true, 12 | "jest": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "no-constant-condition": ["error", { "checkLoops": false }], 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "@typescript-eslint/ban-ts-comment": "off", 22 | "@typescript-eslint/no-explicit-any": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/openbook/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "env": { 11 | "node": true, 12 | "jest": true 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2020, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "no-constant-condition": ["error", { "checkLoops": false }], 20 | "@typescript-eslint/explicit-module-boundary-types": "off", 21 | "@typescript-eslint/ban-ts-comment": "off", 22 | "@typescript-eslint/no-explicit-any": "off" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/tokens/rollup/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import sourceMaps from 'rollup-plugin-sourcemaps'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import json from 'rollup-plugin-json'; 6 | 7 | const pkg = require('../package.json'); 8 | 9 | export default { 10 | input: `src/index.ts`, 11 | output: [ 12 | { 13 | file: pkg.main, 14 | name: 'index', 15 | format: 'umd', 16 | sourcemap: true, 17 | }, 18 | { 19 | file: pkg.module, 20 | format: 'es', 21 | sourcemap: true, 22 | }, 23 | ], 24 | external: [], 25 | watch: { 26 | include: 'src/**', 27 | }, 28 | plugins: [ 29 | resolve(), 30 | commonjs(), 31 | json(), 32 | typescript({ useTsconfigDeclarationDir: true }), 33 | sourceMaps(), 34 | ], 35 | }; 36 | -------------------------------------------------------------------------------- /packages/tokens/rollup/rollup.config.browser.ts: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import sourceMaps from 'rollup-plugin-sourcemaps'; 4 | import typescript from 'rollup-plugin-typescript2'; 5 | import json from 'rollup-plugin-json'; 6 | 7 | export default { 8 | input: `src/index.ts`, 9 | output: [ 10 | { 11 | file: './dist/index.browser.umd.js', 12 | name: 'index', 13 | format: 'umd', 14 | sourcemap: true, 15 | }, 16 | { 17 | file: './dist/index.browser.es5.js', 18 | format: 'es', 19 | sourcemap: true, 20 | }, 21 | ], 22 | external: [], 23 | watch: { 24 | include: 'src/**', 25 | }, 26 | plugins: [ 27 | resolve({ 28 | browser: true, 29 | }), 30 | commonjs(), 31 | json(), 32 | typescript({ useTsconfigDeclarationDir: true }), 33 | sourceMaps(), 34 | ], 35 | }; 36 | -------------------------------------------------------------------------------- /packages/borsh/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/borsh", 3 | "version": "0.0.7", 4 | "description": "Openbook Borsh", 5 | "main": "dist/lib/index.js", 6 | "types": "dist/lib/index.d.ts", 7 | "exports": { 8 | ".": "./dist/lib/index.js" 9 | }, 10 | "license": "Apache-2.0", 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "engines": { 15 | "node": ">=10" 16 | }, 17 | "scripts": { 18 | "build": "tsc", 19 | "docs": "typedoc --out ../../docs/borsh --mode library --composite false --rootDir src src/index.ts src/*.d.ts", 20 | "test": "", 21 | "clean": "rm -rf dist", 22 | "prepack": "yarn clean && yarn build" 23 | }, 24 | "dependencies": { 25 | "bn.js": "^5.1.2", 26 | "buffer-layout": "^1.2.0" 27 | }, 28 | "peerDependencies": { 29 | "@solana/web3.js": "^1.70.0" 30 | }, 31 | "files": [ 32 | "dist" 33 | ], 34 | "devDependencies": { 35 | "@types/bn.js": "^5.1.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/common", 3 | "version": "0.0.7", 4 | "description": "Openbook common utilities", 5 | "main": "dist/lib/index.js", 6 | "types": "dist/lib/index.d.ts", 7 | "exports": { 8 | ".": "./dist/lib/index.js" 9 | }, 10 | "license": "Apache-2.0", 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "engines": { 15 | "node": ">=10" 16 | }, 17 | "scripts": { 18 | "build": "tsc", 19 | "watch": "tsc --watch", 20 | "test": "jest test", 21 | "clean": "rm -rf dist", 22 | "prepack": "yarn clean && yarn build" 23 | }, 24 | "dependencies": { 25 | "@openbook-dex/openbook": "0.0.7", 26 | "bn.js": "^5.1.2", 27 | "superstruct": "0.8.3" 28 | }, 29 | "peerDependencies": { 30 | "@solana/web3.js": "^1.70.0" 31 | }, 32 | "files": [ 33 | "dist" 34 | ], 35 | "devDependencies": { 36 | "@types/bn.js": "^5.1.1", 37 | "@types/bs58": "^4.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/associated-token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/associated-token", 3 | "version": "0.0.7", 4 | "description": "Library for interacting with SPL Associated Token Accounts", 5 | "repository": "project-serum/serum-ts", 6 | "main": "dist/lib/index.js", 7 | "types": "dist/lib/index.d.ts", 8 | "exports": { 9 | ".": "./dist/lib/index.js" 10 | }, 11 | "license": "Apache-2.0", 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "engines": { 16 | "node": ">=10" 17 | }, 18 | "scripts": { 19 | "build": "tsc", 20 | "docs": "typedoc --out ../../docs/associated-token --mode library --composite false --rootDir src src/index.ts src/*.d.ts", 21 | "test": "", 22 | "clean": "rm -rf dist", 23 | "prepack": "yarn clean && yarn build" 24 | }, 25 | "peerDependencies": { 26 | "@solana/web3.js": "^1.70.0" 27 | }, 28 | "devDependencies": { 29 | "@solana/web3.js": "^1.70.0" 30 | }, 31 | "files": [ 32 | "dist" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /packages/openbook/src/instructions.test.js: -------------------------------------------------------------------------------- 1 | import { encodeInstruction } from './instructions'; 2 | import BN from 'bn.js'; 3 | 4 | describe('instruction', () => { 5 | it('encodes initialize market', () => { 6 | const b = encodeInstruction({ 7 | initializeMarket: { 8 | baseLotSize: new BN(10), 9 | quoteLotSize: new BN(100000), 10 | feeRateBps: 5, 11 | vaultSignerNonce: new BN(1), 12 | quoteDustThreshold: new BN(10), 13 | }, 14 | }); 15 | expect(b.toString('hex')).toEqual( 16 | '00000000000a00000000000000a086010000000000050001000000000000000a00000000000000', 17 | ); 18 | }); 19 | 20 | it('encodes new order', () => { 21 | const b = encodeInstruction({ 22 | newOrder: { 23 | side: 'sell', 24 | limitPrice: new BN(10), 25 | maxQuantity: new BN(5), 26 | orderType: 'postOnly', 27 | }, 28 | }); 29 | expect(b.toString('hex')).toEqual( 30 | '0001000000010000000a000000000000000500000000000000020000000000000000000000', 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/token", 3 | "version": "0.0.7", 4 | "description": "Utilities for interacting with SPL Tokens", 5 | "repository": "openbook-dex/openbook-ts", 6 | "main": "dist/lib/index.js", 7 | "types": "dist/lib/index.d.ts", 8 | "exports": { 9 | ".": "./dist/lib/index.js" 10 | }, 11 | "license": "Apache-2.0", 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "engines": { 16 | "node": ">=10" 17 | }, 18 | "scripts": { 19 | "build": "tsc", 20 | "docs": "typedoc --out ../../docs/token --mode library --composite false --rootDir src src/index.ts src/*.d.ts", 21 | "start": "tsc --watch", 22 | "test": "", 23 | "clean": "rm -rf dist", 24 | "prepack": "yarn clean && yarn build" 25 | }, 26 | "dependencies": { 27 | "@openbook-dex/borsh": "0.0.7", 28 | "bn.js": "^5.1.3" 29 | }, 30 | "devDependencies": { 31 | "@types/bn.js": "^5.1.1" 32 | }, 33 | "peerDependencies": { 34 | "@solana/web3.js": "^1.70.0" 35 | }, 36 | "files": [ 37 | "dist" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/openbook/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Market, 3 | Orderbook, 4 | OpenOrders, 5 | MARKET_STATE_LAYOUT_V3, 6 | MARKET_STATE_LAYOUT_V2, 7 | } from './market'; 8 | export { 9 | DexInstructions, 10 | decodeInstruction, 11 | decodeInstructionV2, 12 | SETTLE_FUNDS_BASE_WALLET_INDEX, 13 | SETTLE_FUNDS_QUOTE_WALLET_INDEX, 14 | NEW_ORDER_OPEN_ORDERS_INDEX, 15 | NEW_ORDER_OWNER_INDEX, 16 | NEW_ORDER_V3_OPEN_ORDERS_INDEX, 17 | NEW_ORDER_V3_OWNER_INDEX, 18 | } from './instructions'; 19 | export { getFeeTier, getFeeRates, supportsSrmFeeDiscounts } from './fees'; 20 | export { getLayoutVersion } from './tokens_and_markets'; 21 | export { 22 | decodeEventQueue, 23 | decodeRequestQueue, 24 | REQUEST_QUEUE_LAYOUT, 25 | EVENT_QUEUE_LAYOUT, 26 | } from './queue'; 27 | export * as TokenInstructions from './token-instructions'; 28 | export * from './error'; 29 | export { MarketProxy, MarketProxyBuilder } from './market-proxy'; 30 | export { 31 | OpenOrdersPda, 32 | ReferralFees, 33 | PermissionedCrank, 34 | Logger, 35 | Middleware, 36 | } from './market-proxy/middleware'; 37 | -------------------------------------------------------------------------------- /packages/tokens/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/tokens", 3 | "version": "0.0.7", 4 | "description": "Openbook tokens", 5 | "main": "dist/index.umd.js", 6 | "module": "dist/index.es5.js", 7 | "types": "dist/lib/src/index.d.ts", 8 | "license": "Apache-2.0", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "scripts": { 13 | "build": "yarn build:node && yarn build:browser", 14 | "build:node": "tsc -b && rollup -c rollup/rollup.config.ts", 15 | "build:browser": "tsc -b && rollup -c rollup/rollup.config.browser.ts", 16 | "coverage": "jest --coverage test", 17 | "prepack": "yarn build" 18 | }, 19 | "jest": { 20 | "transform": { 21 | ".(ts)": "ts-jest" 22 | }, 23 | "testEnvironment": "node", 24 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts)$", 25 | "moduleFileExtensions": [ 26 | "ts", 27 | "tsx", 28 | "js" 29 | ] 30 | }, 31 | "browser": { 32 | "./dist/index.umd.js": "./dist/index.browser.umd.js", 33 | "./dist/index.es5.js": "./dist/index.browser.es5.js" 34 | }, 35 | "devDependencies": { 36 | "typescript": "^4.9.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/swap/README.md: -------------------------------------------------------------------------------- 1 | # Serum Swap 2 | 3 | [![Build Status](https://travis-ci.com/project-serum/serum-ts.svg?branch=master)](https://travis-ci.com/project-serum/serum-ts) 4 | [![npm (scoped)](https://img.shields.io/npm/v/@project-serum/swap)](https://www.npmjs.com/package/@project-serum/swap) 5 | [![Discord Chat](https://img.shields.io/discord/739225212658122886?color=blueviolet)](https://discord.com/channels/739225212658122886) 6 | [![Documentation](https://img.shields.io/badge/typedoc-documentation-blue)](https://project-serum.github.io/serum-ts/swap/classes/swap.html) 7 | [![License](https://img.shields.io/github/license/project-serum/serum-dex?color=blue)](https://opensource.org/licenses/Apache-2.0) 8 | 9 | Client library for swapping directly on the serum orderbook. 10 | The Solana program can be found [here](https://github.com/project-serum/swap). 11 | 12 | ## Installation 13 | 14 | Using npm: 15 | 16 | ``` 17 | npm install @solana/web3.js @project-serum/swap 18 | ``` 19 | 20 | Using yarn: 21 | 22 | ``` 23 | yarn add @solana/web3.js @project-serum/swap 24 | ``` 25 | 26 | ## API Reference 27 | 28 | [API Reference](https://project-serum.github.io/serum-ts/swap/classes/swap.html). 29 | -------------------------------------------------------------------------------- /packages/tokens/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/tokens", 3 | "version": "0.0.4", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@openbook-dex/tokens", 9 | "version": "0.0.4", 10 | "license": "Apache-2.0", 11 | "devDependencies": { 12 | "typescript": "^4.9.4" 13 | } 14 | }, 15 | "node_modules/typescript": { 16 | "version": "4.9.4", 17 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", 18 | "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", 19 | "dev": true, 20 | "bin": { 21 | "tsc": "bin/tsc", 22 | "tsserver": "bin/tsserver" 23 | }, 24 | "engines": { 25 | "node": ">=4.2.0" 26 | } 27 | } 28 | }, 29 | "dependencies": { 30 | "typescript": { 31 | "version": "4.9.4", 32 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", 33 | "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", 34 | "dev": true 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/common/src/simulate-transaction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Commitment, 3 | Connection, 4 | RpcResponseAndContext, 5 | SimulatedTransactionResponse, 6 | Transaction, 7 | } from '@solana/web3.js'; 8 | 9 | /** Copy of Connection.simulateTransaction that takes a commitment parameter. */ 10 | export async function simulateTransaction( 11 | connection: Connection, 12 | transaction: Transaction, 13 | commitment: Commitment, 14 | ): Promise> { 15 | // @ts-ignore 16 | transaction.recentBlockhash = await connection._recentBlockhash( 17 | // @ts-ignore 18 | connection._disableBlockhashCaching, 19 | ); 20 | 21 | const signData = transaction.serializeMessage(); 22 | // @ts-ignore 23 | const wireTransaction = transaction._serialize(signData); 24 | const encodedTransaction = wireTransaction.toString('base64'); 25 | const config: any = { encoding: 'base64', commitment }; 26 | const args = [encodedTransaction, config]; 27 | 28 | // @ts-ignore 29 | const res = await connection._rpcRequest('simulateTransaction', args); 30 | if (res.error) { 31 | throw new Error('failed to simulate transaction: ' + res.error.message); 32 | } 33 | return res.result; 34 | } 35 | -------------------------------------------------------------------------------- /packages/tokens/src/devnet.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "tokenSymbol": "SOL", 4 | "mintAddress": "So11111111111111111111111111111111111111112", 5 | "tokenName": "Solana", 6 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png" 7 | }, 8 | { 9 | "tokenSymbol": "XYZ", 10 | "mintAddress": "DEhAasscXF4kEGxFgJ3bq4PpVGp5wyUxMRvn6TzGVHaw", 11 | "tokenName": "XYZ Test", 12 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png" 13 | }, 14 | { 15 | "tokenSymbol": "ABC", 16 | "mintAddress": "6z83b76xbSm5UhdG33ePh7QCbLS8YaXCQ9up86tDTCUH", 17 | "tokenName": "ABC Test", 18 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png" 19 | }, 20 | { 21 | "tokenSymbol": "DEF", 22 | "mintAddress": "3pyeDv6AV1RQuA6KzsqkZrpsNn4b3hooHrQhGs7K2TYa", 23 | "tokenName": "DEF Test", 24 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /packages/token/src/state.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | import { bool, Layout, option, publicKey, u64 } from '@openbook-dex/borsh'; 4 | import { struct, u8 } from 'buffer-layout'; 5 | 6 | export interface Mint { 7 | mintAuthority: PublicKey | null; 8 | supply: BN; 9 | decimals: number; 10 | initialized: boolean; 11 | freezeAuthority: PublicKey | null; 12 | } 13 | 14 | export interface TokenAccount { 15 | mint: PublicKey; 16 | owner: PublicKey; 17 | amount: BN; 18 | delegate: PublicKey | null; 19 | state: number; 20 | native: BN | null; 21 | delegatedAmount: BN; 22 | closeAuthority: PublicKey | null; 23 | } 24 | 25 | export const Mint: Layout = struct([ 26 | option(publicKey(), 'mintAuthority'), 27 | u64('supply'), 28 | u8('decimals'), 29 | bool('initialized'), 30 | option(publicKey(), 'freezeAuthority'), 31 | ]); 32 | 33 | export const TokenAccount: Layout = struct([ 34 | publicKey('mint'), 35 | publicKey('owner'), 36 | u64('amount'), 37 | option(publicKey(), 'delegate'), 38 | u8('state'), 39 | option(u64(), 'delegatedAmount'), 40 | option(publicKey(), 'closeAuthority'), 41 | ]); 42 | 43 | export function decodeMintAccountData(data: Buffer): Mint { 44 | return Mint.decode(data); 45 | } 46 | 47 | export function decodeTokenAccountData(data: Buffer): TokenAccount { 48 | return TokenAccount.decode(data); 49 | } 50 | -------------------------------------------------------------------------------- /packages/openbook/src/fees.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { getLayoutVersion } from './tokens_and_markets'; 3 | 4 | export function supportsSrmFeeDiscounts(programId: PublicKey) { 5 | return getLayoutVersion(programId) > 1; 6 | } 7 | 8 | export function getFeeRates(feeTier: number): { taker: number; maker: number } { 9 | if (feeTier === 1) { 10 | // SRM2 11 | return { taker: 0.002, maker: -0.0003 }; 12 | } else if (feeTier === 2) { 13 | // SRM3 14 | return { taker: 0.0018, maker: -0.0003 }; 15 | } else if (feeTier === 3) { 16 | // SRM4 17 | return { taker: 0.0016, maker: -0.0003 }; 18 | } else if (feeTier === 4) { 19 | // SRM5 20 | return { taker: 0.0014, maker: -0.0003 }; 21 | } else if (feeTier === 5) { 22 | // SRM6 23 | return { taker: 0.0012, maker: -0.0003 }; 24 | } else if (feeTier === 6) { 25 | // MSRM 26 | return { taker: 0.001, maker: -0.0005 }; 27 | } 28 | // Base 29 | return { taker: 0.0022, maker: -0.0003 }; 30 | } 31 | 32 | export function getFeeTier(msrmBalance: number, srmBalance: number): number { 33 | if (msrmBalance >= 1) { 34 | return 6; 35 | } else if (srmBalance >= 1_000_000) { 36 | return 5; 37 | } else if (srmBalance >= 100_000) { 38 | return 4; 39 | } else if (srmBalance >= 10_000) { 40 | return 3; 41 | } else if (srmBalance >= 1_000) { 42 | return 2; 43 | } else if (srmBalance >= 100) { 44 | return 1; 45 | } else { 46 | return 0; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/swap/src/utils.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { PublicKey } from '@solana/web3.js'; 3 | 4 | // Serum DEX program id on mainnet-beta. 5 | export const DEX_PID = new PublicKey( 6 | 'srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX', 7 | ); 8 | 9 | // Swap program id on mainnet-beta. 10 | export const SWAP_PID = new PublicKey( 11 | '22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD', 12 | ); 13 | 14 | // USDC mint on mainnet-beta. 15 | export const USDC_PUBKEY = new PublicKey( 16 | 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 17 | ); 18 | 19 | // USDT mint on mainnet-beta. 20 | export const USDT_PUBKEY = new PublicKey( 21 | 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', 22 | ); 23 | 24 | // Return the program derived address used by the serum DEX to control token 25 | // vaults. 26 | export async function getVaultOwnerAndNonce( 27 | marketPublicKey: PublicKey, 28 | dexProgramId: PublicKey = DEX_PID, 29 | ) { 30 | const nonce = new BN(0); 31 | while (nonce.toNumber() < 255) { 32 | try { 33 | const vaultOwner = await PublicKey.createProgramAddress( 34 | [marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, 'le', 8)], 35 | dexProgramId, 36 | ); 37 | return [vaultOwner, nonce]; 38 | } catch (e: any) { 39 | nonce.iaddn(1); 40 | } 41 | } 42 | throw new Error('Unable to find nonce'); 43 | } 44 | 45 | // Returns an associated token address for spl tokens. 46 | export async function getAssociatedTokenAddress( 47 | associatedProgramId: PublicKey, 48 | programId: PublicKey, 49 | mint: PublicKey, 50 | owner: PublicKey, 51 | ): Promise { 52 | return ( 53 | await PublicKey.findProgramAddress( 54 | [owner.toBuffer(), programId.toBuffer(), mint.toBuffer()], 55 | associatedProgramId, 56 | ) 57 | )[0]; 58 | } 59 | -------------------------------------------------------------------------------- /packages/openbook/src/market.test.js: -------------------------------------------------------------------------------- 1 | import { accountFlagsLayout } from './layout'; 2 | 3 | describe('accountFlags', () => { 4 | const layout = accountFlagsLayout(); 5 | it('parses', () => { 6 | const b = Buffer.from('0000000000000000', 'hex'); 7 | expect(layout.getSpan(b)).toBe(8); 8 | expect(layout.decode(b).initialized).toBe(false); 9 | expect(layout.decode(Buffer.from('0000000000000000', 'hex'))).toMatchObject( 10 | { 11 | initialized: false, 12 | market: false, 13 | openOrders: false, 14 | requestQueue: false, 15 | eventQueue: false, 16 | bids: false, 17 | asks: false, 18 | }, 19 | ); 20 | expect(layout.decode(Buffer.from('0300000000000000', 'hex'))).toMatchObject( 21 | { 22 | initialized: true, 23 | market: true, 24 | openOrders: false, 25 | requestQueue: false, 26 | eventQueue: false, 27 | bids: false, 28 | asks: false, 29 | }, 30 | ); 31 | expect(layout.decode(Buffer.from('0500000000000000', 'hex'))).toMatchObject( 32 | { 33 | initialized: true, 34 | market: false, 35 | openOrders: true, 36 | requestQueue: false, 37 | eventQueue: false, 38 | bids: false, 39 | asks: false, 40 | }, 41 | ); 42 | }); 43 | 44 | it('serializes', () => { 45 | const b = Buffer.alloc(8); 46 | expect( 47 | layout.encode( 48 | { 49 | initialized: true, 50 | market: false, 51 | openOrders: false, 52 | requestQueue: false, 53 | eventQueue: false, 54 | bids: false, 55 | asks: true, 56 | }, 57 | b, 58 | ), 59 | ).toBe(8); 60 | expect(b.toString('hex')).toEqual('4100000000000000'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /packages/openbook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/openbook", 3 | "version": "0.0.7", 4 | "description": "Library for interacting with the openbook dex", 5 | "license": "MIT", 6 | "repository": "openbook-dex/openbook-ts", 7 | "main": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "scripts": { 16 | "build": "tsc", 17 | "start": "tsc --watch", 18 | "clean": "rm -rf lib", 19 | "prepack": "yarn clean && yarn build", 20 | "shell": "node -e \"$(< shell)\" -i --experimental-repl-await", 21 | "test": "run-s test:unit test:lint test:build", 22 | "test:build": "run-s build", 23 | "test:lint": "eslint src", 24 | "test:unit": "jest", 25 | "test:watch": "jest --watch" 26 | }, 27 | "devDependencies": { 28 | "@tsconfig/node12": "^1.0.7", 29 | "@types/bn.js": "^5.1.1", 30 | "@types/jest": "^26.0.9", 31 | "@typescript-eslint/eslint-plugin": "^4.6.0", 32 | "@typescript-eslint/parser": "^4.6.0", 33 | "babel-eslint": "^10.0.3", 34 | "cross-env": "^7.0.2", 35 | "eslint": "^7.6.0", 36 | "eslint-config-prettier": "^6.11.0", 37 | "jest": "^26.4.0", 38 | "npm-run-all": "^4.1.5", 39 | "prettier": "^2.0.5", 40 | "ts-jest": "^26.2.0", 41 | "typescript": "^4.0.5" 42 | }, 43 | "files": [ 44 | "lib" 45 | ], 46 | "prettier": { 47 | "singleQuote": true, 48 | "trailingComma": "all" 49 | }, 50 | "dependencies": { 51 | "@project-serum/anchor": "^0.11.1", 52 | "@solana/spl-token": "^0.1.6", 53 | "@solana/web3.js": "^1.70.0", 54 | "bn.js": "^5.2.1", 55 | "buffer-layout": "^1.2.0" 56 | }, 57 | "browserslist": [ 58 | ">0.2%", 59 | "not dead", 60 | "not op_mini all", 61 | "maintained node versions" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /packages/swap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openbook-dex/swap", 3 | "version": "0.0.7", 4 | "description": "Client for swapping on the Openbook DEX", 5 | "license": "MIT", 6 | "repository": "openbook-dex/openbook-ts", 7 | "main": "lib/index.js", 8 | "source": "src/index.js", 9 | "types": "lib/index.d.ts", 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "scripts": { 17 | "build": "tsc", 18 | "start": "tsc --watch", 19 | "clean": "rm -rf lib", 20 | "docs": "typedoc --excludePrivate --out ../../docs/swap src/index.ts --includeVersion --readme none", 21 | "prepack": "yarn clean && yarn build", 22 | "shell": "node -e \"$(< shell)\" -i --experimental-repl-await" 23 | }, 24 | "devDependencies": { 25 | "@tsconfig/node12": "^1.0.7", 26 | "@types/bn.js": "^4.11.6", 27 | "@types/jest": "^26.0.9", 28 | "@typescript-eslint/eslint-plugin": "^4.6.0", 29 | "@typescript-eslint/parser": "^4.6.0", 30 | "babel-eslint": "^10.0.3", 31 | "cross-env": "^7.0.2", 32 | "eslint": "^7.6.0", 33 | "eslint-config-prettier": "^6.11.0", 34 | "jest": "^26.4.0", 35 | "npm-run-all": "^4.1.5", 36 | "prettier": "^2.0.5", 37 | "ts-jest": "^26.2.0", 38 | "typedoc": "^0.20.36", 39 | "typescript": "^4.0.5" 40 | }, 41 | "files": [ 42 | "lib" 43 | ], 44 | "prettier": { 45 | "singleQuote": true, 46 | "trailingComma": "all" 47 | }, 48 | "dependencies": { 49 | "@openbook-dex/openbook": "0.0.7", 50 | "@project-serum/anchor": "^0.5.1-beta.2", 51 | "@solana/spl-token": "^0.1.3", 52 | "@solana/spl-token-registry": "^0.2.68", 53 | "@solana/web3.js": "^1.70.0", 54 | "base64-js": "^1.5.1", 55 | "bn.js": "^5.1.2" 56 | }, 57 | "browserslist": [ 58 | ">0.2%", 59 | "not dead", 60 | "not op_mini all", 61 | "maintained node versions" 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "keywords": [], 4 | "workspaces": { 5 | "packages": [ 6 | "packages/*" 7 | ] 8 | }, 9 | "license": "Apache-2.0", 10 | "engines": { 11 | "node": ">=6.0.0" 12 | }, 13 | "scripts": { 14 | "build": "lerna run build", 15 | "lint": "eslint 'packages/*/{src,test}/**/*.ts' && npx prettier -c 'packages/*/{src,test}/**/*.ts'", 16 | "lint:fix": "eslint --fix 'packages/*/{src,test}/**/*.ts' && npx prettier --write 'packages/*/{src,test}/**/*.ts'", 17 | "deploy:docs": "lerna run docs", 18 | "test": "lerna run test --concurrency 1 --stream" 19 | }, 20 | "lint-staged": { 21 | "packages/*/{src,test}/**/*.ts": [ 22 | "prettier --write" 23 | ] 24 | }, 25 | "husky": { 26 | "hooks": { 27 | "pre-commit": "yarn lint" 28 | } 29 | }, 30 | "prettier": { 31 | "arrowParens": "avoid", 32 | "semi": true, 33 | "singleQuote": true, 34 | "trailingComma": "all" 35 | }, 36 | "commitlint": { 37 | "extends": [ 38 | "@commitlint/config-conventional" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@commitlint/cli": "^8.2.0", 43 | "@commitlint/config-conventional": "^8.2.0", 44 | "@types/jest": "^26.0.15", 45 | "@typescript-eslint/eslint-plugin": "^4.6.0", 46 | "@typescript-eslint/parser": "^4.6.0", 47 | "eslint": "^7.12.1", 48 | "eslint-config-prettier": "^6.15.0", 49 | "gh-pages": "^3.1.0", 50 | "husky": "^4.3.0", 51 | "jest": "26.6.0", 52 | "jest-config": "26.6.0", 53 | "lerna": "3.22.1", 54 | "lint-staged": "^10.5.0", 55 | "prettier": "^2.1.2", 56 | "rollup": "^1.23.1", 57 | "rollup-plugin-commonjs": "^10.1.0", 58 | "rollup-plugin-json": "^4.0.0", 59 | "rollup-plugin-node-resolve": "^5.2.0", 60 | "rollup-plugin-sourcemaps": "^0.4.2", 61 | "rollup-plugin-terser": "^5.1.3", 62 | "rollup-plugin-typescript2": "^0.25.2", 63 | "ts-jest": "^26.4.3", 64 | "ts-node": "^9.0.0", 65 | "typescript": "^4.0.5" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/associated-token/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PublicKey, 3 | SystemProgram, 4 | SYSVAR_RENT_PUBKEY, 5 | TransactionInstruction, 6 | } from '@solana/web3.js'; 7 | 8 | const TOKEN_PROGRAM_ID = new PublicKey( 9 | 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 10 | ); 11 | 12 | /** Program ID for the associated token account program. */ 13 | export const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey( 14 | 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL', 15 | ); 16 | 17 | /** 18 | * Derives the associated token address for the given wallet address and token mint. 19 | * @param owner Wallet address 20 | * @param mint Mint address 21 | */ 22 | export async function getAssociatedTokenAddress( 23 | owner: PublicKey, 24 | mint: PublicKey, 25 | ): Promise { 26 | const [address] = await PublicKey.findProgramAddress( 27 | [owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()], 28 | ASSOCIATED_TOKEN_PROGRAM_ID, 29 | ); 30 | return address; 31 | } 32 | 33 | /** 34 | * Instruction to create the associated token address for the given wallet address and token mint. 35 | * 36 | * @param payer Account to use to pay for fees 37 | * @param owner Wallet address for the new associated token address 38 | * @param mint Mint address for the new associated token address 39 | */ 40 | export async function createAssociatedTokenAccount( 41 | payer: PublicKey, 42 | owner: PublicKey, 43 | mint: PublicKey, 44 | ): Promise { 45 | const associatedTokenAddress = await getAssociatedTokenAddress(owner, mint); 46 | return new TransactionInstruction({ 47 | keys: [ 48 | { pubkey: payer, isSigner: true, isWritable: true }, 49 | { pubkey: associatedTokenAddress, isSigner: false, isWritable: true }, 50 | { pubkey: owner, isSigner: false, isWritable: false }, 51 | { pubkey: mint, isSigner: false, isWritable: false }, 52 | { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, 53 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 54 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 55 | ], 56 | programId: ASSOCIATED_TOKEN_PROGRAM_ID, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /packages/common/src/token.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | import { ProgramAccount } from './'; 3 | import { AccountInfo as TokenAccount } from '@solana/spl-token'; 4 | import { TokenInstructions } from '@openbook-dex/openbook'; 5 | import * as bs58 from 'bs58'; 6 | import * as BufferLayout from 'buffer-layout'; 7 | 8 | export async function getOwnedTokenAccounts( 9 | connection: Connection, 10 | publicKey: PublicKey, 11 | ): Promise[]> { 12 | const filters = getOwnedAccountsFilters(publicKey); 13 | // @ts-ignore 14 | const resp = await connection._rpcRequest('getProgramAccounts', [ 15 | TokenInstructions.TOKEN_PROGRAM_ID.toBase58(), 16 | { 17 | commitment: connection.commitment, 18 | filters, 19 | }, 20 | ]); 21 | if (resp.error) { 22 | throw new Error( 23 | 'failed to get token accounts owned by ' + 24 | publicKey.toBase58() + 25 | ': ' + 26 | resp.error.message, 27 | ); 28 | } 29 | return ( 30 | resp.result 31 | // @ts-ignore 32 | .map(({ pubkey, account: { data } }) => { 33 | data = bs58.decode(data); 34 | return { 35 | publicKey: new PublicKey(pubkey), 36 | account: parseTokenAccountData(data), 37 | }; 38 | }) 39 | ); 40 | } 41 | 42 | // todo: remove 43 | export const ACCOUNT_LAYOUT = BufferLayout.struct([ 44 | BufferLayout.blob(32, 'mint'), 45 | BufferLayout.blob(32, 'owner'), 46 | BufferLayout.nu64('amount'), 47 | BufferLayout.blob(93), 48 | ]); 49 | export const MINT_LAYOUT = BufferLayout.struct([ 50 | BufferLayout.blob(44), 51 | BufferLayout.u8('decimals'), 52 | BufferLayout.blob(37), 53 | ]); 54 | 55 | export function parseTokenAccountData(data: any) { 56 | // @ts-ignore 57 | const { mint, owner, amount } = ACCOUNT_LAYOUT.decode(data); 58 | return { 59 | mint: new PublicKey(mint), 60 | owner: new PublicKey(owner), 61 | amount, 62 | }; 63 | } 64 | 65 | // @ts-ignore 66 | export function parseMintData(data) { 67 | // @ts-ignore 68 | const { decimals } = MINT_LAYOUT.decode(data); 69 | return { decimals }; 70 | } 71 | 72 | // @ts-ignore 73 | export function getOwnedAccountsFilters(publicKey: PublicKey) { 74 | return [ 75 | { 76 | memcmp: { 77 | // @ts-ignore 78 | offset: ACCOUNT_LAYOUT.offsetOf('owner'), 79 | bytes: publicKey.toBase58(), 80 | }, 81 | }, 82 | { 83 | dataSize: ACCOUNT_LAYOUT.span, 84 | }, 85 | ]; 86 | } 87 | -------------------------------------------------------------------------------- /packages/token/src/metadata.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { 3 | rustEnum, 4 | bool, 5 | u8, 6 | publicKey, 7 | str, 8 | vec, 9 | option, 10 | struct, 11 | } from '@openbook-dex/borsh'; 12 | import { u16 } from 'buffer-layout'; 13 | 14 | const KEY_LAYOUT = rustEnum([ 15 | struct([], 'uninitialized'), 16 | struct([], 'editionV1'), 17 | struct([], 'masterEditionV1'), 18 | struct([], 'reservationListV1'), 19 | struct([], 'metadataV1'), 20 | struct([], 'reservationListV2'), 21 | struct([], 'masterEditionV2'), 22 | struct([], 'editionMarker'), 23 | ]); 24 | 25 | const CREATOR_LAYOUT = struct([ 26 | publicKey('address'), 27 | bool('verified'), 28 | u8('share'), 29 | ]); 30 | 31 | const DATA_LAYOUT = struct([ 32 | str('name'), 33 | str('symbol'), 34 | str('uri'), 35 | u16('sellerFeeBasisPoints'), 36 | option(vec(CREATOR_LAYOUT.replicate('creators')), 'creators'), 37 | ]); 38 | 39 | const METADATA_LAYOUT = struct([ 40 | KEY_LAYOUT.replicate('key'), 41 | publicKey('updateAuthority'), 42 | publicKey('mint'), 43 | DATA_LAYOUT.replicate('data'), 44 | bool('primarySaleHappened'), 45 | bool('isMutable'), 46 | option(u8(), 'editionNonce'), 47 | ]); 48 | 49 | export interface Metadata { 50 | key: Key; 51 | updateAuthority: PublicKey; 52 | mint: PublicKey; 53 | data: Data; 54 | primarySaleHappened: boolean; 55 | isMutable: boolean; 56 | editionNonce: number; 57 | } 58 | 59 | export interface Data { 60 | name: string; 61 | symbol: string; 62 | uri: string; 63 | sellerFeeBasisPoints: number; 64 | creators: Array | null; 65 | } 66 | 67 | export interface Creator { 68 | address: PublicKey; 69 | verified: boolean; 70 | share: number; 71 | } 72 | 73 | export type Key = 74 | | { unitialized: unknown } 75 | | { editionV1: unknown } 76 | | { masterEditionV1: unknown } 77 | | { reserverationListV1: unknown } 78 | | { metadataV1: unknown } 79 | | { reservationListV2: unknown } 80 | | { masterEditionV2: unknown } 81 | | { editoinMarket: unknown }; 82 | 83 | // eslint-disable-next-line no-control-regex 84 | const METADATA_REPLACE = new RegExp('\u0000', 'g'); 85 | 86 | export function decodeMetadata(buffer: Buffer): Metadata { 87 | const metadata: any = METADATA_LAYOUT.decode(buffer); 88 | metadata.data.name = metadata.data.name.replace(METADATA_REPLACE, ''); 89 | metadata.data.uri = metadata.data.uri.replace(METADATA_REPLACE, ''); 90 | metadata.data.symbol = metadata.data.symbol.replace(METADATA_REPLACE, ''); 91 | return metadata; 92 | } 93 | -------------------------------------------------------------------------------- /packages/openbook/README.md: -------------------------------------------------------------------------------- 1 | [![npm (scoped)](https://img.shields.io/npm/v/@project-serum/serum)](https://www.npmjs.com/package/@openbook-dex/openbook) 2 | 3 | # Serum JS Client Library 4 | 5 | JavaScript client library for interacting with the OpenBook DEX 6 | 7 | ## Installation 8 | 9 | Using npm: 10 | 11 | ``` 12 | npm install @solana/web3.js @openbook-dex/openbook 13 | ``` 14 | 15 | Using yarn: 16 | 17 | ``` 18 | yarn add @solana/web3.js @openbook-dex/openbook 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```js 24 | import { Account, Connection, PublicKey } from '@solana/web3.js'; 25 | import { Market } from '@openbook-dex/openbook'; 26 | 27 | let connection = new Connection('https://testnet.solana.com'); 28 | let marketAddress = new PublicKey('...'); 29 | let programAddress = new PublicKey('...'); 30 | let market = await Market.load(connection, marketAddress, {}, programAddress); 31 | 32 | // Fetching orderbooks 33 | let bids = await market.loadBids(connection); 34 | let asks = await market.loadAsks(connection); 35 | // L2 orderbook data 36 | for (let [price, size] of bids.getL2(20)) { 37 | console.log(price, size); 38 | } 39 | // Full orderbook data 40 | for (let order of asks) { 41 | console.log( 42 | order.orderId, 43 | order.price, 44 | order.size, 45 | order.side, // 'buy' or 'sell' 46 | ); 47 | } 48 | 49 | // Placing orders 50 | let owner = new Account('...'); 51 | let payer = new PublicKey('...'); // spl-token account 52 | await market.placeOrder(connection, { 53 | owner, 54 | payer, 55 | side: 'buy', // 'buy' or 'sell' 56 | price: 123.45, 57 | size: 17.0, 58 | orderType: 'limit', // 'limit', 'ioc', 'postOnly' 59 | }); 60 | 61 | // Retrieving open orders by owner 62 | let myOrders = await market.loadOrdersForOwner(connection, owner.publicKey); 63 | 64 | // Cancelling orders 65 | for (let order of myOrders) { 66 | await market.cancelOrder(connection, owner, order); 67 | } 68 | 69 | // Retrieving fills 70 | for (let fill of await market.loadFills(connection)) { 71 | console.log(fill.orderId, fill.price, fill.size, fill.side); 72 | } 73 | 74 | // Settle funds 75 | for (let openOrders of await market.findOpenOrdersAccountsForOwner( 76 | connection, 77 | owner.publicKey, 78 | )) { 79 | if (openOrders.baseTokenFree > 0 || openOrders.quoteTokenFree > 0) { 80 | // spl-token accounts to which to send the proceeds from trades 81 | let baseTokenAccount = new PublicKey('...'); 82 | let quoteTokenAccount = new PublicKey('...'); 83 | 84 | await market.settleFunds( 85 | connection, 86 | owner, 87 | openOrders, 88 | baseTokenAccount, 89 | quoteTokenAccount, 90 | ); 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /packages/common/src/connection.ts: -------------------------------------------------------------------------------- 1 | import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'; 2 | import { struct } from 'superstruct'; 3 | import assert from 'assert'; 4 | 5 | export async function getMultipleSolanaAccounts( 6 | connection: Connection, 7 | publicKeys: PublicKey[], 8 | ): Promise }>> { 9 | const args = [publicKeys.map(k => k.toBase58()), { commitment: 'recent' }]; 10 | // @ts-ignore 11 | const unsafeRes = await connection._rpcRequest('getMultipleAccounts', args); 12 | const res = GetMultipleAccountsAndContextRpcResult(unsafeRes); 13 | if (res.error) { 14 | throw new Error( 15 | 'failed to get info about accounts ' + 16 | publicKeys.map(k => k.toBase58()).join(', ') + 17 | ': ' + 18 | res.error.message, 19 | ); 20 | } 21 | assert(typeof res.result !== 'undefined'); 22 | const accounts: Array<{ 23 | executable: any; 24 | owner: PublicKey; 25 | lamports: any; 26 | data: Buffer; 27 | }> = []; 28 | for (const account of res.result.value) { 29 | let value: { 30 | executable: any; 31 | owner: PublicKey; 32 | lamports: any; 33 | data: Buffer; 34 | } | null = null; 35 | if (res.result.value) { 36 | const { executable, owner, lamports, data } = account; 37 | assert(data[1] === 'base64'); 38 | value = { 39 | executable, 40 | owner: new PublicKey(owner), 41 | lamports, 42 | data: Buffer.from(data[0], 'base64'), 43 | }; 44 | } 45 | if (value === null) { 46 | throw new Error('Invalid response'); 47 | } 48 | accounts.push(value); 49 | } 50 | return accounts.map((account, idx) => { 51 | return { 52 | publicKey: publicKeys[idx], 53 | account, 54 | }; 55 | }); 56 | } 57 | 58 | function jsonRpcResult(resultDescription: any) { 59 | const jsonRpcVersion = struct.literal('2.0'); 60 | return struct.union([ 61 | struct({ 62 | jsonrpc: jsonRpcVersion, 63 | id: 'string', 64 | error: 'any', 65 | }), 66 | struct({ 67 | jsonrpc: jsonRpcVersion, 68 | id: 'string', 69 | error: 'null?', 70 | result: resultDescription, 71 | }), 72 | ]); 73 | } 74 | 75 | function jsonRpcResultAndContext(resultDescription: any) { 76 | return jsonRpcResult({ 77 | context: struct({ 78 | slot: 'number', 79 | }), 80 | value: resultDescription, 81 | }); 82 | } 83 | 84 | const AccountInfoResult = struct({ 85 | executable: 'boolean', 86 | owner: 'string', 87 | lamports: 'number', 88 | data: 'any', 89 | rentEpoch: 'number?', 90 | }); 91 | 92 | export const GetMultipleAccountsAndContextRpcResult = jsonRpcResultAndContext( 93 | struct.array([struct.union(['null', AccountInfoResult])]), 94 | ); 95 | -------------------------------------------------------------------------------- /packages/swap/examples/swap.js: -------------------------------------------------------------------------------- 1 | const anchor = require('@project-serum/anchor'); 2 | const Provider = anchor.Provider; 3 | const Wallet = anchor.Wallet; 4 | const BN = anchor.BN; 5 | const Connection = require('@solana/web3.js').Connection; 6 | const PublicKey = require('@solana/web3.js').PublicKey; 7 | const TokenListProvider = 8 | require('@solana/spl-token-registry').TokenListProvider; 9 | const Swap = require('..').Swap; 10 | 11 | // Mainnet beta addresses. 12 | const SRM = new PublicKey('SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt'); 13 | const USDC = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); 14 | const USDT = new PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'); 15 | const WBTC = new PublicKey('9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E'); 16 | const DECIMALS = 6; 17 | 18 | async function main() { 19 | // Client for sending transactions to the swap program on mainnet. 20 | const client = await swapClient(); 21 | 22 | // All tokens available for swapping. 23 | const _tokens = client.tokens(); 24 | 25 | // All tokens available for swapping with SRM. 26 | const _srmSwapPairs = client.pairs(SRM); 27 | 28 | // Estimate the amount received by swapping from SRM -> USDC. 29 | const estimatedUsdc = await client.estimate({ 30 | fromMint: SRM, 31 | toMint: USDC, 32 | amount: toNative(1), 33 | }); 34 | 35 | const estimatedBtc = await client.estimate({ 36 | fromMint: SRM, 37 | toMint: WBTC, 38 | amount: toNative(1), 39 | }); 40 | console.log('estimate', estimatedBtc.toNumber()); 41 | /* 42 | // Swaps SRM -> USDC on the Serum orderbook. If the resulting USDC is 43 | // has greater than a 1% error from the estimate, then fails. 44 | const usdcSwapTx = await client.swap({ 45 | fromMint: SRM, 46 | toMint: USDC, 47 | amount: toNative(1), 48 | minExpectedSwapAmount: estimatedUsdc.mul(new BN(99)).div(new BN(100)), 49 | }); 50 | 51 | // Uses the default minExpectedSwapAmount calculation. 52 | const usdcSwapTxDefault = await client.swap({ 53 | fromMint: SRM, 54 | toMint: USDC, 55 | amount: toNative(1), 56 | }); 57 | 58 | // Transitive swap from SRM -> USDC -> BTC. 59 | const btcSwapTx = await client.swap({ 60 | fromMint: SRM, 61 | toMint: WBTC, 62 | amount: toNative(1), 63 | }); 64 | 65 | console.log('resp', fromNative(estimatedUsdc)); 66 | console.log('resp', fromNative(estimatedBtc)); 67 | console.log('resp', usdcSwapTx); 68 | console.log('resp', usdcSwapTxDefault); 69 | console.log('resp', btcSwapTx); 70 | */ 71 | } 72 | 73 | async function swapClient() { 74 | const provider = new Provider( 75 | new Connection('https://api.mainnet-beta.solana.com', 'recent'), 76 | Wallet.local(), 77 | Provider.defaultOptions(), 78 | ); 79 | const tokenList = await new TokenListProvider().resolve(); 80 | return new Swap(provider, tokenList); 81 | } 82 | 83 | // Converts the given number to native units (i.e. with decimals). 84 | // The mints used in this example all have 6 decimals. One should dynamically 85 | // fetch decimals for the tokens they are swapping in production. 86 | function toNative(amount) { 87 | return new BN(amount * 10 ** DECIMALS); 88 | } 89 | 90 | function fromNative(amount) { 91 | return amount.toNumber() / 10 ** DECIMALS; 92 | } 93 | 94 | main().catch(console.error); 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

openbook-ts

5 | 6 |

7 | Openbook Monorepo 8 |

9 | 10 |

11 | Build Status 12 | Discord Chat 13 | License 14 |

15 | 16 |

17 | Website 18 | | 19 | Awesome 20 | | 21 | Twitter 22 | | 23 | Rust 24 |

25 |
26 | 27 | ## Packages 28 | 29 | | Package | Version | Description | 30 | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | 31 | | [`@openbook-dex/borsh`](/packages/borsh) | [![npm](https://img.shields.io/npm/v/@openbook-dex/borsh.svg)](https://www.npmjs.com/package/@openbook-dex/borsh) | Borsh serialization primitives | 32 | | [`@openbook-dex/common`](/packages/common) | [![npm](https://img.shields.io/npm/v/@openbook-dex/common.svg)](https://www.npmjs.com/package/@openbook-dex/common) | Common utilities | 33 | | [`@openbook-dex/openbook`](/packages/openbook) | [![npm](https://img.shields.io/npm/v/@openbook-dex/openbook.svg)](https://www.npmjs.com/package/@openbook-dex/openbook) | Library for interacting with the Openbook DEX | 34 | | [`@openbook-dex/swap`](/packages/swap) | [![npm](https://img.shields.io/npm/v/@openbook-dex/swap.svg)](https://www.npmjs.com/package/@openbook-dex/swap) | Client for swapping on the Openbook DEX | 35 | | [`@openbook-dex/tokens`](/packages/tokens) | [![npm](https://img.shields.io/npm/v/@openbook-dex/tokens.svg)](https://www.npmjs.com/package/@openbook-dex/tokens) | Solana token addresses | 36 | 37 | ## Contributing 38 | 39 | ### Installing 40 | 41 | To get started first install the required build tools: 42 | 43 | ``` 44 | npm install -g yarn 45 | ``` 46 | 47 | Then bootstrap the workspace: 48 | 49 | ``` 50 | yarn 51 | ``` 52 | 53 | ### Building 54 | 55 | To build the workspace: 56 | 57 | ``` 58 | yarn build 59 | ``` 60 | 61 | ### Testing 62 | 63 | To run all tests: 64 | 65 | ``` 66 | yarn test 67 | ``` 68 | 69 | ### Linting 70 | 71 | To lint: 72 | 73 | ``` 74 | yarn lint 75 | ``` 76 | 77 | To apply lint fixes: 78 | 79 | ``` 80 | yarn lint:fix 81 | ``` 82 | -------------------------------------------------------------------------------- /packages/openbook/src/markets.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "address": "8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6", 4 | "deprecated": false, 5 | "name": "SOL/USDC", 6 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 7 | }, 8 | { 9 | "address": "B2na8Awyd7cpC59iEU43FagJAPLigr3AP3s38KM982bu", 10 | "deprecated": false, 11 | "name": "USDT/USDC", 12 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 13 | }, 14 | { 15 | "address": "9Lyhks5bQQxb9EyyX55NtgKQzpM4WK7JCmeaWuQ5MoXD", 16 | "deprecated": false, 17 | "name": "mSOL/USDC", 18 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 19 | }, 20 | { 21 | "address": "3vtRgLDesutQdwotnoUuSMuKKj8YJAE85s938mGKfxXZ", 22 | "deprecated": false, 23 | "name": "scnSOL/USDC", 24 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 25 | }, 26 | { 27 | "address": "BbJgE7HZMaDp5NTYvRh5jZSkQPVDTU8ubPFtpogUkEj4", 28 | "deprecated": false, 29 | "name": "ETH/USDC", 30 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 31 | }, 32 | { 33 | "address": "3NnxQvDcZXputNMxaxsGvqiKpqgPfSYXpNigZNFcknmD", 34 | "deprecated": false, 35 | "name": "MNGO/USDC", 36 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 37 | }, 38 | { 39 | "address": "GpHbiJJ9VHiuHVXeoet121Utrbm1CSNNzYrBKB8Xz2oz", 40 | "deprecated": false, 41 | "name": "RAY/USDT", 42 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 43 | }, 44 | { 45 | "address": "JCKa72xFYGWBEVJZ7AKZ2ofugWPBfrrouQviaGaohi3R", 46 | "deprecated": false, 47 | "name": "stSOL/USDC", 48 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 49 | }, 50 | { 51 | "address": "2AdaV97p6SfkuMQJdu8DHhBhmJe7oWdvbm52MJfYQmfA", 52 | "deprecated": false, 53 | "name": "SOL/USDT", 54 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 55 | }, 56 | { 57 | "address": "DZjbn4XC8qoHKikZqzmhemykVzmossoayV9ffbsUqxVj", 58 | "deprecated": false, 59 | "name": "RAY/USDC", 60 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 61 | }, 62 | { 63 | "address": "HTHMfoxePjcXFhrV74pfCUNoWGe374ecFwiDjPGTkzHr", 64 | "deprecated": false, 65 | "name": "SLND/USDC", 66 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 67 | }, 68 | { 69 | "address": "GfVBNfqWbEUpJcjDbxUcnzRZcPvyt85o2K4Y6tMUifCT", 70 | "deprecated": false, 71 | "name": "XSB/USDC", 72 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 73 | }, 74 | { 75 | "address": "8PhnCfgqpgFM7ZJvttGdBVMXHuU4Q23ACxCvWkbs1M71", 76 | "deprecated": false, 77 | "name": "BONK/USDC", 78 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 79 | }, 80 | { 81 | "address": "ACgDPLf4v6HqouPyAcqFo8nMogVo6axn9jmHo58rw6ox", 82 | "deprecated": false, 83 | "name": "GOFX/USDC", 84 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 85 | }, 86 | { 87 | "address": "6QNusiQ1g7fKierMQhNeAJxfLXomfcAX3tGRMwxfESsw", 88 | "deprecated": false, 89 | "name": "bSOL/SOL", 90 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 91 | }, 92 | { 93 | "address": "ARjaHVxGCQfTvvKjLd7U7srvk6orthZSE6uqWchCczZc", 94 | "deprecated": false, 95 | "name": "bSOL/USDC", 96 | "programId": "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX" 97 | } 98 | ] 99 | -------------------------------------------------------------------------------- /packages/openbook/src/error.ts: -------------------------------------------------------------------------------- 1 | import { Transaction, SystemProgram } from '@solana/web3.js'; 2 | import { PROGRAM_LAYOUT_VERSIONS } from './tokens_and_markets'; 3 | import { TOKEN_PROGRAM_ID } from './token-instructions'; 4 | 5 | export enum DexError { 6 | InvalidMarketFlags = 0, 7 | InvalidAskFlags, 8 | InvalidBidFlags, 9 | InvalidQueueLength, 10 | OwnerAccountNotProvided, 11 | 12 | ConsumeEventsQueueFailure, 13 | WrongCoinVault, 14 | WrongPcVault, 15 | WrongCoinMint, 16 | WrongPcMint, 17 | 18 | CoinVaultProgramId = 10, 19 | PcVaultProgramId, 20 | CoinMintProgramId, 21 | PcMintProgramId, 22 | 23 | WrongCoinMintSize, 24 | WrongPcMintSize, 25 | WrongCoinVaultSize, 26 | WrongPcVaultSize, 27 | 28 | UninitializedVault, 29 | UninitializedMint, 30 | 31 | CoinMintUninitialized = 20, 32 | PcMintUninitialized, 33 | WrongMint, 34 | WrongVaultOwner, 35 | VaultHasDelegate, 36 | 37 | AlreadyInitialized, 38 | WrongAccountDataAlignment, 39 | WrongAccountDataPaddingLength, 40 | WrongAccountHeadPadding, 41 | WrongAccountTailPadding, 42 | 43 | RequestQueueEmpty = 30, 44 | EventQueueTooSmall, 45 | SlabTooSmall, 46 | BadVaultSignerNonce, 47 | InsufficientFunds, 48 | 49 | SplAccountProgramId, 50 | SplAccountLen, 51 | WrongFeeDiscountAccountOwner, 52 | WrongFeeDiscountMint, 53 | 54 | CoinPayerProgramId, 55 | PcPayerProgramId = 40, 56 | ClientIdNotFound, 57 | TooManyOpenOrders, 58 | 59 | FakeErrorSoWeDontChangeNumbers, 60 | BorrowError, 61 | 62 | WrongOrdersAccount, 63 | WrongBidsAccount, 64 | WrongAsksAccount, 65 | WrongRequestQueueAccount, 66 | WrongEventQueueAccount, 67 | 68 | RequestQueueFull = 50, 69 | EventQueueFull, 70 | MarketIsDisabled, 71 | WrongSigner, 72 | TransferFailed, 73 | ClientOrderIdIsZero, 74 | 75 | WrongRentSysvarAccount, 76 | RentNotProvided, 77 | OrdersNotRentExempt, 78 | OrderNotFound, 79 | OrderNotYours, 80 | 81 | WouldSelfTrade, 82 | 83 | Unknown = 1000, 84 | } 85 | 86 | export const KNOWN_PROGRAMS = { 87 | [TOKEN_PROGRAM_ID.toString()]: 'Token program', 88 | [SystemProgram.programId.toString()]: 'System program', 89 | }; 90 | 91 | type CustomError = { Custom: number }; 92 | type InstructionError = [number, CustomError]; 93 | 94 | export function parseInstructionErrorResponse( 95 | transaction: Transaction, 96 | errorResponse: InstructionError, 97 | ): { 98 | failedInstructionIndex: number; 99 | error: string; 100 | failedProgram: string; 101 | } { 102 | const [failedInstructionIndex, customError] = errorResponse; 103 | const failedInstruction = transaction.instructions[failedInstructionIndex]; 104 | let parsedError; 105 | if (failedInstruction.programId.toString() in PROGRAM_LAYOUT_VERSIONS) { 106 | parsedError = DexError[customError['Custom']]; 107 | } else if (failedInstruction.programId.toString() in KNOWN_PROGRAMS) { 108 | const program = KNOWN_PROGRAMS[failedInstruction.programId.toString()]; 109 | parsedError = `${program} error ${customError['Custom']}`; 110 | } else { 111 | parsedError = `Unknown program ${failedInstruction.programId.toString()} custom error: ${ 112 | customError['Custom'] 113 | }`; 114 | } 115 | return { 116 | failedInstructionIndex, 117 | error: parsedError, 118 | failedProgram: failedInstruction.programId.toString(), 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /packages/openbook/src/slab.test.js: -------------------------------------------------------------------------------- 1 | import { Slab } from './slab'; 2 | import BN from 'bn.js'; 3 | 4 | const SLAB_BUFFER = Buffer.from( 5 | '0900000000000000020000000000000008000000000000000400000000000000010000001e00000000000040952fe4da5c1f3c860200000004000000030000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b0000000000000000000000000000000200000002000000000000a0ca17726dae0f1e43010000001111111111111111111111111111111111111111111111111111111111111111410100000000000000000000000000000200000001000000d20a3f4eeee073c3f60fe98e010000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b000000000000000000000000000000020000000300000000000040952fe4da5c1f3c8602000000131313131313131313131313131313131313131313131313131313131313131340e20100000000000000000000000000010000001f0000000500000000000000000000000000000005000000060000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b0000000000000000000000000000000200000004000000040000000000000000000000000000001717171717171717171717171717171717171717171717171717171717171717020000000000000000000000000000000100000020000000000000a0ca17726dae0f1e430100000001000000020000000d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d7b000000000000000000000000000000040000000000000004000000000000000000000000000000171717171717171717171717171717171717171717171717171717171717171702000000000000000000000000000000030000000700000005000000000000000000000000000000171717171717171717171717171717171717171717171717171717171717171702000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 6 | 'hex', 7 | ); 8 | 9 | describe('slab', () => { 10 | let slab; 11 | 12 | it('parses', () => { 13 | slab = Slab.decode(SLAB_BUFFER); 14 | expect(slab).toBeTruthy(); 15 | expect(slab.header.bumpIndex).toBe(9); 16 | expect(slab.nodes).toHaveLength(9); 17 | }); 18 | 19 | it('finds nodes', () => { 20 | expect(slab.get(new BN('123456789012345678901234567890')).ownerSlot).toBe( 21 | 1, 22 | ); 23 | expect(slab.get(new BN('100000000000000000000000000000')).ownerSlot).toBe( 24 | 2, 25 | ); 26 | expect(slab.get(new BN('200000000000000000000000000000')).ownerSlot).toBe( 27 | 3, 28 | ); 29 | expect(slab.get(4).ownerSlot).toBe(4); 30 | }); 31 | 32 | it('does not find nonexistant nodes', () => { 33 | expect(slab.get(0)).toBeNull(); 34 | expect(slab.get(3)).toBeNull(); 35 | expect(slab.get(5)).toBeNull(); 36 | expect(slab.get(6)).toBeNull(); 37 | expect(slab.get(new BN('200000000000000000000000000001'))).toBeNull(); 38 | expect(slab.get(new BN('100000000000000000000000000001'))).toBeNull(); 39 | expect(slab.get(new BN('123456789012345678901234567889'))).toBeNull(); 40 | expect(slab.get(new BN('123456789012345678901234567891'))).toBeNull(); 41 | expect(slab.get(new BN('99999999999999999999999999999'))).toBeNull(); 42 | }); 43 | 44 | it('iterates', () => { 45 | expect(Array.from(slab)).toHaveLength(4); 46 | }); 47 | 48 | it('iterates in order', () => { 49 | let previous = null; 50 | for (const item of slab) { 51 | if (previous) { 52 | expect(item.key.gt(previous.key)).toBeTruthy(); 53 | } 54 | previous = item; 55 | } 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /types/buffer-layout/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'buffer-layout' { 2 | // TODO: remove `any`. 3 | export class Layout { 4 | span: number; 5 | property?: string; 6 | 7 | constructor(span: number, property?: string); 8 | 9 | decode(b: Buffer, offset?: number): T; 10 | encode(src: T, b: Buffer, offset?: number): number; 11 | getSpan(b: Buffer, offset?: number): number; 12 | replicate(name: string): this; 13 | } 14 | // TODO: remove any. 15 | export class Structure extends Layout { 16 | span: any; 17 | } 18 | export function greedy( 19 | elementSpan?: number, 20 | property?: string, 21 | ): Layout; 22 | export function offset( 23 | layout: Layout, 24 | offset?: number, 25 | property?: string, 26 | ): Layout; 27 | export function u8(property?: string): Layout; 28 | export function u16(property?: string): Layout; 29 | export function u24(property?: string): Layout; 30 | export function u32(property?: string): Layout; 31 | export function u40(property?: string): Layout; 32 | export function u48(property?: string): Layout; 33 | export function nu64(property?: string): Layout; 34 | export function u16be(property?: string): Layout; 35 | export function u24be(property?: string): Layout; 36 | export function u32be(property?: string): Layout; 37 | export function u40be(property?: string): Layout; 38 | export function u48be(property?: string): Layout; 39 | export function nu64be(property?: string): Layout; 40 | export function s8(property?: string): Layout; 41 | export function s16(property?: string): Layout; 42 | export function s24(property?: string): Layout; 43 | export function s32(property?: string): Layout; 44 | export function s40(property?: string): Layout; 45 | export function s48(property?: string): Layout; 46 | export function ns64(property?: string): Layout; 47 | export function s16be(property?: string): Layout; 48 | export function s24be(property?: string): Layout; 49 | export function s32be(property?: string): Layout; 50 | export function s40be(property?: string): Layout; 51 | export function s48be(property?: string): Layout; 52 | export function ns64be(property?: string): Layout; 53 | export function f32(property?: string): Layout; 54 | export function f32be(property?: string): Layout; 55 | export function f64(property?: string): Layout; 56 | export function f64be(property?: string): Layout; 57 | export function struct( 58 | fields: Layout[], 59 | property?: string, 60 | decodePrefixes?: boolean, 61 | ): Layout; 62 | export function bits( 63 | word: Layout, 64 | msb?: boolean, 65 | property?: string, 66 | ): any; 67 | export function seq( 68 | elementLayout: Layout, 69 | count: number | Layout, 70 | property?: string, 71 | ): Layout; 72 | export function union( 73 | discr: Layout, 74 | defaultLayout?: any, 75 | property?: string, 76 | ): any; 77 | export function unionLayoutDiscriminator( 78 | layout: Layout, 79 | property?: string, 80 | ): any; 81 | export function blob( 82 | length: number | Layout, 83 | property?: string, 84 | ): Layout; 85 | export function cstr(property?: string): Layout; 86 | export function utf8(maxSpan: number, property?: string): Layout; 87 | } 88 | -------------------------------------------------------------------------------- /packages/openbook/src/slab.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { blob, offset, seq, struct, u32, u8, union } from 'buffer-layout'; 3 | import { publicKeyLayout, setLayoutDecoder, u128, u64, zeros } from './layout'; 4 | import { PublicKey } from '@solana/web3.js'; 5 | 6 | const SLAB_HEADER_LAYOUT = struct( 7 | [ 8 | // Number of modified slab nodes 9 | u32('bumpIndex'), 10 | zeros(4), // Consider slabs with more than 2^32 nodes to be invalid 11 | 12 | // Linked list of unused nodes 13 | u32('freeListLen'), 14 | zeros(4), 15 | u32('freeListHead'), 16 | 17 | u32('root'), 18 | 19 | u32('leafCount'), 20 | zeros(4), 21 | ], 22 | 'header', 23 | ); 24 | 25 | const SLAB_NODE_LAYOUT = union(u32('tag'), blob(68), 'node'); 26 | SLAB_NODE_LAYOUT.addVariant(0, struct([]), 'uninitialized'); 27 | SLAB_NODE_LAYOUT.addVariant( 28 | 1, 29 | struct([ 30 | // Only the first prefixLen high-order bits of key are meaningful 31 | u32('prefixLen'), 32 | u128('key'), 33 | seq(u32(), 2, 'children'), 34 | ]), 35 | 'innerNode', 36 | ); 37 | SLAB_NODE_LAYOUT.addVariant( 38 | 2, 39 | struct([ 40 | u8('ownerSlot'), // Index into OPEN_ORDERS_LAYOUT.orders 41 | u8('feeTier'), 42 | blob(2), 43 | u128('key'), // (price, seqNum) 44 | publicKeyLayout('owner'), // Open orders account 45 | u64('quantity'), // In units of lot size 46 | u64('clientOrderId'), 47 | ]), 48 | 'leafNode', 49 | ); 50 | SLAB_NODE_LAYOUT.addVariant(3, struct([u32('next')]), 'freeNode'); 51 | SLAB_NODE_LAYOUT.addVariant(4, struct([]), 'lastFreeNode'); 52 | 53 | export const SLAB_LAYOUT = struct([ 54 | SLAB_HEADER_LAYOUT, 55 | seq( 56 | SLAB_NODE_LAYOUT, 57 | offset( 58 | SLAB_HEADER_LAYOUT.layoutFor('bumpIndex'), 59 | SLAB_HEADER_LAYOUT.offsetOf('bumpIndex') - SLAB_HEADER_LAYOUT.span, 60 | ), 61 | 'nodes', 62 | ), 63 | ]); 64 | 65 | export class Slab { 66 | private header: any; 67 | private nodes: any; 68 | 69 | constructor(header, nodes) { 70 | this.header = header; 71 | this.nodes = nodes; 72 | } 73 | 74 | static decode(buffer: Buffer) { 75 | return SLAB_LAYOUT.decode(buffer); 76 | } 77 | 78 | get(searchKey: BN | number) { 79 | if (this.header.leafCount === 0) { 80 | return null; 81 | } 82 | if (!(searchKey instanceof BN)) { 83 | searchKey = new BN(searchKey); 84 | } 85 | let index = this.header.root; 86 | while (true) { 87 | const { leafNode, innerNode } = this.nodes[index]; 88 | if (leafNode) { 89 | if (leafNode.key.eq(searchKey)) { 90 | return leafNode; 91 | } 92 | return null; 93 | } else if (innerNode) { 94 | if ( 95 | !innerNode.key 96 | .xor(searchKey) 97 | .iushrn(128 - innerNode.prefixLen) 98 | .isZero() 99 | ) { 100 | return null; 101 | } 102 | index = 103 | innerNode.children[ 104 | searchKey.testn(128 - innerNode.prefixLen - 1) ? 1 : 0 105 | ]; 106 | } else { 107 | throw new Error('Invalid slab'); 108 | } 109 | } 110 | } 111 | 112 | [Symbol.iterator]() { 113 | return this.items(false); 114 | } 115 | 116 | *items(descending = false): Generator<{ 117 | ownerSlot: number; 118 | key: BN; 119 | owner: PublicKey; 120 | quantity: BN; 121 | feeTier: number; 122 | clientOrderId: BN; 123 | }> { 124 | if (this.header.leafCount === 0) { 125 | return; 126 | } 127 | const stack = [this.header.root]; 128 | while (stack.length > 0) { 129 | const index = stack.pop(); 130 | const { leafNode, innerNode } = this.nodes[index]; 131 | if (leafNode) { 132 | yield leafNode; 133 | } else if (innerNode) { 134 | if (descending) { 135 | stack.push(innerNode.children[0], innerNode.children[1]); 136 | } else { 137 | stack.push(innerNode.children[1], innerNode.children[0]); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | setLayoutDecoder(SLAB_LAYOUT, ({ header, nodes }) => new Slab(header, nodes)); 145 | -------------------------------------------------------------------------------- /packages/swap/src/swap-markets.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@project-serum/anchor'; 2 | import { OpenOrders } from '@openbook-dex/openbook'; 3 | import { TokenListContainer } from '@solana/spl-token-registry'; 4 | import { PublicKey } from '@solana/web3.js'; 5 | import { DEX_PID, USDC_PUBKEY, USDT_PUBKEY } from './utils'; 6 | 7 | // Utility class to parse the token list for markets. 8 | export default class SwapMarkets { 9 | constructor( 10 | private provider: Provider, 11 | private tokenList: TokenListContainer, 12 | ) {} 13 | 14 | public tokens(): PublicKey[] { 15 | return this.tokenList 16 | .getList() 17 | .filter((t) => { 18 | const isUsdxQuoted = 19 | t.extensions?.serumV3Usdt || t.extensions?.serumV3Usdc; 20 | return isUsdxQuoted; 21 | }) 22 | .map((t) => new PublicKey(t.address)); 23 | } 24 | 25 | public pairs(mint: PublicKey): PublicKey[] { 26 | const tokenList = this.tokenList.getList(); 27 | 28 | const mintInfo = this.tokenList 29 | .getList() 30 | .filter((t) => t.address === mint.toString())[0]; 31 | if (mintInfo === undefined) { 32 | return []; 33 | } 34 | const pairs = new Set(); 35 | 36 | // Add all tokens that also have USDC quoted markets. 37 | if (mintInfo.extensions?.serumV3Usdc) { 38 | pairs.add(USDC_PUBKEY.toString()); 39 | const iter = tokenList 40 | .filter( 41 | (t) => t.address !== mintInfo.address && t.extensions?.serumV3Usdc, 42 | ) 43 | .map((t) => t.address); 44 | iter.forEach(pairs.add, pairs); 45 | } 46 | 47 | // Add all tokens that also have USDT quoted markets. 48 | if (mintInfo.extensions?.serumV3Usdt) { 49 | pairs.add(USDT_PUBKEY.toString()); 50 | tokenList 51 | .filter( 52 | (t) => t.address !== mintInfo.address && t.extensions?.serumV3Usdt, 53 | ) 54 | .map((t) => t.address) 55 | .forEach(pairs.add, pairs); 56 | } 57 | 58 | return [...pairs].map((t) => new PublicKey(t)); 59 | } 60 | 61 | // Returns the `usdxMint` quoted market address *if* no open orders account 62 | // already exists. 63 | public async getMarketAddressIfNeeded( 64 | usdxMint: PublicKey, 65 | baseMint: PublicKey, 66 | ): Promise { 67 | const marketAddress = this.getMarketAddress(usdxMint, baseMint); 68 | const accounts = await OpenOrders.findForMarketAndOwner( 69 | this.provider.connection, 70 | marketAddress, 71 | this.provider.wallet.publicKey, 72 | DEX_PID, 73 | ); 74 | if (accounts[0] !== undefined) { 75 | throw new Error('Open orders account already exists'); 76 | } 77 | return marketAddress; 78 | } 79 | 80 | // Returns the `usdxMint` quoted market address. 81 | public getMarketAddress(usdxMint: PublicKey, baseMint: PublicKey): PublicKey { 82 | const market = this.tokenList 83 | .getList() 84 | .filter((t) => { 85 | if (t.address !== baseMint?.toString()) { 86 | return false; 87 | } 88 | if (usdxMint.equals(USDC_PUBKEY)) { 89 | return t.extensions?.serumV3Usdc !== undefined; 90 | } else if (usdxMint.equals(USDT_PUBKEY)) { 91 | return t.extensions?.serumV3Usdt !== undefined; 92 | } else { 93 | return false; 94 | } 95 | }) 96 | .map((t) => { 97 | if (usdxMint!.equals(USDC_PUBKEY)) { 98 | return new PublicKey(t.extensions!.serumV3Usdc as string); 99 | } else { 100 | return new PublicKey(t.extensions!.serumV3Usdt as string); 101 | } 102 | })[0]; 103 | if (market === undefined) { 104 | throw new Error( 105 | `Usd(x) quoted market not found for ${baseMint.toString()}`, 106 | ); 107 | } 108 | return market; 109 | } 110 | 111 | // Returns true if there's a trade across two USDC quoted markets 112 | // `fromMint` `toMint`. 113 | public usdcPathExists(fromMint: PublicKey, toMint: PublicKey): boolean { 114 | const fromMarket = this.tokenList 115 | .getList() 116 | .filter((t) => t.address === fromMint.toString()) 117 | .filter((t) => t.extensions?.serumV3Usdc !== undefined)[0]; 118 | const toMarket = this.tokenList 119 | .getList() 120 | .filter((t) => t.address === toMint.toString()) 121 | .filter((t) => t.extensions?.serumV3Usdc !== undefined)[0]; 122 | return fromMarket !== undefined && toMarket !== undefined; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /packages/openbook/src/layout.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import { bits, Blob, Layout, u32, UInt } from 'buffer-layout'; 4 | import { PublicKey } from '@solana/web3.js'; 5 | import BN from 'bn.js'; 6 | 7 | class Zeros extends Blob { 8 | decode(b, offset) { 9 | const slice = super.decode(b, offset); 10 | if (!slice.every((v) => v === 0)) { 11 | throw new Error('nonzero padding bytes'); 12 | } 13 | return slice; 14 | } 15 | } 16 | 17 | export function zeros(length) { 18 | return new Zeros(length); 19 | } 20 | 21 | class PublicKeyLayout extends Blob { 22 | constructor(property) { 23 | super(32, property); 24 | } 25 | 26 | decode(b, offset) { 27 | return new PublicKey(super.decode(b, offset)); 28 | } 29 | 30 | encode(src, b, offset) { 31 | return super.encode(src.toBuffer(), b, offset); 32 | } 33 | } 34 | 35 | export function publicKeyLayout(property) { 36 | return new PublicKeyLayout(property); 37 | } 38 | 39 | class BNLayout extends Blob { 40 | decode(b, offset) { 41 | return new BN(super.decode(b, offset), 10, 'le'); 42 | } 43 | 44 | encode(src, b, offset) { 45 | return super.encode(src.toArrayLike(Buffer, 'le', this.span), b, offset); 46 | } 47 | } 48 | 49 | export function u64(property) { 50 | return new BNLayout(8, property); 51 | } 52 | 53 | export function i64(property) { 54 | return new BNLayout(8, property); 55 | } 56 | 57 | export function u128(property) { 58 | return new BNLayout(16, property); 59 | } 60 | 61 | export class WideBits extends Layout { 62 | constructor(property) { 63 | super(8, property); 64 | this._lower = bits(u32(), false); 65 | this._upper = bits(u32(), false); 66 | } 67 | 68 | addBoolean(property) { 69 | if (this._lower.fields.length < 32) { 70 | this._lower.addBoolean(property); 71 | } else { 72 | this._upper.addBoolean(property); 73 | } 74 | } 75 | 76 | decode(b, offset = 0) { 77 | const lowerDecoded = this._lower.decode(b, offset); 78 | const upperDecoded = this._upper.decode(b, offset + this._lower.span); 79 | return { ...lowerDecoded, ...upperDecoded }; 80 | } 81 | 82 | encode(src, b, offset = 0) { 83 | return ( 84 | this._lower.encode(src, b, offset) + 85 | this._upper.encode(src, b, offset + this._lower.span) 86 | ); 87 | } 88 | } 89 | 90 | export class VersionedLayout extends Layout { 91 | constructor(version, inner, property) { 92 | super(inner.span > 0 ? inner.span + 1 : inner.span, property); 93 | this.version = version; 94 | this.inner = inner; 95 | } 96 | 97 | decode(b, offset = 0) { 98 | // if (b.readUInt8(offset) !== this._version) { 99 | // throw new Error('invalid version'); 100 | // } 101 | return this.inner.decode(b, offset + 1); 102 | } 103 | 104 | encode(src, b, offset = 0) { 105 | b.writeUInt8(this.version, offset); 106 | return 1 + this.inner.encode(src, b, offset + 1); 107 | } 108 | 109 | getSpan(b, offset = 0) { 110 | return 1 + this.inner.getSpan(b, offset + 1); 111 | } 112 | } 113 | 114 | class EnumLayout extends UInt { 115 | constructor(values, span, property) { 116 | super(span, property); 117 | this.values = values; 118 | } 119 | 120 | encode(src, b, offset) { 121 | if (this.values[src] !== undefined) { 122 | return super.encode(this.values[src], b, offset); 123 | } 124 | throw new Error('Invalid ' + this.property); 125 | } 126 | 127 | decode(b, offset) { 128 | const decodedValue = super.decode(b, offset); 129 | const entry = Object.entries(this.values).find( 130 | ([, value]) => value === decodedValue, 131 | ); 132 | if (entry) { 133 | return entry[0]; 134 | } 135 | throw new Error('Invalid ' + this.property); 136 | } 137 | } 138 | 139 | export function sideLayout(property) { 140 | return new EnumLayout({ buy: 0, sell: 1 }, 4, property); 141 | } 142 | 143 | export function orderTypeLayout(property) { 144 | return new EnumLayout({ limit: 0, ioc: 1, postOnly: 2 }, 4, property); 145 | } 146 | 147 | export function selfTradeBehaviorLayout(property) { 148 | return new EnumLayout( 149 | { decrementTake: 0, cancelProvide: 1, abortTransaction: 2 }, 150 | 4, 151 | property, 152 | ); 153 | } 154 | 155 | const ACCOUNT_FLAGS_LAYOUT = new WideBits(); 156 | ACCOUNT_FLAGS_LAYOUT.addBoolean('initialized'); 157 | ACCOUNT_FLAGS_LAYOUT.addBoolean('market'); 158 | ACCOUNT_FLAGS_LAYOUT.addBoolean('openOrders'); 159 | ACCOUNT_FLAGS_LAYOUT.addBoolean('requestQueue'); 160 | ACCOUNT_FLAGS_LAYOUT.addBoolean('eventQueue'); 161 | ACCOUNT_FLAGS_LAYOUT.addBoolean('bids'); 162 | ACCOUNT_FLAGS_LAYOUT.addBoolean('asks'); 163 | 164 | export function accountFlagsLayout(property = 'accountFlags') { 165 | return ACCOUNT_FLAGS_LAYOUT.replicate(property); 166 | } 167 | 168 | export function setLayoutDecoder(layout, decoder) { 169 | const originalDecode = layout.decode; 170 | layout.decode = function decode(b, offset = 0) { 171 | return decoder(originalDecode.call(this, b, offset)); 172 | }; 173 | } 174 | 175 | export function setLayoutEncoder(layout, encoder) { 176 | const originalEncode = layout.encode; 177 | layout.encode = function encode(src, b, offset) { 178 | return originalEncode.call(this, encoder(src), b, offset); 179 | }; 180 | return layout; 181 | } 182 | -------------------------------------------------------------------------------- /packages/common/src/provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Account, 4 | PublicKey, 5 | Transaction, 6 | TransactionSignature, 7 | ConfirmOptions, 8 | sendAndConfirmRawTransaction, 9 | } from '@solana/web3.js'; 10 | import { simulateTransaction } from './simulate-transaction'; 11 | import { readFileSync } from 'fs'; 12 | import { homedir } from 'os'; 13 | 14 | export class Provider { 15 | constructor( 16 | readonly connection: Connection, 17 | readonly wallet: Wallet, 18 | readonly opts: ConfirmOptions, 19 | ) {} 20 | 21 | static defaultOptions(): ConfirmOptions { 22 | return { 23 | preflightCommitment: 'recent', 24 | commitment: 'recent', 25 | }; 26 | } 27 | 28 | static local(url?: string, opts?: ConfirmOptions): Provider { 29 | opts = opts || Provider.defaultOptions(); 30 | const connection = new Connection( 31 | url || 'http://localhost:8899', 32 | opts.preflightCommitment, 33 | ); 34 | const wallet = NodeWallet.local(); 35 | return new Provider(connection, wallet, opts); 36 | } 37 | 38 | async send( 39 | tx: Transaction, 40 | signers?: Array, 41 | opts?: ConfirmOptions, 42 | ): Promise { 43 | if (signers === undefined) { 44 | signers = []; 45 | } 46 | if (opts === undefined) { 47 | opts = this.opts; 48 | } 49 | 50 | const signerKps = signers.filter(s => s !== undefined) as Array; 51 | const signerPubkeys = [this.wallet.publicKey].concat( 52 | signerKps.map(s => s.publicKey), 53 | ); 54 | 55 | tx.setSigners(...signerPubkeys); 56 | tx.recentBlockhash = ( 57 | await this.connection.getRecentBlockhash(opts.preflightCommitment) 58 | ).blockhash; 59 | 60 | await this.wallet.signTransaction(tx); 61 | signerKps.forEach(kp => { 62 | tx.partialSign(kp); 63 | }); 64 | 65 | const rawTx = tx.serialize(); 66 | 67 | try { 68 | const txId = await sendAndConfirmRawTransaction( 69 | this.connection, 70 | rawTx, 71 | opts, 72 | ); 73 | 74 | return txId; 75 | } catch (err) { 76 | console.error('Transaction failed. Simulating for logs...'); 77 | const r = await simulateTransaction( 78 | this.connection, 79 | tx, 80 | opts.commitment ?? 'recent', 81 | ); 82 | console.error(r); 83 | throw err; 84 | } 85 | } 86 | 87 | async sendAll( 88 | reqs: Array, 89 | opts?: ConfirmOptions, 90 | ): Promise> { 91 | if (opts === undefined) { 92 | opts = this.opts; 93 | } 94 | const blockhash = await this.connection.getRecentBlockhash( 95 | opts.preflightCommitment, 96 | ); 97 | 98 | const txs = reqs.map(r => { 99 | const tx = r.tx; 100 | let signers = r.signers; 101 | 102 | if (signers === undefined) { 103 | signers = []; 104 | } 105 | 106 | const signerKps = signers.filter(s => s !== undefined) as Array; 107 | const signerPubkeys = [this.wallet.publicKey].concat( 108 | signerKps.map(s => s.publicKey), 109 | ); 110 | 111 | tx.setSigners(...signerPubkeys); 112 | tx.recentBlockhash = blockhash.blockhash; 113 | signerKps.forEach(kp => { 114 | tx.partialSign(kp); 115 | }); 116 | 117 | return tx; 118 | }); 119 | 120 | const signedTxs = await this.wallet.signAllTransactions(txs); 121 | 122 | const sigs = []; 123 | 124 | for (let k = 0; k < txs.length; k += 1) { 125 | const tx = signedTxs[k]; 126 | const rawTx = tx.serialize(); 127 | try { 128 | sigs.push( 129 | await sendAndConfirmRawTransaction(this.connection, rawTx, opts), 130 | ); 131 | } catch (err) { 132 | console.error('Transaction failed. Simulating for logs...'); 133 | const r = await simulateTransaction( 134 | this.connection, 135 | tx, 136 | opts.commitment ?? 'recent', 137 | ); 138 | console.error(r); 139 | throw err; 140 | } 141 | } 142 | 143 | return sigs; 144 | } 145 | } 146 | 147 | export type SendTxRequest = { 148 | tx: Transaction; 149 | signers: Array; 150 | }; 151 | 152 | export interface Wallet { 153 | signTransaction(tx: Transaction): Promise; 154 | signAllTransactions(txs: Transaction[]): Promise; 155 | publicKey: PublicKey; 156 | } 157 | 158 | export class NodeWallet implements Wallet { 159 | constructor(readonly payer: Account) {} 160 | 161 | static local(): NodeWallet { 162 | const payer = new Account( 163 | Buffer.from( 164 | JSON.parse( 165 | readFileSync(homedir() + '/.config/solana/id.json', { 166 | encoding: 'utf-8', 167 | }), 168 | ), 169 | ), 170 | ); 171 | return new NodeWallet(payer); 172 | } 173 | 174 | async signTransaction(tx: Transaction): Promise { 175 | tx.partialSign(this.payer); 176 | return tx; 177 | } 178 | 179 | async signAllTransactions(txs: Transaction[]): Promise { 180 | return txs.map(t => { 181 | t.partialSign(this.payer); 182 | return t; 183 | }); 184 | } 185 | 186 | get publicKey(): PublicKey { 187 | return this.payer.publicKey; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /packages/openbook/src/queue.ts: -------------------------------------------------------------------------------- 1 | import { bits, blob, struct, u32, u8 } from 'buffer-layout'; 2 | import { 3 | accountFlagsLayout, 4 | publicKeyLayout, 5 | u128, 6 | u64, 7 | zeros, 8 | } from './layout'; 9 | import BN from 'bn.js'; 10 | import { PublicKey } from '@solana/web3.js'; 11 | 12 | const REQUEST_QUEUE_HEADER = struct([ 13 | blob(5), 14 | 15 | accountFlagsLayout('accountFlags'), 16 | u32('head'), 17 | zeros(4), 18 | u32('count'), 19 | zeros(4), 20 | u32('nextSeqNum'), 21 | zeros(4), 22 | ]); 23 | 24 | const REQUEST_FLAGS = bits(u8(), false, 'requestFlags'); 25 | REQUEST_FLAGS.addBoolean('newOrder'); 26 | REQUEST_FLAGS.addBoolean('cancelOrder'); 27 | REQUEST_FLAGS.addBoolean('bid'); 28 | REQUEST_FLAGS.addBoolean('postOnly'); 29 | REQUEST_FLAGS.addBoolean('ioc'); 30 | 31 | const REQUEST = struct([ 32 | REQUEST_FLAGS, 33 | u8('openOrdersSlot'), 34 | u8('feeTier'), 35 | blob(5), 36 | u64('maxBaseSizeOrCancelId'), 37 | u64('nativeQuoteQuantityLocked'), 38 | u128('orderId'), 39 | publicKeyLayout('openOrders'), 40 | u64('clientOrderId'), 41 | ]); 42 | 43 | const EVENT_QUEUE_HEADER = struct([ 44 | blob(5), 45 | 46 | accountFlagsLayout('accountFlags'), 47 | u32('head'), 48 | zeros(4), 49 | u32('count'), 50 | zeros(4), 51 | u32('seqNum'), 52 | zeros(4), 53 | ]); 54 | 55 | const EVENT_FLAGS = bits(u8(), false, 'eventFlags'); 56 | EVENT_FLAGS.addBoolean('fill'); 57 | EVENT_FLAGS.addBoolean('out'); 58 | EVENT_FLAGS.addBoolean('bid'); 59 | EVENT_FLAGS.addBoolean('maker'); 60 | 61 | const EVENT = struct([ 62 | EVENT_FLAGS, 63 | u8('openOrdersSlot'), 64 | u8('feeTier'), 65 | blob(5), 66 | u64('nativeQuantityReleased'), // Amount the user received 67 | u64('nativeQuantityPaid'), // Amount the user paid 68 | u64('nativeFeeOrRebate'), 69 | u128('orderId'), 70 | publicKeyLayout('openOrders'), 71 | u64('clientOrderId'), 72 | ]); 73 | 74 | export interface Event { 75 | eventFlags: { fill: boolean; out: boolean; bid: boolean; maker: boolean }; 76 | 77 | seqNum?: number; 78 | orderId: BN; 79 | openOrders: PublicKey; 80 | openOrdersSlot: number; 81 | feeTier: number; 82 | 83 | nativeQuantityReleased: BN; 84 | nativeQuantityPaid: BN; 85 | nativeFeeOrRebate: BN; 86 | } 87 | 88 | function decodeQueueItem(headerLayout, nodeLayout, buffer: Buffer, nodeIndex) { 89 | return nodeLayout.decode( 90 | buffer, 91 | headerLayout.span + nodeIndex * nodeLayout.span, 92 | ); 93 | } 94 | 95 | function decodeQueue( 96 | headerLayout, 97 | nodeLayout, 98 | buffer: Buffer, 99 | history?: number, 100 | ) { 101 | const header = headerLayout.decode(buffer); 102 | const allocLen = Math.floor( 103 | (buffer.length - headerLayout.span) / nodeLayout.span, 104 | ); 105 | const nodes: any[] = []; 106 | if (history) { 107 | for (let i = 0; i < Math.min(history, allocLen); ++i) { 108 | const nodeIndex = 109 | (header.head + header.count + allocLen - 1 - i) % allocLen; 110 | nodes.push(decodeQueueItem(headerLayout, nodeLayout, buffer, nodeIndex)); 111 | } 112 | } else { 113 | for (let i = 0; i < header.count; ++i) { 114 | const nodeIndex = (header.head + i) % allocLen; 115 | nodes.push(decodeQueueItem(headerLayout, nodeLayout, buffer, nodeIndex)); 116 | } 117 | } 118 | return { header, nodes }; 119 | } 120 | 121 | export function decodeEventsSince(buffer: Buffer, lastSeqNum: number): Event[] { 122 | const header = EVENT_QUEUE_HEADER.decode(buffer); 123 | const allocLen = Math.floor( 124 | (buffer.length - EVENT_QUEUE_HEADER.span) / EVENT.span, 125 | ); 126 | 127 | // calculate number of missed events 128 | // account for u32 & ringbuffer overflows 129 | const modulo32Uint = 0x100000000; 130 | let missedEvents = (header.seqNum - lastSeqNum + modulo32Uint) % modulo32Uint; 131 | if (missedEvents > allocLen) { 132 | missedEvents = allocLen - 1; 133 | } 134 | const startSeq = (header.seqNum - missedEvents + modulo32Uint) % modulo32Uint; 135 | 136 | // define boundary indexes in ring buffer [start;end) 137 | const endIndex = (header.head + header.count) % allocLen; 138 | const startIndex = (endIndex - missedEvents + allocLen) % allocLen; 139 | 140 | const results: Event[] = []; 141 | for (let i = 0; i < missedEvents; ++i) { 142 | const nodeIndex = (startIndex + i) % allocLen; 143 | const event = decodeQueueItem(EVENT_QUEUE_HEADER, EVENT, buffer, nodeIndex); 144 | event.seqNum = (startSeq + i) % modulo32Uint; 145 | results.push(event); 146 | } 147 | return results; 148 | } 149 | 150 | export function decodeRequestQueue(buffer: Buffer, history?: number) { 151 | const { header, nodes } = decodeQueue( 152 | REQUEST_QUEUE_HEADER, 153 | REQUEST, 154 | buffer, 155 | history, 156 | ); 157 | if (!header.accountFlags.initialized || !header.accountFlags.requestQueue) { 158 | throw new Error('Invalid requests queue'); 159 | } 160 | return nodes; 161 | } 162 | 163 | export function decodeEventQueue(buffer: Buffer, history?: number): Event[] { 164 | const { header, nodes } = decodeQueue( 165 | EVENT_QUEUE_HEADER, 166 | EVENT, 167 | buffer, 168 | history, 169 | ); 170 | if (!header.accountFlags.initialized || !header.accountFlags.eventQueue) { 171 | throw new Error('Invalid events queue'); 172 | } 173 | return nodes; 174 | } 175 | 176 | export const REQUEST_QUEUE_LAYOUT = { 177 | HEADER: REQUEST_QUEUE_HEADER, 178 | NODE: REQUEST, 179 | }; 180 | 181 | export const EVENT_QUEUE_LAYOUT = { 182 | HEADER: EVENT_QUEUE_HEADER, 183 | NODE: EVENT, 184 | }; 185 | -------------------------------------------------------------------------------- /packages/openbook/src/token-instructions.js: -------------------------------------------------------------------------------- 1 | import * as BufferLayout from 'buffer-layout'; 2 | import { 3 | PublicKey, 4 | SYSVAR_RENT_PUBKEY, 5 | TransactionInstruction, 6 | } from '@solana/web3.js'; 7 | import { publicKeyLayout } from './layout'; 8 | 9 | // NOTE: Update these if the position of arguments for the initializeAccount instruction changes 10 | export const INITIALIZE_ACCOUNT_ACCOUNT_INDEX = 0; 11 | export const INITIALIZE_ACCOUNT_MINT_INDEX = 1; 12 | export const INITIALIZE_ACCOUNT_OWNER_INDEX = 2; 13 | 14 | // NOTE: Update these if the position of arguments for the transfer instruction changes 15 | export const TRANSFER_SOURCE_INDEX = 0; 16 | export const TRANSFER_DESTINATION_INDEX = 1; 17 | export const TRANSFER_OWNER_INDEX = 2; 18 | 19 | // NOTE: Update these if the position of arguments for the closeAccount instruction changes 20 | export const CLOSE_ACCOUNT_SOURCE_INDEX = 0; 21 | export const CLOSE_ACCOUNT_DESTINATION_INDEX = 1; 22 | export const CLOSE_ACCOUNT_OWNER_INDEX = 2; 23 | 24 | export const TOKEN_PROGRAM_ID = new PublicKey( 25 | 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 26 | ); 27 | 28 | export const WRAPPED_SOL_MINT = new PublicKey( 29 | 'So11111111111111111111111111111111111111112', 30 | ); 31 | 32 | export const MSRM_MINT = new PublicKey( 33 | 'MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L', 34 | ); 35 | export const MSRM_DECIMALS = 0; 36 | 37 | export const SRM_MINT = new PublicKey( 38 | 'SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt', 39 | ); 40 | export const SRM_DECIMALS = 6; 41 | 42 | const LAYOUT = BufferLayout.union(BufferLayout.u8('instruction')); 43 | LAYOUT.addVariant( 44 | 0, 45 | BufferLayout.struct([ 46 | BufferLayout.u8('decimals'), 47 | publicKeyLayout('mintAuthority'), 48 | BufferLayout.u8('freezeAuthorityOption'), 49 | publicKeyLayout('freezeAuthority'), 50 | ]), 51 | 'initializeMint', 52 | ); 53 | LAYOUT.addVariant(1, BufferLayout.struct([]), 'initializeAccount'); 54 | LAYOUT.addVariant( 55 | 3, 56 | BufferLayout.struct([BufferLayout.nu64('amount')]), 57 | 'transfer', 58 | ); 59 | LAYOUT.addVariant( 60 | 4, 61 | BufferLayout.struct([BufferLayout.nu64('amount')]), 62 | 'approve', 63 | ); 64 | LAYOUT.addVariant(5, BufferLayout.struct([]), 'revoke'); 65 | LAYOUT.addVariant( 66 | 6, 67 | BufferLayout.struct([ 68 | BufferLayout.u8('authorityType'), 69 | BufferLayout.u8('newAuthorityOption'), 70 | publicKeyLayout('newAuthority'), 71 | ]), 72 | 'setAuthority', 73 | ); 74 | LAYOUT.addVariant( 75 | 7, 76 | BufferLayout.struct([BufferLayout.nu64('amount')]), 77 | 'mintTo', 78 | ); 79 | LAYOUT.addVariant( 80 | 8, 81 | BufferLayout.struct([BufferLayout.nu64('amount')]), 82 | 'burn', 83 | ); 84 | LAYOUT.addVariant(9, BufferLayout.struct([]), 'closeAccount'); 85 | 86 | const instructionMaxSpan = Math.max( 87 | ...Object.values(LAYOUT.registry).map((r) => r.span), 88 | ); 89 | 90 | function encodeTokenInstructionData(instruction) { 91 | const b = Buffer.alloc(instructionMaxSpan); 92 | const span = LAYOUT.encode(instruction, b); 93 | return b.slice(0, span); 94 | } 95 | 96 | export function decodeTokenInstructionData(instruction) { 97 | return LAYOUT.decode(instruction); 98 | } 99 | 100 | export function initializeMint({ 101 | mint, 102 | decimals, 103 | mintAuthority, 104 | freezeAuthority = null, 105 | }) { 106 | const keys = [ 107 | { pubkey: mint, isSigner: false, isWritable: true }, 108 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 109 | ]; 110 | return new TransactionInstruction({ 111 | keys, 112 | data: encodeTokenInstructionData({ 113 | initializeMint: { 114 | decimals, 115 | mintAuthority, 116 | freezeAuthorityOption: !!freezeAuthority, 117 | freezeAuthority: freezeAuthority || new PublicKey(0), 118 | }, 119 | }), 120 | programId: TOKEN_PROGRAM_ID, 121 | }); 122 | } 123 | 124 | export function initializeAccount({ account, mint, owner }) { 125 | const keys = [ 126 | { pubkey: account, isSigner: false, isWritable: true }, 127 | { pubkey: mint, isSigner: false, isWritable: false }, 128 | { pubkey: owner, isSigner: false, isWritable: false }, 129 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 130 | ]; 131 | return new TransactionInstruction({ 132 | keys, 133 | data: encodeTokenInstructionData({ 134 | initializeAccount: {}, 135 | }), 136 | programId: TOKEN_PROGRAM_ID, 137 | }); 138 | } 139 | 140 | export function transfer({ source, destination, amount, owner }) { 141 | const keys = [ 142 | { pubkey: source, isSigner: false, isWritable: true }, 143 | { pubkey: destination, isSigner: false, isWritable: true }, 144 | { pubkey: owner, isSigner: true, isWritable: false }, 145 | ]; 146 | return new TransactionInstruction({ 147 | keys, 148 | data: encodeTokenInstructionData({ 149 | transfer: { amount }, 150 | }), 151 | programId: TOKEN_PROGRAM_ID, 152 | }); 153 | } 154 | 155 | export function approve({ source, delegate, amount, owner }) { 156 | const keys = [ 157 | { pubkey: source, isSigner: false, isWritable: true }, 158 | { pubkey: delegate, isSigner: false, isWritable: false }, 159 | { pubkey: owner, isSigner: true, isWritable: false }, 160 | ]; 161 | return new TransactionInstruction({ 162 | keys, 163 | data: encodeTokenInstructionData({ 164 | approve: { amount }, 165 | }), 166 | programId: TOKEN_PROGRAM_ID, 167 | }); 168 | } 169 | 170 | export function revoke({ source, owner }) { 171 | const keys = [ 172 | { pubkey: source, isSigner: false, isWritable: true }, 173 | { pubkey: owner, isSigner: true, isWritable: false }, 174 | ]; 175 | return new TransactionInstruction({ 176 | keys, 177 | data: encodeTokenInstructionData({ 178 | revoke: {}, 179 | }), 180 | programId: TOKEN_PROGRAM_ID, 181 | }); 182 | } 183 | 184 | export function setAuthority({ 185 | target, 186 | currentAuthority, 187 | newAuthority, 188 | authorityType, 189 | }) { 190 | const keys = [ 191 | { pubkey: target, isSigner: false, isWritable: true }, 192 | { pubkey: currentAuthority, isSigner: true, isWritable: false }, 193 | ]; 194 | return new TransactionInstruction({ 195 | keys, 196 | data: encodeTokenInstructionData({ 197 | setAuthority: { 198 | authorityType, 199 | newAuthorityOption: !!newAuthority, 200 | newAuthority, 201 | }, 202 | }), 203 | programId: TOKEN_PROGRAM_ID, 204 | }); 205 | } 206 | 207 | export function mintTo({ mint, destination, amount, mintAuthority }) { 208 | const keys = [ 209 | { pubkey: mint, isSigner: false, isWritable: true }, 210 | { pubkey: destination, isSigner: false, isWritable: true }, 211 | { pubkey: mintAuthority, isSigner: true, isWritable: false }, 212 | ]; 213 | return new TransactionInstruction({ 214 | keys, 215 | data: encodeTokenInstructionData({ 216 | mintTo: { amount }, 217 | }), 218 | programId: TOKEN_PROGRAM_ID, 219 | }); 220 | } 221 | 222 | export function closeAccount({ source, destination, owner }) { 223 | const keys = [ 224 | { pubkey: source, isSigner: false, isWritable: true }, 225 | { pubkey: destination, isSigner: false, isWritable: true }, 226 | { pubkey: owner, isSigner: true, isWritable: false }, 227 | ]; 228 | return new TransactionInstruction({ 229 | keys, 230 | data: encodeTokenInstructionData({ 231 | closeAccount: {}, 232 | }), 233 | programId: TOKEN_PROGRAM_ID, 234 | }); 235 | } 236 | -------------------------------------------------------------------------------- /packages/common/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Account, 3 | SystemProgram, 4 | PublicKey, 5 | Transaction, 6 | TransactionInstruction, 7 | } from '@solana/web3.js'; 8 | import { Provider } from './provider'; 9 | import { 10 | MintInfo, 11 | MintLayout, 12 | AccountInfo, 13 | AccountLayout, 14 | u64, 15 | } from '@solana/spl-token'; 16 | import { TokenInstructions } from '@openbook-dex/openbook'; 17 | import BN from 'bn.js'; 18 | 19 | export * from './provider'; 20 | export * as token from './token'; 21 | export { simulateTransaction } from './simulate-transaction'; 22 | export * as connection from './connection'; 23 | 24 | export const SPL_SHARED_MEMORY_ID = new PublicKey( 25 | 'shmem4EWT2sPdVGvTZCzXXRAURL9G5vpPxNwSeKhHUL', 26 | ); 27 | 28 | export async function createMint( 29 | provider: Provider, 30 | authority?: PublicKey, 31 | decimals?: number, 32 | ): Promise { 33 | if (authority === undefined) { 34 | authority = provider.wallet.publicKey; 35 | } 36 | const mint = new Account(); 37 | const instructions = await createMintInstructions( 38 | provider, 39 | authority, 40 | mint.publicKey, 41 | decimals, 42 | ); 43 | 44 | const tx = new Transaction(); 45 | tx.add(...instructions); 46 | 47 | await provider.send(tx, [mint]); 48 | 49 | return mint.publicKey; 50 | } 51 | 52 | export async function createMintInstructions( 53 | provider: Provider, 54 | authority: PublicKey, 55 | mint: PublicKey, 56 | decimals?: number, 57 | ): Promise { 58 | const instructions = [ 59 | SystemProgram.createAccount({ 60 | fromPubkey: provider.wallet.publicKey, 61 | newAccountPubkey: mint, 62 | space: 82, 63 | lamports: await provider.connection.getMinimumBalanceForRentExemption(82), 64 | programId: TokenInstructions.TOKEN_PROGRAM_ID, 65 | }), 66 | TokenInstructions.initializeMint({ 67 | mint, 68 | decimals: decimals ?? 0, 69 | mintAuthority: authority, 70 | }), 71 | ]; 72 | return instructions; 73 | } 74 | 75 | export async function createMintAndVault( 76 | provider: Provider, 77 | amount: BN, 78 | owner?: PublicKey, 79 | decimals?: number, 80 | ): Promise<[PublicKey, PublicKey]> { 81 | if (owner === undefined) { 82 | owner = provider.wallet.publicKey; 83 | } 84 | const mint = new Account(); 85 | const vault = new Account(); 86 | const tx = new Transaction(); 87 | tx.add( 88 | SystemProgram.createAccount({ 89 | fromPubkey: provider.wallet.publicKey, 90 | newAccountPubkey: mint.publicKey, 91 | space: 82, 92 | lamports: await provider.connection.getMinimumBalanceForRentExemption(82), 93 | programId: TokenInstructions.TOKEN_PROGRAM_ID, 94 | }), 95 | TokenInstructions.initializeMint({ 96 | mint: mint.publicKey, 97 | decimals: decimals ?? 0, 98 | mintAuthority: provider.wallet.publicKey, 99 | }), 100 | SystemProgram.createAccount({ 101 | fromPubkey: provider.wallet.publicKey, 102 | newAccountPubkey: vault.publicKey, 103 | space: 165, 104 | lamports: await provider.connection.getMinimumBalanceForRentExemption( 105 | 165, 106 | ), 107 | programId: TokenInstructions.TOKEN_PROGRAM_ID, 108 | }), 109 | TokenInstructions.initializeAccount({ 110 | account: vault.publicKey, 111 | mint: mint.publicKey, 112 | owner, 113 | }), 114 | TokenInstructions.mintTo({ 115 | mint: mint.publicKey, 116 | destination: vault.publicKey, 117 | amount, 118 | mintAuthority: provider.wallet.publicKey, 119 | }), 120 | ); 121 | await provider.send(tx, [mint, vault]); 122 | return [mint.publicKey, vault.publicKey]; 123 | } 124 | 125 | export async function createTokenAccount( 126 | provider: Provider, 127 | mint: PublicKey, 128 | owner: PublicKey, 129 | ): Promise { 130 | const vault = new Account(); 131 | const tx = new Transaction(); 132 | tx.add( 133 | ...(await createTokenAccountInstrs(provider, vault.publicKey, mint, owner)), 134 | ); 135 | await provider.send(tx, [vault]); 136 | return vault.publicKey; 137 | } 138 | 139 | export async function createTokenAccountInstrs( 140 | provider: Provider, 141 | newAccountPubkey: PublicKey, 142 | mint: PublicKey, 143 | owner: PublicKey, 144 | lamports?: number, 145 | ): Promise { 146 | if (lamports === undefined) { 147 | lamports = await provider.connection.getMinimumBalanceForRentExemption(165); 148 | } 149 | return [ 150 | SystemProgram.createAccount({ 151 | fromPubkey: provider.wallet.publicKey, 152 | newAccountPubkey, 153 | space: 165, 154 | lamports, 155 | programId: TokenInstructions.TOKEN_PROGRAM_ID, 156 | }), 157 | TokenInstructions.initializeAccount({ 158 | account: newAccountPubkey, 159 | mint, 160 | owner, 161 | }), 162 | ]; 163 | } 164 | 165 | export async function createAccountRentExempt( 166 | provider: Provider, 167 | programId: PublicKey, 168 | size: number, 169 | ): Promise { 170 | const acc = new Account(); 171 | const tx = new Transaction(); 172 | tx.add( 173 | SystemProgram.createAccount({ 174 | fromPubkey: provider.wallet.publicKey, 175 | newAccountPubkey: acc.publicKey, 176 | space: size, 177 | lamports: await provider.connection.getMinimumBalanceForRentExemption( 178 | size, 179 | ), 180 | programId, 181 | }), 182 | ); 183 | await provider.send(tx, [acc]); 184 | return acc; 185 | } 186 | 187 | export async function getMintInfo( 188 | provider: Provider, 189 | addr: PublicKey, 190 | ): Promise { 191 | const depositorAccInfo = await provider.connection.getAccountInfo(addr); 192 | if (depositorAccInfo === null) { 193 | throw new Error('Failed to find token account'); 194 | } 195 | return parseMintAccount(depositorAccInfo.data); 196 | } 197 | 198 | export function parseMintAccount(data: Buffer): MintInfo { 199 | const m = MintLayout.decode(data); 200 | m.mintAuthority = new PublicKey(m.mintAuthority); 201 | m.supply = u64.fromBuffer(m.supply); 202 | m.isInitialized = m.state !== 0; 203 | return m; 204 | } 205 | 206 | export async function getTokenAccount( 207 | provider: Provider, 208 | addr: PublicKey, 209 | ): Promise { 210 | const depositorAccInfo = await provider.connection.getAccountInfo(addr); 211 | if (depositorAccInfo === null) { 212 | throw new Error('Failed to find token account'); 213 | } 214 | return parseTokenAccount(depositorAccInfo.data); 215 | } 216 | 217 | export function parseTokenAccount(data: Buffer): AccountInfo { 218 | const accountInfo = AccountLayout.decode(data); 219 | accountInfo.mint = new PublicKey(accountInfo.mint); 220 | accountInfo.owner = new PublicKey(accountInfo.owner); 221 | accountInfo.amount = u64.fromBuffer(accountInfo.amount); 222 | 223 | if (accountInfo.delegateOption === 0) { 224 | accountInfo.delegate = null; 225 | // eslint-disable-next-line new-cap 226 | accountInfo.delegatedAmount = new u64(0); 227 | } else { 228 | accountInfo.delegate = new PublicKey(accountInfo.delegate); 229 | accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount); 230 | } 231 | 232 | accountInfo.isInitialized = accountInfo.state !== 0; 233 | accountInfo.isFrozen = accountInfo.state === 2; 234 | 235 | if (accountInfo.isNativeOption === 1) { 236 | accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative); 237 | accountInfo.isNative = true; 238 | } else { 239 | accountInfo.rentExemptReserve = null; 240 | accountInfo.isNative = false; 241 | } 242 | 243 | if (accountInfo.closeAuthorityOption === 0) { 244 | accountInfo.closeAuthority = null; 245 | } else { 246 | accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority); 247 | } 248 | 249 | return accountInfo; 250 | } 251 | 252 | export function sleep(ms: number): Promise { 253 | return new Promise(resolve => setTimeout(resolve, ms)); 254 | } 255 | 256 | export type ProgramAccount = { 257 | publicKey: PublicKey; 258 | account: T; 259 | }; 260 | -------------------------------------------------------------------------------- /packages/openbook/src/market-proxy/middleware.ts: -------------------------------------------------------------------------------- 1 | import { utils } from '@project-serum/anchor'; 2 | import { 3 | SystemProgram, 4 | PublicKey, 5 | TransactionInstruction, 6 | } from '@solana/web3.js'; 7 | 8 | export interface Middleware { 9 | initOpenOrders(ix: TransactionInstruction): void; 10 | newOrderV3(ix: TransactionInstruction): void; 11 | cancelOrderV2(ix: TransactionInstruction): void; 12 | cancelOrderByClientIdV2(ix: TransactionInstruction): void; 13 | settleFunds(ix: TransactionInstruction): void; 14 | closeOpenOrders(ix: TransactionInstruction): void; 15 | prune(ix: TransactionInstruction): void; 16 | consumeEvents(ix: TransactionInstruction): void; 17 | consumeEventsPermissioned(ix: TransactionInstruction): void; 18 | } 19 | 20 | export class OpenOrdersPda implements Middleware { 21 | private _proxyProgramId: PublicKey; 22 | private _dexProgramId: PublicKey; 23 | 24 | constructor({ 25 | proxyProgramId, 26 | dexProgramId, 27 | }: { 28 | proxyProgramId: PublicKey; 29 | dexProgramId: PublicKey; 30 | }) { 31 | this._proxyProgramId = proxyProgramId; 32 | this._dexProgramId = dexProgramId; 33 | } 34 | 35 | // PDA authorized to create open orders accounts. 36 | public static async marketAuthority( 37 | market: PublicKey, 38 | dexProgramId: PublicKey, 39 | proxyProgramId: PublicKey, 40 | ): Promise { 41 | // b"open-orders-init" 42 | const openOrdersStr = Buffer.from([ 43 | 111, 112, 101, 110, 45, 111, 114, 100, 101, 114, 115, 45, 105, 110, 105, 44 | 116, 45 | ]); 46 | const [addr] = await PublicKey.findProgramAddress( 47 | [openOrdersStr, dexProgramId.toBuffer(), market.toBuffer()], 48 | proxyProgramId, 49 | ); 50 | return addr; 51 | } 52 | 53 | public static async openOrdersAddress( 54 | market: PublicKey, 55 | owner: PublicKey, 56 | dexProgramId: PublicKey, 57 | proxyProgramId: PublicKey, 58 | ): Promise { 59 | // b"open-orders". 60 | const openOrdersStr = Buffer.from([ 61 | 111, 112, 101, 110, 45, 111, 114, 100, 101, 114, 115, 62 | ]); 63 | const [addr] = await PublicKey.findProgramAddress( 64 | [ 65 | openOrdersStr, 66 | dexProgramId.toBuffer(), 67 | market.toBuffer(), 68 | owner.toBuffer(), 69 | ], 70 | proxyProgramId, 71 | ); 72 | return addr; 73 | } 74 | 75 | initOpenOrders(ix: TransactionInstruction) { 76 | const market = ix.keys[2].pubkey; 77 | const owner = ix.keys[1].pubkey; 78 | // b"open-orders" 79 | const openOrdersSeed = Buffer.from([ 80 | 111, 112, 101, 110, 45, 111, 114, 100, 101, 114, 115, 81 | ]); 82 | 83 | // b"open-orders-init" 84 | const openOrdersInitSeed = Buffer.from([ 85 | 111, 112, 101, 110, 45, 111, 114, 100, 101, 114, 115, 45, 105, 110, 105, 86 | 116, 87 | ]); 88 | const [openOrders, bump] = utils.publicKey.findProgramAddressSync( 89 | [ 90 | openOrdersSeed, 91 | this._dexProgramId.toBuffer(), 92 | market.toBuffer(), 93 | owner.toBuffer(), 94 | ], 95 | this._proxyProgramId, 96 | ); 97 | const [marketAuthority, bumpInit] = utils.publicKey.findProgramAddressSync( 98 | [openOrdersInitSeed, this._dexProgramId.toBuffer(), market.toBuffer()], 99 | this._proxyProgramId, 100 | ); 101 | 102 | // Override the open orders account and market authority. 103 | ix.keys[0].pubkey = openOrders; 104 | ix.keys[4].pubkey = marketAuthority; 105 | 106 | // Writable because it must pay for the PDA initialization. 107 | ix.keys[1].isWritable = true; 108 | 109 | // Prepend to the account list extra accounts needed for PDA initialization. 110 | ix.keys = [ 111 | { pubkey: this._dexProgramId, isSigner: false, isWritable: false }, 112 | { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, 113 | ...ix.keys, 114 | ]; 115 | // Prepend the ix discriminator, bump, and bumpInit to the instruction data, 116 | // which saves the program compute by avoiding recalculating them in the 117 | // program. 118 | ix.data = Buffer.concat([Buffer.from([0, bump, bumpInit]), ix.data]); 119 | } 120 | newOrderV3(ix: TransactionInstruction) { 121 | ix.data = Buffer.concat([Buffer.from([1]), ix.data]); 122 | } 123 | cancelOrderV2(ix: TransactionInstruction) { 124 | ix.data = Buffer.concat([Buffer.from([2]), ix.data]); 125 | } 126 | cancelOrderByClientIdV2(ix: TransactionInstruction) { 127 | ix.data = Buffer.concat([Buffer.from([3]), ix.data]); 128 | } 129 | settleFunds(ix: TransactionInstruction) { 130 | ix.data = Buffer.concat([Buffer.from([4]), ix.data]); 131 | } 132 | closeOpenOrders(ix: TransactionInstruction) { 133 | ix.data = Buffer.concat([Buffer.from([5]), ix.data]); 134 | } 135 | prune(ix: TransactionInstruction) { 136 | ix.data = Buffer.concat([Buffer.from([6]), ix.data]); 137 | } 138 | consumeEvents(ix: TransactionInstruction) { 139 | ix.data = Buffer.concat([Buffer.from([7]), ix.data]); 140 | } 141 | consumeEventsPermissioned(ix: TransactionInstruction) { 142 | ix.data = Buffer.concat([Buffer.from([8]), ix.data]); 143 | } 144 | } 145 | 146 | export class ReferralFees implements Middleware { 147 | // eslint-disable-next-line 148 | initOpenOrders(_ix: TransactionInstruction) {} 149 | // eslint-disable-next-line 150 | newOrderV3(_ix: TransactionInstruction) {} 151 | // eslint-disable-next-line 152 | cancelOrderV2(_ix: TransactionInstruction) {} 153 | // eslint-disable-next-line 154 | cancelOrderByClientIdV2(_ix: TransactionInstruction) {} 155 | // eslint-disable-next-line 156 | settleFunds(_ix: TransactionInstruction) {} 157 | // eslint-disable-next-line 158 | closeOpenOrders(_ix: TransactionInstruction) {} 159 | // eslint-disable-next-line 160 | prune(_ix: TransactionInstruction) {} 161 | // eslint-disable-next-line 162 | consumeEvents(_ix: TransactionInstruction) {} 163 | // eslint-disable-next-line 164 | consumeEventsPermissioned(_ix: TransactionInstruction) {} 165 | } 166 | 167 | export class PermissionedCrank implements Middleware { 168 | // eslint-disable-next-line 169 | initOpenOrders(_ix: TransactionInstruction) {} 170 | // eslint-disable-next-line 171 | newOrderV3(_ix: TransactionInstruction) {} 172 | // eslint-disable-next-line 173 | cancelOrderV2(_ix: TransactionInstruction) {} 174 | // eslint-disable-next-line 175 | cancelOrderByClientIdV2(_ix: TransactionInstruction) {} 176 | // eslint-disable-next-line 177 | settleFunds(_ix: TransactionInstruction) {} 178 | // eslint-disable-next-line 179 | closeOpenOrders(_ix: TransactionInstruction) {} 180 | // eslint-disable-next-line 181 | prune(_ix: TransactionInstruction) {} 182 | // eslint-disable-next-line 183 | consumeEvents(_ix: TransactionInstruction) {} 184 | // eslint-disable-next-line 185 | consumeEventsPermissioned(ix: TransactionInstruction) { 186 | ix.keys[ix.keys.length - 1].isSigner = false; 187 | } 188 | } 189 | 190 | export class Logger implements Middleware { 191 | initOpenOrders(ix: TransactionInstruction) { 192 | console.log('Proxying initOpenOrders', this.ixToDisplay(ix)); 193 | } 194 | newOrderV3(ix: TransactionInstruction) { 195 | console.log('Proxying newOrderV3', this.ixToDisplay(ix)); 196 | } 197 | cancelOrderV2(ix: TransactionInstruction) { 198 | console.log('Proxying cancelOrderV2', this.ixToDisplay(ix)); 199 | } 200 | cancelOrderByClientIdV2(ix: TransactionInstruction) { 201 | console.log('Proxying cancelOrderByClientIdV2', this.ixToDisplay(ix)); 202 | } 203 | settleFunds(ix: TransactionInstruction) { 204 | console.log('Proxying settleFunds', this.ixToDisplay(ix)); 205 | } 206 | closeOpenOrders(ix: TransactionInstruction) { 207 | console.log('Proxying closeOpenOrders', this.ixToDisplay(ix)); 208 | } 209 | prune(ix: TransactionInstruction) { 210 | console.log('Proxying prune', this.ixToDisplay(ix)); 211 | } 212 | consumeEvents(ix: TransactionInstruction) { 213 | console.log('Proxying consumeEvents', this.ixToDisplay(ix)); 214 | } 215 | consumeEventsPermissioned(ix: TransactionInstruction) { 216 | console.log('Proxying consumeEventsPermissioned', this.ixToDisplay(ix)); 217 | } 218 | ixToDisplay(ix: TransactionInstruction): any { 219 | const keys = ix.keys.map((i) => { 220 | return { ...i, pubkey: i.pubkey.toString() }; 221 | }); 222 | const programId = ix.programId.toString(); 223 | const data = new Uint8Array(ix.data); 224 | return { keys, programId, data }; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /packages/swap/src/idl.ts: -------------------------------------------------------------------------------- 1 | import { Idl } from '@project-serum/anchor'; 2 | 3 | // Idl for client generation. 4 | export const IDL: Idl = { 5 | version: '0.0.0', 6 | name: 'swap', 7 | instructions: [ 8 | { 9 | name: 'swap', 10 | accounts: [ 11 | { 12 | name: 'market', 13 | accounts: [ 14 | { 15 | name: 'market', 16 | isMut: true, 17 | isSigner: false, 18 | }, 19 | { 20 | name: 'openOrders', 21 | isMut: true, 22 | isSigner: false, 23 | }, 24 | { 25 | name: 'requestQueue', 26 | isMut: true, 27 | isSigner: false, 28 | }, 29 | { 30 | name: 'eventQueue', 31 | isMut: true, 32 | isSigner: false, 33 | }, 34 | { 35 | name: 'bids', 36 | isMut: true, 37 | isSigner: false, 38 | }, 39 | { 40 | name: 'asks', 41 | isMut: true, 42 | isSigner: false, 43 | }, 44 | { 45 | name: 'orderPayerTokenAccount', 46 | isMut: true, 47 | isSigner: false, 48 | }, 49 | { 50 | name: 'coinVault', 51 | isMut: true, 52 | isSigner: false, 53 | }, 54 | { 55 | name: 'pcVault', 56 | isMut: true, 57 | isSigner: false, 58 | }, 59 | { 60 | name: 'vaultSigner', 61 | isMut: false, 62 | isSigner: false, 63 | }, 64 | { 65 | name: 'coinWallet', 66 | isMut: true, 67 | isSigner: false, 68 | }, 69 | ], 70 | }, 71 | { 72 | name: 'authority', 73 | isMut: false, 74 | isSigner: true, 75 | }, 76 | { 77 | name: 'pcWallet', 78 | isMut: true, 79 | isSigner: false, 80 | }, 81 | { 82 | name: 'dexProgram', 83 | isMut: false, 84 | isSigner: false, 85 | }, 86 | { 87 | name: 'tokenProgram', 88 | isMut: false, 89 | isSigner: false, 90 | }, 91 | { 92 | name: 'rent', 93 | isMut: false, 94 | isSigner: false, 95 | }, 96 | ], 97 | args: [ 98 | { 99 | name: 'side', 100 | type: { 101 | defined: 'Side', 102 | }, 103 | }, 104 | { 105 | name: 'amount', 106 | type: 'u64', 107 | }, 108 | { 109 | name: 'minExpectedSwapAmount', 110 | type: 'u64', 111 | }, 112 | ], 113 | }, 114 | { 115 | name: 'swapTransitive', 116 | accounts: [ 117 | { 118 | name: 'from', 119 | accounts: [ 120 | { 121 | name: 'market', 122 | isMut: true, 123 | isSigner: false, 124 | }, 125 | { 126 | name: 'openOrders', 127 | isMut: true, 128 | isSigner: false, 129 | }, 130 | { 131 | name: 'requestQueue', 132 | isMut: true, 133 | isSigner: false, 134 | }, 135 | { 136 | name: 'eventQueue', 137 | isMut: true, 138 | isSigner: false, 139 | }, 140 | { 141 | name: 'bids', 142 | isMut: true, 143 | isSigner: false, 144 | }, 145 | { 146 | name: 'asks', 147 | isMut: true, 148 | isSigner: false, 149 | }, 150 | { 151 | name: 'orderPayerTokenAccount', 152 | isMut: true, 153 | isSigner: false, 154 | }, 155 | { 156 | name: 'coinVault', 157 | isMut: true, 158 | isSigner: false, 159 | }, 160 | { 161 | name: 'pcVault', 162 | isMut: true, 163 | isSigner: false, 164 | }, 165 | { 166 | name: 'vaultSigner', 167 | isMut: false, 168 | isSigner: false, 169 | }, 170 | { 171 | name: 'coinWallet', 172 | isMut: true, 173 | isSigner: false, 174 | }, 175 | ], 176 | }, 177 | { 178 | name: 'to', 179 | accounts: [ 180 | { 181 | name: 'market', 182 | isMut: true, 183 | isSigner: false, 184 | }, 185 | { 186 | name: 'openOrders', 187 | isMut: true, 188 | isSigner: false, 189 | }, 190 | { 191 | name: 'requestQueue', 192 | isMut: true, 193 | isSigner: false, 194 | }, 195 | { 196 | name: 'eventQueue', 197 | isMut: true, 198 | isSigner: false, 199 | }, 200 | { 201 | name: 'bids', 202 | isMut: true, 203 | isSigner: false, 204 | }, 205 | { 206 | name: 'asks', 207 | isMut: true, 208 | isSigner: false, 209 | }, 210 | { 211 | name: 'orderPayerTokenAccount', 212 | isMut: true, 213 | isSigner: false, 214 | }, 215 | { 216 | name: 'coinVault', 217 | isMut: true, 218 | isSigner: false, 219 | }, 220 | { 221 | name: 'pcVault', 222 | isMut: true, 223 | isSigner: false, 224 | }, 225 | { 226 | name: 'vaultSigner', 227 | isMut: false, 228 | isSigner: false, 229 | }, 230 | { 231 | name: 'coinWallet', 232 | isMut: true, 233 | isSigner: false, 234 | }, 235 | ], 236 | }, 237 | { 238 | name: 'authority', 239 | isMut: false, 240 | isSigner: true, 241 | }, 242 | { 243 | name: 'pcWallet', 244 | isMut: true, 245 | isSigner: false, 246 | }, 247 | { 248 | name: 'dexProgram', 249 | isMut: false, 250 | isSigner: false, 251 | }, 252 | { 253 | name: 'tokenProgram', 254 | isMut: false, 255 | isSigner: false, 256 | }, 257 | { 258 | name: 'rent', 259 | isMut: false, 260 | isSigner: false, 261 | }, 262 | ], 263 | args: [ 264 | { 265 | name: 'amount', 266 | type: 'u64', 267 | }, 268 | { 269 | name: 'minExpectedSwapAmount', 270 | type: 'u64', 271 | }, 272 | ], 273 | }, 274 | ], 275 | types: [ 276 | { 277 | name: 'Side', 278 | type: { 279 | kind: 'enum', 280 | variants: [ 281 | { 282 | name: 'Bid', 283 | }, 284 | { 285 | name: 'Ask', 286 | }, 287 | ], 288 | }, 289 | }, 290 | ], 291 | events: [ 292 | { 293 | name: 'DidSwap', 294 | fields: [ 295 | { 296 | name: 'given_amount', 297 | type: 'u64', 298 | index: false, 299 | }, 300 | { 301 | name: 'min_expected_swap_amount', 302 | type: 'u64', 303 | index: false, 304 | }, 305 | { 306 | name: 'from_amount', 307 | type: 'u64', 308 | index: false, 309 | }, 310 | { 311 | name: 'to_amount', 312 | type: 'u64', 313 | index: false, 314 | }, 315 | { 316 | name: 'spill_amount', 317 | type: 'u64', 318 | index: false, 319 | }, 320 | { 321 | name: 'from_mint', 322 | type: 'publicKey', 323 | index: false, 324 | }, 325 | { 326 | name: 'to_mint', 327 | type: 'publicKey', 328 | index: false, 329 | }, 330 | { 331 | name: 'quote_mint', 332 | type: 'publicKey', 333 | index: false, 334 | }, 335 | { 336 | name: 'authority', 337 | type: 'publicKey', 338 | index: false, 339 | }, 340 | ], 341 | }, 342 | ], 343 | errors: [ 344 | { 345 | code: 100, 346 | name: 'SwapTokensCannotMatch', 347 | msg: 'The tokens being swapped must have different mints', 348 | }, 349 | { 350 | code: 101, 351 | name: 'SlippageExceeded', 352 | msg: 'Slippage tolerance exceeded', 353 | }, 354 | ], 355 | }; 356 | -------------------------------------------------------------------------------- /packages/borsh/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | blob, 3 | Layout as LayoutCls, 4 | offset, 5 | seq, 6 | struct, 7 | u32, 8 | u8, 9 | union, 10 | } from 'buffer-layout'; 11 | import { PublicKey } from '@solana/web3.js'; 12 | import BN from 'bn.js'; 13 | export { 14 | u8, 15 | s8 as i8, 16 | u16, 17 | s16 as i16, 18 | u32, 19 | s32 as i32, 20 | f32, 21 | f64, 22 | struct, 23 | } from 'buffer-layout'; 24 | 25 | export interface Layout { 26 | span: number; 27 | property?: string; 28 | 29 | decode(b: Buffer, offset?: number): T; 30 | 31 | encode(src: T, b: Buffer, offset?: number): number; 32 | 33 | getSpan(b: Buffer, offset?: number): number; 34 | 35 | replicate(name: string): this; 36 | } 37 | 38 | class BNLayout extends LayoutCls { 39 | blob: Layout; 40 | signed: boolean; 41 | 42 | constructor(span: number, signed: boolean, property?: string) { 43 | super(span, property); 44 | this.blob = blob(span); 45 | this.signed = signed; 46 | } 47 | 48 | decode(b: Buffer, offset = 0) { 49 | const num = new BN(this.blob.decode(b, offset), 10, 'le'); 50 | if (this.signed) { 51 | return num.fromTwos(this.span * 8).clone(); 52 | } 53 | return num; 54 | } 55 | 56 | encode(src: BN, b: Buffer, offset = 0) { 57 | if (this.signed) { 58 | src = src.toTwos(this.span * 8); 59 | } 60 | return this.blob.encode( 61 | src.toArrayLike(Buffer, 'le', this.span), 62 | b, 63 | offset, 64 | ); 65 | } 66 | } 67 | 68 | export function u64(property?: string): Layout { 69 | return new BNLayout(8, false, property); 70 | } 71 | 72 | export function i64(property?: string): Layout { 73 | return new BNLayout(8, true, property); 74 | } 75 | 76 | export function u128(property?: string): Layout { 77 | return new BNLayout(16, false, property); 78 | } 79 | 80 | export function i128(property?: string): Layout { 81 | return new BNLayout(16, true, property); 82 | } 83 | 84 | class WrappedLayout extends LayoutCls { 85 | layout: Layout; 86 | decoder: (data: T) => U; 87 | encoder: (src: U) => T; 88 | 89 | constructor( 90 | layout: Layout, 91 | decoder: (data: T) => U, 92 | encoder: (src: U) => T, 93 | property?: string, 94 | ) { 95 | super(layout.span, property); 96 | this.layout = layout; 97 | this.decoder = decoder; 98 | this.encoder = encoder; 99 | } 100 | 101 | decode(b: Buffer, offset?: number): U { 102 | return this.decoder(this.layout.decode(b, offset)); 103 | } 104 | 105 | encode(src: U, b: Buffer, offset?: number): number { 106 | return this.layout.encode(this.encoder(src), b, offset); 107 | } 108 | 109 | getSpan(b: Buffer, offset?: number): number { 110 | return this.layout.getSpan(b, offset); 111 | } 112 | } 113 | 114 | export function publicKey(property?: string): Layout { 115 | return new WrappedLayout( 116 | blob(32), 117 | (b: Buffer) => new PublicKey(b), 118 | (key: PublicKey) => key.toBuffer(), 119 | property, 120 | ); 121 | } 122 | 123 | class OptionLayout extends LayoutCls { 124 | layout: Layout; 125 | discriminator: Layout; 126 | 127 | constructor(layout: Layout, property?: string) { 128 | super(-1, property); 129 | this.layout = layout; 130 | this.discriminator = u8(); 131 | } 132 | 133 | encode(src: T | null, b: Buffer, offset = 0): number { 134 | if (src === null || src === undefined) { 135 | return this.discriminator.encode(0, b, offset); 136 | } 137 | this.discriminator.encode(1, b, offset); 138 | return this.layout.encode(src, b, offset + 1) + 1; 139 | } 140 | 141 | decode(b: Buffer, offset = 0): T | null { 142 | const discriminator = this.discriminator.decode(b, offset); 143 | if (discriminator === 0) { 144 | return null; 145 | } else if (discriminator === 1) { 146 | return this.layout.decode(b, offset + 1); 147 | } 148 | throw new Error('Invalid option ' + this.property); 149 | } 150 | 151 | getSpan(b: Buffer, offset = 0): number { 152 | const discriminator = this.discriminator.decode(b, offset); 153 | if (discriminator === 0) { 154 | return 1; 155 | } else if (discriminator === 1) { 156 | return this.layout.getSpan(b, offset + 1) + 1; 157 | } 158 | throw new Error('Invalid option ' + this.property); 159 | } 160 | } 161 | 162 | export function option( 163 | layout: Layout, 164 | property?: string, 165 | ): Layout { 166 | return new OptionLayout(layout, property); 167 | } 168 | 169 | export function bool(property?: string): Layout { 170 | return new WrappedLayout(u8(), decodeBool, encodeBool, property); 171 | } 172 | 173 | function decodeBool(value: number): boolean { 174 | if (value === 0) { 175 | return false; 176 | } else if (value === 1) { 177 | return true; 178 | } 179 | throw new Error('Invalid bool: ' + value); 180 | } 181 | 182 | function encodeBool(value: boolean): number { 183 | return value ? 1 : 0; 184 | } 185 | 186 | export function vec( 187 | elementLayout: Layout, 188 | property?: string, 189 | ): Layout { 190 | const length = u32('length'); 191 | const layout: Layout<{ values: T[] }> = struct([ 192 | length, 193 | seq(elementLayout, offset(length, -length.span), 'values'), 194 | ]); 195 | return new WrappedLayout( 196 | layout, 197 | ({ values }) => values, 198 | values => ({ values }), 199 | property, 200 | ); 201 | } 202 | 203 | export function tagged( 204 | tag: BN, 205 | layout: Layout, 206 | property?: string, 207 | ): Layout { 208 | const wrappedLayout: Layout<{ tag: BN; data: T }> = struct([ 209 | u64('tag'), 210 | layout.replicate('data'), 211 | ]); 212 | 213 | function decodeTag({ tag: receivedTag, data }: { tag: BN; data: T }) { 214 | if (!receivedTag.eq(tag)) { 215 | throw new Error( 216 | 'Invalid tag, expected: ' + 217 | tag.toString('hex') + 218 | ', got: ' + 219 | receivedTag.toString('hex'), 220 | ); 221 | } 222 | return data; 223 | } 224 | 225 | return new WrappedLayout( 226 | wrappedLayout, 227 | decodeTag, 228 | data => ({ tag, data }), 229 | property, 230 | ); 231 | } 232 | 233 | export function vecU8(property?: string): Layout { 234 | const length = u32('length'); 235 | const layout: Layout<{ data: Buffer }> = struct([ 236 | length, 237 | blob(offset(length, -length.span), 'data'), 238 | ]); 239 | return new WrappedLayout( 240 | layout, 241 | ({ data }) => data, 242 | data => ({ data }), 243 | property, 244 | ); 245 | } 246 | 247 | export function str(property?: string): Layout { 248 | return new WrappedLayout( 249 | vecU8(), 250 | data => data.toString('utf-8'), 251 | s => Buffer.from(s, 'utf-8'), 252 | property, 253 | ); 254 | } 255 | 256 | export interface EnumLayout extends Layout { 257 | registry: Record>; 258 | } 259 | 260 | export function rustEnum( 261 | variants: Layout[], 262 | property?: string, 263 | discriminant?: Layout, 264 | ): EnumLayout { 265 | const unionLayout = union(discriminant ?? u8(), property); 266 | variants.forEach((variant, index) => 267 | unionLayout.addVariant(index, variant, variant.property), 268 | ); 269 | return unionLayout; 270 | } 271 | 272 | export function array( 273 | elementLayout: Layout, 274 | length: number, 275 | property?: string, 276 | ): Layout { 277 | const layout: Layout<{ values: T[] }> = struct([ 278 | seq(elementLayout, length, 'values'), 279 | ]); 280 | return new WrappedLayout( 281 | layout, 282 | ({ values }) => values, 283 | values => ({ values }), 284 | property, 285 | ); 286 | } 287 | 288 | class MapEntryLayout extends LayoutCls<[K, V]> { 289 | keyLayout: Layout; 290 | valueLayout: Layout; 291 | 292 | constructor(keyLayout: Layout, valueLayout: Layout, property?: string) { 293 | super(keyLayout.span + valueLayout.span, property); 294 | this.keyLayout = keyLayout; 295 | this.valueLayout = valueLayout; 296 | } 297 | 298 | decode(b: Buffer, offset?: number): [K, V] { 299 | offset = offset || 0; 300 | const key = this.keyLayout.decode(b, offset); 301 | const value = this.valueLayout.decode( 302 | b, 303 | offset + this.keyLayout.getSpan(b, offset), 304 | ); 305 | return [key, value]; 306 | } 307 | 308 | encode(src: [K, V], b: Buffer, offset?: number): number { 309 | offset = offset || 0; 310 | const keyBytes = this.keyLayout.encode(src[0], b, offset); 311 | const valueBytes = this.valueLayout.encode(src[1], b, offset + keyBytes); 312 | return keyBytes + valueBytes; 313 | } 314 | 315 | getSpan(b: Buffer, offset?: number): number { 316 | return ( 317 | this.keyLayout.getSpan(b, offset) + this.valueLayout.getSpan(b, offset) 318 | ); 319 | } 320 | } 321 | 322 | export function map( 323 | keyLayout: Layout, 324 | valueLayout: Layout, 325 | property?: string, 326 | ): Layout> { 327 | const length = u32('length'); 328 | const layout: Layout<{ values: [K, V][] }> = struct([ 329 | length, 330 | seq( 331 | new MapEntryLayout(keyLayout, valueLayout), 332 | offset(length, -length.span), 333 | 'values', 334 | ), 335 | ]); 336 | return new WrappedLayout( 337 | layout, 338 | ({ values }) => new Map(values), 339 | values => ({ values: Array.from(values.entries()) }), 340 | property, 341 | ); 342 | } 343 | -------------------------------------------------------------------------------- /packages/openbook/src/market-proxy/index.ts: -------------------------------------------------------------------------------- 1 | import BN from 'bn.js'; 2 | import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js'; 3 | import { utils } from '@project-serum/anchor'; 4 | import { 5 | Market, 6 | MarketOptions, 7 | OrderParams, 8 | MARKET_STATE_LAYOUT_V3, 9 | Order, 10 | } from '../market'; 11 | import { DexInstructions } from '../instructions'; 12 | import { Middleware } from './middleware'; 13 | 14 | // MarketProxy provides an API for constructing transactions to an on-chain 15 | // DEX proxy, which relays all instructions to the orderbook. Minimally, this 16 | // requires two modifications for DEX instructions. 17 | // 18 | // 1. Transasctions are sent to the proxy program--not the DEX. 19 | // 2. The DEX program ID must be inserted as the first account in instructions 20 | // using the proxy relay, so that the proxy can use the account for CPI. 21 | // The program is responsible for removing this account before relaying to 22 | // the dex. 23 | // 24 | // Additionally, a middleware abstraction is provided so that one can configure 25 | // both the client and the smart contract with the ability to send and processs 26 | // arbitrary accounts and instruction data *in addition* to what the Serum DEX 27 | // expects. 28 | // 29 | // Similar to the layers of an onion, each middleware wraps a transaction 30 | // request with additional accounts and instruction data before sending it to 31 | // the program. Upon receiving the request, the program--with its own set of 32 | // middleware-- unwraps and processes each layer. The process ends with all 33 | // layers being unwrapped and the proxy relaying the transaction to the DEX. 34 | // 35 | // As a result, the order of the middleware matters and the client should 36 | // process middleware in the *reverse* order of the proxy smart contract. 37 | export class MarketProxy { 38 | // DEX market being proxied. 39 | get market(): Market { 40 | return this._market; 41 | } 42 | private _market: Market; 43 | 44 | // Instruction namespace. 45 | get instruction(): MarketProxyInstruction { 46 | return this._instruction; 47 | } 48 | private _instruction: MarketProxyInstruction; 49 | 50 | // Serum DEX program ID. 51 | get dexProgramId(): PublicKey { 52 | return this._market.programId; 53 | } 54 | 55 | // Proxy program ID. 56 | get proxyProgramId(): PublicKey { 57 | return this._instruction.proxyProgramId; 58 | } 59 | 60 | // Ctor. 61 | constructor(market: Market, instruction: MarketProxyInstruction) { 62 | this._market = market; 63 | this._instruction = instruction; 64 | } 65 | } 66 | 67 | // Instruction builder for the market proxy. 68 | export class MarketProxyInstruction { 69 | // Program ID of the permissioning proxy program. 70 | get proxyProgramId(): PublicKey { 71 | return this._proxyProgramId; 72 | } 73 | private _proxyProgramId: PublicKey; 74 | 75 | // Dex program ID. 76 | private _dexProgramId: PublicKey; 77 | 78 | // Underlying DEX market. 79 | private _market: Market; 80 | 81 | // Middlewares for processing the creation of transactions. 82 | private _middlewares: Middleware[]; 83 | 84 | constructor( 85 | proxyProgramId: PublicKey, 86 | dexProgramId: PublicKey, 87 | market: Market, 88 | middlewares: Middleware[], 89 | ) { 90 | this._proxyProgramId = proxyProgramId; 91 | this._dexProgramId = dexProgramId; 92 | this._market = market; 93 | this._middlewares = middlewares; 94 | } 95 | 96 | public newOrderV3(params: OrderParams): TransactionInstruction { 97 | const tradeIx = this._market.makeNewOrderV3Instruction({ 98 | ...params, 99 | programId: this._proxyProgramId, 100 | }); 101 | this._middlewares.forEach((mw) => mw.newOrderV3(tradeIx)); 102 | return this.proxy(tradeIx); 103 | } 104 | 105 | public initOpenOrders( 106 | owner: PublicKey, 107 | market: PublicKey, 108 | openOrders: PublicKey, 109 | marketAuthority: PublicKey, 110 | ): TransactionInstruction { 111 | const ix = DexInstructions.initOpenOrders({ 112 | market, 113 | openOrders, 114 | owner, 115 | programId: this._proxyProgramId, 116 | marketAuthority, 117 | }); 118 | this._middlewares.forEach((mw) => mw.initOpenOrders(ix)); 119 | return this.proxy(ix); 120 | } 121 | 122 | public cancelOrder(owner: PublicKey, order: Order): TransactionInstruction { 123 | const ix = DexInstructions.cancelOrderV2({ 124 | market: this._market.address, 125 | owner, 126 | openOrders: order.openOrdersAddress, 127 | bids: this._market.decoded.bids, 128 | asks: this._market.decoded.asks, 129 | eventQueue: this._market.decoded.eventQueue, 130 | side: order.side, 131 | orderId: order.orderId, 132 | openOrdersSlot: order.openOrdersSlot, 133 | programId: this._proxyProgramId, 134 | }); 135 | this._middlewares.forEach((mw) => mw.cancelOrderV2(ix)); 136 | return this.proxy(ix); 137 | } 138 | 139 | public cancelOrderByClientId( 140 | owner: PublicKey, 141 | openOrders: PublicKey, 142 | clientId: BN, 143 | ): TransactionInstruction { 144 | const ix = DexInstructions.cancelOrderByClientIdV2({ 145 | market: this._market.address, 146 | openOrders, 147 | owner, 148 | bids: this._market.decoded.bids, 149 | asks: this._market.decoded.asks, 150 | eventQueue: this._market.decoded.eventQueue, 151 | clientId, 152 | programId: this._proxyProgramId, 153 | }); 154 | this._middlewares.forEach((mw) => mw.cancelOrderByClientIdV2(ix)); 155 | return this.proxy(ix); 156 | } 157 | 158 | public settleFunds( 159 | openOrders: PublicKey, 160 | owner: PublicKey, 161 | baseWallet: PublicKey, 162 | quoteWallet: PublicKey, 163 | referrerQuoteWallet: PublicKey, 164 | ): TransactionInstruction { 165 | const ix = DexInstructions.settleFunds({ 166 | market: this._market.address, 167 | openOrders, 168 | owner, 169 | baseVault: this._market.decoded.baseVault, 170 | quoteVault: this._market.decoded.quoteVault, 171 | baseWallet, 172 | quoteWallet, 173 | vaultSigner: utils.publicKey.createProgramAddressSync( 174 | [ 175 | this._market.address.toBuffer(), 176 | this._market.decoded.vaultSignerNonce.toArrayLike(Buffer, 'le', 8), 177 | ], 178 | this._dexProgramId, 179 | ), 180 | programId: this._proxyProgramId, 181 | referrerQuoteWallet: referrerQuoteWallet as any, 182 | }); 183 | this._middlewares.forEach((mw) => mw.settleFunds(ix)); 184 | return this.proxy(ix); 185 | } 186 | 187 | public closeOpenOrders( 188 | openOrders: PublicKey, 189 | owner: PublicKey, 190 | solWallet: PublicKey, 191 | ): TransactionInstruction { 192 | const ix = DexInstructions.closeOpenOrders({ 193 | market: this._market.address, 194 | openOrders, 195 | owner, 196 | solWallet, 197 | programId: this._proxyProgramId, 198 | }); 199 | this._middlewares.forEach((mw) => mw.closeOpenOrders(ix)); 200 | return this.proxy(ix); 201 | } 202 | 203 | public prune( 204 | openOrders: PublicKey, 205 | openOrdersOwner: PublicKey, 206 | limit?: number, 207 | ): TransactionInstruction { 208 | if (!limit) { 209 | limit = 65535; 210 | } 211 | const ix = DexInstructions.prune({ 212 | market: this._market.address, 213 | bids: this._market.decoded.bids, 214 | asks: this._market.decoded.asks, 215 | eventQueue: this._market.decoded.eventQueue, 216 | pruneAuthority: this._market.decoded.pruneAuthority, 217 | openOrders, 218 | openOrdersOwner, 219 | programId: this._proxyProgramId, 220 | limit, 221 | }); 222 | this._middlewares.forEach((mw) => mw.prune(ix)); 223 | return this.proxy(ix); 224 | } 225 | 226 | public consumeEvents( 227 | openOrdersAccounts: Array, 228 | limit: number, 229 | ): TransactionInstruction { 230 | const ix = DexInstructions.consumeEvents({ 231 | market: this._market.address, 232 | eventQueue: this._market.decoded.eventQueue, 233 | coinFee: this._market.decoded.eventQueue, 234 | pcFee: this._market.decoded.eventQueue, 235 | openOrdersAccounts, 236 | limit, 237 | programId: this._proxyProgramId, 238 | }); 239 | this._middlewares.forEach((mw) => mw.consumeEvents(ix)); 240 | return this.proxy(ix); 241 | } 242 | 243 | public consumeEventsPermissioned( 244 | openOrdersAccounts: Array, 245 | limit: number, 246 | ): TransactionInstruction { 247 | const ix = DexInstructions.consumeEventsPermissioned({ 248 | market: this._market.address, 249 | eventQueue: this._market.decoded.eventQueue, 250 | crankAuthority: this._market.decoded.consumeEventsAuthority, 251 | openOrdersAccounts, 252 | limit, 253 | programId: this._proxyProgramId, 254 | }); 255 | this._middlewares.forEach((mw) => mw.consumeEventsPermissioned(ix)); 256 | return this.proxy(ix); 257 | } 258 | 259 | // Adds the serum dex account to the instruction so that proxies can 260 | // relay (CPI requires the executable account). 261 | private proxy(ix: TransactionInstruction) { 262 | ix.keys = [ 263 | { pubkey: this._dexProgramId, isWritable: false, isSigner: false }, 264 | ...ix.keys, 265 | ]; 266 | 267 | return ix; 268 | } 269 | } 270 | 271 | export class MarketProxyBuilder { 272 | private _middlewares: Middleware[]; 273 | 274 | constructor() { 275 | this._middlewares = []; 276 | } 277 | 278 | public middleware(mw: Middleware): MarketProxyBuilder { 279 | this._middlewares.push(mw); 280 | return this; 281 | } 282 | 283 | public async load({ 284 | connection, 285 | market, 286 | options = {}, 287 | dexProgramId, 288 | proxyProgramId, 289 | }: { 290 | connection: Connection; 291 | market: PublicKey; 292 | options: MarketOptions; 293 | dexProgramId: PublicKey; 294 | proxyProgramId: PublicKey; 295 | }): Promise { 296 | const marketClient = await Market.load( 297 | connection, 298 | market, 299 | options, 300 | dexProgramId, 301 | MARKET_STATE_LAYOUT_V3, 302 | ); 303 | const instruction = new MarketProxyInstruction( 304 | proxyProgramId, 305 | dexProgramId, 306 | marketClient, 307 | this._middlewares, 308 | ); 309 | return new MarketProxy(marketClient, instruction); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Serum Foundation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /packages/token/src/instructions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PublicKey, 3 | SYSVAR_RENT_PUBKEY, 4 | TransactionInstruction, 5 | } from '@solana/web3.js'; 6 | import BN from 'bn.js'; 7 | import { 8 | option, 9 | publicKey, 10 | rustEnum, 11 | u64, 12 | struct, 13 | u8, 14 | EnumLayout, 15 | } from '@openbook-dex/borsh'; 16 | 17 | export const TOKEN_PROGRAM_ID = new PublicKey( 18 | 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 19 | ); 20 | 21 | export const WRAPPED_SOL_MINT = new PublicKey( 22 | 'So11111111111111111111111111111111111111112', 23 | ); 24 | 25 | export type TokenInstructionLayout = 26 | | { 27 | initializeMint: { 28 | decimals: number; 29 | mintAuthority: PublicKey; 30 | freezeAuthority: PublicKey | null; 31 | }; 32 | } 33 | | { initializeAccount: any } 34 | | { initializeMultisig: { m: number } } 35 | | { transfer: { amount: BN } } 36 | | { approve: { amount: BN } } 37 | | { revoke: any } 38 | | { setAuthority: { authorityType: number; newAuthority: PublicKey | null } } 39 | | { mintTo: { amount: BN } } 40 | | { burn: { amount: BN } } 41 | | { closeAccount: any } 42 | | { freezeAccount: any } 43 | | { thawAccount: any } 44 | | { transferChecked: { amount: BN; decimals: number } } 45 | | { approveChecked: { amount: BN; decimals: number } } 46 | | { mintToChecked: { amount: BN; decimals: number } } 47 | | { burnChecked: { amount: BN; decimals: number } }; 48 | 49 | export const TokenInstructionLayout: EnumLayout = 50 | rustEnum([ 51 | struct( 52 | [ 53 | u8('decimals'), 54 | publicKey('mintAuthority'), 55 | option(publicKey(), 'freezeAuthority'), 56 | ], 57 | 'initializeMint', 58 | ), 59 | struct([], 'initializeAccount'), 60 | struct([u8('m')], 'initializeMultisig'), 61 | struct([u64('amount')], 'transfer'), 62 | struct([u64('amount')], 'approve'), 63 | struct([], 'revoke'), 64 | struct( 65 | [u8('authorityType'), option(publicKey(), 'newAuthority')], 66 | 'setAuthority', 67 | ), 68 | struct([u64('amount')], 'mintTo'), 69 | struct([u64('amount')], 'burn'), 70 | struct([], 'closeAccount'), 71 | struct([], 'freezeAccount'), 72 | struct([], 'thawAccount'), 73 | struct([u64('amount'), u8('decimals')], 'transferChecked'), 74 | struct([u64('amount'), u8('decimals')], 'approveChecked'), 75 | struct([u64('amount'), u8('decimals')], 'mintToChecked'), 76 | struct([u64('amount'), u8('decimals')], 'burnChecked'), 77 | ]); 78 | 79 | const instructionMaxSpan = Math.max( 80 | ...Object.values(TokenInstructionLayout.registry).map(r => r.span), 81 | ); 82 | 83 | function encodeTokenInstructionData(instruction: TokenInstructionLayout) { 84 | const b = Buffer.alloc(instructionMaxSpan); 85 | const span = TokenInstructionLayout.encode(instruction, b); 86 | return b.slice(0, span); 87 | } 88 | 89 | function decodeTokenInstructionData(data: Buffer) { 90 | return TokenInstructionLayout.decode(data); 91 | } 92 | 93 | export interface InitializeMintParams { 94 | mint: PublicKey; 95 | decimals: number; 96 | mintAuthority: PublicKey; 97 | freezeAuthority?: PublicKey | null; 98 | } 99 | 100 | export interface InitializeAccountParams { 101 | account: PublicKey; 102 | mint: PublicKey; 103 | owner: PublicKey; 104 | } 105 | 106 | export interface TransferParams { 107 | source: PublicKey; 108 | destination: PublicKey; 109 | amount: BN; 110 | owner: PublicKey; 111 | } 112 | 113 | export interface ApproveParams { 114 | source: PublicKey; 115 | delegate: PublicKey; 116 | amount: BN; 117 | owner: PublicKey; 118 | } 119 | 120 | export interface RevokeParams { 121 | source: PublicKey; 122 | owner: PublicKey; 123 | } 124 | 125 | export interface SetAuthorityParams { 126 | target: PublicKey; 127 | currentAuthority: PublicKey; 128 | newAuthority?: PublicKey | null; 129 | authorityType: number; 130 | } 131 | 132 | export interface MintToParams { 133 | mint: PublicKey; 134 | destination: PublicKey; 135 | amount: BN; 136 | mintAuthority: PublicKey; 137 | } 138 | 139 | export interface BurnParams { 140 | source: PublicKey; 141 | mint: PublicKey; 142 | amount: BN; 143 | owner: PublicKey; 144 | } 145 | 146 | export interface CloseAccountParams { 147 | source: PublicKey; 148 | destination: PublicKey; 149 | owner: PublicKey; 150 | } 151 | 152 | export class TokenInstructions { 153 | static initializeMint({ 154 | mint, 155 | decimals, 156 | mintAuthority, 157 | freezeAuthority, 158 | }: InitializeMintParams): TransactionInstruction { 159 | const keys = [ 160 | { pubkey: mint, isSigner: false, isWritable: true }, 161 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 162 | ]; 163 | return new TransactionInstruction({ 164 | keys, 165 | data: encodeTokenInstructionData({ 166 | initializeMint: { 167 | decimals, 168 | mintAuthority, 169 | freezeAuthority: freezeAuthority ?? null, 170 | }, 171 | }), 172 | programId: TOKEN_PROGRAM_ID, 173 | }); 174 | } 175 | 176 | static initializeAccount({ 177 | account, 178 | mint, 179 | owner, 180 | }: InitializeAccountParams): TransactionInstruction { 181 | const keys = [ 182 | { pubkey: account, isSigner: false, isWritable: true }, 183 | { pubkey: mint, isSigner: false, isWritable: false }, 184 | { pubkey: owner, isSigner: false, isWritable: false }, 185 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 186 | ]; 187 | return new TransactionInstruction({ 188 | keys, 189 | data: encodeTokenInstructionData({ 190 | initializeAccount: {}, 191 | }), 192 | programId: TOKEN_PROGRAM_ID, 193 | }); 194 | } 195 | 196 | static transfer({ 197 | source, 198 | destination, 199 | amount, 200 | owner, 201 | }: TransferParams): TransactionInstruction { 202 | const keys = [ 203 | { pubkey: source, isSigner: false, isWritable: true }, 204 | { pubkey: destination, isSigner: false, isWritable: true }, 205 | { pubkey: owner, isSigner: true, isWritable: false }, 206 | ]; 207 | return new TransactionInstruction({ 208 | keys, 209 | data: encodeTokenInstructionData({ 210 | transfer: { 211 | amount, 212 | }, 213 | }), 214 | programId: TOKEN_PROGRAM_ID, 215 | }); 216 | } 217 | 218 | static approve({ 219 | source, 220 | delegate, 221 | amount, 222 | owner, 223 | }: ApproveParams): TransactionInstruction { 224 | const keys = [ 225 | { pubkey: source, isSigner: false, isWritable: true }, 226 | { pubkey: delegate, isSigner: false, isWritable: false }, 227 | { pubkey: owner, isSigner: true, isWritable: false }, 228 | ]; 229 | return new TransactionInstruction({ 230 | keys, 231 | data: encodeTokenInstructionData({ 232 | approve: { amount }, 233 | }), 234 | programId: TOKEN_PROGRAM_ID, 235 | }); 236 | } 237 | 238 | static revoke({ source, owner }: RevokeParams): TransactionInstruction { 239 | const keys = [ 240 | { pubkey: source, isSigner: false, isWritable: true }, 241 | { pubkey: owner, isSigner: true, isWritable: false }, 242 | ]; 243 | return new TransactionInstruction({ 244 | keys, 245 | data: encodeTokenInstructionData({ 246 | revoke: {}, 247 | }), 248 | programId: TOKEN_PROGRAM_ID, 249 | }); 250 | } 251 | 252 | static setAuthority({ 253 | target, 254 | currentAuthority, 255 | newAuthority, 256 | authorityType, 257 | }: SetAuthorityParams): TransactionInstruction { 258 | const keys = [ 259 | { pubkey: target, isSigner: false, isWritable: true }, 260 | { pubkey: currentAuthority, isSigner: true, isWritable: false }, 261 | ]; 262 | return new TransactionInstruction({ 263 | keys, 264 | data: encodeTokenInstructionData({ 265 | setAuthority: { 266 | authorityType, 267 | newAuthority: newAuthority ?? null, 268 | }, 269 | }), 270 | programId: TOKEN_PROGRAM_ID, 271 | }); 272 | } 273 | 274 | static mintTo({ 275 | mint, 276 | destination, 277 | amount, 278 | mintAuthority, 279 | }: MintToParams): TransactionInstruction { 280 | const keys = [ 281 | { pubkey: mint, isSigner: false, isWritable: true }, 282 | { pubkey: destination, isSigner: false, isWritable: true }, 283 | { pubkey: mintAuthority, isSigner: true, isWritable: false }, 284 | ]; 285 | return new TransactionInstruction({ 286 | keys, 287 | data: encodeTokenInstructionData({ 288 | mintTo: { 289 | amount, 290 | }, 291 | }), 292 | programId: TOKEN_PROGRAM_ID, 293 | }); 294 | } 295 | 296 | static closeAccount({ 297 | source, 298 | destination, 299 | owner, 300 | }: CloseAccountParams): TransactionInstruction { 301 | const keys = [ 302 | { pubkey: source, isSigner: false, isWritable: true }, 303 | { pubkey: destination, isSigner: false, isWritable: true }, 304 | { pubkey: owner, isSigner: true, isWritable: false }, 305 | ]; 306 | return new TransactionInstruction({ 307 | keys, 308 | data: encodeTokenInstructionData({ 309 | closeAccount: {}, 310 | }), 311 | programId: TOKEN_PROGRAM_ID, 312 | }); 313 | } 314 | } 315 | 316 | export type TokenInstruction = 317 | | { type: 'initializeMint'; params: InitializeMintParams } 318 | | { type: 'initializeAccount'; params: InitializeAccountParams } 319 | | { type: 'transfer'; params: TransferParams } 320 | | { type: 'approve'; params: ApproveParams } 321 | | { type: 'revoke'; params: RevokeParams } 322 | | { type: 'setAuthority'; params: SetAuthorityParams } 323 | | { type: 'mintTo'; params: MintToParams } 324 | | { type: 'burn'; params: BurnParams } 325 | | { type: 'closeAccount'; params: CloseAccountParams }; 326 | 327 | export function decodeTokenInstruction( 328 | instruction: TransactionInstruction, 329 | ): TokenInstruction { 330 | const data = decodeTokenInstructionData(instruction.data); 331 | if ('initializeMint' in data) { 332 | const type = 'initializeMint'; 333 | const params: InitializeMintParams = { 334 | decimals: data.initializeMint.decimals, 335 | mint: instruction.keys[0].pubkey, 336 | mintAuthority: data.initializeMint.mintAuthority, 337 | freezeAuthority: data.initializeMint.freezeAuthority, 338 | }; 339 | return { type, params }; 340 | } else if ('initializeAccount' in data) { 341 | const type = 'initializeAccount'; 342 | const params: InitializeAccountParams = { 343 | account: instruction.keys[0].pubkey, 344 | mint: instruction.keys[1].pubkey, 345 | owner: instruction.keys[2].pubkey, 346 | }; 347 | return { type, params }; 348 | } else if ('transfer' in data) { 349 | const type = 'transfer'; 350 | const params: TransferParams = { 351 | source: instruction.keys[0].pubkey, 352 | destination: instruction.keys[1].pubkey, 353 | owner: instruction.keys[2].pubkey, 354 | amount: data.transfer.amount, 355 | }; 356 | return { type, params }; 357 | } else if ('approve' in data) { 358 | const type = 'approve'; 359 | const params: ApproveParams = { 360 | source: instruction.keys[0].pubkey, 361 | delegate: instruction.keys[1].pubkey, 362 | owner: instruction.keys[2].pubkey, 363 | amount: data.approve.amount, 364 | }; 365 | return { type, params }; 366 | } else if ('revoke' in data) { 367 | const type = 'revoke'; 368 | const params: RevokeParams = { 369 | source: instruction.keys[0].pubkey, 370 | owner: instruction.keys[1].pubkey, 371 | }; 372 | return { type, params }; 373 | } else if ('setAuthority' in data) { 374 | const type = 'setAuthority'; 375 | const params: SetAuthorityParams = { 376 | target: instruction.keys[0].pubkey, 377 | currentAuthority: instruction.keys[1].pubkey, 378 | newAuthority: data.setAuthority.newAuthority, 379 | authorityType: data.setAuthority.authorityType, 380 | }; 381 | return { type, params }; 382 | } else if ('mintTo' in data) { 383 | const type = 'mintTo'; 384 | const params: MintToParams = { 385 | mint: instruction.keys[0].pubkey, 386 | destination: instruction.keys[1].pubkey, 387 | mintAuthority: instruction.keys[2].pubkey, 388 | amount: data.mintTo.amount, 389 | }; 390 | return { type, params }; 391 | } else if ('burn' in data) { 392 | const type = 'burn'; 393 | const params: BurnParams = { 394 | source: instruction.keys[0].pubkey, 395 | mint: instruction.keys[1].pubkey, 396 | owner: instruction.keys[2].pubkey, 397 | amount: data.burn.amount, 398 | }; 399 | return { type, params }; 400 | } else if ('closeAccount' in data) { 401 | const type = 'closeAccount'; 402 | const params: CloseAccountParams = { 403 | source: instruction.keys[0].pubkey, 404 | destination: instruction.keys[1].pubkey, 405 | owner: instruction.keys[2].pubkey, 406 | }; 407 | return { type, params }; 408 | } else if ('transferChecked' in data) { 409 | const type = 'transfer'; 410 | const params: TransferParams = { 411 | source: instruction.keys[0].pubkey, 412 | destination: instruction.keys[2].pubkey, 413 | owner: instruction.keys[3].pubkey, 414 | amount: data.transferChecked.amount, 415 | }; 416 | return { type, params }; 417 | } else if ('approveChecked' in data) { 418 | const type = 'approve'; 419 | const params: ApproveParams = { 420 | source: instruction.keys[0].pubkey, 421 | delegate: instruction.keys[2].pubkey, 422 | owner: instruction.keys[3].pubkey, 423 | amount: data.approveChecked.amount, 424 | }; 425 | return { type, params }; 426 | } else if ('mintToChecked' in data) { 427 | const type = 'mintTo'; 428 | const params: MintToParams = { 429 | mint: instruction.keys[0].pubkey, 430 | destination: instruction.keys[1].pubkey, 431 | mintAuthority: instruction.keys[2].pubkey, 432 | amount: data.mintToChecked.amount, 433 | }; 434 | return { type, params }; 435 | } else if ('burnChecked' in data) { 436 | const type = 'burn'; 437 | const params: BurnParams = { 438 | source: instruction.keys[0].pubkey, 439 | mint: instruction.keys[1].pubkey, 440 | owner: instruction.keys[2].pubkey, 441 | amount: data.burnChecked.amount, 442 | }; 443 | return { type, params }; 444 | } else { 445 | throw new Error('Unsupported token instruction type'); 446 | } 447 | } 448 | -------------------------------------------------------------------------------- /packages/tokens/src/mainnet-beta.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "tokenSymbol": "SOL", 4 | "mintAddress": "So11111111111111111111111111111111111111112", 5 | "tokenName": "Solana", 6 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/solana/info/logo.png" 7 | }, 8 | { 9 | "tokenSymbol": "BTC", 10 | "mintAddress": "9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E", 11 | "tokenName": "Wrapped Bitcoin (Sollet)", 12 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/bitcoin/info/logo.png" 13 | }, 14 | { 15 | "tokenSymbol": "soETH", 16 | "mintAddress": "2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk", 17 | "tokenName": "Wrapped Ether (Sollet)", 18 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" 19 | }, 20 | { 21 | "tokenSymbol": "USDC", 22 | "mintAddress": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", 23 | "tokenName": "USDC", 24 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" 25 | }, 26 | { 27 | "tokenSymbol": "soYFI", 28 | "mintAddress": "3JSf5tPeuscJGtaCp5giEiDhv51gQ4v3zWg8DGgyLfAB", 29 | "tokenName": "Wrapped YFI (Sollet)", 30 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png" 31 | }, 32 | { 33 | "tokenSymbol": "soLINK", 34 | "mintAddress": "CWE8jPTUYhdCTZYWPTe1o5DFqfdjzWKc9WKz6rSjQUdG", 35 | "tokenName": "Wrapped Chainlink (Sollet)", 36 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png" 37 | }, 38 | { 39 | "tokenSymbol": "XRP", 40 | "mintAddress": "Ga2AXHpfAF6mv2ekZwcsJFqu7wB4NV331qNH7fW9Nst8", 41 | "tokenName": "Wrapped XRP (Sollet)", 42 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ripple/info/logo.png" 43 | }, 44 | { 45 | "tokenSymbol": "soUSDT", 46 | "mintAddress": "BQcdHdAQW1hczDbBi9hiegXAR7A98Q9jx3X3iBBBDiq4", 47 | "tokenName": "Wrapped USDT (Sollet)", 48 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png" 49 | }, 50 | { 51 | "tokenSymbol": "USDT", 52 | "mintAddress": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", 53 | "tokenName": "USDT", 54 | "icon": "https://cdn.jsdelivr.net/gh/solana-labs/explorer/public/tokens/usdt.svg" 55 | }, 56 | { 57 | "tokenSymbol": "soSUSHI", 58 | "mintAddress": "AR1Mtgh7zAtxuxGd2XPovXPVjcSdY3i4rQYisNadjfKy", 59 | "tokenName": "Wrapped SUSHI (Sollet)", 60 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B3595068778DD592e39A122f4f5a5cF09C90fE2/logo.png" 61 | }, 62 | { 63 | "tokenSymbol": "soALEPH", 64 | "mintAddress": "CsZ5LZkDS7h9TDKjrbL7VAwQZ9nsRu8vJLhRYfmGaN8K", 65 | "tokenName": "Wrapped ALEPH (Sollet)", 66 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/6996a371cd02f516506a8f092eeb29888501447c/blockchains/nuls/assets/NULSd6HgyZkiqLnBzTaeSQfx1TNg2cqbzq51h/logo.png" 67 | }, 68 | { 69 | "tokenSymbol": "soSXP", 70 | "mintAddress": "SF3oTvfWzEP3DTwGSvUXRrGTvr75pdZNnBLAH9bzMuX", 71 | "tokenName": "Wrapped SXP (Sollet)", 72 | "icon": "https://github.com/trustwallet/assets/raw/b0ab88654fe64848da80d982945e4db06e197d4f/blockchains/ethereum/assets/0x8CE9137d39326AD0cD6491fb5CC0CbA0e089b6A9/logo.png" 73 | }, 74 | { 75 | "tokenSymbol": "soHGET", 76 | "mintAddress": "BtZQfWqDGbk9Wf2rXEiWyQBdBY1etnUUn6zEphvVS7yN", 77 | "tokenName": "Wrapped HGET (Sollet)" 78 | }, 79 | { 80 | "tokenSymbol": "soCREAM", 81 | "mintAddress": "5Fu5UUgbjpUvdBveb3a1JTNirL8rXtiYeSMWvKjtUNQv", 82 | "tokenName": "Wrapped CREAM (Sollet)", 83 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/4c82c2a409f18a4dd96a504f967a55a8fe47026d/blockchains/smartchain/assets/0xd4CB328A82bDf5f03eB737f37Fa6B370aef3e888/logo.png" 84 | }, 85 | { 86 | "tokenSymbol": "soUBXT", 87 | "mintAddress": "873KLxCbz7s9Kc4ZzgYRtNmhfkQrhfyWGZJBmyCbC3ei", 88 | "tokenName": "Wrapped UBXT (Sollet)" 89 | }, 90 | { 91 | "tokenSymbol": "soHNT", 92 | "mintAddress": "HqB7uswoVg4suaQiDP3wjxob1G5WdZ144zhdStwMCq7e", 93 | "tokenName": "Wrapped HNT (Sollet)" 94 | }, 95 | { 96 | "tokenSymbol": "soFRONT", 97 | "mintAddress": "9S4t2NEAiJVMvPdRYKVrfJpBafPBLtvbvyS3DecojQHw", 98 | "tokenName": "Wrapped FRONT (Sollet)", 99 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/6e375e4e5fb0ffe09ed001bae1ef8ca1d6c86034/blockchains/ethereum/assets/0xf8C3527CC04340b208C854E985240c02F7B7793f/logo.png" 100 | }, 101 | { 102 | "tokenSymbol": "soAKRO", 103 | "mintAddress": "6WNVCuxCGJzNjmMZoKyhZJwvJ5tYpsLyAtagzYASqBoF", 104 | "tokenName": "Wrapped AKRO (Sollet)", 105 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/878dcab0fab90e6593bcb9b7d941be4915f287dc/blockchains/ethereum/assets/0xb2734a4Cec32C81FDE26B0024Ad3ceB8C9b34037/logo.png" 106 | }, 107 | { 108 | "tokenSymbol": "soHXRO", 109 | "mintAddress": "DJafV9qemGp7mLMEn5wrfqaFwxsbLgUsGVS16zKRk9kc", 110 | "tokenName": "Wrapped HXRO (Sollet)" 111 | }, 112 | { 113 | "tokenSymbol": "soUNI", 114 | "mintAddress": "DEhAasscXF4kEGxFgJ3bq4PpVGp5wyUxMRvn6TzGVHaw", 115 | "tokenName": "Wrapped UNI (Sollet)", 116 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/08d734b5e6ec95227dc50efef3a9cdfea4c398a1/blockchains/ethereum/assets/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984/logo.png" 117 | }, 118 | { 119 | "mintAddress": "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", 120 | "tokenName": "Serum", 121 | "tokenSymbol": "SRM", 122 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png" 123 | }, 124 | { 125 | "tokenSymbol": "soFTT", 126 | "mintAddress": "AGFEad2et2ZJif9jaGpdMixQqvW5i81aBdvKe7PHNfz3", 127 | "tokenName": "Wrapped FTT (Sollet)", 128 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0x50D1c9771902476076eCFc8B2A83Ad6b9355a4c9/logo.png" 129 | }, 130 | { 131 | "mintAddress": "MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L", 132 | "tokenName": "MegaSerum", 133 | "tokenSymbol": "MSRM", 134 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x476c5E26a75bd202a9683ffD34359C0CC15be0fF/logo.png" 135 | }, 136 | { 137 | "tokenSymbol": "soUSDC", 138 | "mintAddress": "BXXkv6z8ykpG1yuvUDPgh732wzVHB69RnB9YgSYh3itW", 139 | "tokenName": "Wrapped USDC (Sollet)", 140 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png" 141 | }, 142 | { 143 | "tokenSymbol": "soTOMO", 144 | "mintAddress": "GXMvfY2jpQctDqZ9RoU3oWPhufKiCcFEfchvYumtX7jd", 145 | "tokenName": "Wrapped TOMO (Sollet)", 146 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/tomochain/info/logo.png" 147 | }, 148 | { 149 | "tokenSymbol": "soKARMA", 150 | "mintAddress": "EcqExpGNFBve2i1cMJUTR4bPXj4ZoqmDD2rTkeCcaTFX", 151 | "tokenName": "Wrapped KARMA (Sollet)", 152 | "icon": "https://raw.githubusercontent.com/machi-x/assets/152f2ca62611ef23298fac9a8e657386984d2d33/blockchains/ethereum/assets/0xdfe691f37b6264a90ff507eb359c45d55037951c/logo.png" 153 | }, 154 | { 155 | "tokenSymbol": "soLUA", 156 | "mintAddress": "EqWCKXfs3x47uVosDpTRgFniThL9Y8iCztJaapxbEaVX", 157 | "tokenName": "Wrapped LUA (Sollet)", 158 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/2d2491130e6beda208ba4fc6df028a82a0106ab6/blockchains/ethereum/assets/0xB1f66997A5760428D3a87D68b90BfE0aE64121cC/logo.png" 159 | }, 160 | { 161 | "tokenSymbol": "soMATH", 162 | "mintAddress": "GeDS162t9yGJuLEHPWXXGrb1zwkzinCgRwnT8vHYjKza", 163 | "tokenName": "Wrapped MATH (Sollet)" 164 | }, 165 | { 166 | "tokenSymbol": "soKEEP", 167 | "mintAddress": "GUohe4DJUA5FKPWo3joiPgsB7yzer7LpDmt1Vhzy3Zht", 168 | "tokenName": "Wrapped KEEP (Sollet)" 169 | }, 170 | { 171 | "tokenSymbol": "soSWAG", 172 | "mintAddress": "9F9fNTT6qwjsu4X4yWYKZpsbw5qT7o6yR2i57JF2jagy", 173 | "tokenName": "Wrapped SWAG (Sollet)" 174 | }, 175 | { 176 | "tokenSymbol": "FIDA", 177 | "mintAddress": "EchesyfXePKdLtoiZSL8pBe8Myagyy8ZRqsACNCFGnvp", 178 | "tokenName": "Bonfida", 179 | "icon": "https://raw.githubusercontent.com/dr497/awesome-serum-markets/02ce7c74fd2e9bd4cb55a15f735fc3ad0e7335f6/icons/fida.svg" 180 | }, 181 | { 182 | "tokenSymbol": "KIN", 183 | "mintAddress": "kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6", 184 | "tokenName": "KIN", 185 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/kin/info/logo.png" 186 | }, 187 | { 188 | "tokenSymbol": "MAPS", 189 | "mintAddress": "MAPS41MDahZ9QdKXhVa4dWB9RuyfV4XqhyAZ8XcYepb", 190 | "tokenName": "MAPS" 191 | }, 192 | { 193 | "tokenSymbol": "CYS", 194 | "mintAddress": "BRLsMczKuaR5w9vSubF4j8HwEGGprVAyyVgS4EX7DKEg", 195 | "tokenName": "Cyclos" 196 | }, 197 | { 198 | "tokenSymbol": "COPE", 199 | "mintAddress": "3K6rftdAaQYMPunrtNRHgnK2UAtjm2JwyT2oCiTDouYE", 200 | "tokenName": "COPE" 201 | }, 202 | { 203 | "tokenSymbol": "ETH", 204 | "mintAddress": "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs", 205 | "tokenName": "Ether (Wormhole)", 206 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2/logo.png" 207 | }, 208 | { 209 | "tokenSymbol": "YFI", 210 | "mintAddress": "BXZX2JRJFjvKazM1ibeDFxgAngKExb74MRXzXKvgikxX", 211 | "tokenName": "YFI (Wormhole)", 212 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e/logo.png" 213 | }, 214 | { 215 | "tokenSymbol": "LINK", 216 | "mintAddress": "2wpTofQ8SkACrkZWrZDjXPitYa8AwWgX8AfxdeBRRVLX", 217 | "tokenName": "Chainlink (Wormhole)", 218 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x514910771AF9Ca656af840dff83E8264EcF986CA/logo.png" 219 | }, 220 | { 221 | "tokenSymbol": "USDTet", 222 | "mintAddress": "Dn4noZ5jgGfkntzcQSUZ8czkreiZ1ForXYoV2H8Dm7S1", 223 | "tokenName": "Tether USD (Wormhole from Ethereum)", 224 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/f3ffd0b9ae2165336279ce2f8db1981a55ce30f8/blockchains/ethereum/assets/0xdAC17F958D2ee523a2206206994597C13D831ec7/logo.png" 225 | }, 226 | { 227 | "tokenSymbol": "SUSHI", 228 | "mintAddress": "ChVzxWRmrTeSgwd3Ui3UumcN8KX7VK3WaD4KGeSKpypj", 229 | "tokenName": "SUSHI (Wormhole)", 230 | "icon": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B3595068778DD592e39A122f4f5a5cF09C90fE2/logo.png" 231 | }, 232 | { 233 | "mintAddress": "3UCMiSnkcnkPE1pgQ5ggPCBv6dXgVUy16TmMUe1WpG9x", 234 | "tokenName": "Aleph.im (Wormhole)", 235 | "tokenSymbol": "ALEPH", 236 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/3UCMiSnkcnkPE1pgQ5ggPCBv6dXgVUy16TmMUe1WpG9x/logo.png" 237 | }, 238 | { 239 | "mintAddress": "3CyiEDRehaGufzkpXJitCP5tvh7cNhRqd9rPBxZrgK5z", 240 | "tokenName": "Swipe (Wormhole)", 241 | "tokenSymbol": "SXP", 242 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/3CyiEDRehaGufzkpXJitCP5tvh7cNhRqd9rPBxZrgK5z/logo.png" 243 | }, 244 | { 245 | "mintAddress": "2ueY1bLcPHfuFzEJq7yN1V2Wrpu8nkun9xG2TVCE1mhD", 246 | "tokenName": "Hedget (Wormhole)", 247 | "tokenSymbol": "HGET", 248 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/2ueY1bLcPHfuFzEJq7yN1V2Wrpu8nkun9xG2TVCE1mhD/logo.png" 249 | }, 250 | { 251 | "mintAddress": "HihxL2iM6L6P1oqoSeiixdJ3PhPYNxvSKH9A2dDqLVDH", 252 | "tokenName": "Cream (Wormhole)", 253 | "tokenSymbol": "CREAM", 254 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/HihxL2iM6L6P1oqoSeiixdJ3PhPYNxvSKH9A2dDqLVDH/logo.png" 255 | }, 256 | { 257 | "mintAddress": "FTtXEUosNn6EKG2SQtfbGuYB4rBttreQQcoWn1YDsuTq", 258 | "tokenName": "UpBots (Wormhole)", 259 | "tokenSymbol": "UBXT", 260 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/FTtXEUosNn6EKG2SQtfbGuYB4rBttreQQcoWn1YDsuTq/logo.png" 261 | }, 262 | { 263 | "mintAddress": "A9ik2NrpKRRG2snyTjofZQcTuav9yH3mNVHLsLiDQmYt", 264 | "tokenName": "Frontier Token (Wormhole)", 265 | "tokenSymbol": "FRONT", 266 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/A9ik2NrpKRRG2snyTjofZQcTuav9yH3mNVHLsLiDQmYt/logo.png" 267 | }, 268 | { 269 | "mintAddress": "12uHjozDVgyGWeLqQ8DMCRbig8amW5VmvZu3FdMMdcaG", 270 | "tokenName": "Akropolis (Wormhole)", 271 | "tokenSymbol": "AKRO", 272 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/G3h8NZgJozk9crme2me6sKDJuSQ12mNCtvC9NbSWqGuk/logo.png" 273 | }, 274 | { 275 | "mintAddress": "HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK", 276 | "tokenName": "Hxro (Wormhole)", 277 | "tokenSymbol": "HXRO", 278 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/HxhWkVpk5NS4Ltg5nij2G671CKXFRKPK8vy271Ub4uEK/logo.png" 279 | }, 280 | { 281 | "mintAddress": "8FU95xFJhUUkyyCLU13HSzDLs7oC4QZdXQHL6SCeab36", 282 | "tokenName": "Uniswap (Wormhole)", 283 | "tokenSymbol": "UNI", 284 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/8FU95xFJhUUkyyCLU13HSzDLs7oC4QZdXQHL6SCeab36/logo.png" 285 | }, 286 | { 287 | "mintAddress": "EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv", 288 | "tokenName": "FTX Token (Wormhole)", 289 | "tokenSymbol": "FTT", 290 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EzfgjvkSwthhgHaceR3LnKXUoRkP6NUhfghdaHAj1tUv/logo.png" 291 | }, 292 | { 293 | "mintAddress": "5Wc4U1ZoQRzF4tPdqKQzBwRSjYe8vEf3EvZMuXgtKUW6", 294 | "tokenName": "LuaToken (Wormhole)", 295 | "tokenSymbol": "LUA", 296 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/5Wc4U1ZoQRzF4tPdqKQzBwRSjYe8vEf3EvZMuXgtKUW6/logo.png" 297 | }, 298 | { 299 | "mintAddress": "CaGa7pddFXS65Gznqwp42kBhkJQdceoFVT7AQYo8Jr8Q", 300 | "tokenName": "MATH Token (Wormhole)", 301 | "tokenSymbol": "MATH", 302 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/CaGa7pddFXS65Gznqwp42kBhkJQdceoFVT7AQYo8Jr8Q/logo.png" 303 | }, 304 | { 305 | "mintAddress": "64L6o4G2H7Ln1vN7AHZsUMW4pbFciHyuwn4wUdSbcFxh", 306 | "tokenName": "Keep Network (Wormhole)", 307 | "tokenSymbol": "KEEP", 308 | "icon": "https://assets.coingecko.com/coins/images/3373/thumb/IuNzUb5b_400x400.jpg" 309 | }, 310 | { 311 | "mintAddress": "5hcdG6NjQwiNhVa9bcyaaDsCyA1muPQ6WRzQwHfgeeKo", 312 | "tokenName": "Swag Token (Wormhole)", 313 | "tokenSymbol": "SWAG", 314 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/5hcdG6NjQwiNhVa9bcyaaDsCyA1muPQ6WRzQwHfgeeKo/logo.png" 315 | }, 316 | { 317 | "mintAddress": "KgV1GvrHQmRBY8sHQQeUKwTm2r2h8t4C8qt12Cw1HVE", 318 | "tokenName": "AVAX (Wormhole)", 319 | "tokenSymbol": "AVAX", 320 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/KgV1GvrHQmRBY8sHQQeUKwTm2r2h8t4C8qt12Cw1HVE/logo.png" 321 | }, 322 | { 323 | "mintAddress": "HysWcbHiYY9888pHbaqhwLYZQeZrcQMXKQWRqS7zcPK5", 324 | "tokenName": "Axie Infinity Shard (Wormhole from Ethereum)", 325 | "tokenSymbol": "AXSet", 326 | "icon": "https://cloudflare-ipfs.com/ipfs/QmVUzbiJP2xm2rH69Y42rmTxD8MZxEpGmvdfVKe551zZcR/" 327 | }, 328 | { 329 | "mintAddress": "9gP2kCy3wA1ctvYWQk75guqXuHfrEomqydHLtcTCqiLa", 330 | "tokenName": "Binance Coin (Wormhole)", 331 | "tokenSymbol": "BNB", 332 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/9gP2kCy3wA1ctvYWQk75guqXuHfrEomqydHLtcTCqiLa/logo.png" 333 | }, 334 | { 335 | "mintAddress": "AuGz22orMknxQHTVGwAu7e3dJikTJKgcjFwMNDikEKmF", 336 | "tokenName": "Gala (Wormhole)", 337 | "tokenSymbol": "GALA", 338 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/AuGz22orMknxQHTVGwAu7e3dJikTJKgcjFwMNDikEKmF/logo.png" 339 | }, 340 | { 341 | "mintAddress": "F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W", 342 | "tokenName": "LUNA (Wormhole)", 343 | "tokenSymbol": "LUNA", 344 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/F6v4wfAdJB8D8p77bMXZgYt8TDKsYxLYxH5AFhUkYx9W/logo.png" 345 | }, 346 | { 347 | "mintAddress": "Gz7VkD4MacbEB6yC5XD3HcumEiYx2EtDYYrfikGsvopG", 348 | "tokenName": "Matic (Wormhole from Polygon)", 349 | "tokenSymbol": "MATICpo", 350 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/C7NNPWuZCNjZBfW5p6JvGsR8pUdsRpEdP1ZAhnoDwj7h/logo.png" 351 | }, 352 | { 353 | "mintAddress": "S3SQfD6RheMXQ3EEYn1Z5sJsbtwfXdt7tSAVXPQFtYo", 354 | "tokenName": "ROSE (Wormhole)", 355 | "tokenSymbol": "ROSE", 356 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/S3SQfD6RheMXQ3EEYn1Z5sJsbtwfXdt7tSAVXPQFtYo/logo.png" 357 | }, 358 | { 359 | "mintAddress": "49c7WuCZkQgc3M4qH8WuEUNXfgwupZf1xqWkDQ7gjRGt", 360 | "tokenName": "Sandbox (Wormhole)", 361 | "tokenSymbol": "SAND", 362 | "icon": "https://gemini.com/images/currencies/icons/default/sand.svg" 363 | }, 364 | { 365 | "mintAddress": "CiKu4eHsVrc1eueVQeHn7qhXTcVu95gSQmBpX4utjL9z", 366 | "tokenName": "SHIBA INU (Wormhole)", 367 | "tokenSymbol": "SHIB", 368 | "icon": "https://cloudflare-ipfs.com/ipfs/QmU8BZDCVQuVGyX25ApGGkRy2KKG2QDqe4UhKrxYbNMjwr/" 369 | }, 370 | { 371 | "mintAddress": "9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i", 372 | "tokenName": "UST (Wormhole)", 373 | "tokenSymbol": "UST", 374 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i/logo.png" 375 | }, 376 | { 377 | "mintAddress": "EdAhkbj5nF9sRM7XN7ewuW8C9XEUMs8P7cnoQ57SYE96", 378 | "tokenName": "FABRIC", 379 | "tokenSymbol": "FAB", 380 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EdAhkbj5nF9sRM7XN7ewuW8C9XEUMs8P7cnoQ57SYE96/logo.png" 381 | }, 382 | { 383 | "mintAddress": "JET6zMJWkCN9tpRT2v2jfAmm5VnQFDpUBCyaKojmGtz", 384 | "tokenName": "Jet Protocol", 385 | "tokenSymbol": "JET", 386 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/JET6zMJWkCN9tpRT2v2jfAmm5VnQFDpUBCyaKojmGtz/logo.png" 387 | }, 388 | { 389 | "mintAddress": "5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm", 390 | "tokenName": "Socean staked SOL", 391 | "tokenSymbol": "scnSOL", 392 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm/logo.png" 393 | }, 394 | { 395 | "mintAddress": "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj", 396 | "tokenName": "Lido Staked SOL", 397 | "tokenSymbol": "stSOL", 398 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj/logo.png" 399 | }, 400 | { 401 | "mintAddress": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", 402 | "tokenName": "Bonk", 403 | "tokenSymbol": "BONK" 404 | }, 405 | { 406 | "mintAddress": "GFX1ZjR2P15tmrSwow6FjyDYcEkoFb4p4gJCpLBjaxHD", 407 | "tokenName": "GooseFX", 408 | "tokenSymbol": "GOFX", 409 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/GFX1ZjR2P15tmrSwow6FjyDYcEkoFb4p4gJCpLBjaxHD/logo.png" 410 | }, 411 | { 412 | "mintAddress": "bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1", 413 | "tokenName": "BlazeStake Staked SOL", 414 | "tokenSymbol": "bSOL", 415 | "icon": "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1/logo.png" 416 | } 417 | ] 418 | -------------------------------------------------------------------------------- /packages/openbook/src/instructions.js: -------------------------------------------------------------------------------- 1 | import { 2 | PublicKey, SYSVAR_RENT_PUBKEY, 3 | TransactionInstruction 4 | } from '@solana/web3.js'; 5 | import BN from 'bn.js'; 6 | import { seq, struct, u16, u32, u8, union } from 'buffer-layout'; 7 | import { 8 | i64, orderTypeLayout, 9 | publicKeyLayout, 10 | selfTradeBehaviorLayout, 11 | sideLayout, 12 | u128, 13 | u64, VersionedLayout 14 | } from './layout'; 15 | import { TOKEN_PROGRAM_ID } from './token-instructions'; 16 | 17 | // NOTE: Update these if the position of arguments for the settleFunds instruction changes 18 | export const SETTLE_FUNDS_BASE_WALLET_INDEX = 5; 19 | export const SETTLE_FUNDS_QUOTE_WALLET_INDEX = 6; 20 | 21 | // NOTE: Update these if the position of arguments for the newOrder instruction changes 22 | export const NEW_ORDER_OPEN_ORDERS_INDEX = 1; 23 | export const NEW_ORDER_OWNER_INDEX = 4; 24 | 25 | // NOTE: Update these if the position of arguments for the newOrder instruction changes 26 | export const NEW_ORDER_V3_OPEN_ORDERS_INDEX = 1; 27 | export const NEW_ORDER_V3_OWNER_INDEX = 7; 28 | 29 | export const INSTRUCTION_LAYOUT = new VersionedLayout( 30 | 0, 31 | union(u32('instruction')), 32 | ); 33 | INSTRUCTION_LAYOUT.inner.addVariant( 34 | 0, 35 | struct([ 36 | u64('baseLotSize'), 37 | u64('quoteLotSize'), 38 | u16('feeRateBps'), 39 | u64('vaultSignerNonce'), 40 | u64('quoteDustThreshold'), 41 | ]), 42 | 'initializeMarket', 43 | ); 44 | INSTRUCTION_LAYOUT.inner.addVariant( 45 | 1, 46 | struct([ 47 | sideLayout('side'), 48 | u64('limitPrice'), 49 | u64('maxQuantity'), 50 | orderTypeLayout('orderType'), 51 | u64('clientId'), 52 | ]), 53 | 'newOrder', 54 | ); 55 | INSTRUCTION_LAYOUT.inner.addVariant(2, struct([u16('limit')]), 'matchOrders'); 56 | INSTRUCTION_LAYOUT.inner.addVariant(3, struct([u16('limit')]), 'consumeEvents'); 57 | INSTRUCTION_LAYOUT.inner.addVariant( 58 | 4, 59 | struct([ 60 | sideLayout('side'), 61 | u128('orderId'), 62 | publicKeyLayout('openOrders'), 63 | u8('openOrdersSlot'), 64 | ]), 65 | 'cancelOrder', 66 | ); 67 | INSTRUCTION_LAYOUT.inner.addVariant(5, struct([]), 'settleFunds'); 68 | INSTRUCTION_LAYOUT.inner.addVariant( 69 | 6, 70 | struct([u64('clientId')]), 71 | 'cancelOrderByClientId', 72 | ); 73 | INSTRUCTION_LAYOUT.inner.addVariant( 74 | 10, 75 | struct([ 76 | sideLayout('side'), 77 | u64('limitPrice'), 78 | u64('maxBaseQuantity'), 79 | u64('maxQuoteQuantity'), 80 | selfTradeBehaviorLayout('selfTradeBehavior'), 81 | orderTypeLayout('orderType'), 82 | u64('clientId'), 83 | u16('limit'), 84 | ]), 85 | 'newOrderV3', 86 | ); 87 | INSTRUCTION_LAYOUT.inner.addVariant( 88 | 11, 89 | struct([sideLayout('side'), u128('orderId')]), 90 | 'cancelOrderV2', 91 | ); 92 | INSTRUCTION_LAYOUT.inner.addVariant( 93 | 12, 94 | struct([u64('clientId')]), 95 | 'cancelOrderByClientIdV2', 96 | ); 97 | INSTRUCTION_LAYOUT.inner.addVariant( 98 | 13, 99 | struct([ 100 | sideLayout('side'), 101 | u64('limitPrice'), 102 | u64('maxBaseQuantity'), 103 | u64('maxQuoteQuantity'), 104 | u64('minBaseQuantity'), 105 | u64('minQuoteQuantity'), 106 | u16('limit'), 107 | ]), 108 | 'sendTake' 109 | ); 110 | INSTRUCTION_LAYOUT.inner.addVariant(14, struct([]), 'closeOpenOrders'); 111 | INSTRUCTION_LAYOUT.inner.addVariant(15, struct([]), 'initOpenOrders'); 112 | INSTRUCTION_LAYOUT.inner.addVariant(16, struct([u16('limit')]), 'prune'); 113 | INSTRUCTION_LAYOUT.inner.addVariant(17, struct([u16('limit')]), 'consumeEventsPermissioned'); 114 | INSTRUCTION_LAYOUT.inner.addVariant( 115 | 18, 116 | struct([seq(u64(), 8, 'clientIds')]), 117 | 'cancelOrdersByClientIds', 118 | ); 119 | 120 | const orderStruct = () => struct([ 121 | sideLayout('side'), 122 | u64('limitPrice'), 123 | u64('maxBaseQuantity'), 124 | u64('maxQuoteQuantity'), 125 | selfTradeBehaviorLayout('selfTradeBehavior'), 126 | orderTypeLayout('orderType'), 127 | u64('clientId'), 128 | u16('limit'), 129 | i64('maxTs'), 130 | ]); 131 | 132 | INSTRUCTION_LAYOUT.inner.addVariant( 133 | 19, 134 | orderStruct(), 135 | 'replaceOrderByClientId' 136 | ) 137 | INSTRUCTION_LAYOUT.inner.addVariant( 138 | 20, 139 | struct([u64('orderAmount'), seq(orderStruct(), 8, 'orders')]), 140 | 'replaceOrdersByClientIds' 141 | ) 142 | 143 | export const INSTRUCTION_LAYOUT_V2 = new VersionedLayout( 144 | 0, 145 | union(u32('instruction')), 146 | ); 147 | INSTRUCTION_LAYOUT_V2.inner.addVariant( 148 | 10, 149 | orderStruct(), 150 | 'newOrderV3', 151 | ); 152 | 153 | export function encodeInstruction(instruction, maxLength = 100) { 154 | const b = Buffer.alloc(maxLength); 155 | return b.slice(0, INSTRUCTION_LAYOUT.encode(instruction, b)); 156 | } 157 | 158 | export function encodeInstructionV2(instruction) { 159 | const b = Buffer.alloc(100); 160 | return b.slice(0, INSTRUCTION_LAYOUT_V2.encode(instruction, b)); 161 | } 162 | 163 | export function decodeInstruction(message) { 164 | return INSTRUCTION_LAYOUT.decode(message); 165 | } 166 | 167 | export function decodeInstructionV2(message) { 168 | return INSTRUCTION_LAYOUT_V2.decode(message); 169 | } 170 | 171 | export class DexInstructions { 172 | static initializeMarket({ 173 | market, 174 | requestQueue, 175 | eventQueue, 176 | bids, 177 | asks, 178 | baseVault, 179 | quoteVault, 180 | baseMint, 181 | quoteMint, 182 | baseLotSize, 183 | quoteLotSize, 184 | feeRateBps, 185 | vaultSignerNonce, 186 | quoteDustThreshold, 187 | programId, 188 | authority = undefined, 189 | pruneAuthority = undefined, 190 | crankAuthority = undefined, 191 | }) { 192 | let rentSysvar = new PublicKey( 193 | 'SysvarRent111111111111111111111111111111111', 194 | ); 195 | return new TransactionInstruction({ 196 | keys: [ 197 | { pubkey: market, isSigner: false, isWritable: true }, 198 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 199 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 200 | { pubkey: bids, isSigner: false, isWritable: true }, 201 | { pubkey: asks, isSigner: false, isWritable: true }, 202 | { pubkey: baseVault, isSigner: false, isWritable: true }, 203 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 204 | { pubkey: baseMint, isSigner: false, isWritable: false }, 205 | { pubkey: quoteMint, isSigner: false, isWritable: false }, 206 | // Use a dummy address if using the new dex upgrade to save tx space. 207 | { 208 | pubkey: authority ? quoteMint : SYSVAR_RENT_PUBKEY, 209 | isSigner: false, 210 | isWritable: false, 211 | }, 212 | ] 213 | .concat( 214 | authority 215 | ? { pubkey: authority, isSigner: false, isWritable: false } 216 | : [], 217 | ) 218 | .concat( 219 | authority && pruneAuthority 220 | ? { pubkey: pruneAuthority, isSigner: false, isWritable: false } 221 | : [], 222 | ) 223 | .concat( 224 | authority && pruneAuthority && crankAuthority 225 | ? { pubkey: crankAuthority, isSigner: false, isWritable: false } 226 | : [], 227 | ), 228 | programId, 229 | data: encodeInstruction({ 230 | initializeMarket: { 231 | baseLotSize, 232 | quoteLotSize, 233 | feeRateBps, 234 | vaultSignerNonce, 235 | quoteDustThreshold, 236 | }, 237 | }), 238 | }); 239 | } 240 | 241 | static newOrder({ 242 | market, 243 | openOrders, 244 | payer, 245 | owner, 246 | requestQueue, 247 | baseVault, 248 | quoteVault, 249 | side, 250 | limitPrice, 251 | maxQuantity, 252 | orderType, 253 | clientId, 254 | programId, 255 | feeDiscountPubkey = null, 256 | }) { 257 | const keys = [ 258 | { pubkey: market, isSigner: false, isWritable: true }, 259 | { pubkey: openOrders, isSigner: false, isWritable: true }, 260 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 261 | { pubkey: payer, isSigner: false, isWritable: true }, 262 | { pubkey: owner, isSigner: true, isWritable: false }, 263 | { pubkey: baseVault, isSigner: false, isWritable: true }, 264 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 265 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 266 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 267 | ]; 268 | if (feeDiscountPubkey) { 269 | keys.push({ 270 | pubkey: feeDiscountPubkey, 271 | isSigner: false, 272 | isWritable: false, 273 | }); 274 | } 275 | return new TransactionInstruction({ 276 | keys, 277 | programId, 278 | data: encodeInstruction({ 279 | newOrder: clientId 280 | ? { side, limitPrice, maxQuantity, orderType, clientId } 281 | : { side, limitPrice, maxQuantity, orderType }, 282 | }), 283 | }); 284 | } 285 | 286 | static newOrderV3({ 287 | market, 288 | openOrders, 289 | payer, 290 | owner, 291 | requestQueue, 292 | eventQueue, 293 | bids, 294 | asks, 295 | baseVault, 296 | quoteVault, 297 | side, 298 | limitPrice, 299 | maxBaseQuantity, 300 | maxQuoteQuantity, 301 | orderType, 302 | clientId, 303 | programId, 304 | selfTradeBehavior, 305 | feeDiscountPubkey = null, 306 | maxTs = null, 307 | replaceIfExists = false, 308 | }) { 309 | const keys = [ 310 | { pubkey: market, isSigner: false, isWritable: true }, 311 | { pubkey: openOrders, isSigner: false, isWritable: true }, 312 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 313 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 314 | { pubkey: bids, isSigner: false, isWritable: true }, 315 | { pubkey: asks, isSigner: false, isWritable: true }, 316 | { pubkey: payer, isSigner: false, isWritable: true }, 317 | { pubkey: owner, isSigner: true, isWritable: false }, 318 | { pubkey: baseVault, isSigner: false, isWritable: true }, 319 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 320 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 321 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 322 | ]; 323 | if (feeDiscountPubkey) { 324 | keys.push({ 325 | pubkey: feeDiscountPubkey, 326 | isSigner: false, 327 | isWritable: false, 328 | }); 329 | } 330 | 331 | let instructionName, encoder; 332 | if (replaceIfExists) { 333 | instructionName = 'replaceOrderByClientId'; 334 | encoder = encodeInstruction; 335 | } else { 336 | instructionName = 'newOrderV3'; 337 | encoder = maxTs ? encodeInstructionV2 : encodeInstruction; 338 | } 339 | 340 | return new TransactionInstruction({ 341 | keys, 342 | programId, 343 | data: encoder({ 344 | [instructionName]: { 345 | side, 346 | limitPrice, 347 | maxBaseQuantity, 348 | maxQuoteQuantity, 349 | selfTradeBehavior, 350 | orderType, 351 | clientId, 352 | limit: 65535, 353 | maxTs: new BN(maxTs ?? '9223372036854775807'), 354 | }, 355 | }), 356 | }); 357 | } 358 | 359 | static sendTake({ 360 | market, 361 | requestQueue, 362 | eventQueue, 363 | bids, 364 | asks, 365 | baseWallet, 366 | quoteWallet, 367 | owner, 368 | baseVault, 369 | quoteVault, 370 | vaultSigner, 371 | side, 372 | limitPrice, 373 | maxBaseQuantity, 374 | maxQuoteQuantity, 375 | minBaseQuantity, 376 | minQuoteQuantity, 377 | limit, 378 | programId, 379 | feeDiscountPubkey = null, 380 | }) { 381 | const keys = [ 382 | { pubkey: market, isSigner: false, isWritable: true }, 383 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 384 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 385 | { pubkey: bids, isSigner: false, isWritable: true }, 386 | { pubkey: asks, isSigner: false, isWritable: true }, 387 | { pubkey: baseWallet, isSigner: false, isWritable: true }, 388 | { pubkey: quoteWallet, isSigner: false, isWritable: true }, 389 | { pubkey: owner, isSigner: true, isWritable: false }, 390 | { pubkey: baseVault, isSigner: false, isWritable: true }, 391 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 392 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 393 | { pubkey: vaultSigner, isSigner: false, isWritable: false }, 394 | ]; 395 | if (feeDiscountPubkey) { 396 | keys.push({ 397 | pubkey: feeDiscountPubkey, 398 | isSigner: false, 399 | isWritable: false, 400 | }); 401 | } 402 | return new TransactionInstruction({ 403 | keys, 404 | programId, 405 | data: encodeInstruction({ 406 | sendTake: { 407 | side, 408 | limitPrice, 409 | maxBaseQuantity, 410 | maxQuoteQuantity, 411 | minBaseQuantity, 412 | minQuoteQuantity, 413 | limit, 414 | } 415 | }) 416 | }) 417 | } 418 | 419 | static replaceOrdersByClientIds({ 420 | market, 421 | openOrders, 422 | payer, 423 | owner, 424 | requestQueue, 425 | eventQueue, 426 | bids, 427 | asks, 428 | baseVault, 429 | quoteVault, 430 | feeDiscountPubkey = null, 431 | programId, 432 | orders 433 | }) { 434 | const keys = [ 435 | { pubkey: market, isSigner: false, isWritable: true }, 436 | { pubkey: openOrders, isSigner: false, isWritable: true }, 437 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 438 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 439 | { pubkey: bids, isSigner: false, isWritable: true }, 440 | { pubkey: asks, isSigner: false, isWritable: true }, 441 | { pubkey: payer, isSigner: false, isWritable: true }, 442 | { pubkey: owner, isSigner: true, isWritable: false }, 443 | { pubkey: baseVault, isSigner: false, isWritable: true }, 444 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 445 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 446 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 447 | ]; 448 | if (feeDiscountPubkey) { 449 | keys.push({ 450 | pubkey: feeDiscountPubkey, 451 | isSigner: false, 452 | isWritable: false, 453 | }); 454 | } 455 | 456 | return new TransactionInstruction({ 457 | keys, 458 | programId, 459 | data: encodeInstruction({ 460 | replaceOrdersByClientIds: { 461 | orderAmount: new BN(orders.length), 462 | orders: orders.map(order => ({ 463 | ...order, 464 | maxTs: new BN(order.maxTs ?? '9223372036854775807'), 465 | limit: 65535, 466 | })) 467 | } 468 | }, 15 + orders.length * 60).slice(0, 13 + orders.length * 54) 469 | }); 470 | } 471 | 472 | static matchOrders({ 473 | market, 474 | requestQueue, 475 | eventQueue, 476 | bids, 477 | asks, 478 | baseVault, 479 | quoteVault, 480 | limit, 481 | programId, 482 | }) { 483 | return new TransactionInstruction({ 484 | keys: [ 485 | { pubkey: market, isSigner: false, isWritable: true }, 486 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 487 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 488 | { pubkey: bids, isSigner: false, isWritable: true }, 489 | { pubkey: asks, isSigner: false, isWritable: true }, 490 | { pubkey: baseVault, isSigner: false, isWritable: true }, 491 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 492 | ], 493 | programId, 494 | data: encodeInstruction({ matchOrders: { limit } }), 495 | }); 496 | } 497 | 498 | static consumeEvents({ 499 | market, 500 | eventQueue, 501 | coinFee, 502 | pcFee, 503 | openOrdersAccounts, 504 | limit, 505 | programId, 506 | }) { 507 | return new TransactionInstruction({ 508 | keys: [ 509 | ...openOrdersAccounts.map((account) => ({ 510 | pubkey: account, 511 | isSigner: false, 512 | isWritable: true, 513 | })), 514 | { pubkey: market, isSigner: false, isWritable: true }, 515 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 516 | { pubkey: coinFee, isSigner: false, isWriteable: true }, 517 | { pubkey: pcFee, isSigner: false, isWritable: true }, 518 | ], 519 | programId, 520 | data: encodeInstruction({ consumeEvents: { limit } }), 521 | }); 522 | } 523 | 524 | static consumeEventsPermissioned({ 525 | market, 526 | eventQueue, 527 | crankAuthority, 528 | openOrdersAccounts, 529 | limit, 530 | programId, 531 | }) { 532 | return new TransactionInstruction({ 533 | keys: [ 534 | ...openOrdersAccounts.map((account) => ({ 535 | pubkey: account, 536 | isSigner: false, 537 | isWritable: true, 538 | })), 539 | { pubkey: market, isSigner: false, isWritable: true }, 540 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 541 | { pubkey: crankAuthority, isSigner: true, isWritable: false }, 542 | ], 543 | programId, 544 | data: encodeInstruction({ consumeEventsPermissioned: { limit } }), 545 | }); 546 | } 547 | 548 | static cancelOrder({ 549 | market, 550 | openOrders, 551 | owner, 552 | requestQueue, 553 | side, 554 | orderId, 555 | openOrdersSlot, 556 | programId, 557 | }) { 558 | return new TransactionInstruction({ 559 | keys: [ 560 | { pubkey: market, isSigner: false, isWritable: false }, 561 | { pubkey: openOrders, isSigner: false, isWritable: true }, 562 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 563 | { pubkey: owner, isSigner: true, isWritable: false }, 564 | ], 565 | programId, 566 | data: encodeInstruction({ 567 | cancelOrder: { side, orderId, openOrders, openOrdersSlot }, 568 | }), 569 | }); 570 | } 571 | 572 | static cancelOrderV2(order) { 573 | const { 574 | market, 575 | bids, 576 | asks, 577 | eventQueue, 578 | openOrders, 579 | owner, 580 | side, 581 | orderId, 582 | programId, 583 | } = order; 584 | return new TransactionInstruction({ 585 | keys: [ 586 | { pubkey: market, isSigner: false, isWritable: false }, 587 | { pubkey: bids, isSigner: false, isWritable: true }, 588 | { pubkey: asks, isSigner: false, isWritable: true }, 589 | { pubkey: openOrders, isSigner: false, isWritable: true }, 590 | { pubkey: owner, isSigner: true, isWritable: false }, 591 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 592 | ], 593 | programId, 594 | data: encodeInstruction({ 595 | cancelOrderV2: { side, orderId }, 596 | }), 597 | }); 598 | } 599 | 600 | static cancelOrderByClientId({ 601 | market, 602 | openOrders, 603 | owner, 604 | requestQueue, 605 | clientId, 606 | programId, 607 | }) { 608 | return new TransactionInstruction({ 609 | keys: [ 610 | { pubkey: market, isSigner: false, isWritable: false }, 611 | { pubkey: openOrders, isSigner: false, isWritable: true }, 612 | { pubkey: requestQueue, isSigner: false, isWritable: true }, 613 | { pubkey: owner, isSigner: true, isWritable: false }, 614 | ], 615 | programId, 616 | data: encodeInstruction({ 617 | cancelOrderByClientId: { clientId }, 618 | }), 619 | }); 620 | } 621 | 622 | static cancelOrderByClientIdV2({ 623 | market, 624 | openOrders, 625 | owner, 626 | bids, 627 | asks, 628 | eventQueue, 629 | clientId, 630 | programId, 631 | }) { 632 | return new TransactionInstruction({ 633 | keys: [ 634 | { pubkey: market, isSigner: false, isWritable: false }, 635 | { pubkey: bids, isSigner: false, isWritable: true }, 636 | { pubkey: asks, isSigner: false, isWritable: true }, 637 | { pubkey: openOrders, isSigner: false, isWritable: true }, 638 | { pubkey: owner, isSigner: true, isWritable: false }, 639 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 640 | ], 641 | programId, 642 | data: encodeInstruction({ 643 | cancelOrderByClientIdV2: { clientId }, 644 | }), 645 | }); 646 | } 647 | 648 | static cancelOrdersByClientIds({ 649 | market, 650 | openOrders, 651 | owner, 652 | bids, 653 | asks, 654 | eventQueue, 655 | clientIds, 656 | programId, 657 | }) { 658 | if (clientIds.length > 8) { 659 | throw new Error("Number of client ids cannot exceed 8!"); 660 | } 661 | 662 | while (clientIds.length < 8) { 663 | clientIds.push(new BN(0)); 664 | } 665 | 666 | return new TransactionInstruction({ 667 | keys: [ 668 | { pubkey: market, isSigner: false, isWritable: false }, 669 | { pubkey: bids, isSigner: false, isWritable: true }, 670 | { pubkey: asks, isSigner: false, isWritable: true }, 671 | { pubkey: openOrders, isSigner: false, isWritable: true }, 672 | { pubkey: owner, isSigner: true, isWritable: false }, 673 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 674 | ], 675 | programId, 676 | data: encodeInstruction({ 677 | cancelOrdersByClientIds: { clientIds }, 678 | }), 679 | }); 680 | } 681 | 682 | static settleFunds({ 683 | market, 684 | openOrders, 685 | owner, 686 | baseVault, 687 | quoteVault, 688 | baseWallet, 689 | quoteWallet, 690 | vaultSigner, 691 | programId, 692 | referrerQuoteWallet = null, 693 | }) { 694 | const keys = [ 695 | { pubkey: market, isSigner: false, isWritable: true }, 696 | { pubkey: openOrders, isSigner: false, isWritable: true }, 697 | { pubkey: owner, isSigner: true, isWritable: false }, 698 | { pubkey: baseVault, isSigner: false, isWritable: true }, 699 | { pubkey: quoteVault, isSigner: false, isWritable: true }, 700 | { pubkey: baseWallet, isSigner: false, isWritable: true }, 701 | { pubkey: quoteWallet, isSigner: false, isWritable: true }, 702 | { pubkey: vaultSigner, isSigner: false, isWritable: false }, 703 | { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, 704 | ]; 705 | if (referrerQuoteWallet) { 706 | keys.push({ 707 | pubkey: referrerQuoteWallet, 708 | isSigner: false, 709 | isWritable: true, 710 | }); 711 | } 712 | return new TransactionInstruction({ 713 | keys, 714 | programId, 715 | data: encodeInstruction({ 716 | settleFunds: {}, 717 | }), 718 | }); 719 | } 720 | 721 | static closeOpenOrders({ market, openOrders, owner, solWallet, programId }) { 722 | const keys = [ 723 | { pubkey: openOrders, isSigner: false, isWritable: true }, 724 | { pubkey: owner, isSigner: true, isWritable: false }, 725 | { pubkey: solWallet, isSigner: false, isWritable: true }, 726 | { pubkey: market, isSigner: false, isWritable: false }, 727 | ]; 728 | return new TransactionInstruction({ 729 | keys, 730 | programId, 731 | data: encodeInstruction({ 732 | closeOpenOrders: {}, 733 | }), 734 | }); 735 | } 736 | 737 | static initOpenOrders({ 738 | market, 739 | openOrders, 740 | owner, 741 | programId, 742 | marketAuthority, 743 | }) { 744 | const keys = [ 745 | { pubkey: openOrders, isSigner: false, isWritable: true }, 746 | { pubkey: owner, isSigner: true, isWritable: false }, 747 | { pubkey: market, isSigner: false, isWritable: false }, 748 | { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false }, 749 | ].concat( 750 | marketAuthority 751 | ? { pubkey: marketAuthority, isSigner: false, isWritable: false } 752 | : [], 753 | ); 754 | return new TransactionInstruction({ 755 | keys, 756 | programId, 757 | data: encodeInstruction({ 758 | initOpenOrders: {}, 759 | }), 760 | }); 761 | } 762 | 763 | static prune({ 764 | market, 765 | bids, 766 | asks, 767 | eventQueue, 768 | pruneAuthority, 769 | openOrders, 770 | openOrdersOwner, 771 | programId, 772 | limit, 773 | }) { 774 | const keys = [ 775 | { pubkey: market, isSigner: false, isWritable: true }, 776 | { pubkey: bids, isSigner: false, isWritable: true }, 777 | { pubkey: asks, isSigner: false, isWritable: true }, 778 | // Keep signer false so that one can use a PDA. 779 | { pubkey: pruneAuthority, isSigner: false, isWritable: false }, 780 | { pubkey: openOrders, isSigner: false, isWritable: true }, 781 | { pubkey: openOrdersOwner, isSigner: false, isWritable: false }, 782 | { pubkey: eventQueue, isSigner: false, isWritable: true }, 783 | ]; 784 | return new TransactionInstruction({ 785 | keys, 786 | programId, 787 | data: encodeInstruction({ 788 | prune: { limit }, 789 | }), 790 | }); 791 | } 792 | } 793 | --------------------------------------------------------------------------------