├── .dockerignore ├── .env.example ├── .gitignore ├── .gplconfig ├── .graphqlconfig.yml ├── .graphqlrc ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── nodemon.json ├── package-lock.json ├── package.json ├── scripts └── convert_schema.ts ├── src ├── resolverMap.ts ├── resolvers │ ├── block-resolver.ts │ ├── contract-resolver.ts │ ├── delegate-resolver.ts │ ├── enum-resolver.ts │ ├── internal-operation-result-resolver.ts │ ├── operation-contents-resolver.ts │ ├── operation-resolver.ts │ ├── queries │ │ └── blocks │ │ │ ├── block-query-resolver.ts │ │ │ └── blocks-query-resolver.ts │ ├── scalars │ │ ├── base58-resolvers.ts │ │ └── mutez-resolver.ts │ └── utils.ts ├── schema.ts ├── schema │ └── schema.graphql ├── server.ts ├── services │ └── tezos-service.ts └── types │ └── types.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | scripts -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_KEY=random_hash 2 | ENABLE_API_KEY=false 3 | ENABLE_TEZOS_NODE_HEALTHCHECK=false 4 | GRAPHQL_ENABLE_INTROSPECTION=true 5 | GRAPHQL_ENABLE_PLAYGROUND=true 6 | HOST=0.0.0.0 7 | MAX_BLOCKS=5 8 | PORT=3000 9 | TEZOS_BAKING_RIGHTS_MAX_PRIORITY=5 10 | TEZOS_NODE=https://api.tezos.org.ua 11 | TEZOS_RIGHTS_MAX_CYCLES=5 12 | TEZOS_RIGHTS_MAX_LEVELS=20480 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | tmp 15 | 16 | # Build 17 | public/css/main.css 18 | 19 | # Coverage reports 20 | coverage 21 | 22 | # API keys and secrets 23 | .env 24 | 25 | # Dependency directory 26 | node_modules 27 | bower_components 28 | 29 | # Editors 30 | .idea 31 | *.iml 32 | 33 | # OS metadata 34 | .DS_Store 35 | Thumbs.db 36 | 37 | # Ignore built ts files 38 | dist/**/* 39 | -------------------------------------------------------------------------------- /.gplconfig: -------------------------------------------------------------------------------- 1 | { 2 | schema: { 3 | files: 'src/schema/*.graphql' 4 | } 5 | } -------------------------------------------------------------------------------- /.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | app: 3 | schemaPath: src/schema/schema.graphql 4 | includes: ["**/*.graphql"] 5 | extensions: 6 | endpoints: 7 | default: http://localhost:4000 -------------------------------------------------------------------------------- /.graphqlrc: -------------------------------------------------------------------------------- 1 | { 2 | "request" : { 3 | "url": "http://localhost:3000/graphql" 4 | } 5 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 160, 3 | "tabWidth": 4, 4 | "trailingComma": "es5", 5 | "arrowParens": "avoid", 6 | "singleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "Node: Nodemon", 8 | // "processId": "${command:PickProcess}", 9 | "port": 9229, 10 | "restart": true, 11 | "protocol": "inspector", 12 | }, 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib", 3 | "editor.formatOnSave": true, 4 | "[typescript]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:13.12.0-alpine3.11 2 | 3 | # Default Env variables 4 | ENV PORT 3000 5 | ENV HOST 0.0.0.0 6 | ENV TEZOS_NODE https://api.tezos.org.ua 7 | ENV MAX_BLOCKS 5 8 | ENV GRAPHQL_ENABLE_PLAYGROUND=true 9 | ENV GRAPHQL_ENABLE_INTROSPECTION=true 10 | 11 | WORKDIR /home/node/app 12 | COPY package*.json ./ 13 | ENV NODE_ENV=production 14 | 15 | #RUN npm ci --only=production 16 | RUN npm i 17 | COPY . . 18 | RUN npm run build 19 | USER node 20 | EXPOSE 3000 21 | CMD [ "npm", "run", "start" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 agile-ventures 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TaaS GraphQL 2 | 3 | ## About TaaS GraphQL 4 | 5 | TaaS GraphQL provides [GraphQL](https://graphql.org/) API interface to [Tezos Node RPC API](https://tezos.gitlab.io/api/rpc.html) based on [TZIP-14](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-14/tzip-14.md). 6 | 7 | ## Documentation 8 | 9 | > [https://docs.tezoslive.io/docs-welcome](https://docs.tezoslive.io/docs-welcome) 10 | 11 | ## Running TaaS GraphQL in Docker 12 | 13 | **Requirements** 14 | 15 | - Docker 16 | - Tezos Node with enabled RPC endpoint supporting read-only calls 17 | 18 | Ready-to-use docker image is available from Docker Hub here: 19 | [**https://hub.docker.com/r/tezoslive/taas-graphql**](https://hub.docker.com/r/tezoslive/taas-graphql) 20 | 21 | Example of the `docker run` command 22 | 23 | - exposing port 3000 24 | - setting the Tezos:NodeUrl environment variable to [https://api.tezos.org.ua](https://api.tezos.org.ua) 25 | 26 | Do not forget to change the **Tezos:NodeUrl** based on your configuration! 27 | 28 | ```bash 29 | docker run --rm -it -p 3000:3000 \ 30 | --env TEZOS_NODE="https://api.tezos.org.ua" \ 31 | tezoslive/taas-graphql 32 | ``` 33 | 34 | ### Optional Configuration 35 | 36 | By providing the following ENV variables you can override default configuration. 37 | 38 | ```bash 39 | docker run --rm -it -p 3000:3000 \ 40 | --env PORT="3000" \ 41 | --env TEZOS_NODE="https://api.tezos.org.ua" \ 42 | --env MAX_BLOCKS="5" \ 43 | --env GRAPHQL_ENABLE_PLAYGROUND="true" \ 44 | --env GRAPHQL_ENABLE_INTROSPECTION="true" \ 45 | --env ENABLE_API_KEY="true" \ 46 | --env API_KEY="random!@#123String" \ 47 | tezoslive/taas-graphql 48 | ``` 49 | 50 | - `PORT` configuration for the port on which NodeJS server listens. Default is `3000`. 51 | - `TEZOS_NODE` configuration for the Tezos Node RPC API endpoint. Default is [`https://api.tezos.org.ua`](https://api.tezos.org.ua) 52 | - `MAX_BLOCKS` configuration for the maximum number of blocks that can be fetched using `blocks` GraphQL query. Default is `5`. 53 | - `GRAPHQL_ENABLE_PLAYGROUND` configuration for the GraphQL playground. Default is `true`. It is recommend as best practice to disable this in production environment by Apollo. 54 | - `GRAPHQL_ENABLE_INTROSPECTION` configuration for the GraphQL introspection. Default is `true`. It is recommend as best practice to disable this in production environment by Apollo. 55 | - `ENABLE_API_KEY` if enabled API Key is required with each request. API key needs to be provided in the `X-TaaS-Key` header. Default is `false`. 56 | - `API_KEY` configures the API key value. There is no default value. 57 | 58 | ### Testing your GraphQL API endpoint 59 | 60 | If you have used default port number \(3000\) and exposed the port using `docker run` command mentioned above, you should be able to access the following URL in the browser \(with enabled `GRAPHQL_ENABLE_PLAYGROUND`\). 61 | 62 | > [htp://127.0.0.1:3000/graphql](htp://127.0.0.1:3000/graphql) 63 | 64 | ### GraphQL Schema 65 | 66 | You can take a look at the schema in the GraphQL playground or on the link below. 67 | 68 | > [https://github.com/agile-ventures/TaaS-docs/blob/master/docs-graphql/schema.graphql](https://github.com/agile-ventures/TaaS-docs/blob/master/docs-graphql/schema.graphql) 69 | 70 | ### Performance & Caching 71 | 72 | If you need a caching layer between your GraphQL Docker container and your Tezos Node, you can use TezProxy. 73 | 74 | > [https://github.com/tezexInfo/TezProxy](https://github.com/tezexInfo/TezProxy) 75 | 76 | ### Security & Deployment 77 | 78 | For a simple use case you can enable single API key directly on the TaaS GraphQL cointainer with 79 | 80 | - `ENABLE_API_KEY` 81 | - `API_KEY` 82 | 83 | If you need more complex solution, like JWT token authentication, rate limiting \(throttling\) or load balancing please take a look at the following resources. 84 | 85 | - [https://www.haproxy.com/blog/using-haproxy-as-an-api-gateway-part-1/](https://www.haproxy.com/blog/using-haproxy-as-an-api-gateway-part-1/) 86 | 87 | - [https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-jwt-authentication/](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-jwt-authentication/) 88 | 89 | - [https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-to-api.html](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-to-api.html) 90 | 91 | - [https://docs.microsoft.com/en-us/azure/api-management/api-management-key-concepts](https://docs.microsoft.com/en-us/azure/api-management/api-management-key-concepts) 92 | 93 | One possible scenario if you opt out to have TaaS deployed on VMs or dedicated servers could be to have HAProxy load balancer configure with JWT authentication running in from of the TaaS docker containers. 94 | 95 | There are multiple options in case you will opt out to host your TaaS GraphQL stack in the cloud running in the serverless mode \(AWS Lambda, Azure Functions, Cloudflare Workers, etc.\) and the best solution will depend on your specific cloud provider. 96 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "restartable": "rs", 3 | "ignore": [".git", "node_modules/**/node_modules"], 4 | "verbose": true, 5 | "execMap": { 6 | "ts": "node --require ts-node/register" 7 | }, 8 | "watch": [".env", "src/"], 9 | "env": { 10 | "NODE_ENV": "development" 11 | }, 12 | "ext": "js,json,ts,graphql,env" 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taas-graphql", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node dist/server.js", 8 | "build": "tsc -p . && ncp src/schema dist/schema", 9 | "start:dev": "npm run build:dev", 10 | "build:dev": "nodemon src/server.ts --exec ts-node src/server.ts -e ts,graphql", 11 | "debug": "nodemon --inspect src/server.ts" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@playlyfe/gql": "^2.6.2", 18 | "@taquito/michelson-encoder": "^v6.1.1-beta.0", 19 | "@taquito/rpc": "^v6.1.1-beta.0", 20 | "@taquito/taquito": "^v6.1.1-beta.0", 21 | "@taquito/utils": "^6.1.1-beta.0", 22 | "@types/axios": "^0.14.0", 23 | "@types/compression": "^1.0.1", 24 | "@types/express": "^4.17.2", 25 | "@types/graphql": "^14.5.0", 26 | "@types/graphql-depth-limit": "^1.1.2", 27 | "@types/graphql-iso-date": "^3.3.3", 28 | "@types/graphql-type-json": "^0.3.2", 29 | "@types/lodash": "^4.14.149", 30 | "@types/node": "^13.7.0", 31 | "@types/validator": "^12.0.0", 32 | "apollo-server-express": "^2.14.2", 33 | "axios": "^0.21.2", 34 | "bignumber": "^1.1.0", 35 | "blakejs": "^1.1.0", 36 | "bs58check": "^2.1.2", 37 | "compression": "^1.7.4", 38 | "cors": "^2.8.5", 39 | "dotenv": "^8.2.0", 40 | "express": "^4.17.1", 41 | "graphql": "^14.6.0", 42 | "graphql-depth-limit": "^1.1.0", 43 | "graphql-import": "^1.0.0-beta.2", 44 | "graphql-import-node": "0.0.4", 45 | "graphql-iso-date": "^3.6.1", 46 | "graphql-type-json": "^0.3.1", 47 | "http": "0.0.0", 48 | "lodash": "^4.17.21", 49 | "ncp": "^2.0.0", 50 | "nodemon": "^2.0.2", 51 | "reflect-metadata": "^0.1.13", 52 | "ts-node": "^8.6.2", 53 | "tsyringe": "^4.1.0", 54 | "typescript": "^3.7.5", 55 | "validator": "^13.7.0" 56 | }, 57 | "devDependencies": { 58 | "prettier": "^2.0.2", 59 | "pretty-quick": "^2.0.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /scripts/convert_schema.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | (async function () { 4 | var schemaText = fs.readFileSync(`src/schema/schema.graphql`, 'utf8'); 5 | var cmnt = false; 6 | var indent = ''; 7 | for (var l of schemaText.split(/\r?\n/)) { 8 | if (l.endsWith('"""')) { 9 | cmnt = !cmnt; 10 | indent = l.substring(0, l.length - 3); 11 | } else { 12 | if (cmnt) { 13 | process.stdout.write(indent); 14 | process.stdout.write('# '); 15 | process.stdout.write(l.trim()); 16 | } else { 17 | process.stdout.write(l); 18 | } 19 | process.stdout.write('\n'); 20 | } 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /src/resolverMap.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | 3 | import { GraphQLDateTime } from 'graphql-iso-date'; 4 | import { IResolvers } from 'graphql-tools'; 5 | import GraphQLJSONObject from 'graphql-type-json'; 6 | import { merge } from 'lodash'; 7 | 8 | import { blockResolver } from './resolvers/block-resolver'; 9 | import { contractResolver } from './resolvers/contract-resolver'; 10 | import { delegateResolver } from './resolvers/delegate-resolver'; 11 | import { enumResolver } from './resolvers/enum-resolver'; 12 | import { internalOperationResultResolver } from './resolvers/internal-operation-result-resolver'; 13 | import { operationContentsResolver } from './resolvers/operation-contents-resolver'; 14 | import { operationResolver } from './resolvers/operation-resolver'; 15 | import { blockQueryResolver } from './resolvers/queries/blocks/block-query-resolver'; 16 | import { blocksQueryResolver } from './resolvers/queries/blocks/blocks-query-resolver'; 17 | import { base58Resolvers } from './resolvers/scalars/base58-resolvers'; 18 | import { mutezResolver } from './resolvers/scalars/mutez-resolver'; 19 | 20 | const dateTimeResolver: IResolvers = { 21 | DateTime: GraphQLDateTime, 22 | }; 23 | 24 | const jsonResolver: IResolvers = { 25 | JSON: GraphQLJSONObject, 26 | }; 27 | 28 | const queries = merge(blockQueryResolver, blocksQueryResolver); 29 | const typeResolvers = merge( 30 | dateTimeResolver, 31 | jsonResolver, 32 | operationContentsResolver, 33 | internalOperationResultResolver, 34 | enumResolver, 35 | mutezResolver, 36 | base58Resolvers 37 | ); 38 | const schemaResolvers = merge(blockResolver, contractResolver, delegateResolver, operationResolver); 39 | const resolvers = merge(queries, typeResolvers, schemaResolvers); 40 | 41 | export default resolvers; 42 | -------------------------------------------------------------------------------- /src/resolvers/block-resolver.ts: -------------------------------------------------------------------------------- 1 | import { OperationResultStatusEnum, OpKind } from '@taquito/rpc'; 2 | import { flatten, isArray } from 'lodash'; 3 | import { container } from 'tsyringe'; 4 | import { TezosService } from '../services/tezos-service'; 5 | import { 6 | ActivateAccount, 7 | BalanceUpdateKind, 8 | Ballot, 9 | BallotVote, 10 | Block, 11 | Constants, 12 | Contract, 13 | Delegate, 14 | Delegation, 15 | DoubleBakingEvidence, 16 | DoubleEndorsementEvidence, 17 | Endorsement, 18 | OperationContents, 19 | OperationEntry, 20 | Origination, 21 | Proposals, 22 | Reveal, 23 | Transaction, 24 | } from '../types/types'; 25 | import { convertResponse, convertResponseOrNull } from './utils'; 26 | 27 | interface OperationArguments { 28 | address?: string; 29 | ballot?: BallotVote; 30 | delegate?: string; 31 | destination?: string; 32 | hash?: string; 33 | originatedContract?: string; 34 | proposal?: string; 35 | source?: string; 36 | status?: OperationResultStatusEnum; 37 | } 38 | 39 | const tezosService = container.resolve(TezosService); 40 | 41 | export const blockResolver = { 42 | Block: { 43 | activations: (root: Block, args: OperationArguments) => filterActivations(root.operations[2], OpKind.ACTIVATION, args), 44 | ballots: (root: Block, args: OperationArguments) => filterBallots(root.operations[1], OpKind.BALLOT, args), 45 | delegations: (root: Block, args: OperationArguments) => filterDelegations(root.operations[3], OpKind.DELEGATION, args), 46 | doubleBakingEvidence: (root: Block, args: OperationArguments) => filterDoubleBakings(root.operations[2], OpKind.DOUBLE_BAKING_EVIDENCE, args), 47 | doubleEndorsementEvidence: (root: Block, args: OperationArguments) => 48 | filterDoubleEndorsements(root.operations[2], OpKind.DOUBLE_ENDORSEMENT_EVIDENCE, args), 49 | endorsements: (root: Block, args: OperationArguments) => filterEndorsements(root.operations[0], OpKind.ENDORSEMENT, args), 50 | originations: (root: Block, args: OperationArguments) => filterOriginations(root.operations[3], OpKind.ORIGINATION, args), 51 | proposals: (root: Block, args: OperationArguments) => filterProposals(root.operations[1], OpKind.PROPOSALS, args), 52 | reveals: (root: Block, args: OperationArguments) => filterReveals(root.operations[3], OpKind.REVEAL, args), 53 | seedNonceRevelations: (root: Block, args: OperationArguments) => filterOperations(root.operations[1], OpKind.SEED_NONCE_REVELATION, args), 54 | transactions: (root: Block, args: OperationArguments) => filterTransactions(root.operations[3], OpKind.TRANSACTION, args), 55 | operation: (root: Block, args: { hash: string }) => { 56 | // find the operation with given hash 57 | for (let ops of root.operations) { 58 | for (let op of ops) { 59 | if (op.hash == args.hash) { 60 | return op; 61 | } 62 | } 63 | } 64 | return null; 65 | }, 66 | operations: (root: Block) => { 67 | return root.operations.map(opsArray => opsArray.map(extendOperation)); 68 | }, 69 | delegate: async (root: Block, args: { address: string }): Promise => { 70 | const result = convertResponseOrNull(await tezosService.client.getDelegates(args.address, { block: root.hash })); 71 | if (result != null) { 72 | return { 73 | ...result, 74 | blockHash: root.hash, 75 | address: args.address, 76 | }; 77 | } 78 | return null; 79 | }, 80 | contract: async (root: Block, args: { address: string }): Promise => { 81 | const result = convertResponseOrNull( 82 | await TezosService.handleNotFound(() => tezosService.client.getContract(args.address, { block: root.hash })) 83 | ); 84 | if (result != null) { 85 | return { 86 | ...result, 87 | blockHash: root.hash, 88 | address: args.address, 89 | }; 90 | } 91 | return null; 92 | }, 93 | constants: async (root: Block): Promise => { 94 | // can't use taquito here, bcs they do not have a support for Carthaganet constants 95 | // const result = await tezosRpcService.client.getConstants({ block: root.hash })); 96 | const result = await tezosService.axios.get(`/chains/main/blocks/${root.hash}/context/constants`); 97 | let constants = result.data; 98 | 99 | // Carthaganet breaking change fix 100 | constants.endorsement_reward = isArray(constants.endorsement_reward) ? constants.endorsement_reward : [constants.endorsement_reward]; 101 | 102 | return convertResponse(constants); 103 | }, 104 | }, 105 | }; 106 | 107 | function extendOperation(op: OperationEntry): OperationEntry { 108 | op.contents.forEach(c => (c.operation = op)); 109 | return op; 110 | } 111 | 112 | function filterOperations(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 113 | operations = operations.map(extendOperation); 114 | const ops = flatten(operations.map(o => o.contents)); 115 | return ops.filter(c => c.kind == opKind && (!args.hash || c.operation.hash == args.hash)); 116 | } 117 | 118 | function filterActivations(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 119 | let filteredOps = filterOperations(operations, opKind, args); 120 | if (args?.address) { 121 | filteredOps = filteredOps.filter(o => (o).pkh == args.address); 122 | } 123 | return filteredOps; 124 | } 125 | 126 | function filterBallots(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 127 | let filteredOps = filterOperations(operations, opKind, args); 128 | if (args?.source) { 129 | filteredOps = filteredOps.filter(o => (o).source == args.source); 130 | } 131 | if (args?.proposal) { 132 | filteredOps = filteredOps.filter(o => (o).proposal == args.proposal); 133 | } 134 | if (args?.ballot) { 135 | filteredOps = filteredOps.filter(o => (o).ballot == args.ballot); 136 | } 137 | return filteredOps; 138 | } 139 | 140 | function filterDelegations(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 141 | let filteredOps = filterOperations(operations, opKind, args); 142 | if (args?.source) { 143 | filteredOps = filteredOps.filter(o => (o).source == args.source); 144 | } 145 | if (args?.delegate) { 146 | filteredOps = filteredOps.filter(o => (o).delegate == args.delegate); 147 | } 148 | if (args?.status) { 149 | filteredOps = filteredOps.filter(o => (o).metadata.operationResult.status == args.status); 150 | } 151 | return filteredOps; 152 | } 153 | 154 | function filterDoubleBakings(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 155 | let filteredOps = filterOperations(operations, opKind, args); 156 | if (args?.delegate) { 157 | filteredOps = filteredOps.filter( 158 | o => (o).metadata.balanceUpdates.find(bu => bu.kind == BalanceUpdateKind.FREEZER)?.delegate == args.delegate 159 | ); 160 | } 161 | return filteredOps; 162 | } 163 | 164 | function filterDoubleEndorsements(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 165 | let filteredOps = filterOperations(operations, opKind, args); 166 | if (args?.delegate) { 167 | filteredOps = filteredOps.filter( 168 | o => (o).metadata.balanceUpdates.find(bu => bu.kind == BalanceUpdateKind.FREEZER)?.delegate == args.delegate 169 | ); 170 | } 171 | return filteredOps; 172 | } 173 | 174 | function filterEndorsements(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 175 | let filteredOps = filterOperations(operations, opKind, args); 176 | if (args?.delegate) { 177 | filteredOps = filteredOps.filter(o => (o).metadata.delegate == args.delegate); 178 | } 179 | return filteredOps; 180 | } 181 | 182 | function filterOriginations(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 183 | let filteredOps = filterOperations(operations, opKind, args); 184 | if (args?.source) { 185 | filteredOps = filteredOps.filter(o => (o).source == args.source); 186 | } 187 | if (args?.delegate) { 188 | filteredOps = filteredOps.filter(o => (o).delegate == args.delegate); 189 | } 190 | if (args?.originatedContract) { 191 | filteredOps = filteredOps.filter(o => (o).metadata.operationResult.originatedContracts?.some(oc => oc == args.originatedContract)); 192 | } 193 | if (args?.status) { 194 | filteredOps = filteredOps.filter(o => (o).metadata.operationResult.status == args.status); 195 | } 196 | return filteredOps; 197 | } 198 | 199 | function filterProposals(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 200 | let filteredOps = filterOperations(operations, opKind, args); 201 | if (args?.source) { 202 | filteredOps = filteredOps.filter(o => (o).source == args.source); 203 | } 204 | if (args?.proposal) { 205 | filteredOps = filteredOps.filter(o => (o).proposals?.some(p => p == args.proposal)); 206 | } 207 | return filteredOps; 208 | } 209 | 210 | function filterReveals(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 211 | let filteredOps = filterOperations(operations, opKind, args); 212 | if (args?.source) { 213 | filteredOps = filteredOps.filter(o => (o).source == args.source); 214 | } 215 | if (args?.status) { 216 | filteredOps = filteredOps.filter(o => (o).metadata.operationResult.status == args.status); 217 | } 218 | return filteredOps; 219 | } 220 | 221 | function filterTransactions(operations: OperationEntry[], opKind: OpKind, args: OperationArguments): OperationContents[] { 222 | let filteredOps = filterOperations(operations, opKind, args); 223 | if (args?.source) { 224 | filteredOps = filteredOps.filter(o => (o).source == args.source); 225 | } 226 | if (args?.destination) { 227 | filteredOps = filteredOps.filter(o => (o).destination == args.destination); 228 | } 229 | if (args?.status) { 230 | filteredOps = filteredOps.filter(o => (o).metadata.operationResult.status == args.status); 231 | } 232 | return filteredOps; 233 | } 234 | -------------------------------------------------------------------------------- /src/resolvers/contract-resolver.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from '@taquito/michelson-encoder'; 2 | import { ManagerKeyResponse as TaquitoManagerKeyResponse, PackDataParams, ScriptResponse } from '@taquito/rpc'; 3 | import { b58cencode, b58decode, hex2buf } from '@taquito/utils'; 4 | import { ApolloError } from 'apollo-server-express'; 5 | import { container } from 'tsyringe'; 6 | 7 | import { TezosService } from '../services/tezos-service'; 8 | import { BigMapKeyType, Contract, EntrypointPath, Entrypoints, ManagerKey, MichelsonExpression } from '../types/types'; 9 | import { convertResponseOrNull, convertResponse } from './utils'; 10 | 11 | const blake = require('blakejs'); 12 | const tezosService = container.resolve(TezosService) as TezosService; 13 | 14 | export const contractResolver = { 15 | Contract: { 16 | async entrypoints(contract: Contract): Promise { 17 | const result = convertResponseOrNull( 18 | await TezosService.handleNotFound(() => tezosService.client.getEntrypoints(contract.address, { block: contract.blockHash })) 19 | ); 20 | if (result != null) { 21 | return { 22 | ...result, 23 | unreachable: result.unreachable 24 | ? { 25 | ...result.unreachable, 26 | path: result.unreachable.path.map(p => EntrypointPath[p]), //is there a better way to go from ('left' | 'right') to Enum? 27 | } 28 | : undefined, 29 | }; 30 | } 31 | return null; 32 | }, 33 | async managerKey(contract: Contract): Promise { 34 | const result = convertResponseOrNull( 35 | await TezosService.handleNotFound(() => tezosService.client.getManagerKey(contract.address, { block: contract.blockHash })) 36 | ); 37 | if (result != null) { 38 | return managerKeyIsString(result) ? { key: result } : { key: result.key, invalid: true }; 39 | } 40 | return null; 41 | }, 42 | async storage(contract: Contract): Promise { 43 | return convertResponseOrNull( 44 | await TezosService.handleNotFound(() => tezosService.client.getStorage(contract.address, { block: contract.blockHash })) 45 | ); 46 | }, 47 | async storageDecoded(contract: Contract): Promise { 48 | const contractSchema = Schema.fromRPCResponse({ script: contract.script as ScriptResponse }); 49 | let storage = await tezosService.client.getStorage(contract.address, { block: contract.blockHash }); 50 | return contractSchema.Execute(storage); 51 | }, 52 | schema(contract: Contract): any { 53 | var schema = Schema.fromRPCResponse({ script: contract.script as ScriptResponse }); 54 | return schema.ExtractSchema(); 55 | }, 56 | async bigMapValue(contract: Contract, args: { key: any; keyType?: BigMapKeyType; bigMapId?: number }): Promise { 57 | if (args.bigMapId) { 58 | // query using RPC with map ID 59 | if (!args.keyType) { 60 | throw new ApolloError('Parameter keyType has to be present when fetching big map of given ID'); 61 | } 62 | 63 | const encodedExpr = await getBigMapKeyHash(args); 64 | return convertResponseOrNull( 65 | await TezosService.handleNotFound(() => 66 | tezosService.client.getBigMapExpr(args.bigMapId!.toString(), encodedExpr, { block: contract.blockHash }) 67 | ) 68 | ); 69 | } else { 70 | // query using deprecated big map RPC 71 | const contractSchema = Schema.fromRPCResponse({ script: contract.script as ScriptResponse }); 72 | const encodedKey = contractSchema.EncodeBigMapKey(args.key); 73 | return convertResponseOrNull( 74 | TezosService.handleNotFound(() => tezosService.client.getBigMapKey(contract.address, encodedKey, { block: contract.blockHash })) 75 | ); 76 | } 77 | }, 78 | async bigMapValueDecoded(contract: Contract, args: { key: any }): Promise { 79 | const contractSchema = Schema.fromRPCResponse({ script: contract.script as ScriptResponse }); 80 | const encodedKey = contractSchema.EncodeBigMapKey(args.key); 81 | 82 | // tslint:disable-next-line: deprecation 83 | const val = await TezosService.handleNotFound(() => tezosService.client.getBigMapKey(contract.address, encodedKey)); 84 | if (val != null) { 85 | return contractSchema.ExecuteOnBigMapValue(val); 86 | } 87 | return null; 88 | }, 89 | delegate(contract: Contract): Promise { 90 | return TezosService.handleNotFound(() => tezosService.client.getDelegate(contract.address, { block: contract.blockHash })); 91 | }, 92 | }, 93 | }; 94 | 95 | async function getBigMapKeyHash(args: { key: any; keyType?: any; bigMapId?: number | undefined }): Promise { 96 | // pack via RPC 97 | let params: PackDataParams; 98 | switch (args.keyType) { 99 | case 'address': 100 | params = { data: { bytes: b58decode(args.key) }, type: { prim: 'bytes' } }; 101 | break; 102 | case 'bool': 103 | params = { data: { prim: args.key ? 'True' : 'False' }, type: { prim: 'bool' } }; 104 | break; 105 | case 'bytes': 106 | params = { data: { bytes: args.key }, type: { prim: 'bytes' } }; 107 | break; 108 | case 'int': 109 | case 'mutez': 110 | case 'nat': 111 | params = { data: { int: args.key }, type: { prim: args.keyType } }; 112 | break; 113 | case 'string': 114 | case 'key_hash': 115 | params = { data: { string: args.key }, type: { prim: args.keyType } }; 116 | break; 117 | default: 118 | throw new ApolloError(`Unsupported key type ${args.keyType}`); 119 | } 120 | const { packed } = await tezosService.client.packData(params); 121 | 122 | // apply blake2, add prefix and encode to b58 123 | const blakeHash = blake.blake2b(hex2buf(packed), null, 32); 124 | return b58cencode(blakeHash, new Uint8Array([13, 44, 64, 27])); 125 | } 126 | 127 | function managerKeyIsString(value: TaquitoManagerKeyResponse): value is string { 128 | return typeof value === 'string'; 129 | } 130 | -------------------------------------------------------------------------------- /src/resolvers/delegate-resolver.ts: -------------------------------------------------------------------------------- 1 | import { ApolloError } from 'apollo-server-express'; 2 | import { container } from 'tsyringe'; 3 | 4 | import { TezosService } from '../services/tezos-service'; 5 | import { BakingRight, Delegate, EndorsingRight } from '../types/types'; 6 | 7 | const tezosService = container.resolve(TezosService) as TezosService; 8 | 9 | export const delegateResolver = { 10 | Delegate: { 11 | bakingRights: async (root: Delegate, args: { level?: number[]; cycle?: number[]; maxPriority?: number }): Promise => { 12 | checkMaxPriority(args); 13 | checkMaxCycles(args); 14 | checkMaxLevels(args); 15 | return TezosService.handleNotFound(() => tezosService.client.getBakingRights({ ...args, delegate: root.address }, { block: root.blockHash })); 16 | }, 17 | endorsingRights: async (root: Delegate, args: { level?: number[]; cycle?: number[] }): Promise => { 18 | checkMaxCycles(args); 19 | checkMaxLevels(args); 20 | return TezosService.handleNotFound(() => tezosService.client.getEndorsingRights({ ...args, delegate: root.address }, { block: root.blockHash })); 21 | }, 22 | }, 23 | }; 24 | 25 | function checkMaxPriority(args: any) { 26 | const maxPriority = process.env.TEZOS_BAKING_RIGHTS_MAX_PRIORITY ? +process.env.TEZOS_BAKING_RIGHTS_MAX_PRIORITY : 5; 27 | if (!args.maxPriority) { 28 | args.maxPriority = maxPriority; 29 | } 30 | if (args.maxPriority > maxPriority) { 31 | throw new ApolloError( 32 | `maxPriority must be lower or equal to ${process.env.TEZOS_BAKING_RIGHTS_MAX_PRIORITY}. You can set this value in the ENV variable TEZOS_BAKING_RIGHTS_MAX_PRIORITY.` 33 | ); 34 | } 35 | } 36 | 37 | function checkMaxCycles(args: any) { 38 | const maxCycles = process.env.TEZOS_RIGHTS_MAX_CYCLES ? +process.env.TEZOS_RIGHTS_MAX_CYCLES : 5; 39 | if (args.cycle?.length > maxCycles) { 40 | throw new ApolloError(`number of cycles must be lower or equal to ${maxCycles}. You can set this value in the ENV variable TEZOS_RIGHTS_MAX_CYCLES.`); 41 | } 42 | } 43 | 44 | function checkMaxLevels(args: any) { 45 | const maxLevels = process.env.TEZOS_RIGHTS_MAX_LEVELS ? +process.env.TEZOS_RIGHTS_MAX_LEVELS : 20480; 46 | if (args.level?.length > maxLevels) { 47 | throw new ApolloError(`number of levels must be lower or equal to ${maxLevels}. You can set this value in the ENV variable TEZOS_RIGHTS_MAX_LEVELS.`); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/resolvers/enum-resolver.ts: -------------------------------------------------------------------------------- 1 | export const enumResolver = { 2 | BalanceUpdateKind: { 3 | CONTRACT: 'contract', 4 | FREEZER: 'freezer', 5 | }, 6 | 7 | BalanceUpdateCategory: { 8 | DEPOSITS: 'deposits', 9 | FEES: 'fees', 10 | REWARDS: 'rewards', 11 | }, 12 | 13 | BallotVote: { 14 | NAY: 'nay', 15 | PASS: 'pass', 16 | YAY: 'yay', 17 | }, 18 | 19 | OperationKind: { 20 | ACTIVATE_ACCOUNT: 'activate_account', 21 | BALLOT: 'ballot', 22 | DELEGATION: 'delegation', 23 | DOUBLE_BAKING_EVIDENCE: 'double_baking_evidence', 24 | DOUBLE_ENDORSEMENT_EVIDENCE: 'double_endorsement_evidence', 25 | ENDORSEMENT: 'endorsement', 26 | ORIGINATION: 'origination', 27 | PROPOSALS: 'proposals', 28 | REVEAL: 'reveal', 29 | SEED_NONCE_REVELATION: 'seed_nonce_revelation', 30 | TRANSACTION: 'transaction', 31 | }, 32 | 33 | InternalOperationKind: { 34 | DELEGATION: 'delegation', 35 | ORIGINATION: 'origination', 36 | REVEAL: 'reveal', 37 | TRANSACTION: 'transaction', 38 | }, 39 | 40 | OperationResultStatus: { 41 | APPLIED: 'applied', 42 | BACKTRACKED: 'backtracked', 43 | FAILED: 'failed', 44 | SKIPPED: 'skipped', 45 | }, 46 | 47 | BigMapKeyType: { 48 | STRING: 'string', 49 | NAT: 'nat', 50 | INT: 'int', 51 | BYTES: 'bytes', 52 | BOOL: 'bool', 53 | MUTEZ: 'mutez', 54 | ADDRESS: 'address', 55 | KEY_HASH: 'key_hash', 56 | }, 57 | 58 | TestChainStatusType: { 59 | NOT_RUNNING: 'not_running', 60 | FORKING: 'forking', 61 | RUNNING: 'running', 62 | }, 63 | 64 | VotingPeriodKind: { 65 | PROPOSAL: 'proposal', 66 | TESTING_VOTE: 'testing_vote', 67 | TESTING: 'testing', 68 | PROMOTION_VOTE: 'promotion_vote', 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/resolvers/internal-operation-result-resolver.ts: -------------------------------------------------------------------------------- 1 | import { OpKind } from '@taquito/taquito'; 2 | 3 | export const internalOperationResultResolver = { 4 | OperationResult: { 5 | __resolveType(obj: any) { 6 | switch (obj.kind as OpKind) { 7 | case OpKind.REVEAL: 8 | return 'RevealOperationResult'; 9 | case OpKind.TRANSACTION: 10 | return 'TransactionOperationResult'; 11 | case OpKind.ORIGINATION: 12 | return 'OriginationOperationResult'; 13 | case OpKind.DELEGATION: 14 | return 'DelegationOperationResult'; 15 | default: 16 | return null; 17 | } 18 | }, 19 | }, 20 | 21 | InternalOperationResult: { 22 | result(obj: any) { 23 | return { 24 | ...obj.result, 25 | kind: obj.kind, 26 | }; 27 | }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/resolvers/operation-contents-resolver.ts: -------------------------------------------------------------------------------- 1 | export const operationContentsResolver = { 2 | OperationContents: { 3 | __resolveType(obj: any, context: any, info: any) { 4 | switch (obj.kind) { 5 | case 'endorsement': 6 | return 'Endorsement'; 7 | case 'seed_nonce_revelation': 8 | return 'SeedNonceRevelation'; 9 | case 'double_endorsement_evidence': 10 | return 'DoubleEndorsementEvidence'; 11 | case 'double_baking_evidence': 12 | return 'DoubleBakingEvidence'; 13 | case 'activate_account': 14 | return 'ActivateAccount'; 15 | case 'proposals': 16 | return 'Proposals'; 17 | case 'ballot': 18 | return 'Ballot'; 19 | case 'reveal': 20 | return 'Reveal'; 21 | case 'transaction': 22 | return 'Transaction'; 23 | case 'delegation': 24 | return 'Delegation'; 25 | case 'origination': 26 | return 'Origination'; 27 | default: 28 | return null; 29 | } 30 | }, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/resolvers/operation-resolver.ts: -------------------------------------------------------------------------------- 1 | export const operationResolver = { 2 | OperationEntry: { 3 | info(obj: any) { 4 | return obj; 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /src/resolvers/queries/blocks/block-query-resolver.ts: -------------------------------------------------------------------------------- 1 | import { TezosService } from '../../../services/tezos-service'; 2 | import { container } from 'tsyringe'; 3 | import { convertResponseOrNull } from '../../utils'; 4 | import { Block } from '../../../types/types'; 5 | 6 | const tezosRpcService = container.resolve(TezosService); 7 | 8 | export const blockQueryResolver = { 9 | Query: { 10 | async block(obj: any, args: { block: string | number | null }): Promise { 11 | return convertResponseOrNull(await TezosService.handleNotFound(() => tezosRpcService.client.getBlock({ block: args.block?.toString() || 'head' }))); 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/resolvers/queries/blocks/blocks-query-resolver.ts: -------------------------------------------------------------------------------- 1 | import { BlockResponse } from '@taquito/rpc'; 2 | import { UserInputError } from 'apollo-server-express'; 3 | import { container } from 'tsyringe'; 4 | 5 | import { TezosService } from '../../../services/tezos-service'; 6 | import { Block } from '../../../types/types'; 7 | import { convertResponse } from '../../utils'; 8 | 9 | const tezosRpcService = container.resolve(TezosService); 10 | 11 | function fetchBlock(block: string | number): Promise { 12 | return tezosRpcService.client.getBlock({ block: block.toString() }); 13 | } 14 | 15 | export const blocksQueryResolver = { 16 | Query: { 17 | async blocks(obj: any, args: { from: string | number | null; to: string | number | null; count: number | null }, context: any): Promise { 18 | if (args.from == null && args.to == null && args.count == null) { 19 | throw new UserInputError(`Neither "from", "to" or "count" argument specified`); 20 | } 21 | if (args.from != null && args.to != null && args.count != null) { 22 | throw new UserInputError(`You cannot limit your query from both ends ("from" and "to") and by maximum count at the same time`); 23 | } 24 | if (args.count != null && args.count <= 0) { 25 | throw new UserInputError(`The "count" argument has to be greater than 0`); 26 | } 27 | 28 | // calculate the boundaries and potentially keep some fetched blocks for later 29 | let fromLevel: number; 30 | let count: number; 31 | let firstBlock: BlockResponse | null; 32 | let lastBlock: BlockResponse | null; 33 | if (args.count == null) { 34 | firstBlock = await fetchBlock(args.from || '0'); 35 | lastBlock = await fetchBlock(args.to || 'head'); 36 | fromLevel = firstBlock.header.level; 37 | count = lastBlock.header.level - firstBlock.header.level + 1; 38 | } else { 39 | if (args.from != null) { 40 | firstBlock = await fetchBlock(args.from || '0'); 41 | lastBlock = null; 42 | fromLevel = firstBlock.header.level; 43 | count = args.count; 44 | } else { 45 | lastBlock = await fetchBlock(args.to || 'head'); 46 | firstBlock = null; 47 | fromLevel = Math.max(0, lastBlock.header.level - args.count + 1); 48 | count = lastBlock.header.level - fromLevel + 1; 49 | } 50 | } 51 | 52 | // handle corner cases first 53 | if (count <= 0) { 54 | return []; 55 | } 56 | if (count > +process.env.MAX_BLOCKS!) { 57 | throw new UserInputError(`Number of blocks has to be lower than ${process.env.MAX_BLOCKS!}.`); 58 | } 59 | 60 | let blocks: Block[] = [convertResponse(firstBlock || (await fetchBlock(fromLevel)))]; 61 | for (let i = 1; i < count; i++) { 62 | if (i == count - 1 && lastBlock != null) { 63 | // if we are at the last block and we have fetched it already... 64 | blocks.push(convertResponse(lastBlock)); 65 | } else { 66 | // fetch the block 67 | let block: BlockResponse; 68 | try { 69 | block = await fetchBlock(fromLevel + i); 70 | } catch (e) { 71 | if (e.status == 404) { 72 | // we are at the end of the list 73 | break; 74 | } 75 | throw e; 76 | } 77 | blocks.push(convertResponse(block)); 78 | } 79 | } 80 | return blocks; 81 | }, 82 | }, 83 | }; 84 | -------------------------------------------------------------------------------- /src/resolvers/scalars/base58-resolvers.ts: -------------------------------------------------------------------------------- 1 | import { ApolloError } from 'apollo-server-express'; 2 | import { GraphQLScalarType, Kind, ValueNode } from 'graphql'; 3 | 4 | var bs58check = require('bs58check'); 5 | 6 | export const base58Resolvers = { 7 | Address: new GraphQLScalarType({ 8 | name: 'Address', 9 | description: 'Address identifier (Base58Check-encoded) prefixed with tz1 (ed25519), tz2 (secp256k1), tz3 (p256) or KT1.', 10 | parseValue: validateAddress, 11 | serialize: validateAddress, 12 | parseLiteral: (ast: any) => validateAddress(ast.value), 13 | }), 14 | BlockHash: new GraphQLScalarType({ 15 | name: 'BlockHash', 16 | description: 'Block identifier (Base58Check-encoded) prefixed with B.', 17 | parseValue: validateBlockHash, 18 | serialize: validateBlockHash, 19 | parseLiteral: validateBlockHash, 20 | }), 21 | ContextHash: new GraphQLScalarType({ 22 | name: 'ContextHash', 23 | description: 'ContextHash identifier (Base58Check-encoded) prefixed with Co.', 24 | parseValue: validateContextHash, 25 | serialize: validateContextHash, 26 | parseLiteral: validateContextHash, 27 | }), 28 | ChainId: new GraphQLScalarType({ 29 | name: 'ChainId', 30 | description: 'Chain identifier (Base58Check-encoded) prefixed with Net.', 31 | parseValue: validateChainId, 32 | serialize: validateChainId, 33 | parseLiteral: validateChainId, 34 | }), 35 | NonceHash: new GraphQLScalarType({ 36 | name: 'NonceHash', 37 | description: 'Nonce hash (Base58Check-encoded).', 38 | parseValue: validatenonceHash, 39 | serialize: validatenonceHash, 40 | parseLiteral: validatenonceHash, 41 | }), 42 | OperationHash: new GraphQLScalarType({ 43 | name: 'OperationHash', 44 | description: 'Operation identifier (Base58Check-encoded) prefixed with o.', 45 | parseValue: validateOperationHash, 46 | serialize: validateOperationHash, 47 | parseLiteral: validateOperationHash, 48 | }), 49 | OperationsHash: new GraphQLScalarType({ 50 | name: 'OperationsHash', 51 | description: 'OperationsHash identifier (Base58Check-encoded) prefixed with LLo.', 52 | parseValue: validateOperationsHash, 53 | serialize: validateOperationsHash, 54 | parseLiteral: validateOperationsHash, 55 | }), 56 | ProtocolHash: new GraphQLScalarType({ 57 | name: 'ProtocolHash', 58 | description: 'Protocol identifier (Base58Check-encoded) prefixed with P.', 59 | parseValue: validateProtocolHash, 60 | serialize: validateProtocolHash, 61 | parseLiteral: validateProtocolHash, 62 | }), 63 | PublicKey: new GraphQLScalarType({ 64 | name: 'PublicKey', 65 | description: 'Public key (Base58Check-encoded) prefixed with edpk, sppk or p2pk.', 66 | parseValue: validatePublicKey, 67 | serialize: validatePublicKey, 68 | parseLiteral: validatePublicKey, 69 | }), 70 | Signature: new GraphQLScalarType({ 71 | name: 'Signature', 72 | description: 'Generic signature (Base58Check-encoded) prefixed with sig.', 73 | parseValue: validateSignature, 74 | serialize: validateSignature, 75 | parseLiteral: validateSignature, 76 | }), 77 | BlockIdentifier: new GraphQLScalarType({ 78 | name: 'BlockIdentifier', 79 | description: 'BlockIdentifier', 80 | parseValue: val => val, 81 | serialize: validateProtocolHash, 82 | parseLiteral: validateBlockIdentifier, 83 | }), 84 | }; 85 | 86 | function validate58Hash(hash: string) { 87 | try { 88 | bs58check.decode(hash); 89 | } catch { 90 | throw new ApolloError(`Invalid base58 hash: ${hash}`); 91 | } 92 | } 93 | 94 | function hasPrefixAndLength(value: any, prefix: string, length: number) { 95 | return value.startsWith(prefix) && value.length === length; 96 | } 97 | 98 | function validateAddress(value: any) { 99 | // Order of the prefixes is based on their relative quantity in the Tezos network (tz1 are most widely used) 100 | const isValid = 101 | hasPrefixAndLength(value, 'tz1', 36) || 102 | hasPrefixAndLength(value, 'KT1', 36) || 103 | hasPrefixAndLength(value, 'tz3', 36) || 104 | hasPrefixAndLength(value, 'tz2', 36); 105 | validate58Hash(value); 106 | if (!isValid) throw new ApolloError('Wrong Address'); 107 | return value; 108 | } 109 | 110 | function validateContextHash(value: any) { 111 | if (!hasPrefixAndLength(value, 'Co', 52)) throw new ApolloError('Wrong ContextHash'); 112 | validate58Hash(value); 113 | return value; 114 | } 115 | 116 | function validateChainId(value: any) { 117 | if (!hasPrefixAndLength(value, 'Net', 15)) throw new ApolloError('Wrong ChainId'); 118 | validate58Hash(value); 119 | return value; 120 | } 121 | 122 | function validateBlockHash(value: any) { 123 | if (!hasPrefixAndLength(value, 'B', 51)) throw new ApolloError('Wrong BlockHash'); 124 | validate58Hash(value); 125 | return value; 126 | } 127 | 128 | function validatenonceHash(value: any) { 129 | validate58Hash(value); 130 | return value; 131 | } 132 | 133 | function validateOperationHash(value: any) { 134 | if (!hasPrefixAndLength(value, 'o', 51)) throw new ApolloError('Wrong OperationHash'); 135 | validate58Hash(value); 136 | return value; 137 | } 138 | 139 | function validateOperationsHash(value: any) { 140 | if (!hasPrefixAndLength(value, 'LLo', 53)) throw new ApolloError('Wrong OperationsHash'); 141 | validate58Hash(value); 142 | return value; 143 | } 144 | 145 | function validateProtocolHash(value: any) { 146 | if (!hasPrefixAndLength(value, 'P', 51)) throw new ApolloError('Wrong ProtocolHash'); 147 | validate58Hash(value); 148 | return value; 149 | } 150 | 151 | function validatePublicKey(value: any) { 152 | // Order of the prefixes is based on their relative quantity in the Tezos network (ed25519 is the most used) 153 | const isValid = hasPrefixAndLength(value, 'edpk', 54) || hasPrefixAndLength(value, 'p2pk', 55) || hasPrefixAndLength(value, 'sppk', 55); 154 | validate58Hash(value); 155 | if (!isValid) throw new ApolloError('Wrong Public Key'); 156 | return value; 157 | } 158 | 159 | function validateSignature(value: any) { 160 | if (!hasPrefixAndLength(value, 'sig', 96)) throw new ApolloError('Wrong Signature'); 161 | validate58Hash(value); 162 | return value; 163 | } 164 | 165 | function isBlockRelativeIdentifier(value: any) { 166 | return /^head(?:~\d+)?$/.test(value); 167 | } 168 | 169 | function validateBlockIdentifier(ast: ValueNode) { 170 | switch (ast.kind) { 171 | case Kind.INT: 172 | const level = parseInt(ast.value); 173 | return isNaN(level) || level < 0 ? null : level; 174 | case Kind.STRING: 175 | if (hasPrefixAndLength(ast.value, 'B', 51)) { 176 | validate58Hash(ast.value); 177 | } else if (!isBlockRelativeIdentifier(ast.value)) { 178 | throw new ApolloError('Wrong BlockIdentifier'); 179 | } 180 | return ast.value; 181 | default: 182 | return null; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/resolvers/scalars/mutez-resolver.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { GraphQLScalarType } from 'graphql'; 3 | 4 | function isPositiveNumber(value: any) { 5 | const x = new BigNumber(value); 6 | return x.isNaN() || x.isNegative() ? null : value; 7 | } 8 | 9 | export const mutezResolver = { 10 | Mutez: new GraphQLScalarType({ 11 | name: 'Mutez', 12 | description: 'Micro tez. Positive bignumber. 1 tez = 1,000,000 micro tez.', 13 | parseValue: isPositiveNumber, 14 | serialize: isPositiveNumber, 15 | parseLiteral: isPositiveNumber, 16 | }), 17 | }; 18 | -------------------------------------------------------------------------------- /src/resolvers/utils.ts: -------------------------------------------------------------------------------- 1 | import { isPlainObject, camelCase, isArray, each } from 'lodash'; 2 | 3 | function convertToCamelCase(obj: any): any { 4 | if (isPlainObject(obj)) { 5 | let result: any = {}; 6 | each(obj, (v, k) => { 7 | result[camelCase(k)] = convertToCamelCase(v); 8 | }); 9 | return result; 10 | } else if (isArray(obj)) { 11 | return obj.map(convertToCamelCase); 12 | } else { 13 | return obj; 14 | } 15 | } 16 | 17 | export function convertResponse(response: any): T { 18 | return convertToCamelCase(response) as T; 19 | } 20 | 21 | export function convertResponseOrNull(response: any | null): T | null { 22 | if (!response) { 23 | return null; 24 | } 25 | return convertResponse(response!); 26 | } 27 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import 'graphql-import-node'; 2 | import * as typeDefs from './schema/schema.graphql'; 3 | import { makeExecutableSchema } from 'graphql-tools'; 4 | import resolvers from './resolverMap'; 5 | import { GraphQLSchema } from 'graphql'; 6 | const schema: GraphQLSchema = makeExecutableSchema({ 7 | typeDefs, 8 | resolvers, 9 | }); 10 | export default schema; 11 | -------------------------------------------------------------------------------- /src/schema/schema.graphql: -------------------------------------------------------------------------------- 1 | """ 2 | Tezos address. Represented as public key hash (Base58Check-encoded) prefixed with tz1, tz2, tz3 or KT1. 3 | """ 4 | scalar Address 5 | 6 | """ 7 | Timestamp specified as a ISO-8601 UTC date string (2020-02-04T15:31:39Z) 8 | """ 9 | scalar DateTime 10 | 11 | """ 12 | JSON represents any valid JSON object 13 | """ 14 | scalar JSON 15 | 16 | """ 17 | Raw Michelson expression represented as JSON 18 | """ 19 | scalar MichelsonExpression 20 | 21 | """ 22 | Arbitrary precision number represented as string in JSON. 23 | """ 24 | scalar BigNumber 25 | 26 | """ 27 | Micro tez. Positive bignumber. 1 tez = 1,000,000 micro tez. 28 | """ 29 | scalar Mutez 30 | 31 | """ 32 | Operation identifier (Base58Check-encoded) prefixed with o. 33 | """ 34 | scalar OperationHash 35 | 36 | """ 37 | Block identifier (Base58Check-encoded) prefixed with B. 38 | """ 39 | scalar BlockHash 40 | 41 | """ 42 | Protocol identifier (Base58Check-encoded) prefixed with P. 43 | """ 44 | scalar ProtocolHash 45 | 46 | """ 47 | Context identifier (Base58Check-encoded) prefixed with Co. 48 | """ 49 | scalar ContextHash 50 | 51 | """ 52 | Operations identifier (Base58Check-encoded) prefixed with LLo (List of a list of operations). 53 | """ 54 | scalar OperationsHash 55 | 56 | """ 57 | Chain identifier (Base58Check-encoded) prefixed with Net. 58 | """ 59 | scalar ChainId 60 | 61 | """ 62 | Generic signature (Base58Check-encoded) prefixed with sig. 63 | """ 64 | scalar Signature 65 | 66 | """ 67 | Public key (Base58Check-encoded) prefixed with edpk, sppk or p2pk. 68 | """ 69 | scalar PublicKey 70 | 71 | """ 72 | Nonce hash (Base58Check-encoded). 73 | """ 74 | scalar NonceHash 75 | 76 | """ 77 | Block identifier. Hash (Base58Check-encoded), level (specified as a number), relative pointer (head~n) and head are allowed. 78 | 79 | Examples of valid block identifiers: 80 | "head" 81 | 946213 (level - specified as a number) 82 | "head~4" (5th block relative to head) 83 | "BMX2db8rN3MczS9urDdS8Hh6Q7UyGve3631t2YwzunU3VDddoJf" (hash) 84 | """ 85 | scalar BlockIdentifier 86 | 87 | schema { 88 | query: Query 89 | } 90 | 91 | type Query { 92 | """ 93 | Block fetches a Tezos block by number, hash (Base58Check-encoded) or relative pointer (head~n). 94 | If neither is supplied, the most recent known block (head) is returned. 95 | """ 96 | block(block: BlockIdentifier): Block 97 | 98 | """ 99 | Blocks returns all blocks in a given interval, but maximum of elements. 100 | Hash (Base58Check-encoded), level, relative pointer (head~n) and head are allowed in the "from" a "to" arguments. 101 | 102 | Example queries: 103 | blocks(from: "head~4") {...} # return last 5 blocks 104 | blocks(count: 5) {...} # return last 5 blocks 105 | blocks(from: "head~9", count: 5) {...} # five blocks before them 106 | blocks(to: "", count: 5) {...} # return 5 blocks before and including 107 | blocks(from: "", count: 5) {...} # return 5 blocks since and including 108 | """ 109 | blocks(from: BlockIdentifier, to: BlockIdentifier, count: Int): [Block!]! 110 | } 111 | 112 | """ 113 | Tezos block. See https://tezos.gitlab.io/api/rpc.html#get-block-id 114 | """ 115 | type Block { 116 | """ 117 | Tezos protocol ID (Base58Check-encoded) 118 | """ 119 | protocol: ProtocolHash! 120 | 121 | """ 122 | The network identifier (Base58Check-encoded) 123 | """ 124 | chainId: ChainId! 125 | 126 | """ 127 | The block's unique identifier (Base58Check-encoded) 128 | """ 129 | hash: BlockHash! 130 | 131 | """ 132 | Associated header object. 133 | """ 134 | header: BlockHeader! 135 | 136 | """ 137 | Associated metadata object. 138 | """ 139 | metadata: BlockMetadata! 140 | 141 | """ 142 | Returns the delegate with the given address, or null 143 | """ 144 | delegate(address: Address!): Delegate 145 | 146 | """ 147 | Returns the constants 148 | """ 149 | constants: Constants! 150 | 151 | """ 152 | Returns the contract with the given address, or null 153 | """ 154 | contract(address: Address): Contract 155 | 156 | """ 157 | Returns the operation entry with the given hash, or null 158 | """ 159 | operation(hash: OperationHash!): OperationEntry 160 | 161 | """ 162 | All operations in this block as returned from the Tezos node 163 | """ 164 | operations: [[OperationEntry]] 165 | 166 | """ 167 | Returns activations from block. Results can be filtered by operation hash, or address (pkh) 168 | """ 169 | activations(hash: OperationHash, address: Address): [ActivateAccount!]! 170 | 171 | """ 172 | Returns ballots from block. Results can be filtered by operation hash, source, proposal or vote (ballot). 173 | """ 174 | ballots(hash: OperationHash, source: Address, proposal: ProtocolHash, ballot: BallotVote): [Ballot!]! 175 | 176 | """ 177 | Returns delegations from block. Results can be filtered by operation hash, source, delegate or operation result status. 178 | """ 179 | delegations(hash: OperationHash, source: Address, delegate: Address, status: OperationResultStatus): [Delegation!]! 180 | 181 | """ 182 | Returns double baking evidence from block. Results can be filtered by operation hash or delegate. 183 | """ 184 | doubleBakingEvidence(hash: OperationHash, delegate: Address): [DoubleBakingEvidence!]! 185 | 186 | """ 187 | Returns double endorsement evidence from block. Results can be filtered by operation hash or delegate. 188 | """ 189 | doubleEndorsementEvidence(hash: OperationHash, delegate: Address): [DoubleEndorsementEvidence!]! 190 | 191 | """ 192 | Returns endorsements from block. Results can be filtered by operation hash, or delegate. 193 | """ 194 | endorsements(hash: OperationHash, delegate: Address): [Endorsement!]! 195 | 196 | """ 197 | Returns originations from block. Results can be filtered by 198 | operation hash, source, delegate, originated contract or operation result status. 199 | """ 200 | originations(hash: OperationHash, source: Address, delegate: Address, originatedContract: Address, status: OperationResultStatus): [Origination!]! 201 | 202 | """ 203 | Returns proposals from block. Results can be filtered by operation hash, source or proposal hash. 204 | """ 205 | proposals(hash: OperationHash, source: Address, proposal: ProtocolHash): [Proposals!]! 206 | 207 | """ 208 | Returns reveals from block. Results can be filtered by operation hash, source or operation result status. 209 | """ 210 | reveals(hash: OperationHash, source: Address, status: OperationResultStatus): [Reveal!]! 211 | 212 | """ 213 | Returns seed nonce revelations from block. Results can be filtered by operation hash, source or operation result status. 214 | """ 215 | seedNonceRevelations(hash: OperationHash, delegate: Address): [SeedNonceRevelation!]! 216 | 217 | """ 218 | Returns transactions from block. Results can be filtered by operation hash, source, destination or operation result status. 219 | """ 220 | transactions(hash: OperationHash, source: Address, destination: Address, status: OperationResultStatus): [Transaction!]! 221 | } 222 | 223 | """ 224 | A block header. See https://tezos.gitlab.io/api/rpc.html#get-block-id-header 225 | """ 226 | type BlockHeader { 227 | level: Int! 228 | proto: Int! 229 | predecessor: BlockHash! 230 | timestamp: DateTime! 231 | validationPass: Int! 232 | operationsHash: OperationsHash 233 | fitness: [String!]! 234 | context: ContextHash! 235 | priority: Int! 236 | proofOfWorkNonce: String! 237 | seedNonceHash: NonceHash 238 | signature: Signature! 239 | } 240 | 241 | """ 242 | Block metadata. See https://tezos.gitlab.io/api/rpc.html#get-block-id-metadata 243 | """ 244 | type BlockMetadata { 245 | protocol: ProtocolHash! 246 | nextProtocol: ProtocolHash! 247 | testChainStatus: TestChainStatus! 248 | maxOperationsTtl: Int! 249 | maxOperationDataLength: Int! 250 | maxBlockHeaderLength: Int! 251 | maxOperationListLength: [MaxOperationListLength!] 252 | baker: Address! 253 | level: Level! 254 | votingPeriodKind: VotingPeriodKind! 255 | nonceHash: NonceHash 256 | consumedGas: BigNumber 257 | deactivated: [Address!] 258 | balanceUpdates: [BalanceUpdate!] 259 | } 260 | 261 | """ 262 | Contains information about the block's level. 263 | """ 264 | type Level { 265 | """ 266 | The level of the block relative to genesis. This is also the Shell's notion of level 267 | """ 268 | level: Int! 269 | 270 | """ 271 | The level of the block relative to the block that starts protocol alpha 272 | """ 273 | levelPosition: Int! 274 | 275 | """ 276 | The current cycle's number. Note that cycles are a protocol-specific notion. As a result, the cycle number starts at 0 with the first block of protocol alpha. 277 | """ 278 | cycle: Int! 279 | 280 | """ 281 | The current level of the block relative to the first block of the current cycle. 282 | """ 283 | cyclePosition: Int! 284 | 285 | """ 286 | The current voting period's index. Note that cycles are a protocol-specific notion. As a result, the voting period index starts at 0 with the first block of protocol alpha. 287 | """ 288 | votingPeriod: Int! 289 | 290 | """ 291 | The current level of the block relative to the first block of the current voting period. 292 | """ 293 | votingPeriodPosition: Int! 294 | 295 | """ 296 | Tells whether the baker of this block has to commit a seed nonce hash. 297 | """ 298 | expectedCommitment: Boolean! 299 | } 300 | 301 | """ 302 | Status of the test chain 303 | """ 304 | type TestChainStatus { 305 | """ 306 | The status value: notRunning (there is no test chain at the moment), forking (the test chain is being setup), 307 | running (the test chain is running). 308 | """ 309 | status: TestChainStatusType! 310 | } 311 | 312 | type MaxOperationListLength { 313 | maxSize: Int! 314 | maxOp: Int 315 | } 316 | 317 | enum BalanceUpdateKind { 318 | CONTRACT 319 | FREEZER 320 | } 321 | 322 | enum BalanceUpdateCategory { 323 | DEPOSITS 324 | FEES 325 | REWARDS 326 | } 327 | 328 | """ 329 | Everything about a delegate. See https://tezos.gitlab.io/api/rpc.html#get-block-id-context-delegates-pkh 330 | """ 331 | type Delegate { 332 | """ 333 | The full balance of a given delegate, including the frozen balances. 334 | """ 335 | balance: Mutez! 336 | 337 | """ 338 | The total frozen balances of a given delegate, this includes the frozen deposits, rewards and fees. 339 | """ 340 | frozenBalance: Mutez! 341 | 342 | """ 343 | Returns the frozen balances of a given delegate, indexed by the cycle by which it will be unfrozen. 344 | """ 345 | frozenBalanceByCycle: [FrozenBalanceByCycle!]! 346 | 347 | """ 348 | The total amount of tokens delegated to a given delegate. This includes the balances of all the contracts that delegate to it, 349 | but also the balance of the delegate itself and its frozen fees and deposits. The rewards do not count in the delegated balance until they are unfrozen. 350 | """ 351 | stakingBalance: Mutez! 352 | 353 | """ 354 | The list of contracts that delegate to a given delegate. 355 | """ 356 | delegatedContracts: [Address!]! 357 | 358 | """ 359 | The balances of all the contracts that delegate to a given delegate. This excludes the delegate's own balance and its frozen balances. 360 | """ 361 | delegatedBalance: Mutez! 362 | 363 | """ 364 | Tells whether the delegate is currently tagged as deactivated or not. 365 | """ 366 | deactivated: Boolean! 367 | 368 | """ 369 | The cycle by the end of which the delegate might be deactivated if she fails to execute any delegate action. A deactivated delegate might be reactivated 370 | (without loosing any rolls) by simply re-registering as a delegate. For deactivated delegates, this value contains the cycle by which they were deactivated. 371 | """ 372 | gracePeriod: Int! 373 | 374 | """ 375 | Returns baking rights for a delegate or null. Results can be filtered by maxPriority, level or cycle. If maxPriority parameter is empty, server will default to the ENV value TEZOS_BAKING_RIGHTS_MAX_PRIORITY. 376 | """ 377 | bakingRights(maxPriority: Int, level: [Int], cycle: [Int]): [BakingRight] 378 | 379 | """ 380 | Returns endorsing rights for a delegate or null. Results can be filtered by level or cycle. 381 | """ 382 | endorsingRights(level: [Int], cycle: [Int]): [EndorsingRight] 383 | } 384 | 385 | type BakingRight { 386 | level: Int! 387 | delegate: Address! 388 | priority: Int! 389 | 390 | """ 391 | Omitted for levels in the past, and only an estimate for levels later that the next block, based on the hypothesis that all predecessor blocks were baked at the first priority. 392 | """ 393 | estimatedTime: DateTime 394 | } 395 | 396 | type EndorsingRight { 397 | level: Int! 398 | delegate: Address! 399 | slots: [Int]! 400 | 401 | """ 402 | Omitted for levels in the past, and only an estimate for levels later that the next block, based on the hypothesis that all predecessor blocks were baked at the first priority. 403 | """ 404 | estimatedTime: DateTime 405 | } 406 | 407 | """ 408 | Frozen balance of a given delegate, indexed by the cycle by which it will be unfrozen 409 | """ 410 | type FrozenBalanceByCycle { 411 | cycle: Int! 412 | deposit: Mutez! 413 | fees: Mutez! 414 | rewards: Mutez! 415 | } 416 | 417 | """ 418 | All constants 419 | """ 420 | type Constants { 421 | bakingRewardPerEndorsement: [BigNumber] 422 | blockSecurityDeposit: Mutez 423 | blocksPerCommitment: Int 424 | blocksPerCycle: Int 425 | blocksPerRollSnapshot: Int 426 | blocksPerVotingPeriod: Int 427 | costPerByte: Mutez 428 | delayPerMissingEndorsement: BigNumber 429 | endorsementReward: [Mutez] 430 | endorsementSecurityDeposit: Mutez 431 | endorsersPerBlock: Int 432 | hardGasLimitPerBlock: BigNumber 433 | hardGasLimitPerOperation: BigNumber 434 | hardStorageLimitPerOperation: BigNumber 435 | initialEndorsers: Int 436 | maxOperationDataLength: Int 437 | maxProposalsPerDelegate: Int 438 | maxRevelationsPerBlock: Int 439 | michelsonMaximumTypeSize: Int 440 | minProposalQuorum: Int 441 | nonceLength: Int 442 | originationSize: Int 443 | preservedCycles: Int 444 | proofOfWorkNonceSize: Int 445 | proofOfWorkThreshold: BigNumber 446 | quorumMax: Int 447 | quorumMin: Int 448 | seedNonceRevelationTip: Mutez 449 | testChainDuration: BigNumber 450 | timeBetweenBlocks: [BigNumber] 451 | tokensPerRoll: Mutez 452 | """ 453 | Pre Carthaganet 454 | """ 455 | blockReward: BigNumber 456 | } 457 | 458 | """ 459 | Information about a smart contract. 460 | """ 461 | type Contract { 462 | """ 463 | The unique address (public key hash). 464 | """ 465 | address: Address! 466 | 467 | """ 468 | The balance of the contract. 469 | """ 470 | balance: Mutez! 471 | 472 | """ 473 | Script belonging to the contract. 474 | """ 475 | script: ScriptedContracts 476 | 477 | """ 478 | The counter of the contract, if any 479 | """ 480 | counter: BigNumber 481 | 482 | """ 483 | Entrypoint information of the contract 484 | """ 485 | entrypoints: Entrypoints 486 | 487 | """ 488 | The delegate of the contract, if any 489 | """ 490 | delegate: Address 491 | 492 | """ 493 | The manager of the contract. 494 | """ 495 | managerKey: ManagerKey 496 | 497 | """ 498 | Storage in Michelson format. 499 | """ 500 | storage: MichelsonExpression 501 | 502 | """ 503 | Decoded version of the storage. 504 | """ 505 | storageDecoded: JSON 506 | 507 | """ 508 | JSON representation of the storage schema. 509 | """ 510 | schema: JSON 511 | 512 | """ 513 | Returns the value stored for a given key in a big map owned by this contract. If a map ID is given, the keyType argument is also required. 514 | """ 515 | bigMapValue(key: String!, keyType: BigMapKeyType, bigMapId: Int): MichelsonExpression 516 | 517 | """ 518 | Returns the decoded value stored for a given key in this contract's big map. Note that this is only supported for the contracts originated 519 | with the "one bigMap per contract" scheme. Babylon protocol introduced multiple bigmaps per contract, which cannot be accessed using this field. 520 | """ 521 | bigMapValueDecoded(key: String!): JSON 522 | } 523 | 524 | """ 525 | One of the currently supported key types used for fetching bigMap values. 526 | """ 527 | enum BigMapKeyType { 528 | STRING 529 | NAT 530 | INT 531 | BYTES 532 | BOOL 533 | MUTEZ 534 | ADDRESS 535 | KEY_HASH 536 | } 537 | 538 | """ 539 | Manager of a contract. 540 | """ 541 | type ManagerKey { 542 | """ 543 | The public key 544 | """ 545 | key: PublicKey! 546 | 547 | """ 548 | Validity flag 549 | """ 550 | invalid: Boolean 551 | } 552 | 553 | """ 554 | Information about entrypoints in a contract. 555 | """ 556 | type Entrypoints { 557 | """ 558 | List of entrypoints 559 | """ 560 | entrypoints: JSON! 561 | 562 | unreachable: [EntrypointUnreachable!] 563 | } 564 | 565 | type EntrypointUnreachable { 566 | path: [String!]! 567 | } 568 | 569 | """ 570 | Code and storage a contract 571 | """ 572 | type ScriptedContracts { 573 | """ 574 | The code of the script 575 | """ 576 | code: [MichelsonExpression!]! 577 | 578 | """ 579 | The current storage value in Michelson format 580 | """ 581 | storage: MichelsonExpression! 582 | } 583 | 584 | type OperationEntry { 585 | info: OperationEntryInfo! 586 | contents: [OperationContents!]! 587 | } 588 | 589 | type OperationEntryInfo { 590 | protocol: ProtocolHash! 591 | chainId: ChainId! 592 | hash: OperationHash! 593 | branch: BlockHash! 594 | signature: Signature 595 | } 596 | 597 | interface OperationContents { 598 | kind: OperationKind! 599 | operation: OperationEntryInfo! 600 | } 601 | 602 | enum OperationKind { 603 | ACTIVATE_ACCOUNT 604 | BALLOT 605 | DELEGATION 606 | DOUBLE_BAKING_EVIDENCE 607 | DOUBLE_ENDORSEMENT_EVIDENCE 608 | ENDORSEMENT 609 | ORIGINATION 610 | PROPOSALS 611 | REVEAL 612 | SEED_NONCE_REVELATION 613 | TRANSACTION 614 | } 615 | 616 | type Endorsement implements OperationContents { 617 | # kind is always 'endorsement' 618 | kind: OperationKind! 619 | level: Int! 620 | metadata: EndorsementMetadata! 621 | operation: OperationEntryInfo! 622 | } 623 | 624 | type EndorsementMetadata { 625 | balanceUpdates: [BalanceUpdate!]! 626 | delegate: Address! 627 | slots: [Int!]! 628 | } 629 | 630 | type SeedNonceRevelation implements OperationContents { 631 | # kind is always 'seedNonceRevelation' 632 | kind: OperationKind! 633 | level: Int! 634 | nonce: String! 635 | metadata: OperationContentMetadata! 636 | operation: OperationEntryInfo! 637 | } 638 | 639 | type DoubleEndorsementEvidence implements OperationContents { 640 | # kind is always 'doubleEndorsementEvidence' 641 | kind: OperationKind! 642 | op1: InlinedEndorsement! 643 | op2: InlinedEndorsement! 644 | metadata: OperationContentMetadata! 645 | operation: OperationEntryInfo! 646 | } 647 | 648 | type InlinedEndorsement { 649 | branch: BlockHash! 650 | operations: InlinedEndorsementContents! 651 | signature: Signature 652 | } 653 | 654 | type InlinedEndorsementContents { 655 | # kind is always 'endorsement' 656 | kind: OperationKind! 657 | level: Int! 658 | } 659 | 660 | type OperationContentMetadata { 661 | balanceUpdates: [BalanceUpdate!]! 662 | } 663 | 664 | type DoubleBakingEvidence implements OperationContents { 665 | # kind is always 'doubleBakingEvidence' 666 | kind: OperationKind! 667 | bh1: BlockHeader! 668 | bh2: BlockHeader! 669 | metadata: OperationContentMetadata! 670 | operation: OperationEntryInfo! 671 | } 672 | 673 | type ActivateAccount implements OperationContents { 674 | # kind is always 'activateAccount' 675 | kind: OperationKind! 676 | pkh: Address! 677 | secret: String! 678 | metadata: OperationContentMetadata! 679 | operation: OperationEntryInfo! 680 | } 681 | 682 | type Reveal implements OperationContents { 683 | # kind is always 'reveal' 684 | kind: OperationKind! 685 | source: Address! 686 | fee: Mutez! 687 | counter: BigNumber! 688 | gasLimit: BigNumber! 689 | storageLimit: BigNumber! 690 | publicKey: PublicKey! 691 | metadata: RevealMetadata! 692 | operation: OperationEntryInfo! 693 | } 694 | 695 | type RevealMetadata { 696 | balanceUpdates: [BalanceUpdate!]! 697 | internalOperationResults: [InternalOperationResult!] 698 | operationResult: RevealOperationResult! 699 | } 700 | 701 | type RevealOperationResult implements OperationResult { 702 | status: OperationResultStatus! 703 | consumedGas: BigNumber 704 | errors: [OperationError!] 705 | } 706 | 707 | type Transaction implements OperationContents { 708 | # kind is always 'transaction' 709 | kind: OperationKind! 710 | source: Address! 711 | fee: Mutez! 712 | counter: BigNumber! 713 | gasLimit: BigNumber! 714 | storageLimit: BigNumber! 715 | amount: Mutez! 716 | destination: Address! 717 | parameters: MichelsonExpression 718 | metadata: TransactionMetadata! 719 | operation: OperationEntryInfo! 720 | } 721 | 722 | type TransactionMetadata { 723 | balanceUpdates: [BalanceUpdate!]! 724 | operationResult: TransactionOperationResult! 725 | internalOperationResults: [InternalOperationResult!] 726 | } 727 | 728 | type TransactionOperationResult implements OperationResult { 729 | status: OperationResultStatus! 730 | consumedGas: BigNumber 731 | errors: [OperationError!] 732 | storage: MichelsonExpression 733 | bigMapDiff: [BigMapDiffItem!] 734 | balanceUpdates: [BalanceUpdate!] 735 | originatedContracts: [Address!] 736 | storageSize: BigNumber 737 | paidStorageSizeDiff: BigNumber 738 | allocatedDestinationContract: Boolean 739 | } 740 | 741 | type BigMapDiffItem { 742 | keyHash: String! 743 | key: MichelsonExpression! 744 | value: MichelsonExpression 745 | } 746 | 747 | type Delegation implements OperationContents { 748 | # kind is always 'delegation' 749 | kind: OperationKind! 750 | source: Address! 751 | fee: Mutez! 752 | counter: BigNumber! 753 | gasLimit: BigNumber! 754 | storageLimit: BigNumber! 755 | delegate: Address 756 | metadata: DelegationMetadata! 757 | operation: OperationEntryInfo! 758 | } 759 | 760 | type DelegationMetadata { 761 | balanceUpdates: [BalanceUpdate!]! 762 | operationResult: DelegationOperationResult! 763 | internalOperationResults: [InternalOperationResult!] 764 | } 765 | 766 | type DelegationOperationResult implements OperationResult { 767 | status: OperationResultStatus! 768 | consumedGas: BigNumber 769 | errors: [OperationError!] 770 | } 771 | 772 | type Origination implements OperationContents { 773 | # kind is always 'origination' 774 | kind: OperationKind! 775 | source: Address! 776 | fee: Mutez! 777 | counter: BigNumber! 778 | gasLimit: BigNumber! 779 | storageLimit: BigNumber! 780 | balance: Mutez! 781 | delegate: Address 782 | script: ScriptedContracts 783 | metadata: OriginationMetadata! 784 | operation: OperationEntryInfo! 785 | } 786 | 787 | type OriginationMetadata { 788 | balanceUpdates: [BalanceUpdate!]! 789 | operationResult: OriginationOperationResult! 790 | internalOperationResults: [InternalOperationResult!] 791 | } 792 | 793 | type OriginationOperationResult implements OperationResult { 794 | status: OperationResultStatus! 795 | consumedGas: BigNumber 796 | errors: [OperationError!] 797 | balanceUpdates: [BalanceUpdate!] 798 | originatedContracts: [Address!] 799 | storageSize: BigNumber 800 | paidStorageSizeDiff: BigNumber 801 | } 802 | 803 | type Proposals implements OperationContents { 804 | # kind is always 'proposals' 805 | kind: OperationKind! 806 | source: Address! 807 | period: Int! 808 | proposals: [ProtocolHash!]! 809 | metadata: JSON 810 | operation: OperationEntryInfo! 811 | } 812 | 813 | type Ballot implements OperationContents { 814 | # kind is always 'ballot' 815 | kind: OperationKind! 816 | source: Address! 817 | period: Int! 818 | proposal: ProtocolHash! 819 | ballot: BallotVote! 820 | metadata: JSON 821 | operation: OperationEntryInfo! 822 | } 823 | 824 | enum BallotVote { 825 | NAY 826 | PASS 827 | YAY 828 | } 829 | 830 | type BalanceUpdate { 831 | kind: BalanceUpdateKind! 832 | category: BalanceUpdateCategory 833 | contract: Address 834 | delegate: Address 835 | cycle: Int 836 | change: BigNumber! 837 | } 838 | 839 | type InternalOperationResult { 840 | kind: InternalOperationKind! 841 | source: Address! 842 | nonce: Int! 843 | amount: Mutez 844 | destination: Address 845 | parameters: TransactionOperationParameter 846 | publicKey: PublicKey 847 | balance: Mutez 848 | delegate: Address 849 | script: ScriptedContracts 850 | result: OperationResult! 851 | } 852 | 853 | enum InternalOperationKind { 854 | DELEGATION 855 | ORIGINATION 856 | REVEAL 857 | TRANSACTION 858 | } 859 | 860 | type TransactionOperationParameter { 861 | entrypoint: String! 862 | value: MichelsonExpression! 863 | } 864 | 865 | interface OperationResult { 866 | status: OperationResultStatus! 867 | consumedGas: BigNumber 868 | errors: [OperationError!] 869 | } 870 | 871 | enum OperationResultStatus { 872 | APPLIED 873 | BACKTRACKED 874 | FAILED 875 | SKIPPED 876 | } 877 | 878 | type OperationError { 879 | kind: String! 880 | id: String! 881 | } 882 | 883 | enum TestChainStatusType { 884 | NOT_RUNNING 885 | FORKING 886 | RUNNING 887 | } 888 | 889 | enum VotingPeriodKind { 890 | PROPOSAL 891 | TESTING_VOTE 892 | TESTING 893 | PROMOTION_VOTE 894 | } 895 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, ApolloServerExpressConfig, AuthenticationError } from 'apollo-server-express'; 3 | import depthLimit from 'graphql-depth-limit'; 4 | import { createServer } from 'http'; 5 | import compression from 'compression'; 6 | import cors from 'cors'; 7 | 8 | import dotenv from 'dotenv'; 9 | import 'reflect-metadata'; 10 | import { ExpressContext, ServerRegistration } from 'apollo-server-express/dist/ApolloServer'; 11 | 12 | dotenv.config(); 13 | // TODO Check here that we have all mandatory configs in place from ENV 14 | 15 | // NOTE: this is here for a purpose (we need to call dotenv first) 16 | import schema from './schema'; 17 | import { container } from 'tsyringe'; 18 | import { TezosService } from './services/tezos-service'; 19 | 20 | const app = express(); 21 | const config: ApolloServerExpressConfig = { 22 | schema, 23 | validationRules: [depthLimit(7)], 24 | }; 25 | 26 | if (process.env.GRAPHQL_ENABLE_PLAYGROUND === 'true') { 27 | config.playground = true; 28 | } 29 | 30 | if (process.env.GRAPHQL_ENABLE_INTROSPECTION === 'true') { 31 | config.introspection = true; 32 | } 33 | 34 | if (process.env.ENABLE_API_KEY === 'true' && process.env.API_KEY) { 35 | config.context = (ctx: ExpressContext) => { 36 | if (ctx.req.header('X-TaaS-Key') !== process.env.API_KEY) { 37 | throw new AuthenticationError('Provided X-TaaS-Key header value is wrong or empty!'); 38 | } 39 | }; 40 | } 41 | 42 | const middlewareConfig = { app, path: '/graphql' } as ServerRegistration; 43 | if (process.env.ENABLE_TEZOS_NODE_HEALTHCHECK === 'true') { 44 | const tezosService = container.resolve(TezosService) as TezosService; 45 | middlewareConfig.onHealthCheck = () => { 46 | return tezosService.client.getConstants(); 47 | }; 48 | } 49 | 50 | const server = new ApolloServer(config); 51 | app.use('*', cors()); 52 | app.use(compression()); 53 | server.applyMiddleware(middlewareConfig); 54 | 55 | const httpServer = createServer(app); 56 | httpServer.listen({ host: process.env.HOST, port: process.env.PORT }, (): void => 57 | console.log(`\n🚀 GraphQL is now running on http://${process.env.HOST}:${process.env.PORT}/graphql`) 58 | ); 59 | -------------------------------------------------------------------------------- /src/services/tezos-service.ts: -------------------------------------------------------------------------------- 1 | import { RpcClient } from '@taquito/rpc'; 2 | import { singleton } from 'tsyringe'; 3 | import { TezosToolkit, Tezos } from '@taquito/taquito'; 4 | import axios, { AxiosInstance } from 'axios'; 5 | @singleton() 6 | export class TezosService { 7 | static async handleNotFound(run: () => Promise): Promise { 8 | try { 9 | return await run(); 10 | } catch (e) { 11 | if (e.status === 404) { 12 | return null; 13 | } 14 | throw e; 15 | } 16 | } 17 | client: RpcClient; 18 | toolkit: TezosToolkit; 19 | axios: AxiosInstance; 20 | constructor() { 21 | const provider = process.env.TEZOS_NODE; 22 | this.client = new RpcClient(provider); 23 | Tezos.setRpcProvider(provider); 24 | this.toolkit = Tezos; 25 | this.axios = axios.create({ baseURL: provider }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/types/types.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | export interface Block { 4 | protocol: string; 5 | chainId: string; 6 | hash: string; 7 | header: BlockHeader; 8 | metadata: BlockMetadata; 9 | operations: OperationEntry[][]; 10 | } 11 | 12 | export interface BlockHeader { 13 | level: number; 14 | proto: number; 15 | predecessor: string; 16 | timestamp: string; 17 | validationPass: number; 18 | operationsHash: string; 19 | fitness: string[]; 20 | context: string; 21 | priority: number; 22 | proofOfWorkNonce: string; 23 | seedNonceHash?: string; 24 | signature: string; 25 | } 26 | 27 | export interface BlockMetadata { 28 | protocol: string; 29 | nextProtocol: string; 30 | testChainStatus: TestChainStatus; 31 | maxOperationsTtl: number; 32 | maxOperationDataLength: number; 33 | maxBlockHeaderLength: number; 34 | maxOperationListLength: MaxOperationListLength[]; 35 | baker: string; 36 | level: Level; 37 | votingPeriodKind: string; 38 | nonceHash?: string; 39 | consumedGas: string; 40 | deactivated: string[]; 41 | balanceUpdates: BalanceUpdate[]; 42 | } 43 | 44 | export interface Level { 45 | level: number; 46 | levelPosition: number; 47 | cycle: number; 48 | cyclePosition: number; 49 | votingPeriod: number; 50 | votingPeriodPosition: number; 51 | expectedCommitment: boolean; 52 | } 53 | 54 | export interface TestChainStatus { 55 | status: string; 56 | } 57 | 58 | export interface MaxOperationListLength { 59 | maxSize: number; 60 | maxOp?: number; 61 | } 62 | 63 | export enum BalanceUpdateKind { 64 | CONTRACT = 'contract', 65 | FREEZER = 'freezer', 66 | } 67 | 68 | export type BalanceUpdateCategory = 'rewards' | 'fees' | 'deposits'; 69 | 70 | export interface BakingRight { 71 | level: number; 72 | delegate: string; 73 | priority: number; 74 | estimatedTime?: Date; 75 | } 76 | 77 | export interface EndorsingRight { 78 | level: number; 79 | delegate: string; 80 | slots: number[]; 81 | estimatedTime?: Date; 82 | } 83 | 84 | export interface Delegate { 85 | balance: BigNumber; 86 | frozenBalance: BigNumber; 87 | frozenBalanceByCycle: FrozenBalanceByCycle[]; 88 | stakingBalance: BigNumber; 89 | delegatedContracts: string[]; 90 | delegatedBalance: BigNumber; 91 | deactivated: boolean; 92 | gracePeriod: number; 93 | blockHash: string; 94 | address: string; 95 | } 96 | 97 | interface FrozenBalanceByCycle { 98 | cycle: number; 99 | deposit: BigNumber; 100 | fees: BigNumber; 101 | rewards: BigNumber; 102 | } 103 | 104 | export interface Contract { 105 | address: string; 106 | blockHash: string; 107 | balance: BigNumber; 108 | script?: ScriptedContracts; 109 | counter?: string; 110 | } 111 | 112 | export interface Constants { 113 | proofOfWorkNonceSize: number; 114 | nonceLength: number; 115 | maxRevelationsPerBlock: number; 116 | maxOperationDataLength: number; 117 | preservedCycles: number; 118 | blocksPerCycle: number; 119 | blocksPerCommitment: number; 120 | blocksPerRollSnapshot: number; 121 | blocksPerVotingPeriod: number; 122 | timeBetweenBlocks: BigNumber[]; 123 | endorsersPerBlock: number; 124 | hardGasLimitPerOperation: BigNumber; 125 | hardGasLimitPerBlock: BigNumber; 126 | proofOfWorkThreshold: BigNumber; 127 | tokensPerRoll: BigNumber; 128 | michelsonMaximumTypeSize: number; 129 | seedNonceRevelationTip: string; 130 | originationBurn: string; 131 | blockSecurityDeposit: BigNumber; 132 | endorsementSecurityDeposit: BigNumber; 133 | blockReward?: BigNumber; 134 | endorsementReward?: BigNumber | [BigNumber]; 135 | costPerByte: BigNumber; 136 | hardStorageLimitPerOperation: BigNumber; 137 | minProposalQuorum?: number; 138 | quorumMax?: number; 139 | quorumMin?: number; 140 | delayPerMissingEndorsement?: number; 141 | initialEndorsers?: string[]; 142 | bakingRewardPerEndorsement?: [BigNumber]; 143 | } 144 | 145 | export interface ManagerKey { 146 | key: string; 147 | invalid?: boolean; 148 | } 149 | 150 | export type Entrypoints = { 151 | entrypoints: { [key: string]: Object }; 152 | unreachable?: { path: EntrypointPath[] }; 153 | }; 154 | 155 | export enum EntrypointPath { 156 | Left = 'Left', 157 | Right = 'Right', 158 | } 159 | 160 | export interface ScriptedContracts { 161 | code: MichelsonExpression[]; 162 | storage: MichelsonExpression; 163 | } 164 | 165 | export type MichelsonExpression = MichelsonExpressionBase | MichelsonExpressionExtended; 166 | 167 | export interface MichelsonExpressionBase { 168 | int?: string; 169 | string?: string; 170 | bytes?: string; 171 | } 172 | 173 | export interface MichelsonExpressionExtended { 174 | prim: string; 175 | args?: MichelsonExpression[]; 176 | annots?: string[]; 177 | } 178 | 179 | export interface OperationEntry { 180 | protocol: string; 181 | chainId: string; 182 | hash: string; 183 | branch: string; 184 | contents: OperationContents[]; 185 | signature?: string; 186 | } 187 | 188 | export interface OperationContents { 189 | kind: string; 190 | operation: OperationEntry; 191 | } 192 | 193 | export interface Endorsement extends OperationContents { 194 | kind: 'endorsement'; 195 | level: number; 196 | metadata: EndorsementMetadata; 197 | } 198 | 199 | export interface EndorsementMetadata { 200 | balanceUpdates: BalanceUpdate[]; 201 | delegate: string; 202 | slots: number[]; 203 | } 204 | 205 | export interface SeedNonceRevelation extends OperationContents { 206 | kind: 'seedNonceRevelation'; 207 | level: number; 208 | nonce: string; 209 | metadata: OperationContentMetadata; 210 | } 211 | 212 | export interface DoubleEndorsementEvidence extends OperationContents { 213 | kind: 'doubleEndorsementEvidence'; 214 | op1: InlinedEndorsement; 215 | op2: InlinedEndorsement; 216 | metadata: OperationContentMetadata; 217 | } 218 | 219 | export interface InlinedEndorsement { 220 | branch: string; 221 | operations: InlinedEndorsementContents; 222 | signature?: string; 223 | } 224 | 225 | export interface InlinedEndorsementContents { 226 | kind: InlinedEndorsementKindEnum; 227 | level: number; 228 | } 229 | 230 | export type InlinedEndorsementKindEnum = 'endorsement'; 231 | 232 | export interface OperationContentMetadata { 233 | balanceUpdates: BalanceUpdate[]; 234 | } 235 | 236 | export interface DoubleBakingEvidence extends OperationContents { 237 | kind: 'doubleBakingEvidence'; 238 | bh1: BlockHeader; 239 | bh2: BlockHeader; 240 | metadata: OperationContentMetadata; 241 | } 242 | 243 | export interface ActivateAccount extends OperationContents { 244 | kind: 'activateAccount'; 245 | pkh: string; 246 | secret: string; 247 | metadata: OperationContentMetadata; 248 | } 249 | 250 | export interface Reveal extends OperationContents { 251 | kind: 'reveal'; 252 | source: string; 253 | fee: string; 254 | counter: string; 255 | gasLimit: string; 256 | storageLimit: string; 257 | publicKey: string; 258 | metadata: RevealMetadata; 259 | } 260 | 261 | export interface RevealMetadata { 262 | balanceUpdates: BalanceUpdate[]; 263 | operationResult: RevealOperationResult; 264 | internalOperationResults?: InternalOperationResult[]; 265 | } 266 | 267 | export interface RevealOperationResult extends OperationResult {} 268 | 269 | export interface Transaction extends OperationContents { 270 | kind: 'transaction'; 271 | source: string; 272 | fee: string; 273 | counter: string; 274 | gasLimit: string; 275 | storageLimit: string; 276 | amount: string; 277 | destination: string; 278 | parameters?: MichelsonExpression; 279 | metadata: TransactionMetadata; 280 | } 281 | 282 | export interface TransactionMetadata { 283 | balanceUpdates: BalanceUpdate[]; 284 | operationResult: TransactionOperationResult; 285 | internalOperationResults?: InternalOperationResult[]; 286 | } 287 | 288 | export interface TransactionOperationResult extends OperationResult { 289 | storage?: MichelsonExpression; 290 | bigMapDiff?: BigMapDiffItem[]; 291 | balanceUpdates?: BalanceUpdate[]; 292 | originatedContracts?: string[]; 293 | storageSize?: string; 294 | paidStorageSizeDiff?: string; 295 | allocatedDestinationContract?: boolean; 296 | } 297 | 298 | export interface BigMapDiffItem { 299 | keyHash: string; 300 | key: MichelsonExpression; 301 | value?: MichelsonExpression; 302 | } 303 | 304 | export interface Delegation extends OperationContents { 305 | kind: 'delegation'; 306 | source: string; 307 | fee: string; 308 | counter: string; 309 | gasLimit: string; 310 | storageLimit: string; 311 | delegate?: string; 312 | metadata: DelegationMetadata; 313 | } 314 | 315 | export interface DelegationMetadata { 316 | balanceUpdates: BalanceUpdate[]; 317 | operationResult: DelegationOperationResult; 318 | internalOperationResults?: InternalOperationResult[]; 319 | } 320 | 321 | export interface DelegationOperationResult extends OperationResult {} 322 | 323 | export interface Origination extends OperationContents { 324 | kind: 'origination'; 325 | source: string; 326 | fee: string; 327 | counter: string; 328 | gasLimit: string; 329 | storageLimit: string; 330 | balance: string; 331 | delegate?: string; 332 | script?: ScriptedContracts; 333 | metadata: OriginationMetadata; 334 | } 335 | 336 | export interface OriginationMetadata { 337 | balanceUpdates: BalanceUpdate[]; 338 | operationResult: OriginationOperationResult; 339 | internalOperationResults?: InternalOperationResult[]; 340 | } 341 | 342 | export interface OriginationOperationResult extends OperationResult { 343 | balanceUpdates?: BalanceUpdate[]; 344 | originatedContracts?: string[]; 345 | storageSize?: string; 346 | paidStorageSizeDiff?: string; 347 | } 348 | 349 | export interface Proposals extends OperationContents { 350 | kind: 'proposals'; 351 | source: string; 352 | period: number; 353 | proposals: string[]; 354 | metadata: any; 355 | } 356 | 357 | export interface Ballot extends OperationContents { 358 | kind: 'ballot'; 359 | source: string; 360 | period: number; 361 | proposal: string; 362 | ballot: BallotVote; 363 | metadata: any; 364 | } 365 | 366 | export type BallotVote = 'nay' | 'yay' | 'pass'; 367 | 368 | export interface BalanceUpdate { 369 | kind: BalanceUpdateKind; 370 | category?: BalanceUpdateCategory; 371 | contract?: string; 372 | delegate?: string; 373 | cycle?: number; 374 | change: string; 375 | } 376 | 377 | export interface InternalOperationResult { 378 | kind: InternalOperationKind; 379 | source: string; 380 | nonce: number; 381 | amount?: string; 382 | destination?: string; 383 | parameters?: TransactionOperationParameter; 384 | publicKey?: string; 385 | balance?: string; 386 | delegate?: string; 387 | script?: ScriptedContracts; 388 | result: OperationResult; 389 | } 390 | 391 | export type InternalOperationKind = 'reveal' | 'transaction' | 'origination' | 'delegation'; 392 | 393 | export interface TransactionOperationParameter { 394 | entrypoint: string; 395 | value: MichelsonExpression; 396 | } 397 | 398 | export interface OperationResult { 399 | status: OperationResultStatus; 400 | consumedGas?: string; 401 | errors?: OperationError[]; 402 | } 403 | 404 | export type OperationResultStatus = 'applied' | 'failed' | 'skipped' | 'backtracked'; 405 | 406 | export interface OperationError { 407 | kind: string; 408 | id: string; 409 | } 410 | 411 | export type BigMapKeyType = 'string' | 'nat' | 'int' | 'bytes' | 'bool' | 'mutez' | 'address' | 'key_hash'; 412 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 7 | "lib": ["dom", "es6"] /* Specify library files to be included in the compilation. */, 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "dist" /* Redirect output structure to the directory. */, 16 | "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | "removeComments": true /* Do not emit comments to output. */, 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, 61 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */, 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": ["scripts"] 67 | } 68 | --------------------------------------------------------------------------------