From 0ef5a9b1d790e7a0c92b14af4c2b434a119b4308 Mon Sep 17 00:00:00 2001 From: DR497 <47689875+dr497@users.noreply.github.com> Date: Fri, 30 Apr 2021 06:47:10 +0800 Subject: [PATCH] explorer: add Bonfida Bots instructions (#16872) * explorer: add Bonfida Bots instructions * Update explorer/src/components/instruction/BonfidaBotDetails.tsx Co-authored-by: Josh * explorer: Bonfida Bot instructions fixes Co-authored-by: Josh --- explorer/package-lock.json | 233 +++++++++- explorer/package.json | 1 + .../components/account/TokenHistoryCard.tsx | 14 + .../instruction/BonfidaBotDetails.tsx | 92 ++++ .../bonfida-bot/CancelOrderDetails.tsx | 105 +++++ .../bonfida-bot/CollectFeesDetails.tsx | 60 +++ .../bonfida-bot/CreateBotDetails.tsx | 94 ++++ .../bonfida-bot/CreateOrderDetails.tsx | 164 +++++++ .../bonfida-bot/DepositDetails.tsx | 86 ++++ .../bonfida-bot/InitializeBotDetails.tsx | 63 +++ .../instruction/bonfida-bot/RedeemDetails.tsx | 72 +++ .../bonfida-bot/SettleFundsDetails.tsx | 108 +++++ .../instruction/bonfida-bot/types.ts | 433 ++++++++++++++++++ .../transaction/InstructionsSection.tsx | 6 +- explorer/src/utils/instruction.ts | 14 + 15 files changed, 1541 insertions(+), 4 deletions(-) create mode 100644 explorer/src/components/instruction/BonfidaBotDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/CancelOrderDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/CollectFeesDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/CreateBotDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/CreateOrderDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/DepositDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/InitializeBotDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/RedeemDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/SettleFundsDetails.tsx create mode 100644 explorer/src/components/instruction/bonfida-bot/types.ts diff --git a/explorer/package-lock.json b/explorer/package-lock.json index 0e85985fd0..32af8c71ea 100644 --- a/explorer/package-lock.json +++ b/explorer/package-lock.json @@ -1270,6 +1270,49 @@ "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, + "@dr497/awesome-serum-markets": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/@dr497/awesome-serum-markets/-/awesome-serum-markets-1.1.31.tgz", + "integrity": "sha512-DGI/WOChZ7oQdaD3o1KfqW7E2yeYsGLD0MMocHQFVMH/2Vi5agj50XezO8QIrRsDw2F4ZMQSOKIZ03XiRXsvSQ==", + "requires": { + "@solana/web3.js": "^0.87.1" + }, + "dependencies": { + "@solana/web3.js": { + "version": "0.87.2", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.87.2.tgz", + "integrity": "sha512-BtrOGfg7zUud/zLL/BxXV3E84qpXK1HknqncNQDNr8JlJ4tGc+Xsip5DzSMP/fNUJgbAWblHqWWJxFdN2mtO6A==", + "requires": { + "@babel/runtime": "^7.3.1", + "bn.js": "^5.0.0", + "bs58": "^4.0.1", + "buffer": "^6.0.1", + "buffer-layout": "^1.2.0", + "crypto-hash": "^1.2.2", + "esdoc-inject-style-plugin": "^1.0.0", + "jayson": "^3.0.1", + "keccak": "^3.0.1", + "mz": "^2.7.0", + "node-fetch": "^2.2.0", + "npm-run-all": "^4.1.5", + "rpc-websockets": "^7.4.2", + "secp256k1": "^4.0.2", + "superstruct": "^0.8.3", + "tweetnacl": "^1.0.0", + "ws": "^7.0.0" + } + }, + "superstruct": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.8.4.tgz", + "integrity": "sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==", + "requires": { + "kind-of": "^6.0.2", + "tiny-invariant": "^1.0.6" + } + } + } + }, "@emotion/cache": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.1.3.tgz", @@ -2483,6 +2526,15 @@ } } }, + "@project-serum/sol-wallet-adapter": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@project-serum/sol-wallet-adapter/-/sol-wallet-adapter-0.1.8.tgz", + "integrity": "sha512-lKMgp7bsKpkrtBtIaEjtGuUMke0GUqFUL39Z7cjqsQpTVhkU5Ez4zHyjhXqAEORRGLFbwx/+H6HLpwppxpUDMQ==", + "requires": { + "bs58": "^4.0.1", + "eventemitter3": "^4.0.4" + } + }, "@react-hook/debounce": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@react-hook/debounce/-/debounce-3.0.0.tgz", @@ -2627,6 +2679,68 @@ "@sinonjs/commons": "^1.7.0" } }, + "@solana/spl-token": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.0.13.tgz", + "integrity": "sha512-WT8M9V/hxURR5jLbhr3zgwVsgcY6m8UhHtK045w7o+jx8FJ9MKARkj387WBFU7mKiFq0k8jw/8YL7XmnIUuH8Q==", + "requires": { + "@babel/runtime": "^7.10.5", + "@solana/web3.js": "^0.86.1", + "bn.js": "^5.0.0", + "buffer-layout": "^1.2.0", + "dotenv": "8.2.0", + "mkdirp": "1.0.4" + }, + "dependencies": { + "@solana/web3.js": { + "version": "0.86.4", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.86.4.tgz", + "integrity": "sha512-FpabDmdyxBN5aHIVUWc9Q6pXJFWiLRm/xeyxFg9O9ICHjiUkd38omds7G0CAmykIccG7zaMziwtkXp+0KvQOhA==", + "requires": { + "@babel/runtime": "^7.3.1", + "bn.js": "^5.0.0", + "bs58": "^4.0.1", + "buffer": "^5.4.3", + "buffer-layout": "^1.2.0", + "crypto-hash": "^1.2.2", + "esdoc-inject-style-plugin": "^1.0.0", + "jayson": "^3.0.1", + "keccak": "^3.0.1", + "mz": "^2.7.0", + "node-fetch": "^2.2.0", + "npm-run-all": "^4.1.5", + "rpc-websockets": "^7.4.2", + "secp256k1": "^4.0.2", + "superstruct": "^0.8.3", + "tweetnacl": "^1.0.0", + "ws": "^7.0.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "superstruct": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.8.4.tgz", + "integrity": "sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==", + "requires": { + "kind-of": "^6.0.2", + "tiny-invariant": "^1.0.6" + } + } + } + }, "@solana/spl-token-registry": { "version": "0.2.64", "resolved": "https://registry.npmjs.org/@solana/spl-token-registry/-/spl-token-registry-0.2.64.tgz", @@ -4769,11 +4883,31 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, "requires": { "file-uri-to-path": "1.0.0" } }, + "bip32": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", + "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", + "requires": { + "@types/node": "10.12.18", + "bs58check": "^2.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "tiny-secp256k1": "^1.1.3", + "typeforce": "^1.11.5", + "wif": "^2.0.6" + }, + "dependencies": { + "@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + } + } + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -4821,6 +4955,58 @@ } } }, + "bonfida-bot": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/bonfida-bot/-/bonfida-bot-0.4.9.tgz", + "integrity": "sha512-gPOaQl1rFeazoteSj2PpyrRDlp4A9AuKtg7drRrm7W+DWe73p902KAs76Vs3XlS+aQjF2kWlSLqBGsC79iijgA==", + "requires": { + "@dr497/awesome-serum-markets": "^1.1.16", + "@project-serum/serum": "^0.13.30", + "@project-serum/sol-wallet-adapter": "^0.1.5", + "@solana/spl-token": "0.0.13", + "@solana/web3.js": "^0.94.2", + "bip32": "^2.0.6", + "bn.js": "^5.1.3", + "bs58": "4.0.1", + "buffer-layout": "^1.2.0", + "tweetnacl": "^1.0.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.17", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz", + "integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@solana/web3.js": { + "version": "0.94.2", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-0.94.2.tgz", + "integrity": "sha512-enJZ9eVJMvNtpuXdygAZHBlPC+2Q3paLY+KforFhVUpi/bkBADDKJWd90RICyu3sPKiVt8YLAs9cIxriQpQqng==", + "requires": { + "@babel/runtime": "^7.12.5", + "bn.js": "^5.0.0", + "bs58": "^4.0.1", + "buffer": "6.0.1", + "buffer-layout": "^1.2.0", + "crypto-hash": "^1.2.2", + "jayson": "^3.4.4", + "js-sha3": "^0.8.0", + "node-fetch": "^2.6.1", + "rpc-websockets": "^7.4.2", + "secp256k1": "^4.0.2", + "superstruct": "^0.14.2", + "tweetnacl": "^1.0.0" + } + }, + "superstruct": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.14.2.tgz", + "integrity": "sha512-nPewA6m9mR3d6k7WkZ8N8zpTWfenFH3q9pA2PkuiZxINr9DKB2+40wEQf0ixn8VaGuJ78AB6iWOtStI+/4FKZQ==" + } + } + }, "bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", @@ -4958,6 +5144,16 @@ "base-x": "^3.0.2" } }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -8055,8 +8251,7 @@ "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, "filesize": { "version": "6.1.0", @@ -18218,6 +18413,25 @@ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" }, + "tiny-secp256k1": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", + "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", + "requires": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + } + } + }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", @@ -18417,6 +18631,11 @@ "is-typedarray": "^1.0.0" } }, + "typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, "typescript": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", @@ -19766,6 +19985,14 @@ "string-width": "^1.0.2 || 2" } }, + "wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "requires": { + "bs58check": "<3.0.0" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/explorer/package.json b/explorer/package.json index 66f740100d..154774e370 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -26,6 +26,7 @@ "@types/socket.io-client": "^1.4.36", "bignumber.js": "^9.0.1", "bn.js": "^5.2.0", + "bonfida-bot": "^0.4.9", "bootstrap": "^4.6.0", "bs58": "^4.0.1", "chai": "^4.3.4", diff --git a/explorer/src/components/account/TokenHistoryCard.tsx b/explorer/src/components/account/TokenHistoryCard.tsx index 4a7d8e1cac..0a129ba689 100644 --- a/explorer/src/components/account/TokenHistoryCard.tsx +++ b/explorer/src/components/account/TokenHistoryCard.tsx @@ -39,6 +39,10 @@ import { isSerumInstruction, parseSerumInstructionTitle, } from "components/instruction/serum/types"; +import { + isBonfidaBotInstruction, + parseBonfidaBotInstructionTitle, +} from "components/instruction/bonfida-bot/types"; import { INNER_INSTRUCTIONS_START_SLOT } from "pages/TransactionDetailsPage"; import { useCluster, Cluster } from "providers/cluster"; import { Link } from "react-router-dom"; @@ -488,6 +492,16 @@ const TokenTransactionRow = React.memo( reportError(error, { signature: tx.signature }); return undefined; } + } else if ( + transactionInstruction && + isBonfidaBotInstruction(transactionInstruction) + ) { + try { + name = parseBonfidaBotInstructionTitle(transactionInstruction); + } catch (error) { + reportError(error, { signature: tx.signature }); + return undefined; + } } else { if ( ix.accounts.findIndex((account) => diff --git a/explorer/src/components/instruction/BonfidaBotDetails.tsx b/explorer/src/components/instruction/BonfidaBotDetails.tsx new file mode 100644 index 0000000000..c74ca29ebe --- /dev/null +++ b/explorer/src/components/instruction/BonfidaBotDetails.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { TransactionInstruction, SignatureResult } from "@solana/web3.js"; +import { InstructionCard } from "./InstructionCard"; +import { useCluster } from "providers/cluster"; +import { reportError } from "utils/sentry"; +import { + decodeCancelOrder, + decodeInitializeBot, + decodeCreateBot, + decodeDeposit, + decodeCreateOrder, + decodeSettleFunds, + decodeRedeem, + decodeCollectFees, + parseBonfidaBotInstructionTitle, +} from "./bonfida-bot/types"; +import { CancelOrderDetailsCard } from "./bonfida-bot/CancelOrderDetails"; +import { CollectFeesDetailsCard } from "./bonfida-bot/CollectFeesDetails"; +import { CreateBotDetailsCard } from "./bonfida-bot/CreateBotDetails"; +import { DepositDetailsCard } from "./bonfida-bot/DepositDetails"; +import { InitializeBotDetailsCard } from "./bonfida-bot/InitializeBotDetails"; +import { RedeemDetailsCard } from "./bonfida-bot/RedeemDetails"; +import { SettleFundsDetailsCard } from "./bonfida-bot/SettleFundsDetails"; +import { CreateOrderDetailsCard } from "./bonfida-bot/CreateOrderDetails"; + +export function BonfidaBotDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + signature: string; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, signature, innerCards, childIndex } = props; + + const { url } = useCluster(); + + let title; + try { + title = parseBonfidaBotInstructionTitle(ix); + + switch (title) { + case "Initialize Bot": + return ( + + ); + case "Create Bot": + return ; + case "Deposit": + return ; + case "Create Order": + return ( + + ); + case "Cancel Order": + return ( + + ); + case "Settle Funds": + return ( + + ); + case "settleFunds": + return ( + + ); + case "Redeem": + return ; + case "Collect Fees": + return ( + + ); + } + } catch (error) { + reportError(error, { + url: url, + signature: signature, + }); + } + + return ( + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/CancelOrderDetails.tsx b/explorer/src/components/instruction/bonfida-bot/CancelOrderDetails.tsx new file mode 100644 index 0000000000..d2da2ec33b --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/CancelOrderDetails.tsx @@ -0,0 +1,105 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { CancelOrder } from "./types"; + +export function CancelOrderDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: CancelOrder; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + + return ( + + + Program + +
+ + + + + Market + +
+ + + + + Signal Provider Address + +
+ + + + + Open Orders + +
+ + + + + Serum Event Queue + +
+ + + + + Serum Bids + +
+ + + + + Serum Asks + +
+ + + + + Bot Address + +
+ + + + + Serum Program ID + +
+ + + + + Pool Seed + {info.poolSeed} + + + + Side + {info.side} + + + + Order Id + {info.orderId.toString(10)} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/CollectFeesDetails.tsx b/explorer/src/components/instruction/bonfida-bot/CollectFeesDetails.tsx new file mode 100644 index 0000000000..2deb771cd4 --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/CollectFeesDetails.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { CollectFees } from "./types"; + +export function CollectFeesDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: CollectFees; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + + return ( + + + Program + +
+ + + + + Signal Provider + +
+ + + + + Insurance Fund + +
+ + + + + Buy and Burn + +
+ + + + + Pool Seed + {info.poolSeed} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/CreateBotDetails.tsx b/explorer/src/components/instruction/bonfida-bot/CreateBotDetails.tsx new file mode 100644 index 0000000000..bd3c466eaf --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/CreateBotDetails.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { CreateBot } from "./types"; + +export function CreateBotDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: CreateBot; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + + return ( + + + Program + +
+ + + + + Bot Token Mint + +
+ + + + + Bot Address + +
+ + + + + Target Pool Token Address + +
+ + + + + Serum Program ID + +
+ + + + + Signal Provider Address + +
+ + + + + Pool Seed + {info.poolSeed} + + + + Fee Ratio + {info.feeRatio} + + + + Fee Collection Period + {info.feeCollectionPeriod} + + + + Serum Markets + {info.markets} + + + + Deposit Amounts + {info.depositAmounts} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/CreateOrderDetails.tsx b/explorer/src/components/instruction/bonfida-bot/CreateOrderDetails.tsx new file mode 100644 index 0000000000..ce1672dd47 --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/CreateOrderDetails.tsx @@ -0,0 +1,164 @@ +import React from "react"; +import { + SignatureResult, + TransactionInstruction, + PublicKey, +} from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { CreateOrder } from "./types"; + +export function CreateOrderDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: CreateOrder; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + console.log("Test"); + return ( + + + Program + +
+ + + + + Signal Provider Address + +
+ + + + + Market + +
+ + + + + Payer Bot Asset Address + +
+ + + + + Open Order + +
+ + + + + Serum Request Queue + +
+ + + + + Serum Event Queue + +
+ + + + + Serum Bids + +
+ + + + + Serum Asks + +
+ + + + + Bot Address + +
+ + + + + Coin Vault + +
+ + + + + Pc Vault + +
+ + + + + Serum Program ID + +
+ + + + + Bot Token Mint + +
+ + + + + Pool Seed + {info.poolSeed} + + + + Side + {info.side} + + + + Limit Price + {info.limitPrice} + + + + Ratio to Trade + {info.ratioOfPoolAssetsToTrade} + + + + Order Type + {info.orderType} + + + + Coin Lot Size + {info.coinLotSize.toString()} + + + + Pc Lot Size + {info.pcLotSize.toString()} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/DepositDetails.tsx b/explorer/src/components/instruction/bonfida-bot/DepositDetails.tsx new file mode 100644 index 0000000000..22a0aea14d --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/DepositDetails.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { Deposit } from "./types"; + +export function DepositDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: Deposit; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + + return ( + + + Program + +
+ + + + + Signal Provider Fee Address + +
+ + + + + Insurance Funds + +
+ + + + + Buy and Burn + +
+ + + + + Bot Token Mint + +
+ + + + + Bot Address + +
+ + + + + Target Pool Token Address + +
+ + + + + Pool Seed + {info.poolSeed} + + + + Pool Token Amount + {info.poolTokenAmount.toString()} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/InitializeBotDetails.tsx b/explorer/src/components/instruction/bonfida-bot/InitializeBotDetails.tsx new file mode 100644 index 0000000000..804d900826 --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/InitializeBotDetails.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { InitializeBot } from "./types"; + +export function InitializeBotDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: InitializeBot; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + + return ( + + + Program + +
+ + + + + Pool Account + +
+ + + + + Mint Account + +
+ + + + + Pool Seed + {info.poolSeed} + + + + Max Number of Assets + {info.maxNumberOfAsset} + + + + Number of Markets + {info.numberOfMarkets} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/RedeemDetails.tsx b/explorer/src/components/instruction/bonfida-bot/RedeemDetails.tsx new file mode 100644 index 0000000000..edf424cfc4 --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/RedeemDetails.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { Redeem } from "./types"; + +export function RedeemDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: Redeem; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + + return ( + + + Program + +
+ + + + + Bot Token Mint + +
+ + + + + Bot Address + +
+ + + + + Source Bot Token Owner + +
+ + + + + Source Bot Token Address + +
+ + + + + Pool Seed + {info.poolSeed} + + + + Pool Token Amount + {info.poolTokenAmount} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/SettleFundsDetails.tsx b/explorer/src/components/instruction/bonfida-bot/SettleFundsDetails.tsx new file mode 100644 index 0000000000..dc0d24ba7e --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/SettleFundsDetails.tsx @@ -0,0 +1,108 @@ +import React from "react"; +import { SignatureResult, TransactionInstruction } from "@solana/web3.js"; +import { InstructionCard } from "../InstructionCard"; +import { Address } from "components/common/Address"; +import { SettleFunds } from "./types"; + +export function SettleFundsDetailsCard(props: { + ix: TransactionInstruction; + index: number; + result: SignatureResult; + info: SettleFunds; + innerCards?: JSX.Element[]; + childIndex?: number; +}) { + const { ix, index, result, info, innerCards, childIndex } = props; + return ( + + + Program + +
+ + + + + Market + +
+ + + + + Open Orders + +
+ + + + + Bot Address + +
+ + + + + Bot Token Mint + +
+ + + + + Coin Vault + +
+ + + + + Pc Vault + +
+ + + + + Bot's Coin Address + +
+ + + + + Bot's Pc Address + +
+ + + + + Vault Signer + +
+ + + + + Serum Program ID + +
+ + + + + Pool Seed + {info.poolSeed} + + + ); +} diff --git a/explorer/src/components/instruction/bonfida-bot/types.ts b/explorer/src/components/instruction/bonfida-bot/types.ts new file mode 100644 index 0000000000..3bcfb1ff52 --- /dev/null +++ b/explorer/src/components/instruction/bonfida-bot/types.ts @@ -0,0 +1,433 @@ +/* eslint-disable @typescript-eslint/no-redeclare */ +import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import { + enums, + number, + type, + Infer, + create, + array, + string, + optional, + coerce, + any, +} from "superstruct"; +import { + Instruction, + decodeInstruction, + BONFIDABOT_PROGRAM_ID, +} from "bonfida-bot"; + +export const SERUM_DECODED_MAX = 6; + +export type Side = Infer; +export const Side = enums([0, 1]); + +export type OrderType = Infer; +export const OrderType = enums([0, 1, 2]); + +const PublicKeyToString = coerce(string(), any(), (value) => value.toBase58()); + +export type InitializeBot = { + systemProgramAccount: PublicKey; + rentSysvarAccount: PublicKey; + splTokenProgramAccount: PublicKey; + poolAccount: PublicKey; + mintAccount: PublicKey; + payerAccount: PublicKey; + + poolSeed: string; + maxNumberOfAsset: number | undefined | null; + numberOfMarkets: number; + programId: PublicKey; +}; + +export const InitializeBotDecode = type({ + poolSeed: string(), + maxNumberOfAsset: optional(number()), + numberOfMarkets: number(), +}); + +export const decodeInitializeBot = ( + ix: TransactionInstruction +): InitializeBot => { + const decoded = create( + decodeInstruction(ix.data, Instruction.Init), + InitializeBotDecode + ); + const initializeBot: InitializeBot = { + systemProgramAccount: ix.keys[0].pubkey, + rentSysvarAccount: ix.keys[1].pubkey, + splTokenProgramAccount: ix.keys[2].pubkey, + poolAccount: ix.keys[3].pubkey, + mintAccount: ix.keys[4].pubkey, + payerAccount: ix.keys[5].pubkey, + poolSeed: decoded.poolSeed, + maxNumberOfAsset: decoded.maxNumberOfAsset, + numberOfMarkets: decoded.numberOfMarkets, + programId: ix.programId, + }; + + return initializeBot; +}; + +export type CreateBot = { + splTokenProgramId: PublicKey; + clockSysvarKey: PublicKey; + mintKey: PublicKey; + poolKey: PublicKey; + poolSeed: string; + targetPoolTokenKey: PublicKey; + serumProgramId: PublicKey; + signalProviderKey: PublicKey; + depositAmounts: number[] | undefined | null; + markets: string[] | undefined | null; + feeCollectionPeriod: number; + feeRatio: number; + programId: PublicKey; +}; + +export const CreateBotDecode = type({ + poolSeed: string(), + feeCollectionPeriod: number(), + feeRatio: number(), + depositAmounts: array(number()), + markets: array(PublicKeyToString), +}); + +export const decodeCreateBot = (ix: TransactionInstruction): CreateBot => { + const decoded = create( + decodeInstruction(ix.data, Instruction.Create), + CreateBotDecode + ); + const createBot: CreateBot = { + splTokenProgramId: ix.keys[0].pubkey, + clockSysvarKey: ix.keys[1].pubkey, + mintKey: ix.keys[4].pubkey, + poolKey: ix.keys[6].pubkey, + poolSeed: decoded.poolSeed, + targetPoolTokenKey: ix.keys[5].pubkey, + serumProgramId: ix.keys[2].pubkey, + signalProviderKey: ix.keys[3].pubkey, + depositAmounts: decoded.depositAmounts, + markets: decoded.markets, + feeCollectionPeriod: decoded.feeCollectionPeriod, + feeRatio: decoded.feeRatio, + programId: ix.programId, + }; + + return createBot; +}; + +export type Deposit = { + splTokenProgramId: PublicKey; + programId: PublicKey; + sigProviderFeeReceiverKey: PublicKey; + bonfidaFeeReceiverKey: PublicKey; + bonfidaBuyAndBurnKey: PublicKey; + mintKey: PublicKey; + poolKey: PublicKey; + targetPoolTokenKey: PublicKey; + poolSeed: string; + poolTokenAmount: number; +}; + +export const DepositDecode = type({ + poolSeed: string(), + poolTokenAmount: number(), +}); + +export const decodeDeposit = (ix: TransactionInstruction): Deposit => { + const decoded = create( + decodeInstruction(ix.data, Instruction.Deposit), + DepositDecode + ); + + const deposit: Deposit = { + splTokenProgramId: ix.keys[0].pubkey, + programId: ix.programId, + sigProviderFeeReceiverKey: ix.keys[3].pubkey, + bonfidaFeeReceiverKey: ix.keys[4].pubkey, + bonfidaBuyAndBurnKey: ix.keys[5].pubkey, + mintKey: ix.keys[1].pubkey, + poolKey: ix.keys[6].pubkey, + targetPoolTokenKey: ix.keys[2].pubkey, + poolSeed: decoded.poolSeed, + poolTokenAmount: decoded.poolTokenAmount, + }; + + return deposit; +}; + +export type CreateOrder = { + programId: PublicKey; + signalProviderKey: PublicKey; + market: PublicKey; + payerPoolAssetKey: PublicKey; + openOrdersKey: PublicKey; + serumRequestQueue: PublicKey; + serumEventQueue: PublicKey; + serumMarketBids: PublicKey; + serumMarketAsks: PublicKey; + poolKey: PublicKey; + coinVaultKey: PublicKey; + pcVaultKey: PublicKey; + splTokenProgramId: PublicKey; + dexProgramKey: PublicKey; + rentProgramId: PublicKey; + srmReferrerKey: PublicKey | null | undefined; + poolSeed: string; + side: Side; + limitPrice: number; + ratioOfPoolAssetsToTrade: number; + orderType: OrderType; + clientId: number; + coinLotSize: number; + pcLotSize: number; + targetMint: string; +}; + +export const CreateDecode = type({ + poolSeed: string(), + side: Side, + limitPrice: number(), + ratioOfPoolAssetsToTrade: number(), + orderType: OrderType, + clientId: number(), + coinLotSize: number(), + pcLotSize: number(), + targetMint: string(), +}); + +export const decodeCreateOrder = (ix: TransactionInstruction): CreateOrder => { + const decoded = create( + decodeInstruction(ix.data, Instruction.CreateOrder), + CreateDecode + ); + + const createOrder: CreateOrder = { + programId: ix.programId, + signalProviderKey: ix.keys[0].pubkey, + market: ix.keys[1].pubkey, + payerPoolAssetKey: ix.keys[2].pubkey, + openOrdersKey: ix.keys[3].pubkey, + serumRequestQueue: ix.keys[5].pubkey, + serumEventQueue: ix.keys[4].pubkey, + serumMarketBids: ix.keys[6].pubkey, + serumMarketAsks: ix.keys[7].pubkey, + poolKey: ix.keys[8].pubkey, + coinVaultKey: ix.keys[9].pubkey, + pcVaultKey: ix.keys[10].pubkey, + splTokenProgramId: ix.keys[11].pubkey, + dexProgramKey: ix.keys[13].pubkey, + rentProgramId: ix.keys[12].pubkey, + srmReferrerKey: ix.keys[14]?.pubkey, + // Miss maxQuantity + // + poolSeed: decoded.poolSeed, + side: decoded.side, + limitPrice: decoded.limitPrice, + ratioOfPoolAssetsToTrade: decoded.ratioOfPoolAssetsToTrade, + orderType: decoded.orderType, + clientId: decoded.clientId, + coinLotSize: decoded.coinLotSize, + pcLotSize: decoded.pcLotSize, + targetMint: decoded.targetMint, + }; + + return createOrder; +}; + +export type CancelOrder = { + programId: PublicKey; + signalProviderKey: PublicKey; + market: PublicKey; + openOrdersKey: PublicKey; + serumEventQueue: PublicKey; + serumMarketBids: PublicKey; + serumMarketAsks: PublicKey; + poolKey: PublicKey; + dexProgramKey: PublicKey; + poolSeed: string; + side: Side; + orderId: number; +}; + +export const CancelOrderDecode = type({ + poolSeed: string(), + side: Side, + orderId: number(), +}); + +export const decodeCancelOrder = (ix: TransactionInstruction): CancelOrder => { + const decoded = create( + decodeInstruction(ix.data, Instruction.CancelOrder), + CancelOrderDecode + ); + + const cancelOrder: CancelOrder = { + programId: ix.programId, + signalProviderKey: ix.keys[0].pubkey, + market: ix.keys[1].pubkey, + openOrdersKey: ix.keys[2].pubkey, + serumEventQueue: ix.keys[3].pubkey, + serumMarketBids: ix.keys[4].pubkey, + serumMarketAsks: ix.keys[5].pubkey, + poolKey: ix.keys[6].pubkey, + dexProgramKey: ix.keys[7].pubkey, + // + poolSeed: decoded.poolSeed, + side: decoded.side, + orderId: decoded.orderId, + }; + return cancelOrder; +}; + +export type SettleFunds = { + programId: PublicKey; + market: PublicKey; + openOrdersKey: PublicKey; + poolKey: PublicKey; + poolMintKey: PublicKey; + coinVaultKey: PublicKey; + pcVaultKey: PublicKey; + coinPoolAssetKey: PublicKey; + pcPoolAssetKey: PublicKey; + vaultSignerKey: PublicKey; + splTokenProgramId: PublicKey; + dexProgramKey: PublicKey; + srmReferrerKey: PublicKey | null; + poolSeed: string; +}; + +export const SettleFundsDecode = type({ + poolSeed: string(), + pcIndex: number(), + orderId: optional(number()), +}); + +export const decodeSettleFunds = (ix: TransactionInstruction): SettleFunds => { + const decoded = create( + decodeInstruction(ix.data, Instruction.SettleFunds), + SettleFundsDecode + ); + + const settleFunds: SettleFunds = { + programId: ix.programId, + market: ix.keys[0].pubkey, + openOrdersKey: ix.keys[1].pubkey, + poolKey: ix.keys[2].pubkey, + poolMintKey: ix.keys[3].pubkey, + coinVaultKey: ix.keys[4].pubkey, + pcVaultKey: ix.keys[5].pubkey, + coinPoolAssetKey: ix.keys[6].pubkey, + pcPoolAssetKey: ix.keys[7].pubkey, + vaultSignerKey: ix.keys[8].pubkey, + splTokenProgramId: ix.keys[9].pubkey, + dexProgramKey: ix.keys[10].pubkey, + srmReferrerKey: ix.keys[11]?.pubkey, + poolSeed: decoded.poolSeed, + }; + return settleFunds; +}; + +export type Redeem = { + splTokenProgramId: PublicKey; + programId: PublicKey; + mintKey: PublicKey; + poolKey: PublicKey; + sourcePoolTokenOwnerKey: PublicKey; + sourcePoolTokenKey: PublicKey; + poolSeed: string; + poolTokenAmount: number; +}; + +export const RedeemDecode = type({ + poolSeed: string(), + poolTokenAmount: number(), +}); + +export const decodeRedeem = (ix: TransactionInstruction): Redeem => { + const decoded = create( + decodeInstruction(ix.data, Instruction.Redeem), + RedeemDecode + ); + + const redeem: Redeem = { + programId: ix.programId, + splTokenProgramId: ix.keys[0].pubkey, + mintKey: ix.keys[2].pubkey, + poolKey: ix.keys[5].pubkey, + sourcePoolTokenOwnerKey: ix.keys[3].pubkey, + sourcePoolTokenKey: ix.keys[4].pubkey, + poolSeed: decoded.poolSeed, + poolTokenAmount: decoded.poolTokenAmount, + }; + + return redeem; +}; + +export type CollectFees = { + splTokenProgramId: PublicKey; + clockSysvarKey: PublicKey; + programId: PublicKey; + poolKey: PublicKey; + mintKey: PublicKey; + signalProviderPoolTokenKey: PublicKey; + bonfidaFeePoolTokenKey: PublicKey; + bonfidaBnBPTKey: PublicKey; + poolSeed: string; +}; + +export const CollectFeesDecode = type({ + poolSeed: string(), +}); + +export const decodeCollectFees = (ix: TransactionInstruction): CollectFees => { + const decoded = create( + decodeInstruction(ix.data, Instruction.CollectFees), + CollectFeesDecode + ); + + const collectFees: CollectFees = { + programId: ix.programId, + splTokenProgramId: ix.keys[0].pubkey, + clockSysvarKey: ix.keys[1].pubkey, + poolKey: ix.keys[2].pubkey, + mintKey: ix.keys[3].pubkey, + signalProviderPoolTokenKey: ix.keys[4].pubkey, + bonfidaFeePoolTokenKey: ix.keys[5].pubkey, + bonfidaBnBPTKey: ix.keys[6].pubkey, + poolSeed: decoded.poolSeed, + }; + + return collectFees; +}; + +export const isBonfidaBotInstruction = ( + instruction: TransactionInstruction +) => { + return instruction.programId.equals(BONFIDABOT_PROGRAM_ID); +}; + +export const INSTRUCTION_LOOKUP: { [key: number]: string } = { + 0: "Initialize Bot", + 1: "Create Bot", + 2: "Deposit", + 3: "Create Order", + 4: "Cancel Order", + 5: "Settle Funds", + 6: "Redeem", + 7: "Collect Fees", +}; + +export const parseBonfidaBotInstructionTitle = ( + instruction: TransactionInstruction +): string => { + const code = instruction.data[0]; + + if (!(code in INSTRUCTION_LOOKUP)) { + throw new Error(`Unrecognized Bonfida Bot instruction code: ${code}`); + } + return INSTRUCTION_LOOKUP[code]; +}; diff --git a/explorer/src/components/transaction/InstructionsSection.tsx b/explorer/src/components/transaction/InstructionsSection.tsx index 503352b9fb..701ad2e4fd 100644 --- a/explorer/src/components/transaction/InstructionsSection.tsx +++ b/explorer/src/components/transaction/InstructionsSection.tsx @@ -20,6 +20,7 @@ import { TokenLendingDetailsCard } from "components/instruction/TokenLendingDeta import { TokenSwapDetailsCard } from "components/instruction/TokenSwapDetailsCard"; import { WormholeDetailsCard } from "components/instruction/WormholeDetailsCard"; import { UnknownDetailsCard } from "components/instruction/UnknownDetailsCard"; +import { BonfidaBotDetailsCard } from "components/instruction/BonfidaBotDetails"; import { SignatureProps, INNER_INSTRUCTIONS_START_SLOT, @@ -28,6 +29,7 @@ import { intoTransactionInstruction } from "utils/tx"; import { isSerumInstruction } from "components/instruction/serum/types"; import { isTokenLendingInstruction } from "components/instruction/token-lending/types"; import { isTokenSwapInstruction } from "components/instruction/token-swap/types"; +import { isBonfidaBotInstruction } from "components/instruction/bonfida-bot/types"; import { useFetchTransactionDetails } from "providers/transactions/details"; import { useTransactionDetails, @@ -208,7 +210,9 @@ function renderInstructionCard({ childIndex, }; - if (isSerumInstruction(transactionIx)) { + if (isBonfidaBotInstruction(transactionIx)) { + return ; + } else if (isSerumInstruction(transactionIx)) { return ; } else if (isTokenSwapInstruction(transactionIx)) { return ; diff --git a/explorer/src/utils/instruction.ts b/explorer/src/utils/instruction.ts index 36a13469e5..5e9bd038b0 100644 --- a/explorer/src/utils/instruction.ts +++ b/explorer/src/utils/instruction.ts @@ -24,6 +24,10 @@ import { isSerumInstruction, parseSerumInstructionTitle, } from "components/instruction/serum/types"; +import { + isBonfidaBotInstruction, + parseBonfidaBotInstructionTitle, +} from "components/instruction/bonfida-bot/types"; import { TOKEN_PROGRAM_ID } from "providers/accounts/tokens"; export type InstructionType = { @@ -105,6 +109,16 @@ export function getTokenInstructionName( } else { return undefined; } + } else if ( + transactionInstruction && + isBonfidaBotInstruction(transactionInstruction) + ) { + try { + name = parseBonfidaBotInstructionTitle(transactionInstruction); + } catch (error) { + reportError(error, { signature: signatureInfo.signature }); + return undefined; + } } else if ( transactionInstruction && isSerumInstruction(transactionInstruction)